|
|
|
@@ -54,17 +54,114 @@ docker run -d \
|
|
|
|
|
--name tmux-host \
|
|
|
|
|
--restart unless-stopped \
|
|
|
|
|
-v /DATA:/DATA \
|
|
|
|
|
-v /media/HDD-Storage:/media/HDD-Storage \
|
|
|
|
|
-v /var/run/docker.sock:/var/run/docker.sock \
|
|
|
|
|
-v /DATA/AppData/k3d-lab-v3/claude-code-home:/root/.claude \
|
|
|
|
|
-v /DATA/AppData/k3d-lab-v3/claude-code-home.json:/root/.claude.json \
|
|
|
|
|
--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"
|
|
|
|
|
alpine sh -c "apk add --no-cache tmux docker-cli bash curl git kubectl nodejs npm && npm install -g @anthropic-ai/claude-code && git config --global --add safe.directory '*' && 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 |
|
|
|
|
|
| `/media/HDD-Storage` | `/media/HDD-Storage` | Ver "Gotcha: el symlink `/DATA/AppData`" abajo — sin esto, los repos no se ven dentro del contenedor pese a montar `/DATA` |
|
|
|
|
|
| `/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 |
|
|
|
|
|
| `/DATA/AppData/k3d-lab-v3/claude-code-home.json` | `/root/.claude.json` | Ver "Gotcha: la confianza del folder no persistía" abajo — es un archivo suelto, no vive dentro de `~/.claude/` |
|
|
|
|
|
|
|
|
|
|
## Gotcha: el symlink `/DATA/AppData`
|
|
|
|
|
|
|
|
|
|
Montar `-v /DATA:/DATA` **no alcanza** para ver los repos dentro del
|
|
|
|
|
contenedor. `/DATA/AppData` en el host es un symlink *absoluto*:
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
/DATA/AppData -> /media/HDD-Storage/AppData
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Los repos del lab (`lab-cruzcloud`) viven físicamente en el disco
|
|
|
|
|
btrfs `/media/HDD-Storage`, no en el ext4 de `/DATA` — ZimaOS lo
|
|
|
|
|
expone dentro de `/DATA` como submount (`/DATA/.media/HDD-Storage/...`,
|
|
|
|
|
que sí viaja al contenedor porque Docker hace bind recursivo por
|
|
|
|
|
defecto) pero el path "bonito" `/DATA/AppData/...` que usan `CLAUDE.md`
|
|
|
|
|
y todos los scripts del lab llega ahí vía ese symlink absoluto, no vía
|
|
|
|
|
el submount. Un symlink absoluto que apunta fuera del árbol montado
|
|
|
|
|
queda roto dentro del contenedor aunque el submount subyacente esté
|
|
|
|
|
perfectamente accesible por otra ruta.
|
|
|
|
|
|
|
|
|
|
**Cómo se detectó:** todo el trabajo de configuración inicial de este
|
|
|
|
|
mismo setup se hizo con `git`/`docker exec` corriendo *en el host*, no
|
|
|
|
|
dentro del contenedor — nadie había probado `cd
|
|
|
|
|
/DATA/AppData/k3d-lab-v3/lab-cruzcloud` desde una sesión `lab` real
|
|
|
|
|
hasta que se preguntó explícitamente por esto. `ls` devolvía "No such
|
|
|
|
|
file or directory" pese a que el volumen de `/DATA` estaba montado y
|
|
|
|
|
"andando".
|
|
|
|
|
|
|
|
|
|
**Fix:** montar también `-v /media/HDD-Storage:/media/HDD-Storage`,
|
|
|
|
|
para que el symlink resuelva exactamente igual que en el host.
|
|
|
|
|
|
|
|
|
|
## Gotcha: git "dubious ownership" dentro del contenedor
|
|
|
|
|
|
|
|
|
|
Con el mount anterior ya arreglado, `git status`/`git log` dentro del
|
|
|
|
|
contenedor fallan con:
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
fatal: detected dubious ownership in repository at '/media/HDD-Storage/...'
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Los archivos en `/DATA`/`/media/HDD-Storage` pertenecen al usuario
|
|
|
|
|
`devops` del host (via samba), pero los procesos dentro del
|
|
|
|
|
contenedor corren como `root` (UID 0) — git, desde la versión que
|
|
|
|
|
introdujo la protección CVE-2022-24765, rechaza operar sobre un repo
|
|
|
|
|
cuyo dueño no coincide con el UID del proceso, a menos que el path
|
|
|
|
|
esté explícitamente marcado como seguro.
|
|
|
|
|
|
|
|
|
|
**Fix:** `git config --global --add safe.directory '*'` dentro del
|
|
|
|
|
contenedor. El wildcard (en vez de listar cada repo) es intencional
|
|
|
|
|
acá: este contenedor ya tiene acceso root-equivalente al host vía el
|
|
|
|
|
volumen de `/var/run/docker.sock`, así que restringir `safe.directory`
|
|
|
|
|
por repo no suma aislamiento real.
|
|
|
|
|
|
|
|
|
|
**Por qué está en el comando de arranque y no como paso manual
|
|
|
|
|
aparte:** igual que los paquetes `apk`, esta configuración vive en el
|
|
|
|
|
filesystem efímero del contenedor (`/root/.gitconfig`, que no está
|
|
|
|
|
bajo el volumen de `/root/.claude`) — se perdería si el contenedor se
|
|
|
|
|
recrea. Ponerla en el `sh -c` de arranque la deja recreándose sola
|
|
|
|
|
cada vez, sin pasos manuales que recordar.
|
|
|
|
|
|
|
|
|
|
## Gotcha: la confianza del folder no persistía
|
|
|
|
|
|
|
|
|
|
Al abrir `claude` por primera vez en un directorio, pide confirmar un
|
|
|
|
|
diálogo de "¿confiás en este folder?" (más aún acá, porque este repo
|
|
|
|
|
trae un `.claude/settings.local.json` con permisos pre-aprobados). Esa
|
|
|
|
|
confirmación se guarda en `~/.claude.json` — un **archivo suelto en
|
|
|
|
|
`/root`**, no dentro de la carpeta `~/.claude/` que ya estaba montada
|
|
|
|
|
como volumen. Resultado: aunque el login (`~/.claude/.credentials.json`)
|
|
|
|
|
sí persistía, el diálogo de confianza volvía a aparecer cada vez que
|
|
|
|
|
se recreaba el contenedor — justo el tipo de "reconfigurar de nuevo"
|
|
|
|
|
que este setup existe para evitar.
|
|
|
|
|
|
|
|
|
|
**Cómo se detectó:** al confirmar el diálogo por primera vez, se
|
|
|
|
|
comparó `/root/.claude.json` dentro del contenedor contra sí mismo
|
|
|
|
|
antes y después — mismo tamaño y mismo `mtime`, es decir, la
|
|
|
|
|
confirmación en pantalla nunca se había registrado. La causa real:
|
|
|
|
|
la primera vez se movió el selector con las flechas pero nunca se
|
|
|
|
|
presionó Enter, así que en rigor el diálogo seguía sin confirmar; una
|
|
|
|
|
vez confirmado *de verdad* (`tmux send-keys` moviendo el cursor a
|
|
|
|
|
"Yes, I trust this folder" + Enter), el archivo saltó de 2.2 KB a
|
|
|
|
|
40 KB y apareció la clave `projects` con la ruta del repo adentro.
|
|
|
|
|
|
|
|
|
|
**Fix:** copiar ese `.claude.json` ya confirmado a
|
|
|
|
|
`/DATA/AppData/k3d-lab-v3/claude-code-home.json` en el host, y
|
|
|
|
|
montarlo **archivo a archivo** (no como carpeta) en
|
|
|
|
|
`-v .../claude-code-home.json:/root/.claude.json`. A diferencia de
|
|
|
|
|
los demás volúmenes de este setup, este necesitó sembrarse con
|
|
|
|
|
contenido real primero — un bind mount de archivo con un origen que
|
|
|
|
|
no existe todavía en el host termina creando un directorio vacío en
|
|
|
|
|
su lugar, no un archivo, y rompe el arranque del contenedor.
|
|
|
|
|
Verificado en vivo: tras recrear el contenedor con este mount, `claude`
|
|
|
|
|
arrancó directo en la sesión normal, sin volver a pedir el diálogo de
|
|
|
|
|
confianza.
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
@@ -104,7 +201,7 @@ crea. No hay que recordar cuál de los dos casos aplica.
|
|
|
|
|
|---|---|---|
|
|
|
|
|
| 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 |
|
|
|
|
|
| `docker rm` + recrear el contenedor desde cero | No | Sí, vía el mismo volumen — no hace falta volver a autenticar ni reconfirmar el diálogo de confianza del folder (ver el mount de `claude-code-home.json`) |
|
|
|
|
|
|
|
|
|
|
## Validación en vivo
|
|
|
|
|
|
|
|
|
@@ -119,3 +216,10 @@ 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.
|
|
|
|
|
|
|
|
|
|
Después de agregar el mount de `/media/HDD-Storage` y el
|
|
|
|
|
`safe.directory`, se validó además: `md5sum` de `CLAUDE.md` idéntico
|
|
|
|
|
leído desde el host y desde dentro del contenedor (vía el path con
|
|
|
|
|
symlink `/DATA/AppData/...`), y `git log`/`git status` corriendo sin
|
|
|
|
|
error sobre `apps-registry` y `scripts` desde una shell dentro de
|
|
|
|
|
`tmux-host`.
|