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…

⎇ f1e6628312689e3fa33cce0e04c3809f8424a3e7 scripts/lib/adoptar.sh Shell · 187 siq'ikuna · 11.2 KB Ñawpa willay ch'uya

feat(instalador): la etiqueta sin e2fsprogs, que es lo que le faltaba a adoptar para arrancar f1e662831 · sergio ·

1#!/bin/sh
2# lib/adoptar.sh — adoptar una partición en vez de formatearla.
3#
4# Vive aparte de `takana-install-partition.sh` por una razón práctica: así se puede **certificar sin
5# root**. El instalador entero necesita losetup, mount y un disco; estas funciones, sobre un
6# directorio, se prueban enteras en un segundo (`scripts/adoptar-test.sh`). Un mecanismo que sólo se
7# puede probar con el hardware delante es un mecanismo que no se prueba.
8#
9# Lo sourcea el instalador; usa `$DEST` y las funciones `es_bloque` de quien la carga.
10
11# ── ADOPTAR LA PARTICIÓN (`--adopt`) — ni formatear, ni convivir ──────────────────────────────
12#
13# El caso más común de todos: **una sola partición, con un Linux adentro y el `/home` adentro de esa
14# misma partición**. De las tres salidas posibles sólo una sirve:
15#
16#   · formatear               → se pierde el /home, que es lo único irreponible de una máquina.
17#   · instalar encima          → quedan DOS juegos de /etc y de paquetes, y nada que diga cuál manda.
18#                                No es una opción; no se ofrece.
19#   · **adoptar**              → el sistema viejo se muda a `/sistema-anterior` y el /home se queda.
20#
21# Lo que la hace barata: mover dentro del mismo filesystem es un `rename(2)` — instantáneo y sin
22# copiar un byte. Y deja algo MEJOR que un respaldo: el sistema anterior queda entero y accesible,
23# se le puede sacar un archivo o volver a él. Formatear no deja nada.
24#
25# Las guardas, cada una por lo que pasaría sin ella:
26#   1. El espacio se comprueba **antes de la primera mudanza**, con el tamaño real del árbol nuevo.
27#      Descubrirlo a mitad deja un sistema partido en dos sitios y ninguno arrancable.
28#   2. Un `mv` por entrada de primer nivel, cada uno atómico: un corte de luz deja un árbol a
29#      medias pero ningún archivo roto, y el estado se puede leer.
30#   3. Queda una marca con la lista de lo que se movió y cuándo.
31#   4. **`/home` no se mueve.** Es el punto entero.
32#   5. `lost+found` tampoco, y si ya hay un `/sistema-anterior` se numera en vez de pisarlo.
33#   6. Si lo que hay adentro no tiene forma de sistema, no se adopta: se dice, y se usa el camino
34#      de siempre. Mover cuatro directorios de datos a `/sistema-anterior` no le sirve a nadie.
35
36# adopt_montar <modo> — monta el destino en /run/takana-adopt. Si ya estaba montado, lo usa donde
37# está y NO lo desmonta después: un `umount` de un montaje que no hicimos puede propagarse.
38adopt_montar() {
39    _am_modo="$1"
40    # Un DIRECTORIO como destino es el modo de prueba: ya está «montado», se usa tal cual. Es lo que
41    # permite certificar la mudanza entera sin losetup, sin mount y sin root — y lo que hace que
42    # este mecanismo tenga pruebas en vez de tener sólo un banco que casi nadie corre.
43    if [ -d "$DEST" ]; then ADOPT_DIR="$DEST"; ADOPT_MONTE_YO=""; return 0; fi
44    ADOPT_DIR=$(awk -v d="$(readlink -f "$DEST")" '$1==d {print $2; exit}' /proc/mounts 2>/dev/null || true)
45    if [ -n "$ADOPT_DIR" ]; then
46        ADOPT_MONTE_YO=""
47        return 0
48    fi
49    ADOPT_DIR="/run/takana-adopt"
50    mkdir -p "$ADOPT_DIR"
51    # Un fichero de prueba se monta por loop; un dispositivo, directo. El modo de prueba sin
52    # hardware es el que hace que esto se pueda certificar sin un disco de verdad.
53    if es_bloque "$DEST"; then
54        mount -o "$_am_modo" "$DEST" "$ADOPT_DIR" 2>/dev/null
55    else
56        mount -o "loop,$_am_modo" "$DEST" "$ADOPT_DIR" 2>/dev/null
57    fi || {
58        echo "takana-install-partition: --adopt no puede montar $DEST (probé $_am_modo)." >&2
59        echo "                          Sin montarlo no hay nada que adoptar, y NADA se tocó." >&2; exit 1; }
60    ADOPT_MONTE_YO=1
61}
62
63adopt_desmontar() {
64    [ -n "$ADOPT_MONTE_YO" ] || return 0
65    umount "$ADOPT_DIR" 2>/dev/null || echo "    ⚠ no pude desmontar $ADOPT_DIR." >&2
66    ADOPT_MONTE_YO=""
67}
68
69# adopt_validar — corre ANTES del plan y ANTES de tocar nada: mira qué hay, elige el subdirectorio y
70# cosecha los usuarios, para que el plan pueda decirlo todo con el disco todavía intacto.
71ADOPT_USUARIOS="${TMPDIR:-/tmp}/takana-adopt-usuarios.$$"
72adopt_validar() {
73    [ -z "$USE_FREE" ] || {
74        echo "takana-install-partition: --adopt y --use-free-space se excluyen." >&2
75        echo "                          En un hueco sin asignar no hay ningún sistema que adoptar." >&2; exit 2; }
76    adopt_montar ro,nodev,nosuid,noexec
77    # 1) ¿Tiene forma de sistema? Por la FORMA del árbol y no por un archivo de una distribución
78    #    concreta: esto tiene que servir en la máquina de cualquiera, no en la de quien lo escribe.
79    _av_etc=""; [ -d "$ADOPT_DIR/etc" ] && _av_etc=1
80    _av_prog=""
81    for _av_d in usr bin sbin; do [ -d "$ADOPT_DIR/$_av_d" ] && _av_prog=1; done
82    [ -n "$_av_etc" ] && [ -n "$_av_prog" ] || {
83        echo "takana-install-partition: en $DEST no hay un sistema que adoptar." >&2
84        echo "                          Vi: $(ls -A "$ADOPT_DIR" 2>/dev/null | tr '\n' ' ' | cut -c1-200)" >&2
85        echo "                          Para instalar acá, hacelo SIN --adopt: se formatea." >&2
86        adopt_desmontar; exit 1; }
87    # 2) El subdirectorio, sin pisar uno anterior. Que se numere y no se pise es lo que hace que
88    #    adoptar dos veces no borre la primera mudanza.
89    ADOPT_SUB="sistema-anterior"
90    if [ -e "$ADOPT_DIR/$ADOPT_SUB" ]; then
91        _av_n=2
92        while [ -e "$ADOPT_DIR/sistema-anterior-$_av_n" ] && [ "$_av_n" -lt 10 ]; do _av_n=$((_av_n + 1)); done
93        [ "$_av_n" -lt 10 ] || {
94            echo "takana-install-partition: ya hay nueve /sistema-anterior en $DEST. No sigo." >&2
95            adopt_desmontar; exit 1; }
96        ADOPT_SUB="sistema-anterior-$_av_n"
97        echo "    (ya había un /sistema-anterior: el de ahora va a $ADOPT_SUB)"
98    fi
99    # 3) Qué se mueve, y qué no. `/home` y `lost+found` se quedan; lo demás viaja.
100    ADOPT_MUEVE=""
101    for _av_e in "$ADOPT_DIR"/* "$ADOPT_DIR"/.*; do
102        [ -e "$_av_e" ] || continue
103        _av_b=$(basename "$_av_e")
104        case "$_av_b" in
105            .|..|home|lost+found|sistema-anterior|sistema-anterior-[0-9]) continue ;;
106        esac
107        ADOPT_MUEVE="$ADOPT_MUEVE $_av_b"
108    done
109    # 4) Los usuarios del /home que está adentro: el mismo criterio que --keep.
110    : > "$ADOPT_USUARIOS"
111    if [ -d "$ADOPT_DIR/home" ]; then cosechar_usuarios "$ADOPT_DIR/home" "$ADOPT_USUARIOS"; fi
112    adopt_desmontar
113}
114
115# adopt_mudar <árbol> — la mudanza de verdad. Corre DESPUÉS de armar el stage, porque hasta que el
116# árbol no existe no se sabe cuánto ocupa, y el espacio es la guarda que no se puede hacer a ojo.
117adopt_mudar() {
118    _ad_t="$1"
119    adopt_montar rw
120    # 1) EL ESPACIO, antes de mover nada. Con el tamaño real del árbol más un margen: un filesystem
121    #    al borde del lleno falla en el último fichero, que es cuando ya no hay vuelta atrás.
122    _ad_necesita=$(du -sk "$_ad_t" 2>/dev/null | awk '{print $1}' || echo "")
123    _ad_libre=$(df -k "$ADOPT_DIR" 2>/dev/null | awk 'NR==2 {print $4}' || echo "")
124    case "${_ad_necesita:-x}${_ad_libre:-x}" in
125        *x*) echo "takana-install-partition: no pude medir el espacio de $DEST (du/df). No sigo." >&2
126             adopt_desmontar; exit 1 ;;
127    esac
128    _ad_con_margen=$(( _ad_necesita + _ad_necesita / 10 + 262144 ))
129    [ "$_ad_libre" -ge "$_ad_con_margen" ] || {
130        echo "takana-install-partition: no entra. El sistema nuevo ocupa $((_ad_necesita / 1024)) MiB" >&2
131        echo "                          y en $DEST quedan $((_ad_libre / 1024)) MiB libres (hacen falta" >&2
132        echo "                          $((_ad_con_margen / 1024)) MiB con margen)." >&2
133        echo "                          NADA se movió: el disco está como estaba." >&2
134        adopt_desmontar; exit 1; }
135    echo "    espacio: ocupa $((_ad_necesita / 1024)) MiB · libres $((_ad_libre / 1024)) MiB ✓"
136    # 2) La marca, ANTES de mover: si se corta la luz a la mitad, lo que quedó a medias se puede
137    #    leer. Una marca escrita al final sólo documenta los casos que salieron bien.
138    mkdir -p "$ADOPT_DIR/$ADOPT_SUB"
139    {
140        echo "# El sistema que había en esta partición, movido acá por el instalador de takana."
141        echo "# Está ENTERO: no se copió ni se borró nada, sólo cambiaron de directorio."
142        echo "# Para volver a usarlo, sus archivos están donde siempre, un nivel más adentro."
143        echo "cuando=$(date -u '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null || echo desconocido)"
144        echo "particion=$DEST"
145        echo "se-quedo-donde-estaba=/home"
146        echo "movido=$ADOPT_MUEVE"
147    } > "$ADOPT_DIR/$ADOPT_SUB/MUDANZA.txt"
148    # 3) Un `mv` por entrada, cada uno atómico. Si uno falla se para ahí y se dice cuál: seguir
149    #    moviendo después de un fallo reparte el sistema viejo entre dos sitios.
150    for _ad_e in $ADOPT_MUEVE; do
151        mv "$ADOPT_DIR/$_ad_e" "$ADOPT_DIR/$ADOPT_SUB/$_ad_e" || {
152            echo "takana-install-partition: no pude mover «$_ad_e» a $ADOPT_SUB. Paro acá." >&2
153            echo "                          Lo que ya se movió está en $ADOPT_SUB y se puede volver a sacar." >&2
154            adopt_desmontar; exit 1; }
155    done
156    echo "    ✓ el sistema anterior está en /$ADOPT_SUB, entero ($(echo $ADOPT_MUEVE | wc -w) entradas)"
157    # 4) El sistema nuevo, encima. `/home` del árbol viene vacío, así que no pisa el que ya está.
158    cp -a "$_ad_t/." "$ADOPT_DIR/" || {
159        echo "takana-install-partition: falló la copia del sistema nuevo." >&2
160        echo "                          El anterior sigue entero en /$ADOPT_SUB." >&2
161        adopt_desmontar; exit 1; }
162    adopt_desmontar
163    # 5) La etiqueta, que es por donde el arranque encuentra la raíz. Sin esto la partición
164    #    adoptada tiene el sistema nuevo adentro y nadie la reconoce al bootear.
165    # El initramfs de pivote encuentra la raíz con `findfs LABEL=hammer-root`: sin la etiqueta, el
166    # sistema está adentro y **nadie lo encuentra al arrancar**. Tres caminos, en orden de
167    # preferencia, porque ninguno está siempre:
168    if [ -d "$DEST" ]; then
169        return 0   # modo de prueba: un directorio no tiene etiqueta de filesystem que poner
170    elif [ -n "${E2LABEL:-}" ] && [ -x "$E2LABEL" ]; then
171        "$E2LABEL" "$DEST" hammer-root || {
172            echo "takana-install-partition: no pude ponerle LABEL=hammer-root a $DEST." >&2; exit 1; }
173    elif command -v takana >/dev/null 2>&1; then
174        # Dentro del initramfs no viaja e2fsprogs —busybox no trae `e2label` ni `tune2fs`— pero el
175        # binario `takana` sí. Escribe los 16 bytes de la etiqueta y recalcula el crc32c del
176        # superbloque si el filesystem trae `metadata_csum`; sin ese recálculo el kernel lo monta
177        # de sólo lectura, y el síntoma no menciona la etiqueta por ningún lado.
178        takana fs-label "$DEST" hammer-root || {
179            echo "takana-install-partition: no pude ponerle LABEL=hammer-root a $DEST." >&2; exit 1; }
180    else
181        echo "takana-install-partition: para --adopt hace falta e2label o el binario takana, y no" >&2
182        echo "                          encontré ninguno. El sistema anterior sigue entero en" >&2
183        echo "                          /$ADOPT_SUB y el nuevo está copiado: falta sólo la etiqueta." >&2
184        exit 1
185    fi
186}
187