instalador por PARTICIÓN: takana llega de visita a un disco que es de otro
Los dos instaladores que había presuponen que el disco es nuestro: install-image-efi.sh fabrica una
imagen entera y takana-live-install.sh hace 'o' en fdisk (tabla NUEVA). El del usuario tiene 893 G
de /home y una Artix. Éste presupone lo contrario y se define por lo que NO hace:
- no escribe la tabla de particiones: no invoca fdisk/sfdisk/parted ni una vez
- formatea EXACTAMENTE la partición que se le nombra
- reusa la ESP existente y escribe sólo en \EFI\takana\ (ruta de vendor). \EFI\BOOT\BOOTX64.EFI
es tierra de nadie: si ya hay algo, NO se pisa sin --claim-fallback; si está libre, se ocupa
- registra la entrada NVRAM propia tras un 'boot entry backup' (ADR 0018). Convivir, no desplazar
- puebla ESP y destino SIN MONTAR nada (mcopy, mke2fs -d): un mount mal apuntado en un disco ajeno
es el accidente que este instalador existe para no tener
Los árboles (initramfs de pivote + /sbin/init que reconcilia) NO se copian: se PIDEN a
install-image-efi.sh con STAGE_ONLY=1 KEEP_STORE=1 — dos hooks de tres líneas en el script ya
validado. Copiarlos allá los haría divergir en silencio, que es peor que un hook feo.
install-particion-test.sh prueba sobre un disco loop CON VECINOS (ESP con Windows simulado +
partición con datos del usuario) y comprueba HASHES, no mensajes: la vecina idéntica byte a byte,
la tabla sin cambios, el BOOTX64.EFI ajeno intacto, el kernel entero en \EFI\takana\. Las tres
guardas se validan DISPARANDO (disco entero, partición montada, destino = ESP).
Dos hallazgos que el banco destapó, y los dos se leen como bugs del instalador sin serlo:
a) UN FAT PUEDE DECLARAR MÁS ESPACIO DEL QUE SU PARTICIÓN TIENE. 'mformat -i img@@offset' formatea
con el tamaño del fichero entero ⇒ ESP de 64 MiB anunciando 2,5 G. Y el 'df' del kernel repite
la mentira: la leyó del mismo sector de arranque. El guardián de capacidad contrasta ahora
contra el tamaño REAL del device. Pasa en la vida real con cualquier ESP encogida sin
reformatear.
b) MONTAR UN EXT4 LO MODIFICA (superbloque) ⇒ la huella de la vecina se toma DESPUÉS de las
guardas. Medirla antes daba un ROJO que acusaba al inocente, y un test que acusa al inocente se
desactiva a la semana.
Lo que este instalador NO hace, a propósito: redimensionar. Encoger pide desmontar (ext4 sólo crece
online) y la partición donde estás corriendo no se desmonta. Eso es física, no una carencia: el
redimensionado vive en el instalador que corre desde otro medio.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Semántico sayk'uy
Mana ima sutichaypas t'ikrakurqanchu: qillqalla, yachachiylla utaq willaylla t'ikrakurqan.
4 qillqakuna · +387 −6
diff --git a/docs/runbooks/instalador-laptop-tigerlake.md b/docs/runbooks/instalador-laptop-tigerlake.md
index e6a068ec..52edca0a 100644
--- a/docs/runbooks/instalador-laptop-tigerlake.md
+++ b/docs/runbooks/instalador-laptop-tigerlake.md
@@ -141,10 +141,24 @@ Hoy el instalador hace lo contrario de eso, y conviene verlo exacto antes de cam
**Lo que falta, nombrado:**
-1. **Un modo «partición destino»** en el instalador: recibir `/dev/nvme0n1p2`, formatearla, **no
- tocar la tabla**, reusar la ESP que ya existe y registrar la entrada NVRAM. Es el 80 % del valor
- y el 20 % del trabajo.
-2. **GPT en vez de MBR** cuando la tabla es nueva: `sfdisk`/`parted` ya están; `busybox fdisk` sale.
+1. ✅ **HECHO (2026-09-23) — `scripts/takana-install-partition.sh`.** Recibe `--root-part` y
+ `--esp`, formatea **exactamente** esa partición, **no invoca fdisk/sfdisk/parted ni una vez**,
+ escribe sólo en `\EFI\takana\` y registra la entrada NVRAM tras un `boot entry backup`.
+ Puebla la ESP y el destino **sin montar nada** (`mcopy` y `mke2fs -d`): un `mount` mal apuntado
+ en un disco ajeno es justo el accidente que este instalador existe para no tener. Verificado por
+ `scripts/install-particion-test.sh` sobre un disco loop con vecinos reales —una ESP con Windows
+ simulado y una partición con datos— comprobando **hashes, no mensajes**: la vecina queda idéntica
+ byte a byte, la tabla no cambia, el `BOOTX64.EFI` ajeno no se pisa. Las tres guardas (disco
+ entero, partición montada, destino = ESP) se prueban **disparando**, no por inspección.
+
+ ⚠ **Dos hallazgos de la primera corrida, porque los dos parecen bugs del instalador y no lo son:**
+ (a) **un FAT puede declarar más espacio del que su partición tiene** —`mformat` sobre un fichero
+ con offset formatea con el tamaño del fichero entero, y cualquier ESP encogida sin reformatear
+ queda igual— y **el `df` del kernel repite la mentira**, así que el guardián de capacidad contrasta
+ contra el tamaño real del device y no contra el filesystem; (b) **montar un ext4 lo modifica**
+ (superbloque), así que la huella de la vecina se toma después de las guardas: medirla antes daba
+ un rojo que acusaba al inocente.
+2. **GPT en vez de MBR** cuando la tabla es nueva (sigue pendiente en `takana-live-install.sh`): `sfdisk`/`parted` ya están; `busybox fdisk` sale.
3. **Encoger** (`resize2fs` + `sfdisk` reescribiendo sólo esa entrada). ⚠ **Límite físico, no de
software: un filesystem montado NO se encoge** — ext4 sólo crece online. Encoger `/home` exige
que esté desmontada ⇒ **hay que correr el instalador desde otro medio**, no desde la Artix viva.
diff --git a/scripts/install-image-efi.sh b/scripts/install-image-efi.sh
index 7763f502..48d2fb84 100755
--- a/scripts/install-image-efi.sh
+++ b/scripts/install-image-efi.sh
@@ -175,10 +175,19 @@ chmod +x "$IRD_TREE/init"
echo " initramfs: $(du -h "$IRD" | cut -f1)"
# --- 2) árbol root: hardlinks del rootfs SIN store/estado (van en sus particiones dedicadas) -----------
-echo "==> staging root (hardlinks del rootfs, sin /store ni /var/lib/hammer)"
+if [ -n "${KEEP_STORE:-}" ]; then
+ echo "==> staging root (hardlinks del rootfs, CON /store y /var/lib/hammer adentro: KEEP_STORE=1)"
+else
+ echo "==> staging root (hardlinks del rootfs, sin /store ni /var/lib/hammer)"
+fi
sonda_hardlink "$ROOTFS" "$STAGE" || exit 1
cp -al "$ROOTFS" "$ROOT_TREE"
-rm -rf "$ROOT_TREE/store" "$ROOT_TREE/var/lib/hammer"
+# ⚠ `KEEP_STORE=1` — el vaciado de abajo es correcto para la IMAGEN (store y estado son particiones
+# dedicadas y el pivote las monta por LABEL) y es DESTRUCTIVO para una instalación en UNA sola
+# partición, donde los dos viven dentro de la raíz. `takana-install-partition.sh` lo pone a 1.
+if [ -z "${KEEP_STORE:-}" ]; then
+ rm -rf "$ROOT_TREE/store" "$ROOT_TREE/var/lib/hammer"
+fi
mkdir -p "$ROOT_TREE/store" "$ROOT_TREE/var/lib/hammer"
# CLI `takana` (static musl) para el menú de arranque por grafo (ADR 0010): el hook de init lo invoca
# como `takana boot menu`. El producto trae arje-zero/hammerd pero NO el CLI ⇒ lo inyectamos acá si
@@ -255,6 +264,15 @@ exec /usr/bin/arje-zero
RINIT
chmod +x "$ROOT_TREE/sbin/init"
+# ⚠ `STAGE_ONLY=1` — cortar acá y no ensamblar ninguna imagen. Los pasos 1 y 2 son el ÚNICO lugar
+# del repo donde se arma el initramfs de pivote y el `/sbin/init` que reconcilia el arranque;
+# `takana-install-partition.sh` los necesita idénticos y copiarlos allá los haría divergir en
+# silencio — que es peor que un hook feo. Con esto el otro instalador los PIDE en vez de repetirlos.
+if [ -n "${STAGE_ONLY:-}" ]; then
+ echo "==> STAGE_ONLY: árboles listos en $STAGE (root=$ROOT_TREE initramfs=$IRD) — no se ensambla imagen"
+ exit 0
+fi
+
# --- 3) ESP FAT32: DOS rutas al MISMO kernel + initramfs.cpio.gz, poblada con mtools de takana ------
# ADR 0018 §1 y §3. El kernel se escribe en DOS sitios de la ESP, y no es redundancia decorativa:
#
diff --git a/scripts/install-particion-test.sh b/scripts/install-particion-test.sh
new file mode 100755
index 00000000..87e3ee88
--- /dev/null
+++ b/scripts/install-particion-test.sh
@@ -0,0 +1,142 @@
+#!/bin/sh
+# install-particion-test.sh — banco de pruebas de `takana-install-partition.sh` sobre un disco REAL
+# (loop device) que trae vecinos: una ESP con Windows simulado y una partición con datos del usuario.
+#
+# ── QUÉ PRUEBA, Y POR QUÉ ASÍ ──────────────────────────────────────────────────────────────────
+# Este instalador se define por lo que NO hace, y eso no se valida leyendo su salida: se valida
+# comprobando que los BYTES de al lado siguen ahí. Por eso el test no mira mensajes, mira hashes:
+#
+# 1. la partición vecina queda idéntica byte a byte (sha256 del device entero, antes y después)
+# 2. la tabla de particiones queda idéntica (`sfdisk -d`)
+# 3. el `\EFI\BOOT\BOOTX64.EFI` y el `\EFI\Microsoft\` del vecino no se tocan
+# 4. nuestro kernel llega entero a `\EFI\takana\takanax64.efi` (mismo sha256 que el bzImage)
+# 5. las tres GUARDAS disparan: disco entero, partición montada, partición = ESP
+# 6. el guardián de capacidad NO se cree al filesystem: una ESP cuyo FAT declara más espacio del
+# que la partición tiene (pasa de verdad con ESPs encogidas sin reformatear) se cota al tamaño
+# real. El `df` del kernel repite la mentira, así que creerle al FS no alcanza.
+#
+# Requiere root (losetup + mount). Usa las herramientas SELLADAS del store, no las del host.
+# sudo scripts/install-particion-test.sh
+set -eu
+ROOT="$(cd "$(dirname "$0")/.." && pwd)"; cd "$ROOT"
+[ "$(id -u)" = "0" ] || { echo "este test necesita root (losetup, mount)" >&2; exit 2; }
+
+P="${P:-work/.install-particion-test}"
+ROOTFS="${ROOTFS:-}"
+KERNEL="${KERNEL:-$(ls -dt /store/*-linux-metal/boot/bzImage store/*-linux-metal/boot/bzImage 2>/dev/null | head -1)}"
+[ -n "${KERNEL:-}" ] && [ -r "$KERNEL" ] || { echo "no encuentro un bzImage de linux-metal (set KERNEL=)" >&2; exit 1; }
+if [ -z "$ROOTFS" ]; then
+ # ⚠ El rootfs tiene que estar en el MISMO filesystem que el stage: el armado usa `cp -al`
+ # (hardlinks) y `linkat()` no cruza mounts (EXDEV). En el hub el store está en otro disco, así
+ # que se copia una vez acá. No es un capricho del test: es el mismo motivo por el que
+ # install-image-efi.sh trae la `sonda_hardlink`.
+ _src=$(ls -dt /store/*-product-rootfs store/*-product-rootfs 2>/dev/null | head -1)
+ [ -n "${_src:-}" ] || { echo "no encuentro un product-rootfs (set ROOTFS=)" >&2; exit 1; }
+ ROOTFS="$P/rootfs"
+ [ -d "$ROOTFS" ] || { mkdir -p "$P"; echo "==> copiando $_src → $ROOTFS (una vez, por EXDEV)"; cp -a "$_src" "$ROOTFS"; }
+fi
+
+. "$(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
+
+mkdir -p "$P"
+D="$P/disco.img"; LOOP=""
+limpiar() { [ -n "$LOOP" ] && losetup -d "$LOOP" 2>/dev/null || true; }
+trap limpiar EXIT INT TERM
+FALLOS=0
+ok() { echo " ✓ $*"; }
+falla(){ echo " ✗ $*" >&2; FALLOS=$(( FALLOS + 1 )); }
+
+# ── el disco de mentira, con vecinos de verdad ────────────────────────────────────────────────
+# ESP_SANA=0 formatea la ESP con el tamaño del FICHERO ENTERO (el FAT miente sobre su capacidad);
+# es el escenario del punto 6. Con 1, la ESP se formatea aparte y se empalma en su offset — que es
+# la forma correcta, y la que usa install-image-efi.sh.
+ESP_SANA="${ESP_SANA:-1}"
+echo "==> fabricando $D (GPT: ESP 64M + destino 1200M + datos del usuario 500M) ESP_SANA=$ESP_SANA"
+rm -f "$D"; truncate -s 2600M "$D"
+"$SFDISK" --quiet "$D" <<EOF
+label: gpt
+start=2048, size=131072, type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B, name="EFI System Partition"
+start=133120, size=2457600, type=linux, name="destino-takana"
+start=2590720, size=1024000, type=linux, name="datos-del-usuario"
+EOF
+head -c 3000000 /dev/urandom > "$P/ajeno.bin"
+if [ "$ESP_SANA" = "1" ]; then
+ rm -f "$P/esp.img"; truncate -s 64M "$P/esp.img"; _esp="$P/esp.img"
+else
+ _esp="$D@@1048576"
+fi
+"$MFORMAT" -F -v VECINO -i "$_esp" ::
+"$MMD" -i "$_esp" ::/EFI ::/EFI/BOOT ::/EFI/Microsoft ::/EFI/Microsoft/Boot
+"$MCOPY" -i "$_esp" "$P/ajeno.bin" ::/EFI/BOOT/BOOTX64.EFI
+"$MCOPY" -i "$_esp" "$P/ajeno.bin" ::/EFI/Microsoft/Boot/bootmgfw.efi
+[ "$ESP_SANA" = "1" ] && dd if="$P/esp.img" of="$D" bs=1M seek=1 conv=notrunc status=none
+rm -rf "$P/datos"; mkdir -p "$P/datos"; echo "la tesis del usuario, 2026" > "$P/datos/NO-BORRAR.txt"
+rm -f "$P/p3.img"; "$MKE2FS" -q -t ext4 -L datos-usuario -d "$P/datos" "$P/p3.img" 500M
+dd if="$P/p3.img" of="$D" bs=512 seek=2590720 conv=notrunc status=none
+
+LOOP=$(losetup -P -f --show "$D")
+echo "==> $D → $LOOP (p1=ESP p2=destino p3=datos)"
+"$SFDISK" -d "$LOOP" 2>/dev/null | grep -E "^${LOOP}p" > "$P/tabla-antes"
+
+# ── las guardas: cada una tiene que ABORTAR ───────────────────────────────────────────────────
+echo "==> guardas (las tres tienen que abortar SIN escribir)"
+corre() { ROOTFS="$ROOTFS" KERNEL="$KERNEL" STAGE="$P/stage" ./scripts/takana-install-partition.sh "$@" >/dev/null 2>&1; }
+corre --root-part "$LOOP" --esp "${LOOP}p1" --no-nvram --yes && falla "aceptó el DISCO ENTERO" || ok "rechaza el disco entero"
+mkdir -p "$P/mnt"; mount "${LOOP}p3" "$P/mnt"
+corre --root-part "${LOOP}p3" --esp "${LOOP}p1" --no-nvram --yes && falla "aceptó una partición MONTADA" || ok "rechaza una partición montada"
+umount "$P/mnt"
+corre --root-part "${LOOP}p1" --esp "${LOOP}p1" --no-nvram --yes && falla "aceptó root-part = ESP" || ok "rechaza root-part = ESP"
+
+# ── la instalación de verdad ──────────────────────────────────────────────────────────────────
+# ⚠ La huella de la vecina se toma ACÁ y no antes de las guardas, y la primera corrida de este test
+# explicó por qué: **montar un ext4 lo MODIFICA** (el superbloque guarda el último montaje y marca
+# el fs como montado), así que el `mount`/`umount` de la guarda de «partición montada» ya cambia el
+# sha256 del device. Medirlo antes daba un ROJO que no era del instalador — un test que acusa al
+# inocente se desactiva a la semana, que es peor que no tenerlo. Lo que este test tiene que probar
+# es que **la instalación** no la toca, y eso es exactamente lo que mide desde acá.
+sha256sum "${LOOP}p3" | cut -d' ' -f1 > "$P/p3-antes"
+echo "==> instalando en ${LOOP}p2"
+ROOTFS="$ROOTFS" KERNEL="$KERNEL" STAGE="$P/stage" \
+ ./scripts/takana-install-partition.sh --root-part "${LOOP}p2" --esp "${LOOP}p1" --no-nvram --yes 2>"$P/stderr" | tail -4
+if [ "$ESP_SANA" = "0" ]; then
+ grep -q "declara más espacio del que existe" "$P/stderr" \
+ && ok "el guardián de capacidad NO se cree al FAT mentiroso" \
+ || falla "el FAT mentiroso pasó sin advertencia"
+fi
+
+# ── lo que hay que comprobar es lo que NO cambió ──────────────────────────────────────────────
+echo "==> veredicto"
+[ "$(sha256sum "${LOOP}p3" | cut -d' ' -f1)" = "$(cat "$P/p3-antes")" ] \
+ && ok "la partición del usuario quedó idéntica byte a byte" || falla "LA PARTICIÓN DEL USUARIO CAMBIÓ"
+"$SFDISK" -d "$LOOP" 2>/dev/null | grep -E "^${LOOP}p" > "$P/tabla-ahora"
+cmp -s "$P/tabla-antes" "$P/tabla-ahora" && ok "la tabla de particiones no se tocó" || falla "LA TABLA CAMBIÓ"
+mkdir -p "$P/esp"; mount -t vfat "${LOOP}p1" "$P/esp"
+_a=$(sha256sum "$P/ajeno.bin" | cut -d' ' -f1)
+[ "$(sha256sum "$P/esp/EFI/BOOT/BOOTX64.EFI" | cut -d' ' -f1)" = "$_a" ] \
+ && ok "\\EFI\\BOOT\\BOOTX64.EFI del vecino intacto" || falla "SE PISÓ EL FALLBACK DEL VECINO"
+[ "$(sha256sum "$P/esp/EFI/Microsoft/Boot/bootmgfw.efi" | cut -d' ' -f1)" = "$_a" ] \
+ && ok "\\EFI\\Microsoft\\ intacto" || falla "SE TOCÓ EL WINDOWS DEL VECINO"
+[ "$(sha256sum "$P/esp/EFI/takana/takanax64.efi" | cut -d' ' -f1)" = "$(sha256sum "$KERNEL" | cut -d' ' -f1)" ] \
+ && ok "nuestro kernel llegó entero a \\EFI\\takana\\" || falla "el kernel de la ESP no coincide con el bzImage"
+[ -r "$P/esp/initramfs.cpio.gz" ] && ok "el initramfs de pivote está en la raíz de la ESP" || falla "falta \\initramfs.cpio.gz"
+umount "$P/esp"
+_lbl=$(blkid -s LABEL -o value "${LOOP}p2" 2>/dev/null || true)
+[ "$_lbl" = "hammer-root" ] && ok "el destino quedó ext4 LABEL=hammer-root" || falla "LABEL del destino = '${_lbl:-}' (esperaba hammer-root)"
+mkdir -p "$P/dst"; mount "${LOOP}p2" "$P/dst"
+[ -x "$P/dst/sbin/init" ] && ok "/sbin/init instalado (el wrapper que reconcilia el arranque)" || falla "el destino no tiene /sbin/init"
+umount "$P/dst"
+# Y el contenido del vecino, no sólo su hash: un fichero que sigue ahí y se lee es la prueba que el
+# usuario entiende.
+mount "${LOOP}p3" "$P/mnt"
+[ "$(cat "$P/mnt/NO-BORRAR.txt" 2>/dev/null)" = "la tesis del usuario, 2026" ] \
+ && ok "el fichero del usuario se sigue leyendo en la partición vecina" || falla "SE PERDIÓ EL FICHERO DEL USUARIO"
+umount "$P/mnt"
+
+echo
+[ "$FALLOS" = "0" ] && { echo "VERDE: instaló sin tocar nada de lo ajeno ($LOOP)"; exit 0; }
+echo "ROJO: $FALLOS comprobación(es) fallaron" >&2; exit 1
diff --git a/scripts/takana-install-partition.sh b/scripts/takana-install-partition.sh
new file mode 100755
index 00000000..9df70323
--- /dev/null
+++ b/scripts/takana-install-partition.sh
@@ -0,0 +1,207 @@
+#!/bin/sh
+# takana-install-partition.sh — instalar takana EN UNA PARTICIÓN, sin tocar la tabla ni las vecinas.
+#
+# ── QUÉ LO SEPARA DE LOS OTROS DOS INSTALADORES ────────────────────────────────────────────────
+# `install-image-efi.sh` fabrica una IMAGEN de disco entera, y `takana-live-install.sh` reparticiona
+# el DISCO entero (`o` en fdisk = tabla nueva). Los dos presuponen que el disco es nuestro. Éste
+# presupone lo contrario: **el disco es de otro y takana llega de visita**.
+#
+# - NO escribe la tabla de particiones. Ni una vez. No invoca fdisk/sfdisk/parted.
+# - Formatea EXACTAMENTE una partición: la que se le nombra.
+# - Reusa la ESP que ya existe, y escribe sólo dentro de `\EFI\takana\` — la ruta de VENDOR, que
+# es nuestra. `\EFI\BOOT\BOOTX64.EFI` es tierra de nadie (Windows la repuebla) y **no se pisa**
+# si ya existe, salvo `--claim-fallback` explícito.
+# - Registra la entrada NVRAM propia (`takana boot entry add`, ADR 0018) tras hacer un `backup`
+# del estado de arranque. Convivir, no desplazar.
+#
+# ⚠ **Lo que este instalador NO hace, a propósito: redimensionar.** Encoger pide desmontar el
+# filesystem (ext4 sólo crece online), y desmontar la partición donde estás corriendo no se puede.
+# Eso es física, no una carencia del script: el redimensionado vive en el instalador que corre desde
+# OTRO medio. Acá la partición destino tiene que existir y estar libre.
+#
+# Uso:
+# sudo scripts/takana-install-partition.sh --root-part /dev/nvme0n1p5 --esp /dev/nvme0n1p1
+# # prueba sin hardware (dos ficheros pre-dimensionados, sin root):
+# scripts/takana-install-partition.sh --root-part work/p-root.img --esp work/p-esp.img --no-nvram
+#
+# Opciones:
+# --root-part <dev|fichero> DESTINO. Se formatea ext4 con LABEL=hammer-root. Lo ÚNICO destructivo.
+# --esp <dev|fichero> ESP existente a poblar (FAT). Sin ella no hay arranque.
+# --disk <dev> --esp-part N para la entrada NVRAM; si no se dan, se deducen del `--esp`.
+# --claim-fallback además, escribir \EFI\BOOT\BOOTX64.EFI (pisa lo que haya ahí).
+# --no-nvram no tocar la NVRAM (pruebas, o firmware que se registra a mano).
+# --yes no preguntar (para guiones; en interactivo pide confirmación).
+# Variables: ROOTFS KERNEL BUSYBOX (como install-image-efi.sh) · STAGE (def work/.install-part-stage)
+set -eu
+ROOT="$(cd "$(dirname "$0")/.." && pwd)"; cd "$ROOT"
+
+DEST=""; ESP=""; DISK=""; ESP_PART=""; CLAIM_FALLBACK=""; NO_NVRAM=""; ASSUME_YES=""
+while [ $# -gt 0 ]; do
+ case "$1" in
+ --root-part) DEST="${2:?}"; shift 2 ;;
+ --esp) ESP="${2:?}"; shift 2 ;;
+ --disk) DISK="${2:?}"; shift 2 ;;
+ --esp-part) ESP_PART="${2:?}"; shift 2 ;;
+ --claim-fallback) CLAIM_FALLBACK=1; shift ;;
+ --no-nvram) NO_NVRAM=1; shift ;;
+ --yes|-y) ASSUME_YES=1; shift ;;
+ -h|--help) sed -n '2,40p' "$0"; exit 0 ;;
+ *) echo "opción desconocida: $1 (--help)" >&2; exit 2 ;;
+ esac
+done
+[ -n "$DEST" ] || { echo "falta --root-part (--help)" >&2; exit 2; }
+
+# ── LAS GUARDAS VAN PRIMERO, Y SON EL PUNTO DE ESTE SCRIPT ─────────────────────────────────────
+# Un instalador que borra de más falla del lado silencioso: el usuario se entera cuando reinicia y
+# le falta su /home. Cada guarda de abajo existe porque su ausencia es irreversible.
+es_bloque() { [ -b "$1" ]; }
+nombre_kernel() { basename "$(readlink -f "$1")"; }
+
+if es_bloque "$DEST"; then
+ _n=$(nombre_kernel "$DEST")
+ # 1) DESTINO TIENE QUE SER UNA PARTICIÓN, NO UN DISCO. `/sys/class/block/<x>/partition` sólo
+ # existe en particiones: es la diferencia entre formatear 200 G y formatear 1,8 T.
+ [ -r "/sys/class/block/$_n/partition" ] || {
+ echo "takana-install-partition: $DEST NO es una partición (¿es el disco entero?)." >&2
+ echo " Este instalador nunca escribe un disco completo." >&2; exit 1; }
+ # 2) NO PUEDE ESTAR MONTADA. mke2fs sobre un fs montado corrompe el que está en uso.
+ if grep -q "^$(readlink -f "$DEST") " /proc/mounts 2>/dev/null; then
+ echo "takana-install-partition: $DEST está MONTADA. Desmontala primero (o elegí otra)." >&2
+ grep "^$(readlink -f "$DEST") " /proc/mounts >&2; exit 1
+ fi
+ # 3) NO PUEDE SER LA RAÍZ VIVA. Es el caso que el usuario pregunta siempre: «¿puedo instalar
+ # encima del Linux que estoy usando?». No desde él: hay que saltar antes a otro kernel/medio.
+ _rootdev=$(awk '$2=="/" {print $1; exit}' /proc/mounts 2>/dev/null || true)
+ [ "$(readlink -f "$DEST")" = "$(readlink -f "${_rootdev:-/nonexistent}" 2>/dev/null || echo /nonexistent)" ] && {
+ echo "takana-install-partition: $DEST es la RAÍZ VIVA de este sistema. No se puede reemplazar" >&2
+ echo " el sistema que está corriendo desde adentro." >&2; exit 1; }
+ # 4) NO PUEDE SER LA ESP que vamos a reusar.
+ [ -n "$ESP" ] && [ "$(readlink -f "$DEST")" = "$(readlink -f "$ESP")" ] && {
+ echo "takana-install-partition: --root-part y --esp son la MISMA partición" >&2; exit 1; }
+else
+ [ -f "$DEST" ] || { echo "takana-install-partition: $DEST no existe (ni bloque ni fichero)" >&2; exit 1; }
+fi
+
+# ── El terreno, DICHO EN VOZ ALTA antes de tocar nada (ADR 0018 §4) ───────────────────────────
+HAMMER="${HAMMER:-$ROOT/target/release/takana}"
+echo "==> destino: $DEST ($( [ -b "$DEST" ] && echo 'partición real' || echo 'fichero de prueba'))"
+if [ -b "$DEST" ] && command -v blkid >/dev/null 2>&1; then
+ echo " lo que hay HOY ahí (se va a PERDER):"
+ blkid "$DEST" 2>/dev/null | sed 's/^/ /' || echo " (sin filesystem reconocible)"
+fi
+if [ -n "$ESP" ] && [ -b "$ESP" ] && [ -x "$HAMMER" ]; then
+ _d="${DISK:-/dev/$(basename "$(readlink -f "/sys/class/block/$(nombre_kernel "$ESP")/..")")}"
+ echo " terreno de $_d (nada de esto se toca):"
+ "$HAMMER" boot entry survey --disk "$_d" 2>&1 | sed 's/^/ /' || true
+fi
+if [ -z "$ASSUME_YES" ] && [ -t 0 ]; then
+ printf " ¿formateo %s y dejo el resto del disco INTACTO? [escribí si] " "$DEST"
+ read -r _r; [ "$_r" = "si" ] || { echo " cancelado."; exit 1; }
+fi
+
+# ── 1) Los árboles (initramfs de pivote + rootfs con su /sbin/init) los arma el script VALIDADO ──
+# No se copian acá: se PIDEN. Ver el comentario de `STAGE_ONLY` en install-image-efi.sh.
+STAGE="${STAGE:-work/.install-part-stage}"
+echo "==> armando árboles (STAGE_ONLY sobre install-image-efi.sh)"
+STAGE_ONLY=1 KEEP_STORE=1 STAGE="$STAGE" ./scripts/install-image-efi.sh
+ROOT_TREE="$STAGE/root"; IRD="$STAGE/initramfs.cpio.gz"
+[ -d "$ROOT_TREE" ] && [ -r "$IRD" ] || { echo "takana-install-partition: el stage no quedó completo" >&2; exit 1; }
+
+. "$(dirname "$0")/lib/herramientas-desde-store.sh"
+MKE2FS=$(herramienta e2fsprogs sbin/mke2fs mke2fs) || 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
+KERNEL="${KERNEL:-$(ls -dt store/*-linux-metal/boot/bzImage 2>/dev/null | head -1)}"
+[ -n "${KERNEL:-}" ] && [ -r "$KERNEL" ] || { echo "no encuentro el kernel EFI-stub (set KERNEL=)" >&2; exit 1; }
+
+# ── 2) Formatear SÓLO la partición destino, y poblarla SIN MONTARLA ────────────────────────────
+# `mke2fs -d` escribe el árbol dentro del filesystem nuevo sin `mount` y sin loop. Que no haya
+# montaje no es elegancia: un `mount` mal apuntado en un disco ajeno es justamente el accidente que
+# este script existe para no tener. Si no somos root, `unshare -r` da los dueños root sin sudo.
+echo "==> ext4 LABEL=hammer-root en $DEST (única escritura destructiva de todo el script)"
+if [ "$(id -u)" = "0" ]; then
+ "$MKE2FS" -q -t ext4 -L hammer-root -d "$ROOT_TREE" "$DEST"
+else
+ unshare -r "$MKE2FS" -q -t ext4 -L hammer-root -d "$ROOT_TREE" "$DEST"
+fi
+echo " ✓ $DEST formateada y poblada"
+
+# ── 3) La ESP: sólo nuestra carpeta de vendor, con mtools y SIN montar ─────────────────────────
+if [ -n "$ESP" ]; then
+ "$MDIR" -i "$ESP" :: >/dev/null 2>&1 || { echo "takana-install-partition: $ESP no parece FAT (¿es la ESP?)" >&2; exit 1; }
+ # Guardián de capacidad: una ESP de fábrica son 100 MiB y Windows ya vive ahí. El margen se
+ # comprueba ANTES, con los números a la vista, no como un «disk full» a mitad de copiar.
+ # ⚠ `mdir` imprime el espacio libre con SEPARADOR DE MILES en espacios («60 056 064 bytes
+ # free»), así que tomar `$1` devuelve **60** y el guardián aborta una instalación perfectamente
+ # posible. Medido en la primera corrida de este script. Hay que juntar los dígitos de la línea.
+ _free=$("$MDIR" -i "$ESP" :: 2>/dev/null | awk '/bytes free/ {l=$0; sub(/ bytes free.*/,"",l); gsub(/[^0-9]/,"",l); print l}' | tail -1)
+ _need=$(( $(wc -c < "$KERNEL") + $(wc -c < "$IRD") ))
+ [ -n "${_free:-}" ] && [ "$_free" -lt "$_need" ] && {
+ echo "takana-install-partition: la ESP tiene ${_free}B libres y hacen falta ${_need}B." >&2
+ echo " No se escribió nada en ella." >&2; exit 1; }
+ # ⚠ **Un FAT puede declarar MÁS espacio del que su partición tiene, y nadie se queja hasta que
+ # una escritura grande cae más allá del final.** Medido acá el 2026-09-23 fabricando el disco de
+ # prueba: `mformat -i disco.img@@<offset>` formatea usando el tamaño del FICHERO ENTERO, no el de
+ # la partición ⇒ una ESP de 64 MiB con un FAT que anuncia 2,5 G. Y no es un artefacto del banco de
+ # pruebas: cualquier ESP que alguien encogió con una herramienta de particionado sin reformatear
+ # queda igual. El `df` del kernel repite la mentira —la leyó del mismo sector de arranque— así que
+ # NO alcanza con preguntarle al filesystem: hay que contrastar contra el TAMAÑO REAL del
+ # contenedor, que es lo único que no puede mentir.
+ if [ -b "$ESP" ]; then
+ _cap=$(blockdev --getsize64 "$ESP" 2>/dev/null || echo "")
+ [ -z "$_cap" ] && _cap=$(( $(cat "/sys/class/block/$(nombre_kernel "$ESP")/size" 2>/dev/null || echo 0) * 512 ))
+ else
+ _cap=$(wc -c < "$ESP")
+ fi
+ if [ -n "${_free:-}" ] && [ "${_cap:-0}" -gt 0 ] && [ "$_free" -gt "$_cap" ]; then
+ echo " ! el FAT de $ESP dice ${_free}B libres pero la partición mide ${_cap}B: el filesystem" >&2
+ echo " declara más espacio del que existe (ESP encogida sin reformatear, o formateada" >&2
+ echo " sobre un contenedor mayor). Uso el tamaño REAL como cota." >&2
+ _free="$_cap"
+ fi
+ echo "==> ESP $ESP: \\EFI\\takana\\ (${_need}B sobre ${_free:-?}B libres)"
+ "$MMD" -i "$ESP" ::/EFI 2>/dev/null || true
+ "$MMD" -i "$ESP" ::/EFI/takana 2>/dev/null || true
+ "$MCOPY" -o -i "$ESP" "$KERNEL" ::/EFI/takana/takanax64.efi
+ "$MCOPY" -o -i "$ESP" "$IRD" ::/initramfs.cpio.gz
+ # La ruta fallback es tierra de nadie: si YA hay algo, es de otro sistema y no se pisa sin que
+ # el usuario lo pida. Si no hay nada, ocuparla es gratis y salva el caso «el firmware ignora la
+ # entrada NVRAM», que es el modo de fallo más común y el más difícil de diagnosticar.
+ if "$MDIR" -i "$ESP" ::/EFI/BOOT/BOOTX64.EFI >/dev/null 2>&1; then
+ if [ -n "$CLAIM_FALLBACK" ]; then
+ "$MCOPY" -o -i "$ESP" "$KERNEL" ::/EFI/BOOT/BOOTX64.EFI
+ echo " ! \\EFI\\BOOT\\BOOTX64.EFI PISADO (--claim-fallback)"
+ else
+ echo " · \\EFI\\BOOT\\BOOTX64.EFI ya existe y es de otro: NO se toca (--claim-fallback para pisarlo)"
+ fi
+ else
+ "$MMD" -i "$ESP" ::/EFI/BOOT 2>/dev/null || true
+ "$MCOPY" -o -i "$ESP" "$KERNEL" ::/EFI/BOOT/BOOTX64.EFI
+ echo " + \\EFI\\BOOT\\BOOTX64.EFI estaba libre ⇒ ocupada (arranca sin depender de la NVRAM)"
+ fi
+ for _p in ::/EFI/takana/takanax64.efi ::/initramfs.cpio.gz; do
+ "$MDIR" -i "$ESP" "$_p" >/dev/null 2>&1 || { echo "takana-install-partition: falta $_p en la ESP" >&2; exit 1; }
+ done
+ echo " ✓ ESP poblada y verificada (sin montarla)"
+else
+ echo " ! sin --esp: el sistema queda instalado pero NO arrancable todavía"
+fi
+
+# ── 4) La entrada de arranque: convivir con el vecino, con respaldo previo ─────────────────────
+if [ -z "$NO_NVRAM" ] && [ -n "$ESP" ] && [ -b "$ESP" ] && [ -d /sys/firmware/efi/efivars ] && [ -x "$HAMMER" ]; then
+ if [ -z "$DISK" ] || [ -z "$ESP_PART" ]; then
+ _en=$(nombre_kernel "$ESP")
+ DISK="${DISK:-/dev/$(basename "$(readlink -f "/sys/class/block/$_en/..")")}"
+ ESP_PART="${ESP_PART:-$(cat "/sys/class/block/$_en/partition" 2>/dev/null || echo 1)}"
+ fi
+ echo "==> respaldo del arranque ANTES de tocar la NVRAM (ADR 0018 §5)"
+ "$HAMMER" boot entry backup || echo " ! el respaldo falló — seguí sólo si sabés por qué" >&2
+ echo "==> entrada NVRAM: $DISK p$ESP_PART → \\EFI\\takana\\takanax64.efi"
+ "$HAMMER" boot entry add --disk "$DISK" --partition "$ESP_PART" --loader '/EFI/takana/takanax64.efi'
+else
+ echo " · NVRAM no tocada (--no-nvram, sin efivarfs, o destino de prueba)"
+fi
+
+echo "✓ takana instalada en $DEST. La tabla de particiones NO se tocó y las demás particiones siguen como estaban."
+echo " Comprobalo antes de reiniciar: lsblk -o NAME,SIZE,FSTYPE,LABEL · $HAMMER boot entry list"