Files
scripts/tmux-host/healthcheck.sh
T
devopsandClaude Sonnet 5 f48860fcd8 fix(tmux-host): lanzar claude con --remote-control al recrear la sesion
Sin la flag, la sesion de tmux-host nunca queda visible como
controlable remotamente en claude.ai: remoteEnabled quedaba en false
porque nadie la pedia. Confirmado en vivo que --remote-control si
empareja (aparecio como sesion activa "zimaos-quizzical") mientras el
proceso se mantiene vivo.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-20 15:16:00 +00:00

156 lines
7.0 KiB
Bash
Executable File

#!/usr/bin/env bash
# Chequeo de salud de tmux-host, corre en background dentro del propio
# contenedor (ver entrypoint.sh). Cada HEALTHCHECK_INTERVAL_SEC (30 min
# por defecto):
#
# a) Conectividad de red real (curl a gitea.cruzcloud.net). Si falla,
# SOLO se registra en el log y se notifica por Telegram (si el canal
# del lab-agent ya esta configurado) -- nunca se intenta "arreglar"
# la red del host desde aca, eso puede ser un problema de
# hardware/BIOS fuera del alcance del contenedor.
# b) Existencia de la sesion tmux "lab". Si no existe (crash, o el
# contenedor se acaba de recrear), SI se recrea automaticamente --
# es solo recuperar el propio entorno de trabajo, no tocar
# infraestructura externa. Al recrearla tambien se lanza `claude`
# adentro (ver 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
# vive bajo /DATA (persistente, sobrevive a recrear el contenedor) y no
# solo en el stdout del contenedor.
#
# set -u sin 'e': un check individual que falle no debe matar el loop
# completo -- el siguiente ciclo tiene que seguir corriendo igual.
set -uo pipefail
WORKDIR="/DATA/AppData/k3d-lab-v3/lab-cruzcloud"
SESSION="lab"
LOG_DIR="/DATA/AppData/k3d-lab-v3/tmux-host/logs"
LOG_FILE="${LOG_DIR}/health.log"
# Reusa el mismo canal de Telegram que ya configuro scripts/lab-agent/ --
# una sola fuente de verdad para el bot token / chat id, en vez de
# duplicar credenciales entre los dos agentes del lab.
LAB_AGENT_ENV="${WORKDIR}/scripts/lab-agent/lab-agent.env"
NETWORK_CHECK_URL="https://gitea.cruzcloud.net/"
INTERVAL_SEC="${HEALTHCHECK_INTERVAL_SEC:-1800}"
# Cuanto esperar, SOLO al arrancar, a que WORKDIR este disponible antes de
# tocarlo. Tras un `docker restart` normal el host y sus mounts ya estaban
# arriba; tras un reinicio COMPLETO del NAS (corte de luz), Docker puede
# arrancar este contenedor antes de que /media/HDD-Storage termine de
# montarse en el host -- y WORKDIR llega a el via el symlink absoluto
# /DATA/AppData -> /media/HDD-Storage/AppData (ver el gotcha del symlink en
# docs-portal/docs/arquitectura/sesion-tmux-persistente.md). Sin esta
# espera, `mkdir -p "$LOG_DIR"` de mas abajo fallaba en silencio (set -u sin
# -e) y ese arranque quedaba SIN NINGUN rastro en el log -- asi se paso por
# alto originalmente el gotcha de "no lanzo claude" reportado 2026-09-19.
STARTUP_MOUNT_WAIT_MAX_SEC="${STARTUP_MOUNT_WAIT_MAX_SEC:-120}"
STARTUP_MOUNT_WAIT_STEP_SEC=5
wait_for_workdir() {
local waited=0
while [[ ! -d "$WORKDIR" ]] && [[ "$waited" -lt "$STARTUP_MOUNT_WAIT_MAX_SEC" ]]; do
sleep "$STARTUP_MOUNT_WAIT_STEP_SEC"
waited=$((waited + STARTUP_MOUNT_WAIT_STEP_SEC))
done
echo "$waited"
}
STARTUP_WAITED_SEC="$(wait_for_workdir)"
mkdir -p "$LOG_DIR" 2>/dev/null || true
log() {
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*" >> "$LOG_FILE" 2>/dev/null || true
}
if [[ ! -d "$WORKDIR" ]]; then
log "ARRANQUE: ERROR -- ${WORKDIR} sigue sin existir tras esperar ${STARTUP_MOUNT_WAIT_MAX_SEC}s. El mount de /DATA o /media/HDD-Storage del host no esta listo; revisar fuera del contenedor."
elif [[ "$STARTUP_WAITED_SEC" -gt 0 ]]; then
log "ARRANQUE: ${WORKDIR} tardo ${STARTUP_WAITED_SEC}s en estar disponible (probable reinicio completo del host reordenando el montaje de /DATA)."
fi
notify_telegram() {
local title="$1" message="$2"
if [[ ! -f "$LAB_AGENT_ENV" ]]; then
log "NOTIFY omitida: no existe ${LAB_AGENT_ENV}"
return 0
fi
local token_file chat_id token resp
token_file="$(grep -E '^TELEGRAM_BOT_TOKEN_FILE=' "$LAB_AGENT_ENV" | cut -d= -f2-)"
chat_id="$(grep -E '^TELEGRAM_CHAT_ID=' "$LAB_AGENT_ENV" | cut -d= -f2-)"
if [[ -z "$token_file" || ! -r "$token_file" || -z "$chat_id" ]]; then
log "NOTIFY omitida: canal de Telegram del lab-agent no esta configurado todavia"
return 0
fi
token="$(<"$token_file")"
resp="$(curl -s -m 10 -X POST "https://api.telegram.org/bot${token}/sendMessage" \
--data-urlencode "chat_id=${chat_id}" \
--data-urlencode "text=🤖 tmux-host healthcheck
${title}
──────────────
${message}" 2>&1)"
token=""
[[ "$resp" == *'"ok":true'* ]] || log "WARN: fallo al notificar por Telegram: ${resp}"
}
check_network() {
local code
code="$(curl -sL -o /dev/null -w '%{http_code}' --max-time 10 "$NETWORK_CHECK_URL" 2>/dev/null || echo "000")"
if [[ "$code" == "000" ]]; then
log "RED: sin conectividad real (curl ${NETWORK_CHECK_URL} sin respuesta). No se intenta corregir desde el contenedor."
notify_telegram "Sin conectividad" "curl ${NETWORK_CHECK_URL} no respondio desde tmux-host (posible falla de red del host/NAS, ej. hardware/BIOS). El contenedor NO intenta corregir esto, solo lo registra."
else
log "RED: ok (curl ${NETWORK_CHECK_URL} -> HTTP ${code})"
fi
}
launch_claude_in_session() {
# 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
# sobrado para un shell interactivo local (no depende de red).
sleep 1
# --remote-control: sin esto la sesion nunca queda visible como
# controlable remotamente en claude.ai (confirmado en vivo 2026-09-20 --
# sin la flag, "remoteEnabled" queda en false para siempre porque nadie
# la pide). El nombre de sesion remota usa el hostname del contenedor
# (ZimaOS) como prefijo por defecto.
if tmux send-keys -t "$SESSION" "claude --remote-control" Enter 2>>"$LOG_FILE"; then
log "TMUX: 'claude --remote-control' lanzado dentro de la sesion recien creada"
else
log "TMUX: ERROR al lanzar 'claude --remote-control' dentro de la sesion recien creada"
fi
}
check_tmux_session() {
if tmux has-session -t "$SESSION" 2>/dev/null; then
log "TMUX: sesion '${SESSION}' ok"
return
fi
if [[ ! -d "$WORKDIR" ]]; then
# No se crea sobre un WORKDIR ausente (quedaria posicionada en /). Se
# reintenta en el proximo ciclo -- ver STARTUP_MOUNT_WAIT_MAX_SEC arriba
# 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)."
return
fi
log "TMUX: sesion '${SESSION}' no existe -- recreando en ${WORKDIR}"
if tmux new-session -d -s "$SESSION" -c "$WORKDIR" 2>>"$LOG_FILE"; then
log "TMUX: sesion '${SESSION}' recreada exitosamente"
# Una sesion recreada sin esto es un shell vacio hasta que alguien
# 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
# recreaba sola (confirmado en el log de ese arranque), pero Claude
# Code nunca arrancaba adentro.
launch_claude_in_session
else
log "TMUX: ERROR al recrear la sesion '${SESSION}'"
fi
}
log "=== healthcheck inicio (intervalo=${INTERVAL_SEC}s) ==="
while true; do
check_network
check_tmux_session
sleep "$INTERVAL_SEC"
done