Compare commits

..
Author SHA1 Message Date
devops 72a2715502 docs: agrega mount de .claude.json (la confianza del folder no persistia)
hasTrustDialogAccepted vive en ~/.claude.json (archivo suelto en
/root), no dentro de ~/.claude/ que ya estaba montado -- se perdia en
cada recreacion del contenedor pese a que el login si persistia.
Fix: bind mount archivo-a-archivo, sembrado con el .claude.json ya
confirmado. Verificado en vivo: claude arranca sin re-pedir el
dialogo tras recrear el contenedor.
2026-08-27 23:04:26 -05:00
gitea-actions 1ed06edbdd chore(gitops): deploy Docs Portal v1.0.187 [skip ci] 2026-08-28 03:56:52 +00:00
devops ffcadc5dca Merge fix/docs-tmux-mounts-symlink-git-ownership into main
Build and Push Docs Portal / Escaneo de Secretos (Gitleaks) (push) Successful in 24s
Build and Push Docs Portal / Construir y publicar Docs Portal (push) Successful in 12m59s
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.
2026-08-27 22:44:07 -05:00
devops 61c57fbf28 docs: corrige receta de tmux-host (symlink /DATA/AppData + git ownership)
El comando docker run documentado no funcionaba realmente desde
dentro del contenedor: /DATA/AppData es un symlink absoluto a
/media/HDD-Storage/AppData, que queda roto si solo se monta /DATA.
Agrega el mount que falta, el git safe.directory necesario (baked en
el arranque, no persiste en el volumen de credenciales), y documenta
ambos gotchas con como se detectaron.
2026-08-27 22:33:27 -05:00
devops a31a0d96b6 Merge fix/docs-sesion-tmux-persistente into main
Build and Push Docs Portal / Escaneo de Secretos (Gitleaks) (push) Successful in 25s
Build and Push Docs Portal / Construir y publicar Docs Portal (push) Failing after 7m38s
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.
2026-08-27 22:18:48 -05:00
2 changed files with 107 additions and 3 deletions

No files matched your search

@@ -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`.
@@ -25,7 +25,7 @@ spec:
runAsNonRoot: false
containers:
- name: app
image: gitea.cruzcloud.net/devops/docs-portal:v1.0.181
image: gitea.cruzcloud.net/devops/docs-portal:v1.0.187
# El base saca TODAS las capabilities. Probado en vivo y
# revertido: con drop: ALL (aunque se agregue NET_BIND_SERVICE
# de vuelta) el propio entrypoint de nginx:1.27-alpine falla al