sergio/takana
> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…
hammer-upgrade: upgrades atómicos con generaciones y rollback (Etapa E4, primer corte)
1#!/bin/sh
2# E2E de la Etapa E4 (upgrades atómicos con rollback) contra el binario `hammer` REAL.
3#
4# Monta un store sintético con dos árboles "producto" (v1, v2), un root FHS vivo, y ejercita:
5# apply v1 → apply v2 → rollback → rollback, verificando el estado del root en cada paso.
6# Todo en host, sin VM: valida el cableado del CLI + la semántica generación/rollback de hammer-upgrade.
7#
8# Uso: scripts/upgrade-e2e-test.sh
9set -eu
10
11ROOT_DIR="$(CDPATH= cd "$(dirname "$0")/.." && pwd)"
12WORK="$(mktemp -d)"
13trap 'chmod -R u+w "$WORK" 2>/dev/null || true; rm -rf "$WORK"' EXIT
14
15STORE="$WORK/store"
16ROOT="$WORK/root"
17STATE="$WORK/state"
18JOURNAL="$WORK/journal"
19mkdir -p "$STORE" "$ROOT/usr/bin" "$ROOT/etc"
20
21echo "==> compilando hammer"
22cargo build -q -p hammer-cli --manifest-path "$ROOT_DIR/Cargo.toml"
23HAMMER="$ROOT_DIR/target/debug/hammer"
24
25up() { "$HAMMER" --store "$STORE" upgrade --root "$ROOT" --state-root "$STATE" --journal "$JOURNAL" "$@"; }
26
27# --- estado pristino del root (lo que el rollback final debe restaurar) ---
28printf 'PRISTINE-ls' > "$ROOT/usr/bin/ls"
29printf 'motd original\n' > "$ROOT/etc/motd"
30
31# --- sella dos árboles producto sintéticos en el store (vía `hammer build`? no: usamos un mini-store
32# a mano replicando la forma <64hex>-<name>; el of_tree lo computa hammer al aplicar) ---
33seal_tree() {
34 # $1=hex64 $2=name $3=builder-fn
35 dir="$STORE/$1-$2"
36 rm -rf "$dir"; mkdir -p "$dir"
37 "$3" "$dir"
38 chmod -R a-w "$dir" # el store sella read-only; replicamos para fidelidad
39}
40hex() { printf '%s' "$1"; i=${#1}; while [ "$i" -lt 64 ]; do printf '0'; i=$((i+1)); done; }
41
42build_v1() {
43 mkdir -p "$1/usr/bin" "$1/bin"
44 printf 'LS-v1' > "$1/usr/bin/ls"
45 printf 'OLDtool'> "$1/usr/bin/oldtool"
46 ln -s busybox "$1/bin/sh"
47}
48build_v2() {
49 mkdir -p "$1/usr/bin" "$1/bin"
50 printf 'LS-v2' > "$1/usr/bin/ls"
51 printf 'NEWtool'> "$1/usr/bin/newtool" # oldtool desaparece
52 ln -s busybox "$1/bin/sh"
53}
54seal_tree "$(hex aa11)" product-rootfs build_v1
55seal_tree "$(hex bb22)" product-rootfs build_v2
56V1="$(hex aa11)-product-rootfs"
57V2="$(hex bb22)-product-rootfs"
58
59assert() { # $1=path $2=expected-content $3=label
60 got="$(cat "$1" 2>/dev/null || echo '<ausente>')"
61 if [ "$got" != "$2" ]; then echo "FALLO [$3]: $1 = '$got', esperaba '$2'" >&2; exit 1; fi
62 echo " ok [$3] $1 = $2"
63}
64assert_absent() { [ ! -e "$1" ] || { echo "FALLO [$2]: $1 debería estar ausente" >&2; exit 1; }; echo " ok [$2] $1 ausente"; }
65
66echo "==> apply v1"
67up apply "$V1"
68assert "$ROOT/usr/bin/ls" "LS-v1" "v1"
69assert "$ROOT/usr/bin/oldtool" "OLDtool" "v1"
70[ -L "$ROOT/bin/sh" ] || { echo "FALLO: bin/sh no es symlink" >&2; exit 1; }
71
72echo "==> apply v2 (pisa ls, añade newtool, RETIRA oldtool)"
73up apply "$V2"
74assert "$ROOT/usr/bin/ls" "LS-v2" "v2"
75assert "$ROOT/usr/bin/newtool" "NEWtool" "v2"
76assert_absent "$ROOT/usr/bin/oldtool" "v2"
77
78echo "==> status"
79up status
80
81echo "==> rollback (v2 → v1: ls vuelve, newtool se borra, oldtool se restaura)"
82up rollback
83assert "$ROOT/usr/bin/ls" "LS-v1" "rb→v1"
84assert "$ROOT/usr/bin/oldtool" "OLDtool" "rb→v1"
85assert_absent "$ROOT/usr/bin/newtool" "rb→v1"
86
87echo "==> rollback (v1 → pristino: el root vuelve EXACTO al estado pre-upgrades)"
88up rollback
89assert "$ROOT/usr/bin/ls" "PRISTINE-ls" "rb→pristino"
90assert "$ROOT/etc/motd" "$(printf 'motd original\n')" "rb→pristino"
91assert_absent "$ROOT/usr/bin/oldtool" "rb→pristino"
92
93echo "==> idempotencia: re-apply v1 sobre pristino vuelve a generar, status coherente"
94up apply "$V1" >/dev/null
95up apply "$V1" # mismo árbol ⇒ no-op
96
97echo "==> diario: registró cambios"
98LINES="$(wc -l < "$JOURNAL/mutations.jsonl")"
99[ "$LINES" -gt 0 ] || { echo "FALLO: diario vacío" >&2; exit 1; }
100echo " ok: $LINES eventos en el diario"
101
102echo
103echo "✓✓ E4 upgrade E2E VERDE: apply/rollback atómicos + restauración exacta + idempotencia + diario"