gioser

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…

TOML 1389Shell 137Markdown 80Rust 802602 Definiciones

⎇ 000a7c8144cce6fc561b41b9b1d4e4a8ae30d54b scripts/instalador-initramfs.sh Shell · 179 líneas · 11.7 KB Historia crudo

instalador en RAM: tres bugs que decían «instalado» sin haber instalado nada 000a7c814 · Sergio ·

1#!/bin/sh
2# instalador-initramfs.sh — empaqueta el instalador ENTERO en un initramfs que corre en RAM.
3#
4# ── PARA QUÉ ───────────────────────────────────────────────────────────────────────────────────
5# Es lo que hace posible reemplazar el Linux que está corriendo **sin un USB**: se salta a este
6# initramfs por `kexec` (`takana kernel kexec --jump`, ADR 0017), el sistema viejo muere en el salto
7# y su disco queda libre ⇒ recién entonces su raíz se puede formatear. El mismo initramfs sirve
8# arrancado por QEMU o por un medio cualquiera; el salto es sólo una forma de llegar.
9#
10# ── POR QUÉ EL PAYLOAD VIAJA ADENTRO Y NO EN EL DISCO ──────────────────────────────────────────
11# Porque el disco es justamente lo que se va a formatear. Dejar el rootfs en la partición destino y
12# formatearla después es morderse la cola; dejarlo en otra partición ata la instalación a que exista
13# esa otra partición. En RAM no depende de nada: el precio es que el initramfs pesa lo que pese el
14# rootfs, y por eso el script COMPRUEBA que entre en la memoria de la máquina antes de armarlo.
15#
16# Uso:
17#   ROOTFS=<product-rootfs> scripts/instalador-initramfs.sh --root-part /dev/nvme0n1p2 --esp /dev/nvme0n1p1
18#   # o sin destino fijo: lo decide el instalador al arrancar (--use-free-space)
19#
20# Salida: work/takana-instalador.cpio.gz  (+ el `takana kernel kexec` que lo salta, impreso al final)
21set -eu
22ROOT="$(cd "$(dirname "$0")/.." && pwd)"; cd "$ROOT"
23
24ARGS_INSTALADOR=""
25while [ $# -gt 0 ]; do ARGS_INSTALADOR="$ARGS_INSTALADOR $1"; shift; done
26[ -n "$ARGS_INSTALADOR" ] || { echo "uso: $0 <argumentos que se le pasarán a takana-install-partition.sh>" >&2; exit 2; }
27
28OUT="${OUT:-work/takana-instalador.cpio.gz}"
29STAGE="${STAGE:-work/.instalador-initramfs-stage}"
30IRD_TREE="${IRD_TREE:-work/.instalador-initramfs-tree}"
31KERNEL="${KERNEL:-$(ls -dt store/*-linux-metal/boot/bzImage /store/*-linux-metal/boot/bzImage 2>/dev/null | head -1)}"
32[ -n "${KERNEL:-}" ] && [ -r "$KERNEL" ] || { echo "no encuentro un bzImage de linux-metal (set KERNEL=)" >&2; exit 1; }
33
34. "$(dirname "$0")/lib/herramientas-desde-store.sh"
35MKE2FS=$(herramienta e2fsprogs sbin/mke2fs mke2fs)  || exit 1
36SFDISK=$(herramienta util-linux sbin/sfdisk sfdisk) || exit 1
37MMD=$(herramienta    mtools usr/bin/mmd   mmd)      || exit 1
38MCOPY=$(herramienta  mtools usr/bin/mcopy mcopy)    || exit 1
39MDIR=$(herramienta   mtools usr/bin/mdir  mdir)     || exit 1
40CPIO=$(herramienta   libarchive usr/bin/bsdcpio cpio) || exit 1
41GZIP=$(herramienta   gzip bin/gzip gzip)            || exit 1
42
43# ── 1) el stage (rootfs a instalar + initramfs de pivote) lo arma el script validado ───────────
44echo "==> stage del sistema a instalar"
45STAGE_ONLY=1 KEEP_STORE=1 STAGE="$STAGE" ./scripts/install-image-efi.sh
46[ -d "$STAGE/root" ] && [ -r "$STAGE/initramfs.cpio.gz" ] || { echo "el stage no quedó completo" >&2; exit 1; }
47
48# ── 2) ¿entra en la RAM de esta máquina? ───────────────────────────────────────────────────────
49# Un initramfs vive ENTERO en memoria, y el kernel no lo avisa con elegancia: se queda sin RAM al
50# desempaquetar y el arranque muere sin un mensaje que diga por qué. Medirlo antes es la diferencia
51# entre «no entra, usá el USB» y un cuelgue mudo en la máquina del usuario.
52_mib_payload=$(du -sm "$STAGE/root" | cut -f1)
53_mib_ram=$(( $(awk '/^MemTotal:/ {print $2}' /proc/meminfo) / 1024 ))
54echo "    payload ${_mib_payload} MiB · RAM de esta máquina ${_mib_ram} MiB"
55if [ "$(( _mib_payload * 100 / _mib_ram ))" -gt 40 ]; then
56    echo "instalador-initramfs: el payload es más del 40 % de la RAM ($_mib_payload de $_mib_ram MiB)." >&2
57    echo "                      En RAM van el initramfs comprimido Y su contenido desempaquetado." >&2
58    echo "                      Este camino no sirve para este rootfs: instalá desde un medio." >&2
59    exit 1
60fi
61
62# ── 3) el árbol del initramfs ──────────────────────────────────────────────────────────────────
63echo "==> árbol del initramfs-instalador"
64rm -rf "$IRD_TREE"; mkdir -p "$IRD_TREE/bin" "$IRD_TREE/dev" "$IRD_TREE/proc" "$IRD_TREE/sys" \
65    "$IRD_TREE/instalador/lib" "$IRD_TREE/payload"
66BUSYBOX="${BUSYBOX:-$STAGE/root/bin/busybox}"
67[ -r "$BUSYBOX" ] || { echo "no encuentro busybox estático en $BUSYBOX" >&2; exit 1; }
68cp "$BUSYBOX" "$IRD_TREE/bin/busybox"; chmod 0755 "$IRD_TREE/bin/busybox"
69# ⚠ `id` estaba fuera de esta lista y el instalador eligió la rama EQUIVOCADA por eso: sin `id -u`
70# concluyó «no soy root» y se fue a `unshare`, que tampoco existía. Los applets que el instalador usa
71# se enumeran acá y su ausencia ya no es silenciosa (el script aborta si le falta `id`).
72for a in sh mount umount mkdir echo sleep ls cat sed awk grep cut tr basename dirname readlink \
73         find sync reboot dmesg blkid setsid tee du wc head tail sort printf test sha256sum \
74         id sh chmod rm cp mv stat; do
75    ln -sf busybox "$IRD_TREE/bin/$a"
76done
77# Las herramientas de disco NO salen de busybox: son las SELLADAS del corpus, estáticas musl. Dentro
78# del initramfs no hay store del que resolverlas por hash, así que viajan copiadas y el instalador
79# las recibe por `TOOL_<binario>` — el override que existe exactamente para esto.
80for t in "$MKE2FS" "$SFDISK" "$MMD" "$MCOPY" "$MDIR"; do
81    cp "$t" "$IRD_TREE/bin/$(basename "$t")"; chmod 0755 "$IRD_TREE/bin/$(basename "$t")"
82done
83TAKANA_MUSL="${TAKANA_MUSL:-$ROOT/target/x86_64-unknown-linux-musl/release/takana}"
84[ -x "$TAKANA_MUSL" ] || TAKANA_MUSL="$ROOT/target/release/takana"
85cp "$TAKANA_MUSL" "$IRD_TREE/bin/takana"; chmod 0755 "$IRD_TREE/bin/takana"
86cp scripts/takana-install-partition.sh "$IRD_TREE/instalador/"
87cp scripts/lib/herramientas-desde-store.sh "$IRD_TREE/instalador/lib/"
88cp "$KERNEL" "$IRD_TREE/payload/bzImage"
89mkdir -p "$IRD_TREE/payload/stage"
90cp -a "$STAGE/root" "$IRD_TREE/payload/stage/root"
91cp "$STAGE/initramfs.cpio.gz" "$IRD_TREE/payload/stage/initramfs.cpio.gz"
92printf '%s\n' "$ARGS_INSTALADOR" > "$IRD_TREE/instalador/args"
93
94# ── 4) /init — lo que corre cuando el kernel nuevo toma el control ─────────────────────────────
95cat > "$IRD_TREE/init" <<'INIT'
96#!/bin/busybox sh
97# PID 1 del instalador. No pivota a ningún disco: instala y reinicia.
98export PATH=/bin
99mount -t proc     proc /proc 2>/dev/null || true
100mount -t sysfs    sys  /sys  2>/dev/null || true
101mount -t devtmpfs dev  /dev  2>/dev/null || true
102# ⚠ El log va a tty0 **y** al serial, por lo mismo que el initramfs de pivote: el cmdline horneado
103# deja /dev/console en ttyS0 y en un metal sin puerto serie todo esto sería invisible.
104# El log va SÓLO a los devices: el stdout de PID 1 ya es /dev/console (= ttyS0 por el cmdline
105# horneado), así que un `echo` suelto duplicaba cada línea en el serial.
106log() { for d in /dev/tty0 /dev/ttyS0; do echo "$*" > "$d" 2>/dev/null || true; done; }
107rescate() {
108    log "TAKANA-INSTALADOR-FALLO: $*"
109    log "  bloques visibles: $(ls /dev/sd* /dev/nvme* /dev/vd* /dev/mmcblk* 2>/dev/null | tr '\n' ' ')"
110    log "  shell de rescate en tty1 — probá: blkid ; dmesg | tail -30"
111    setsid -c sh -c 'exec /bin/sh </dev/tty1 >/dev/tty1 2>&1' >/dev/null 2>&1 &
112    exec sh
113}
114# efivarfs: sin esto el instalador concluye «esta máquina no arrancó por UEFI» y NO registra la
115# entrada de arranque — en una máquina que sí arrancó por UEFI. Es el mismo diagnóstico falso que ya
116# costó una vuelta en el `/sbin/init` del rootfs: `switch_root` y un initramfs fresco no arrastran
117# /sys, y sin sysfs el directorio no existe. Tras un kexec los servicios EFI de runtime siguen ahí,
118# así que acá esto es lo único que falta.
119if [ -d /sys/firmware/efi ]; then
120    mkdir -p /sys/firmware/efi/efivars 2>/dev/null || true
121    mount -t efivarfs efivarfs /sys/firmware/efi/efivars 2>/dev/null || true
122fi
123log "TAKANA-INSTALADOR: arrancado en RAM. El sistema viejo YA NO CORRE: su disco está libre."
124[ -d /sys/firmware/efi/efivars ] && log "TAKANA-INSTALADOR: firmware UEFI visible (efivarfs montado)" \
125    || log "TAKANA-INSTALADOR: sin efivarfs ⇒ no se registrará entrada NVRAM (BIOS, o kernel sin EFI)"
126
127# Los discos tardan en enumerar (USB y NVMe sobre todo). Misma espera larga que el pivote, y por el
128# mismo motivo: en QEMU aparecen al instante y la carrera sólo se ve en metal.
129# ⚠ Esto era `ls /dev/nvme* /dev/sd* /dev/vd* … && break` y **esperaba los 60 s completos incluso
130# con el disco presente**: `ls` sale ≠ 0 si CUALQUIERA de sus argumentos no existe, y los globs sin
131# coincidencia quedan literales. O sea que bastaba con no tener NVMe para que un disco virtio ya
132# enumerado no contara. Medido en QEMU: 60 s de «esperando discos» con /dev/vda ahí desde el
133# segundo uno. En metal eso se lee como un arranque colgado.
134i=0
135while [ "$i" -lt 60 ]; do
136    hay=""
137    for d in /dev/vd? /dev/sd? /dev/nvme?n? /dev/mmcblk?; do [ -b "$d" ] && hay=1; done
138    [ -n "$hay" ] && break
139    i=$(( i + 1 )); [ $(( i % 5 )) -eq 0 ] && log "TAKANA-INSTALADOR: esperando discos… ${i}s"
140    sleep 1
141done
142
143cd /instalador
144ARGS=$(cat /instalador/args)
145log "TAKANA-INSTALADOR: takana-install-partition.sh $ARGS"
146# Las herramientas viajan en /bin: el instalador las toma por TOOL_<binario> en vez de buscarlas en
147# un store que acá no existe. STAGE_LISTO dice que el árbol ya vino armado.
148export TOOL_mke2fs=/bin/mke2fs TOOL_sfdisk=/bin/sfdisk TOOL_mmd=/bin/mmd \
149       TOOL_mcopy=/bin/mcopy TOOL_mdir=/bin/mdir
150export STAGE=/payload/stage STAGE_LISTO=1 KERNEL=/payload/bzImage HAMMER=/bin/takana
151# ⚠ **El estado de un PIPE es el del ÚLTIMO comando, no el del primero.** Esto era
152# `if sh instalador… | tee …; then OK else rescate fi` y el `if` evaluaba el `tee`, que siempre sale
153# 0 ⇒ **el instalador falló, no formateó NADA, y esta pantalla dijo «instalado»**. Medido en QEMU el
154# 2026-09-23, y es el modo de fallo más caro posible: el usuario reinicia confiando y se queda sin
155# sistema. El estado del instalador se guarda aparte y se lee después; el pipe ya no decide nada.
156# shellcheck disable=SC2086
157{ sh /instalador/takana-install-partition.sh $ARGS --yes; echo $? > /rc; } 2>&1 | tee /dev/tty0 > /dev/ttyS0
158if [ "$(cat /rc 2>/dev/null || echo 1)" = "0" ]; then
159    log "TAKANA-INSTALADOR-OK: instalado. Reiniciando en 10 s (sacá el medio si lo hubiera)."
160    sync; sleep 10; reboot -f
161else
162    rescate "el instalador salió con error — NADA se reinicia solo, mirá el log de arriba"
163fi
164INIT
165chmod +x "$IRD_TREE/init"
166
167# ── 5) empaquetar ──────────────────────────────────────────────────────────────────────────────
168mkdir -p "$(dirname "$OUT")"
169( cd "$IRD_TREE" && find . -print0 | "$CPIO" --null -o -H newc --owner=root:root 2>/dev/null | "$GZIP" -1 ) > "$OUT"
170echo "==> $OUT: $(du -h "$OUT" | cut -f1)"
171echo
172echo "Para SALTAR a él sin pasar por el firmware (el userspace de esta máquina muere ACÁ):"
173echo "  sudo takana kernel kexec --probe          # ¿se puede? exit 0 = sí"
174echo "  sudo takana kernel kexec <linux-metal> --initrd $OUT \\"
175echo "       --cmdline 'console=tty0 console=ttyS0,115200' --jump"
176echo
177echo "Para probarlo SIN saltar (y sin riesgo), en QEMU:"
178echo "  qemu-system-x86_64 -m 4096 -kernel $KERNEL -initrd $OUT \\"
179echo "       -append 'console=ttyS0' -drive file=<disco.img>,format=raw -nographic"