#!/usr/bin/env python3 """Run or plan the homelab red-team/blue-team loop.""" from __future__ import annotations import argparse import csv import subprocess from pathlib import Path REPO_ROOT = Path(__file__).resolve().parents[1] CHECKS_FILE = REPO_ROOT / "infra" / "red-blue-loop" / "checks.tsv" LEDGER_FILE = REPO_ROOT / "infra" / "red-blue-loop" / "ledger.tsv" def checks() -> list[dict[str, str]]: with CHECKS_FILE.open(encoding="utf-8", newline="") as handle: return list(csv.DictReader(handle, delimiter="\t")) def plan() -> int: print("Red/Blue Loop") print("=============") for row in checks(): print(f"{row['mode']:5} {row['id']:18} {row['command']}") print(f" risk: {row['risk']}") print() print(f"Ledger: {LEDGER_FILE.relative_to(REPO_ROOT)}") return 0 def run(local_only: bool) -> int: failures = 0 selected = [row for row in checks() if row["mode"] == "local" or not local_only] print("Red/Blue Loop Run") print("=================") for row in selected: print(f"\n== {row['id']} ==") if row["mode"] != "local" and local_only: print("skip: live check gated") continue process = subprocess.run( row["command"].split(), cwd=REPO_ROOT, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, check=False, ) print(process.stdout.rstrip()) if process.returncode != 0: failures += 1 print() print(f"failures={failures}") print(f"ledger={LEDGER_FILE.relative_to(REPO_ROOT)}") return 1 if failures else 0 def ledger() -> int: print(LEDGER_FILE.read_text(encoding="utf-8").rstrip()) return 0 def main() -> int: parser = argparse.ArgumentParser(description=__doc__) subparsers = parser.add_subparsers(dest="command", required=True) subparsers.add_parser("plan") run_parser = subparsers.add_parser("run") run_parser.add_argument("--local", action="store_true", help="Run only deterministic local checks.") subparsers.add_parser("ledger") args = parser.parse_args() if args.command == "plan": return plan() if args.command == "run": return run(local_only=args.local) if args.command == "ledger": return ledger() return 2 if __name__ == "__main__": raise SystemExit(main())