144 lines
4.8 KiB
Python
144 lines
4.8 KiB
Python
from __future__ import annotations
|
|
|
|
import os
|
|
import sys
|
|
import subprocess
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
|
|
from SYS.config import load_config
|
|
from SYS.logger import debug, log
|
|
|
|
_zt_server_proc: Optional[subprocess.Popen] = None
|
|
_zt_server_last_config: Optional[str] = None
|
|
|
|
# We no longer use atexit here because explicit lifecycle management
|
|
# is preferred in TUI/REPL, and background servers use a monitor thread
|
|
# to shut down when the parent dies.
|
|
# atexit.register(lambda: stop_zerotier_server())
|
|
|
|
def ensure_zerotier_server_running() -> None:
|
|
"""Check config and ensure the ZeroTier storage server is running if needed."""
|
|
global _zt_server_proc, _zt_server_last_config
|
|
|
|
try:
|
|
# Load config from the project root (where config.conf typically lives)
|
|
repo_root = Path(__file__).resolve().parent.parent
|
|
cfg = load_config(repo_root)
|
|
except Exception:
|
|
return
|
|
|
|
zt_conf = cfg.get("networking", {}).get("zerotier", {})
|
|
serve_target = zt_conf.get("serve")
|
|
port = zt_conf.get("port") or 999
|
|
api_key = zt_conf.get("api_key")
|
|
|
|
# Config hash to detect changes
|
|
config_id = f"{serve_target}|{port}|{api_key}"
|
|
|
|
# Check if proc is still alive
|
|
if _zt_server_proc:
|
|
if _zt_server_proc.poll() is not None:
|
|
# Process died
|
|
debug("ZeroTier background server died. Restarting...")
|
|
_zt_server_proc = None
|
|
elif config_id == _zt_server_last_config:
|
|
# Already running with correct config
|
|
return
|
|
|
|
# If config changed and we have a proc, stop it
|
|
if _zt_server_proc and config_id != _zt_server_last_config:
|
|
debug("ZeroTier server config changed. Stopping old process...")
|
|
try:
|
|
_zt_server_proc.terminate()
|
|
_zt_server_proc.wait(timeout=2)
|
|
except Exception:
|
|
try:
|
|
_zt_server_proc.kill()
|
|
except Exception:
|
|
pass
|
|
_zt_server_proc = None
|
|
|
|
_zt_server_last_config = config_id
|
|
|
|
if not serve_target:
|
|
return
|
|
|
|
# Resolve path
|
|
storage_path = None
|
|
folders = cfg.get("store", {}).get("folder", {})
|
|
for name, block in folders.items():
|
|
if name.lower() == serve_target.lower():
|
|
storage_path = block.get("path") or block.get("PATH")
|
|
break
|
|
|
|
if not storage_path:
|
|
# Fallback to direct path
|
|
storage_path = serve_target
|
|
|
|
if not storage_path or not Path(storage_path).exists():
|
|
debug(f"ZeroTier host target '{serve_target}' not found at {storage_path}. Cannot start server.")
|
|
return
|
|
|
|
repo_root = Path(__file__).resolve().parent.parent
|
|
server_script = repo_root / "scripts" / "remote_storage_server.py"
|
|
|
|
if not server_script.exists():
|
|
debug(f"ZeroTier server script not found at {server_script}")
|
|
return
|
|
|
|
# Use the same python executable that is currently running
|
|
# On Windows, explicitly prefer the .venv python if it exists
|
|
python_exe = sys.executable
|
|
if sys.platform == "win32":
|
|
venv_py = repo_root / ".venv" / "Scripts" / "python.exe"
|
|
if venv_py.exists():
|
|
python_exe = str(venv_py)
|
|
|
|
cmd = [python_exe, str(server_script),
|
|
"--storage-path", str(storage_path),
|
|
"--port", str(port),
|
|
"--monitor"]
|
|
cmd += ["--parent-pid", str(os.getpid())]
|
|
if api_key:
|
|
cmd += ["--api-key", str(api_key)]
|
|
|
|
try:
|
|
debug(f"Starting ZeroTier storage server: {cmd}")
|
|
# Capture errors to a log file instead of DEVNULL
|
|
log_file = repo_root / "zt_server_error.log"
|
|
with open(log_file, "a") as f:
|
|
f.write(f"\n--- Starting server at {__import__('datetime').datetime.now()} ---\n")
|
|
f.write(f"Command: {' '.join(cmd)}\n")
|
|
f.write(f"CWD: {repo_root}\n")
|
|
f.write(f"Python: {python_exe}\n")
|
|
|
|
err_f = open(log_file, "a")
|
|
# On Windows, CREATE_NO_WINDOW = 0x08000000 ensures no console pops up
|
|
import subprocess
|
|
_zt_server_proc = subprocess.Popen(
|
|
cmd,
|
|
stdout=subprocess.DEVNULL,
|
|
stderr=err_f,
|
|
cwd=str(repo_root),
|
|
creationflags=0x08000000 if sys.platform == "win32" else 0
|
|
)
|
|
log(f"ZeroTier background server started on port {port} (sharing {serve_target})")
|
|
except Exception as e:
|
|
debug(f"Failed to start ZeroTier server: {e}")
|
|
_zt_server_proc = None
|
|
|
|
def stop_zerotier_server() -> None:
|
|
"""Stop the background server if it is running."""
|
|
global _zt_server_proc
|
|
if _zt_server_proc:
|
|
try:
|
|
_zt_server_proc.terminate()
|
|
_zt_server_proc.wait(timeout=2)
|
|
except Exception:
|
|
try:
|
|
_zt_server_proc.kill()
|
|
except Exception:
|
|
pass
|
|
_zt_server_proc = None
|