diff --git a/workloads/docs-portal/docs/arquitectura/sesion-tmux-persistente.md b/workloads/docs-portal/docs/arquitectura/sesion-tmux-persistente.md new file mode 100644 index 0000000..5f9f81e --- /dev/null +++ b/workloads/docs-portal/docs/arquitectura/sesion-tmux-persistente.md @@ -0,0 +1,121 @@ +# Sesión persistente de Claude Code (tmux en Docker) + +## Problema que resuelve + +Correr Claude Code directo en una shell SSH sobre el NAS lo ata al +ciclo de vida de esa conexión: un corte de red, cambiar de laptop a +tablet, o cerrar la terminal por accidente mata el proceso y con él +todo el contexto de trabajo en curso — no hay forma de "retomar +donde quedó" porque el proceso ya no existe. + +**Esto es un problema distinto al de los límites de uso/cuota de +Claude Code.** Este setup no aumenta cuota de mensajes ni tokens — +eso lo controla el backend de Anthropic sin importar dónde corra el +CLI. Lo único que resuelve es que la *sesión de terminal* sobreviva +a cortes de red o cambios de dispositivo; si la cuota se agota, sigue +agotada exactamente igual dentro de este setup. + +## Por qué Docker y no instalación nativa + +ZimaOS no tiene gestor de paquetes tradicional utilizable para esto: +`/` está montado como `squashfs` de solo lectura (imagen del SO +inmutable), y `/usr` es un overlay de solo lectura sobre eso — +`/usr/local/bin` ni siquiera existe y no se puede crear, tampoco como +root (`mkdir` falla con "Read-only file system"). No hay dónde +instalar Node.js, npm ni tmux directamente sobre el host. + +Un contenedor Docker sortea esto por completo: tiene su propio +filesystem de escritura (overlay2), independiente del `squashfs` del +host — ahí es donde `npm install -g` deja el binario de `claude` sin +tocar el host en absoluto. Lo único que el contenedor toma del host +es lo que se monta explícitamente como volumen. + +## Arquitectura + +```mermaid +flowchart LR + SSH["Cliente SSH
(laptop / tablet / lo que sea)"] -->|docker exec| C["Contenedor tmux-host
--network host, --restart unless-stopped"] + C --> T["tmux server
sesión 'lab'"] + T --> CC["proceso claude
(Claude Code)"] + C -.->|bind mount| DATA["/DATA (host)
repos y configs del lab"] + C -.->|bind mount| SOCK["/var/run/docker.sock (host)
control de Docker/k3d"] + C -.->|bind mount| HOME["/DATA/AppData/k3d-lab-v3/claude-code-home (host)
-> /root/.claude"] +``` + +El contenedor sigue vivo aunque la conexión SSH se corte — es el +servidor tmux, no la shell del cliente, el que sostiene el proceso de +Claude Code. `--restart unless-stopped` además lo revive si el NAS +reinicia. + +## Contenedor: `tmux-host` + +```bash +docker run -d \ + --name tmux-host \ + --restart unless-stopped \ + -v /DATA:/DATA \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v /DATA/AppData/k3d-lab-v3/claude-code-home:/root/.claude \ + --network host \ + alpine sh -c "apk add --no-cache tmux docker-cli bash curl git kubectl nodejs npm && npm install -g @anthropic-ai/claude-code && tail -f /dev/null" +``` + +| Volumen | Destino | Por qué | +|---|---|---| +| `/DATA` | `/DATA` | Acceso a los repos (`apps-registry`, `platform-infra`, `scripts`) y configs del lab, mismos paths que en el host | +| `/var/run/docker.sock` | `/var/run/docker.sock` | Seguir controlando Docker y k3d (contenedores, `k3d node`, etc.) desde dentro del contenedor | +| `/DATA/AppData/k3d-lab-v3/claude-code-home` | `/root/.claude` | Persistir login/config de Claude Code — sobrevive no solo a un `docker restart`, sino a recrear el contenedor entero | + +Sobre paquetes: alpine (imagen `alpine` sin pin de versión, la misma +que uso este setup) sí trae `kubectl` en sus repos — no hizo falta +cambiar de imagen base ni instalarlo por binario aparte, contrario a +lo que se sospechaba inicialmente. `nodejs`/`npm` (v24/v11 al momento +de escribir esto) también están en los repos de alpine y cumplen de +sobra el mínimo que pide Claude Code. + +**Nota sobre `--network host` + `docker.sock`:** el contenedor tiene, +en la práctica, control equivalente a root sobre el NAS (puede lanzar +cualquier otro contenedor con cualquier mount). Es una concesión +deliberada — es exactamente lo que hace falta para seguir operando +Docker/k3d desde dentro — no un descuido. + +## Reconexión: un solo comando + +```bash +/media/HDD-Storage/AppData/k3d-lab-v3/lab-cruzcloud/scripts/lab-session.sh +``` + +El script (`scripts/lab-session.sh`) es intencionalmente el mismo +comando sin importar el estado previo: + +```bash +docker exec -it "$CONTAINER" tmux attach -t "$SESSION" || \ + docker exec -it "$CONTAINER" tmux new -s "$SESSION" +``` + +Si la sesión `lab` existe, se adjunta a ella (mismo shell, mismo +`claude` corriendo, mismo directorio de trabajo). Si no existe —por +ejemplo justo después de que el contenedor se recreó desde cero— la +crea. No hay que recordar cuál de los dos casos aplica. + +## Qué sobrevive a qué + +| Evento | ¿Sobrevive la sesión tmux (`lab`)? | ¿Sobrevive el login de Claude Code? | +|---|---|---| +| Corte de SSH / cerrar la terminal / cambiar de dispositivo | Sí — es exactamente el caso que este setup resuelve | Sí | +| `docker restart tmux-host` (reinicio del NAS) | **No** — el proceso `tmux server` muere con el contenedor; `lab-session.sh` crea una sesión nueva automáticamente | Sí, vía el volumen montado en `/root/.claude` | +| `docker rm` + recrear el contenedor desde cero | No | Sí, vía el mismo volumen — no hace falta volver a autenticar | + +## Validación en vivo + +Confirmado en este mismo lab (2026-08-27): se dejó una variable de +entorno (`export MARCA_PERSISTENCIA=ok_`) corriendo dentro +de la sesión `lab` vía `tmux send-keys`, y una invocación +*completamente separada* de `docker exec` (equivalente a una nueva +conexión) leyó de vuelta el mismo valor — confirma que el estado vive +en el servidor tmux del contenedor, no en el cliente que se conecta. +`claude --version` respondió `2.1.250` dentro de esa misma sesión, y +un archivo de prueba escrito en `/root/.claude` dentro del contenedor +apareció de inmediato en +`/DATA/AppData/k3d-lab-v3/claude-code-home` en el host, confirmando +el bind mount de credenciales. diff --git a/workloads/docs-portal/mkdocs.yml b/workloads/docs-portal/mkdocs.yml index d9c7d71..1048e4f 100644 --- a/workloads/docs-portal/mkdocs.yml +++ b/workloads/docs-portal/mkdocs.yml @@ -85,6 +85,7 @@ nav: - Red y exposición: arquitectura/red-y-exposicion.md - Flujo GitOps: arquitectura/gitops-flujo.md - Agente de monitoreo: arquitectura/agente-monitoreo.md + - Sesión tmux persistente: arquitectura/sesion-tmux-persistente.md - Glosario: arquitectura/glosario.md - Decisiones (ADRs): - "0001 · Por qué k3d": decisiones/0001-por-que-k3d.md