From 4f4fecf862bb49ef8d0cb88080b5ab088de4433f Mon Sep 17 00:00:00 2001 From: Sergio Date: Mon, 21 Sep 2026 18:30:33 +0000 Subject: [PATCH] =?UTF-8?q?servidor:=20el=20aviso=20peri=C3=B3dico=20de=20?= =?UTF-8?q?novedades=20del=20repo,=20que=20s=C3=B3lo=20grita=20cuando=20ca?= =?UTF-8?q?mbia?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Corre `takana outdated --json` contra el índice firmado y deja el resultado en un fichero de estado. No actualiza nada: instalar es reproducir y verificar, y eso no se cuelga de un cron — el aviso dice qué comando correr. Tres cosas deliberadas: · Sólo grita cuando la LISTA de novedades difiere de la del ciclo anterior. Un guardián que repite lo mismo cada 30 min se deja de leer, y entonces no avisa de nada. · La huella es nombre+versión publicada, no el JSON entero: éste trae contadores que se mueven solos y harían «cambiar» un estado idéntico. · Un repo caído NO se resume en «al día»: lo dice, sale ≠ 0 y conserva el último estado bueno. Probado en los tres caminos contra un repo servido por HTTP en la caja. --- scripts/servidor/avisar-actualizaciones.sh | 84 ++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100755 scripts/servidor/avisar-actualizaciones.sh diff --git a/scripts/servidor/avisar-actualizaciones.sh b/scripts/servidor/avisar-actualizaciones.sh new file mode 100755 index 00000000..677e3ad8 --- /dev/null +++ b/scripts/servidor/avisar-actualizaciones.sh @@ -0,0 +1,84 @@ +#!/bin/sh +# avisar-actualizaciones.sh — pregunta al repo si hay novedades para lo que esta máquina tiene +# instalado, y AVISA sólo cuando la respuesta cambia. +# +# ── QUÉ HACE Y QUÉ NO ─────────────────────────────────────────────────────────────────────────── +# Corre `takana outdated --json` (que no construye, no baja `.swm` y no escribe nada: lee el índice +# firmado y la DB de instalados) y deja el resultado en un fichero de estado. **No actualiza nada**: +# instalar es reproducir y verificar, y eso no se cuelga de un cron. El aviso dice qué correr. +# +# ── POR QUÉ SÓLO AVISA CUANDO CAMBIA ──────────────────────────────────────────────────────────── +# Un guardián que grita lo mismo cada 30 minutos se deja de leer, y entonces no avisa de nada — es +# la lección que ya está escrita en `vigia-subcomandos.py` ([[guardianes-el-ruido-los-mata]]). +# Así que el ciclo normal es una línea sola («al día», o «3 novedades, las mismas de antes») y el +# grito sale cuando la LISTA de novedades difiere de la del ciclo anterior. +# +# ── LA FIRMA NO ES OPCIONAL ACÁ ───────────────────────────────────────────────────────────────── +# Se pasa `--trust`: decidir «hay que actualizar» a partir de un catálogo que nadie firmó es la +# forma exacta de que te instalen lo que quieran. `outdated` aborta si el índice está manipulado +# (BAD-SIG) e informa si está sin firmar o firmado por una clave desconocida — ese informe sale en +# el log y en el estado, no se traga. +# +# Uso: scripts/servidor/avisar-actualizaciones.sh +# */30 * * * * cd /work/sergio/takana && scripts/servidor/avisar-actualizaciones.sh \ +# >>work/avisar-actualizaciones.log 2>&1 +# +# Env: REPO origen(es) del catálogo — dir local o lista HTTP(S) separada por comas +# DB DB de instalados (def /var/lib/hammer/installed.json) +# TRUST directorio de claves de confianza (def ./trust) +# STATE fichero de estado (def work/actualizaciones.json) +# TAKANA binario (def ./target/release/takana) +set -eu +ROOT="$(cd "$(dirname "$0")/../.." && pwd)"; cd "$ROOT" + +REPO="${REPO:-https://repo.gioser.net}" +DB="${DB:-/var/lib/hammer/installed.json}" +TRUST="${TRUST:-$ROOT/trust}" +STATE="${STATE:-$ROOT/work/actualizaciones.json}" +TAKANA="${TAKANA:-$ROOT/target/release/takana}" + +[ -x "$TAKANA" ] || { echo "!! falta $TAKANA (cargo build --release)" >&2; exit 2; } +mkdir -p "$(dirname "$STATE")" + +NUEVO="$STATE.nuevo" +if ! "$TAKANA" outdated --repo "$REPO" --db "$DB" --trust "$TRUST" --json >"$NUEVO" 2>"$NUEVO.err"; then + # Un repo caído o un índice manipulado NO se resume en «al día»: se dice, y se deja el estado + # anterior intacto para no borrar la última respuesta buena. + echo "$(date -u +%FT%TZ) ✗ no pude preguntarle al repo $REPO:" + sed 's/^/ /' "$NUEVO.err" + rm -f "$NUEVO" "$NUEVO.err" + exit 1 +fi +rm -f "$NUEVO.err" + +# La huella es la LISTA de novedades (nombre+versión publicada), no el JSON entero: el JSON trae +# contadores que se mueven solos (p.ej. `no_instalados`) y harían «cambiar» un estado idéntico. +huella() { [ -f "$1" ] || { echo ""; return; }; python3 - "$1" <<'PY' +import json, sys +try: + d = json.load(open(sys.argv[1])) +except Exception: + print(""); raise SystemExit +print(",".join(sorted(f"{n['name']}={n['publicada']}" for n in d.get("novedades", [])))) +PY +} + +ANTES="$(huella "$STATE")" +AHORA="$(huella "$NUEVO")" +mv "$NUEVO" "$STATE" + +N=$(python3 -c 'import json,sys; print(len(json.load(open(sys.argv[1])).get("novedades",[])))' "$STATE") +SELLO="$(date -u +%FT%TZ)" + +if [ "$N" = "0" ]; then + echo "$SELLO · al día contra $REPO" + exit 0 +fi + +if [ "$AHORA" = "$ANTES" ]; then + echo "$SELLO · $N novedad(es), las mismas del ciclo anterior (estado en $STATE)" + exit 0 +fi + +echo "$SELLO ⚠ HAY $N NOVEDAD(ES) en $REPO:" +"$TAKANA" outdated --repo "$REPO" --db "$DB" --trust "$TRUST" | sed 's/^/ /'