#!/bin/sh # E2E de la Etapa E4 (upgrades atómicos con rollback) contra el binario `hammer` REAL. # # Monta un store sintético con dos árboles "producto" (v1, v2), un root FHS vivo, y ejercita: # apply v1 → apply v2 → rollback → rollback, verificando el estado del root en cada paso. # Todo en host, sin VM: valida el cableado del CLI + la semántica generación/rollback de hammer-upgrade. # # Uso: scripts/upgrade-e2e-test.sh set -eu ROOT_DIR="$(CDPATH= cd "$(dirname "$0")/.." && pwd)" WORK="$(mktemp -d)" trap 'chmod -R u+w "$WORK" 2>/dev/null || true; rm -rf "$WORK"' EXIT STORE="$WORK/store" ROOT="$WORK/root" STATE="$WORK/state" JOURNAL="$WORK/journal" mkdir -p "$STORE" "$ROOT/usr/bin" "$ROOT/etc" echo "==> compilando hammer" cargo build -q -p hammer-cli --manifest-path "$ROOT_DIR/Cargo.toml" HAMMER="$ROOT_DIR/target/debug/hammer" up() { "$HAMMER" --store "$STORE" upgrade --root "$ROOT" --state-root "$STATE" --journal "$JOURNAL" "$@"; } # --- estado pristino del root (lo que el rollback final debe restaurar) --- printf 'PRISTINE-ls' > "$ROOT/usr/bin/ls" printf 'motd original\n' > "$ROOT/etc/motd" # --- sella dos árboles producto sintéticos en el store (vía `hammer build`? no: usamos un mini-store # a mano replicando la forma <64hex>-; el of_tree lo computa hammer al aplicar) --- seal_tree() { # $1=hex64 $2=name $3=builder-fn dir="$STORE/$1-$2" rm -rf "$dir"; mkdir -p "$dir" "$3" "$dir" chmod -R a-w "$dir" # el store sella read-only; replicamos para fidelidad } hex() { printf '%s' "$1"; i=${#1}; while [ "$i" -lt 64 ]; do printf '0'; i=$((i+1)); done; } build_v1() { mkdir -p "$1/usr/bin" "$1/bin" printf 'LS-v1' > "$1/usr/bin/ls" printf 'OLDtool'> "$1/usr/bin/oldtool" ln -s busybox "$1/bin/sh" } build_v2() { mkdir -p "$1/usr/bin" "$1/bin" printf 'LS-v2' > "$1/usr/bin/ls" printf 'NEWtool'> "$1/usr/bin/newtool" # oldtool desaparece ln -s busybox "$1/bin/sh" } seal_tree "$(hex aa11)" product-rootfs build_v1 seal_tree "$(hex bb22)" product-rootfs build_v2 V1="$(hex aa11)-product-rootfs" V2="$(hex bb22)-product-rootfs" assert() { # $1=path $2=expected-content $3=label got="$(cat "$1" 2>/dev/null || echo '')" if [ "$got" != "$2" ]; then echo "FALLO [$3]: $1 = '$got', esperaba '$2'" >&2; exit 1; fi echo " ok [$3] $1 = $2" } assert_absent() { [ ! -e "$1" ] || { echo "FALLO [$2]: $1 debería estar ausente" >&2; exit 1; }; echo " ok [$2] $1 ausente"; } echo "==> apply v1" up apply "$V1" assert "$ROOT/usr/bin/ls" "LS-v1" "v1" assert "$ROOT/usr/bin/oldtool" "OLDtool" "v1" [ -L "$ROOT/bin/sh" ] || { echo "FALLO: bin/sh no es symlink" >&2; exit 1; } echo "==> apply v2 (pisa ls, añade newtool, RETIRA oldtool)" up apply "$V2" assert "$ROOT/usr/bin/ls" "LS-v2" "v2" assert "$ROOT/usr/bin/newtool" "NEWtool" "v2" assert_absent "$ROOT/usr/bin/oldtool" "v2" echo "==> status" up status echo "==> rollback (v2 → v1: ls vuelve, newtool se borra, oldtool se restaura)" up rollback assert "$ROOT/usr/bin/ls" "LS-v1" "rb→v1" assert "$ROOT/usr/bin/oldtool" "OLDtool" "rb→v1" assert_absent "$ROOT/usr/bin/newtool" "rb→v1" echo "==> rollback (v1 → pristino: el root vuelve EXACTO al estado pre-upgrades)" up rollback assert "$ROOT/usr/bin/ls" "PRISTINE-ls" "rb→pristino" assert "$ROOT/etc/motd" "$(printf 'motd original\n')" "rb→pristino" assert_absent "$ROOT/usr/bin/oldtool" "rb→pristino" echo "==> idempotencia: re-apply v1 sobre pristino vuelve a generar, status coherente" up apply "$V1" >/dev/null up apply "$V1" # mismo árbol ⇒ no-op echo "==> diario: registró cambios" LINES="$(wc -l < "$JOURNAL/mutations.jsonl")" [ "$LINES" -gt 0 ] || { echo "FALLO: diario vacío" >&2; exit 1; } echo " ok: $LINES eventos en el diario" echo "==> prune: tras los dos rollback completos las gens 1 y 2 quedaron huérfanas (current=3)" GENS_ANTES="$(ls "$STATE/generations" | wc -l)" up prune GENS_DESPUES="$(ls "$STATE/generations" | wc -l)" [ "$GENS_ANTES" -gt "$GENS_DESPUES" ] || { echo "FALLO: prune no borró huérfanas ($GENS_ANTES→$GENS_DESPUES)" >&2; exit 1; } echo " ok: prune $GENS_ANTES→$GENS_DESPUES generación(es)" # el árbol vivo (gen 3 = v1) sigue intacto tras el prune. assert "$ROOT/usr/bin/ls" "LS-v1" "post-prune" echo echo "✓✓ E4 upgrade E2E VERDE: apply/rollback atómicos + restauración exacta + idempotencia + diario + prune"