#!/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 DESTINO. Se formatea ext4 con LABEL=hammer-root. Lo ÚNICO destructivo. # --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="" 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//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@@` 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"