#!/bin/sh # instalador-usb-test.sh — el USB, arrancado por FIRMWARE UEFI de verdad (OVMF), no por `-kernel`. # # ══ QUÉ PRUEBA ════════════════════════════════════════════════════════════════════════════════ # La cadena entera del medio, que es lo que el salto NO ejercita: # # firmware UEFI → \EFI\BOOT\BOOTX64.EFI (EFI-stub) → initramfs de PIVOTE # → findfs LABEL=takana-medio → switch_root → el instalador # # Con `-kernel` QEMU salta el firmware entero y no probaría ni la tabla, ni la ESP, ni el stub, ni # el nombre `\initramfs.cpio.gz` que el cmdline horneado exige. Por eso acá va OVMF por `pflash` y # **sin `-kernel`**: la firmware busca sola la ruta de medios removibles. # # ⚠ `-vga none`. No es por ahorrar: con pantalla, el instalador manda su pregunta a `tty0` —que es # lo correcto en un portátil— y por serie no se vería nada. Sin pantalla, el `vtconsole` es `dummy`, # el instalador lo detecta y se va al serial, que es donde este arnés puede mirar. Las dos ramas de # esa decisión se ejercitan: ésta acá, y la de pantalla en el metal del usuario. # # Uso: scripts/instalador-usb-test.sh [--conservar] # Vars: WORKER (def dev.gioser.net) · IMG (el USB) · P (dir de trabajo) set -eu ROOT="$(cd "$(dirname "$0")/.." && pwd)"; cd "$ROOT" WORKER="${WORKER:-dev.gioser.net}" IMG="${IMG:-work/takana-instalador-usb.img}" P="${P:-work/.usb-test}" REMOTO="/tmp/usb-test-$$" CONSERVAR=""; INSTALAR="${INSTALAR:-}" for a in "$@"; do case "$a" in --conservar) CONSERVAR=1 ;; # Además de llegar al censo, CONTESTARLO: se teclea la opción por el serial y se comprueba # después, sobre el disco, que se instaló donde se dijo. La opción va fija porque el layout lo # fija este arnés, y el censo se comprueba ANTES de contestar para que un cambio de layout falle # en voz alta en vez de elegir otra partición en silencio. --instalar) INSTALAR=1 ;; *) echo "opción desconocida: $a" >&2; exit 2 ;; esac; done [ -r "$IMG" ] || { echo "no encuentro $IMG (armalo con scripts/instalador-usb.sh)" >&2; exit 1; } . "$(dirname "$0")/lib/herramientas-desde-store.sh" SFDISK=$(herramienta util-linux sbin/sfdisk sfdisk) || exit 1 MFORMAT=$(herramienta mtools usr/bin/mformat mformat) || exit 1 MMD=$(herramienta mtools usr/bin/mmd mmd) || exit 1 MCOPY=$(herramienta mtools usr/bin/mcopy mcopy) || exit 1 GZIP=$(herramienta gzip bin/gzip gzip) || exit 1 FALLOS=0 ok() { echo " ✓ $*"; } falla(){ echo " ✗ $*" >&2; FALLOS=$(( FALLOS + 1 )); } mkdir -p "$P" D="$P/destino.img" echo "==> disco DESTINO con vecinos de verdad (ESP con Windows + 2400M libre + hueco)" rm -f "$D"; truncate -s 4096M "$D" "$SFDISK" --quiet "$D" <<'EOF' label: gpt start=2048, size=262144, type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B, name="EFI System Partition" start=264192, size=4915200, type=linux, name="destino" EOF rm -f "$P/esp.img"; truncate -s 128M "$P/esp.img" "$MFORMAT" -F -v VECINO -i "$P/esp.img" :: "$MMD" -i "$P/esp.img" ::/EFI ::/EFI/Microsoft ::/EFI/Microsoft/Boot head -c 400000 /dev/urandom > "$P/ajeno.bin" "$MCOPY" -i "$P/esp.img" "$P/ajeno.bin" ::/EFI/Microsoft/Boot/bootmgfw.efi dd if="$P/esp.img" of="$D" bs=1M seek=1 conv=notrunc status=none echo "==> subiendo al worker ($WORKER)" ssh -o BatchMode=yes "root@$WORKER" "mkdir -p $REMOTO" for f in "$IMG" "$D"; do "$GZIP" -1 -c "$f" | ssh -o BatchMode=yes "root@$WORKER" "gzip -d -c > $REMOTO/$(basename "$f")" done echo "==> arrancando por FIRMWARE (OVMF, sin -kernel)" LOG="$P/serie.log" # La entrada del menú se TECLEA por el serial. El `sleep` de delante no es pereza: si los # caracteres llegan antes de que el instalador abra la consola se pierden, y el menú se queda # esperando una tecla que ya pasó. if [ -n "$INSTALAR" ]; then # `/dev/vdb2`: el destino de este banco no tiene filesystem, así que el censo lo marca CRUDA y # la confirmación NO es un «sí» — hay que escribir el nombre del dispositivo. Es la contención # que protege un almacén de negación plausible de un instalador ingenuo, y este arnés la # ejercita en vez de esquivarla. TECLAS="(sleep 40; printf '4\n/dev/vdb2\nsi\n'; sleep 300)" else TECLAS="cat /dev/null" fi ssh -o BatchMode=yes "root@$WORKER" "cd $REMOTO && \ OV=\$(ls /usr/share/edk2/ovmf/OVMF_CODE.fd /usr/share/ovmf/x64/OVMF_CODE.fd 2>/dev/null | head -1) && \ OVV=\$(ls /usr/share/edk2/ovmf/OVMF_VARS.fd /usr/share/ovmf/x64/OVMF_VARS.fd 2>/dev/null | head -1) && \ cp \"\$OVV\" vars.fd && \ $TECLAS | timeout 420 qemu-system-x86_64 -m 4096 -smp 2 -nographic -vga none -no-reboot \ -drive if=pflash,format=raw,unit=0,readonly=on,file=\"\$OV\" \ -drive if=pflash,format=raw,unit=1,file=vars.fd \ -drive file=$(basename "$IMG"),format=raw,if=virtio \ -drive file=destino.img,format=raw,if=virtio" > "$LOG" 2>&1 || true if [ -n "$INSTALAR" ]; then echo "==> trayendo el disco destino de vuelta" ssh -o BatchMode=yes "root@$WORKER" "gzip -c $REMOTO/destino.img" > "$P/destino-despues.img.gz" "$GZIP" -d -c "$P/destino-despues.img.gz" > "$P/destino-despues.img" fi echo " log: $LOG ($(wc -l < "$LOG") líneas)" [ -n "$CONSERVAR" ] || ssh -o BatchMode=yes "root@$WORKER" "rm -rf $REMOTO" echo "==> comprobaciones (la CADENA, que es lo que el salto no prueba)" limpio() { sed 's/\x1b\[[0-9;]*[a-zA-Z]//g; s/\r//g' "$LOG"; } limpio > "$P/serie.txt" grep -q 'TAKANA-MEDIO: buscando el medio' "$P/serie.txt" \ && ok "la firmware lanzó BOOTX64.EFI y el EFI-stub cargó el pivote" \ || falla "no llegó al pivote: la firmware no arrancó el medio" grep -q 'TAKANA-MEDIO-OK: switch_root' "$P/serie.txt" \ && ok "el pivote encontró LABEL=takana-medio y pivotó" \ || falla "el pivote no encontró el medio" grep -q 'arrancado desde el MEDIO' "$P/serie.txt" \ && ok "el instalador sabe que vino del medio (y no dice «el sistema viejo ya no corre»)" \ || falla "el instalador no distinguió medio de salto" grep -q 'la pregunta salen por /dev/ttyS0\|salen por /dev/console' "$P/serie.txt" \ && ok "sin pantalla, la pregunta se fue al serial (vtconsole dummy detectado)" \ || falla "la consola elegida no fue la serie estando sin pantalla" grep -q 'el terreno de esta máquina' "$P/serie.txt" \ && ok "el censo del terreno salió por la consola" || falla "no salió el censo" # La prueba de que el medio NO se ofrece a sí mismo: su ext4 es la raíz viva y tiene que estar # marcada NO TOCAR, no ⚡. Ofrecerla sería ofrecer formatear el instalador mientras corre. grep -q 'es el MEDIO desde el que arrancaste' "$P/serie.txt" \ && ok "el medio NO se ofrece a sí mismo como destino" \ || falla "el medio no quedó marcado como intocable" grep -q 'NTFS\|NO TOCAR' "$P/serie.txt" && ok "hay particiones marcadas NO TOCAR" || falla "el censo no marcó nada" # La opción que se va a teclear tiene que ser la que creemos, y se comprueba ANTES de dar por bueno # nada: si el layout cambió, «4» ya no es el destino y el test tiene que decirlo. if [ -n "$INSTALAR" ]; then grep -qE '^ +4 +/dev/vdb2 ' "$P/serie.txt" \ && ok "la opción 4 del menú es /dev/vdb2 (la que se tecleó)" \ || falla "la opción 4 no es /dev/vdb2: el layout cambió y se tecleó a ciegas" grep -q 'Escribí el nombre completo (/dev/vdb2)' "$P/serie.txt" \ && ok "lo que no se identifica pidió confirmación FUERTE (escribir el nombre)" \ || falla "una partición sin identificar se aceptó con un sí" grep -q 'takana instalada en' "$P/serie.txt" && ok "el instalador dijo que instaló" \ || falla "el instalador no llegó a instalar (¿las teclas no entraron por el serial?)" A="$P/destino-despues.img" BL2=$(herramienta util-linux sbin/blkid blkid) || exit 1 dd if="$A" of="$P/p2.img" bs=1M skip=129 count=2400 status=none "$BL2" "$P/p2.img" 2>/dev/null | grep -q 'LABEL="hammer-root"' \ && ok "el destino quedó ext4 con LABEL=hammer-root" \ || falla "el destino no tiene LABEL=hammer-root: $("$BL2" "$P/p2.img" 2>&1 | head -1)" MD=$(herramienta mtools usr/bin/mdir mdir) || exit 1 "$MD" -i "$A@@1048576" ::/EFI/takana 2>/dev/null < /dev/null | grep -qi 'takanax64' \ && ok "la ESP del destino tiene \\EFI\\takana\\takanax64.efi" || falla "la ESP del destino quedó sin nuestro cargador" MC=$(herramienta mtools usr/bin/mcopy mcopy) || exit 1 rm -f "$P/vecino-despues.bin" "$MC" -i "$A@@1048576" ::/EFI/Microsoft/Boot/bootmgfw.efi "$P/vecino-despues.bin" 2>/dev/null < /dev/null || true [ "$(sha256sum "$P/ajeno.bin" | cut -d' ' -f1)" = "$(sha256sum "$P/vecino-despues.bin" 2>/dev/null | cut -d' ' -f1)" ] \ && ok "el bootmgfw.efi del vecino quedó byte a byte igual" || falla "el vecino de la ESP cambió" rm -f "$P/p2.img" "$P/destino-despues.img" "$P/destino-despues.img.gz" fi echo if [ "$FALLOS" = 0 ]; then echo "VERDE — el USB arranca por firmware y llega al instalador."; else echo "ROJO — $FALLOS comprobación(es). Log: $P/serie.txt" >&2; exit 1; fi