This commit is contained in:
2026-01-22 00:22:42 -08:00
parent 51593536af
commit 81f29c7025
3 changed files with 234 additions and 85 deletions

View File

@@ -217,6 +217,8 @@ def install_service_windows(
headless: bool = True,
detached: bool = True,
start_on: str = "logon",
pull: bool = False,
workspace_root: Optional[Path] = None,
) -> bool:
try:
schtasks = shutil.which("schtasks")
@@ -226,14 +228,27 @@ def install_service_windows(
)
return False
bat = repo_root / "run-client.bat"
# If we have a workspace root (Medios-Macina), we use it for the wrapper scripts
base_dir = workspace_root if workspace_root else repo_root
bat = base_dir / "run-client.bat"
python_exe = venv_py
this_script = Path(__file__).resolve()
if not bat.exists():
# Use escaped backslashes to avoid Python "invalid escape sequence" warnings
content = '@echo off\n"%~dp0\\.venv\\Scripts\\python.exe" "%~dp0hydrus_client.py" %*\n'
# Absolute paths ensure the service works regardless of where it starts
content = f'@echo off\n"{python_exe}" "{this_script}" %*\n'
bat.write_text(content, encoding="utf-8")
tr = str(bat)
sc = "ONLOGON" if start_on == "logon" else "ONSTART"
task_args = "--detached "
if headless: task_args += "--headless "
if pull: task_args += "--pull "
# Force the correct repo root for the service
task_args += f'--repo-root "{repo_root}" '
cmd = [
schtasks,
"/Create",
@@ -242,7 +257,7 @@ def install_service_windows(
"/TN",
service_name,
"/TR",
f'"{tr}"',
f'{tr} {task_args}',
"/RL",
"LIMITED",
"/F",
@@ -283,7 +298,9 @@ def install_service_systemd(
repo_root: Path,
venv_py: Path,
headless: bool = True,
detached: bool = True
detached: bool = True,
pull: bool = False,
workspace_root: Optional[Path] = None,
) -> bool:
try:
systemctl = shutil.which("systemctl")
@@ -296,15 +313,22 @@ def install_service_systemd(
repo_root,
venv_py,
headless,
detached
detached,
pull=pull,
workspace_root=workspace_root
)
unit_dir = Path.home() / ".config" / "systemd" / "user"
unit_dir.mkdir(parents=True, exist_ok=True)
unit_file = unit_dir / f"{service_name}.service"
exec_args = f'"{venv_py}" "{str(repo_root / "run_client.py")}" --detached '
exec_args += "--headless " if headless else "--gui "
content = f"[Unit]\nDescription=Hydrus Client (user)\nAfter=network.target\n\n[Service]\nType=simple\nExecStart={exec_args}\nWorkingDirectory={str(repo_root)}\nRestart=on-failure\nEnvironment=PYTHONUNBUFFERED=1\n\n[Install]\nWantedBy=default.target\n"
this_script = Path(__file__).resolve()
exec_args = f'"{venv_py}" "{this_script}" --detached '
if headless: exec_args += "--headless "
if pull: exec_args += "--pull "
exec_args += f'--repo-root "{repo_root}" '
content = f"[Unit]\nDescription=Medios-Macina Client\nAfter=network.target\n\n[Service]\nType=simple\nExecStart={exec_args}\nWorkingDirectory={str(workspace_root if workspace_root else repo_root)}\nRestart=on-failure\nEnvironment=PYTHONUNBUFFERED=1\n\n[Install]\nWantedBy=default.target\n"
unit_file.write_text(content, encoding="utf-8")
subprocess.run([systemctl, "--user", "daemon-reload"], check=True)
subprocess.run(
@@ -356,14 +380,23 @@ def install_service_cron(
repo_root: Path,
venv_py: Path,
headless: bool = True,
detached: bool = True
detached: bool = True,
pull: bool = False,
workspace_root: Optional[Path] = None,
) -> bool:
try:
crontab = shutil.which("crontab")
if not crontab:
print("crontab not available; cannot install reboot cron job.")
return False
entry = f"@reboot {venv_py} {str(repo_root / 'run_client.py')} --detached {'--headless' if headless else '--gui'} # {service_name}\n"
this_script = Path(__file__).resolve()
exec_args = f'"{venv_py}" "{this_script}" --detached '
if headless: exec_args += "--headless "
if pull: exec_args += "--pull "
exec_args += f'--repo-root "{repo_root}" '
entry = f"@reboot {exec_args} # {service_name}\n"
proc = subprocess.run(
[crontab,
"-l"],
@@ -421,7 +454,9 @@ def install_service_auto(
repo_root: Path,
venv_py: Path,
headless: bool = True,
detached: bool = True
detached: bool = True,
pull: bool = False,
workspace_root: Optional[Path] = None,
) -> bool:
try:
if os.name == "nt":
@@ -430,7 +465,9 @@ def install_service_auto(
repo_root,
venv_py,
headless=headless,
detached=detached
detached=detached,
pull=pull,
workspace_root=workspace_root
)
else:
if shutil.which("systemctl"):
@@ -439,7 +476,9 @@ def install_service_auto(
repo_root,
venv_py,
headless=headless,
detached=detached
detached=detached,
pull=pull,
workspace_root=workspace_root
)
else:
return install_service_cron(
@@ -447,11 +486,16 @@ def install_service_auto(
repo_root,
venv_py,
headless=headless,
detached=detached
detached=detached,
pull=pull,
workspace_root=workspace_root
)
except Exception as exc:
print("install_service_auto error:", exc)
return False
except Exception as exc:
print("install_service_auto error:", exc)
return False
def uninstall_service_auto(service_name: str, repo_root: Path, venv_py: Path) -> bool:
@@ -602,6 +646,11 @@ def main(argv: Optional[List[str]] = None) -> int:
help=
"Attempt to launch the client without showing the Qt GUI (best-effort). Default for subsequent runs; first run will show GUI unless --headless is supplied",
)
p.add_argument(
"--pull",
action="store_true",
help="Run 'git pull' before starting the client",
)
p.add_argument(
"--gui",
action="store_true",
@@ -660,6 +709,23 @@ def main(argv: Optional[List[str]] = None) -> int:
else:
repo_root = workspace_root
# Handle git pull update if requested
if args.pull:
if shutil.which("git"):
if (repo_root / ".git").exists():
if not args.quiet:
print(f"Updating repository via 'git pull' in {repo_root}...")
try:
subprocess.run(["git", "pull"], cwd=str(repo_root), check=False)
except Exception as e:
print(f"Warning: git pull failed: {e}")
else:
if not args.quiet:
print("Skipping 'git pull': directory is not a git repository.")
else:
if not args.quiet:
print("Skipping 'git pull': 'git' not found on PATH.")
venv_py = find_venv_python(repo_root, args.venv, args.venv_name)
def _is_running_in_virtualenv() -> bool:
@@ -817,7 +883,9 @@ def main(argv: Optional[List[str]] = None) -> int:
repo_root,
venv_py,
headless=use_headless,
detached=True
detached=True,
pull=args.pull,
workspace_root=workspace_root
)
return 0 if ok else 6
if args.uninstall_service: