tawasuyu

tawasuyu/brahman

Rust 582TOML 222Markdown 83JSON 198635 Sutichaykuna

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>

ec458b8a6f5930da8330191146b04add0be6a570 · ruwaq sergio · (5 killa ñawpaq) · tayta 0e66cda07 · kay commitpi sach'ata qhaway

Semántico sayk'uy

Mana ima sutichaypas t'ikrakurqanchu: qillqalla, yachachiylla utaq willaylla t'ikrakurqan.

2 qillqakuna · +231 −38

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 <<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"