"""First deployment / explicit port cutover. Only manages this application's resources.""" import argparse import json import os from pathlib import Path import shutil import socket import subprocess import sys ROOT = Path("/adamwen-work-space/xas-test-library") NAME = "adamwen-script-console" IMAGE = "adamwen/script-console:20261020-1" UNIT = "adamwen-script-worker.service" def validate_runtime(root): for path in (root / "runtime", *(root / "runtime" / name for name in ("console-data", "library", "runs", "native-ipc"))): if not path.resolve().is_relative_to(root.resolve()): raise ValueError("Runtime must remain inside this project") if any(part.is_symlink() for part in (path, *path.parents)): raise ValueError("Runtime symlink rejected") def command(*args, timeout=30): return subprocess.check_output(args, text=True, timeout=timeout).strip() def web_arguments(port): validate_runtime(ROOT) runtime = ROOT / "runtime" args = ["docker", "run", "-d", "--name", NAME, "--label", "xas.application=script-console", "--runtime", "runc", "--network", "host", "--user", "0:0", "--cap-drop", "ALL", "--security-opt", "no-new-privileges", "--read-only", "--memory", "512m", "--cpus", "1", "--pids-limit", "96", "--tmpfs", "/tmp:rw,nosuid,nodev,noexec,size=67108864", "--log-driver", "local", "--log-opt", "max-size=10m", "--log-opt", "max-file=2", "--env", "XAS_CONSOLE_ROOT=" + str(runtime)] for name in ("console-data", "library", "runs", "native-ipc"): path = runtime / name args += ["--mount", f"type=bind,src={path},dst={path}" + (",readonly" if name in {"runs", "native-ipc"} else "")] sources = json.loads((ROOT / "catalog/external-sources.json").read_text())["sources"] for source in sources: path = Path(source["path"]) if path.is_dir(): args += ["--mount", f"type=bind,src={path},dst={path},readonly"] if path.name == "scripts": for folder in ("logs", "results"): logs = path.parent / folder if logs.is_dir(): args += ["--mount", f"type=bind,src={logs},dst={logs},readonly"] return args + [IMAGE, "--port", str(port)] def prepare(): if os.geteuid() != 0 or ROOT.resolve() != ROOT or not (ROOT / "console/worker.py").is_file(): raise RuntimeError("Run through the documented sudo flow in the confirmed project") validate_runtime(ROOT) for name in ("console-data", "library", "runs", "native-ipc"): path = ROOT / "runtime" / name if path.is_symlink(): raise RuntimeError("Runtime symlink rejected") path.mkdir(mode=0o750, exist_ok=True) runner = command("docker", "image", "inspect", "--format", "{{.Id}}", "adamwen/test-console:20261013-2") config = {"project": str(ROOT), "runner_image": runner, "smoke_upstream": "http://100.64.0.55:18100", "smoke_credential": "/dengyuyun-joy-work-space/workpc-claude-code/20261010-dsv41-trial/configs/vllm.env"} path = ROOT / "deploy/console-worker.json" if path.exists() and json.loads(path.read_text()) != config: raise RuntimeError("Existing worker configuration differs; review before replacing") path.write_text(json.dumps(config, indent=2)) path.chmod(0o600) # Existing roots are 0700/root. Container root has no capabilities and read-only mounts. # Runtime code used by non-root jobs needs read access, without changing original scripts. for p in (ROOT / "console").rglob("*"): p.chmod(0o755 if p.is_dir() else 0o644) (ROOT / "console").chmod(0o755) def start(port): existing = command("docker", "ps", "-aq", "--filter", "name=^/" + NAME + "$") if existing: raise RuntimeError("Console container already exists; inspect before replacement") with socket.socket() as probe: probe.bind(("10.88.118.188", port)) print(command(*web_arguments(port))) service = Path("/etc/systemd/system") / UNIT content = (ROOT / "deploy/console-worker.service").read_bytes() if service.exists() and service.read_bytes() != content: raise RuntimeError("Existing systemd service differs") service.write_bytes(content) command("systemctl", "daemon-reload") # Start only; no automatic boot scheduling is enabled by this deployment. command("systemctl", "start", UNIT) print(command("systemctl", "is-active", UNIT)) if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("--port", type=int, choices=[14440, 14442], default=14442) options = parser.parse_args() prepare() start(options.port)