a little house cleaning
authorThomas Walker Lynch <eknp9n@reasoningtechnology.com>
Mon, 15 Sep 2025 05:34:22 +0000 (22:34 -0700)
committerThomas Walker Lynch <eknp9n@reasoningtechnology.com>
Mon, 15 Sep 2025 05:34:22 +0000 (22:34 -0700)
16 files changed:
developer/source/DNS/deloy.sh [deleted file]
developer/source/DNS/deploy.py [new file with mode: 0644]
developer/source/DNS/scratchpad/.gitignore [new file with mode: 0644]
developer/source/DNS_bundle.tgz [new file with mode: 0644]
developer/source/tunnel-client/mothball/stage/.gitignore [deleted file]
developer/source/tunnel-client/mothball/stage_IP_routes_script.py [deleted file]
developer/source/tunnel-client/mothball/stage_IP_rules_script.py [deleted file]
developer/source/tunnel-client/mothball/stage_StanleyPark.py [deleted file]
developer/source/tunnel-client/mothball/stage_UID_routes.py [deleted file]
developer/source/tunnel-client/mothball/stage_list_clients.py [deleted file]
developer/source/tunnel-client/mothball/stage_list_uid.py [deleted file]
developer/source/tunnel-client/mothball/stage_populate.py [deleted file]
developer/source/tunnel-client/mothball/stage_preferred_server.py [deleted file]
developer/source/tunnel-client/mothball/stage_wg_conf.py [deleted file]
developer/source/tunnel-client/mothball/stage_wg_unit_IP_scripts.py [deleted file]
developer/source/tunnel-client/mothball/stage_wipe.py [deleted file]

