This commit is contained in:
hashbro
2026-08-05 05:42:10 +08:00
parent d4fb538997
commit 22942f1a78
19 changed files with 986 additions and 164 deletions
+42 -20
View File
@@ -11,10 +11,12 @@ import subprocess
import sys
from pathlib import Path
from _channel_patch import gen_channel_id, validate_channel_id
TOOLS = Path(__file__).resolve().parent
LAB_ROOT = TOOLS.parent
SOURCE_ROOT = LAB_ROOT / "source"
CAMPAIGN_HASH = "34f5121f572d6742703eb84ec2f866a6"
ORIGINAL_CHANNEL_ID = "34f5121f572d6742703eb84ec2f866a6"
def gen_seed() -> str:
@@ -32,25 +34,28 @@ def _ignore_junk(_dir: str, names: list[str]) -> set[str]:
return {n for n in names if n in skip or n.endswith(".pyc")}
def ensure_working_tree(root: Path) -> None:
"""If web/sync missing under root, copy from source/ (same as new_project --skip-patch)."""
camp = root / "web" / CAMPAIGN_HASH
def ensure_working_tree(root: Path, channel: str) -> None:
"""Ensure sync/ and web/<channel>/ exist (copy from source template if needed)."""
camp = root / "web" / channel
sync = root / "sync"
if camp.is_dir() and sync.is_dir():
return
src_web = SOURCE_ROOT / "web"
src_camp = SOURCE_ROOT / "web" / ORIGINAL_CHANNEL_ID
src_sync = SOURCE_ROOT / "sync"
if not (src_web / CAMPAIGN_HASH).is_dir() or not src_sync.is_dir():
if not src_camp.is_dir() or not src_sync.is_dir():
raise SystemExit(
f"missing working tree and source template.\n"
f"expected: {src_web / CAMPAIGN_HASH} and {src_sync}"
f"missing source template.\n"
f"expected: {src_camp} and {src_sync}"
)
print("=== bootstrap working tree from source/ ===")
for src, dst in ((src_web, root / "web"), (src_sync, root / "sync")):
if dst.exists():
shutil.rmtree(dst)
shutil.copytree(src, dst, symlinks=False, ignore=_ignore_junk)
print(f"copied {src.relative_to(LAB_ROOT)} -> {dst}")
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 campaign -> {camp}")
if not camp.is_dir() or not sync.is_dir():
raise SystemExit(f"failed to bootstrap {camp} / {sync}")
print()
@@ -63,6 +68,10 @@ def main() -> int:
)
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 web/<id>/ + type-0x01 embed (default: random)",
)
parser.add_argument(
"--deployment-domains",
action="append",
@@ -83,7 +92,7 @@ def main() -> int:
parser.add_argument(
"--apply",
action="store_true",
help="write into --root web/ + sync/",
help="write into --root web/<channel-id>/ + sync/",
)
parser.add_argument(
"-n",
@@ -99,8 +108,10 @@ def main() -> int:
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())
ensure_working_tree(args.root.resolve(), channel)
fixed_mode = bool(args.deployment_domains)
dep = args.deployment_seed or gen_seed()
@@ -117,6 +128,7 @@ def main() -> int:
{
"deployment_seed": dep,
"reporting_seed": rep,
"channel_id": channel,
"mode": "fixed_domains" if fixed_mode else "dga",
},
indent=2,
@@ -124,8 +136,9 @@ def main() -> int:
+ "\n"
)
print("=== seeds ===")
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}")
@@ -136,6 +149,7 @@ def main() -> int:
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:
@@ -152,6 +166,7 @@ def main() -> int:
dep,
"--reporting-seed",
rep,
*channel_args,
*domain_args,
*root,
*apply,
@@ -177,7 +192,6 @@ def main() -> int:
domains_path = out_root / "domains.json"
if fixed_mode:
# Prefer MANIFEST from patch_core output
manifest_path = out_root / "sync" / "MANIFEST.json"
if not manifest_path.is_file():
manifest_path = LAB_ROOT / "out" / "sync" / "MANIFEST.json"
@@ -212,8 +226,14 @@ def main() -> int:
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}")
@@ -223,6 +243,8 @@ def main() -> int:
print()
print(f"seeds: {seeds_path}")
print(f"domains: {domains_path}")
if args.apply and args.root:
print(f"web: {args.root.resolve() / 'web' / channel}")
if not args.apply:
print("Note: outputs are under out/ only. Use new_project.py or --apply --root <project>.")
return 0