#!/usr/bin/env python3 from __future__ import annotations import argparse import os import sys from pathlib import Path REPO_ROOT = Path(__file__).resolve().parents[3] if str(REPO_ROOT) not in sys.path: sys.path.insert(0, str(REPO_ROOT)) from deployments.offline.runtime.common import ( DeployError, load_env_file, log, render_template, require_commands, require_env, run, shell_quote_env_value, sudo_write_text_atomic, write_text_atomic, ) def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser() parser.add_argument("--downstream-root") parser.add_argument("--run-now", action="store_true") return parser.parse_args() def deployment_root() -> Path: raw = os.environ.get("DEPLOYMENT_ROOT") if raw: return Path(raw).expanduser().resolve() return (Path.home() / "nexuscrm-deployment").resolve() def prod_env_root() -> Path: return deployment_root() / "online" / "envs" / "prod" def guard_root() -> Path: return prod_env_root() / "guard" def default_downstream_root() -> Path: return guard_root() / "repos" / "nexuscrm-pingcap-pack" def guard_env_file() -> Path: return guard_root() / "guard.env" def update_env_file(path: Path, updates: dict[str, str]) -> None: path.parent.mkdir(parents=True, exist_ok=True) lines = path.read_text().splitlines() if path.is_file() else [] seen: set[str] = set() out: list[str] = [] for line in lines: if not line or line.startswith("#") or "=" not in line: out.append(line) continue key, _ = line.split("=", 1) if key in updates: out.append(f"{key}={shell_quote_env_value(updates[key])}") seen.add(key) else: out.append(line) for key, value in updates.items(): if key not in seen: out.append(f"{key}={shell_quote_env_value(value)}") write_text_atomic(path, "\n".join(out) + "\n") def materialize_guard_env() -> Path: path = guard_env_file() updates = { key: value for key in [ "FEISHU_APP_ID", "FEISHU_APP_SECRET", "FEISHU_CHAT_ID", "FEISHU_GUARD_PROD_HEADER_TEMPLATE", "FEISHU_GUARD_DEFAULT_HEADER_TEMPLATE", "PROD_PUBLIC_URL", "PROD_HEALTHCHECK_HOST", "PROD_BACKEND_SERVICE", "PROD_RUNTIME_ENV_FILE", "PROD_NGINX_CONF", "PROD_NGINX_ACCESS_LOG", "PROD_BACKEND_STDERR_LOG", ] if (value := os.environ.get(key, "").strip()) } if updates: update_env_file(path, updates) if not path.is_file(): raise DeployError(f"online guard env was not materialized: {path}") require_env(load_env_file(path), ["FEISHU_APP_ID", "FEISHU_APP_SECRET", "FEISHU_CHAT_ID"]) return path def install_guard_units(framework_dir: Path, downstream_root: Path) -> None: replacements = { "__SYSTEMD_USER__": os.environ.get("USER", "ubuntu"), "__DOWNSTREAM_ROOT__": str(downstream_root), "__GUARD_ROOT__": str(guard_root()), } unit_specs = [ ( framework_dir / "systemd/nexuscrm-online-host-guard.service.template", Path("/etc/systemd/system/nexuscrm-online-host-guard.service"), ), ( framework_dir / "systemd/nexuscrm-online-host-guard.timer.template", Path("/etc/systemd/system/nexuscrm-online-host-guard.timer"), ), ] for template_path, target_path in unit_specs: rendered = render_template(template_path, replacements) sudo_write_text_atomic(target_path, rendered) run(["sudo", "systemctl", "daemon-reload"]) run(["sudo", "systemctl", "enable", "--now", "nexuscrm-online-host-guard.timer"]) def run_guard_now() -> None: run(["sudo", "systemctl", "start", "nexuscrm-online-host-guard.service"]) run(["sudo", "systemctl", "status", "--no-pager", "nexuscrm-online-host-guard.service"], allow_exit_codes={0, 3}) def main() -> int: args = parse_args() try: require_commands("systemctl") framework_dir = Path(__file__).resolve().parents[1] downstream_root = Path(args.downstream_root).expanduser().resolve() if args.downstream_root else default_downstream_root() if not downstream_root.is_dir(): raise DeployError(f"downstream root not found: {downstream_root}") env_path = materialize_guard_env() install_guard_units(framework_dir, downstream_root) if args.run_now: run_guard_now() log(f"online guard env refreshed: {env_path}") log(f"online guard repo root: {downstream_root}") return 0 except DeployError as exc: log(f"failed: {exc}") return 1 if __name__ == "__main__": raise SystemExit(main())