Files
ai-novel-engine/scripts/setup_mascarade_launchd.py
2026-04-06 11:49:28 +02:00

141 lines
4.6 KiB
Python

#!/usr/bin/env python3
from __future__ import annotations
import argparse
import os
from pathlib import Path
import plistlib
import subprocess
import sys
REPO_ROOT = Path(__file__).resolve().parents[1]
if str(REPO_ROOT) not in sys.path:
sys.path.insert(0, str(REPO_ROOT))
from core.runtime.remote_hosts import RemoteHostConfig, read_remote_hosts
HostConfig = RemoteHostConfig
def _read_hosts(config_path: Path) -> list[HostConfig]:
return read_remote_hosts(config_path, stderr=sys.stderr)
def _plist_payload(host: HostConfig) -> dict:
return {
"Label": host.label,
"ProgramArguments": [
"/usr/bin/ssh",
"-N",
"-o",
"ExitOnForwardFailure=yes",
"-o",
"ServerAliveInterval=30",
"-o",
"ServerAliveCountMax=3",
"-L",
f"{host.local_bind_host}:{host.local_tunnel_port}:127.0.0.1:{host.remote_core_port}",
host.ssh_target,
],
"RunAtLoad": True,
"KeepAlive": {"SuccessfulExit": False},
"ProcessType": "Background",
}
def _write_plists(hosts: list[HostConfig], launch_agents_dir: Path, dry_run: bool) -> list[Path]:
paths: list[Path] = []
for host in hosts:
plist_path = launch_agents_dir / host.plist_name
paths.append(plist_path)
if dry_run:
continue
payload = _plist_payload(host)
plist_path.parent.mkdir(parents=True, exist_ok=True)
plist_path.write_bytes(plistlib.dumps(payload, fmt=plistlib.FMT_XML))
return paths
def _run_launchctl(command: list[str], dry_run: bool) -> int:
if dry_run:
return 0
result = subprocess.run(command, check=False, capture_output=True, text=True)
if result.returncode != 0:
print(f"launchctl failed ({result.returncode}): {' '.join(command)}", file=sys.stderr)
stderr = (result.stderr or "").strip()
if stderr:
print(stderr, file=sys.stderr)
return result.returncode
def install(hosts: list[HostConfig], launch_agents_dir: Path, dry_run: bool) -> int:
paths = _write_plists(hosts, launch_agents_dir, dry_run)
failed = False
for plist_path in paths:
print(f"plist: {plist_path}")
_run_launchctl(["launchctl", "unload", str(plist_path)], dry_run)
if _run_launchctl(["launchctl", "load", str(plist_path)], dry_run) != 0:
failed = True
if dry_run:
print("dry-run: aucun fichier ecrit et aucun launchctl execute")
return 1 if failed else 0
def uninstall(hosts: list[HostConfig], launch_agents_dir: Path, dry_run: bool) -> int:
for host in hosts:
plist_path = launch_agents_dir / host.plist_name
print(f"remove: {plist_path}")
_run_launchctl(["launchctl", "unload", str(plist_path)], dry_run)
if not dry_run and plist_path.exists():
plist_path.unlink()
return 0
def status(hosts: list[HostConfig]) -> int:
uid = os.getuid()
failed = False
for host in hosts:
print(host.label)
result = subprocess.run(["launchctl", "print", f"gui/{uid}/{host.label}"], check=False)
if result.returncode != 0:
failed = True
return 1 if failed else 0
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(prog="python3 scripts/setup_mascarade_launchd.py")
parser.add_argument("command", choices=["install", "uninstall", "render", "status"], help="Action a executer")
parser.add_argument("--config", default="automation/mascarade_hosts.toml")
parser.add_argument("--launch-agents-dir", default=str(Path.home() / "Library/LaunchAgents"))
parser.add_argument("--dry-run", action="store_true")
return parser
def main(argv: list[str] | None = None) -> int:
args = build_parser().parse_args(argv)
config_path = Path(args.config)
launch_agents_dir = Path(args.launch_agents_dir).expanduser()
hosts = _read_hosts(config_path)
if not hosts:
print("Aucun host valide trouve dans la config.")
return 1
if args.command == "install":
return install(hosts, launch_agents_dir, args.dry_run)
if args.command == "uninstall":
return uninstall(hosts, launch_agents_dir, args.dry_run)
if args.command == "status":
return status(hosts)
# render
paths = _write_plists(hosts, launch_agents_dir, dry_run=True)
for host, plist_path in zip(hosts, paths):
print(f"[{host.name}] {plist_path}")
print(plistlib.dumps(_plist_payload(host), fmt=plistlib.FMT_XML).decode("utf-8"))
return 0
if __name__ == "__main__":
raise SystemExit(main())