nas-runbooks/common/mcp-vps.md

5.7 KiB

mcp-vps — MCP Linux sur le VPS memory-node (12/07/2026)

Statut : production. Rôle : organe d'action de Claude pendant un blackout NAS (complète le nœud mémoire — voir common/vps-memory-node.md). Zéro dépendance NAS au runtime.

Accès

  • URL publique : https://mcp.yesminedor.tn/<MCP_PATH_SECRET>/mcp — Cloudflare Tunnel, aucun port ouvert sur l'IP Contabo. Le secret (64 hex) est dans /opt/memory-node/mcp-vps/.env (600) et chez Nabil (Bitwarden/Telegram). /mcp sans secret → 404.
  • Connecteur claude.ai « vps » : URL complète avec secret, champs OAuth vides (même pattern que mcp-nas : le connecteur ne sait pas envoyer de header Authorization — Cloudflare Access écarté pour cette raison).
  • Alerte Telegram à chaque nouvelle session MCP (POST /mcp sans header mcp-session-id), anti-spam 60 s, non bloquante. Même bot que les alertes SSH claude-oversight. Audit : NEW_SESSION | ip=<CF-Connecting-IP> dans audit.log.

Architecture

  • Code : /opt/memory-node/mcp-vps (git local = source de vérité en blackout ; miroir Gitea bolbol/mcp-vps). Dérivé de bolbol/linux-mcp-nas (run_command v2, _redact audit, monkey-patch output_schema, fix lifespan — voir runbooks mcp-nas).
  • Stack compose (2 services, réseau bridge dédié mcp-vps, aucun port publié) :
    • linux-mcp-vps : build local, init, restart: always, cap_drop: ALL, no-new-privileges, healthcheck GET /health /30s, dns: 1.1.1.1.
    • cloudflared-mcp-vps : tunnel --no-autoupdate run, TUNNEL_TOKEN via .env.
  • Volumes : /opt/memory-node/git et /opt/memory-node/data en ro/mnt/memory-node/{git,data} (miroir bare nas-runbooks + répliques nyora-notes/context-hub ; subdirs seulement : n'expose ni .restic-pass ni les .env hôte) ; ./data RW (audit.log, root:root 700) ; ./ssh-claude/root/.ssh (clé persistée au recreate).
  • Outils MCP : run_command, send_keys, capture_pane, list/new/kill/reset_session. PAS d'outils NyoraNotes (service NAS) : lire les répliques via run_command.
  • Actions host : ssh vps.internal '<cmd>' (= claude-oversight@hôte via host-gateway, clé ed25519 dédiée mcp-vps-claude, authorized_keys restreinte from="172.16.0.0/12", sudoers scopé inchangé — jamais NOPASSWD:ALL).
  • Firewall : ufw allow proto tcp from 172.16.0.0/12 to any port 22 (containers→host ssh).

Cloudflare (zone + tunnel)

  • Zone yesminedor.tn (plan Free) migrée OVH→Cloudflare le 11/07/2026 (NS kallie/valentin.ns.cloudflare.com). Compte CF f47839fbf54ef0a5ed89db20e9a02902.
  • Tunnel mcp-vps id 700c0fe6-2b2c-4e1a-abfa-3f94f2f7c909, config gérée côté Cloudflare (config_src: cloudflare), ingress mcp.yesminedor.tn → http://linux-mcp-vps:8000, fallback 404. DNS : CNAME proxié mcp → <tunnel-id>.cfargotunnel.com (créé au dashboard, le token API n'a pas la permission zone).
  • Token API CF (cfat_…, account-owned, chez Nabil) — opérations :
    # créer      : POST /accounts/<acc>/cfd_tunnel  {"name":"…","config_src":"cloudflare"}
    # ingress    : PUT  /accounts/<acc>/cfd_tunnel/<id>/configurations
    # token      : GET  /accounts/<acc>/cfd_tunnel/<id>/token   → TUNNEL_TOKEN .env
    # état       : GET  /accounts/<acc>/cfd_tunnel/<id>         → status: healthy
    
    ⚠️ un token account-owned (cfat_) se vérifie sur /accounts/<acc>/tokens/verify, PAS /user/tokens/verify (qui renvoie « Invalid API Token »).

Rotations

  • Chemin secret : openssl rand -hex 32.envdocker compose up -d → MAJ connecteur claude.ai.
  • TUNNEL_TOKEN : révocable/regénérable au dashboard Zero Trust ou par API (GET …/token) → .env → up.
  • Clé SSH container : regénérer dans ./ssh-claude (via container éphémère, ownership root), remplacer la ligne dans ~claude-oversight/.ssh/authorized_keys (garder from="172.16.0.0/12").

Gotchas (coûteux à redécouvrir)

  • DNS docker cassé sur ce VPS : le resolv.conf hôte pointe 10.100.0.1 (injoignable depuis les bridges docker) → build : network: host dans compose ; runtime : dns: [1.1.1.1, 8.8.8.8].
  • cap_drop: ALL ⇒ root conteneur SANS CAP_DAC_OVERRIDE : tout volume doit être possédé par root avec modes cohérents (data/ 700 root, ssh-claude/ 700 root + clés 600), sinon Permission denied silencieux (audit.log, known_hosts). Créer/chown via container éphémère (docker run --rm -v … chown 0:0 …) — claude-oversight n'a pas le droit de chown vers root.
  • Miroir bare lu par root conteneur : chmod -R o+rX sur nas-runbooks.git (700 à la création)
    • git config --system --add safe.directory "*" dans l'image (dubious ownership).
  • umask 177 casse git add (répertoires objets créés sans bit x → « insufficient permission for adding an object ») : garder umask 022 pour les opérations git ; réparer par find .git/objects -type d ! -perm -u+x -exec chmod u+rwx {} +.
  • Jamais de recreate depuis mcp-vps lui-même (suicide) : opérer via Tailscale SSH (claude-oversight@100.94.90.119).
  • Test local du MCP depuis le réseau docker : forcer -H "Host: mcp.yesminedor.tn" (anti DNS-rebinding du SDK, sinon 421).

Test blackout (validé 12/07/2026)

Via l'URL publique uniquement, zéro appel NAS :

  1. run_command: git -C /mnt/memory-node/git/nas-runbooks.git show main:common/vps-memory-node.md
  2. run_command: ls /mnt/memory-node/data/{nyora-notes,context-hub}
  3. run_command: ssh vps.internal 'hostname && id' → claude-oversight sur l'hôte ✓
  4. Recreate compose down/up : audit.log + clé SSH persistés, tunnel reconnecte ✓