[READ-ONLY] Mirror of https://github.com/mrgnw/scripts.
1#!/usr/bin/env -S uv run --script
2# /// script
3# requires-python = ">=3.14"
4# dependencies = ["pyyaml"]
5# ///
6
7import subprocess
8import sys
9from pathlib import Path
10from concurrent.futures import ThreadPoolExecutor, as_completed
11import yaml
12
13CONFIG = Path.home() / ".cloudflared" / "config.yml"
14
15def register(tunnel: str, hostname: str) -> tuple[str, bool, str]:
16 result = subprocess.run(
17 ["cloudflared", "tunnel", "route", "dns", tunnel, hostname],
18 capture_output=True,
19 text=True,
20 )
21 combined = result.stdout + result.stderr
22 ok = result.returncode == 0 or "already exists" in combined.lower()
23 return hostname, ok, combined.strip()
24
25def main():
26 config = yaml.safe_load(CONFIG.read_text())
27 tunnel = config["tunnel"]
28
29 hostnames = [
30 rule["hostname"]
31 for rule in config.get("ingress", [])
32 if "hostname" in rule
33 ]
34
35 if not hostnames:
36 print("No hostnames found in config.yml")
37 return
38
39 print(f"Tunnel: {tunnel}")
40 print(f"Syncing {len(hostnames)} DNS routes:\n")
41
42 ok = []
43 failed = []
44
45 with ThreadPoolExecutor(max_workers=10) as pool:
46 futures = {pool.submit(register, tunnel, h): h for h in hostnames}
47 for future in as_completed(futures):
48 hostname, success, output = future.result()
49 if success:
50 print(f" ✓ {hostname}")
51 ok.append(hostname)
52 else:
53 print(f" ✗ {hostname}")
54 print(f" {output}")
55 failed.append(hostname)
56
57 print(f"\n{len(ok)} ok, {len(failed)} failed")
58 if failed:
59 sys.exit(1)
60
61if __name__ == "__main__":
62 main()