diff --git a/lab-session.sh b/lab-session.sh index 48fadc1..3e1569a 100755 --- a/lab-session.sh +++ b/lab-session.sh @@ -10,6 +10,11 @@ set -Eeuo pipefail CONTAINER="tmux-host" SESSION="lab" +# Repo raiz del lab (CLAUDE.md, apps-registry/, platform-infra/, scripts/). +# Mismo path que el WORKDIR de scripts/tmux-host/Dockerfile -- pasado +# explicito acรก (-c) para no depender de que docker exec herede el WORKDIR +# de la imagen. +WORKDIR="/DATA/AppData/k3d-lab-v3/lab-cruzcloud" if ! docker ps --format '{{.Names}}' | grep -qx "$CONTAINER"; then echo "El contenedor '$CONTAINER' no esta corriendo. Verifica con 'docker ps -a'." >&2 @@ -17,4 +22,4 @@ if ! docker ps --format '{{.Names}}' | grep -qx "$CONTAINER"; then fi docker exec -it "$CONTAINER" tmux attach -t "$SESSION" || \ - docker exec -it "$CONTAINER" tmux new -s "$SESSION" + docker exec -it "$CONTAINER" tmux new -s "$SESSION" -c "$WORKDIR" diff --git a/tmux-host/Dockerfile b/tmux-host/Dockerfile new file mode 100644 index 0000000..48fcc0d --- /dev/null +++ b/tmux-host/Dockerfile @@ -0,0 +1,23 @@ +# Imagen de tmux-host: sesion tmux persistente para Claude Code sobre el +# lab GitOps de cruzcloud. Reemplaza el `docker run ... alpine sh -c "apk +# add ... && npm install ..."` inline documentado antes en +# docs/arquitectura/sesion-tmux-persistente.md โ€” mismo contenido, pero +# construido una vez como imagen en vez de reinstalar todo en cada arranque +# del contenedor. +FROM alpine:3.24 + +RUN 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 '*' + +# Repo raiz del lab (CLAUDE.md, apps-registry/, platform-infra/, scripts/). +# docker exec hereda este WORKDIR como cwd por defecto, y ademas +# lab-session.sh y healthcheck.sh lo pasan explicito via `tmux ... -c` +# para no depender de ese comportamiento implicito. +WORKDIR /DATA/AppData/k3d-lab-v3/lab-cruzcloud + +COPY healthcheck.sh /usr/local/bin/healthcheck.sh +COPY entrypoint.sh /usr/local/bin/entrypoint.sh +RUN chmod +x /usr/local/bin/healthcheck.sh /usr/local/bin/entrypoint.sh + +ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] diff --git a/tmux-host/entrypoint.sh b/tmux-host/entrypoint.sh new file mode 100755 index 0000000..3e693fd --- /dev/null +++ b/tmux-host/entrypoint.sh @@ -0,0 +1,9 @@ +#!/bin/sh +# Proceso principal (PID 1) del contenedor tmux-host: lanza el chequeo de +# salud en background y mantiene el contenedor vivo, igual que el +# `tail -f /dev/null` del `docker run` inline original. +set -eu + +/usr/local/bin/healthcheck.sh & + +exec tail -f /dev/null diff --git a/tmux-host/healthcheck.sh b/tmux-host/healthcheck.sh new file mode 100755 index 0000000..c90183c --- /dev/null +++ b/tmux-host/healthcheck.sh @@ -0,0 +1,94 @@ +#!/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. +# +# 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}" + +mkdir -p "$LOG_DIR" + +log() { + echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*" >> "$LOG_FILE" +} + +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 +} + +check_tmux_session() { + if tmux has-session -t "$SESSION" 2>/dev/null; then + log "TMUX: sesion '${SESSION}' ok" + 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" + 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