tawasuyu

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…

⎇ main scripts/instalador-usb.sh Shell · 177 lines · 10.9 KB History raw

full trae los escritorios: mirada, sway, COSMIC y KDE — 17 GiB 508ea9800 · Sergio ·

1#!/bin/sh
2# instalador-usb.sh — la imagen USB del instalador, para cuando el salto NO se puede.
3#
4# ══ POR QUÉ EXISTE ════════════════════════════════════════════════════════════════════════════
5# `takana metal install` reemplaza el Linux que corre sin USB, saltando con `kexec`. Eso pide tres
6# cosas del kernel de ESA máquina —`CONFIG_KEXEC_FILE`, ser root y que Secure Boot no esté en
7# lockdown— y cualquiera de las tres puede faltar. Cuando falta, `metal check` lo dice y manda acá.
8#
9# **El instalador es el mismo**: el mismo árbol, el mismo censo, las mismas guardas. Lo único que
10# cambia es cómo se llega a él.
11#
12# ── EL DISEÑO, Y LA MEDICIÓN QUE LO OBLIGA ────────────────────────────────────────────────────
13# La forma obvia sería poner el initramfs del instalador en la ESP y dejar que el EFI-stub lo
14# cargue. **No se puede, y está medido:** el cuelgue del primer viaje a metal fue exactamente eso,
15# un initrd de ~100 MiB cargado por el stub (ADR 0010 paso 2). El del instalador son ~122 MiB.
16#
17# Entonces el USB usa el MISMO patrón que un disco instalado, que sí está probado:
18#
19#   firmware → \EFI\BOOT\BOOTX64.EFI (= bzImage, EFI-stub)
20#            → initramfs de PIVOTE (~800 KiB, el que sí entra)
21#            → findfs LABEL=takana-medio → switch_root
22#            → /init del instalador, ya sobre un filesystem de verdad
23#
24# ⚠ La etiqueta es `takana-medio` y **no** `hammer-root` a propósito: si el medio se llamara igual
25# que la raíz de un sistema instalado, el pivote podría montar el disco del usuario en vez del USB
26# —o al revés— y el que pierde es el disco. Dos cosas distintas, dos nombres distintos.
27#
28# Uso:  scripts/instalador-usb.sh [--salida <img>]
29# Vars: ROOTFS KERNEL (como instalador-paquete.sh) · IMG · ESP_MB (def 96) · HOLGURA_MB (def 128)
30#
31# Grabarlo:  sudo dd if=work/takana-instalador-usb.img of=/dev/sdX bs=4M oflag=sync status=progress
32set -eu
33ROOT="$(cd "$(dirname "$0")/.." && pwd)"; cd "$ROOT"
34
35IMG="${IMG:-work/takana-instalador-usb.img}"
36ESP_MB="${ESP_MB:-96}"
37# ⚠ **La holgura no puede ser un número fijo.** `du` mide el contenido, no lo que el ext4 NECESITA:
38# inodos, journal, mapas de bloques. Con 128 MiB fijos alcanzaba para un árbol de 1 GiB y se quedó
39# corto con uno de 16 —`mke2fs` murió con «Could not allocate block» a mitad de poblar, después de
40# veinte minutos de trabajo—. Se escala: un 8 % del árbol, y nunca menos de 128 MiB.
41HOLGURA_MB="${HOLGURA_MB:-}"
42ETIQUETA="takana-medio"
43[ "${1:-}" = "--salida" ] && { IMG="${2:?}"; shift 2; }
44
45. "$(dirname "$0")/lib/herramientas-desde-store.sh"
46SFDISK=$(herramienta util-linux sbin/sfdisk sfdisk)   || exit 1
47MKE2FS=$(herramienta e2fsprogs  sbin/mke2fs mke2fs)   || exit 1
48MFORMAT=$(herramienta mtools usr/bin/mformat mformat) || exit 1
49MMD=$(herramienta     mtools usr/bin/mmd     mmd)     || exit 1
50MCOPY=$(herramienta   mtools usr/bin/mcopy   mcopy)   || exit 1
51MDIR=$(herramienta    mtools usr/bin/mdir    mdir)    || exit 1
52CPIO=$(herramienta    libarchive usr/bin/bsdcpio cpio) || exit 1
53GZIP=$(herramienta    gzip bin/gzip gzip)             || exit 1
54
55# ── 1) el árbol del instalador — el MISMO que viaja por kexec ─────────────────────────────────
56TREE="${IRD_TREE:-work/.instalador-initramfs-tree}"
57echo "==> árbol del instalador (TREE_ONLY: lo arma el generador de siempre)"
58TREE_ONLY=1 IRD_TREE="$TREE" ROOTFS="${ROOTFS:-}" KERNEL="${KERNEL:-}" ./scripts/instalador-initramfs.sh
59[ -x "$TREE/init" ] && [ -d "$TREE/payload/stage/root" ] || {
60    echo "el árbol no quedó completo en $TREE" >&2; exit 1; }
61KERNEL="${KERNEL:-$TREE/payload/bzImage}"
62[ -r "$KERNEL" ] || { echo "no encuentro el bzImage (set KERNEL=)" >&2; exit 1; }
63
64TREE_MB=$(du -sm "$TREE" | cut -f1)
65if [ -z "$HOLGURA_MB" ]; then
66    HOLGURA_MB=$(( TREE_MB / 12 ))
67    [ "$HOLGURA_MB" -lt 128 ] && HOLGURA_MB=128
68fi
69EXT_MB=$(( TREE_MB + HOLGURA_MB ))
70TOTAL_MB=$(( ESP_MB + EXT_MB + 2 ))
71echo "    árbol ${TREE_MB} MiB · ext4 ${EXT_MB} MiB · imagen ${TOTAL_MB} MiB"
72
73# ── 2) el initramfs de PIVOTE: lo único que carga el EFI-stub ─────────────────────────────────
74# Es hermano del pivote de `install-image-efi.sh` y comparte sus dos lecciones caras: el log va a
75# tty0 **y** al serial porque el cmdline horneado deja /dev/console en el serial, y la espera del
76# disco es LARGA porque el USB enumera de forma asíncrona y tarda segundos (en QEMU nunca se ve).
77P="work/.instalador-usb"; rm -rf "$P"; mkdir -p "$P/pivote/bin" "$P/pivote/dev" "$P/pivote/proc" \
78    "$P/pivote/sys" "$P/pivote/newroot"
79BUSYBOX="${BUSYBOX:-$TREE/bin/busybox}"
80[ -r "$BUSYBOX" ] || { echo "no encuentro busybox en $BUSYBOX" >&2; exit 1; }
81cp "$BUSYBOX" "$P/pivote/bin/busybox"; chmod 0755 "$P/pivote/bin/busybox"
82for a in sh mount umount findfs switch_root mkdir echo sleep ls cat setsid dmesg blkid; do
83    ln -sf busybox "$P/pivote/bin/$a"
84done
85cat > "$P/pivote/init" <<'PIVOTE'
86#!/bin/busybox sh
87# Pivote del MEDIO de instalación: encuentra el USB por etiqueta y le entrega el control al
88# instalador. No toca ningún disco: sólo monta el medio del que se arrancó.
89export PATH=/bin
90mount -t proc     proc /proc 2>/dev/null || true
91mount -t sysfs    sys  /sys  2>/dev/null || true
92mount -t devtmpfs dev  /dev  2>/dev/null || true
93# El cmdline horneado deja /dev/console en el SERIAL: sin escribir explícitamente en tty0, en un
94# portátil sin puerto serie todo esto es invisible y el arranque parece congelado.
95log() { for d in /dev/tty0 /dev/ttyS0; do echo "$*" > "$d" 2>/dev/null || true; done; }
96rescate() {
97    log "TAKANA-MEDIO-FALLO: $*"
98    log "   bloques visibles: $(ls /dev/sd* /dev/nvme* /dev/vd* /dev/mmcblk* 2>/dev/null | tr '\n' ' ')"
99    log "   shell de rescate en tty1 — probá: blkid ; dmesg | tail -30"
100    setsid -c sh -c 'exec /bin/sh </dev/tty1 >/dev/tty1 2>&1' >/dev/null 2>&1 &
101    exec sh
102}
103log "TAKANA-MEDIO: buscando el medio de instalación (LABEL=takana-medio)"
104# ⏱ 60 s y no 5: el bus USB enumera asíncrono —reset del hub, 1 s de settling por dispositivo, scan
105# SCSI— y tarda segundos. En QEMU el disco está desde el instante cero, así que esta carrera NO se
106# ve en validación: aparece la primera vez que alguien arranca de un pendrive de verdad.
107DEV=""; i=0
108while [ "$i" -lt 60 ]; do
109    DEV="$(findfs LABEL=takana-medio 2>/dev/null || true)"
110    [ -n "$DEV" ] && break
111    i=$(( i + 1 )); [ $(( i % 5 )) -eq 0 ] && log "TAKANA-MEDIO: esperando el medio… ${i}s (el USB tarda en enumerar)"
112    sleep 1
113done
114[ -n "$DEV" ] || rescate "no encuentro LABEL=takana-medio tras ${i}s"
115log "TAKANA-MEDIO: $DEV → montando"
116mount -t ext4 "$DEV" /newroot || rescate "no pude montar $DEV"
117[ -x /newroot/init ] || rescate "el medio no trae /init"
118log "TAKANA-MEDIO-OK: switch_root → el instalador"
119# El instalador tiene que SABER que vino de un medio: con eso su raíz viva es este USB —y no se
120# ofrece— mientras que por el salto la raíz viva es el sistema del usuario, que sí se reemplaza.
121# El entorno sobrevive al `exec` de switch_root, así que esto llega intacto al otro lado.
122export TAKANA_MEDIO=1
123exec switch_root /newroot /init
124PIVOTE
125chmod 0755 "$P/pivote/init"
126( cd "$P/pivote" && find . -print0 | "$CPIO" --null -o -H newc --owner=root:root 2>/dev/null | "$GZIP" -9 ) > "$P/initramfs.cpio.gz"
127echo "==> pivote: $(( $(wc -c < "$P/initramfs.cpio.gz") / 1024 )) KiB"
128
129# ── 3) la tabla ───────────────────────────────────────────────────────────────────────────────
130rm -f "$IMG"; truncate -s "${TOTAL_MB}M" "$IMG"
131ESP_SECT=$(( ESP_MB * 2048 ))
132"$SFDISK" --quiet "$IMG" <<EOF
133label: gpt
134start=2048, size=$ESP_SECT, type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B, name="EFI System Partition"
135start=$(( 2048 + ESP_SECT )), type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, name="takana-medio"
136EOF
137
138# ── 4) la ESP, poblada SIN montarla ───────────────────────────────────────────────────────────
139# Se formatea aparte y se empalma en su offset: formatear in-situ con `@@` hace que el FAT declare
140# el tamaño del fichero ENTERO, y después `df` repite la mentira.
141"$MFORMAT" -F -v TAKANA -i "$P/esp.img" -C -T $(( ESP_MB * 2048 )) ::
142"$MMD"   -i "$P/esp.img" ::/EFI ::/EFI/BOOT ::/EFI/takana
143# Las DOS rutas al MISMO bzImage: la de vendor y la de medios removibles (ADR 0018 §3). De un USB
144# manda la segunda, que es la que el firmware busca sin que nadie registre nada en la NVRAM.
145"$MCOPY" -i "$P/esp.img" "$KERNEL" ::/EFI/takana/takanax64.efi
146"$MCOPY" -i "$P/esp.img" "$KERNEL" ::/EFI/BOOT/BOOTX64.EFI
147# El nombre NO es libre: el cmdline horneado del kernel dice `initrd=/initramfs.cpio.gz`.
148"$MCOPY" -i "$P/esp.img" "$P/initramfs.cpio.gz" ::/initramfs.cpio.gz
149for f in ::/EFI/takana/takanax64.efi ::/EFI/BOOT/BOOTX64.EFI ::/initramfs.cpio.gz; do
150    "$MDIR" -i "$P/esp.img" "$f" >/dev/null 2>&1 < /dev/null || {
151        echo "la ESP no quedó con $f" >&2; exit 1; }
152done
153dd if="$P/esp.img" of="$IMG" bs=1M seek=1 conv=notrunc status=none
154
155# ── 5) el ext4 con el árbol adentro, sin montar nada ──────────────────────────────────────────
156echo "==> ext4 «$ETIQUETA» con el instalador adentro (${EXT_MB} MiB, sin montar)"
157"$MKE2FS" -q -t ext4 -L "$ETIQUETA" -d "$TREE" "$P/medio.img" "${EXT_MB}M"
158dd if="$P/medio.img" of="$IMG" bs=1M seek=$(( ESP_MB + 1 )) conv=notrunc status=none
159
160# ── 6) el control: que la imagen diga lo que creemos ──────────────────────────────────────────
161echo "==> control"
162FALLOS=0
163ok()   { echo "  ✓ $*"; }
164falla(){ echo "  ✗ $*" >&2; FALLOS=$(( FALLOS + 1 )); }
165"$SFDISK" -d "$IMG" | grep -q 'C12A7328' && ok "la tabla trae una ESP" || falla "sin ESP en la tabla"
166"$MDIR" -i "$IMG@@1048576" ::/EFI/BOOT >/dev/null 2>&1 < /dev/null \
167    && ok "la ESP se lee desde su offset en la imagen" || falla "la ESP no se lee"
168dd if="$IMG" bs=1M skip=$(( ESP_MB + 1 )) count="$EXT_MB" status=none > "$P/verif-ext.img"
169BL=$(herramienta util-linux sbin/blkid blkid) || exit 1
170"$BL" "$P/verif-ext.img" 2>/dev/null | grep -q "LABEL=\"$ETIQUETA\"" \
171    && ok "el ext4 quedó etiquetado «$ETIQUETA»" || falla "el ext4 no tiene la etiqueta"
172rm -rf "$P/verif-ext.img" "$P/medio.img" "$P/esp.img"
173[ "$FALLOS" = 0 ] || exit 1
174
175echo
176echo "✓ $IMG  ($(( $(wc -c < "$IMG") / 1048576 )) MiB)"
177echo "  sudo dd if=$IMG of=/dev/sdX bs=4M oflag=sync status=progress"