Merge fix/docs-sesion-tmux-persistente into main
Merge local directo (sin PR de Gitea) por acuerdo del usuario; ver feedback_git_fix_branches en memoria del proyecto para el porque de este flujo estandar.
This commit is contained in:
commit
a31a0d96b6
2 files changed
+122
No files matched your search
@@ -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<br/>(laptop / tablet / lo que sea)"] -->|docker exec| C["Contenedor tmux-host<br/>--network host, --restart unless-stopped"]
|
||||||
|
C --> T["tmux server<br/>sesión 'lab'"]
|
||||||
|
T --> CC["proceso claude<br/>(Claude Code)"]
|
||||||
|
C -.->|bind mount| DATA["/DATA (host)<br/>repos y configs del lab"]
|
||||||
|
C -.->|bind mount| SOCK["/var/run/docker.sock (host)<br/>control de Docker/k3d"]
|
||||||
|
C -.->|bind mount| HOME["/DATA/AppData/k3d-lab-v3/claude-code-home (host)<br/>-> /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_<timestamp>`) 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.
|
||||||
@@ -85,6 +85,7 @@ nav:
|
|||||||
- Red y exposición: arquitectura/red-y-exposicion.md
|
- Red y exposición: arquitectura/red-y-exposicion.md
|
||||||
- Flujo GitOps: arquitectura/gitops-flujo.md
|
- Flujo GitOps: arquitectura/gitops-flujo.md
|
||||||
- Agente de monitoreo: arquitectura/agente-monitoreo.md
|
- Agente de monitoreo: arquitectura/agente-monitoreo.md
|
||||||
|
- Sesión tmux persistente: arquitectura/sesion-tmux-persistente.md
|
||||||
- Glosario: arquitectura/glosario.md
|
- Glosario: arquitectura/glosario.md
|
||||||
- Decisiones (ADRs):
|
- Decisiones (ADRs):
|
||||||
- "0001 · Por qué k3d": decisiones/0001-por-que-k3d.md
|
- "0001 · Por qué k3d": decisiones/0001-por-que-k3d.md
|
||||||
|
|||||||
Reference in new issue
Block a user