runbook: mcp-vps (MCP VPS memory-node, Cloudflare Tunnel) — 12/07/2026

This commit is contained in:
bolbol 2026-07-11 22:50:01 +00:00
parent af5b268887
commit 8b961332ca
1 changed files with 87 additions and 0 deletions

87
common/mcp-vps.md Normal file
View File

@ -0,0 +1,87 @@
# 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``.env``docker 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 ✓