SPB Git
7.4 KB · 130 lines python
1"""`mld prepare <nœud>` : installe les runtimes manquants (Homebrew), copie la config ngrok, active pm2 au démarrage, pose le marqueur."""2import os3import shlex4from . import config, nodes, ssh567def _sudo_pw():8    if os.path.exists(config.SUDO_PW_FILE):9        return open(config.SUDO_PW_FILE).read().strip()10    return None111213def prepare(alias, runtimes=None, ka_helpers=False):14    ip = nodes.ip_of(alias)15    if not ip:16        raise SystemExit("nœud %s injoignable" % alias)17    info = nodes.probe(alias, ip)18    have = info.get("runtimes", {})19    want = runtimes or ["node", "pnpm", "pm2", "ngrok", "python@3.14", "python@3.13", "uv", "git"]20    print("prepare %s (%s) — présents : %s" % (alias, ip, ", ".join(k for k, v in have.items() if v)))21    if not have.get("brew"):22        print("  ✗ Homebrew absent : bootstrap requis (Xcode CLT). Lancer d'abord :  mld bootstrap %s" % alias)23        return False24    missing = [r for r in want if not have.get(r)]25    script = ""26    for r in missing:27        if r in config.BREW_FORMULAE:28            fallback = " || npm install -g pm2 >/dev/null 2>&1" if r == "pm2" else ""29            script += "echo '  brew install %s'; brew install -q %s >/dev/null 2>&1%s || echo '   ! échec %s'\n" % (r, config.BREW_FORMULAE[r], fallback, r)30        elif r in config.BREW_CASKS:31            script += "echo '  brew install --cask ngrok'; brew install -q --cask ngrok >/dev/null 2>&1 || brew install -q ngrok/ngrok/ngrok >/dev/null 2>&1 || echo '   ! échec ngrok'\n"32    # config ngrok (authtoken) depuis la passerelle33    if "ngrok" in missing or not have.get("ngrok"):34        if os.path.exists(config.NGROK_CFG):35            rc0, _, _ = ssh.write_remote_file(ip, "/Users/%s/Library/Application Support/ngrok/ngrok.yml" % config.USER, open(config.NGROK_CFG).read(), mode="600")36            script += "echo '  ngrok.yml %s'\n" % ("copié" if rc0 == 0 else "ÉCHEC de copie")37    # marqueur38    script += "[ -f ~/.maclustr-node ] || echo %s > ~/.maclustr-node\n" % alias39    # pm2 startup (launchd) — nécessite sudo40    pw = _sudo_pw()41    script += "if ! ls ~/Library/LaunchAgents 2>/dev/null | grep -qi pm2; then cmd=$(pm2 startup launchd -u %s --hp /Users/%s 2>/dev/null | grep '^sudo' | head -1); if [ -n \"$cmd\" ]; then %s echo \"  pm2 startup: $( ${cmd#sudo } >/dev/null 2>&1 && echo ok || echo 'à faire manuellement')\"; fi; fi\n" % (42        config.USER, config.USER, ("echo %s | sudo -S -v 2>/dev/null;" % shlex.quote(pw)) if pw else "")43    rc, out, err = ssh.run(ip, script, timeout=1800)44    if ka_helpers:45        out += ka_helpers_install(alias, ip)46    print(out)47    if err.strip():48        print("  stderr:", err.strip()[-300:])49    info = nodes.probe(alias, ip)50    still = [r for r in want if not info.get("runtimes", {}).get(r)]51    print("  résultat : %s" % ("tout est là" if not still else "manquent encore " + ", ".join(still)))52    return not still535455def bootstrap(alias):56    """Installe Xcode CLT + Homebrew sans GUI. Active un sudo NOPASSWD temporaire (retiré à la fin). Long (plusieurs Go)."""57    ip = nodes.ip_of(alias)58    pw = _sudo_pw()59    if not pw:60        raise SystemExit("mot de passe sudo requis dans %s" % config.SUDO_PW_FILE)61    script = r'''62export LC_ALL=C63echo %s | sudo -S -p '' sh -c 'echo "%s ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/mld-bootstrap && chmod 440 /etc/sudoers.d/mld-bootstrap'64trap 'sudo rm -f /etc/sudoers.d/mld-bootstrap' EXIT65if ! xcode-select -p >/dev/null 2>&1; then66  echo '  installation Command Line Tools...'67  touch /tmp/.com.apple.dt.CommandLineTools.installondemand.in-progress68  PROD=$(softwareupdate -l 2>&1 | grep -oE 'Label: Command Line Tools for Xcode[^,]*' | sed 's/^Label: //' | sort -V | tail -1)69  echo "  produit: ${PROD:-introuvable}"70  if [ -n "$PROD" ]; then sudo softwareupdate -i "$PROD" --verbose 2>&1 | tail -3; fi71  rm -f /tmp/.com.apple.dt.CommandLineTools.installondemand.in-progress72  sudo xcode-select --switch /Library/Developer/CommandLineTools 2>/dev/null || true73fi74if [ ! -x /opt/homebrew/bin/brew ]; then75  echo '  installation Homebrew...'76  NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" 2>&1 | tail -477  grep -q 'opt/homebrew/bin/brew shellenv' ~/.zprofile 2>/dev/null || echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile78fi79echo "  CLT: $(xcode-select -p 2>/dev/null || echo absent)  brew: $(/opt/homebrew/bin/brew --version 2>/dev/null | head -1 || echo absent)"80''' % (shlex.quote(pw), config.USER)81    rc, out, err = ssh.run(ip, script, timeout=5400)82    print(out)83    if err.strip():84        print("  stderr:", err.strip()[-400:])85    return rc == 0868788def gateway_mdns():89    import subprocess90    name = subprocess.run(["scutil", "--get", "LocalHostName"], capture_output=True, text=True).stdout.strip()91    return (name + ".local") if name else nodes.load_lan()["ips"].get(config.GATEWAY, "")929394def ka_helpers_install(alias, ip):95    """Pousseur Ka (launchd zsh → git push vers spbgit) + alias SSH `gitsrv` → passerelle + clé autorisée sur la passerelle."""96    assets = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "assets")97    out = ["  ka-helpers sur %s :" % alias]98    ssh.write_remote_file(ip, "/Users/%s/ka-pousseur.sh" % config.USER, open(os.path.join(assets, "ka-pousseur.sh")).read(), mode="755")99    ssh.write_remote_file(ip, "/Users/%s/Library/LaunchAgents/com.ka.pousseur.plist" % config.USER, open(os.path.join(assets, "com.ka.pousseur.plist")).read(), mode="644")100    mdns = gateway_mdns()101    block = "Host gitsrv\n  HostName %s\n  User %s\n  IdentityFile ~/.ssh/ka_guardian_ed25519\n  IdentitiesOnly yes\n  ControlMaster no\n  ControlPath none\n  StrictHostKeyChecking no\n  UserKnownHostsFile /dev/null\n  LogLevel ERROR\n\n" % (mdns, config.USER)102    script = r"""103mkdir -p ~/.ssh ~/ka-guardian-spool; touch ~/.ka-pousseur-repos; chmod 700 ~/.ssh104[ -f ~/.ssh/ka_guardian_ed25519 ] || ssh-keygen -q -t ed25519 -N '' -C "ka-guardian@$(cat ~/.maclustr-node 2>/dev/null || hostname -s)" -f ~/.ssh/ka_guardian_ed25519105# alias gitsrv en TÊTE du config (avant Host *) — remplace un bloc existant106python3 - <<'PYEOF'107import os,re108p=os.path.expanduser('~/.ssh/config'); s=open(p).read() if os.path.exists(p) else ''109s=re.sub(r'(?ms)^Host gitsrv\n(?:[ \t]+.*\n?)*\n?', '', s)110open(p,'w').write(%r + s); os.chmod(p, 0o600); print('  gitsrv -> %s')111PYEOF112cat ~/.ssh/ka_guardian_ed25519.pub113launchctl bootout gui/$(id -u)/com.ka.pousseur >/dev/null 2>&1; launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.ka.pousseur.plist && echo '  pousseur launchd chargé'114""" % (block, mdns)115    rc, o, e = ssh.run(ip, script, timeout=120)116    pub = [l for l in o.splitlines() if l.startswith("ssh-ed25519")]117    out += ["  " + l for l in o.splitlines() if not l.startswith("ssh-ed25519")]118    if pub:119        ak = os.path.expanduser("~/.ssh/authorized_keys")120        cur = open(ak).read() if os.path.exists(ak) else ""121        if pub[0].split()[1] not in cur:122            with open(ak, "a") as f:123                f.write(pub[0].strip() + "\n")124            out.append("  clé %s autorisée sur la passerelle" % (pub[0].split()[-1] if len(pub[0].split()) > 2 else alias))125        else:126            out.append("  clé déjà autorisée sur la passerelle")127    rc, o, e = ssh.run(ip, "ssh -o BatchMode=yes -o ConnectTimeout=6 gitsrv 'echo ok' 2>&1 | tail -1", timeout=30)128    out.append("  test ssh gitsrv : %s" % (o.strip() or e.strip()[-80:]))129    return "\n".join(out) + "\n"130
Files · 12078944da76aa6b92a218f2ffb4e258777fecd8
Menu

Theme