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 1572Shell 203Rust 109Markdown 993728 Definiciones

⎇ 4190c7b43e5bd323a9b166340cfa16cd324ca2e4 scripts/farm/latido.sh Shell · 103 líneas · 5.0 KB Historia crudo

granja: el lock ya no se lo puede quedar un proceso fugado 524782145 · Sergio ·

1#!/usr/bin/env bash
2# latido.sh — el latido de la granja SIN depender del init.
3#
4# POR QUÉ EXISTE (2026-07-22): el latido vivía en el crontab, y eso resultó frágil por dos motivos
5# que se destaparon juntos tras un corte sucio:
6#   1. Este laptop arranca a veces con `init=/usr/local/sbin/arje-zero` y a veces con OpenRC (KDE).
7#      No hay systemd en NINGUNO de los dos. `cronie` es un servicio OpenRC ⇒ en el arranque arje
8#      simplemente no está, y el latido no late. Peor: no late EN SILENCIO, nadie se entera.
9#   2. Aun arrancando OpenRC, se vio el runlevel `default` quedar a medias (cronie/metalog/acpid
10#      caídos, NetworkManager/dbus arriba) ⇒ ni siquiera ahí es garantía.
11# El síntoma final es caro: sin latido el hub no siembra, el worker agota la cola y se queda idle
12# quemando € sin moler (viola [[vps-nunca-idle]]).
13#
14# LA IDEA: colgar el latido de la SESIÓN en vez del init. Cualquier terminal que abras, en cualquier
15# arranque, se asegura de que haya exactamente un latido vivo. Sin root, sin unidad de servicio, sin
16# nada que mantener por duplicado en dos inits.
17#
18# CONTRA (asumido a propósito): no late con la máquina encendida pero sin sesión tuya abierta. Es un
19# laptop, no un server: cuando no hay sesión tampoco hay nadie esperando la cosecha. Y el primer
20# ciclo dispara AL INSTANTE de abrir la terminal, así que un arranque nunca arrastra el hueco (el
21# cron, en cambio, esperaba hasta 30 min al próximo tick).
22#
23# SINGLETON: el lock lo lleva `cosecha-cron.sh` (así vale también si el cron de OpenRC dispara a la
24# vez); acá hay un segundo lock, el del BUCLE, para no acumular un demonio por terminal abierta.
25#
26# Uso:
27#   latido.sh --ensure    # idempotente y barato: si no hay latido, lo lanza. Esto va en ~/.zshrc
28#   latido.sh --loop      # el bucle en sí (lo lanza --ensure; no hace falta llamarlo a mano)
29#   latido.sh --status    # ¿late? desde cuándo, y cuándo fue el último ciclo
30#   latido.sh --stop      # parar el latido
31# Env: INTERVALO (def 1800s = los mismos 30 min que tenía el crontab)
32set -uo pipefail
33
34ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
35INTERVALO="${INTERVALO:-1800}"
36LOCK="$ROOT/work/.latido.lock"
37LOG="$ROOT/work/latido.log"
38COSECHA_LOG="$ROOT/work/cosecha-cron.log"
39mkdir -p "$ROOT/work"
40
41ts() { date -u +%FT%TZ; }
42
43case "${1:---ensure}" in
44  --ensure)
45    # Barato a propósito: esto corre en CADA terminal que abrís. Un flock que falla es ~1ms y no
46    # imprime nada — abrir una terminal no puede volverse lento ni ruidoso por el latido.
47    exec 9>"$LOCK"
48    if flock -n 9; then
49      flock -u 9
50      # setsid + nohup: que sobreviva al cierre de ESTA terminal. Si no, el latido se muere con la
51      # ventana que lo parió y volvemos al problema de origen.
52      setsid nohup "$0" --loop </dev/null >/dev/null 2>&1 &
53      disown 2>/dev/null || true
54    fi
55    exit 0
56    ;;
57
58  --status)
59    if exec 9>"$LOCK" && flock -n 9; then
60      flock -u 9
61      echo "latido: PARADO"
62      exit 1
63    else
64      echo "latido: VIVO"
65      [ -f "$LOG" ] && tail -3 "$LOG"
66      exit 0
67    fi
68    ;;
69
70  --stop)
71    pkill -f "latido.sh --loop" && echo "latido: parado" || echo "latido: no había ninguno vivo"
72    exit 0
73    ;;
74
75  --loop) ;;
76  *) echo "uso: latido.sh --ensure|--loop|--status|--stop" >&2; exit 2 ;;
77esac
78
79# --- el bucle ---
80# El lock se mantiene TOMADO mientras el bucle vive: es lo que hace que --ensure sepa que ya hay uno
81# y que --status pueda responder sin pidfiles (un pidfile miente si el proceso murió; el lock no).
82exec 9>"$LOCK"
83flock -n 9 || exit 0   # otro latido ganó la carrera (dos terminales abiertas a la vez)
84
85echo "── $(ts) latido arranca (pid $$, intervalo ${INTERVALO}s)" >>"$LOG"
86trap 'echo "── $(ts) latido termina (pid $$)" >>"$LOG"' EXIT
87
88while :; do
89  # `|| true` + `set -uo pipefail` sin `-e`: un ciclo que falla (worker caído, red, gitea) NO puede
90  # matar el latido. Se loguea y se reintenta al próximo tick; ésa es toda la robustez que hace falta.
91  # `9>&-` cierra el fd DEL LOCK DEL LATIDO en el hijo. Sin esto, un proceso fugado de la cosecha
92  # lo retiene para siempre y el latido queda MUERTO PERO APARENTANDO ESTAR VIVO: `--status` lee
93  # el lock, lo ve tomado y dice VIVO, y `--ensure` se niega a levantar otro. Es el peor modo de
94  # fallo posible acá — la máquina deja de latir y todo indica que late. Medido 2026-09-08 con
95  # dos `firefox` colgados que retuvieron el lock de build hora y media (regla 1 de CLAUDE.md).
96  #   ⚠ Y acá NO va `flock -o`, al revés que en cosecha-cron/campana-deuda/harvest: en este script
97  # el fd retenido ES el mecanismo de vida a propósito (así `--status` responde sin pidfile, que
98  # mentiría si el proceso muriera). Lo que sobra es que lo hereden los HIJOS, no que lo tenga el
99  # bucle. Por eso el arreglo es distinto aunque el defecto sea el mismo.
100  ( cd "$ROOT" && ./scripts/farm/cosecha-cron.sh ) 9>&- >>"$COSECHA_LOG" 2>&1 || \
101    echo "── $(ts) latido: el ciclo salió != 0 (ver cosecha-cron.log) — sigo" >>"$LOG"
102  sleep "$INTERVALO"
103done