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…
primero se elige, después se salta — y sólo si hace falta (SDD 32)
1#!/bin/sh
2# instalador-usb-test.sh — el USB, arrancado por FIRMWARE UEFI de verdad (OVMF), no por `-kernel`.
3#
4# ══ QUÉ PRUEBA ════════════════════════════════════════════════════════════════════════════════
5# La cadena entera del medio, que es lo que el salto NO ejercita:
6#
7# firmware UEFI → \EFI\BOOT\BOOTX64.EFI (EFI-stub) → initramfs de PIVOTE
8# → findfs LABEL=takana-medio → switch_root → el instalador
9#
10# Con `-kernel` QEMU salta el firmware entero y no probaría ni la tabla, ni la ESP, ni el stub, ni
11# el nombre `\initramfs.cpio.gz` que el cmdline horneado exige. Por eso acá va OVMF por `pflash` y
12# **sin `-kernel`**: la firmware busca sola la ruta de medios removibles.
13#
14# ⚠ `-vga none`. No es por ahorrar: con pantalla, el instalador manda su pregunta a `tty0` —que es
15# lo correcto en un portátil— y por serie no se vería nada. Sin pantalla, el `vtconsole` es `dummy`,
16# el instalador lo detecta y se va al serial, que es donde este arnés puede mirar. Las dos ramas de
17# esa decisión se ejercitan: ésta acá, y la de pantalla en el metal del usuario.
18#
19# Uso: scripts/instalador-usb-test.sh [--conservar]
20# Vars: WORKER (def dev.gioser.net) · IMG (el USB) · P (dir de trabajo)
21set -eu
22ROOT="$(cd "$(dirname "$0")/.." && pwd)"; cd "$ROOT"
23WORKER="${WORKER:-dev.gioser.net}"
24IMG="${IMG:-work/takana-instalador-usb.img}"
25P="${P:-work/.usb-test}"
26REMOTO="/tmp/usb-test-$$"
27CONSERVAR=""; INSTALAR="${INSTALAR:-}"
28for a in "$@"; do case "$a" in
29 --conservar) CONSERVAR=1 ;;
30 # Además de llegar al censo, CONTESTARLO: se teclea la opción por el serial y se comprueba
31 # después, sobre el disco, que se instaló donde se dijo. La opción va fija porque el layout lo
32 # fija este arnés, y el censo se comprueba ANTES de contestar para que un cambio de layout falle
33 # en voz alta en vez de elegir otra partición en silencio.
34 --instalar) INSTALAR=1 ;;
35 *) echo "opción desconocida: $a" >&2; exit 2 ;;
36esac; done
37
38[ -r "$IMG" ] || { echo "no encuentro $IMG (armalo con scripts/instalador-usb.sh)" >&2; exit 1; }
39
40. "$(dirname "$0")/lib/herramientas-desde-store.sh"
41SFDISK=$(herramienta util-linux sbin/sfdisk sfdisk) || exit 1
42MFORMAT=$(herramienta mtools usr/bin/mformat mformat) || exit 1
43MMD=$(herramienta mtools usr/bin/mmd mmd) || exit 1
44MCOPY=$(herramienta mtools usr/bin/mcopy mcopy) || exit 1
45GZIP=$(herramienta gzip bin/gzip gzip) || exit 1
46
47FALLOS=0
48ok() { echo " ✓ $*"; }
49falla(){ echo " ✗ $*" >&2; FALLOS=$(( FALLOS + 1 )); }
50
51mkdir -p "$P"
52D="$P/destino.img"
53echo "==> disco DESTINO con vecinos de verdad (ESP con Windows + 2400M libre + hueco)"
54rm -f "$D"; truncate -s 4096M "$D"
55"$SFDISK" --quiet "$D" <<'EOF'
56label: gpt
57start=2048, size=262144, type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B, name="EFI System Partition"
58start=264192, size=4915200, type=linux, name="destino"
59EOF
60rm -f "$P/esp.img"; truncate -s 128M "$P/esp.img"
61"$MFORMAT" -F -v VECINO -i "$P/esp.img" ::
62"$MMD" -i "$P/esp.img" ::/EFI ::/EFI/Microsoft ::/EFI/Microsoft/Boot
63head -c 400000 /dev/urandom > "$P/ajeno.bin"
64"$MCOPY" -i "$P/esp.img" "$P/ajeno.bin" ::/EFI/Microsoft/Boot/bootmgfw.efi
65dd if="$P/esp.img" of="$D" bs=1M seek=1 conv=notrunc status=none
66
67echo "==> subiendo al worker ($WORKER)"
68ssh -o BatchMode=yes "root@$WORKER" "mkdir -p $REMOTO"
69for f in "$IMG" "$D"; do
70 "$GZIP" -1 -c "$f" | ssh -o BatchMode=yes "root@$WORKER" "gzip -d -c > $REMOTO/$(basename "$f")"
71done
72
73echo "==> arrancando por FIRMWARE (OVMF, sin -kernel)"
74LOG="$P/serie.log"
75# La entrada del menú se TECLEA por el serial. El `sleep` de delante no es pereza: si los
76# caracteres llegan antes de que el instalador abra la consola se pierden, y el menú se queda
77# esperando una tecla que ya pasó.
78if [ -n "$INSTALAR" ]; then
79 # `/dev/vdb2`: el destino de este banco no tiene filesystem, así que el censo lo marca CRUDA y
80 # la confirmación NO es un «sí» — hay que escribir el nombre del dispositivo. Es la contención
81 # que protege un almacén de negación plausible de un instalador ingenuo, y este arnés la
82 # ejercita en vez de esquivarla.
83 TECLAS="(sleep 40; printf '4\n/dev/vdb2\nsi\n'; sleep 300)"
84else
85 TECLAS="cat /dev/null"
86fi
87ssh -o BatchMode=yes "root@$WORKER" "cd $REMOTO && \
88 OV=\$(ls /usr/share/edk2/ovmf/OVMF_CODE.fd /usr/share/ovmf/x64/OVMF_CODE.fd 2>/dev/null | head -1) && \
89 OVV=\$(ls /usr/share/edk2/ovmf/OVMF_VARS.fd /usr/share/ovmf/x64/OVMF_VARS.fd 2>/dev/null | head -1) && \
90 cp \"\$OVV\" vars.fd && \
91 $TECLAS | timeout 420 qemu-system-x86_64 -m 4096 -smp 2 -nographic -vga none -no-reboot \
92 -drive if=pflash,format=raw,unit=0,readonly=on,file=\"\$OV\" \
93 -drive if=pflash,format=raw,unit=1,file=vars.fd \
94 -drive file=$(basename "$IMG"),format=raw,if=virtio \
95 -drive file=destino.img,format=raw,if=virtio" > "$LOG" 2>&1 || true
96if [ -n "$INSTALAR" ]; then
97 echo "==> trayendo el disco destino de vuelta"
98 ssh -o BatchMode=yes "root@$WORKER" "gzip -c $REMOTO/destino.img" > "$P/destino-despues.img.gz"
99 "$GZIP" -d -c "$P/destino-despues.img.gz" > "$P/destino-despues.img"
100fi
101echo " log: $LOG ($(wc -l < "$LOG") líneas)"
102[ -n "$CONSERVAR" ] || ssh -o BatchMode=yes "root@$WORKER" "rm -rf $REMOTO"
103
104echo "==> comprobaciones (la CADENA, que es lo que el salto no prueba)"
105limpio() { sed 's/\x1b\[[0-9;]*[a-zA-Z]//g; s/\r//g' "$LOG"; }
106limpio > "$P/serie.txt"
107grep -q 'TAKANA-MEDIO: buscando el medio' "$P/serie.txt" \
108 && ok "la firmware lanzó BOOTX64.EFI y el EFI-stub cargó el pivote" \
109 || falla "no llegó al pivote: la firmware no arrancó el medio"
110grep -q 'TAKANA-MEDIO-OK: switch_root' "$P/serie.txt" \
111 && ok "el pivote encontró LABEL=takana-medio y pivotó" \
112 || falla "el pivote no encontró el medio"
113grep -q 'arrancado desde el MEDIO' "$P/serie.txt" \
114 && ok "el instalador sabe que vino del medio (y no dice «el sistema viejo ya no corre»)" \
115 || falla "el instalador no distinguió medio de salto"
116grep -q 'la pregunta salen por /dev/ttyS0\|salen por /dev/console' "$P/serie.txt" \
117 && ok "sin pantalla, la pregunta se fue al serial (vtconsole dummy detectado)" \
118 || falla "la consola elegida no fue la serie estando sin pantalla"
119grep -q 'el terreno de esta máquina' "$P/serie.txt" \
120 && ok "el censo del terreno salió por la consola" || falla "no salió el censo"
121# La prueba de que el medio NO se ofrece a sí mismo: su ext4 es la raíz viva y tiene que estar
122# marcada NO TOCAR, no ⚡. Ofrecerla sería ofrecer formatear el instalador mientras corre.
123grep -q 'es el MEDIO desde el que arrancaste' "$P/serie.txt" \
124 && ok "el medio NO se ofrece a sí mismo como destino" \
125 || falla "el medio no quedó marcado como intocable"
126grep -q 'NTFS\|NO TOCAR' "$P/serie.txt" && ok "hay particiones marcadas NO TOCAR" || falla "el censo no marcó nada"
127
128# La opción que se va a teclear tiene que ser la que creemos, y se comprueba ANTES de dar por bueno
129# nada: si el layout cambió, «4» ya no es el destino y el test tiene que decirlo.
130if [ -n "$INSTALAR" ]; then
131 grep -qE '^ +4 +/dev/vdb2 ' "$P/serie.txt" \
132 && ok "la opción 4 del menú es /dev/vdb2 (la que se tecleó)" \
133 || falla "la opción 4 no es /dev/vdb2: el layout cambió y se tecleó a ciegas"
134 grep -q 'Escribí el nombre completo (/dev/vdb2)' "$P/serie.txt" \
135 && ok "lo que no se identifica pidió confirmación FUERTE (escribir el nombre)" \
136 || falla "una partición sin identificar se aceptó con un sí"
137 grep -q 'takana instalada en' "$P/serie.txt" && ok "el instalador dijo que instaló" \
138 || falla "el instalador no llegó a instalar (¿las teclas no entraron por el serial?)"
139 A="$P/destino-despues.img"
140 BL2=$(herramienta util-linux sbin/blkid blkid) || exit 1
141 dd if="$A" of="$P/p2.img" bs=1M skip=129 count=2400 status=none
142 "$BL2" "$P/p2.img" 2>/dev/null | grep -q 'LABEL="hammer-root"' \
143 && ok "el destino quedó ext4 con LABEL=hammer-root" \
144 || falla "el destino no tiene LABEL=hammer-root: $("$BL2" "$P/p2.img" 2>&1 | head -1)"
145 MD=$(herramienta mtools usr/bin/mdir mdir) || exit 1
146 "$MD" -i "$A@@1048576" ::/EFI/takana 2>/dev/null < /dev/null | grep -qi 'takanax64' \
147 && ok "la ESP del destino tiene \\EFI\\takana\\takanax64.efi" || falla "la ESP del destino quedó sin nuestro cargador"
148 MC=$(herramienta mtools usr/bin/mcopy mcopy) || exit 1
149 rm -f "$P/vecino-despues.bin"
150 "$MC" -i "$A@@1048576" ::/EFI/Microsoft/Boot/bootmgfw.efi "$P/vecino-despues.bin" 2>/dev/null < /dev/null || true
151 [ "$(sha256sum "$P/ajeno.bin" | cut -d' ' -f1)" = "$(sha256sum "$P/vecino-despues.bin" 2>/dev/null | cut -d' ' -f1)" ] \
152 && ok "el bootmgfw.efi del vecino quedó byte a byte igual" || falla "el vecino de la ESP cambió"
153 rm -f "$P/p2.img" "$P/destino-despues.img" "$P/destino-despues.img.gz"
154fi
155
156echo
157if [ "$FALLOS" = 0 ]; then echo "VERDE — el USB arranca por firmware y llega al instalador."; else
158 echo "ROJO — $FALLOS comprobación(es). Log: $P/serie.txt" >&2; exit 1; fi