spb/maclustr-dispatch
Public
Python 95.8%
Shell 4.2%
1"""maclustr-dispatch — configuration centrale.23Tout tourne SUR la passerelle (M1M32). Python 3.9 (système macOS) : pas de syntaxe > 3.9.4"""5import os67USER = "simon-pierreboucher"8HOME = os.path.expanduser("~")9GATEWAY = "M1M32"10DOMAIN = "maclustr.io"11LAN_PREFIX = "192.168.2."12SSH_KEY = os.path.join(HOME, ".ssh", "id_ed25519") # clé « maclustr-m1m32 », autorisée sur tous les nœuds13STATE = os.path.join(HOME, "dispatch") # état : apps/, stage/, registry.json, lan.json, logs/14APPS_DIR = os.path.join(STATE, "apps")15STAGE_DIR = os.path.join(STATE, "stage")16LOG_DIR = os.path.join(STATE, "logs")17REGISTRY = os.path.join(STATE, "registry.json")18LAN_CACHE = os.path.join(STATE, "lan.json")19SCAN_CACHE = os.path.join(STATE, "scan.json")20RUN_DIR = "~/dispatch-run" # sur les nœuds : ecosystem PM2 générés21SUDO_PW_FILE = os.path.join(STATE, ".sudo") # mot de passe sudo des nœuds (0600), pour `prepare`22HEAL_STATE = os.path.join(STATE, "heal-state.json") # mémoire de `mld heal` (actions récentes, nœuds en panne)2324# MacLustr Tunnel (remplace ngrok depuis le 2026-09-10) : passerelles OVH = hub WireGuard + Caddy (TLS auto), pilotées25# par `tunnelctl` (sudo sans mot de passe pour ubuntu). La clé de la passerelle M1M32 est autorisée pour `ubuntu`.26# Chaque Mac porte une interface par passerelle (wg1 → BHS64, wg0 → R9128), LaunchDaemon io.maclustr.wireguard-<iface>.27TUNNEL_GATEWAYS = {28 "BHS64": {"host": "51.161.112.61", "user": "ubuntu", "subnet": "10.67.0", "iface": "wg1", "endpoint": "51.161.112.61:51820", "place": "OVH Beauharnois (Québec)"},29 "R9128": {"host": "51.255.75.61", "user": "ubuntu", "subnet": "10.66.0", "iface": "wg0", "endpoint": "51.255.75.61:51820", "place": "OVH Gravelines (France)"},30}31TUNNEL_DEFAULT = "BHS64"3233# Inventaire statique (alias -> cœurs, RAM Go, modèle). Les IP LAN sont découvertes en live.34NODES = {35 "M3U96a": {"cores": 32, "ram_gb": 96, "model": "Mac Studio"},36 "M3U96b": {"cores": 32, "ram_gb": 96, "model": "Mac Studio"},37 "M2U64": {"cores": 24, "ram_gb": 64, "model": "Mac Studio"},38 "M4M64a": {"cores": 16, "ram_gb": 64, "model": "Mac Studio"},39 "M4M64b": {"cores": 16, "ram_gb": 64, "model": "Mac Studio"},40 "M4BP48": {"cores": 16, "ram_gb": 48, "model": "MacBook Pro"},41 "M4BP36": {"cores": 14, "ram_gb": 36, "model": "MacBook Pro"},42 "M4M36": {"cores": 14, "ram_gb": 36, "model": "Mac Studio"},43 "M2M32": {"cores": 12, "ram_gb": 32, "model": "Mac Studio"},44 "M2M32b": {"cores": 12, "ram_gb": 32, "model": "Mac Studio"},45 "M2M32c": {"cores": 12, "ram_gb": 32, "model": "Mac Studio"},46 "m4mc": {"cores": 12, "ram_gb": 24, "model": "Mac mini"},47 "M1M32": {"cores": 10, "ram_gb": 32, "model": "Mac Studio"},48 "m4ma": {"cores": 10, "ram_gb": 24, "model": "Mac mini"},49 "m4mb": {"cores": 10, "ram_gb": 16, "model": "Mac mini"},50 "m2m16": {"cores": 10, "ram_gb": 16, "model": "Mac mini"},51 "M3BA24": {"cores": 8, "ram_gb": 24, "model": "MacBook Air"},52 "M3BA16": {"cores": 8, "ram_gb": 16, "model": "MacBook Air"},53 "m2m8a": {"cores": 8, "ram_gb": 8, "model": "Mac mini"},54 "m2m8b": {"cores": 8, "ram_gb": 8, "model": "Mac mini"},55 "m4md": {"cores": 10, "ram_gb": 16, "model": "Mac mini"}, # ajouté 2026-09-04 (Mac16,10, M4, 100.78.227.79)56 "M1M64": {"cores": 10, "ram_gb": 64, "model": "Mac Studio"}, # ajouté 2026-09-08 (Mac13,1, M1 Max, loué, hors LAN — voir REMOTE_NODES)57 "m2m16b": {"cores": 10, "ram_gb": 16, "model": "Mac mini"}, # ajouté 2026-09-08 (Mac14,12, M2 Pro, loué, hors LAN — voir REMOTE_NODES)58 "m1m16": {"cores": 8, "ram_gb": 16, "model": "Mac mini"}, # ajouté 2026-09-08 (Macmini9,1, M1, loué, hors LAN — voir REMOTE_NODES)59 "m2m16c": {"cores": 8, "ram_gb": 16, "model": "Mac mini"}, # ajouté 2026-09-08 (Mac14,3, M2, loué, hors LAN — voir REMOTE_NODES)60 "m4me": {"cores": 10, "ram_gb": 16, "model": "Mac mini"}, # ajouté 2026-09-08 (Mac16,10, M4, loué Macly, hors LAN — voir REMOTE_NODES)61 "m4mf": {"cores": 10, "ram_gb": 16, "model": "Mac mini"}, # ajouté 2026-09-09 (Mac16,10, M4, loué Macly, hors LAN — voir REMOTE_NODES)62 "m4mg": {"cores": 10, "ram_gb": 16, "model": "Mac mini"}, # ajouté 2026-09-09 (Mac16,10, M4, loué rentamac, Chypre, via gate1.rentamac.io:27335 — voir REMOTE_NODES)63 "m4mh": {"cores": 10, "ram_gb": 16, "model": "Mac mini"}, # ajouté 2026-09-10 (Mac16,10, M4, loué Macly, hors LAN — voir REMOTE_NODES)64 "m1m16b": {"cores": 8, "ram_gb": 16, "model": "Mac mini"}, # ajouté 2026-09-10 (Macmini9,1, M1 — vendu comme M4 par Macly, hors LAN — voir REMOTE_NODES)65 "m4mi": {"cores": 10, "ram_gb": 16, "model": "Mac mini"}, # ajouté 2026-09-10 (Mac16,10, M4, loué Macly, hors LAN — voir REMOTE_NODES)66 "m4mj": {"cores": 10, "ram_gb": 16, "model": "Mac mini"}, # ajouté 2026-09-10 (Mac16,10, M4, loué Macly, hors LAN — voir REMOTE_NODES)67 "m4mk": {"cores": 10, "ram_gb": 16, "model": "Mac mini"}, # ajouté 2026-09-10 (Mac16,10, M4, loué Macly, hors LAN — voir REMOTE_NODES)68 "m4ml": {"cores": 10, "ram_gb": 16, "model": "Mac mini"}, # ajouté 2026-09-21 (Mac16,10, M4, LAN, Tailscale 100.70.177.46, S/N VLXC9XWJLY)69 "m4mm": {"cores": 12, "ram_gb": 48, "model": "Mac mini"}, # ajouté 2026-09-21 (Mac16,11, M4 Pro, LAN, Tailscale 100.115.250.59, S/N DYH4X6RW3R)70 "m4mn": {"cores": 12, "ram_gb": 48, "model": "Mac mini"}, # ajouté 2026-09-21 (Mac16,11, M4 Pro, LAN, Tailscale 100.95.88.71, S/N J322FLVRW6)71}7273# Nœuds HORS LAN : IP publique fixe + autre utilisateur + autre mot de passe sudo. Ils ne sont pas trouvés par le74# balayage 192.168.2.x : `discover` s'y connecte directement et vérifie le marqueur ~/.maclustr-node.75REMOTE_NODES = {76 "M1M64": {"host": "45.74.241.221", "user": "simon", "sudo_pw_file": os.path.join(STATE, ".sudo-M1M64")},77 "m2m16b": {"host": "208.52.154.68", "user": "administrator", "sudo_pw_file": os.path.join(STATE, ".sudo-m2m16b")},78 "m1m16": {"host": "207.254.29.71", "user": "administrator", "sudo_pw_file": os.path.join(STATE, ".sudo-m1m16")},79 "m2m16c": {"host": "207.254.29.128", "user": "administrator", "sudo_pw_file": os.path.join(STATE, ".sudo-m2m16c")},80 "m4me": {"host": "45.74.245.36", "user": "simon", "sudo_pw_file": os.path.join(STATE, ".sudo-m4me")},81 "m4mf": {"host": "45.74.245.37", "user": "simon", "sudo_pw_file": os.path.join(STATE, ".sudo-m4mf")},82 # m4mg : Mac derrière NAT chez rentamac ; joint via la passerelle SSH du fournisseur. Le port 27335 est porté par83 # ~/.ssh/config de la passerelle M1M32 (`Host gate1.rentamac.io` → Port 27335), mld n'a pas de notion de port.84 "m4mg": {"host": "gate1.rentamac.io", "user": "rentamac", "sudo_pw_file": os.path.join(STATE, ".sudo-m4mg")},85 "m4mh": {"host": "45.74.241.182", "user": "simon", "sudo_pw_file": os.path.join(STATE, ".sudo-m4mh")},86 "m1m16b": {"host": "45.74.241.171", "user": "simon", "sudo_pw_file": os.path.join(STATE, ".sudo-m1m16b")},87 "m4mi": {"host": "45.74.245.39", "user": "simon", "sudo_pw_file": os.path.join(STATE, ".sudo-m4mi")},88 "m4mj": {"host": "45.74.245.38", "user": "simon", "sudo_pw_file": os.path.join(STATE, ".sudo-m4mj")},89 "m4mk": {"host": "45.74.241.98", "user": "simon", "sudo_pw_file": os.path.join(STATE, ".sudo-m4mk")},90}919293def _remote(key):94 for alias, r in REMOTE_NODES.items():95 if key in (alias, r["host"]):96 return r97 return None9899100def user_of(key=None):101 """Utilisateur SSH d'un nœud (alias ou IP) — simon-pierreboucher partout sauf REMOTE_NODES."""102 r = _remote(key) if key else None103 return r["user"] if r else USER104105106def home_of(key=None):107 return "/Users/%s" % user_of(key)108109110def sudo_pw_file(key=None):111 r = _remote(key) if key else None112 return r["sudo_pw_file"] if r else SUDO_PW_FILE113114115# Nœuds réservés : inéligibles au placement automatique (sauf app épinglée dessus).116RESERVED = {117 "M3U96b": "réservé à hfmarketdata (lac FirstRate Data) depuis le 2026-09-04",118 "M1M64": "nœud distant hors LAN (loué, user simon) : pas de LAN 192.168.2.x ni de gardiens Ka → placement seulement avec --node",119 "m2m16b": "nœud distant hors LAN (loué, user administrator) : pas de LAN 192.168.2.x ni de gardiens Ka → placement seulement avec --node",120 "m1m16": "nœud distant hors LAN (loué, user administrator) : pas de LAN 192.168.2.x ni de gardiens Ka → placement seulement avec --node",121 "m2m16c": "nœud distant hors LAN (loué, user administrator) : pas de LAN 192.168.2.x ni de gardiens Ka → placement seulement avec --node",122 "m4me": "nœud distant hors LAN (loué Macly, user simon) : pas de LAN 192.168.2.x ni de gardiens Ka → placement seulement avec --node",123 "m4mf": "nœud distant hors LAN (loué Macly, user simon) : pas de LAN 192.168.2.x ni de gardiens Ka → placement seulement avec --node",124 "m4mg": "nœud distant hors LAN (loué rentamac, user rentamac, via gate1.rentamac.io:27335) : pas de LAN ni de gardiens Ka → placement seulement avec --node",125 "m4mh": "nœud distant hors LAN (loué Macly, user simon) : pas de LAN 192.168.2.x ni de gardiens Ka → placement seulement avec --node",126 "m1m16b": "nœud distant hors LAN (loué Macly, user simon, M1) : pas de LAN 192.168.2.x ni de gardiens Ka → placement seulement avec --node",127 "m4mi": "nœud distant hors LAN (loué Macly, user simon) : pas de LAN 192.168.2.x ni de gardiens Ka → placement seulement avec --node",128 "m4mj": "nœud distant hors LAN (loué Macly, user simon) : pas de LAN 192.168.2.x ni de gardiens Ka → placement seulement avec --node",129 "m4mk": "nœud distant hors LAN (loué Macly, user simon) : pas de LAN 192.168.2.x ni de gardiens Ka → placement seulement avec --node",130}131132# Rôles : gateway = jamais d'app ; worker = éligible. Un nœud absent du scan est « offline ».133ROLES = {alias: ("gateway" if alias == GATEWAY else "worker") for alias in NODES}134135# Runtimes que `prepare` sait installer via Homebrew, et comment les détecter sur un nœud.136RUNTIME_CHECKS = {137 "node": "test -x /opt/homebrew/bin/node",138 "pnpm": "test -x /opt/homebrew/bin/pnpm",139 "pm2": "test -x /opt/homebrew/bin/pm2",140 # raccordé au MacLustr Tunnel (wg1 → BHS64 monté) : informatif, `mld deploy` raccorde lui-même un nœud qui ne l'est pas141 "tunnel": "test -f /Library/LaunchDaemons/io.maclustr.wireguard-wg1.plist && ifconfig 2>/dev/null | grep -q 'inet 10\\.67\\.0\\.'",142 "python@3.14": "test -x /opt/homebrew/opt/python@3.14/bin/python3.14",143 "python@3.13": "test -x /opt/homebrew/opt/python@3.13/bin/python3.13",144 "python@3.12": "test -x /opt/homebrew/opt/python@3.12/bin/python3.12",145 "uv": "test -x /opt/homebrew/bin/uv || test -x $HOME/.local/bin/uv",146 "uv-python@3.12": "ls -d $HOME/.local/share/uv/python/cpython-3.12* >/dev/null 2>&1",147 "redis": "test -x /opt/homebrew/bin/redis-server && redis-cli ping >/dev/null 2>&1",148 "docker": "test -x /opt/homebrew/bin/docker",149 "postgresql@17": "test -x /opt/homebrew/opt/postgresql@17/bin/postgres",150 "brew": "test -x /opt/homebrew/bin/brew",151 "git": "test -x /opt/homebrew/bin/git || test -x /usr/bin/git",152}153BREW_FORMULAE = {154 "node": "node", "pnpm": "pnpm", "pm2": "pm2", "python@3.14": "python@3.14", "python@3.13": "python@3.13",155 "python@3.12": "python@3.12", "uv": "uv", "redis": "redis", "docker": "docker", "postgresql@17": "postgresql@17", "git": "git",156}157BREW_CASKS = {} # ngrok (cask) retiré le 2026-09-10 : l'exposition publique passe par TUNNEL_GATEWAYS158159os.makedirs(APPS_DIR, exist_ok=True)160os.makedirs(STAGE_DIR, exist_ok=True)161os.makedirs(LOG_DIR, exist_ok=True)162