#!/bin/sh # instalador-usb.sh — la imagen USB del instalador, para cuando el salto NO se puede. # # ══ POR QUÉ EXISTE ════════════════════════════════════════════════════════════════════════════ # `takana metal install` reemplaza el Linux que corre sin USB, saltando con `kexec`. Eso pide tres # cosas del kernel de ESA máquina —`CONFIG_KEXEC_FILE`, ser root y que Secure Boot no esté en # lockdown— y cualquiera de las tres puede faltar. Cuando falta, `metal check` lo dice y manda acá. # # **El instalador es el mismo**: el mismo árbol, el mismo censo, las mismas guardas. Lo único que # cambia es cómo se llega a él. # # ── EL DISEÑO, Y LA MEDICIÓN QUE LO OBLIGA ──────────────────────────────────────────────────── # La forma obvia sería poner el initramfs del instalador en la ESP y dejar que el EFI-stub lo # cargue. **No se puede, y está medido:** el cuelgue del primer viaje a metal fue exactamente eso, # un initrd de ~100 MiB cargado por el stub (ADR 0010 paso 2). El del instalador son ~122 MiB. # # Entonces el USB usa el MISMO patrón que un disco instalado, que sí está probado: # # firmware → \EFI\BOOT\BOOTX64.EFI (= bzImage, EFI-stub) # → initramfs de PIVOTE (~800 KiB, el que sí entra) # → findfs LABEL=takana-medio → switch_root # → /init del instalador, ya sobre un filesystem de verdad # # ⚠ La etiqueta es `takana-medio` y **no** `hammer-root` a propósito: si el medio se llamara igual # que la raíz de un sistema instalado, el pivote podría montar el disco del usuario en vez del USB # —o al revés— y el que pierde es el disco. Dos cosas distintas, dos nombres distintos. # # Uso: scripts/instalador-usb.sh [--salida ] # Vars: ROOTFS KERNEL (como instalador-paquete.sh) · IMG · ESP_MB (def 96) · HOLGURA_MB (def 128) # # Grabarlo: sudo dd if=work/takana-instalador-usb.img of=/dev/sdX bs=4M oflag=sync status=progress set -eu ROOT="$(cd "$(dirname "$0")/.." && pwd)"; cd "$ROOT" IMG="${IMG:-work/takana-instalador-usb.img}" ESP_MB="${ESP_MB:-96}" # ⚠ **La holgura no puede ser un número fijo.** `du` mide el contenido, no lo que el ext4 NECESITA: # inodos, journal, mapas de bloques. Con 128 MiB fijos alcanzaba para un árbol de 1 GiB y se quedó # corto con uno de 16 —`mke2fs` murió con «Could not allocate block» a mitad de poblar, después de # veinte minutos de trabajo—. Se escala: un 8 % del árbol, y nunca menos de 128 MiB. HOLGURA_MB="${HOLGURA_MB:-}" ETIQUETA="takana-medio" [ "${1:-}" = "--salida" ] && { IMG="${2:?}"; shift 2; } . "$(dirname "$0")/lib/herramientas-desde-store.sh" SFDISK=$(herramienta util-linux sbin/sfdisk sfdisk) || exit 1 MKE2FS=$(herramienta e2fsprogs sbin/mke2fs mke2fs) || 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 MDIR=$(herramienta mtools usr/bin/mdir mdir) || exit 1 CPIO=$(herramienta libarchive usr/bin/bsdcpio cpio) || exit 1 GZIP=$(herramienta gzip bin/gzip gzip) || exit 1 # ── 1) el árbol del instalador — el MISMO que viaja por kexec ───────────────────────────────── TREE="${IRD_TREE:-work/.instalador-initramfs-tree}" echo "==> árbol del instalador (TREE_ONLY: lo arma el generador de siempre)" TREE_ONLY=1 IRD_TREE="$TREE" ROOTFS="${ROOTFS:-}" KERNEL="${KERNEL:-}" ./scripts/instalador-initramfs.sh [ -x "$TREE/init" ] && [ -d "$TREE/payload/stage/root" ] || { echo "el árbol no quedó completo en $TREE" >&2; exit 1; } KERNEL="${KERNEL:-$TREE/payload/bzImage}" [ -r "$KERNEL" ] || { echo "no encuentro el bzImage (set KERNEL=)" >&2; exit 1; } TREE_MB=$(du -sm "$TREE" | cut -f1) if [ -z "$HOLGURA_MB" ]; then HOLGURA_MB=$(( TREE_MB / 12 )) [ "$HOLGURA_MB" -lt 128 ] && HOLGURA_MB=128 fi EXT_MB=$(( TREE_MB + HOLGURA_MB )) TOTAL_MB=$(( ESP_MB + EXT_MB + 2 )) echo " árbol ${TREE_MB} MiB · ext4 ${EXT_MB} MiB · imagen ${TOTAL_MB} MiB" # ── 2) el initramfs de PIVOTE: lo único que carga el EFI-stub ───────────────────────────────── # Es hermano del pivote de `install-image-efi.sh` y comparte sus dos lecciones caras: el log va a # tty0 **y** al serial porque el cmdline horneado deja /dev/console en el serial, y la espera del # disco es LARGA porque el USB enumera de forma asíncrona y tarda segundos (en QEMU nunca se ve). P="work/.instalador-usb"; rm -rf "$P"; mkdir -p "$P/pivote/bin" "$P/pivote/dev" "$P/pivote/proc" \ "$P/pivote/sys" "$P/pivote/newroot" BUSYBOX="${BUSYBOX:-$TREE/bin/busybox}" [ -r "$BUSYBOX" ] || { echo "no encuentro busybox en $BUSYBOX" >&2; exit 1; } cp "$BUSYBOX" "$P/pivote/bin/busybox"; chmod 0755 "$P/pivote/bin/busybox" for a in sh mount umount findfs switch_root mkdir echo sleep ls cat setsid dmesg blkid; do ln -sf busybox "$P/pivote/bin/$a" done cat > "$P/pivote/init" <<'PIVOTE' #!/bin/busybox sh # Pivote del MEDIO de instalación: encuentra el USB por etiqueta y le entrega el control al # instalador. No toca ningún disco: sólo monta el medio del que se arrancó. export PATH=/bin mount -t proc proc /proc 2>/dev/null || true mount -t sysfs sys /sys 2>/dev/null || true mount -t devtmpfs dev /dev 2>/dev/null || true # El cmdline horneado deja /dev/console en el SERIAL: sin escribir explícitamente en tty0, en un # portátil sin puerto serie todo esto es invisible y el arranque parece congelado. log() { for d in /dev/tty0 /dev/ttyS0; do echo "$*" > "$d" 2>/dev/null || true; done; } rescate() { log "TAKANA-MEDIO-FALLO: $*" log " bloques visibles: $(ls /dev/sd* /dev/nvme* /dev/vd* /dev/mmcblk* 2>/dev/null | tr '\n' ' ')" log " shell de rescate en tty1 — probá: blkid ; dmesg | tail -30" setsid -c sh -c 'exec /bin/sh /dev/tty1 2>&1' >/dev/null 2>&1 & exec sh } log "TAKANA-MEDIO: buscando el medio de instalación (LABEL=takana-medio)" # ⏱ 60 s y no 5: el bus USB enumera asíncrono —reset del hub, 1 s de settling por dispositivo, scan # SCSI— y tarda segundos. En QEMU el disco está desde el instante cero, así que esta carrera NO se # ve en validación: aparece la primera vez que alguien arranca de un pendrive de verdad. DEV=""; i=0 while [ "$i" -lt 60 ]; do DEV="$(findfs LABEL=takana-medio 2>/dev/null || true)" [ -n "$DEV" ] && break i=$(( i + 1 )); [ $(( i % 5 )) -eq 0 ] && log "TAKANA-MEDIO: esperando el medio… ${i}s (el USB tarda en enumerar)" sleep 1 done [ -n "$DEV" ] || rescate "no encuentro LABEL=takana-medio tras ${i}s" log "TAKANA-MEDIO: $DEV → montando" mount -t ext4 "$DEV" /newroot || rescate "no pude montar $DEV" [ -x /newroot/init ] || rescate "el medio no trae /init" log "TAKANA-MEDIO-OK: switch_root → el instalador" # El instalador tiene que SABER que vino de un medio: con eso su raíz viva es este USB —y no se # ofrece— mientras que por el salto la raíz viva es el sistema del usuario, que sí se reemplaza. # El entorno sobrevive al `exec` de switch_root, así que esto llega intacto al otro lado. export TAKANA_MEDIO=1 exec switch_root /newroot /init PIVOTE chmod 0755 "$P/pivote/init" ( cd "$P/pivote" && find . -print0 | "$CPIO" --null -o -H newc --owner=root:root 2>/dev/null | "$GZIP" -9 ) > "$P/initramfs.cpio.gz" echo "==> pivote: $(( $(wc -c < "$P/initramfs.cpio.gz") / 1024 )) KiB" # ── 3) la tabla ─────────────────────────────────────────────────────────────────────────────── rm -f "$IMG"; truncate -s "${TOTAL_MB}M" "$IMG" ESP_SECT=$(( ESP_MB * 2048 )) "$SFDISK" --quiet "$IMG" </dev/null 2>&1 < /dev/null || { echo "la ESP no quedó con $f" >&2; exit 1; } done dd if="$P/esp.img" of="$IMG" bs=1M seek=1 conv=notrunc status=none # ── 5) el ext4 con el árbol adentro, sin montar nada ────────────────────────────────────────── echo "==> ext4 «$ETIQUETA» con el instalador adentro (${EXT_MB} MiB, sin montar)" "$MKE2FS" -q -t ext4 -L "$ETIQUETA" -d "$TREE" "$P/medio.img" "${EXT_MB}M" dd if="$P/medio.img" of="$IMG" bs=1M seek=$(( ESP_MB + 1 )) conv=notrunc status=none # ── 6) el control: que la imagen diga lo que creemos ────────────────────────────────────────── echo "==> control" FALLOS=0 ok() { echo " ✓ $*"; } falla(){ echo " ✗ $*" >&2; FALLOS=$(( FALLOS + 1 )); } "$SFDISK" -d "$IMG" | grep -q 'C12A7328' && ok "la tabla trae una ESP" || falla "sin ESP en la tabla" "$MDIR" -i "$IMG@@1048576" ::/EFI/BOOT >/dev/null 2>&1 < /dev/null \ && ok "la ESP se lee desde su offset en la imagen" || falla "la ESP no se lee" dd if="$IMG" bs=1M skip=$(( ESP_MB + 1 )) count="$EXT_MB" status=none > "$P/verif-ext.img" BL=$(herramienta util-linux sbin/blkid blkid) || exit 1 "$BL" "$P/verif-ext.img" 2>/dev/null | grep -q "LABEL=\"$ETIQUETA\"" \ && ok "el ext4 quedó etiquetado «$ETIQUETA»" || falla "el ext4 no tiene la etiqueta" rm -rf "$P/verif-ext.img" "$P/medio.img" "$P/esp.img" [ "$FALLOS" = 0 ] || exit 1 echo echo "✓ $IMG ($(( $(wc -c < "$IMG") / 1048576 )) MiB)" echo " sudo dd if=$IMG of=/dev/sdX bs=4M oflag=sync status=progress"