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…
feat(instalador): la etiqueta sin e2fsprogs, que es lo que le faltaba a adoptar para arrancar
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