#!/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. **Usá PARTUUID=**: los nombres nvmeXn1 NO son # estables entre arranques (medido) y el kexec cambia quién enumera. # --survey mirar y NO tocar: el censo del terreno de esta máquina, con el # PARTUUID de cada partición y qué se puede usar. Empezá por acá. # --json (con --survey) el mismo censo en JSON. Es el CONTRATO que consume # una cara gráfica: `takana metal survey --json` lo envuelve. # **Versión 2** desde el 2026-09-25: cada fila trae además `clase`, # `disco`, `inicio_bytes`, `tamano_bytes` y `tipo_guid`, y hay un array # `discos` con el aparato. Es lo que hace falta para DIBUJAR el terreno # en vez de listarlo; nada de eso decide — `veredicto` y `usable` # siguen siendo los únicos que deciden. # ⚠ El JSON sale por stdout y SOLO el JSON: la línea de qué herramienta # se resolvió del store va por stderr. Si algún día se cuela en stdout, # la cara deja de parsear y el censo se lee como «no hay discos». # --pick mostrar el censo, DEJAR ELEGIR y NO TOCAR NADA: deja la elección en # (`DEST=…`, `FREE=1`, `DISK=…`) y sale. Es lo que permite # decidir ANTES del salto — así lo de después no necesita pantalla. # La respuesta va a un FICHERO y no a stdout porque stdout es donde # sale el menú: mezclarlos obliga a quien lo llama a adivinar dónde # termina la conversación y empieza el dato. # (sin destino) modo INTERACTIVO: muestra el censo y te deja elegir. Es lo que hace # que un mismo medio sirva en cualquier máquina. # --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í). # --keep : # CONSERVAR esa partición y montarla en en el sistema nuevo, # SIN formatearla. Hoy el único admitido es `/home`. Los # usuarios que encuentre ahí se crean en el sistema nuevo CON SU MISMO # uid, y con el nombre del directorio como login. Se comprueba que # monte ANTES de tocar la raíz: descubrir que el /home que se iba a # conservar no monta, con la raíz ya formateada, es el peor momento. # --adopt NO FORMATEAR el destino: mover el sistema que ya hay ahí a # `/sistema-anterior` y dejar su `/home` donde está. Es el caso más # común de todos —una sola partición con un Linux y el /home adentro— # y el que peor resuelve formatear: lo único irreponible de una # máquina es el /home de quien la usa. Mover dentro del mismo # filesystem es un `rename(2)`: no copia un byte. El sistema anterior # queda ENTERO y accesible, que es mejor que un respaldo. # Incompatible con `--use-free-space` (ahí no hay nada que adoptar). # --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}"; SOLO_CENSO=""; CENSO_JSON=""; SOLO_ELEGIR="" KEEP_SPEC=""; KEEP_PUNTO=""; KEEP_DEV=""; KEEP_UUID=""; KEEP_PARTUUID=""; KEEP_DIR=""; KEEP_MONTE_YO="" ADOPT=""; ADOPT_DIR=""; ADOPT_MONTE_YO=""; ADOPT_SUB=""; ADOPT_MUEVE="" 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 ;; --survey) SOLO_CENSO=1; shift ;; --json) CENSO_JSON=1; shift ;; --pick) SOLO_ELEGIR="${2:?--pick necesita el fichero donde dejar la respuesta}"; shift 2 ;; --use-free-space) USE_FREE=1; shift ;; --keep) KEEP_SPEC="${2:?--keep necesita :}"; shift 2 ;; --adopt) ADOPT=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 # Sin destino no se aborta: se PREGUNTA. Eso es lo que separa un instalador de un guion que hay que # preparar por máquina. Sólo si tampoco hay con quién hablar (sin terminal) se pide el parámetro. NECESITA_ELEGIR="" if [ -z "$DEST" ] && [ -z "$USE_FREE" ] && [ -z "$SOLO_CENSO" ]; then NECESITA_ELEGIR=1; fi 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" # Los dos caminos que conservan datos ajenos viven en sus propias librerías, y eso es lo que permite # certificarlos SIN root (`scripts/adoptar-test.sh`): el instalador entero necesita losetup, mount y # un disco; estas funciones, sobre directorios, se prueban enteras en un segundo. . "$(dirname "$0")/lib/usuarios-del-home.sh" . "$(dirname "$0")/lib/adoptar.sh" # La cuenta de quien va a usar la máquina. Este camino —`--keep`/`--adopt`, el más común de todos— # no preguntaba NADA: los usuarios salían del `/home` conservado con `!` en shadow y root quedaba # cerrado, o sea una máquina instalada e inadministrable. Medido en metal el 2026-10-14. . "$(dirname "$0")/lib/cuenta.sh" SFDISK=$(herramienta util-linux sbin/sfdisk sfdisk) || exit 1 # `e2label` sólo hace falta para `--adopt`, que no formatea: ahí la etiqueta por la que el arranque # encuentra la raíz hay que ponerla a mano. Se resuelve acá arriba —con el resto— y sólo cuando se # pidió adoptar, para no exigir una herramienta que el camino normal no usa. # ⚠ **Sin `|| exit 1`, y eso es el punto.** `e2label` es OPCIONAL: cuando no está, la etiqueta la # escribe `takana fs-label`, que viaja en el mismo medio. Con el `|| exit 1` que tenía, el script se # moría acá arriba —antes de tocar nada, sí, pero también antes de llegar al plan B— y la # instalación entera fallaba con «falta e2label» sobre una máquina que podía perfectamente adoptar. # Medido en QEMU el 2026-10-02. Una herramienta con alternativa no se exige: se busca. E2LABEL="" if [ -n "$ADOPT" ]; then E2LABEL=$(herramienta e2fsprogs sbin/e2label e2label 2>/dev/null || true) fi # 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 "") # ⚠ **MIRAR no puede costar lo mismo que ESCRIBIR.** Todo lo de abajo —los tres binarios de mtools, # mke2fs y el bzImage— sólo hace falta para INSTALAR, y hasta el 2026-09-23 se exigía también para # `--survey`: en una máquina ajena, sin store y sin kernel del corpus, el censo moría con «no # encuentro el kernel EFI-stub» ANTES de imprimir una sola línea del terreno. Y el censo es # justamente lo primero que se corre en una máquina desconocida, donde nada de eso existe todavía. # El censo necesita `sfdisk` y `blkid`, nada más. if [ -z "$SOLO_CENSO" ] && [ -z "$SOLO_ELEGIR" ]; then 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 /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; } fi 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")/..")")"; } # ── resolver_dev — NUNCA confíes en `/dev/nvme1n1p6` ─────────────────────────────────── # ⚠ **Los nombres `nvmeXn1` NO son estables entre arranques, y esto está MEDIDO, no temido** # (2026-09-23, QEMU con dos NVMe): el disco declarado primero en la línea de comandos apareció # como `nvme1n1` y el segundo como `nvme0n1` — **cruzados**. Salen del orden de sondeo del bus, no # del orden físico ni del que vos ves hoy con `lsblk`. # # Para un instalador eso no es una molestia: es la diferencia entre formatear la raíz vieja y # formatear el disco de Windows del usuario. Y el salto por kexec lo agrava, porque el kernel que # enumera después del salto NO es el que te dio los nombres antes. # # Por eso se acepta —y se recomienda— direccionar por identidad y no por posición: # PARTUUID= el de la TABLA GPT. **No cambia al formatear la partición**, así que es el # correcto para nombrar un destino que se va a formatear. # UUID= el del FILESYSTEM. Sirve para identificar lo que hay HOY (y muere al formatear). # LABEL= cómodo, pero puede estar repetido en dos discos. resolver_dev() { case "$1" in /dev/*) echo "$1"; return 0 ;; PARTUUID=*|partuuid=*) _rd_v="${1#*=}"; _rd_por=partuuid ;; UUID=*|uuid=*) _rd_v="${1#*=}"; _rd_por=uuid ;; LABEL=*|label=*) _rd_v="${1#*=}"; _rd_por=label ;; *) echo "$1"; return 0 ;; # un fichero (pruebas) esac # `findfs` resuelve UUID y LABEL en busybox y en util-linux; para PARTUUID no se puede contar # con él, así que ese caso se resuelve leyendo la TABLA con sfdisk — que es donde vive el dato. if [ "$_rd_por" != partuuid ] && command -v findfs >/dev/null 2>&1; then _rd_d=$(findfs "$(printf '%s' "$_rd_por" | tr 'a-z' 'A-Z')=$_rd_v" 2>/dev/null || true) [ -n "${_rd_d:-}" ] && { echo "$_rd_d"; return 0; } fi # `/dev/loop?` está en la lista porque el banco de pruebas trabaja sobre loop devices: sin él, # la resolución por identidad quedaba SIN COBERTURA en el único sitio donde se puede probar sin # arriesgar un disco de verdad. Lo detectó el propio banco al pasar a PARTUUID. for _rd_disco in /dev/nvme?n? /dev/sd? /dev/vd? /dev/mmcblk? /dev/loop? /dev/loop??; do [ -b "$_rd_disco" ] || continue case "$_rd_por" in partuuid) _rd_hit=$("$SFDISK" -d "$_rd_disco" 2>/dev/null \ | awk -v u="$(printf '%s' "$_rd_v" | tr 'A-Z' 'a-z')" \ 'tolower($0) ~ ("uuid=" u) {print $1; exit}') ;; *) _rd_hit=$(for _rd_p in "$_rd_disco"*; do [ -b "$_rd_p" ] || continue blkid "$_rd_p" 2>/dev/null | grep -qi "$(printf '%s' "$_rd_por" | tr 'a-z' 'A-Z')=\"$_rd_v\"" && { echo "$_rd_p"; break; } done) ;; esac [ -n "${_rd_hit:-}" ] && { echo "$_rd_hit"; return 0; } done echo "takana-install-partition: no encuentro ninguna partición con $1" >&2 echo " (mirá 'lsblk -o NAME,SIZE,PARTUUID,LABEL' en el sistema vivo)" >&2 return 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 } # ── EL CENSO DEL TERRENO ─────────────────────────────────────────────────────────────────────── # Un instalador que hay que preparar a mano para cada máquina no es un instalador. Esto enumera lo # que hay —en CUALQUIER máquina— y dice de cada partición si se puede usar, si no se debe tocar y # por qué. Es la base del modo interactivo y de la recomendación automática. # # Dos fuentes, porque ninguna sola alcanza: # · `sfdisk -d` da el TIPO de la tabla (GUID) y el **PARTUUID**, que es la identidad estable; # · `blkid` da lo que hay ADENTRO (ntfs, crypto_LUKS, ext4, swap…), que el tipo GPT miente # a menudo: media instalación de Windows vive en «datos básicos», y un LUKS puede # estar en una partición marcada como Linux. # Y `/proc/mounts` dice qué está en uso AHORA, que es lo único que distingue «vacía» de «tu raíz». # ── raiz_viva_majmin — quién es «/» de verdad ───────────────────────────────────────────────── # ⚠ **`/proc/mounts` NO alcanza para saber cuál es la raíz viva, y esto está MEDIDO** (2026-09-23, # sobre el anfitrión de este hub): la línea de «/» dice `/dev/root`, un nombre que no existe en # `/dev` ni casa con ninguna partición. Comparar por nombre daba «ninguna partición es la raíz» y el # censo **ofrecía formatear la raíz del sistema que estaba corriendo**. Pasa también con `UUID=…` # en la cmdline, con initramfs que montan por label, y con `/dev/mapper/*`. # # Lo que sí es inequívoco es el NÚMERO de dispositivo: `stat` lo da para «/» y cada partición lo # publica en `/sys/class/block//dev`. La descomposición es la de Linux (12 bits de major, minor # partido en dos), no el `/256` ingenuo, que se rompe justo en NVMe (major 259). raiz_viva_majmin() { _rv_hex=$(stat -c %D / 2>/dev/null) || return 1 [ -n "${_rv_hex:-}" ] || return 1 _rv_d=$(( 0x$_rv_hex )) 2>/dev/null || return 1 _rv_maj=$(( (_rv_d >> 8) & 0xfff )) _rv_min=$(( (_rv_d & 0xff) | ((_rv_d >> 12) & 0xfff00) )) echo "${_rv_maj}:${_rv_min}" } RAIZ_MAJMIN="$(raiz_viva_majmin || true)" es_la_raiz_viva() { # [ -n "${RAIZ_MAJMIN:-}" ] || return 1 _er_n=$(nombre_kernel "$1") [ "$(cat "/sys/class/block/$_er_n/dev" 2>/dev/null)" = "$RAIZ_MAJMIN" ] } GUID_ESP="c12a7328-f81f-11d2-ba4b-00a0c93ec93b" GUID_MSR="e3c9e316-0b5c-4db8-817d-f92df00215ae" GUID_WINRE="de94bba4-06d1-4d40-a16a-bfd50179d6ac" GUID_MSBASIC="ebd0a0a2-b9e5-4433-87c0-68b6b72699c7" GUID_LINUX="0fc63daf-8483-4772-8e79-3d69d8477de4" GUID_LUKS="ca7d7ccb-63ed-4c53-861c-1742536059cc" GUID_SWAP="0657fd6d-a4ab-43c4-84e5-0933c84b4f4f" discos_del_sistema() { for _dd in /dev/nvme?n? /dev/sd? /dev/vd? /dev/mmcblk? /dev/loop? /dev/loop??; do [ -b "$_dd" ] || continue # Tamaño 0 = un `loop` libre sin fichero detrás. Existen por docenas y no son discos: si se # listan, el censo se llena de ruido y lo que importa deja de verse. Un guardián ruidoso se # ignora igual que uno mudo. [ "$(cat "/sys/class/block/$(basename "$(readlink -f "$_dd")")/size" 2>/dev/null || echo 0)" -gt 0 ] || continue echo "$_dd" done } mib_de_sectores() { echo $(( ${1:-0} / 2048 )); } mib_de_bytes() { echo $(( ${1:-0} / 1048576 )); } # ── sector_bytes_de — el sector LÓGICO, que no siempre es 512 ────────────────────────── # `sfdisk -d` cuenta en sectores LÓGICOS y lo declara en su cabecera (`sector-size: 512`). En un # disco 4Kn eso son 4096, y dividir por 2048 a ciegas da un tamaño **ocho veces menor**: una # partición de 40 G se muestra como 5 G y el piso de `mib_necesarios` la rechaza por chica — o sea # que el error no se queda en la pantalla, **decide**. # # ⚠ Y no se puede unificar con `/sys/class/block//size`, que es lo que usa la rama del disco sin # tabla: **ése siempre cuenta en unidades de 512**, por convención del kernel, incluso en 4Kn. # Mezclar las dos conversiones es el error que esta separación evita. sector_bytes_de() { _sb_v=$("$SFDISK" -d "$1" 2>/dev/null | sed -n 's/^sector-size:[ ]*\([0-9]*\).*/\1/p' | head -1) case "${_sb_v:-}" in ''|*[!0-9]*) echo 512 ;; *) echo "$_sb_v" ;; esac } # ── bytes_del_disco — el tamaño del APARATO, en bytes ────────────────────────────────── bytes_del_disco() { echo $(( $(cat "/sys/class/block/$(nombre_kernel "$1")/size" 2>/dev/null || echo 0) * 512 )); } # ── mib_minimos — el piso, calculado UNA vez ────────────────────────────────────────────────── # `mib_necesarios` mide el árbol y no es gratis (`du -sm` sobre el rootfs); el censo la llamaría una # vez por partición. Se cachea en una global, que es la única forma de memoizar en sh. mib_minimos() { [ -n "${MIB_MINIMOS:-}" ] || MIB_MINIMOS=$(mib_necesarios) echo "$MIB_MINIMOS" } # clasificar [mib] → VEREDICTO|ETIQUETA # El veredicto es lo que decide el menú: SOLO_ESP, NO_TOCAR, CANDIDATA, RAIZ_VIVA, EN_USO. clasificar() { _cl_guid=$(printf '%s' "${1:-}" | tr 'A-Z' 'a-z') _cl_fs="${2:-}"; _cl_mnt="${3:-}"; _cl_mib="${4:-0}" case "$_cl_guid" in "$GUID_ESP") echo "SOLO_ESP|ESP (partición de arranque EFI)"; return ;; "$GUID_MSR") echo "NO_TOCAR|reservada de Microsoft"; return ;; "$GUID_WINRE") echo "NO_TOCAR|recuperación de Windows"; return ;; "$GUID_SWAP") echo "NO_TOCAR|swap"; return ;; # «Datos básicos de Microsoft» es el tipo que Windows le pone a sus volúmenes. Aunque el # filesystem no se reconozca —puede estar cerrado por BitLocker, o simplemente vacío— no se # ofrece: el coste de equivocarse acá es el disco de otro sistema operativo. "$GUID_MSBASIC") echo "NO_TOCAR|volumen de datos de Windows (tipo GPT «basic data»)"; return ;; # ⚠ El tipo LUKS de la TABLA tiene que bastar por sí solo. Estaba definido y **no se # consultaba**: en la prueba con el layout del usuario, una partición marcada como LUKS en # GPT salió ofrecida como «se puede usar» porque `blkid` no veía cabecera dentro. Un # contenedor cifrado es justo el caso donde no se puede mirar adentro para decidir — el # único dato disponible es el tipo, así que el tipo manda. "$GUID_LUKS") echo "NO_TOCAR|VOLUMEN CIFRADO (LUKS, por tipo de la tabla)"; return ;; esac case "$_cl_fs" in crypto_LUKS) echo "NO_TOCAR|VOLUMEN CIFRADO (LUKS) — lo que hay dentro no se puede ver desde acá"; return ;; ntfs) echo "NO_TOCAR|NTFS (Windows u otro sistema)"; return ;; vfat|msdos) echo "NO_TOCAR|FAT — puede ser una ESP o datos compartidos"; return ;; swap) echo "NO_TOCAR|swap"; return ;; BitLocker|bitlocker) echo "NO_TOCAR|BitLocker"; return ;; esac if [ "$_cl_mnt" = "/" ]; then # ⚠ **La raíz viva no siempre es del usuario.** Arrancado desde un USB o una imagen, la raíz # viva es EL MEDIO — y ofrecerlo como destino es ofrecer formatear el instalador mientras # corre. El medio se sabe en el arranque (`TAKANA_MEDIO`, lo pone el `/init` mirando si el # cmdline trae `rdinit=`), no se adivina acá. if [ -n "${TAKANA_MEDIO:-}" ]; then echo "NO_TOCAR|es el MEDIO desde el que arrancaste (el instalador corre desde acá)"; return fi echo "RAIZ_VIVA|la raíz del sistema que está corriendo AHORA"; return fi if [ -n "$_cl_mnt" ]; then echo "EN_USO|montada en $_cl_mnt"; return fi # ⚠ **«No reconozco nada» NO es «está vacío», y confundirlos DESTRUYE.** Un almacén de # negación plausible (`qullqa`, tawasuyu) es por diseño un dispositivo lleno de aleatorio y sin # ninguna firma — o sea **exactamente** lo que un instalador ingenuo llama «partición vacía, se # puede usar». Formatearla no filtra información: la borra, y el dueño se entera cuando ya no # está. La contención que el editor de discos de tawasuyu ya implementó (su T16) es general y # ésta la copia: **lo que no se pudo identificar nace CRUDO, no libre**, y sólo se usa con una # confirmación fuerte. # # Y el mensaje es EL MISMO para un pozo, un disco recién borrado y un contenedor cifrado ajeno. # Eso no es pereza: un aviso que dijera «esto parece un volumen oculto» delataría exactamente lo # que el diseño existe para no delatar. Acá no se nombra nada de eso, a propósito. case "$_cl_fs" in ext2|ext3|ext4|xfs|btrfs|f2fs) _cl_q="Linux ($_cl_fs) sin montar" ;; "") _cl_min=$(mib_minimos) if [ "${_cl_mib:-0}" -lt "$_cl_min" ] 2>/dev/null; then echo "NO_TOCAR|sin nada que se pueda identificar, y son ${_cl_mib} M (hacen falta ${_cl_min} M)"; return fi echo "CRUDA|no reconozco NADA acá: puede estar vacía o en uso por algo que este instalador no sabe leer"; return ;; *) echo "NO_TOCAR|$_cl_fs (desconocido para este instalador)"; return ;; esac # ⚠ **Una candidata que no entra no es una candidata.** Sin esta puerta el censo de este mismo # hub ofrecía `/dev/sda1` —2 MiB, sin filesystem, el hueco de arranque— como «✓ se puede usar»: # el veredicto sólo miraba QUÉ hay dentro y nunca CUÁNTO cabe. Elegirla formatea algo real y # muere a mitad de la copia, que es el peor momento para enterarse. El piso es el mismo número # con el que se talla un hueco (`mib_necesarios`), así que las dos puertas no pueden divergir. _cl_min=$(mib_minimos) if [ "${_cl_mib:-0}" -lt "$_cl_min" ] 2>/dev/null; then echo "NO_TOCAR|$_cl_q, pero son ${_cl_mib} M y hacen falta al menos ${_cl_min} M"; return fi echo "CANDIDATA|$_cl_q" } # censo [--solo-candidatas] → una línea por partición, hueco o disco sin tabla: # ||||