tawasuyu

sergio/takana

> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…

TOML 1574Shell 204Rust 111Markdown 993763 Definiciones

latido: colgar el latido de la sesión, no del init (+ lock anti-solapamiento)

El latido vivía sólo en el crontab y eso resultó frágil: este laptop arranca a veces con
`init=/usr/local/sbin/arje-zero` y a veces con OpenRC (KDE), y no hay systemd en ninguno de los
dos. `cronie` es un servicio OpenRC ⇒ en el arranque arje no existe y el latido no late. Peor: no
late EN SILENCIO. Se destapó tras un corte sucio del 2026-07-22, en el que además se vio el
runlevel `default` quedar a medias (cronie/metalog/acpid caídos, NetworkManager/dbus arriba) ⇒ ni
siquiera arrancando OpenRC era garantía.

El costo del síntoma es caro y callado: sin latido el hub no siembra, el worker agota la cola y se
queda idle quemando € sin moler.

`latido.sh` cuelga el latido de la SESIÓN: cualquier terminal, en cualquier arranque, asegura que
haya exactamente un latido vivo (`--ensure` desde ~/.zshrc, ~5ms y mudo si ya hay uno). Sin root,
sin unidad de servicio, sin mantener lo mismo por duplicado en dos inits. Contra asumido: no late
sin sesión abierta — es un laptop, no un server, y el primer ciclo dispara al instante de abrir la
terminal (el cron esperaba hasta 30 min al próximo tick).

El lock va DENTRO de cosecha-cron.sh, no en el llamador, para que valga venga de donde venga. Eso
además tapa un bug latente que ya existía: nada impedía que dos ciclos se solaparan haciendo rsync
sobre el mismo store y `git commit`+`push` a la vez. Con el lock, el crontab puede quedarse: bajo
KDE dispara cron, bajo arje dispara la sesión, y nunca se pisan.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

f3206d752074761637d796de1aa402d5cb394c19 · por sergio · (hace 3 meses) · padre 660f86c3f · ver el árbol en este commit

Delta semántico

Ninguna definición cambió: el cambio es de texto, documentación o datos.

2 archivos · +109 −0

diff --git a/scripts/farm/cosecha-cron.sh b/scripts/farm/cosecha-cron.sh
index 9f6b68a4..dd7a0a0a 100755
--- a/scripts/farm/cosecha-cron.sh
+++ b/scripts/farm/cosecha-cron.sh
@@ -25,6 +25,21 @@ set -uo pipefail
 
 ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
 cd "$ROOT"
+
+# LOCK (2026-07-22): el latido ya no viene sólo del crontab — `latido.sh` lo lanza desde la sesión
+# porque el laptop arranca a veces con arje-zero y a veces con OpenRC, y cronie sólo existe en uno
+# de los dos. Con dos disparadores posibles, dos ciclos pueden solaparse: ambos hacen rsync sobre el
+# mismo store y, peor, `git commit`+`push` a la vez (index.lock, o un push que pisa al otro). El lock
+# va ACÁ y no en el llamador para que valga venga de donde venga: cron, latido o a mano.
+# -n = no esperar: si ya hay un ciclo corriendo, este sobra (el próximo tick recoge lo mismo).
+LOCKFILE="${LOCKFILE:-$ROOT/work/.cosecha-cron.lock}"
+mkdir -p "$(dirname "$LOCKFILE")"
+exec 9>"$LOCKFILE"
+if ! flock -n 9; then
+  echo "── $(date -u +%FT%TZ) cosecha-cron: ya hay un ciclo en curso ⇒ salgo (no me solapo)"
+  exit 0
+fi
+
 SSH_KEY="${SSH_KEY:-$HOME/.ssh/github5}"
 REMOTE="${REMOTE:-/opt/hammer}"
 FLEET="$ROOT/scripts/farm/.fleet"
