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…

TOML 1572Shell 203Rust 109Markdown 993728 Sutichaykuna

⎇ main scripts/instalador-autoextraible.sh Shell · 239 siq'ikuna · 13.3 KB Ñawpa willay ch'uya

el instalador se llama .sh, y las siete descargas son enlaces con su nombre baaf39bd9 · Sergio ·

1#!/bin/sh
2# instalador-autoextraible.sh — UN fichero que se baja y sabe hacer todo.
3#
4# ══ POR QUÉ ═══════════════════════════════════════════════════════════════════════════════════
5# El paquete del instalador son cuatro cosas (el binario, el instalador en RAM, su kernel y el
6# censo) y hasta hoy había que copiarlas juntas, con su árbol, y saber cuál correr. Eso sirve para
7# quien ya sabe; no sirve para publicar. Lo que se publica tiene que ser **un fichero**: se baja, se
8# le da permiso de ejecución, se corre, y él se encarga.
9#
10#     curl -LO https://takana-os.net/descargas/takana-instalador.sh
11#     chmod +x takana-instalador.sh
12#     sudo ./takana-instalador.sh
13#
14# ── CÓMO ESTÁ HECHO ───────────────────────────────────────────────────────────────────────────
15# Cabecera `sh` + payload `tar.gz` pegado detrás, separado por OFFSET DE BYTES y no por número de
16# línea: un payload binario puede traer cualquier cosa, y `tail -n +N` sobre bytes arbitrarios es
17# una apuesta. El offset se calcula midiendo la cabecera ya escrita, con un hueco de ancho fijo para
18# que sustituirlo no cambie el tamaño de lo que se midió.
19#
20# ── LO QUE NO HACE, A PROPÓSITO ───────────────────────────────────────────────────────────────
21# **No baja nada.** Todo viaja adentro: sin red, sin espejo, sin resolver un nombre. Un instalador
22# de sistema que necesita internet para instalar falla justo donde más caro sale — en la máquina que
23# todavía no tiene sistema. El precio son ~140 MiB, y se pagan una vez.
24#
25# Uso:  scripts/instalador-autoextraible.sh [--paquete <dir>] [--salida <fichero>]
26set -eu
27ROOT="$(cd "$(dirname "$0")/.." && pwd)"; cd "$ROOT"
28PAQUETE="${PAQUETE:-work/instalador-salida}"
29SALIDA="${SALIDA:-work/takana-instalador.sh}"
30while [ $# -gt 0 ]; do
31    case "$1" in
32        --paquete) PAQUETE="${2:?}"; shift 2 ;;
33        --salida)  SALIDA="${2:?}";  shift 2 ;;
34        -h|--help) sed -n '2,30p' "$0"; exit 0 ;;
35        *) echo "opción desconocida: $1" >&2; exit 2 ;;
36    esac
37done
38
39for f in takana takana-instalador.cpio.gz kernel/boot/bzImage takana-install-partition.sh \
40         lib/herramientas-desde-store.sh; do
41    [ -r "$PAQUETE/$f" ] || { echo "el paquete $PAQUETE no trae $f — armalo con scripts/instalador-paquete.sh" >&2; exit 1; }
42done
43
44. "$(dirname "$0")/lib/herramientas-desde-store.sh"
45GZIP=$(herramienta gzip bin/gzip gzip) || exit 1
46
47TMP="work/.autoextraible"; rm -rf "$TMP"; mkdir -p "$TMP"
48
49# ── 1) el payload ─────────────────────────────────────────────────────────────────────────────
50# `tar` del host: es el único punto donde no se usa una herramienta sellada, y es a propósito —
51# lo que importa del tar es que lo pueda LEER cualquier Linux, no con qué se escribió. El formato
52# es el POSIX de siempre.
53echo "==> payload"
54tar -C "$PAQUETE" -cf "$TMP/payload.tar" . 2>/dev/null || {
55    echo "no pude armar el tar del paquete (¿hay 'tar' en el PATH?)" >&2; exit 1; }
56"$GZIP" -9 -c "$TMP/payload.tar" > "$TMP/payload.tar.gz"
57rm -f "$TMP/payload.tar"
58SHA=$(sha256sum "$TMP/payload.tar.gz" | cut -d' ' -f1)
59MIB=$(( $(wc -c < "$TMP/payload.tar.gz") / 1048576 ))
60echo "    $MIB MiB · sha256 $(echo "$SHA" | cut -c1-16)…"
61
62# ── 2) la cabecera ────────────────────────────────────────────────────────────────────────────
63# `@@@@@@@@@@` son diez caracteres y el offset se escribe con diez dígitos: la sustitución no mueve
64# ni un byte de lo que ya se midió. Si alguien cambia el ancho de uno, tiene que cambiar el del otro.
65cat > "$TMP/cabecera" <<HDREOF
66#!/bin/sh
67# takana — instalador. UN fichero: adentro viene todo y no baja nada de internet.
68#
69#   sudo ./takana-instalador.sh              mirar, preguntar e instalar
70#   sudo ./takana-instalador.sh --check      ¿se puede sin USB? No toca nada.
71#   sudo ./takana-instalador.sh --survey     ¿qué hay en los discos? No toca nada.
72#        ./takana-instalador.sh --extraer D  sólo desempaquetar en D, sin correr nada
73#        ./takana-instalador.sh --sha256     el sha256 del payload que trae adentro
74#
75# Lo que hay adentro: el programa \`takana\` (ELF estático, corre en cualquier Linux x86-64), el
76# instalador que corre en RAM, su kernel, y el censo del terreno.
77set -eu
78OFFSET=@@@@@@@@@@
79SHA_ESPERADO=$SHA
80PAYLOAD_MIB=$MIB
81
82# ⚠ **Los ceros a la izquierda son OCTAL en \`\$(( ))\`.** El offset se escribe con ancho fijo para
83# que sustituirlo no mueva la cabecera, y eso obliga a rellenar con ceros; pero \`\$(( 0000001234 + 1 ))\`
84# en sh es octal, o sea 668, y el payload se lee desde el byte equivocado. El síntoma es «el payload
85# no coincide con su sha256» — un mensaje de integridad para un bug de aritmética. Medido acá el
86# 2026-09-23 en la primera corrida. Se limpian los ceros ANTES de cualquier cuenta.
87OFFSET=\$(printf '%s' "\$OFFSET" | sed 's/^0*//')
88
89AUTO="\$0"
90[ -r "\$AUTO" ] || { echo "no puedo leerme a mí mismo (\$AUTO)" >&2; exit 1; }
91
92decir() { printf '%s\n' "\$*"; }
93morir() { printf '%s\n' "\$*" >&2; exit 1; }
94
95MODO=instalar; DESTINO_EXTRAER=""
96case "\${1:-}" in
97    --check)   MODO=check;   shift ;;
98    --survey)  MODO=survey;  shift ;;
99    --extraer) MODO=extraer; DESTINO_EXTRAER="\${2:?--extraer necesita un directorio}"; shift 2 ;;
100    --sha256)  printf '%s  payload\n' "\$SHA_ESPERADO"; exit 0 ;;
101    -h|--help) sed -n '2,12p' "\$AUTO" | sed 's/^# \{0,1\}//'; exit 0 ;;
102esac
103
104# ── la máquina ────────────────────────────────────────────────────────────────────────────────
105# El binario y el kernel son x86-64. En otra arquitectura no hay nada que intentar, y decirlo acá
106# es la diferencia entre un mensaje claro y un "cannot execute binary file" tres pasos después.
107ARQ=\$(uname -m 2>/dev/null || echo desconocida)
108case "\$ARQ" in
109    x86_64|amd64) ;;
110    *) morir "takana-instalador: esto es para x86-64 y esta máquina es \$ARQ. No hay nada acá que le sirva." ;;
111esac
112
113# ── dónde desempaquetar ───────────────────────────────────────────────────────────────────────
114# \`/var/tmp\` ANTES que \`/tmp\`: en casi toda distro moderna \`/tmp\` es tmpfs, o sea RAM, y acá se
115# desempaquetan ~\$PAYLOAD_MIB MiB que después se vuelven a cargar en memoria para el salto. Hacerlo
116# en RAM lo cuenta dos veces, y en una máquina justa eso es un OOM en el peor momento.
117if [ -n "\$DESTINO_EXTRAER" ]; then
118    DIR="\$DESTINO_EXTRAER"; mkdir -p "\$DIR"
119else
120    BASE="\${TAKANA_TMP:-}"
121    [ -n "\$BASE" ] || for c in /var/tmp /tmp; do [ -d "\$c" ] && [ -w "\$c" ] && { BASE="\$c"; break; }; done
122    [ -n "\$BASE" ] || morir "takana-instalador: no encuentro un directorio donde escribir (probá TAKANA_TMP=/ruta)"
123    DIR="\$BASE/takana-instalador.\$\$"
124    mkdir -p "\$DIR"
125    # Sin \`trap\`, un fallo a mitad deja \$PAYLOAD_MIB MiB tirados con un nombre que nadie reconoce.
126    # Si el salto ocurre, esto no corre nunca — y es correcto: para entonces ya no hay userspace.
127    trap 'rm -rf "\$DIR"' EXIT INT TERM
128fi
129
130# El espacio se mide ANTES: un \`tar\` que se queda sin disco a mitad deja un árbol incompleto y el
131# error que sale es del último fichero, no del disco.
132LIBRE=\$(df -Pk "\$DIR" 2>/dev/null | awk 'NR==2 {print int(\$4/1024)}')
133if [ -n "\${LIBRE:-}" ] && [ "\$LIBRE" -lt \$(( PAYLOAD_MIB + 64 )) ]; then
134    morir "takana-instalador: hacen falta ~\$(( PAYLOAD_MIB + 64 )) MiB en \$DIR y hay \$LIBRE MiB.
135                    Probá con TAKANA_TMP=/ruta/con/espacio."
136fi
137
138# ── el payload ────────────────────────────────────────────────────────────────────────────────
139decir "==> desempaquetando (\$PAYLOAD_MIB MiB) en \$DIR"
140tail -c +\$(( OFFSET + 1 )) "\$AUTO" > "\$DIR/payload.tar.gz" || morir "no pude leer el payload"
141
142# La comprobación de integridad NO se salta en silencio. Si no hay con qué hacerla, se dice: un
143# chequeo ausente y uno que pasó se ven igual en pantalla si nadie lo cuenta.
144SUMA=""
145for h in sha256sum "shasum -a 256"; do
146    if command -v "\${h%% *}" >/dev/null 2>&1; then
147        SUMA=\$(\$h "\$DIR/payload.tar.gz" 2>/dev/null | cut -d' ' -f1); break
148    fi
149done
150if [ -z "\$SUMA" ] && command -v openssl >/dev/null 2>&1; then
151    SUMA=\$(openssl dgst -sha256 "\$DIR/payload.tar.gz" 2>/dev/null | sed 's/.*= *//')
152fi
153if [ -z "\$SUMA" ]; then
154    decir "    ⚠ esta máquina no tiene sha256sum, shasum ni openssl: el payload NO se verificó."
155elif [ "\$SUMA" != "\$SHA_ESPERADO" ]; then
156    morir "takana-instalador: el payload no coincide con su sha256.
157                    esperado \$SHA_ESPERADO
158                    leído    \$SUMA
159                    La descarga se cortó, o el fichero no es el que se publicó. No sigo."
160else
161    decir "    ✓ payload verificado (sha256)"
162fi
163
164gzip -d -c "\$DIR/payload.tar.gz" | tar -x -C "\$DIR" || morir "no pude desempaquetar (¿hay gzip y tar?)"
165rm -f "\$DIR/payload.tar.gz"
166chmod +x "\$DIR/takana" 2>/dev/null || true
167
168if [ "\$MODO" = extraer ]; then
169    trap - EXIT INT TERM
170    decir "✓ desempaquetado en \$DIR"
171    decir "  el instalador es:  sudo \$DIR/takana metal install"
172    exit 0
173fi
174
175TK="\$DIR/takana"
176[ -x "\$TK" ] || morir "el payload no trae un 'takana' ejecutable"
177
178# ⚠ **Sin \`exec\`, y no es un detalle de estilo: \`exec\` se lleva puesto el \`trap\`.** Estas dos
179# ramas eran \`exec "\$TK" metal …\` y dejaban los \$PAYLOAD_MIB MiB desempaquetados tirados en el
180# disco DESPUÉS DE CADA MIRADA — y \`--check\`/\`--survey\` son justamente los que alguien corre
181# varias veces antes de decidirse. Medido acá el 2026-09-23: 141 MiB en /tmp tras un \`--check\`.
182# En una máquina con la raíz chica eso no se degrada: se llena y la tira.
183case "\$MODO" in
184    check)  set +e; "\$TK" metal check;  RC=\$?; set -e; exit "\$RC" ;;
185    survey) set +e; "\$TK" metal survey; RC=\$?; set -e; exit "\$RC" ;;
186esac
187
188# ── el camino completo ────────────────────────────────────────────────────────────────────────
189# check informa, survey enseña y install pregunta. Los tres por separado y en ese orden, porque es
190# el orden en el que una persona necesita saber las cosas: si se puede, qué hay, y recién entonces
191# qué se va a hacer.
192decir ""
193if ! "\$TK" metal check; then
194    decir ""
195    decir "Con la imagen USB se instala igual: el instalador que viaja acá adentro es el mismo."
196    exit 1
197fi
198decir ""
199"\$TK" metal survey || true
200decir ""
201# Sin \`exec\`: el \`trap\` de limpieza tiene que seguir vivo si el salto NO ocurre.
202"\$TK" metal install "\$@"
203HDREOF
204
205# `exit 0` cierra la cabecera: sin él, el shell seguiría leyendo el payload como si fuera guion.
206printf '%s\n' 'exit 0' >> "$TMP/cabecera"
207
208# ── 3) el offset, medido sobre la cabecera ya escrita ─────────────────────────────────────────
209LARGO=$(wc -c < "$TMP/cabecera")
210sed "s/@@@@@@@@@@/$(printf '%010d' "$LARGO")/" "$TMP/cabecera" > "$TMP/cabecera.final"
211LARGO2=$(wc -c < "$TMP/cabecera.final")
212[ "$LARGO" = "$LARGO2" ] || { echo "la sustitución del offset cambió el tamaño ($LARGO → $LARGO2)" >&2; exit 1; }
213
214mkdir -p "$(dirname "$SALIDA")"
215cat "$TMP/cabecera.final" "$TMP/payload.tar.gz" > "$SALIDA"
216chmod 0755 "$SALIDA"
217rm -rf "$TMP"
218
219# ── 4) el control: que se desempaquete de verdad ──────────────────────────────────────────────
220# Un autoextraíble que no se probó es un fichero de 140 MiB que nadie sabe si abre. Se extrae acá
221# mismo y se compara con el paquete de origen, fichero por fichero.
222echo "==> control: desempaquetar y comparar"
223V="work/.autoextraible-verif"; rm -rf "$V"
224"./$SALIDA" --extraer "$V" >/dev/null || { echo "el autoextraíble no se desempaqueta" >&2; exit 1; }
225FALLOS=0
226for f in takana takana-instalador.cpio.gz kernel/boot/bzImage takana-install-partition.sh; do
227    a=$(sha256sum "$PAQUETE/$f" | cut -d' ' -f1)
228    b=$(sha256sum "$V/$f" 2>/dev/null | cut -d' ' -f1)
229    if [ "$a" = "$b" ]; then echo "    ✓ $f"; else echo "    ✗ $f NO coincide" >&2; FALLOS=$((FALLOS+1)); fi
230done
231rm -rf "$V"
232[ "$FALLOS" = 0 ] || exit 1
233
234echo
235echo "✓ $SALIDA  ($(( $(wc -c < "$SALIDA") / 1048576 )) MiB)"
236echo "  sha256: $(sha256sum "$SALIDA" | cut -d' ' -f1)"
237echo
238echo "  curl -LO https://takana-os.net/descargas/takana-instalador.sh"
239echo "  chmod +x takana-instalador.sh && sudo ./takana-instalador.sh"