#!/usr/bin/env python3 """Reset server/public web/ + sync/ from source/, then patch_all --apply.""" from __future__ import annotations import argparse import shutil import subprocess import sys from pathlib import Path TOOLS = Path(__file__).resolve().parent LAB_ROOT = TOOLS.parent SOURCE_ROOT = LAB_ROOT / "source" APPLY_ROOT = LAB_ROOT / "server" / "public" CAMPAIGN_HASH = "34f5121f572d6742703eb84ec2f866a6" 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 replace_tree(src: Path, dst: Path) -> None: if dst.exists(): shutil.rmtree(dst) shutil.copytree(src, dst, symlinks=False, ignore=_ignore_junk) def main() -> int: parser = argparse.ArgumentParser( description=( "Copy source/web + source/sync to server/public, then run " "patch_all.py --apply --root server/public." ) ) parser.add_argument( "--deployment-seed", help="optional; forwarded to patch_all (default: random)", ) parser.add_argument( "--reporting-seed", help="optional; forwarded to patch_all (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( "-n", "--count", type=int, default=5, help="DGA candidates to print when not using fixed domains (default 5)", ) parser.add_argument( "--skip-patch", action="store_true", help="only copy source trees to server/public; do not run patch_all", ) args = parser.parse_args() if bool(args.deployment_domains) != bool(args.reporting_domains): raise SystemExit("provide both --deployment-domains and --reporting-domains, or neither") src_web = SOURCE_ROOT / "web" src_sync = SOURCE_ROOT / "sync" if not (src_web / CAMPAIGN_HASH).is_dir(): raise SystemExit(f"missing source campaign: {src_web / CAMPAIGN_HASH}") if not src_sync.is_dir(): raise SystemExit(f"missing source sync: {src_sync}") APPLY_ROOT.mkdir(parents=True, exist_ok=True) dst_web = APPLY_ROOT / "web" dst_sync = APPLY_ROOT / "sync" print("=== reset server/public from source ===") print(f"from: {SOURCE_ROOT}") print(f"to: {APPLY_ROOT}/{{web,sync}}") replace_tree(src_web, dst_web) replace_tree(src_sync, dst_sync) print(f"copied web/ ({CAMPAIGN_HASH}) + sync/") if args.skip_patch: print("skip-patch: done (source copy only)") return 0 cmd = [ sys.executable, str(TOOLS / "patch_all.py"), "--apply", "--root", str(APPLY_ROOT), "-n", str(args.count), ] if args.deployment_seed: cmd += ["--deployment-seed", args.deployment_seed] if args.reporting_seed: cmd += ["--reporting-seed", args.reporting_seed] for item in args.deployment_domains: cmd += ["--deployment-domains", item] for item in args.reporting_domains: cmd += ["--reporting-domains", item] print() print("=== patch_all --apply ===") print("+", " ".join(cmd), flush=True) subprocess.run(cmd, cwd=str(LAB_ROOT), check=True) out_root = APPLY_ROOT / "out" print() print("=== ready ===") print(f"web: {dst_web / CAMPAIGN_HASH}") print(f"sync: {dst_sync}") print(f"seeds: {out_root / 'seeds.json'}") print(f"domains: {out_root / 'domains.json'}") print() print("served by Laravel public:") print(f" /web/{CAMPAIGN_HASH}/support.html") print(" /sync/daily.html") return 0 if __name__ == "__main__": raise SystemExit(main())