my-homelab-configs/scripts/red-blue-loop

84 lines
2.4 KiB
Python
Executable File

#!/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())