nas-runbooks/common/mcp-nas-run-command-v2.md

85 lines
5.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# MCP NAS — run_command v2 (P0) : heredocs, commandes longues, reset_session
**Date** : 2026-07-04 | Commit `30560984fa` sur `bolbol/linux-mcp-nas@main` | Testé et validé le jour même.
> **Ce runbook remplace les RÈGLES 13 de [MCP-NAS-SCRIPTING.md](MCP-NAS-SCRIPTING.md).**
> Le chunking base64, la limite ~350 chars et l'interdiction de heredoc n'existent plus.
> La section « Services internes (172.17.0.1) » de l'ancien runbook reste valide.
---
## Ce qui a changé
Avant, `run_command` envoyait la commande brute à tmux via `send-keys` : le parsing shell
cassait sur les quotes, les heredocs déclenchaient un timeout tueur de session, et au-delà
de ~350 caractères la commande était tronquée — d'où le workaround base64 en 2 appels.
Depuis le patch P0, le serveur **écrit la commande dans un fichier script depuis Python**
(`open(f"/tmp/mcp_{uid}.sh","w").write(command)` — zéro parsing shell) et n'envoie au pane que :
```
. /tmp/mcp_XXX.sh > /tmp/mcp_XXX.out 2>&1; echo $? > /tmp/mcp_XXX.rc
```
via `send-keys -l` (mode littéral). Conséquences :
| Avant (v1) | Maintenant (v2) |
|------------|-----------------|
| Heredoc → timeout 120 s, session morte | Heredocs natifs, y compris `<<'EOF'` avec quotes/`$VAR`/backticks |
| Limite ~350 chars, base64 obligatoire | Testé 5 250 chars en un appel, intégrité parfaite |
| Quote simple `'` → troncature silencieuse | Quotes arbitraires OK (aucun parsing shell) |
| Prompt de continuation `>` empoisonnait la session | Mécaniquement impossible |
| Empilement sur pane occupé (vim/ssh/pager) | **Busy-check** : refus propre avec message d'options |
| Sessions poisonnées → new_session en chaîne | **reset_session** : reprise sans destruction |
## Nouveaux comportements
- **Busy-check** : si le pane exécute déjà quelque chose, `run_command` renvoie
`[SESSION OCCUPÉE] 'x' exécute actuellement 'sleep'…` au lieu d'empiler. Options proposées :
`capture_pane`, `send_keys`, `reset_session`, ou une autre session.
- **Timeout intelligent** : à expiration, le serveur tente un Ctrl-C puis sonde le shell.
S'il répond → session préservée (cwd/env conservés). Sinon → session recréée dans le même cwd.
- **`reset_session(session)`** : envoie Ctrl-C, `q` (pager), Ctrl-U, vérifie que le shell répond.
Reprend la main sur une session bloquée SANS la détruire. Pour vim : `send_keys ':q!'` d'abord.
- **Cleanup au démarrage** : sessions tmux orphelines et fichiers `/tmp/mcp_*` purgés au boot
du container (les 24 sessions zombies accumulées ont disparu au premier rebuild).
## Tests de validation (2026-07-04)
1. **Heredoc** avec quotes simples/doubles, `$VAR`, backticks, `$(subshell)`, `!#&*(){}[]|;<>`
→ contenu intact, zéro expansion, exit code correct.
2. **Commande de 5 250 caractères** (heredoc 50 lignes × 100 chars + vérifications)
→ 5 050 octets écrits, 50/50 lignes conformes au motif.
3. **Busy-check + reset_session** : `sleep 300` lancé via send_keys → `run_command` refusé
proprement → `reset_session` → « shell réactif, cwd/env conservés (cwd: /tmp) ».
## Bonnes pratiques v2
- Écrire les scripts Python directement en heredoc : `cat <<'EOF' > /tmp/s.py … EOF` puis `python3 /tmp/s.py`.
- Ne plus jamais utiliser le workaround base64 (2 appels) ni le découpage printf-append.
- Session par défaut `claude` fiable ; `reset_session` avant de créer une session neuve.
- Le rebuild du container se fait **depuis le Mac** (`ssh nas`), jamais depuis mcp-nas lui-même :
```
ssh -p 22222 Best0f@192.168.100.33
cd /volume1/docker/linux-mcp-nas
sudo docker compose build && sudo docker compose up -d --force-recreate
curl -s http://127.0.0.1:3042/health
```
## P1 — denylist ancrée + nas.internal (2026-07-04, commit `7fb13c5`, déployé et testé)
- **Denylist ancrée** : les patterns ne sont plus cherchés n'importe où dans la chaîne brute.
La commande est découpée en segments (`;` `&` `|` retours ligne), les préfixes neutres
(`sudo`, `env`, `VAR=…`, chemin `/sbin/…`) sont retirés, et les patterns sont ancrés sur le
**début de chaque commande**. Les **corps de heredoc sont exclus** de l'analyse.
- Fini les faux positifs : `grep reboot /var/log`, `echo "ne pas faire reboot"`, un heredoc
de doc contenant « mkfs » → **acceptés**.
- Toujours refusés : `reboot`, `/sbin/reboot`, `echo ok && reboot`, `VAR=1 sudo shutdown`,
`rm -rf /volume*|/opt|/mnt|/`, `mkfs.*`, `dd of=/dev/*`, fork bomb, `> /dev/sd*`,
`docker rm -f`, `fdisk`, `parted`, `syno*`, `chmod -R 777 /`. (30/30 tests unitaires.)
- Changement assumé : `rm -rf /tmp/x` est désormais **autorisé** (avant, tout chemin absolu
était bloqué) ; les racines critiques restent protégées.
- **`nas.internal`** : `extra_hosts: nas.internal:host-gateway` dans le compose → depuis toute
session, `nas.internal` = l'hôte NAS (172.17.0.1). Utiliser `http://nas.internal:3232` (Gitea),
`:9000` (Portainer), `:8787` (NyoraNotes)… au lieu de mémoriser 172.17.0.1.