#!/bin/sh
# instalador-autoextraible.sh — UN fichero que se baja y sabe hacer todo.
#
# ══ POR QUÉ ═══════════════════════════════════════════════════════════════════════════════════
# El paquete del instalador son cuatro cosas (el binario, el instalador en RAM, su kernel y el
# censo) y hasta hoy había que copiarlas juntas, con su árbol, y saber cuál correr. Eso sirve para
# quien ya sabe; no sirve para publicar. Lo que se publica tiene que ser **un fichero**: se baja, se
# le da permiso de ejecución, se corre, y él se encarga.
#
# curl -LO https://takana-os.net/descargas/takana-instalador.sh
# chmod +x takana-instalador.sh
# sudo ./takana-instalador.sh
#
# ── CÓMO ESTÁ HECHO ───────────────────────────────────────────────────────────────────────────
# Cabecera `sh` + payload `tar.gz` pegado detrás, separado por OFFSET DE BYTES y no por número de
# línea: un payload binario puede traer cualquier cosa, y `tail -n +N` sobre bytes arbitrarios es
# una apuesta. El offset se calcula midiendo la cabecera ya escrita, con un hueco de ancho fijo para
# que sustituirlo no cambie el tamaño de lo que se midió.
#
# ── LO QUE NO HACE, A PROPÓSITO ───────────────────────────────────────────────────────────────
# **No baja nada.** Todo viaja adentro: sin red, sin espejo, sin resolver un nombre. Un instalador
# de sistema que necesita internet para instalar falla justo donde más caro sale — en la máquina que
# todavía no tiene sistema. El precio son ~140 MiB, y se pagan una vez.
#
# Uso: scripts/instalador-autoextraible.sh [--paquete
] [--salida ]
set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd)"; cd "$ROOT"
PAQUETE="${PAQUETE:-work/instalador-salida}"
SALIDA="${SALIDA:-work/takana-instalador.sh}"
while [ $# -gt 0 ]; do
case "$1" in
--paquete) PAQUETE="${2:?}"; shift 2 ;;
--salida) SALIDA="${2:?}"; shift 2 ;;
-h|--help) sed -n '2,30p' "$0"; exit 0 ;;
*) echo "opción desconocida: $1" >&2; exit 2 ;;
esac
done
for f in takana takana-instalador.cpio.gz kernel/boot/bzImage takana-install-partition.sh \
lib/herramientas-desde-store.sh; do
[ -r "$PAQUETE/$f" ] || { echo "el paquete $PAQUETE no trae $f — armalo con scripts/instalador-paquete.sh" >&2; exit 1; }
done
. "$(dirname "$0")/lib/herramientas-desde-store.sh"
GZIP=$(herramienta gzip bin/gzip gzip) || exit 1
TMP="work/.autoextraible"; rm -rf "$TMP"; mkdir -p "$TMP"
# ── 1) el payload ─────────────────────────────────────────────────────────────────────────────
# `tar` del host: es el único punto donde no se usa una herramienta sellada, y es a propósito —
# lo que importa del tar es que lo pueda LEER cualquier Linux, no con qué se escribió. El formato
# es el POSIX de siempre.
echo "==> payload"
tar -C "$PAQUETE" -cf "$TMP/payload.tar" . 2>/dev/null || {
echo "no pude armar el tar del paquete (¿hay 'tar' en el PATH?)" >&2; exit 1; }
"$GZIP" -9 -c "$TMP/payload.tar" > "$TMP/payload.tar.gz"
rm -f "$TMP/payload.tar"
SHA=$(sha256sum "$TMP/payload.tar.gz" | cut -d' ' -f1)
MIB=$(( $(wc -c < "$TMP/payload.tar.gz") / 1048576 ))
echo " $MIB MiB · sha256 $(echo "$SHA" | cut -c1-16)…"
# ── 2) la cabecera ────────────────────────────────────────────────────────────────────────────
# `@@@@@@@@@@` son diez caracteres y el offset se escribe con diez dígitos: la sustitución no mueve
# ni un byte de lo que ya se midió. Si alguien cambia el ancho de uno, tiene que cambiar el del otro.
cat > "$TMP/cabecera" <&2; exit 1; }
decir() { printf '%s\n' "\$*"; }
morir() { printf '%s\n' "\$*" >&2; exit 1; }
MODO=instalar; DESTINO_EXTRAER=""
case "\${1:-}" in
--check) MODO=check; shift ;;
--survey) MODO=survey; shift ;;
--extraer) MODO=extraer; DESTINO_EXTRAER="\${2:?--extraer necesita un directorio}"; shift 2 ;;
--sha256) printf '%s payload\n' "\$SHA_ESPERADO"; exit 0 ;;
-h|--help) sed -n '2,12p' "\$AUTO" | sed 's/^# \{0,1\}//'; exit 0 ;;
esac
# ── la máquina ────────────────────────────────────────────────────────────────────────────────
# El binario y el kernel son x86-64. En otra arquitectura no hay nada que intentar, y decirlo acá
# es la diferencia entre un mensaje claro y un "cannot execute binary file" tres pasos después.
ARQ=\$(uname -m 2>/dev/null || echo desconocida)
case "\$ARQ" in
x86_64|amd64) ;;
*) morir "takana-instalador: esto es para x86-64 y esta máquina es \$ARQ. No hay nada acá que le sirva." ;;
esac
# ── dónde desempaquetar ───────────────────────────────────────────────────────────────────────
# \`/var/tmp\` ANTES que \`/tmp\`: en casi toda distro moderna \`/tmp\` es tmpfs, o sea RAM, y acá se
# desempaquetan ~\$PAYLOAD_MIB MiB que después se vuelven a cargar en memoria para el salto. Hacerlo
# en RAM lo cuenta dos veces, y en una máquina justa eso es un OOM en el peor momento.
if [ -n "\$DESTINO_EXTRAER" ]; then
DIR="\$DESTINO_EXTRAER"; mkdir -p "\$DIR"
else
BASE="\${TAKANA_TMP:-}"
[ -n "\$BASE" ] || for c in /var/tmp /tmp; do [ -d "\$c" ] && [ -w "\$c" ] && { BASE="\$c"; break; }; done
[ -n "\$BASE" ] || morir "takana-instalador: no encuentro un directorio donde escribir (probá TAKANA_TMP=/ruta)"
DIR="\$BASE/takana-instalador.\$\$"
mkdir -p "\$DIR"
# Sin \`trap\`, un fallo a mitad deja \$PAYLOAD_MIB MiB tirados con un nombre que nadie reconoce.
# Si el salto ocurre, esto no corre nunca — y es correcto: para entonces ya no hay userspace.
trap 'rm -rf "\$DIR"' EXIT INT TERM
fi
# El espacio se mide ANTES: un \`tar\` que se queda sin disco a mitad deja un árbol incompleto y el
# error que sale es del último fichero, no del disco.
LIBRE=\$(df -Pk "\$DIR" 2>/dev/null | awk 'NR==2 {print int(\$4/1024)}')
if [ -n "\${LIBRE:-}" ] && [ "\$LIBRE" -lt \$(( PAYLOAD_MIB + 64 )) ]; then
morir "takana-instalador: hacen falta ~\$(( PAYLOAD_MIB + 64 )) MiB en \$DIR y hay \$LIBRE MiB.
Probá con TAKANA_TMP=/ruta/con/espacio."
fi
# ── el payload ────────────────────────────────────────────────────────────────────────────────
decir "==> desempaquetando (\$PAYLOAD_MIB MiB) en \$DIR"
tail -c +\$(( OFFSET + 1 )) "\$AUTO" > "\$DIR/payload.tar.gz" || morir "no pude leer el payload"
# La comprobación de integridad NO se salta en silencio. Si no hay con qué hacerla, se dice: un
# chequeo ausente y uno que pasó se ven igual en pantalla si nadie lo cuenta.
SUMA=""
for h in sha256sum "shasum -a 256"; do
if command -v "\${h%% *}" >/dev/null 2>&1; then
SUMA=\$(\$h "\$DIR/payload.tar.gz" 2>/dev/null | cut -d' ' -f1); break
fi
done
if [ -z "\$SUMA" ] && command -v openssl >/dev/null 2>&1; then
SUMA=\$(openssl dgst -sha256 "\$DIR/payload.tar.gz" 2>/dev/null | sed 's/.*= *//')
fi
if [ -z "\$SUMA" ]; then
decir " ⚠ esta máquina no tiene sha256sum, shasum ni openssl: el payload NO se verificó."
elif [ "\$SUMA" != "\$SHA_ESPERADO" ]; then
morir "takana-instalador: el payload no coincide con su sha256.
esperado \$SHA_ESPERADO
leído \$SUMA
La descarga se cortó, o el fichero no es el que se publicó. No sigo."
else
decir " ✓ payload verificado (sha256)"
fi
gzip -d -c "\$DIR/payload.tar.gz" | tar -x -C "\$DIR" || morir "no pude desempaquetar (¿hay gzip y tar?)"
rm -f "\$DIR/payload.tar.gz"
chmod +x "\$DIR/takana" 2>/dev/null || true
if [ "\$MODO" = extraer ]; then
trap - EXIT INT TERM
decir "✓ desempaquetado en \$DIR"
decir " el instalador es: sudo \$DIR/takana metal install"
exit 0
fi
TK="\$DIR/takana"
[ -x "\$TK" ] || morir "el payload no trae un 'takana' ejecutable"
# ⚠ **Sin \`exec\`, y no es un detalle de estilo: \`exec\` se lleva puesto el \`trap\`.** Estas dos
# ramas eran \`exec "\$TK" metal …\` y dejaban los \$PAYLOAD_MIB MiB desempaquetados tirados en el
# disco DESPUÉS DE CADA MIRADA — y \`--check\`/\`--survey\` son justamente los que alguien corre
# varias veces antes de decidirse. Medido acá el 2026-09-23: 141 MiB en /tmp tras un \`--check\`.
# En una máquina con la raíz chica eso no se degrada: se llena y la tira.
case "\$MODO" in
check) set +e; "\$TK" metal check; RC=\$?; set -e; exit "\$RC" ;;
survey) set +e; "\$TK" metal survey; RC=\$?; set -e; exit "\$RC" ;;
esac
# ── el camino completo ────────────────────────────────────────────────────────────────────────
# check informa, survey enseña y install pregunta. Los tres por separado y en ese orden, porque es
# el orden en el que una persona necesita saber las cosas: si se puede, qué hay, y recién entonces
# qué se va a hacer.
decir ""
if ! "\$TK" metal check; then
decir ""
decir "Con la imagen USB se instala igual: el instalador que viaja acá adentro es el mismo."
exit 1
fi
decir ""
"\$TK" metal survey || true
decir ""
# Sin \`exec\`: el \`trap\` de limpieza tiene que seguir vivo si el salto NO ocurre.
"\$TK" metal install "\$@"
HDREOF
# `exit 0` cierra la cabecera: sin él, el shell seguiría leyendo el payload como si fuera guion.
printf '%s\n' 'exit 0' >> "$TMP/cabecera"
# ── 3) el offset, medido sobre la cabecera ya escrita ─────────────────────────────────────────
LARGO=$(wc -c < "$TMP/cabecera")
sed "s/@@@@@@@@@@/$(printf '%010d' "$LARGO")/" "$TMP/cabecera" > "$TMP/cabecera.final"
LARGO2=$(wc -c < "$TMP/cabecera.final")
[ "$LARGO" = "$LARGO2" ] || { echo "la sustitución del offset cambió el tamaño ($LARGO → $LARGO2)" >&2; exit 1; }
mkdir -p "$(dirname "$SALIDA")"
cat "$TMP/cabecera.final" "$TMP/payload.tar.gz" > "$SALIDA"
chmod 0755 "$SALIDA"
rm -rf "$TMP"
# ── 4) el control: que se desempaquete de verdad ──────────────────────────────────────────────
# Un autoextraíble que no se probó es un fichero de 140 MiB que nadie sabe si abre. Se extrae acá
# mismo y se compara con el paquete de origen, fichero por fichero.
echo "==> control: desempaquetar y comparar"
V="work/.autoextraible-verif"; rm -rf "$V"
"./$SALIDA" --extraer "$V" >/dev/null || { echo "el autoextraíble no se desempaqueta" >&2; exit 1; }
FALLOS=0
for f in takana takana-instalador.cpio.gz kernel/boot/bzImage takana-install-partition.sh; do
a=$(sha256sum "$PAQUETE/$f" | cut -d' ' -f1)
b=$(sha256sum "$V/$f" 2>/dev/null | cut -d' ' -f1)
if [ "$a" = "$b" ]; then echo " ✓ $f"; else echo " ✗ $f NO coincide" >&2; FALLOS=$((FALLOS+1)); fi
done
rm -rf "$V"
[ "$FALLOS" = 0 ] || exit 1
echo
echo "✓ $SALIDA ($(( $(wc -c < "$SALIDA") / 1048576 )) MiB)"
echo " sha256: $(sha256sum "$SALIDA" | cut -d' ' -f1)"
echo
echo " curl -LO https://takana-os.net/descargas/takana-instalador.sh"
echo " chmod +x takana-instalador.sh && sudo ./takana-instalador.sh"