#!/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"