Author SHA1 Message Date
devops 510cc4151a Merge branch 'fix/tmux-host-supervisor-healthcheck' 2026-09-26 17:06:58 +00:00
devopsandClaude Opus 5.5 c6e0e94a0d fix(tmux-host): supervisar healthcheck.sh y quitar set -e del entrypoint
healthcheck.sh corria en background sin nadie que lo relanzara si moria.
Se agrega un supervisor que lo relanza tras 10s y deja un WARN en
health.log. Se cambia `set -eu` por `set -u`: con -e heredado, el propio
subshell del supervisor moria en cuanto healthcheck.sh salia con error,
antes de llegar a la linea que lo relanzaba.

Ya desplegado en la imagen tmux-host:2026-09-26-fix-real (validado en
vivo: relanzo tras exit=137 el 2026-09-26 05:04Z). Ver playbook
incidente-tmux-host-cadena-fallos-2026-09-25 en docs-portal.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018r19s4H8MBGegqToqw5guP
2026-09-26 17:02:51 +00:00
devops 331d4c8b19 Merge branch 'feat/tmux-host-uv-workspace-mcp-2026-09-20' 2026-09-20 21:32:22 +00:00
devops d68ff6fd63 feat(tmux-host): agregar uv para correr servidores MCP en Python bajo demanda 2026-09-20 21:32:00 +00:00
devops ff73a2ad2c Merge branch 'feat/tmux-host-cloud-practice-session-2026-09-20' 2026-09-20 21:12:54 +00:00
devops 801646bb54 feat(tmux-host): generalizar healthcheck a multiples sesiones, agrega cloud-practice 2026-09-20 21:12:34 +00:00
devops 9cfc3a3bdd Merge branch 'fix/promote-script-sin-perl-2026-09-20' 2026-09-20 17:46:01 +00:00
devops b06c1f211e fix(promote-to-production): reemplazar perl por awk, no esta en el host ZimaOS 2026-09-20 17:44:52 +00:00
devops 2058f1ca2a Merge branch 'docs/promote-script-obsoleto-2026-09-20' 2026-09-20 17:32:31 +00:00
devops dfaf5c5a7d docs: marcar promote-to-production.sh como obsoleto (ahora automatico) 2026-09-20 17:31:19 +00:00
devopsandClaude Sonnet 5 f99c955751 Merge branch 'fix/tmux-host-remote-control-continue'
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QMP7ULbxbsB5g1vELNAT3D
2026-09-20 15:46:10 +00:00
devopsandClaude Sonnet 5 e64afedf31 fix(tmux-host): retomar la ultima conversacion al recrear la sesion
launch_claude_in_session() ahora lanza 'claude --remote-control --continue'
en vez de 'claude --remote-control' a secas. -c/--continue retoma la
conversacion mas reciente en WORKDIR; si no hay ninguna arranca una nueva
sin error. Evita que cada recreacion de la sesion tmux (reinicio del NAS,
crash del contenedor) empiece con una conversacion en blanco.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QMP7ULbxbsB5g1vELNAT3D
2026-09-20 15:45:51 +00:00
devopsandClaude Sonnet 5 0ffa1ff15c Merge branch 'fix/tmux-host-remote-control'
Lanza claude --remote-control al recrear la sesion tmux-host para que
quede visible en claude.ai sin intervencion manual.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-20 15:16:58 +00:00
4 changed files with 77 additions and 33 deletions

No files matched your search

