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 188Rust 55Shell 30Markdown 281211 Sutichaykuna

⎇ 534014c3ec69daea789a1d0523e1d00a69c6ed65 scripts/instalador-initramfs.sh Shell · 349 siq'ikuna · 23.7 KB Ñawpa willay ch'uya

fix(instalador): la cuenta de quien va a usar la maquina se pregunta antes de instalar 0f66c9973 · 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
24# Los argumentos son OPCIONALES a propósito. Un initramfs con el destino horneado sirve para UNA
25# máquina y hay que rehacerlo para la siguiente — eso no es un instalador, es un apaño. Sin
26# argumentos, el mismo fichero arranca en cualquier máquina y decide allí: lee la cmdline del kernel
27# (`takana.root=`, `takana.esp=`, `takana.free`, `takana.adopt`) y, si no hay nada, muestra el censo y
28# pregunta. Lo que se pase acá queda sólo como VALOR POR DEFECTO.
29ARGS_INSTALADOR=""
30while [ $# -gt 0 ]; do ARGS_INSTALADOR="$ARGS_INSTALADOR $1"; shift; done
31
32OUT="${OUT:-work/takana-instalador.cpio.gz}"
33STAGE="${STAGE:-work/.instalador-initramfs-stage}"
34IRD_TREE="${IRD_TREE:-work/.instalador-initramfs-tree}"
35KERNEL="${KERNEL:-$(ls -dt store/*-linux-metal/boot/bzImage /store/*-linux-metal/boot/bzImage 2>/dev/null | head -1)}"
36[ -n "${KERNEL:-}" ] && [ -r "$KERNEL" ] || { echo "no encuentro un bzImage de linux-metal (set KERNEL=)" >&2; exit 1; }
37
38. "$(dirname "$0")/lib/herramientas-desde-store.sh"
39MKE2FS=$(herramienta e2fsprogs sbin/mke2fs mke2fs)  || exit 1
40SFDISK=$(herramienta util-linux sbin/sfdisk sfdisk) || exit 1
41MMD=$(herramienta    mtools usr/bin/mmd   mmd)      || exit 1
42MCOPY=$(herramienta  mtools usr/bin/mcopy mcopy)    || exit 1
43MDIR=$(herramienta   mtools usr/bin/mdir  mdir)     || exit 1
44CPIO=$(herramienta   libarchive usr/bin/bsdcpio cpio) || exit 1
45GZIP=$(herramienta   gzip bin/gzip gzip)            || exit 1
46
47# ── 1) el stage (rootfs a instalar + initramfs de pivote) lo arma el script validado ───────────
48echo "==> stage del sistema a instalar"
49STAGE_ONLY=1 KEEP_STORE=1 STAGE="$STAGE" ./scripts/install-image-efi.sh
50[ -d "$STAGE/root" ] && [ -r "$STAGE/initramfs.cpio.gz" ] || { echo "el stage no quedó completo" >&2; exit 1; }
51
52# ── 2) ¿entra en la RAM de esta máquina? ───────────────────────────────────────────────────────
53# Un initramfs vive ENTERO en memoria, y el kernel no lo avisa con elegancia: se queda sin RAM al
54# desempaquetar y el arranque muere sin un mensaje que diga por qué. Medirlo antes es la diferencia
55# entre «no entra, usá el USB» y un cuelgue mudo en la máquina del usuario.
56# ⚠ **Esta guarda es del camino en RAM, y sólo de ése.** Con `TREE_ONLY=1` el árbol se va a
57# escribir en el ext4 de un USB, donde no hay ningún límite de memoria: aplicarla ahí rechazaba la
58# variante `full` por no caber en la RAM de una máquina que no es la que va a instalar. Medido el
59# 2026-09-23 armando las tres variantes.
60#
61# ⚠ Y hay un segundo defecto, que se deja anotado y no barrido: mide la RAM de **esta** máquina, no
62# la de aquella donde se va a instalar. Acá acierta por casualidad —el hub tiene 7,6 GiB, parecido a
63# un portátil—, pero construir en una caja grande dejaría pasar una imagen que allá no arranca.
64if [ -n "${TREE_ONLY:-}" ]; then
65    echo "    (TREE_ONLY: el árbol va a un disco, no a RAM — la guarda de memoria no aplica)"
66else
67_mib_payload=$(du -sm "$STAGE/root" | cut -f1)
68_mib_ram=$(( $(awk '/^MemTotal:/ {print $2}' /proc/meminfo) / 1024 ))
69echo "    payload ${_mib_payload} MiB · RAM de esta máquina ${_mib_ram} MiB"
70if [ "$(( _mib_payload * 100 / _mib_ram ))" -gt 40 ]; then
71    echo "instalador-initramfs: el payload es más del 40 % de la RAM ($_mib_payload de $_mib_ram MiB)." >&2
72    echo "                      En RAM van el initramfs comprimido Y su contenido desempaquetado." >&2
73    echo "                      Este camino no sirve para este rootfs: instalá desde un medio." >&2
74    exit 1
75fi
76fi
77
78# ── 3) el árbol del initramfs ──────────────────────────────────────────────────────────────────
79echo "==> árbol del initramfs-instalador"
80# `/tmp` y `/run` NO son decorativos: el censo del terreno escribe ahí su fichero de trabajo, y sin
81# ellos el instalador fallaba diciendo «no veo ningún disco» con once discos delante.
82rm -rf "$IRD_TREE"; mkdir -p "$IRD_TREE/bin" "$IRD_TREE/dev" "$IRD_TREE/proc" "$IRD_TREE/sys" \
83    "$IRD_TREE/tmp" "$IRD_TREE/run" "$IRD_TREE/instalador/lib" "$IRD_TREE/payload"
84BUSYBOX="${BUSYBOX:-$STAGE/root/bin/busybox}"
85[ -r "$BUSYBOX" ] || { echo "no encuentro busybox estático en $BUSYBOX" >&2; exit 1; }
86cp "$BUSYBOX" "$IRD_TREE/bin/busybox"; chmod 0755 "$IRD_TREE/bin/busybox"
87# ⚠ `id` estaba fuera de esta lista y el instalador eligió la rama EQUIVOCADA por eso: sin `id -u`
88# concluyó «no soy root» y se fue a `unshare`, que tampoco existía. Los applets que el instalador usa
89# se enumeran acá y su ausencia ya no es silenciosa (el script aborta si le falta `id`).
90for a in sh mount umount mkdir echo sleep ls cat sed awk grep cut tr basename dirname readlink \
91         find sync reboot dmesg blkid setsid tee du wc head tail sort printf test sha256sum \
92         id sh chmod rm cp mv stat findfs; do
93    ln -sf busybox "$IRD_TREE/bin/$a"
94done
95# Las herramientas de disco NO salen de busybox: son las SELLADAS del corpus, estáticas musl. Dentro
96# del initramfs no hay store del que resolverlas por hash, así que viajan copiadas y el instalador
97# las recibe por `TOOL_<binario>` — el override que existe exactamente para esto.
98for t in "$MKE2FS" "$SFDISK" "$MMD" "$MCOPY" "$MDIR"; do
99    cp "$t" "$IRD_TREE/bin/$(basename "$t")"; chmod 0755 "$IRD_TREE/bin/$(basename "$t")"
100done
101TAKANA_MUSL="${TAKANA_MUSL:-$ROOT/target/x86_64-unknown-linux-musl/release/takana}"
102[ -x "$TAKANA_MUSL" ] || TAKANA_MUSL="$ROOT/target/release/takana"
103cp "$TAKANA_MUSL" "$IRD_TREE/bin/takana"; chmod 0755 "$IRD_TREE/bin/takana"
104cp scripts/takana-install-partition.sh "$IRD_TREE/instalador/"
105# ── LAS LIBRERÍAS QUE EL INSTALADOR SOURCEA, DERIVADAS DEL PROPIO SCRIPT ──────────────────────────
106# Acá había UNA copia a mano (`herramientas-desde-store.sh`) y el script sourcea CUATRO:
107# `usuarios-del-home.sh`, `adoptar.sh`, `cuenta.sh` y ésa. Una librería que no viaja no da un error
108# claro: el `.` falla y el instalador sigue hasta que llama a la función, ya con el disco en la mano.
109# Derivarlas del script es lo que hace que agregar la quinta no exija acordarse de este sitio — la
110# lista a mano ya había quedado corta tres veces.
111for _lib in $(sed -n 's|^\. "$(dirname "$0")/lib/\([a-z-]*\.sh\)".*|\1|p' scripts/takana-install-partition.sh); do
112	[ -r "scripts/lib/$_lib" ] || { echo "falta scripts/lib/$_lib, que el instalador sourcea" >&2; exit 1; }
113	cp "scripts/lib/$_lib" "$IRD_TREE/instalador/lib/"
114done
115echo "   libs del instalador: $(sed -n 's|^\. "$(dirname "$0")/lib/\([a-z-]*\.sh\)".*|\1|p' scripts/takana-install-partition.sh | tr '\n' ' ')"
116cp "$KERNEL" "$IRD_TREE/payload/bzImage"
117mkdir -p "$IRD_TREE/payload/stage"
118cp -a "$STAGE/root" "$IRD_TREE/payload/stage/root"
119
120# ── SIN SÍMBOLOS DE DEPURACIÓN ────────────────────────────────────────────────────────────────
121# ⚠ **Casi todo el peso de lo que se publica son símbolos, no funciones.** Medido: `ssh` pesa
122# 19,3 MiB y 5,3 stripeado —el 72 %—, y un rootfs de escritorio entero baja de 1348 a 891 MiB. Con
123# binarios estáticos cada uno lleva su propia copia de todo, así que el multiplicador es grande.
124#
125# ⚠ **Y esto NO contradice al SDD 23**, que dice —con razón— que `strip` posterior no sirve: eso
126# vale para el STORE, donde el `ArtifactHash` es de ENTRADA y quedaría apuntando a un contenido que
127# un rebuild ya no reproduce. Acá se stripea una COPIA que no es un artefacto: no se sella, nadie
128# verifica su hash contra una receta, y el store conserva los símbolos intactos para quien depure.
129# `strip` es determinista, así que la imagen sigue siendo reproducible.
130#
131# `SIN_STRIP=1` lo apaga (para depurar la imagen misma, que es el único caso donde estorba).
132if [ -z "${SIN_STRIP:-}" ]; then
133    STRIP=$(herramienta binutils usr/bin/strip strip) || exit 1
134    _sr="$IRD_TREE/payload/stage/root"
135    # ⚠ GUARDA: si la copia compartiera inodo con el store, stripear MUTARÍA EL STORE — y con él
136    # todos los artefactos que enlazan a ese fichero, en silencio y para siempre. `cp -a` copia
137    # contenido y no enlaza al origen, pero eso es una propiedad del comando de arriba y no de este
138    # bloque: si alguien lo cambia a `cp -al` por rapidez, esto tiene que gritar antes de tocar nada.
139    _muestra=$(find "$_sr" -type f -perm /111 -size +64k -print -quit 2>/dev/null)
140    if [ -n "$_muestra" ] && [ "$(stat -c %h "$_muestra" 2>/dev/null || echo 1)" -gt 1 ]; then
141        _ino=$(stat -c %i "$_muestra")
142        if find "$STAGE/root" -inum "$_ino" -print -quit 2>/dev/null | grep -q .; then
143            echo "instalador-initramfs: el árbol a stripear COMPARTE INODOS con el stage." >&2
144            echo "                      Stripear mutaría el store. Abortado sin tocar nada." >&2
145            exit 1
146        fi
147    fi
148    _antes=$(du -sm "$_sr" | cut -f1)
149    # Ejecutables y bibliotecas: un `.so` puede no tener el bit de ejecución y pesa lo mismo.
150    find "$_sr" -type f \( -perm /111 -o -name '*.so' -o -name '*.so.*' \) \
151        -exec "$STRIP" --strip-unneeded {} + 2>/dev/null || true
152    _despues=$(du -sm "$_sr" | cut -f1)
153    _pct=0; [ "${_antes:-0}" -gt 0 ] && _pct=$(( (_antes - _despues) * 100 / _antes ))
154    echo "==> sin símbolos de depuración: ${_antes} → ${_despues} MiB (${_pct} % menos)"
155    echo "    (el store los conserva: lo que se stripea es esta copia, que no es un artefacto)"
156fi
157cp "$STAGE/initramfs.cpio.gz" "$IRD_TREE/payload/stage/initramfs.cpio.gz"
158printf '%s\n' "$ARGS_INSTALADOR" > "$IRD_TREE/instalador/args"
159
160# ── 4) /init — lo que corre cuando el kernel nuevo toma el control ─────────────────────────────
161cat > "$IRD_TREE/init" <<'INIT'
162#!/bin/busybox sh
163# PID 1 del instalador. No pivota a ningún disco: instala y reinicia.
164export PATH=/bin
165mount -t proc     proc /proc 2>/dev/null || true
166mount -t sysfs    sys  /sys  2>/dev/null || true
167mount -t devtmpfs dev  /dev  2>/dev/null || true
168# ⚠ El log va a tty0 **y** al serial, por lo mismo que el initramfs de pivote: el cmdline horneado
169# deja /dev/console en ttyS0 y en un metal sin puerto serie todo esto sería invisible.
170# El log va SÓLO a los devices: el stdout de PID 1 ya es /dev/console (= ttyS0 por el cmdline
171# horneado), así que un `echo` suelto duplicaba cada línea en el serial.
172log() { for d in /dev/tty0 /dev/ttyS0; do echo "$*" > "$d" 2>/dev/null || true; done; }
173rescate() {
174    log "TAKANA-INSTALADOR-FALLO: $*"
175    log "  bloques visibles: $(ls /dev/sd* /dev/nvme* /dev/vd* /dev/mmcblk* 2>/dev/null | tr '\n' ' ')"
176    log "  shell de rescate en tty1 — probá: blkid ; dmesg | tail -30"
177    setsid -c sh -c 'exec /bin/sh </dev/tty1 >/dev/tty1 2>&1' >/dev/null 2>&1 &
178    exec sh
179}
180# efivarfs: sin esto el instalador concluye «esta máquina no arrancó por UEFI» y NO registra la
181# entrada de arranque — en una máquina que sí arrancó por UEFI. Es el mismo diagnóstico falso que ya
182# costó una vuelta en el `/sbin/init` del rootfs: `switch_root` y un initramfs fresco no arrastran
183# /sys, y sin sysfs el directorio no existe. Tras un kexec los servicios EFI de runtime siguen ahí,
184# así que acá esto es lo único que falta.
185if [ -d /sys/firmware/efi ]; then
186    mkdir -p /sys/firmware/efi/efivars 2>/dev/null || true
187    mount -t efivarfs efivarfs /sys/firmware/efi/efivars 2>/dev/null || true
188fi
189# ── ¿POR DÓNDE SE LLEGÓ? ──────────────────────────────────────────────────────────────────────
190# Lo dice el pivote del medio, que exporta `TAKANA_MEDIO=1` antes de su `switch_root` — y el
191# entorno sobrevive al exec. No es un detalle: la diferencia cambia QUÉ ES LA RAÍZ VIVA (por el
192# salto, el sistema del usuario; por el medio, el propio USB) y con eso qué se ofrece formatear.
193#
194# ⚠ **La primera versión miraba `rdinit=` en /proc/cmdline y estaba MAL.** Con `CONFIG_CMDLINE_BOOL`
195# y sin `CONFIG_CMDLINE_OVERRIDE`, el cmdline horneado del kernel se CONCATENA con el que pasa quien
196# lo arranca: `rdinit=` aparece SIEMPRE, también tras un kexec, así que el instalador se creía en un
197# medio estando en el salto. Medido el 2026-09-23 — y el síntoma fue benigno de casualidad (un
198# mensaje raro), pero la consecuencia real era que la raíz viva del usuario habría quedado marcada
199# «es el MEDIO desde el que arrancaste» y NO SE HABRÍA PODIDO ELEGIR, que es justo el caso que el
200# salto existe para resolver.
201MEDIO="${TAKANA_MEDIO:-}"
202if [ -n "$MEDIO" ]; then
203    log "TAKANA-INSTALADOR: arrancado desde el MEDIO de instalación. Ningún disco de esta máquina está en uso."
204else
205    log "TAKANA-INSTALADOR: arrancado en RAM. El sistema viejo YA NO CORRE: su disco está libre."
206fi
207[ -d /sys/firmware/efi/efivars ] && log "TAKANA-INSTALADOR: firmware UEFI visible (efivarfs montado)" \
208    || log "TAKANA-INSTALADOR: sin efivarfs ⇒ no se registrará entrada NVRAM (BIOS, o kernel sin EFI)"
209
210# Los discos tardan en enumerar (USB y NVMe sobre todo). Misma espera larga que el pivote, y por el
211# mismo motivo: en QEMU aparecen al instante y la carrera sólo se ve en metal.
212# ⚠ Esto era `ls /dev/nvme* /dev/sd* /dev/vd* … && break` y **esperaba los 60 s completos incluso
213# con el disco presente**: `ls` sale ≠ 0 si CUALQUIERA de sus argumentos no existe, y los globs sin
214# coincidencia quedan literales. O sea que bastaba con no tener NVMe para que un disco virtio ya
215# enumerado no contara. Medido en QEMU: 60 s de «esperando discos» con /dev/vda ahí desde el
216# segundo uno. En metal eso se lee como un arranque colgado.
217i=0
218while [ "$i" -lt 60 ]; do
219    hay=""
220    for d in /dev/vd? /dev/sd? /dev/nvme?n? /dev/mmcblk?; do [ -b "$d" ] && hay=1; done
221    [ -n "$hay" ] && break
222    i=$(( i + 1 )); [ $(( i % 5 )) -eq 0 ] && log "TAKANA-INSTALADOR: esperando discos… ${i}s"
223    sleep 1
224done
225
226cd /instalador
227# ── De dónde sale QUÉ instalar, en orden de precedencia ───────────────────────────────────────
228#   1. la CMDLINE del kernel  (`takana.root=PARTUUID=… takana.esp=… takana.free takana.yes`)
229#   2. los valores por defecto horneados al armar el initramfs, si los hubo
230#   3. NADA ⇒ modo interactivo: el censo del terreno y una pregunta
231# El 1 es lo que hace que este mismo initramfs sirva en cualquier máquina: quien salta por kexec
232# pasa la elección por la cmdline, y quien arranca de un USB la edita en el menú del loader.
233ARGS=""; YES=""; CONSOLA_PEDIDA=""
234for _w in $(cat /proc/cmdline 2>/dev/null); do
235    case "$_w" in
236        takana.root=*) ARGS="$ARGS --root-part ${_w#takana.root=}" ;;
237        takana.esp=*)  ARGS="$ARGS --esp ${_w#takana.esp=}" ;;
238        takana.disk=*) ARGS="$ARGS --disk ${_w#takana.disk=}" ;;
239        takana.free)   ARGS="$ARGS --use-free-space" ;;
240        takana.keep=*) ARGS="$ARGS --keep ${_w#takana.keep=}" ;;
241        takana.adopt)  ARGS="$ARGS --adopt" ;;
242        takana.yes)    YES=1 ;;
243        takana.console=*) CONSOLA_PEDIDA="${_w#takana.console=}" ;;
244    esac
245done
246# ── DÓNDE SE PREGUNTA ─────────────────────────────────────────────────────────────────────────
247# ⚠ `/dev/console` es la ÚLTIMA `console=` del cmdline, y **el cmdline HORNEADO del kernel pone
248# `tty0` PRIMERO y `ttyS0` último** ⇒ arrancado de un USB, `/dev/console` es el SERIAL. En un
249# portátil sin puerto serie eso significa que el censo y la pregunta salen por un sitio que no
250# existe: pantalla muerta y un instalador que parece colgado esperando una tecla que nadie puede
251# escribir. Por el salto el cmdline lo ponemos nosotros y `tty0` va último, pero por USB no se
252# puede elegir sin recompilar el kernel.
253#
254# Regla: si hay una VT de verdad, se pregunta en la PANTALLA. Y se dice en voz alta por los dos
255# lados, porque el caso contrario —una máquina sin monitor, administrada por serie— existe y tiene
256# que poder corregirlo sin adivinar: `takana.console=/dev/ttyS0` en la cmdline.
257# ⚠ Y no alcanza con que `tty0` EXISTA: con `console=tty0` en el cmdline el kernel lo registra
258# igual en una máquina sin pantalla, con el driver `dummy` detrás — o sea un dispositivo que acepta
259# todo lo que le escribas y no lo muestra en ningún lado. La pregunta se iría a un agujero. Lo que
260# distingue una pantalla de verdad es el vtconsole: `dummy device` contra `frame buffer device` o
261# `VGA+`. Medido en QEMU, que da las dos según se le pida `-vga std` o `-vga none`.
262hay_pantalla() {
263    for _v in /sys/class/vtconsole/vtcon*/name; do
264        [ -r "$_v" ] || continue
265        grep -qi 'dummy' "$_v" || return 0
266    done
267    return 1
268}
269CONSOLA=/dev/console
270if [ -n "$CONSOLA_PEDIDA" ]; then
271    CONSOLA="$CONSOLA_PEDIDA"
272elif [ -c /dev/tty0 ] && hay_pantalla; then
273    CONSOLA=/dev/tty0
274fi
275[ -z "$ARGS" ] && [ -s /instalador/args ] && ARGS=$(cat /instalador/args)
276log "TAKANA-INSTALADOR: takana-install-partition.sh ${ARGS:-(sin parámetros ⇒ interactivo)}"
277# Las herramientas viajan en /bin: el instalador las toma por TOOL_<binario> en vez de buscarlas en
278# un store que acá no existe. STAGE_LISTO dice que el árbol ya vino armado.
279export TOOL_mke2fs=/bin/mke2fs TOOL_sfdisk=/bin/sfdisk TOOL_mmd=/bin/mmd \
280       TOOL_mcopy=/bin/mcopy TOOL_mdir=/bin/mdir
281export STAGE=/payload/stage STAGE_LISTO=1 KERNEL=/payload/bzImage HAMMER=/bin/takana
282# El instalador necesita saberlo para clasificar bien la raíz viva: por el salto es el sistema del
283# usuario (se puede reemplazar); por el medio es el propio USB (no se toca nunca).
284[ -n "$MEDIO" ] && export TAKANA_MEDIO=1
285# ⚠ **El estado de un PIPE es el del ÚLTIMO comando, no el del primero.** Esto era
286# `if sh instalador… | tee …; then OK else rescate fi` y el `if` evaluaba el `tee`, que siempre sale
287# 0 ⇒ **el instalador falló, no formateó NADA, y esta pantalla dijo «instalado»**. Medido en QEMU el
288# 2026-09-23, y es el modo de fallo más caro posible: el usuario reinicia confiando y se queda sin
289# sistema. El estado del instalador se guarda aparte y se lee después; el pipe ya no decide nada.
290# shellcheck disable=SC2086
291# `< /dev/null`: nada de esto debe leer del teclado —las confirmaciones ya van por `--yes`— y si
292# algo lo intenta tiene que FALLAR, no quedarse esperando en una consola que nadie está mirando.
293# Desatendido SÓLO si la cmdline lo autorizó con `takana.yes`. Si hay parámetros pero nadie dijo
294# que sí, se entra igual por la rama interactiva: preguntar y que nadie conteste es un cuelgue, pero
295# formatear sin que nadie lo haya pedido es peor.
296if [ -n "$ARGS" ] && [ -n "$YES" ]; then
297    { sh /instalador/takana-install-partition.sh $ARGS --yes < /dev/null; echo $? > /rc; } 2>&1 | tee /dev/tty0 > /dev/ttyS0
298else
299    # Interactivo: la consola del kernel es la pantalla (tty0 va ÚLTIMO en la cmdline del salto),
300    # así que entrada y salida van ahí y el instalador ve una terminal de verdad — sin eso su
301    # `[ -t 0 ]` sale falso y se niega a preguntar.
302    log "TAKANA-INSTALADOR: el censo y la pregunta salen por $CONSOLA"
303    log "TAKANA-INSTALADOR: si estás mirando esto por el puerto serie y la pregunta no aparece acá,"
304    log "                   rearrancá con  takana.console=/dev/ttyS0  en la cmdline."
305    sh /instalador/takana-install-partition.sh $ARGS < "$CONSOLA" > "$CONSOLA" 2>&1
306    echo $? > /rc
307fi
308if [ "$(cat /rc 2>/dev/null || echo 1)" = "0" ]; then
309    log "TAKANA-INSTALADOR-OK: instalado. Reiniciando en 10 s (sacá el medio si lo hubiera)."
310    sync; sleep 10; reboot -f
311else
312    rescate "el instalador salió con error — NADA se reinicia solo, mirá el log de arriba"
313fi
314INIT
315chmod +x "$IRD_TREE/init"
316
317# ── 5) empaquetar ──────────────────────────────────────────────────────────────────────────────
318mkdir -p "$(dirname "$OUT")"
319# `TREE_ONLY=1` corta acá: el ÁRBOL es la pieza, y tiene dos medios. Comprimido en un cpio viaja
320# por kexec y dentro del `.run`; escrito en un ext4 es el USB, que existe para las máquinas donde el
321# salto no se puede. Duplicar el armado del árbol en el script del USB sería tener dos instaladores
322# que se parecen — y el que se olvide de actualizar es el que alguien va a arrancar.
323if [ -n "${TREE_ONLY:-}" ]; then
324    echo "==> TREE_ONLY: árbol listo en $IRD_TREE — no se comprime"
325    exit 0
326fi
327( cd "$IRD_TREE" && find . -print0 | "$CPIO" --null -o -H newc --owner=root:root 2>/dev/null | "$GZIP" -1 ) > "$OUT"
328echo "==> $OUT: $(du -h "$OUT" | cut -f1)"
329echo
330echo "Este initramfs NO lleva ninguna máquina horneada: decide al arrancar."
331echo "  · sin parámetros                  ⇒ censo del terreno + pregunta (sirve en cualquier máquina)"
332echo "  · takana.root=PARTUUID=…          ⇒ destino por identidad estable"
333echo "  · takana.free takana.disk=/dev/…  ⇒ instalar al lado, en espacio sin asignar"
334echo "  · takana.keep=PARTUUID=…:/home    ⇒ conservar ese /home sin formatearlo"
335echo "  · takana.adopt                    ⇒ NO formatear: mudar el sistema de ahí a /sistema-anterior"
336echo "  · takana.yes                      ⇒ sin preguntar (sólo junto a lo de arriba)"
337echo
338echo "Para SALTAR a él sin pasar por el firmware (el userspace de esta máquina muere ACÁ), lo normal
339es NO tocar esto y usar el verbo, que comprueba, avisa y arma la cmdline solo (ADR 0021):
340  sudo takana metal check      # ¿se puede?
341  sudo takana metal survey     # el terreno
342  sudo takana metal install    # el viaje entero
343El motor a la vista, por si hace falta:
344  sudo takana kernel kexec <linux-metal> --initrd work/instalador-salida/takana-instalador.cpio.gz \
345       --cmdline 'console=ttyS0,115200 console=tty0' --jump   # tty0 ÚLTIMO = la pantalla
346
347Para probarlo SIN saltar (y sin riesgo), en QEMU:"
348echo "  qemu-system-x86_64 -m 4096 -kernel $KERNEL -initrd $OUT \\"
349echo "       -append 'console=ttyS0' -drive file=<disco.img>,format=raw -nographic"