#!/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 --repo --trust --require-signed"