+20 -8
View File
@@ -1,12 +1,17 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -Eeuo pipefail set -Eeuo pipefail
# Promueve el tag de imagen del frontend actualmente desplegado en Staging # OBSOLETO como camino normal desde 2026-09-20: la promoción de Staging a
# (workloads/ecommerce-staging/frontend/patches/deployment.yaml) al # Producción ahora corre sola dentro de .gitea/workflows/build.yaml (step
# manifiesto real de Producción (workloads/ecommerce/frontend/patches/ # "Promover a Producción (CD)"), en el mismo job que ya despliega a
# deployment.yaml). Es la contraparte manual del job "Actualizar # Staging -- decisión del usuario de eliminar el gate humano entre los
# Manifiesto GitOps (CD)" de .gitea/workflows/build.yaml, que desde # dos ambientes. En uso normal este script ya no hace nada (Producción
# 2026-09-14 solo actualiza Staging automáticamente en cada push a main. # va a estar al día apenas termine el pipeline).
#
# Se conserva solo como herramienta de excepción/rollback manual --
# ej. forzar Producción a una versión de Staging vieja sin pasar por un
# build nuevo. Mismo sed/perl que el step automático del pipeline, con
# la diferencia de que este sí pide confirmación interactiva.
# #
# Asume que este repo (scripts) y apps-registry están clonados como # Asume que este repo (scripts) y apps-registry están clonados como
# hermanos bajo el mismo directorio del lab (mismo layout que # hermanos bajo el mismo directorio del lab (mismo layout que
@@ -49,8 +54,15 @@ git checkout -B main origin/main
sed -i -E "s|(image: ${IMAGE_NAME}:).*|\1${STAGING_VERSION}|g" "${PROD_MANIFEST}" sed -i -E "s|(image: ${IMAGE_NAME}:).*|\1${STAGING_VERSION}|g" "${PROD_MANIFEST}"
# Mismo bump que hace build.yaml en Staging -- el footer lee APP_VERSION # Mismo bump que hace build.yaml en Staging -- el footer lee APP_VERSION
# para mostrar la version real desplegada. # para mostrar la version real desplegada. awk en vez de perl (usado en
perl -0777 -pi -e "s/(- name: APP_VERSION\n\s*value: ).*/\${1}${STAGING_VERSION}/" "${PROD_MANIFEST}" # build.yaml, donde sí está disponible porque corre en un runner
# ubuntu-latest): el host real de ZimaOS donde se corre este script a
# mano no trae perl instalado (confirmado en vivo 2026-09-20), a
# diferencia de sed/awk que sí son parte del sistema base.
awk -v ver="${STAGING_VERSION}" '
/- name: APP_VERSION/ { print; getline; sub(/value:.*/, "value: " ver); print; next }
{ print }
' "${PROD_MANIFEST}" > "${PROD_MANIFEST}.tmp" && mv "${PROD_MANIFEST}.tmp" "${PROD_MANIFEST}"
git add "${PROD_MANIFEST}" git add "${PROD_MANIFEST}"
if git diff --cached --quiet; then if git diff --cached --quiet; then
+5 -1
View File
@@ -6,7 +6,11 @@
# del contenedor. # del contenedor.
FROM alpine:3.24 FROM alpine:3.24
RUN apk add --no-cache tmux docker-cli bash curl git kubectl nodejs npm \ # uv (paquete directo de Alpine, no via pip): corre servidores MCP en
# Python bajo demanda (ej. `uvx workspace-mcp`, ver ADR 0012 practica
# multi-cloud) sin instalar Python a mano -- uv baja el interprete que
# necesite por su cuenta la primera vez que se usa.
RUN apk add --no-cache tmux docker-cli bash curl git kubectl nodejs npm uv \
&& npm install -g @anthropic-ai/claude-code \ && npm install -g @anthropic-ai/claude-code \
&& git config --global --add safe.directory '*' && git config --global --add safe.directory '*'
+10 -2
View File
@@ -2,8 +2,16 @@
# Proceso principal (PID 1) del contenedor tmux-host: lanza el chequeo de # Proceso principal (PID 1) del contenedor tmux-host: lanza el chequeo de
# salud en background y mantiene el contenedor vivo, igual que el # salud en background y mantiene el contenedor vivo, igual que el
# `tail -f /dev/null` del `docker run` inline original. # `tail -f /dev/null` del `docker run` inline original.
set -eu set -u
/usr/local/bin/healthcheck.sh & supervise_healthcheck() {
while true; do
/usr/local/bin/healthcheck.sh
echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) WARN: healthcheck.sh salio inesperadamente (exit=$?) -- relanzando en 10s" \
>> /DATA/AppData/k3d-lab-v3/tmux-host/logs/health.log 2>/dev/null || true
sleep 10
done
}
supervise_healthcheck &
exec tail -f /dev/null exec tail -f /dev/null
+42 -22
View File
@@ -8,12 +8,13 @@
# del lab-agent ya esta configurado) -- nunca se intenta "arreglar" # del lab-agent ya esta configurado) -- nunca se intenta "arreglar"
# la red del host desde aca, eso puede ser un problema de # la red del host desde aca, eso puede ser un problema de
# hardware/BIOS fuera del alcance del contenedor. # hardware/BIOS fuera del alcance del contenedor.
# b) Existencia de la sesion tmux "lab". Si no existe (crash, o el # b) Existencia de cada sesion tmux en SESSIONS (ver mas abajo -- hoy
# contenedor se acaba de recrear), SI se recrea automaticamente -- # "lab" y "cloud-practice"). Si no existe (crash, o el contenedor
# es solo recuperar el propio entorno de trabajo, no tocar # se acaba de recrear), SI se recrea automaticamente -- es solo
# infraestructura externa. Al recrearla tambien se lanza `claude` # recuperar el propio entorno de trabajo, no tocar infraestructura
# adentro (ver launch_claude_in_session) -- una sesion recreada sin # externa. Al recrearla tambien se lanza `claude` adentro (ver
# esto es un shell pelado, no "el entorno de trabajo". # launch_claude_in_session) -- una sesion recreada sin esto es un
# shell pelado, no "el entorno de trabajo".
# #
# Todo evento (chequeo ok, falla, accion tomada) queda en LOG_FILE, que # Todo evento (chequeo ok, falla, accion tomada) queda en LOG_FILE, que
# vive bajo /DATA (persistente, sobrevive a recrear el contenedor) y no # vive bajo /DATA (persistente, sobrevive a recrear el contenedor) y no
@@ -24,7 +25,15 @@
set -uo pipefail set -uo pipefail
WORKDIR="/DATA/AppData/k3d-lab-v3/lab-cruzcloud" WORKDIR="/DATA/AppData/k3d-lab-v3/lab-cruzcloud"
SESSION="lab" # Cada entrada: "nombre-sesion:workdir". "cloud-practice" agregada
# 2026-09-20 (ver ADR 0012, practica multi-cloud) -- vive en un
# subdirectorio de WORKDIR (repo platform-multicloud ya clonado ahi),
# asi que el mismo wait_for_workdir() de abajo (que solo espera por el
# WORKDIR padre) ya cubre el caso de arranque en frio para las dos.
SESSIONS=(
"lab:${WORKDIR}"
"cloud-practice:${WORKDIR}/platform-multicloud"
)
LOG_DIR="/DATA/AppData/k3d-lab-v3/tmux-host/logs" LOG_DIR="/DATA/AppData/k3d-lab-v3/tmux-host/logs"
LOG_FILE="${LOG_DIR}/health.log" LOG_FILE="${LOG_DIR}/health.log"
# Reusa el mismo canal de Telegram que ya configuro scripts/lab-agent/ -- # Reusa el mismo canal de Telegram que ya configuro scripts/lab-agent/ --
@@ -105,6 +114,7 @@ check_network() {
} }
launch_claude_in_session() { launch_claude_in_session() {
local session="$1"
# El pane recien creado tarda un instante en tener su shell listo para # El pane recien creado tarda un instante en tener su shell listo para
# recibir input -- un send-keys inmediato a veces se pierde. 1s alcanza # recibir input -- un send-keys inmediato a veces se pierde. 1s alcanza
# sobrado para un shell interactivo local (no depende de red). # sobrado para un shell interactivo local (no depende de red).
@@ -113,43 +123,53 @@ launch_claude_in_session() {
# controlable remotamente en claude.ai (confirmado en vivo 2026-09-20 -- # controlable remotamente en claude.ai (confirmado en vivo 2026-09-20 --
# sin la flag, "remoteEnabled" queda en false para siempre porque nadie # sin la flag, "remoteEnabled" queda en false para siempre porque nadie
# la pide). El nombre de sesion remota usa el hostname del contenedor # la pide). El nombre de sesion remota usa el hostname del contenedor
# (ZimaOS) como prefijo por defecto. # (ZimaOS) como prefijo por defecto -- con 2 sesiones corriendo
if tmux send-keys -t "$SESSION" "claude --remote-control" Enter 2>>"$LOG_FILE"; then # "claude --remote-control" a la vez, cada una aparece como una sesion
log "TMUX: 'claude --remote-control' lanzado dentro de la sesion recien creada" # remota distinta en claude.ai (mismo prefijo de host, pero cada una
# con su propio ID de sesion real).
# --continue (-c): retoma la conversacion mas reciente en WORKDIR en vez
# de arrancar una sesion en blanco cada vez que se recrea el pane (reinicio
# del NAS, crash del contenedor, etc). Si no hay conversacion previa en el
# directorio, el propio CLI arranca una nueva sin error.
if tmux send-keys -t "$session" "claude --remote-control --continue" Enter 2>>"$LOG_FILE"; then
log "TMUX: 'claude --remote-control --continue' lanzado dentro de la sesion '${session}' recien creada"
else else
log "TMUX: ERROR al lanzar 'claude --remote-control' dentro de la sesion recien creada" log "TMUX: ERROR al lanzar 'claude --remote-control --continue' dentro de la sesion '${session}' recien creada"
fi fi
} }
check_tmux_session() { check_tmux_session() {
if tmux has-session -t "$SESSION" 2>/dev/null; then local session="$1" workdir="$2"
log "TMUX: sesion '${SESSION}' ok" if tmux has-session -t "$session" 2>/dev/null; then
log "TMUX: sesion '${session}' ok"
return return
fi fi
if [[ ! -d "$WORKDIR" ]]; then if [[ ! -d "$workdir" ]]; then
# No se crea sobre un WORKDIR ausente (quedaria posicionada en /). Se # No se crea sobre un workdir ausente (quedaria posicionada en /). Se
# reintenta en el proximo ciclo -- ver STARTUP_MOUNT_WAIT_MAX_SEC arriba # reintenta en el proximo ciclo -- ver STARTUP_MOUNT_WAIT_MAX_SEC arriba
# para el caso especifico de arranque en frio del host. # para el caso especifico de arranque en frio del host.
log "TMUX: sesion '${SESSION}' no existe y ${WORKDIR} tampoco -- no se crea esta vuelta, se reintenta en el proximo ciclo (${INTERVAL_SEC}s)." log "TMUX: sesion '${session}' no existe y ${workdir} tampoco -- no se crea esta vuelta, se reintenta en el proximo ciclo (${INTERVAL_SEC}s)."
return return
fi fi
log "TMUX: sesion '${SESSION}' no existe -- recreando en ${WORKDIR}" log "TMUX: sesion '${session}' no existe -- recreando en ${workdir}"
if tmux new-session -d -s "$SESSION" -c "$WORKDIR" 2>>"$LOG_FILE"; then if tmux new-session -d -s "$session" -c "$workdir" 2>>"$LOG_FILE"; then
log "TMUX: sesion '${SESSION}' recreada exitosamente" log "TMUX: sesion '${session}' recreada exitosamente"
# Una sesion recreada sin esto es un shell vacio hasta que alguien # Una sesion recreada sin esto es un shell vacio hasta que alguien
# entre a mano y escriba "claude" -- exactamente el gotcha reportado # entre a mano y escriba "claude" -- exactamente el gotcha reportado
# tras el reinicio completo del NAS del 2026-09-18/19: la sesion SI se # tras el reinicio completo del NAS del 2026-09-18/19: la sesion SI se
# recreaba sola (confirmado en el log de ese arranque), pero Claude # recreaba sola (confirmado en el log de ese arranque), pero Claude
# Code nunca arrancaba adentro. # Code nunca arrancaba adentro.
launch_claude_in_session launch_claude_in_session "$session"
else else
log "TMUX: ERROR al recrear la sesion '${SESSION}'" log "TMUX: ERROR al recrear la sesion '${session}'"
fi fi
} }
log "=== healthcheck inicio (intervalo=${INTERVAL_SEC}s) ===" log "=== healthcheck inicio (intervalo=${INTERVAL_SEC}s) ==="
while true; do while true; do
check_network check_network
check_tmux_session for entry in "${SESSIONS[@]}"; do
check_tmux_session "${entry%%:*}" "${entry#*:}"
done
sleep "$INTERVAL_SEC" sleep "$INTERVAL_SEC"
done done