From ec458b8a6f5930da8330191146b04add0be6a570 Mon Sep 17 00:00:00 2001 From: sergio Date: Mon, 18 May 2026 19:17:52 +0000 Subject: [PATCH] fix(arje): build-initrd compila musl-static por default MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- docs/arje-boot.md | 142 +++++++++++++++++++++++++++++++---- scripts/build-arje-initrd.sh | 127 +++++++++++++++++++++++++------ 2 files changed, 231 insertions(+), 38 deletions(-) diff --git a/docs/arje-boot.md b/docs/arje-boot.md index 023a3a3..2d3f301 100644 --- a/docs/arje-boot.md +++ b/docs/arje-boot.md @@ -22,12 +22,12 @@ El initrd es un **CPIO + gzip** (formato `newc`, estándar Linux). Su raíz: ``` /init wrapper sh, ejecuta /sbin/ente-zero -/sbin/ente-zero PID 1 +/sbin/ente-zero PID 1 (musl-static por default) /usr/sbin/ente-echo /usr/sbin/ente-policy-provider /usr/sbin/ente-*-compat 13 shims D-Bus (logind, hostnamed, …) /ente/seed.card.json Tarjeta Semilla canónica -/bin/{sh,ls,mount,…} busybox-static (o glibc dinámica + libs) +/bin/{sh,ls,mount,…} busybox-static /etc, /dev, /proc, /sys, /run mountpoints vacíos /sys/fs/cgroup mountpoint cgroup v2 ``` @@ -38,31 +38,69 @@ Path canónico de la semilla en **prod**: `/ente/seed.card.json` (ver ## 2. Build del initrd -Requisitos host: `cargo`, `cpio`, `gzip`, y opcionalmente `busybox-static` -(en Debian/Ubuntu: `apt install busybox-static`). +### 2a. Por qué musl-static + +`ente-zero` corre como **PID 1**: si tiene cualquier dependencia dinámica +(`libgcc_s.so.1`, `libc.so.6`, `ld-linux-x86-64.so.2`) que no está +presente en el initramfs, el kernel paniquea con +`error while loading shared libraries: libgcc_s.so.1`. + +Solución estándar: compilar contra el target `x86_64-unknown-linux-musl`, +que produce un ELF **totalmente estático** (no necesita libc ni +intérprete en runtime). Es el default del script. + +### 2b. Requisitos del host + +- `rust toolchain` con el target musl: + ```bash + rustup target add x86_64-unknown-linux-musl + ``` +- `musl-gcc` para crates con `build.rs` C (sled, blake3, etc.): + ```bash + apt install musl-tools # Debian/Ubuntu + apk add musl-dev gcc # Alpine + pacman -S musl # Arch + ``` +- `cpio`, `gzip` para empaquetar. +- `busybox-static` para el userspace mínimo dentro del initramfs: + ```bash + apt install busybox-static # Debian/Ubuntu + # Alpine ya trae busybox built-in + ``` + +### 2c. Invocación ```bash -# Default: semilla de prod, sale en out/arje.initrd.cpio.gz +# Default: musl-static, semilla de prod, sale en out/arje.initrd.cpio.gz scripts/build-arje-initrd.sh -# Customizar: +# Customizar seed/salida: scripts/build-arje-initrd.sh seeds/arje-minimal.card.json out/min.cpio.gz -# Sin busybox del sistema, apuntar a uno propio: +# busybox-static no está en $PATH: BUSYBOX_BIN=/path/to/busybox scripts/build-arje-initrd.sh -# Vendorear binarios extra (separados por espacio): -EXTRA_BINS="/usr/bin/strace /usr/bin/gdb" scripts/build-arje-initrd.sh +# Vendorear binarios extra estáticos: +EXTRA_BINS="/usr/local/bin/strace-static" scripts/build-arje-initrd.sh + +# (NO RECOMENDADO) build glibc dinámico — kernel panic asegurado salvo que +# vendorees libgcc_s + libc + ld-linux a mano: +ARJE_TARGET=x86_64-unknown-linux-gnu scripts/build-arje-initrd.sh ``` -El script: +### 2d. Qué hace el script -1. Corre `cargo build --release` para `ente-zero` + los 14 shims compat. -2. Valida la seed con `seeds/validate.sh` (parse + `Card::validate`). -3. Stage en un tmpdir → copia binarios + crea mountpoints + escribe `/init`. -4. Empaqueta `find . | cpio -o -H newc | gzip -9`. +1. Verifica que el target musl esté instalado; si no, da el comando + exacto para instalarlo. +2. `cargo build --release --target x86_64-unknown-linux-musl` para + `ente-zero` + los 14 shims compat. +3. Sanity-check con `file`: aborta si algún binario quedó dinámico. +4. Valida la seed con `seeds/validate.sh` (parse + `Card::validate`). +5. Verifica que `busybox` sea estático (también aborta si es dinámico). +6. Stage en un tmpdir → copia binarios + crea mountpoints + escribe `/init`. +7. Empaqueta `find . | cpio -o -H newc | gzip -9`. -Tamaño típico: ~15-25 MB descomprimido, 5-8 MB el `.cpio.gz`. +Tamaño típico (musl): ~10-15 MB descomprimido, 4-6 MB el `.cpio.gz`. ## 3. Boot en QEMU @@ -302,11 +340,83 @@ mkdir /tmp/arje-test && cp seeds/arje-minimal.card.json /tmp/arje-test/seed.card cd /tmp/arje-test && target/release/ente-zero ``` +## 7. Troubleshooting + +### 7a. `error while loading shared libraries: libgcc_s.so.1` al boot + +**Causa**: el `/sbin/ente-zero` del initrd se compiló contra glibc +(`x86_64-unknown-linux-gnu`) y depende dinámicamente de `libgcc_s.so.1`, +`libc.so.6`, `ld-linux-x86-64.so.2`. Esas libs no están en el initramfs, +el kernel mata PID 1 → panic. + +**Fix**: usar el target musl (default del script desde mayo 2026): + +```bash +rustup target add x86_64-unknown-linux-musl +apt install musl-tools # Debian/Ubuntu — para crates con build.rs C +scripts/build-arje-initrd.sh # ya usa musl por default +``` + +Verificar manualmente que el binario quedó estático: + +```bash +file target/x86_64-unknown-linux-musl/release/ente-zero +# → "ELF 64-bit LSB executable, ..., statically linked, ..." +``` + +Si decidís quedarte en glibc dinámico, hay que vendorear `libgcc_s.so.1`, +`libc.so.6` y `ld-linux-x86-64.so.2` (más cualquier dep de `tracing` y +`tokio`) bajo `/lib64/` y `/lib/x86_64-linux-gnu/` del initrd. No +recomendado. + +### 7b. Kernel panic: `Kernel panic - not syncing: Attempted to kill init!` + +**Causa**: `ente-zero` salió con error antes de bind del bus, o `/init` +crashed antes de exec. PID 1 muriendo = panic kernel. + +**Fix**: agregar `panic=10` al cmdline (ya está en el script) para +auto-reboot tras 10 s y ver el error. Bajo QEMU, `-no-reboot` lo +convierte en exit limpio. Capturar el log: + +```bash +HEADLESS=1 scripts/run-arje-qemu.sh 2>&1 | tee /tmp/arje-boot.log +``` + +Buscar la línea anterior a `Attempted to kill init`. + +### 7c. `seed inválida` al correr build-arje-initrd.sh + +**Causa**: el JSON tiene un ULID con caracteres prohibidos (`I L O U` +están **excluidos** de Crockford base32), un `Payload::Native.exec` +vacío, o un campo del schema rechazado por `brahman_card::Card::validate`. + +**Fix**: correr el validador directo para ver el error completo: + +```bash +seeds/validate.sh seeds/mi-seed.card.json +# Si falla, las primeras 40 líneas del log van a stderr. +``` + +### 7d. `cannot find -lcrypt` o `cannot find -lpam` al compilar musl + +**Causa**: algún crate del workspace pulled in una dep C que no +encontró su lib bajo musl. + +**Fix**: para los crates de arje no debería pasar (revisamos las deps). +Si ocurre con un crate nuevo, suele resolverse con `RUSTFLAGS="-C +target-feature=+crt-static"` o aislando el feature problemático. + --- ## Apéndice — checklist pre-deploy -- [ ] `cargo build --release -p ente-zero` sin warnings. +- [ ] `rustup target add x86_64-unknown-linux-musl` instalado en el host build. +- [ ] `musl-tools` (Debian/Ubuntu) o equivalente disponible. +- [ ] `busybox-static` disponible para vendorear userspace. +- [ ] `cargo build --release --target x86_64-unknown-linux-musl -p ente-zero` + compila sin warnings. +- [ ] `file target/x86_64-unknown-linux-musl/release/ente-zero` reporta + "statically linked". - [ ] `seeds/validate.sh seeds/arje-prod.card.json` → OK. - [ ] `scripts/build-arje-initrd.sh` produce `out/arje.initrd.cpio.gz`. - [ ] `scripts/run-arje-qemu.sh` arranca y muestra `Tarjeta Semilla cargada diff --git a/scripts/build-arje-initrd.sh b/scripts/build-arje-initrd.sh index fa4363b..350a7e7 100755 --- a/scripts/build-arje-initrd.sh +++ b/scripts/build-arje-initrd.sh @@ -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 </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"