"""Worker-agent uvicorn launcher. Starts ``decnet.agent.app:app`` over HTTPS with mTLS enforcement. The worker must already have a bundle in ``~/.decnet/agent/`` (delivered by ``decnet swarm enroll`` from the master); if it does not, we refuse to start — unauthenticated agents are not a supported mode. """ from __future__ import annotations import os import pathlib import signal import subprocess # nosec B404 import sys from decnet.logging import get_logger from decnet.swarm import pki log = get_logger("agent.server") def run(host: str, port: int, agent_dir: pathlib.Path = pki.DEFAULT_AGENT_DIR) -> int: bundle = pki.load_worker_bundle(agent_dir) if bundle is None: print( f"[agent] No cert bundle at {agent_dir}. " f"Run `decnet swarm enroll` from the master first.", file=sys.stderr, ) return 2 keyfile = agent_dir / "worker.key" certfile = agent_dir / "worker.crt" cafile = agent_dir / "ca.crt" cmd = [ sys.executable, "-m", "uvicorn", "decnet.agent.app:app", "--host", host, "--port", str(port), "--ssl-keyfile", str(keyfile), "--ssl-certfile", str(certfile), "--ssl-ca-certs", str(cafile), # 2 == ssl.CERT_REQUIRED — clients MUST present a CA-signed cert. "--ssl-cert-reqs", "2", ] log.info("agent starting host=%s port=%d bundle=%s", host, port, agent_dir) # Own process group for clean Ctrl+C / SIGTERM propagation to uvicorn # workers (same pattern as `decnet api`). proc = subprocess.Popen(cmd, start_new_session=True) # nosec B603 try: return proc.wait() except KeyboardInterrupt: try: os.killpg(proc.pid, signal.SIGTERM) try: return proc.wait(timeout=10) except subprocess.TimeoutExpired: os.killpg(proc.pid, signal.SIGKILL) return proc.wait() except ProcessLookupError: return 0