"""maclustr-dispatch — configuration centrale. Tout tourne SUR la passerelle (M1M32). Python 3.9 (système macOS) : pas de syntaxe > 3.9. """ import os USER = "simon-pierreboucher" HOME = os.path.expanduser("~") GATEWAY = "M1M32" DOMAIN = "maclustr.io" LAN_PREFIX = "192.168.2." SSH_KEY = os.path.join(HOME, ".ssh", "id_ed25519") # clé « maclustr-m1m32 », autorisée sur tous les nœuds STATE = os.path.join(HOME, "dispatch") # état : apps/, stage/, registry.json, lan.json, logs/ APPS_DIR = os.path.join(STATE, "apps") STAGE_DIR = os.path.join(STATE, "stage") LOG_DIR = os.path.join(STATE, "logs") REGISTRY = os.path.join(STATE, "registry.json") LAN_CACHE = os.path.join(STATE, "lan.json") SCAN_CACHE = os.path.join(STATE, "scan.json") NGROK_CFG = os.path.join(HOME, "Library", "Application Support", "ngrok", "ngrok.yml") RUN_DIR = "~/dispatch-run" # sur les nœuds : ecosystem PM2 générés SUDO_PW_FILE = os.path.join(STATE, ".sudo") # mot de passe sudo des nœuds (0600), pour `prepare` # Inventaire statique (alias -> cœurs, RAM Go, modèle). Les IP LAN sont découvertes en live. NODES = { "M3U96a": {"cores": 32, "ram_gb": 96, "model": "Mac Studio"}, "M3U96b": {"cores": 32, "ram_gb": 96, "model": "Mac Studio"}, "M2U64": {"cores": 24, "ram_gb": 64, "model": "Mac Studio"}, "M4M64a": {"cores": 16, "ram_gb": 64, "model": "Mac Studio"}, "M4M64b": {"cores": 16, "ram_gb": 64, "model": "Mac Studio"}, "M4BP48": {"cores": 16, "ram_gb": 48, "model": "MacBook Pro"}, "M4BP36": {"cores": 14, "ram_gb": 36, "model": "MacBook Pro"}, "M4M36": {"cores": 14, "ram_gb": 36, "model": "Mac Studio"}, "M2M32": {"cores": 12, "ram_gb": 32, "model": "Mac Studio"}, "M2M32b": {"cores": 12, "ram_gb": 32, "model": "Mac Studio"}, "M2M32c": {"cores": 12, "ram_gb": 32, "model": "Mac Studio"}, "m4mc": {"cores": 12, "ram_gb": 24, "model": "Mac mini"}, "M1M32": {"cores": 10, "ram_gb": 32, "model": "Mac Studio"}, "m4ma": {"cores": 10, "ram_gb": 24, "model": "Mac mini"}, "m4mb": {"cores": 10, "ram_gb": 16, "model": "Mac mini"}, "m2m16": {"cores": 10, "ram_gb": 16, "model": "Mac mini"}, "M3BA24": {"cores": 8, "ram_gb": 24, "model": "MacBook Air"}, "M3BA16": {"cores": 8, "ram_gb": 16, "model": "MacBook Air"}, "m2m8a": {"cores": 8, "ram_gb": 8, "model": "Mac mini"}, "m2m8b": {"cores": 8, "ram_gb": 8, "model": "Mac mini"}, "m4md": {"cores": 10, "ram_gb": 16, "model": "Mac mini"}, # ajouté 2026-09-04 (Mac16,10, M4, 100.78.227.79) "M1M64": {"cores": 10, "ram_gb": 64, "model": "Mac Studio"}, # ajouté 2026-09-08 (Mac13,1, M1 Max, loué, hors LAN — voir REMOTE_NODES) "m2m16b": {"cores": 10, "ram_gb": 16, "model": "Mac mini"}, # ajouté 2026-09-08 (Mac14,12, M2 Pro, loué, hors LAN — voir REMOTE_NODES) "m1m16": {"cores": 8, "ram_gb": 16, "model": "Mac mini"}, # ajouté 2026-09-08 (Macmini9,1, M1, loué, hors LAN — voir REMOTE_NODES) "m2m16c": {"cores": 8, "ram_gb": 16, "model": "Mac mini"}, # ajouté 2026-09-08 (Mac14,3, M2, loué, hors LAN — voir REMOTE_NODES) } # Nœuds HORS LAN : IP publique fixe + autre utilisateur + autre mot de passe sudo. Ils ne sont pas trouvés par le # balayage 192.168.2.x : `discover` s'y connecte directement et vérifie le marqueur ~/.maclustr-node. REMOTE_NODES = { "M1M64": {"host": "45.74.241.221", "user": "simon", "sudo_pw_file": os.path.join(STATE, ".sudo-M1M64")}, "m2m16b": {"host": "208.52.154.68", "user": "administrator", "sudo_pw_file": os.path.join(STATE, ".sudo-m2m16b")}, "m1m16": {"host": "207.254.29.71", "user": "administrator", "sudo_pw_file": os.path.join(STATE, ".sudo-m1m16")}, "m2m16c": {"host": "207.254.29.128", "user": "administrator", "sudo_pw_file": os.path.join(STATE, ".sudo-m2m16c")}, } def _remote(key): for alias, r in REMOTE_NODES.items(): if key in (alias, r["host"]): return r return None def user_of(key=None): """Utilisateur SSH d'un nœud (alias ou IP) — simon-pierreboucher partout sauf REMOTE_NODES.""" r = _remote(key) if key else None return r["user"] if r else USER def home_of(key=None): return "/Users/%s" % user_of(key) def sudo_pw_file(key=None): r = _remote(key) if key else None return r["sudo_pw_file"] if r else SUDO_PW_FILE # Nœuds réservés : inéligibles au placement automatique (sauf app épinglée dessus). RESERVED = { "M3U96b": "réservé à hfmarketdata (lac FirstRate Data) depuis le 2026-09-04", "M1M64": "nœud distant hors LAN (loué, user simon) : pas de LAN 192.168.2.x ni de gardiens Ka → placement seulement avec --node", "m2m16b": "nœud distant hors LAN (loué, user administrator) : pas de LAN 192.168.2.x ni de gardiens Ka → placement seulement avec --node", "m1m16": "nœud distant hors LAN (loué, user administrator) : pas de LAN 192.168.2.x ni de gardiens Ka → placement seulement avec --node", "m2m16c": "nœud distant hors LAN (loué, user administrator) : pas de LAN 192.168.2.x ni de gardiens Ka → placement seulement avec --node", } # Rôles : gateway = jamais d'app ; worker = éligible. Un nœud absent du scan est « offline ». ROLES = {alias: ("gateway" if alias == GATEWAY else "worker") for alias in NODES} # Runtimes que `prepare` sait installer via Homebrew, et comment les détecter sur un nœud. RUNTIME_CHECKS = { "node": "test -x /opt/homebrew/bin/node", "pnpm": "test -x /opt/homebrew/bin/pnpm", "pm2": "test -x /opt/homebrew/bin/pm2", "ngrok": "(test -x /opt/homebrew/bin/ngrok || test -x $HOME/bin/ngrok) && test -f \"$HOME/Library/Application Support/ngrok/ngrok.yml\"", "python@3.14": "test -x /opt/homebrew/opt/python@3.14/bin/python3.14", "python@3.13": "test -x /opt/homebrew/opt/python@3.13/bin/python3.13", "python@3.12": "test -x /opt/homebrew/opt/python@3.12/bin/python3.12", "uv": "test -x /opt/homebrew/bin/uv || test -x $HOME/.local/bin/uv", "uv-python@3.12": "ls -d $HOME/.local/share/uv/python/cpython-3.12* >/dev/null 2>&1", "redis": "test -x /opt/homebrew/bin/redis-server && redis-cli ping >/dev/null 2>&1", "docker": "test -x /opt/homebrew/bin/docker", "postgresql@17": "test -x /opt/homebrew/opt/postgresql@17/bin/postgres", "brew": "test -x /opt/homebrew/bin/brew", "git": "test -x /opt/homebrew/bin/git || test -x /usr/bin/git", } BREW_FORMULAE = { "node": "node", "pnpm": "pnpm", "pm2": "pm2", "python@3.14": "python@3.14", "python@3.13": "python@3.13", "python@3.12": "python@3.12", "uv": "uv", "redis": "redis", "docker": "docker", "postgresql@17": "postgresql@17", "git": "git", } BREW_CASKS = {"ngrok": "ngrok"} os.makedirs(APPS_DIR, exist_ok=True) os.makedirs(STAGE_DIR, exist_ok=True) os.makedirs(LOG_DIR, exist_ok=True)