Dos cosas que el ensayo del guion destapó, las dos medidas: 1. `repo-perfil.sh` NO acotaba el build. `pack --build` es instantáneo con cache-hit y una compilación entera si el artefacto no está sellado, así que «publicá el repo» se convertía en silencio en «ponete a compilar el perfil»: la corrida se puso a moler `libnftnl` en una caja de 4 cores que ya estaba a load 46, y después venía `rust`. `build-repo.sh` llevaba `timeout` desde siempre; el que se corre en producción, no. Ahora BUILD_TIMEOUT (120 s) y SKIP_UNSEALED=1, con el vencimiento tratado como «no estaba sellado» y no como error. Resultado del perfil entero: 173 anclados, 0 sin ancla, 2 saltados (os-release, rust), sin compilar nada. 2. `--install-vhost` pone el bloque de Caddy sin que haya que editar a mano, pero el orden es lo único que lo vuelve seguro: copia → añade → `caddy validate` → SÓLO ENTONCES `reload`. Si validate o reload fallan, restaura la copia y recarga con ella. Un reload con la config rota no tira el sitio nuevo: tira los 19 de la caja. Probado en los dos caminos contra un Caddy de juguete (el de la caja no tiene el admin abierto): con reload bueno, el admin muestra el server nuevo sirviendo el repo y el sitio previo intacto; con reload fallido, el Caddyfile vuelve byte a byte y no queda rastro del bloque. Y `repo-perfil.sh` pasa a respetar TAKANA del entorno: la guarda de publicar-repo.sh comprobaba un binario y se publicaba con otro, que es peor que no tener guarda.
101 lines
5.5 KiB
Bash
Executable File
101 lines
5.5 KiB
Bash
Executable File
#!/bin/sh
|
|
# repo-perfil.sh — publica en un REPOSITORIO FIRMADO la clausura entera de un perfil de
|
|
# `targets.toml`, y verifica la firma antes de darla por buena.
|
|
#
|
|
# ── EN QUÉ SE DIFERENCIA DE `build-repo.sh` ─────────────────────────────────────────────────────
|
|
# `build-repo.sh` recorre las 869 recetas del corpus con `cargo run` por cada una: sirve para el
|
|
# dogfood del corpus completo, no para publicar UNA imagen. Acá el conjunto sale de la misma fuente
|
|
# que la imagen —`yupana.membresia()` sobre `targets.toml`— así que **el repo y la imagen no pueden
|
|
# divergir**: son la misma lista.
|
|
#
|
|
# ── LA CLAVE NO SE INVENTA ──────────────────────────────────────────────────────────────────────
|
|
# `build-repo.sh` genera una clave EFÍMERA si no se le pasa `KEY=`. Un índice firmado con una clave
|
|
# que nadie conoce y que cambia en cada corrida no lo verifica nadie: es decoración (SDD 19 §3.2,
|
|
# «la firma sin gestión de claves es teatro»). Acá, si no está la clave, se ABORTA.
|
|
#
|
|
# ── PUBLICAR NO ES CONSTRUIR (2026-09-21) ───────────────────────────────────────────────────────
|
|
# `pack --build` es instantáneo si el artefacto ya está sellado (cache-hit) y una COMPILACIÓN
|
|
# ENTERA si no lo está. Sin acotarlo, «publicá el repo» se convierte en silencio en «ponete a
|
|
# compilar el perfil»: medido acá, la corrida se paró sola en `libnftnl` y se quedó moliendo en una
|
|
# caja de 4 cores que ya estaba a load 46. `build-repo.sh` ya llevaba el `timeout`; esto no, y es el
|
|
# que se corre en producción. Un vencimiento NO es un error: significa «ése no estaba sellado», y el
|
|
# paquete sale sin ancla (o se lo salta, con SKIP_UNSEALED=1).
|
|
#
|
|
# Uso: ./scripts/repo-perfil.sh [perfil]
|
|
# Env: PERFIL (def servidor) · STORE (def ./store) · REPO (def dist/repo)
|
|
# KEY (def ~/.config/takana/keys/release.ed25519) · TRUST (def ./trust) · DISTRO (def dev)
|
|
# BUILD_TIMEOUT (def 120 s por receta) · SKIP_UNSEALED=1 (no publicar lo que no esté sellado)
|
|
set -eu
|
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"; cd "$ROOT"
|
|
|
|
PERFIL="${1:-${PERFIL:-servidor}}"
|
|
STORE="${STORE:-./store}"
|
|
REPO="${REPO:-dist/repo}"
|
|
KEY="${KEY:-$HOME/.config/takana/keys/release.ed25519}"
|
|
TRUST="${TRUST:-./trust}"
|
|
DISTRO="${DISTRO:-dev}"
|
|
BUILD_TIMEOUT="${BUILD_TIMEOUT:-120}"
|
|
SKIP_UNSEALED="${SKIP_UNSEALED:-0}"
|
|
# Overridable: `publicar-repo.sh` comprueba que el binario traiga el arreglo del SDD 28 §5.5 antes
|
|
# de publicar, y esa guarda es mentira si acá se usa otro. Un guion que valida un binario y ejecuta
|
|
# otro es peor que uno sin guarda: da confianza sin darla.
|
|
TAKANA="${TAKANA:-$ROOT/target/release/takana}"
|
|
|
|
[ -x "$TAKANA" ] || { echo "!! falta $TAKANA (cargo build --release)" >&2; exit 2; }
|
|
[ -r "$KEY" ] || {
|
|
echo "!! no encuentro la clave de firma: $KEY" >&2
|
|
echo " NO se genera una efímera a propósito: un índice firmado con una clave que nadie" >&2
|
|
echo " conoce no lo verifica nadie. Generá la del proyecto una sola vez con:" >&2
|
|
echo " takana keygen release --out ~/.config/takana/keys" >&2
|
|
echo " y publicá la .pub en $TRUST/ (ver trust/README.md)." >&2
|
|
exit 2
|
|
}
|
|
|
|
# El conjunto sale de targets.toml, igual que la imagen.
|
|
LISTA=$(python3 - "$PERFIL" <<'PY'
|
|
import sys, os
|
|
sys.path.insert(0, os.path.abspath("scripts"))
|
|
import yupana, targets as targets_mod
|
|
perfil = sys.argv[1]
|
|
perfiles = targets_mod.load()
|
|
if perfil not in perfiles:
|
|
sys.exit("perfil desconocido: %s" % perfil)
|
|
memb = yupana.membresia(yupana.cargar_colas(), perfiles)
|
|
for cola, nombre in sorted(n for n, ps in memb.items() if perfil in ps and n[0] != "wanted"):
|
|
if cola == "corpus":
|
|
print(nombre)
|
|
PY
|
|
)
|
|
total=$(printf '%s\n' "$LISTA" | grep -c .)
|
|
echo "==> perfil '$PERFIL': $total paquetes en la clausura → $REPO"
|
|
|
|
rm -rf "$REPO"; mkdir -p "$REPO"
|
|
anclados=0; sin_ancla=0; fallaron=""; saltados=""
|
|
for n in $LISTA; do
|
|
r="recipes/$n.toml"
|
|
[ -r "$r" ] || { fallaron="$fallaron $n(sin-receta)"; continue; }
|
|
if timeout "$BUILD_TIMEOUT" "$TAKANA" --store "$STORE" pack "$r" --repo "$REPO" \
|
|
--distro-version "$DISTRO" --build >/dev/null 2>&1; then
|
|
anclados=$((anclados + 1))
|
|
elif [ "$SKIP_UNSEALED" = "1" ]; then
|
|
saltados="$saltados $n"
|
|
elif "$TAKANA" --store "$STORE" pack "$r" --repo "$REPO" --distro-version "$DISTRO" >/dev/null 2>&1; then
|
|
sin_ancla=$((sin_ancla + 1))
|
|
else
|
|
fallaron="$fallaron $n"
|
|
fi
|
|
done
|
|
|
|
echo "==> firmando el índice con $KEY"
|
|
"$TAKANA" --store "$STORE" repo sign --repo "$REPO" --key "$KEY" >/dev/null
|
|
|
|
# ── la guarda: que la firma VERIFIQUE, y que una alterada NO ────────────────────────────────────
|
|
"$TAKANA" --store "$STORE" repo verify --repo "$REPO" --trust "$TRUST" >/dev/null 2>&1 \
|
|
|| { echo "!! el índice NO verifica contra $TRUST — la clave publicada no es la que firmó" >&2; exit 1; }
|
|
echo "==> ✓ el índice verifica contra $TRUST"
|
|
|
|
echo
|
|
echo "publicado: $anclados con expected_hash anclado, $sin_ancla sin ancla.${saltados:+ SIN SELLAR (saltados):$saltados}${fallaron:+ FALLARON:$fallaron}"
|
|
echo "repo firmado en $REPO"
|
|
echo " consumir: takana install <nombre> --repo <url> --trust <trust> --require-signed"
|