diff --git a/scripts/farm/latido.sh b/scripts/farm/latido.sh
new file mode 100755
index 00000000..848aa3ef
--- /dev/null
+++ b/scripts/farm/latido.sh
@@ -0,0 +1,94 @@
+#!/usr/bin/env bash
+# latido.sh — el latido de la granja SIN depender del init.
+#
+# POR QUÉ EXISTE (2026-07-22): el latido vivía en el crontab, y eso resultó frágil por dos motivos
+# que se destaparon juntos tras un corte sucio:
+#   1. Este laptop arranca a veces con `init=/usr/local/sbin/arje-zero` y a veces con OpenRC (KDE).
+#      No hay systemd en NINGUNO de los dos. `cronie` es un servicio OpenRC ⇒ en el arranque arje
+#      simplemente no está, y el latido no late. Peor: no late EN SILENCIO, nadie se entera.
+#   2. Aun arrancando OpenRC, se vio el runlevel `default` quedar a medias (cronie/metalog/acpid
+#      caídos, NetworkManager/dbus arriba) ⇒ ni siquiera ahí es garantía.
+# El síntoma final es caro: sin latido el hub no siembra, el worker agota la cola y se queda idle
+# quemando € sin moler (viola [[vps-nunca-idle]]).
+#
+# LA IDEA: colgar el latido de la SESIÓN en vez del init. Cualquier terminal que abras, en cualquier
+# arranque, se asegura de que haya exactamente un latido vivo. Sin root, sin unidad de servicio, sin
+# nada que mantener por duplicado en dos inits.
+#
+# CONTRA (asumido a propósito): no late con la máquina encendida pero sin sesión tuya abierta. Es un
+# laptop, no un server: cuando no hay sesión tampoco hay nadie esperando la cosecha. Y el primer
+# ciclo dispara AL INSTANTE de abrir la terminal, así que un arranque nunca arrastra el hueco (el
+# cron, en cambio, esperaba hasta 30 min al próximo tick).
+#
+# SINGLETON: el lock lo lleva `cosecha-cron.sh` (así vale también si el cron de OpenRC dispara a la
+# vez); acá hay un segundo lock, el del BUCLE, para no acumular un demonio por terminal abierta.
+#
+# Uso:
+#   latido.sh --ensure    # idempotente y barato: si no hay latido, lo lanza. Esto va en ~/.zshrc
+#   latido.sh --loop      # el bucle en sí (lo lanza --ensure; no hace falta llamarlo a mano)
+#   latido.sh --status    # ¿late? desde cuándo, y cuándo fue el último ciclo
+#   latido.sh --stop      # parar el latido
+# Env: INTERVALO (def 1800s = los mismos 30 min que tenía el crontab)
+set -uo pipefail
+
+ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
+INTERVALO="${INTERVALO:-1800}"
+LOCK="$ROOT/work/.latido.lock"
+LOG="$ROOT/work/latido.log"
+COSECHA_LOG="$ROOT/work/cosecha-cron.log"
+mkdir -p "$ROOT/work"
+
+ts() { date -u +%FT%TZ; }
+
+case "${1:---ensure}" in
+  --ensure)
+    # Barato a propósito: esto corre en CADA terminal que abrís. Un flock que falla es ~1ms y no
+    # imprime nada — abrir una terminal no puede volverse lento ni ruidoso por el latido.
+    exec 9>"$LOCK"
+    if flock -n 9; then
+      flock -u 9
+      # setsid + nohup: que sobreviva al cierre de ESTA terminal. Si no, el latido se muere con la
+      # ventana que lo parió y volvemos al problema de origen.
+      setsid nohup "$0" --loop </dev/null >/dev/null 2>&1 &
+      disown 2>/dev/null || true
+    fi
+    exit 0
+    ;;
+
+  --status)
+    if exec 9>"$LOCK" && flock -n 9; then
+      flock -u 9
+      echo "latido: PARADO"
+      exit 1
+    else
+      echo "latido: VIVO"
+      [ -f "$LOG" ] && tail -3 "$LOG"
+      exit 0
+    fi
+    ;;
+
+  --stop)
+    pkill -f "latido.sh --loop" && echo "latido: parado" || echo "latido: no había ninguno vivo"
+    exit 0
+    ;;
+
+  --loop) ;;
+  *) echo "uso: latido.sh --ensure|--loop|--status|--stop" >&2; exit 2 ;;
+esac
+
+# --- el bucle ---
+# El lock se mantiene TOMADO mientras el bucle vive: es lo que hace que --ensure sepa que ya hay uno
+# y que --status pueda responder sin pidfiles (un pidfile miente si el proceso murió; el lock no).
+exec 9>"$LOCK"
+flock -n 9 || exit 0   # otro latido ganó la carrera (dos terminales abiertas a la vez)
+
+echo "── $(ts) latido arranca (pid $$, intervalo ${INTERVALO}s)" >>"$LOG"
+trap 'echo "── $(ts) latido termina (pid $$)" >>"$LOG"' EXIT
+
+while :; do
+  # `|| true` + `set -uo pipefail` sin `-e`: un ciclo que falla (worker caído, red, gitea) NO puede
+  # matar el latido. Se loguea y se reintenta al próximo tick; ésa es toda la robustez que hace falta.
+  ( cd "$ROOT" && ./scripts/farm/cosecha-cron.sh ) >>"$COSECHA_LOG" 2>&1 || \
+    echo "── $(ts) latido: el ciclo salió != 0 (ver cosecha-cron.log) — sigo" >>"$LOG"
+  sleep "$INTERVALO"
+done