#!/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. # (La única excepción es `--use-free-space`, que AÑADE una entrada en un hueco sin asignar y no # modifica ninguna de las existentes. Se dice aparte porque es la única vez que la tabla cambia.) # - 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 DESTINO. Se formatea ext4 con LABEL=hammer-root. Lo ÚNICO destructivo. # --use-free-space no formatear nada existente: crear la partición en el hueco sin # asignar más grande de `--disk` e instalar ahí. Al lado, no encima. # --on-active-root qué hacer si `--root-part` ES la raíz viva (auto|kexec|free-space|abort). # `auto` (def) = intentar kexec y, si esta máquina no puede, espacio libre. # --esp ESP existente a poblar (FAT). Sin ella no hay arranque. # --disk --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="" USE_FREE=""; ON_ACTIVE="${ON_ACTIVE:-auto}" 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 ;; --use-free-space) USE_FREE=1; shift ;; --on-active-root) ON_ACTIVE="${2:?}"; shift 2 ;; --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" ] || [ -n "$USE_FREE" ] || { echo "falta --root-part (o --use-free-space) — ver --help" >&2; exit 2; } case "$ON_ACTIVE" in auto|kexec|free-space|abort) ;; *) echo "--on-active-root: auto|kexec|free-space|abort" >&2; exit 2 ;; esac # ── Las herramientas, ANTES que nada ─────────────────────────────────────────────────────────── # Se resuelven acá arriba y no junto a su uso porque `--use-free-space` necesita `sfdisk` durante # las GUARDAS, o sea antes de tocar nada. Salen del store por hash vigente; `TOOL_=` las # inyecta cuando no hay store (dentro del initramfs de un instalador, p. ej.). . "$(dirname "$0")/lib/herramientas-desde-store.sh" MKE2FS=$(herramienta e2fsprogs sbin/mke2fs mke2fs) || exit 1 SFDISK=$(herramienta util-linux sbin/sfdisk sfdisk) || exit 1 # Releer la tabla es su propio problema: tras añadir una entrada, el kernel NO se entera solo y el # device nuevo no aparece. `partx -a` se lo dice; `blockdev --rereadpt` es el ioctl crudo, que falla # si alguna partición del disco está montada — por eso se intentan los dos, en ese orden. Los dos # salen del `util-linux` sellado: en el anfitrión musl no existe ninguno (ni `partprobe`). PARTX=$(herramienta util-linux usr/sbin/partx partx 2>/dev/null || echo "") BLOCKDEV=$(herramienta util-linux sbin/blockdev blockdev 2>/dev/null || echo "") 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 /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; } HAMMER="${HAMMER:-$ROOT/target/release/takana}" # ── 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")"; } disco_de() { echo "/dev/$(basename "$(readlink -f "/sys/class/block/$(nombre_kernel "$1")/..")")"; } # ── releer_tabla — que el KERNEL se entere de la entrada nueva ──────────────────────── # Medido el 2026-09-23 en el banco: `sfdisk --append` escribió la entrada perfectamente y # `/dev/loop0p4` NO APARECIÓ. El script lo detectó y abortó —bien— pero el motivo estaba fuera de # él: en el anfitrión musl no existen `partprobe`, `partx` ni `blockdev`, así que los tres intentos # de releer la tabla eran no-ops silenciosos. La tabla en disco y la que el kernel tiene en memoria # son dos cosas distintas, y nada avisa cuando divergen. # ⚠ Y el comando es `partx -u`, NO `partx -a`: medido en el banco el mismo día. `-a` («añadir») # intenta registrar TODAS las particiones y falla entero —«error adding partitions 1-4»— cuando las # viejas ya están, que es siempre en este flujo. `-u` («actualizar») añade lo que falte y deja en paz # lo que ya hay. El nombre de la opción sugiere justo lo contrario de lo que sirve acá. releer_tabla() { [ -n "${PARTX:-}" ] && "$PARTX" -u "$1" >/dev/null 2>&1 && return 0 [ -n "${PARTX:-}" ] && "$PARTX" -a "$1" >/dev/null 2>&1 && return 0 [ -n "${BLOCKDEV:-}" ] && "$BLOCKDEV" --rereadpt "$1" >/dev/null 2>&1 && return 0 partprobe "$1" 2>/dev/null || true } # ── mib_necesarios — cuánto pedirle al hueco ────────────────────────────────────────────────── # El rootfs medido + 40 %, con piso de 2 GiB. El margen no es superstición: `mke2fs` se queda con # ~1,5 % en metadatos y el sistema instalado tiene que poder escribir algo antes de que alguien lo # agrande. Un destino que entra JUSTO produce un sistema que arranca y se llena a la semana. mib_necesarios() { _mn_r="${ROOTFS:-}" [ -z "$_mn_r" ] && _mn_r=$(ls -dt store/*-product-rootfs /store/*-product-rootfs 2>/dev/null | head -1) _mn_m=$(du -sm "$_mn_r" 2>/dev/null | cut -f1) [ -z "${_mn_m:-}" ] && _mn_m=1024 _mn_m=$(( _mn_m * 14 / 10 )) [ "$_mn_m" -lt 2048 ] && _mn_m=2048 echo "$_mn_m" } # ── crear_en_hueco — la ÚNICA vez que este script escribe la tabla ─────────────── # `sfdisk --append` añade una entrada y **no reescribe las existentes**: es la diferencia entre # «instalar al lado» y el `o` de `takana-live-install.sh`, que empieza por tirar la tabla entera. # Aun así es la operación más delicada de acá, así que: se elige el hueco MÁS GRANDE, se dice cuál # es con sus sectores a la vista, y se pide confirmación aparte de la del formateo. crear_en_hueco() { _ch_disco="$1"; _ch_mib="$2" _ch_libre=$("$SFDISK" -F "$_ch_disco" 2>/dev/null | awk 'NF==4 && $1 ~ /^[0-9]+$/ {if ($3+0 > m) {m=$3+0; s=$1; e=$2}} END {if (m>0) print s, e, m}') [ -n "$_ch_libre" ] || { echo "takana-install-partition: $_ch_disco no tiene espacio SIN ASIGNAR" >&2; return 1; } _ch_start=$(echo "$_ch_libre" | cut -d' ' -f1) _ch_sect=$(echo "$_ch_libre" | cut -d' ' -f3) _ch_libre_mib=$(( _ch_sect / 2048 )) echo " hueco más grande: ${_ch_libre_mib} MiB desde el sector ${_ch_start}" >&2 [ "$_ch_libre_mib" -ge "$_ch_mib" ] || { echo "takana-install-partition: el hueco da ${_ch_libre_mib} MiB y hacen falta ${_ch_mib} MiB." >&2 echo " Nada que hacer sin encoger algo, y encoger pide otro medio." >&2; return 1; } _ch_size=$(( _ch_mib * 2048 )) if [ -z "$ASSUME_YES" ] && [ -t 0 ]; then printf " voy a AÑADIR una partición de %s MiB a la tabla de %s (las existentes no se tocan) [escribí si] " "$_ch_mib" "$_ch_disco" >&2 read -r _ch_r; [ "$_ch_r" = "si" ] || { echo " cancelado." >&2; return 1; } fi # La lista de ANTES, para saber después cuál es la nueva por DIFERENCIA y no por su nombre. # ⚠ El primer intento buscaba la línea por `start=` y nunca la encontraba: `sfdisk -d` # **alinea los campos con espacios** (`start= 3614720`), así que el patrón sin espacios no # casa jamás. El síntoma era idéntico al de un device que no aparece — y mandó a buscar el fallo # en `partx` y en udev, que estaban bien. Comparar conjuntos no depende del formato. _ch_lista_antes=$("$SFDISK" -d "$_ch_disco" 2>/dev/null | awk '/^\/dev/ {print $1}') _ch_antes=$(printf '%s\n' "$_ch_lista_antes" | grep -c . || true) echo "start=$_ch_start, size=$_ch_size, type=linux, name=\"takana-root\"" \ | "$SFDISK" --append --quiet "$_ch_disco" >/dev/null 2>&1 \ || { echo "takana-install-partition: sfdisk --append falló sobre $_ch_disco" >&2; return 1; } # El device nuevo tarda en aparecer (udev, o `partx` en un loop): esperarlo es obligatorio, y el # nombre se LEE de la tabla en vez de construirlo a mano — `nvme0n1p4` y `sda4` no se escriben igual. releer_tabla "$_ch_disco" _ch_nuevo="" _ch_i=0 while [ "$_ch_i" -lt 15 ]; do _ch_lista_post=$("$SFDISK" -d "$_ch_disco" 2>/dev/null | awk '/^\/dev/ {print $1}') for _ch_d in $_ch_lista_post; do case " $(printf '%s ' $_ch_lista_antes)" in *" $_ch_d "*) ;; *) _ch_nuevo="$_ch_d" ;; esac done [ -n "$_ch_nuevo" ] && [ -b "$_ch_nuevo" ] && break _ch_nuevo="" _ch_i=$(( _ch_i + 1 )); sleep 1 releer_tabla "$_ch_disco" done [ -n "$_ch_nuevo" ] && [ -b "$_ch_nuevo" ] || { echo "takana-install-partition: la partición se añadió pero su device no apareció (¿hace falta un partprobe?)" >&2; return 1; } _ch_despues=$("$SFDISK" -d "$_ch_disco" 2>/dev/null | awk '/^\/dev/ {print $1}' | grep -c . || true) [ "$_ch_despues" = "$(( _ch_antes + 1 ))" ] || { echo "takana-install-partition: la tabla pasó de $_ch_antes a $_ch_despues entradas — esperaba UNA más" >&2; return 1; } echo "$_ch_nuevo" } # `--use-free-space` sin `--root-part`: el destino todavía no existe, se crea en un hueco. Esto es # «instalar al lado», y es el camino que la política de la raíz viva usa cuando no hay kexec. if [ -n "$USE_FREE" ] && [ -z "$DEST" ]; then _ufs_disco="${DISK:-}" if [ -z "$_ufs_disco" ] && [ -n "$ESP" ] && [ -b "$ESP" ]; then _ufs_disco=$(disco_de "$ESP"); fi [ -n "$_ufs_disco" ] || { echo "takana-install-partition: --use-free-space necesita --disk (o un --esp real)" >&2; exit 2; } echo "==> buscando espacio SIN ASIGNAR en $_ufs_disco" DEST=$(crear_en_hueco "$_ufs_disco" "$(mib_necesarios)") || exit 1 echo " partición creada: $DEST" fi if es_bloque "$DEST"; then _n=$(nombre_kernel "$DEST") # 1) DESTINO TIENE QUE SER UNA PARTICIÓN, NO UN DISCO. `/sys/class/block//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) if [ "$(readlink -f "$DEST")" = "$(readlink -f "${_rootdev:-/nonexistent}" 2>/dev/null || echo /nonexistent)" ]; then politica_raiz_viva # kexec → espacio libre → abortar, según --on-active-root fi # 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 # ── politica_raiz_viva — el «saltito»: qué hacer si el destino ES el sistema que corre ──────── # La pregunta que todo el mundo hace es «¿puedo poner takana encima del Linux que estoy usando, sin # USB?». La respuesta honesta tiene tres partes y este bloque las implementa en orden: # # 1. Desde adentro, NO. Un filesystem montado y en uso no se formatea; eso es el kernel, no una # política nuestra. # 2. Pero se puede SALIR de adentro sin reiniciar por el firmware: `kexec` salta a otro kernel con # un instalador en RAM, y en ese salto el sistema viejo deja de correr ⇒ su disco queda libre. # Es el ADR 0017 §1, y el syscall ya estaba implementado (`kexec_file_load`, sin kexec-tools). # 3. Y si esta máquina no puede saltar —kernel sin `CONFIG_KEXEC_FILE`, o lockdown por Secure # Boot— no se fuerza nada: se instala AL LADO, en espacio sin asignar, y el usuario elige en el # arranque. Peor camino, cero riesgo. # # `--on-active-root` permite fijar uno de los tres a mano; `auto` es la política de arriba. politica_raiz_viva() { echo "⚠ $DEST es la RAÍZ VIVA: el sistema que está corriendo ahora mismo." echo " Un filesystem montado y en uso no se puede formatear desde adentro." case "$ON_ACTIVE" in abort) echo " --on-active-root=abort ⇒ no hago nada más." >&2; exit 1 ;; esac _prv_kexec=no if [ "$ON_ACTIVE" = auto ] || [ "$ON_ACTIVE" = kexec ]; then echo "==> ¿puede esta máquina saltar a otro kernel sin pasar por el firmware?" if _prv_out=$("$HAMMER" kernel kexec --probe 2>&1); then _prv_kexec=si; fi printf '%s\n' "$_prv_out" | sed 's/^/ /' fi if [ "$_prv_kexec" = si ]; then camino_kexec # no vuelve: salta exit 0 fi if [ "$ON_ACTIVE" = kexec ]; then echo " --on-active-root=kexec y esta máquina no puede saltar (motivos arriba)." >&2; exit 1 fi echo "==> sin kexec ⇒ INSTALO AL LADO, en espacio sin asignar (la raíz viva no se toca)" _prv_disco=$(disco_de "$DEST") DEST=$(crear_en_hueco "$_prv_disco" "$(mib_necesarios)") || { echo "takana-install-partition: no hay hueco donde instalar al lado, y sin kexec no hay otra" >&2 echo " desde este sistema. Queda el camino del medio externo (USB)." >&2 exit 1; } echo " destino nuevo: $DEST (la raíz viva sigue intacta)" } # ── camino_kexec — armar el instalador en RAM y saltar. NO VUELVE ───────────────────────────── camino_kexec() { echo "==> camino KEXEC: el instalador viaja en RAM y el sistema viejo muere en el salto" _ck_args="--root-part $DEST" [ -n "$ESP" ] && _ck_args="$_ck_args --esp $ESP" [ -n "$CLAIM_FALLBACK" ] && _ck_args="$_ck_args --claim-fallback" # ⚠ `--on-active-root abort` ADENTRO, y no es decorativo: si por lo que sea el destino siguiera # siendo la raíz viva del sistema nuevo, `auto` volvería a intentar un kexec y tendríamos una # máquina saltando a sí misma en bucle. Abortar ahí es un fallo legible. _ck_args="$_ck_args --on-active-root abort" _ck_ird="${IRD_INSTALADOR:-work/takana-instalador.cpio.gz}" # shellcheck disable=SC2086 OUT="$_ck_ird" KERNEL="$KERNEL" ./scripts/instalador-initramfs.sh $_ck_args || { echo "takana-install-partition: no pude armar el instalador en RAM" >&2; exit 1; } _ck_art=$(dirname "$(dirname "$KERNEL")") # …/-linux-metal/boot/bzImage → el artefacto echo echo " kernel: $_ck_art" echo " initrd: $_ck_ird ($(du -h "$_ck_ird" | cut -f1))" echo " destino: $DEST" if [ -z "$ASSUME_YES" ] && [ -t 0 ]; then printf " ⚡ ESTE COMANDO MATA EL SISTEMA QUE ESTÁS USANDO y arranca el instalador. [escribí saltar] " read -r _ck_r; [ "$_ck_r" = "saltar" ] || { echo " cancelado — no se saltó y no se tocó nada."; exit 1; } fi "$HAMMER" kernel kexec "$_ck_art" --initrd "$_ck_ird" \ --cmdline "console=tty0 console=ttyS0,115200" --jump echo "takana-install-partition: el salto volvió, o sea que NO saltó. Nada se tocó." >&2 exit 1 } # ── El terreno, DICHO EN VOZ ALTA antes de tocar nada (ADR 0018 §4) ─────────────────────────── 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}" # `STAGE_LISTO=1` — el árbol ya está armado y no hay que (ni se puede) rearmarlo. Es lo que usa el # instalador cuando corre DENTRO de un initramfs: allí no hay repo, ni store, ni `install-image-efi.sh`; # el stage viajó horneado en el propio initramfs. Se comprueba que esté completo, no se confía. if [ -n "${STAGE_LISTO:-}" ]; then echo "==> stage ya armado: $STAGE (STAGE_LISTO=1)" else echo "==> armando árboles (STAGE_ONLY sobre install-image-efi.sh)" STAGE_ONLY=1 KEEP_STORE=1 STAGE="$STAGE" ./scripts/install-image-efi.sh fi 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; } # ── 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@@` 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 if [ -n "$USE_FREE" ]; then echo "✓ takana instalada en $DEST, una partición NUEVA creada en espacio sin asignar." echo " La tabla creció en una entrada; ninguna de las que ya estaban se modificó." else echo "✓ takana instalada en $DEST. La tabla de particiones NO se tocó y las demás particiones siguen como estaban." fi echo " Comprobalo antes de reiniciar: lsblk -o NAME,SIZE,FSTYPE,LABEL · $HAMMER boot entry list"