#!/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/^/ /'