fix(arje): build-initrd compila musl-static por default

Sin musl, PID 1 panic con "error while loading shared libraries:
libgcc_s.so.1" porque el initramfs no incluye libgcc/glibc/ld-linux.
Solución estándar: target x86_64-unknown-linux-musl produce un ELF
totalmente estático.

Cambios en scripts/build-arje-initrd.sh:
- ARJE_TARGET=x86_64-unknown-linux-musl por default (override con env).
- Chequeo del target instalado antes de buildear; mensaje accionable
  con los comandos exactos (rustup target add..., apt install
  musl-tools, etc.) si falta.
- Sanity check con `file`: aborta si ente-zero quedó dinámico.
- Sanity check para busybox: aborta si el BUSYBOX_BIN apunta a un
  binario dinámico (la otra causa #1 de panic).
- BIN_DIR ahora apunta a target/$TARGET/release/.

Docs (docs/arje-boot.md):
- §2a explica el porqué de musl.
- §2b lista requisitos del host (rustup target, musl-tools, busybox-static).
- §7 sección nueva de troubleshooting con el síntoma exacto del
  libgcc_s panic + 3 escenarios comunes más.
- Checklist pre-deploy actualizado con el chequeo de "statically linked".

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
sergio
2026-05-18 19:17:52 +00:00
parent 0e66cda079
commit ec458b8a6f
2 changed files with 231 additions and 38 deletions
+105 -22
View File
@@ -2,13 +2,18 @@
# build-arje-initrd.sh — empaqueta ente-zero + shims compat + Tarjeta Semilla
# en un initramfs CPIO+gzip listo para arrancar bajo QEMU o como /init real.
#
# Toolchain: musl-static por default. ente-zero es PID 1; cualquier
# dependencia dinámica (libgcc_s, libc.so.6, ld-linux) que no esté en el
# initramfs produce kernel panic. Compilar contra musl elimina el problema
# completamente — un solo ELF estático.
#
# Layout del initrd resultante:
# /init → wrapper sh que exec /sbin/ente-zero
# /sbin/ente-zero → PID 1
# /usr/sbin/ente-*-compat → shims systemd
# /sbin/ente-zero → PID 1 (musl-static)
# /usr/sbin/ente-*-compat → shims systemd (musl-static)
# /usr/sbin/ente-echo, ente-policy-provider
# /ente/seed.card.json → Tarjeta Semilla
# /bin/{sh,ls,cat,...} → busybox o glibc-static (depende del flag)
# /bin/{sh,ls,cat,...} → busybox-static (recomendado)
# /dev, /proc, /sys, /run → puntos de montaje (ente-zero los monta)
#
# Uso:
@@ -18,15 +23,30 @@
# out default: out/arje.initrd.cpio.gz
#
# Env:
# BUSYBOX_BIN path a un busybox-static (default: $(which busybox))
# EXTRA_BINS binarios extra a copiar, separados por espacio
# ARJE_TARGET triple de cargo (default: x86_64-unknown-linux-musl).
# Para glibc dinámica usar x86_64-unknown-linux-gnu — en
# ese caso hay que vendorear libgcc_s.so.1 + libc.so.6 +
# ld-linux-x86-64.so.2 manualmente o el kernel paniquea.
# BUSYBOX_BIN path a un busybox-static. Default: busca `busybox` en PATH.
# EXTRA_BINS binarios extra a copiar (deben ser estáticos), space-separated.
#
# Requisitos: cpio, gzip, ldd (sólo si no usás busybox-static).
# Requisitos del host:
# - rust toolchain con el target musl instalado:
# rustup target add x86_64-unknown-linux-musl
# - musl-gcc para algunos crates con build.rs C (sled, etc.):
# Debian/Ubuntu: apt install musl-tools
# Alpine: apk add musl-dev gcc
# Arch: pacman -S musl
# - cpio, gzip.
# - busybox-static (recomendado, opcional):
# Debian/Ubuntu: apt install busybox-static
# Alpine: (busybox viene built-in)
set -euo pipefail
SEED="${1:-seeds/arje-prod.card.json}"
OUT="${2:-out/arje.initrd.cpio.gz}"
TARGET="${ARJE_TARGET:-x86_64-unknown-linux-musl}"
BUSYBOX_BIN="${BUSYBOX_BIN:-$(command -v busybox 2>/dev/null || true)}"
EXTRA_BINS="${EXTRA_BINS:-}"
@@ -39,9 +59,40 @@ if [ ! -f "$SEED" ]; then
exit 2
fi
# 1. Build release de ente-zero y todos los compat shims.
echo "[build-initrd] cargo build --release de ente-zero + shims"
cargo build --release \
# Chequeo del target: si es musl, debe estar instalado. Mensaje accionable
# cuando falta — la causa #1 de fracaso al primer intento.
if [[ "$TARGET" == *-musl ]]; then
RUSTLIB="$(rustc --print sysroot 2>/dev/null)/lib/rustlib/$TARGET"
if [ ! -d "$RUSTLIB" ]; then
cat >&2 <<EOF
[build-initrd] target musl no instalado: $TARGET
Instalalo con uno de:
# Si tenés rustup:
rustup target add $TARGET
# Si tu rust viene del paquete del sistema (Debian/Ubuntu):
apt install rustup && rustup default stable && rustup target add $TARGET
Y asegurate de tener musl-gcc para los crates con build.rs C:
apt install musl-tools # Debian/Ubuntu
apk add musl-dev gcc # Alpine
pacman -S musl # Arch
Si querés saltar musl (NO recomendado para PID 1, te dará kernel panic
por libgcc_s.so.1 faltante a menos que la vendorees):
ARJE_TARGET=x86_64-unknown-linux-gnu scripts/build-arje-initrd.sh
EOF
exit 5
fi
fi
# 1. Build release de ente-zero y todos los compat shims con el target elegido.
echo "[build-initrd] cargo build --release --target $TARGET de ente-zero + shims"
cargo build --release --target "$TARGET" \
-p ente-zero \
-p ente-echo \
-p ente-logind-compat \
@@ -59,33 +110,63 @@ cargo build --release \
-p ente-binfmt-compat \
-p ente-policy-provider
# 2. Validar la seed.
BIN_DIR="target/$TARGET/release"
# 2. Validar la seed. validate.sh corre el binario del host (release plain),
# sólo verifica el schema — independiente del target.
if ! seeds/validate.sh "$SEED" >/dev/null 2>&1; then
echo "[build-initrd] seed inválida: $SEED" >&2
exit 3
fi
# 3. Stage root del initrd.
# 3. Sanity check: si compilamos musl, los ELF deben ser ESTÁTICOS.
# Si quedó alguno con interpreter dinámico, el kernel paniquea.
if [[ "$TARGET" == *-musl ]] && command -v file >/dev/null 2>&1; then
bad=0
for b in "$BIN_DIR/ente-zero" "$BIN_DIR/ente-echo"; do
[ -f "$b" ] || continue
if file "$b" | grep -q "dynamically linked"; then
echo "[build-initrd] WARN: $b quedó DYNAMIC (esperábamos static):" >&2
file "$b" >&2
bad=$((bad + 1))
fi
done
if [ "$bad" -gt 0 ]; then
echo "[build-initrd] abortando — un binario PID-1 dinámico paniquea el kernel" >&2
exit 6
fi
fi
# 4. Stage root del initrd.
STAGE="$(mktemp -d -t arje-initrd.XXXXXX)"
trap 'rm -rf "$STAGE"' EXIT
mkdir -p "$STAGE"/{bin,sbin,usr/sbin,etc,ente,proc,sys,dev,run,tmp,sys/fs/cgroup}
# 4. Copiar binarios arje.
install -m 0755 target/release/ente-zero "$STAGE/sbin/ente-zero"
# 5. Copiar binarios arje.
install -m 0755 "$BIN_DIR/ente-zero" "$STAGE/sbin/ente-zero"
for b in ente-echo ente-policy-provider \
ente-logind-compat ente-hostnamed-compat ente-timedated-compat \
ente-localed-compat ente-journald-compat ente-resolved-compat \
ente-polkit-compat ente-machined-compat ente-systemd1-compat \
ente-notify-compat ente-timer-compat ente-tmpfiles-compat \
ente-binfmt-compat; do
install -m 0755 "target/release/$b" "$STAGE/usr/sbin/$b"
install -m 0755 "$BIN_DIR/$b" "$STAGE/usr/sbin/$b"
done
# 5. Userspace mínimo (sh, ls, mount, mkdir, ...). Dos rutas:
# 6. Userspace mínimo. Con binarios musl-static no necesitamos vendorear
# libc/ld-linux. Las dos rutas:
# (a) busybox-static apuntado por $BUSYBOX_BIN → 1 binario, todo simlink.
# (b) sin busybox → copiar /bin/sh + deps con ldd (libc dinámica).
# (b) sin busybox → copiar /bin/sh + sus deps via ldd. Sólo seguro si el
# /bin/sh del host es estático (ej. dash en algunos distros NO lo es).
if [ -n "$BUSYBOX_BIN" ] && [ -x "$BUSYBOX_BIN" ]; then
echo "[build-initrd] usando busybox-static: $BUSYBOX_BIN"
# Validar que el busybox es realmente estático (causa frecuente de panic).
if command -v file >/dev/null 2>&1 && \
file "$BUSYBOX_BIN" | grep -q "dynamically linked"; then
echo "[build-initrd] ERROR: $BUSYBOX_BIN es dinámico — necesitás busybox-static" >&2
echo " apt install busybox-static # Debian/Ubuntu" >&2
exit 7
fi
install -m 0755 "$BUSYBOX_BIN" "$STAGE/bin/busybox"
( cd "$STAGE/bin" && for app in sh ls cat mount umount mkdir cp mv \
echo grep sed awk ps kill sleep insmod modprobe poweroff reboot \
@@ -93,8 +174,9 @@ if [ -n "$BUSYBOX_BIN" ] && [ -x "$BUSYBOX_BIN" ]; then
ln -sf busybox "$app"
done )
else
echo "[build-initrd] sin busybox — copiando /bin/sh + deps via ldd"
install -m 0755 /bin/sh "$STAGE/bin/sh"
echo "[build-initrd] WARN: sin busybox-static — vendoreando /bin/sh + libs via ldd"
echo " Esto es FRÁGIL. Instalá busybox-static y reintentá:"
echo " apt install busybox-static # Debian/Ubuntu"
copy_lib() {
local lib="$1"
[ -f "$lib" ] || return 0
@@ -111,10 +193,10 @@ else
done
fi
# 6. Tarjeta Semilla. Path canónico en prod: /ente/seed.card.json
# 7. Tarjeta Semilla. Path canónico en prod: /ente/seed.card.json
install -m 0644 "$SEED" "$STAGE/ente/seed.card.json"
# 7. /init wrapper. El kernel pasa control a /init; nosotros invocamos
# 8. /init wrapper. El kernel pasa control a /init; nosotros invocamos
# ente-zero como PID 1 real con su env mínimo.
cat > "$STAGE/init" <<'EOF'
#!/bin/sh
@@ -126,7 +208,7 @@ exec /sbin/ente-zero
EOF
chmod 0755 "$STAGE/init"
# 8. Binarios extra a vendorear.
# 9. Binarios extra a vendorear (deben ser estáticos).
if [ -n "$EXTRA_BINS" ]; then
for b in $EXTRA_BINS; do
[ -x "$b" ] || { echo "[build-initrd] EXTRA_BINS: $b no existe"; exit 4; }
@@ -134,7 +216,8 @@ if [ -n "$EXTRA_BINS" ]; then
done
fi
# 9. Empaquetar CPIO + gzip. Formato newc (estándar para Linux initramfs).
# 10. Empaquetar CPIO + gzip. Formato newc (estándar para Linux initramfs).
mkdir -p "$(dirname "$OUT")"
( cd "$STAGE" && find . -print0 | cpio -o -H newc --null --quiet ) | gzip -9 > "$OUT"
echo "[build-initrd] generado: $OUT ($(du -h "$OUT" | cut -f1))"
echo "[build-initrd] target: $TARGET"