diff --git a/developer/source/DNS/deloy.sh b/developer/source/DNS/deloy.sh
deleted file mode 100644 (file)
index 7a033a8..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-sudo ./install_staged_tree.py
-sudo systemctl daemon-reload
-# enable nft snippet (once):
-sudo sed -i '1{/table inet filter {/!{h;s/.*/include "\/etc\/nftables.d\/30-dnsredir.nft"/;H;x}}' /etc/nftables.conf || true
-sudo nft -f /etc/nftables.conf
-
-# bring up Unbound instances (they wait for wg links):
-sudo systemctl enable --now unbound@US unbound@x6
diff --git a/developer/source/DNS/deploy.py b/developer/source/DNS/deploy.py
new file mode 100644 (file)
index 0000000..33f07e0
--- /dev/null
@@ -0,0 +1,55 @@
+#!/usr/bin/env python3
+"""
+deploy_dns.py — installs staged DNS artifacts (Unbound, nftables snippet)
+without starting/stopping services. Prints next-step commands.
+"""
+
+from __future__ import annotations
+from pathlib import Path
+import os, sys
+
+def main(argv=None) -> int:
+  root = Path(__file__).resolve().parent
+  stage = root / "stage"
+  issues = []
+  if os.geteuid() != 0:
+    issues.append("must be run as root (sudo)")
+
+  for rel in [
+    "etc/unbound/unbound-US.conf",
+    "etc/unbound/unbound-x6.conf",
+    "etc/systemd/system/unbound@.service",
+    "etc/nftables.d/30-dnsredir.nft",
+  ]:
+    if not (stage / rel).exists():
+      issues.append(f"missing staged file: stage/{rel}")
+
+  try:
+    import install_staged_tree as ist
+  except Exception as e:
+    issues.append(f"failed to import install_staged_tree: {e}")
+
+  if issues:
+    print("❌ deploy preflight found issue(s):")
+    for i in issues: print(f"  - {i}")
+    return 2
+
+  dest_root = Path("/")
+  staged = ist.install_staged_tree(stage_root=stage, dest_root=dest_root)
+  # Paths printed by install_staged_tree; keep our output short.
+  print("\nNext steps:")
+  print("  sudo systemctl daemon-reload")
+  print('  # ensure nft snippet included in /etc/nftables.conf:')
+  print('  #   include "/etc/nftables.d/30-dnsredir.nft"')
+  print("  sudo nft -f /etc/nftables.conf")
+  print("  sudo install -d -m 0755 /var/lib/unbound")
+  print("  sudo unbound-anchor -a /var/lib/unbound/root.key")
+  print("  sudo systemctl enable --now unbound@US unbound@x6")
+  print("\nVerify:")
+  print("  sudo ss -ltnup '( sport = :5301 or sport = :5302 )'")
+  print("  sudo -u Thomas-US dig example.com +short")
+  print("  sudo -u Thomas-x6 dig example.com +short")
+  return 0
+
+if __name__ == "__main__":
+  sys.exit(main())
diff --git a/developer/source/DNS/scratchpad/.gitignore b/developer/source/DNS/scratchpad/.gitignore
new file mode 100644 (file)
index 0000000..120f485
--- /dev/null
@@ -0,0 +1,2 @@
+*
+!/.gitignore
diff --git a/developer/source/DNS_bundle.tgz b/developer/source/DNS_bundle.tgz
new file mode 100644 (file)
index 0000000..1635cc2
Binary files /dev/null and b/developer/source/DNS_bundle.tgz differ
diff --git a/developer/source/tunnel-client/mothball/stage/.gitignore b/developer/source/tunnel-client/mothball/stage/.gitignore
deleted file mode 100644 (file)
index 53642ce..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-
-*
-!.gitignore
-
diff --git a/developer/source/tunnel-client/mothball/stage_IP_routes_script.py b/developer/source/tunnel-client/mothball/stage_IP_routes_script.py
deleted file mode 100755 (executable)
index d1ec126..0000000
+++ /dev/null
@@ -1,118 +0,0 @@
-#!/usr/bin/env python3
-# stage_IP_route_script.py — emit /usr/local/bin/route_init_<iface>.sh from DB
-# Purpose at runtime of the emitted script:
-#   1) Ensure default + blackhole default in the dedicated route table.
-#   2) Pin the peer endpoint (/32 via GW on NIC, metric 5) outside the tunnel so the handshake cannot vanish.
-#   3) Apply any extra route from the route table (on_up=1).
-#
-# Usage: stage_IP_route_script.py <iface>
-# Output: stage/usr/local/bin/route_init_<iface>.sh (chmod 500)
-# Idempotence (runtime): uses `ip -4 route replace`
-# Failure modes (runtime): if DNS resolution fails, step (2) is skipped; (1) and (3) still apply.
-
-from __future__ import annotations
-import sys, sqlite3
-from pathlib import Path
-import incommon as ic  # open_db(), rows()
-
-def _bash_single_quote(s: str) -> str:
-  # Safe single-quoted literal for bash
-  return "'" + s.replace("'", "'\"'\"'") + "'"
-
-def stage_ip_route_script(iface: str) -> Path:
-  # Resolve DB data
-  with ic.open_db() as conn:
-    row = conn.execute(
-      "SELECT id, rt_table_name_eff FROM v_client_effective WHERE iface=? LIMIT 1;",
-      (iface,)
-    ).fetchone()
-    if not row:
-      raise RuntimeError(f"iface not found in DB: {iface}")
-    iface_id, rtname = int(row[0]), str(row[1])
-
-    # Preferred server: lowest priority, then lowest id
-    srow = conn.execute(
-      """
-      SELECT s.endpoint_host, s.endpoint_port
-        FROM server s
-        JOIN Iface c ON c.id=s.iface_id
-       WHERE c.id=?
-       ORDER BY s.priority ASC, s.id ASC
-       LIMIT 1;
-      """,
-      (iface_id,)
-    ).fetchone()
-    ep_host = str(srow[0]) if srow and srow[0] else ""
-    ep_port = str(srow[1]) if srow and srow[1] else ""
-
-    # Extra route for on_up
-    extra = ic.rows(conn, """
-      SELECT cidr, COALESCE(via,''), COALESCE(table_name,''), COALESCE(metric,'')
-        FROM route
-       WHERE iface_id=? AND on_up=1
-       ORDER BY id;
-    """, (iface_id,))
-
-  # Paths
-  out_path = Path(__file__).resolve().parent / "stage" / "usr" / "local" / "bin" / f"route_init_{iface}.sh"
-  out_path.parent.mkdir(parents=True, exist_ok=True)
-
-  # Emit script
-  lines: list[str] = []
-  lines.append("#!/usr/bin/env bash")
-  lines.append("set -euo pipefail")
-  lines.append(f"table={_bash_single_quote(rtname)}")
-  lines.append(f"dev={_bash_single_quote(iface)}")
-  lines.append(f"endpoint_host={_bash_single_quote(ep_host)}")
-  lines.append(f"endpoint_port={_bash_single_quote(ep_port)}")
-  lines.append("")
-  lines.append("# 1) Default in dedicated table")
-  lines.append('ip -4 route replace default dev "$dev" table "$table"')
-  lines.append('ip -4 route replace blackhole default metric 32767 table "$table"')
-  lines.append("")
-  lines.append("# 2) Keep peer endpoint reachable outside the tunnel")
-  lines.append('ep_ip=$(getent ahostsv4 "$endpoint_host" | awk \'NR==1{print $1}\')')
-  lines.append('if [[ -n "$ep_ip" ]]; then')
-  lines.append('  gw=$(ip -4 route get "$ep_ip" | awk \'/ via /{print $3; exit}\')')
-  lines.append('  nic=$(ip -4 route get "$ep_ip" | awk \'/ dev /{for(i=1;i<=NF;i++) if ($i=="dev"){print $(i+1); exit}}\')')
-  lines.append('  if [[ -n "$gw" && -n "$nic" ]]; then')
-  lines.append('    ip -4 route replace "${ep_ip}/32" via "$gw" dev "$nic" metric 5')
-  lines.append('  fi')
-  lines.append('fi')
-  lines.append("")
-  lines.append("# 3) Extra route from DB")
-  for cidr, via, tbl, met in extra:
-    cidr = str(cidr)
-    via  = str(via or "")
-    tbl  = str(tbl or rtname)
-    met  = str(met or "")
-    cmd = ["ip -4 route replace", cidr]
-    if via: cmd += ["via", via]
-    cmd += ['table', f'"{tbl}"']
-    if met: cmd += ['metric', met]
-    lines.append(" ".join(cmd))
-
-  out_path.write_text("\n".join(lines) + "\n")
-  out_path.chmod(0o500)
-
-  return out_path
-
-def main(argv: list[str]) -> int:
-  if len(argv) != 1:
-    print(f"Usage: {Path(sys.argv[0]).name} <iface>", file=sys.stderr)
-    return 2
-  iface = argv[0]
-  try:
-    out = stage_ip_route_script(iface)
-  except (sqlite3.Error, FileNotFoundError, RuntimeError) as e:
-    print(f"❌ {e}", file=sys.stderr); return 1
-  # Print relative-to-CWD as requested style: 'stage/...'
-  try:
-    rel = out.relative_to(Path.cwd())
-    print(f"staged: {rel}")
-  except ValueError:
-    print(f"staged: {out}")
-  return 0
-
-if __name__ == "__main__":
-  sys.exit(main(sys.argv[1:]))
diff --git a/developer/source/tunnel-client/mothball/stage_IP_rules_script.py b/developer/source/tunnel-client/mothball/stage_IP_rules_script.py
deleted file mode 100755 (executable)
index 7fae716..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-#!/usr/bin/env python3
-"""
-stage_IP_rules.py — stage a runtime script to enforce IPv4 rules for all subu
-
-- Reads subu_cidr from DB.meta
-- For each client: adds FROM <src_cidr> → <table> and per-UID rules
-- Appends a final PROHIBIT for subu_cidr to enforce hard containment
-- Writes: stage/usr/local/bin/<OUTPUT_SCRIPT_NAME> (no args at runtime)
-"""
-
-from __future__ import annotations
-import sys
-from pathlib import Path
-from typing import Optional, Sequence, Dict, List
-import incommon as ic
-
-OUTPUT_SCRIPT_NAME = "set_subu_IP_rules.sh"
-
-def stage_set_subu_ip_rules(ifaces: Optional[Sequence[str]] = None) -> tuple[Path, str]:
-  with ic.open_db() as conn:  # ← no path arg
-    client = ic.fetch_client(conn, ifaces)  # expects id, iface, rtname, addr from v_client_effective
-    if not client: raise RuntimeError("no client selected")
-    subu = ic.subu_cidr(conn, "10.0.0.0/24")
-    ic.validate_unique_hosts(client, subu)
-    uid_map: Dict[int, List[int]] = {int(c["id"]): ic.collect_uids(conn, int(c["id"])) for c in client}
-
-  out = ic.STAGE_ROOT / "usr" / "local" / "bin" / OUTPUT_SCRIPT_NAME
-
-  lines: List[str] = []
-  
-  lines += [
-    "#!/usr/bin/env bash",
-    "# Enforce IPv4 rules for all subu; idempotent per rule.",
-    "set -euo pipefail",
-    "",
-    'add_IP_rule_if_not_exists(){ local search_phrase=$1; shift; if ! ip -4 rule list | grep -F -q -- "$search_phrase"; then ip -4 rule add "$@"; fi; }',
-    ""
-  ]
-
-  for c in client:
-    table = c["rtname"]; src_cidr = c["addr"]; cid = int(c["id"])
-    lines += [f"# client: iface={c['iface']} table={table} src={src_cidr} id={cid}"]
-    lines += [f'add_IP_rule_if_not_exists "from {src_cidr} lookup {table}" from "{src_cidr}" lookup "{table}" pref 17000']
-    for u in uid_map[cid]:
-      lines += [f'add_IP_rule_if_not_exists "from {src_cidr} lookup {table}" from "{src_cidr}" lookup "{table}" pref 17000']
-    lines += [""]
-
-  lines += [
-    "# hard containment for subu space",
-    f'add_IP_rule_if_not_exists "from {subu} prohibit" from "{subu}" prohibit pref 18050',
-    ""
-  ]
-
-  ic.write_exec_quiet(out, "\n".join(lines))
-
-  per_iface = ", ".join(
-    f"{c['iface']}:[{','.join(str(u) for u in uid_map[int(c['id'])]) or '-'}]"
-    for c in client
-  )
-  total_uid_rules = sum(len(uid_map[int(c["id"])]) for c in client)
-  summary = f"client={len(client)}, uid_rules={total_uid_rules} ({per_iface})"
-  return out, summary
-
-def main(argv: Sequence[str]) -> int:
-  ifaces = list(argv) if argv else None
-  try:
-    path, summary = stage_set_subu_ip_rules(ifaces)
-  except Exception as e:
-    print(f"❌ {e}", file=sys.stderr); return 1
-  try:
-    rel = "stage/" + path.relative_to(ic.STAGE_ROOT).as_posix()
-  except Exception:
-    rel = path.as_posix().replace(ic.ROOT.as_posix() + "/", "")
-  print(f"staged: {rel} — {summary}")
-  return 0
-
-if __name__ == "__main__":
-  sys.exit(main(sys.argv[1:]))
diff --git a/developer/source/tunnel-client/mothball/stage_StanleyPark.py b/developer/source/tunnel-client/mothball/stage_StanleyPark.py
deleted file mode 100644 (file)
index c374029..0000000
+++ /dev/null
@@ -1,139 +0,0 @@
-#!/usr/bin/env python3
-# stage_StanleyPark.py — stage artifacts for this client machine only
-# Chooses just the ifaces we run here (x6, US) and reuses existing business funcs.
-
-from __future__ import annotations
-import sys, sqlite3, shutil
-from pathlib import Path
-import incommon as ic
-
-# Reuse business modules (no logic duplication)
-import stage_clean as stclean
-import stage_wg_conf as stconf
-import stage_preferred_server as stpref
-import stage_IP_route_script as striproute
-import stage_IP_rules_script as striprules
-import stage_wg_unit_IP_scripts as stdrop
-
-# Ifaces for THIS machine (adjust if needed)
-IFACES = ["x6", "US"]
-
-def msg_wrapped_call(title: str, fn=None, *args, **kwargs):
-  print(f"→ {title}", flush=True)
-  res = fn(*args, **kwargs) if fn else None
-  print(f"✔ {title}" + (f": {res}" if res not in (None, "") else ""), flush=True)
-  return res
-
-def fetch_client_by_iface(conn: sqlite3.Connection, iface: str) -> dict | None:
-  conn.row_factory = sqlite3.Row
-  # Prefer the effective-view; fall back if missing
-  try:
-    r = conn.execute("""
-      SELECT c.id, c.iface, v.rt_table_name_eff AS rtname,
-             COALESCE(c.rt_table_id,'') AS rtid,
-             c.local_address_cidr AS addr,
-             c.private_key AS priv,
-             COALESCE(c.mtu,'') AS mtu,
-             COALESCE(c.fwmark,'') AS fwmark,
-             c.dns_mode AS dns_mode,
-             COALESCE(c.dns_servers,'') AS dns_servers,
-             c.autostart AS autostart
-        FROM Iface c
-        JOIN v_client_effective v ON v.id=c.id
-       WHERE c.iface=? LIMIT 1;
-    """,(iface,)).fetchone()
-  except sqlite3.Error:
-    r = conn.execute("""
-      SELECT id, iface, COALESCE(rt_table_name,iface) AS rtname,
-             COALESCE(rt_table_id,'') AS rtid,
-             local_address_cidr AS addr,
-             private_key AS priv,
-             COALESCE(mtu,'') AS mtu,
-             COALESCE(fwmark,'') AS fwmark,
-             dns_mode AS dns_mode,
-             COALESCE(dns_servers,'') AS dns_servers,
-             autostart AS autostart
-        FROM Iface WHERE iface=? LIMIT 1;
-    """,(iface,)).fetchone()
-  return (dict(r) if r else None)
-
-def stage_for_ifaces(ifaces: list[str], clean_mode: str | None) -> int:
-  # 0) Clean stage dir
-  if clean_mode == "--clean":
-    msg_wrapped_call("stage clean (--yes)", stclean.clean, yes=True, dry_run=False, hard=False)
-  elif clean_mode == "--no-clean":
-    Path(stclean.stage_root()).mkdir(parents=True, exist_ok=True)
-  else:
-    msg_wrapped_call("stage clean (interactive)", stclean.clean, yes=False, dry_run=False, hard=False)
-
-  root = Path(__file__).resolve().parent
-  stage_root = root / "stage"
-  (stage_root / "wireguard").mkdir(parents=True, exist_ok=True)
-  (stage_root / "systemd").mkdir(parents=True, exist_ok=True)
-  (stage_root / "usr" / "local" / "bin").mkdir(parents=True, exist_ok=True)
-
-  # Optional helper carry-over (kept same behavior)
-  ip_rule_add = root / "IP_rule_add_UID.sh"
-  if ip_rule_add.exists():
-    dst = stage_root / "usr" / "local" / "bin" / "IP_rule_add_UID.sh"
-    shutil.copy2(ip_rule_add, dst); dst.chmod(0o500)
-    print(f"staged: {dst.relative_to(root)}")
-
-  # 1) Global policy script — limit to selected ifaces (so rules are scoped)
-  msg_wrapped_call(f"stage global set_subu_IP_rules.sh for {ifaces}",
-                   striprules.stage_set_subu_ip_rules, ifaces)
-
-  # 2) Per-iface artifacts
-  with ic.open_db() as conn:
-    for iface in ifaces:
-      c = fetch_client_by_iface(conn, iface)
-      if not c:
-        print(f"⚠️  iface '{iface}' not in DB; skipping"); continue
-
-      cid   = int(c["id"])
-      addr  = str(c["addr"])
-      priv  = str(c["priv"])
-      mtu   = str(c["mtu"])
-      fw    = str(c["fwmark"])
-      dns_m = str(c["dns_mode"])
-      dns_s = str(c["dns_servers"])
-
-      srow = stpref.preferred_server_row(cid)
-      if not srow:
-        print(f"⚠️  No server for client '{iface}' (id={cid}). Skipping.")
-        continue
-      (s_name, s_pub, s_psk, s_host, s_port, s_allow, s_ka, s_route) = srow
-
-      # WG conf
-      conf_out = stage_root / "wireguard" / f"{iface}.conf"
-      msg_wrapped_call(f"wg conf for {iface}",
-        stconf.write_wg_conf, conf_out, addr, priv, mtu, fw, dns_m, dns_s,
-        s_pub, s_psk, s_host, str(s_port), s_allow, str(s_ka or "")
-      )
-
-      # Per-iface route script
-      msg_wrapped_call(f"route_init for {iface}", striproute.stage_ip_route_script, iface)
-
-      # Systemd override referencing global rules + per-iface route
-      msg_wrapped_call(f"wg-quick override for {iface}", stdrop.stage_dropin, iface)
-
-      print(f"✔ Staged: {iface}")
-
-  print(f"✅ Stage generation complete in: {stage_root}")
-  return 0
-
-def main(argv: list[str]) -> int:
-  clean_mode = None
-  if argv and argv[0] in ("--clean","--no-clean"):
-    clean_mode = argv[0]
-    argv = argv[1:]
-  if argv:
-    print(f"Usage: {Path(sys.argv[0]).name} [--clean|--no-clean]", file=sys.stderr)
-    return 2
-  try:
-    return stage_for_ifaces(IFACES, clean_mode)
-  except (sqlite3.Error, FileNotFoundError, RuntimeError) as e:
-    print(f"❌ {e}", file=sys.stderr); return 1
-
-if __name__ == "__main__":
-  sys.exit(main(sys.argv[1:]))
diff --git a/developer/source/tunnel-client/mothball/stage_UID_routes.py b/developer/source/tunnel-client/mothball/stage_UID_routes.py
deleted file mode 100755 (executable)
index 7dfeb31..0000000
+++ /dev/null
@@ -1,67 +0,0 @@
-#!/usr/bin/env python3
-# stage_UID_route.py — emit /usr/local/bin/set_subu_UID_route.sh from DB
-
-from __future__ import annotations
-import sys, sqlite3, ipaddress
-from pathlib import Path
-import incommon as ic
-
-OUT = Path(__file__).resolve().parent / "stage" / "usr" / "local" / "bin" / "set_subu_UID_route.sh"
-
-def main(argv: list[str]) -> int:
-  try:
-    with ic.open_db() as conn:
-      rows = ic.rows(conn, """
-        SELECT c.iface, c.rt_table_name_eff AS rtname, c.local_address_cidr,
-               ub.uid
-          FROM Iface c
-     LEFT JOIN user_binding ub ON ub.iface_id=c.id
-      ORDER BY c.iface, ub.uid;
-      """)
-
-      meta = dict(ic.rows(conn, "SELECT key, value FROM meta;"))
-      subu_cidr = meta.get("subu_cidr", "10.0.0.0/24")
-  except (sqlite3.Error, FileNotFoundError) as e:
-    print(f"❌ {e}", file=sys.stderr); return 1
-
-  OUT.parent.mkdir(parents=True, exist_ok=True)
-
-  lines = []
-  lines.append("#!/usr/bin/env bash")
-  lines.append("# Set per-UID policy routing; idempotent.")
-  lines.append("set -euo pipefail")
-  lines.append('ensure(){ local n=\"$1\"; shift; if ! ip -4 rule list | grep -F -q -- \"$n\"; then ip -4 rule add \"$@\"; fi; }')
-  lines.append('ensureroute(){ local tbl=\"$1\"; shift; ip route replace \"$@\" table \"$tbl\"; }')
-  lines.append("")
-
-  seen = set()
-  for iface, rtname, cidr, uid in rows:
-    if not iface: continue
-    try: src_ip = str(ipaddress.IPv4Interface(cidr).ip)
-    except: continue
-    # table name per UID (avoid rt_tables entries by using numeric if you prefer)
-    if uid is None: continue
-    tname = f"{rtname}_u{uid}"
-    key = (iface, uid)
-    if key in seen: continue
-    seen.add(key)
-
-    # route: default via iface with pinned src
-    lines.append(f"# uid {uid} on {iface} → src {src_ip} via table {tname}")
-    lines.append(f'ensureroute "{tname}" default dev {iface} src {src_ip}')
-    lines.append(f'ensure "uidrange {uid}-{uid} lookup {tname}" uidrange "{uid}-{uid}" lookup "{tname}" pref 17010')
-    # symmetry guard for already-sourced packets
-    lines.append(f'ensure "from {src_ip}/32 lookup {tname}" from "{src_ip}/32" lookup "{tname}" pref 17000')
-    lines.append("")
-
-  # global hard containment for subu space
-  lines.append(f'# hard containment for subu space {subu_cidr}')
-  lines.append(f'ensure "from {subu_cidr} prohibit" from "{subu_cidr}" prohibit pref 18050')
-  content = "\n".join(lines) + "\n"
-  OUT.write_text(content)
-  OUT.chmod(0o500)
-  print(f"staged: {OUT.relative_to(Path(__file__).resolve().parent)}")
-  return 0
-
-if __name__ == "__main__":
-  sys.exit(main(sys.argv[1:]))
diff --git a/developer/source/tunnel-client/mothball/stage_list_clients.py b/developer/source/tunnel-client/mothball/stage_list_clients.py
deleted file mode 100755 (executable)
index a36657a..0000000
+++ /dev/null
@@ -1,92 +0,0 @@
-#!/usr/bin/env python3
-# stage_list_client.py — emit one line per client with fields needed for staging
-# Output format (pipe-separated, no header):
-#   id|iface|rt_table_name|rt_table_id|addr|priv|mtu|fwmark|dns_mode|dns_servers|autostart
-
-from __future__ import annotations
-import sys, sqlite3
-from pathlib import Path
-
-def rows(conn: sqlite3.Connection, sql: str, params: tuple = ()) -> list[sqlite3.Row]:
-  conn.row_factory = sqlite3.Row
-  cur = conn.execute(sql, params)
-  return cur.fetchall()
-
-def list_client(db_path: Path) -> int:
-  try:
-    conn = sqlite3.connect(str(db_path))
-  except sqlite3.Error as e:
-    print(f"❌ sqlite open failed: {e}", file=sys.stderr)
-    return 1
-
-  try:
-    # Prefer the view (effective rt_table_name); fall back to COALESCE if view missing.
-    try_sql = """
-      SELECT c.id,
-             c.iface,
-             v.rt_table_name_eff               AS rt_table_name,
-             COALESCE(c.rt_table_id, '')       AS rt_table_id,
-             c.local_address_cidr              AS addr,
-             c.private_key                     AS priv,
-             COALESCE(c.mtu,    '')            AS mtu,
-             COALESCE(c.fwmark, '')            AS fwmark,
-             c.dns_mode,
-             COALESCE(c.dns_servers, '')       AS dns_servers,
-             c.autostart
-        FROM Iface c
-        JOIN v_client_effective v ON v.id = c.id
-       ORDER BY c.id;
-    """
-    try:
-      R = rows(conn, try_sql)
-    except sqlite3.Error:
-      # Fallback without the view
-      fallback_sql = """
-        SELECT id,
-               iface,
-               COALESCE(rt_table_name, iface)   AS rt_table_name,
-               COALESCE(rt_table_id, '')        AS rt_table_id,
-               local_address_cidr               AS addr,
-               private_key                      AS priv,
-               COALESCE(mtu,    '')             AS mtu,
-               COALESCE(fwmark, '')             AS fwmark,
-               dns_mode,
-               COALESCE(dns_servers, '')        AS dns_servers,
-               autostart
-          FROM Iface
-         ORDER BY id;
-      """
-      R = rows(conn, fallback_sql)
-
-    for r in R:
-      fields = [
-        r["id"],
-        r["iface"],
-        r["rt_table_name"],
-        r["rt_table_id"],
-        r["addr"],
-        r["priv"],
-        r["mtu"],
-        r["fwmark"],
-        r["dns_mode"],
-        r["dns_servers"],
-        r["autostart"],
-      ]
-      print("|".join("" if v is None else str(v) for v in fields))
-    return 0
-  finally:
-    conn.close()
-
-def main(argv: list[str]) -> int:
-  if len(argv) != 1:
-    prog = Path(sys.argv[0]).name
-    print(f"Usage: {prog} /path/to/db", file=sys.stderr)
-    return 2
-  db_path = Path(argv[0])
-  if not db_path.exists():
-    print(f"❌ DB not found: {db_path}", file=sys.stderr)
-    return 1
-  return list_client(db_path)
-
-if __name__ == "__main__":
-  sys.exit(main(sys.argv[1:]))
diff --git a/developer/source/tunnel-client/mothball/stage_list_uid.py b/developer/source/tunnel-client/mothball/stage_list_uid.py
deleted file mode 100644 (file)
index 5acf312..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-#!/usr/bin/env python3
-# stage_list_uid.py — print Uid (one per line) bound to a iface_id
-
-from __future__ import annotations
-import sys, sqlite3
-from pathlib import Path
-import incommon as ic
-
-def list_uid(iface_id: int) -> int:
-  try:
-    with ic.open_db() as conn:
-      rows = conn.execute("""
-        SELECT ub.uid
-          FROM user_binding ub
-         WHERE ub.iface_id=? AND ub.uid IS NOT NULL AND ub.uid!=''
-         ORDER BY ub.uid;
-      """,(iface_id,)).fetchall()
-  except (sqlite3.Error, FileNotFoundError) as e:
-    print(f"❌ {e}", file=sys.stderr); return 1
-  for (uid,) in rows:
-    print(uid)
-  return 0
-
-def main(argv):
-  if len(argv)!=1:
-    print(f"Usage: {Path(sys.argv[0]).name} <iface_id>", file=sys.stderr)
-    return 2
-  return list_uid(int(argv[0]))
-
-if __name__ == "__main__":
-  sys.exit(main(sys.argv[1:]))
diff --git a/developer/source/tunnel-client/mothball/stage_populate.py b/developer/source/tunnel-client/mothball/stage_populate.py
deleted file mode 100644 (file)
index bcb803a..0000000
+++ /dev/null
@@ -1,133 +0,0 @@
-#!/usr/bin/env python3
-# stage_populate.py — orchestrate stage generation (no business logic here)
-
-from __future__ import annotations
-import sys, sqlite3, shutil
-from pathlib import Path
-import incommon as ic
-
-# imports of our freshly Pythonized helpers
-import stage_clean as stclean
-import stage_wg_conf as stconf
-import stage_preferred_server as stpref
-import stage_list_uids as stuids
-import stage_IP_route_script as striproute
-import stage_IP_rules_script as striprules
-import stage_wg_unit_IP_scripts as stdrop
-
-def msg_wrapped_call(title: str, fn=None, *args, **kwargs):
-  print(f"→ {title}", flush=True)
-  res = fn(*args, **kwargs) if fn else None
-  print(f"✔ {title}" + (f": {res}" if res not in (None, "") else ""), flush=True)
-  return res
-
-def list_client(conn: sqlite3.Connection) -> list[sqlite3.Row]:
-  conn.row_factory = sqlite3.Row
-  try:
-    sql = """
-      SELECT c.id, c.iface, v.rt_table_name_eff AS rtname,
-             COALESCE(c.rt_table_id,'') AS rtid,
-             c.local_address_cidr AS addr,
-             c.private_key AS priv,
-             COALESCE(c.mtu,'') AS mtu,
-             COALESCE(c.fwmark,'') AS fwmark,
-             c.dns_mode AS dns_mode,
-             COALESCE(c.dns_servers,'') AS dns_servers,
-             c.autostart AS autostart
-        FROM Iface c
-        JOIN v_client_effective v ON v.id=c.id
-       ORDER BY c.id;
-    """
-    return list(conn.execute(sql))
-  except sqlite3.Error:
-    # fallback if view missing
-    sql = """
-      SELECT id, iface, COALESCE(rt_table_name,iface) AS rtname,
-             COALESCE(rt_table_id,'') AS rtid,
-             local_address_cidr AS addr,
-             private_key AS priv,
-             COALESCE(mtu,'') AS mtu,
-             COALESCE(fwmark,'') AS fwmark,
-             dns_mode, COALESCE(dns_servers,'') AS dns_servers,
-             autostart
-        FROM Iface ORDER BY id;
-    """
-    return list(conn.execute(sql))
-
-def stage_populate(clean_mode: str | None) -> int:
-  # 0) clean stage
-  if clean_mode == "--clean":
-    msg_wrapped_call("stage clean (--yes)", stclean.clean, yes=True, dry_run=False, hard=False)
-  elif clean_mode == "--no-clean":
-    Path(stclean.stage_root()).mkdir(parents=True, exist_ok=True)
-  else:
-    # interactive prompt like original
-    msg_wrapped_call("stage clean (interactive)", stclean.clean, yes=False, dry_run=False, hard=False)
-
-  # base dirs
-  root = Path(__file__).resolve().parent
-  stage_root = root / "stage"
-  (stage_root / "wireguard").mkdir(parents=True, exist_ok=True)
-  (stage_root / "systemd").mkdir(parents=True, exist_ok=True)
-  (stage_root / "usr" / "local" / "bin").mkdir(parents=True, exist_ok=True)
-
-  # 1) optional helper copy
-  ip_rule_add = root / "IP_rule_add_UID.sh"
-  if ip_rule_add.exists():
-    dst = stage_root / "usr" / "local" / "bin" / "IP_rule_add_UID.sh"
-    shutil.copy2(ip_rule_add, dst)
-    dst.chmod(0o500)
-    print(f"staged: {dst.relative_to(root)}")
-
-  # 2) stage global policy script once (replaces per-iface policy_init_*.sh)
-  msg_wrapped_call("stage global set_subu_IP_rules.sh", striprules.stage_set_subu_ip_rules)
-
-  # 3) per-client staging
-  with ic.open_db() as conn:
-    for r in list_client(conn):
-      cid   = int(r["id"]); iface = str(r["iface"])
-      rt    = str(r["rtname"])
-      addr  = str(r["addr"]); priv = str(r["priv"])
-      mtu   = str(r["mtu"]);  fw   = str(r["fwmark"])
-      dns_m = str(r["dns_mode"]); dns_s = str(r["dns_servers"])
-
-      # 3a) preferred server
-      srow = stpref.preferred_server_row(cid)
-      if not srow:
-        print(f"⚠️  No server for client '{iface}' (id={cid}). Skipping.")
-        continue
-      (s_name, s_pub, s_psk, s_host, s_port, s_allow, s_ka, s_route) = srow
-
-      # 3b) WG conf
-      conf_out = stage_root / "wireguard" / f"{iface}.conf"
-      msg_wrapped_call(f"wg conf for {iface}",
-        stconf.write_wg_conf, conf_out, addr, priv, mtu, fw, dns_m, dns_s,
-        s_pub, s_psk, s_host, str(s_port), s_allow, str(s_ka or "")
-      )
-
-      # 3c) route init script
-      msg_wrapped_call(f"route_init for {iface}", striproute.stage_ip_route_script, iface)
-
-      # 3d) systemd override referencing global rules + per-iface route
-      msg_wrapped_call(f"wg-quick override for {iface}", stdrop.stage_dropin, iface)
-
-      print(f"✔ Staged: {iface}")
-
-  print(f"✅ Stage generation complete in: {stage_root}")
-  return 0
-
-def main(argv):
-  clean_mode = None
-  if argv:
-    if argv[0] in ("--clean","--no-clean"):
-      clean_mode = argv[0]
-      argv = argv[1:]
-  if argv:
-    print(f"Usage: {Path(sys.argv[0]).name} [--clean|--no-clean]", file=sys.stderr); return 2
-  try:
-    return stage_populate(clean_mode)
-  except (sqlite3.Error, FileNotFoundError, RuntimeError) as e:
-    print(f"❌ {e}", file=sys.stderr); return 1
-
-if __name__ == "__main__":
-  sys.exit(main(sys.argv[1:]))
diff --git a/developer/source/tunnel-client/mothball/stage_preferred_server.py b/developer/source/tunnel-client/mothball/stage_preferred_server.py
deleted file mode 100644 (file)
index 8e39d4d..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-#!/usr/bin/env python3
-# stage_preferred_server.py — emit the preferred server row for a iface_id
-# Output: name|peer_pub|psk|endpoint_host|endpoint_port|allowed_ips|keepalive|route_allowed_ips
-
-from __future__ import annotations
-import sys, sqlite3
-from pathlib import Path
-import incommon as ic
-
-def preferred_server_row(iface_id: int) -> tuple | None:
-  with ic.open_db() as conn:
-    r = conn.execute("""
-      SELECT name, public_key, COALESCE(preshared_key,''),
-             endpoint_host, endpoint_port, allowed_ips,
-             COALESCE(keepalive_s,''), route_allowed_ips
-        FROM server
-       WHERE iface_id=?
-       ORDER BY priority ASC, id ASC
-       LIMIT 1;
-    """,(iface_id,)).fetchone()
-    return tuple(r) if r else None
-
-def main(argv):
-  if len(argv)!=1:
-    print(f"Usage: {Path(sys.argv[0]).name} <iface_id>", file=sys.stderr)
-    return 2
-  row = preferred_server_row(int(argv[0]))
-  if not row:
-    # empty stdout on "no server" just like the shell version
-    return 0
-  print("|".join("" if v is None else str(v) for v in row))
-  return 0
-
-if __name__ == "__main__":
-  sys.exit(main(sys.argv[1:]))
diff --git a/developer/source/tunnel-client/mothball/stage_wg_conf.py b/developer/source/tunnel-client/mothball/stage_wg_conf.py
deleted file mode 100644 (file)
index a395db2..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-#!/usr/bin/env python3
-# stage_wg_conf.py — write stage/wireguard/<iface>.conf
-
-from __future__ import annotations
-import sys, argparse
-from pathlib import Path
-
-def write_wg_conf(out: Path, addr: str, priv: str, mtu: str, fwmark: str,
-                  dns_mode: str, dns_servers: str,
-                  peer_pub: str, psk: str, host: str, port: str,
-                  allowed: str, keepalive: str) -> Path:
-  out.parent.mkdir(parents=True, exist_ok=True)
-  lines = [
-    "[Interface]",
-    f"Address = {addr}",
-    f"PrivateKey = {priv}",
-  ]
-  if mtu:    lines.append(f"MTU = {mtu}")
-  if fwmark: lines.append(f"FwMark = {fwmark}")
-  if dns_mode == "static" and dns_servers:
-    lines.append(f"DNS = {dns_servers}")
-  lines.append("Table = off")  # policy routing handled outside wg-quick
-  lines += [
-    "",
-    "[Peer]",
-    f"PublicKey = {peer_pub}",
-  ]
-  if psk: lines.append(f"PresharedKey = {psk}")
-  lines += [
-    f"Endpoint = {host}:{port}",
-    f"AllowedIPs = {allowed}",
-  ]
-  if keepalive: lines.append(f"PersistentKeepalive = {keepalive}")
-
-  out.write_text("\n".join(lines) + "\n")
-  out.chmod(0o400)
-  return out
-
-def main(argv):
-  ap = argparse.ArgumentParser()
-  ap.add_argument("out"); ap.add_argument("addr"); ap.add_argument("priv")
-  ap.add_argument("mtu"); ap.add_argument("fwmark")
-  ap.add_argument("dns_mode"); ap.add_argument("dns_servers")
-  ap.add_argument("peer_pub"); ap.add_argument("psk")
-  ap.add_argument("host"); ap.add_argument("port")
-  ap.add_argument("allowed"); ap.add_argument("keepalive")
-  args = ap.parse_args(argv)
-  out = write_wg_conf(Path(args.out), args.addr, args.priv, args.mtu, args.fwmark,
-                      args.dns_mode, args.dns_servers, args.peer_pub, args.psk,
-                      args.host, args.port, args.allowed, args.keepalive)
-  print(f"staged: {out}")
-  return 0
-
-if __name__ == "__main__":
-  sys.exit(main(sys.argv[1:]))
diff --git a/developer/source/tunnel-client/mothball/stage_wg_unit_IP_scripts.py b/developer/source/tunnel-client/mothball/stage_wg_unit_IP_scripts.py
deleted file mode 100644 (file)
index cef5abb..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-#!/usr/bin/env python3
-# stage_wg_unit_IP_scripts.py — write systemd unit override for wg-quick@IFACE
-
-from __future__ import annotations
-import sys
-from pathlib import Path
-
-def stage_dropin(iface: str) -> Path:
-  root = Path(__file__).resolve().parent
-  stage_root = root / "stage"
-  dropin_dir = stage_root / "etc" / "systemd" / f"wg-quick@{iface}.service.d"
-  dropin_dir.mkdir(parents=True, exist_ok=True)
-  conf = dropin_dir / "10-postup-IP-scripts.conf"
-  conf.write_text(
-    "[Service]\n"
-    "Restart=on-failure\n"
-    "RestartSec=5\n"
-    f"ExecStartPre=-/usr/sbin/ip link delete {iface}\n"
-    f"ExecStartPost=+/usr/local/bin/set_subu_IP_rules.sh\n"
-    f"ExecStartPost=+/usr/local/bin/route_init_{iface}.sh\n"
-    f"ExecStartPost=+/usr/bin/logger 'wg-quick@{iface} up: rules+route applied'\n"
-  )
-  return conf
-
-def main(argv):
-  if len(argv)!=1:
-    print(f"Usage: {Path(sys.argv[0]).name} <iface>", file=sys.stderr); return 2
-  p = stage_dropin(argv[0])
-  # print a "stage/..." relative path for consistency
-  root = Path(__file__).resolve().parent
-  rel = p.as_posix().replace(root.as_posix() + "/", "")
-  print(f"staged: {rel}")
-  return 0
-
-if __name__ == "__main__":
-  sys.exit(main(sys.argv[1:]))
diff --git a/developer/source/tunnel-client/mothball/stage_wipe.py b/developer/source/tunnel-client/mothball/stage_wipe.py
deleted file mode 100755 (executable)
index 161ab79..0000000
+++ /dev/null
@@ -1,97 +0,0 @@
-#!/usr/bin/env python3
-# stage_wipe.py — safely wipe ./stage contents (keeps hidden files by default)
-# Usage:
-#   ./stage_wipe.py [--yes] [--dry-run] [--hard]
-#
-# Notes:
-# - Default (no --hard): removes ONLY non-hidden entries in ./stage, keeps dotfiles like .gitignore.
-# - --hard: removes the stage directory itself (this will remove hidden files as well), then recreates it.
-
-from __future__ import annotations
-import argparse, shutil, sys, subprocess
-from pathlib import Path
-
-def stage_root() -> Path:
-  return Path(__file__).resolve().parent / "stage"
-
-def human_count_and_size(p: Path) -> tuple[int, str]:
-  try:
-    count = sum(1 for _ in p.rglob("*"))
-  except Exception:
-    count = 0
-  try:
-    cp = subprocess.run(["du", "-sh", p.as_posix()], text=True, capture_output=True)
-    size = cp.stdout.split()[0] if cp.returncode == 0 and cp.stdout else "?"
-  except Exception:
-    size = "?"
-  return count, size
-
-def wipe(yes: bool, dry_run: bool, hard: bool) -> int:
-  st = stage_root()
-  if not st.exists():
-    print(f"Nothing to wipe: {st} does not exist.")
-    return 0
-
-  # Path safety guard
-  safe_root = Path(__file__).resolve().parent / "stage"
-  if st.resolve() != safe_root.resolve():
-    print(f"Refusing: STAGE path looks unsafe: {st}", file=sys.stderr)
-    return 1
-
-  count, size = human_count_and_size(st)
-
-  if dry_run:
-    if hard:
-      print(f"DRY RUN — would remove the entire directory: {st} (items: {count}, size: ~{size})")
-    else:
-      print(f"DRY RUN — would remove NON-HIDDEN contents of: {st} (items: {count}, size: ~{size})")
-      for p in sorted(st.iterdir()):
-        if not p.name.startswith('.'):
-          print("  " + p.as_posix())
-    return 0
-
-  if not yes:
-    prompt = f"Permanently delete {'ALL of ' if hard else 'non-hidden entries in '}{st} (items: {count}, size: ~{size})? [y/N] "
-    try:
-      ans = input(prompt).strip()
-    except EOFError:
-      ans = ""
-    if ans.lower() not in ("y", "yes"):
-      print("Aborted.")
-      return 0
-
-  if hard:
-    # Remove entire stage directory (hidden files included), then recreate it
-    try:
-      shutil.rmtree(st)
-      print(f"Removed stage dir: {st}")
-    except Exception as e:
-      print(f"WARN: rmtree failed: {e}", file=sys.stderr)
-    st.mkdir(parents=True, exist_ok=True)
-  else:
-    # Remove only non-hidden entries; keep dotfiles like .gitignore
-    for p in list(st.iterdir()):
-      if p.name.startswith('.'):
-        continue  # preserve hidden files/dirs
-      try:
-        if p.is_dir():
-          shutil.rmtree(p)
-        else:
-          p.unlink(missing_ok=True)
-      except Exception as e:
-        print(f"WARN: failed to remove {p}: {e}", file=sys.stderr)
-    print(f"Cleared non-hidden contents of: {st}")
-
-  print("✅ Done.")
-  return 0
-
-def main(argv):
-  ap = argparse.ArgumentParser(description="Wipe the stage directory (keeps hidden files unless --hard).")
-  ap.add_argument("--yes", action="store_true", help="do not prompt")
-  ap.add_argument("--dry-run", action="store_true", help="show what would be removed, then exit")
-  ap.add_argument("--hard", action="store_true", help="remove the stage dir itself (also removes hidden files)")
-  args = ap.parse_args(argv)
-  return wipe(args.yes, args.dry_run, args.hard)
-
-if __name__ == "__main__":
-  sys.exit(main(sys.argv[1:]))