gioser

tawasuyu/brahman

Rust 321TOML 122JSON 14Markdown 114306 Sutichaykuna

⎇ ec458b8a6f5930da8330191146b04add0be6a570 docs/arje-boot.md Markdown · 427 siq'ikuna · 14.5 KB Ñawpa willay ch'uya

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

1# Booteando arje — initramfs, QEMU y bare metal
2
3`arje` es el init absorbido por brahman: `ente-zero` corre como PID 1, lee
4la **Tarjeta Semilla** (`/ente/seed.card.json`), monta `/proc /sys /dev
5/sys/fs/cgroup`, y encarna recursivamente cada Card declarada en
6`genesis` vía `ente-incarnate::Incarnator` (`clone(2)` + namespaces + cgroup v2).
7
8Este documento describe el ciclo completo:
9
101. **Layout** del initramfs.
112. **Build** del initrd (`scripts/build-arje-initrd.sh`).
123. **Boot en QEMU** (`scripts/run-arje-qemu.sh`).
134. **Boot en bare metal / VM persistente** (entrada de GRUB).
145. **Tarjeta Semilla** — qué pone, qué valida, cómo customizar.
156. **Debugging**: tracing, sockets de introspección, snapshot/restore.
16
17---
18
19## 1. Layout del initramfs
20
21El initrd es un **CPIO + gzip** (formato `newc`, estándar Linux). Su raíz:
22
23```
24/init                           wrapper sh, ejecuta /sbin/ente-zero
25/sbin/ente-zero                 PID 1 (musl-static por default)
26/usr/sbin/ente-echo
27/usr/sbin/ente-policy-provider
28/usr/sbin/ente-*-compat         13 shims D-Bus (logind, hostnamed, …)
29/ente/seed.card.json            Tarjeta Semilla canónica
30/bin/{sh,ls,mount,…}            busybox-static
31/etc, /dev, /proc, /sys, /run   mountpoints vacíos
32/sys/fs/cgroup                  mountpoint cgroup v2
33```
34
35Path canónico de la semilla en **prod**: `/ente/seed.card.json` (ver
36`crates/core/ente-zero/src/seed.rs`). En **dev** (no PID 1):
37`./seed.card.json` en el cwd, o se sintetiza una mínima.
38
39## 2. Build del initrd
40
41### 2a. Por qué musl-static
42
43`ente-zero` corre como **PID 1**: si tiene cualquier dependencia dinámica
44(`libgcc_s.so.1`, `libc.so.6`, `ld-linux-x86-64.so.2`) que no está
45presente en el initramfs, el kernel paniquea con
46`error while loading shared libraries: libgcc_s.so.1`.
47
48Solución estándar: compilar contra el target `x86_64-unknown-linux-musl`,
49que produce un ELF **totalmente estático** (no necesita libc ni
50intérprete en runtime). Es el default del script.
51
52### 2b. Requisitos del host
53
54- `rust toolchain` con el target musl:
55  ```bash
56  rustup target add x86_64-unknown-linux-musl
57  ```
58- `musl-gcc` para crates con `build.rs` C (sled, blake3, etc.):
59  ```bash
60  apt install musl-tools           # Debian/Ubuntu
61  apk add musl-dev gcc             # Alpine
62  pacman -S musl                   # Arch
63  ```
64- `cpio`, `gzip` para empaquetar.
65- `busybox-static` para el userspace mínimo dentro del initramfs:
66  ```bash
67  apt install busybox-static       # Debian/Ubuntu
68  # Alpine ya trae busybox built-in
69  ```
70
71### 2c. Invocación
72
73```bash
74# Default: musl-static, semilla de prod, sale en out/arje.initrd.cpio.gz
75scripts/build-arje-initrd.sh
76
77# Customizar seed/salida:
78scripts/build-arje-initrd.sh seeds/arje-minimal.card.json out/min.cpio.gz
79
80# busybox-static no está en $PATH:
81BUSYBOX_BIN=/path/to/busybox scripts/build-arje-initrd.sh
82
83# Vendorear binarios extra estáticos:
84EXTRA_BINS="/usr/local/bin/strace-static" scripts/build-arje-initrd.sh
85
86# (NO RECOMENDADO) build glibc dinámico — kernel panic asegurado salvo que
87# vendorees libgcc_s + libc + ld-linux a mano:
88ARJE_TARGET=x86_64-unknown-linux-gnu scripts/build-arje-initrd.sh
89```
90
91### 2d. Qué hace el script
92
931. Verifica que el target musl esté instalado; si no, da el comando
94   exacto para instalarlo.
952. `cargo build --release --target x86_64-unknown-linux-musl` para
96   `ente-zero` + los 14 shims compat.
973. Sanity-check con `file`: aborta si algún binario quedó dinámico.
984. Valida la seed con `seeds/validate.sh` (parse + `Card::validate`).
995. Verifica que `busybox` sea estático (también aborta si es dinámico).
1006. Stage en un tmpdir → copia binarios + crea mountpoints + escribe `/init`.
1017. Empaqueta `find . | cpio -o -H newc | gzip -9`.
102
103Tamaño típico (musl): ~10-15 MB descomprimido, 4-6 MB el `.cpio.gz`.
104
105## 3. Boot en QEMU
106
107Requisito: un kernel Linux + qemu-system-x86_64.
108
109### 3a. Con kernel del host
110
111```bash
112# Default: usa /boot/vmlinuz-$(uname -r)
113scripts/run-arje-qemu.sh
114
115# Con kernel explícito:
116scripts/run-arje-qemu.sh out/arje.initrd.cpio.gz /boot/vmlinuz-6.6.0
117```
118
119El script invoca:
120
121```
122qemu-system-x86_64
123  -accel kvm                    (si /dev/kvm está, fallback tcg)
124  -m 1024 -smp 2
125  -kernel <vmlinuz>
126  -initrd out/arje.initrd.cpio.gz
127  -append "rdinit=/init console=ttyS0,115200 panic=10"
128  -nographic -serial mon:stdio
129  -no-reboot
130```
131
132`rdinit=/init` le dice al kernel que el primer programa a ejecutar es
133nuestro `/init`, no `/sbin/init`. `console=ttyS0,115200` redirige el log
134del kernel + nuestro tracing a la serial → stdio del host. Salida con
135`Ctrl-A X`.
136
137### 3b. Sin kernel del host
138
139Bajar un kernel mínimo (ej. Ubuntu cloud kernel):
140
141```bash
142wget -O /tmp/vmlinuz \
143  https://cloud-images.ubuntu.com/jammy/current/unpacked/jammy-server-cloudimg-amd64-vmlinuz-generic
144scripts/run-arje-qemu.sh out/arje.initrd.cpio.gz /tmp/vmlinuz
145```
146
147O usar el kernel de Alpine (más liviano, ~10 MB):
148
149```bash
150wget -O /tmp/vmlinuz \
151  https://dl-cdn.alpinelinux.org/alpine/v3.20/releases/x86_64/netboot/vmlinuz-lts
152```
153
154### 3c. Override del cmdline
155
156`KERNEL_CMD="ente.log=debug"` se concatena al cmdline. Cosas útiles:
157
158| flag                       | efecto                                              |
159| -------------------------- | --------------------------------------------------- |
160| `panic=10`                 | reboot 10 s tras kernel panic (debug)               |
161| `loglevel=7`               | log del kernel hasta debug                          |
162| `quiet`                    | silencia banner kernel                              |
163| `RUST_LOG=trace`           | (no se interpreta; usar env en `/init`)             |
164| `init=/sbin/ente-zero`     | salta `/init`, ejecuta directo (no recomendado)     |
165
166`ente-zero` lee `RUST_LOG` y `BRAHMAN_*` del env. Para setearlas, editar
167`/init` antes de empaquetar, o agregar `-fw_cfg name=opt/foo,...` a qemu.
168
169### 3d. Smoke test esperado
170
171Con `seeds/arje-prod.card.json`:
172
173```
174ente-zero despierta como PID 1
175Tarjeta Semilla cargada y validada path=/ente/seed.card.json
176bus interno escuchando path=/run/ente-bus.sock
177brahman handshake escuchando (Unix) socket=/run/brahman-init.sock
178brahman admin escuchando socket=/run/brahman-admin.sock
179instanciando genesis seed=arje.seed.prod count=16
180Ente compat-logind encarnado pid=...
181Ente compat-hostnamed encarnado pid=...
182...
183arje#                          ← shell en tty
184```
185
186Con `seeds/arje-minimal.card.json`: solo el shell.
187
188## 4. Boot en bare metal / VM persistente
189
190El mismo `.cpio.gz` sirve como initramfs estándar de Linux. Entrada
191GRUB típica (`/etc/grub.d/40_custom` + `update-grub`):
192
193```
194menuentry "arje" {
195    linux  /boot/vmlinuz-6.6.0 rdinit=/init console=tty1 panic=10
196    initrd /boot/arje.initrd.cpio.gz
197}
198```
199
200Copiar a `/boot/`:
201
202```bash
203sudo cp out/arje.initrd.cpio.gz /boot/arje.initrd.cpio.gz
204sudo update-grub
205```
206
207Para una VM (Proxmox, libvirt, etc.) basta con apuntar el "Direct Kernel
208Boot" al vmlinuz y al initrd. En Proxmox: editar `/etc/pve/qemu-server/<vmid>.conf`:
209
210```
211args: -kernel /boot/vmlinuz-6.6.0 -initrd /boot/arje.initrd.cpio.gz \
212      -append "rdinit=/init console=ttyS0,115200 panic=10"
213serial0: socket
214```
215
216### 4a. Sin pivot_root (initramfs es el rootfs final)
217
218El initrd actual **no hace pivot_root** — se queda como rootfs. Esto es
219intencional: arje no asume nada del disco. Para persistencia, las Cards
220hijas deben montar el FS de disco a demanda (ej. `genesis: [{ label:
221"mount-data", payload: Native("/bin/mount", ["/dev/sda1", "/mnt"]), … }]`).
222
223Cuando necesites pivot_root a un FS real (instalación full-disk), agregar
224un Ente que haga `switch_root` antes de instanciar el resto — pendiente
225de implementar como `Capability::SwitchRoot`.
226
227## 5. Tarjeta Semilla — detalles
228
229### 5a. Seeds estándar
230
231| seed                          | uso                                                   |
232| ----------------------------- | ----------------------------------------------------- |
233| `seeds/arje-minimal.card.json` | PID 1 + 1 shell `/bin/sh`. Smoke test para QEMU.     |
234| `seeds/arje-prod.card.json`    | Constelación completa: 14 compat shims + getty.      |
235
236### 5b. Validación
237
238```bash
239seeds/validate.sh seeds/arje-prod.card.json
240```
241
242El script carga la Card vía `ente_brain::load_card_file()` (que llama a
243`brahman_card::Card::validate()`) y verifica que `ente-zero` la encarne
244hasta `instanciando genesis`.
245
246### 5c. Customizar
247
248Estructura de un genesis-child:
249
250```json
251{
252  "schema_version": 1,
253  "id": "<ULID Crockford base32 — no I L O U>",
254  "label": "mi-servicio",
255  "provides": ["Journal"],
256  "requires": [],
257  "permissions": {
258    "networking": "loopback",
259    "filesystem": "read-write",
260    "ipc": { "allow": ["wit-v1"] },
261    "processes": false
262  },
263  "soma": {
264    "namespaces": { "mount": true, "pid": true, "net": false, ...},
265    "rlimits": { "mem_bytes": 268435456 },
266    "cgroup": { "path": "ente.slice/mi-servicio", "cpu_weight": 100 }
267  },
268  "payload": { "Native": { "exec": "/usr/sbin/mi-bin", "argv": [], "envp": [] } },
269  "supervision": { "Restart": { "initial": 100, "max": 30000 } },
270  "lifecycle": "daemon",
271  "priority": "normal",
272  "flow": { "input": [], "output": [] },
273  "genesis": []
274}
275```
276
277Validación clave (`brahman_card::Card::validate`):
278
279- `schema_version == 1`.
280- `label` no vacío, ≤ 256 bytes.
281- ULID válido (Crockford base32, **sin `I L O U`**).
282- `provides ∩ requires == ∅`.
283- `Payload::Native.exec` no vacío.
284- `Payload::Wasm.module_sha256` no todo ceros.
285- rlimits: `mem_bytes > 0 && < 1 TiB`, `nproc ∈ [1, 65535]`, `nofile ∈ [1, 1M]`.
286- `cgroup.cpu_weight`, `io_weight ∈ [1, 10000]`.
287- `flow.input` y `flow.output` con nombres únicos.
288- Recursivo sobre `genesis`.
289
290## 6. Debugging
291
292### 6a. Tracing
293
294`ente-zero` usa `tracing-subscriber`. Default: `ente_zero=debug,info`.
295Override con `RUST_LOG`:
296
297```bash
298# En el initrd: editar /init antes de empaquetar
299echo 'export RUST_LOG="trace"' >> wrapper
300```
301
302### 6b. Sockets de introspección (Unix, dentro del initrd)
303
304| socket                       | servicio                                          |
305| ---------------------------- | ------------------------------------------------- |
306| `/run/ente-bus.sock`          | bus interno (postcard, `BusRequest::Invoke/...`) |
307| `/run/brahman-init.sock`      | handshake brahman                                 |
308| `/run/brahman-admin.sock`     | snapshots de sesiones + matches                   |
309| `/run/ente-brain.sock`        | introspección del cerebro                         |
310
311Conectar desde un Ente hijo: `socat - UNIX-CONNECT:/run/brahman-admin.sock`.
312(El initrd debe tener `socat` o equivalente; agregalo con `EXTRA_BINS`.)
313
314### 6c. Snapshot / restore
315
316```
317ente-zero --checkpoint /ente/checkpoint.json    # escribe al cerrar
318ente-zero --restore    /ente/checkpoint.json    # reconstruye al boot
319```
320
321Snapshot adjunto del cerebro: `/ente/checkpoint.brain.json`.
322
323### 6d. Metrics
324
325```
326ente-zero --metrics-addr 127.0.0.1:9911
327```
328
329Endpoint Prometheus desde dentro de la VM. Para exponerlo al host bajo
330QEMU, agregar `-netdev user,hostfwd=tcp::9911-:9911 -device virtio-net,netdev=…`.
331
332### 6e. Modo DEV en host (sin PID 1)
333
334`ente-zero` detecta si su PID != 1 y entra en **DEV MODE**: no monta
335kernel surface, no se vuelve subreaper, sale tras 4 s. Útil para
336iterar Cards en el host:
337
338```bash
339mkdir /tmp/arje-test && cp seeds/arje-minimal.card.json /tmp/arje-test/seed.card.json
340cd /tmp/arje-test && target/release/ente-zero
341```
342
343## 7. Troubleshooting
344
345### 7a. `error while loading shared libraries: libgcc_s.so.1` al boot
346
347**Causa**: el `/sbin/ente-zero` del initrd se compiló contra glibc
348(`x86_64-unknown-linux-gnu`) y depende dinámicamente de `libgcc_s.so.1`,
349`libc.so.6`, `ld-linux-x86-64.so.2`. Esas libs no están en el initramfs,
350el kernel mata PID 1 → panic.
351
352**Fix**: usar el target musl (default del script desde mayo 2026):
353
354```bash
355rustup target add x86_64-unknown-linux-musl
356apt install musl-tools             # Debian/Ubuntu — para crates con build.rs C
357scripts/build-arje-initrd.sh       # ya usa musl por default
358```
359
360Verificar manualmente que el binario quedó estático:
361
362```bash
363file target/x86_64-unknown-linux-musl/release/ente-zero
364# → "ELF 64-bit LSB executable, ..., statically linked, ..."
365```
366
367Si decidís quedarte en glibc dinámico, hay que vendorear `libgcc_s.so.1`,
368`libc.so.6` y `ld-linux-x86-64.so.2` (más cualquier dep de `tracing` y
369`tokio`) bajo `/lib64/` y `/lib/x86_64-linux-gnu/` del initrd. No
370recomendado.
371
372### 7b. Kernel panic: `Kernel panic - not syncing: Attempted to kill init!`
373
374**Causa**: `ente-zero` salió con error antes de bind del bus, o `/init`
375crashed antes de exec. PID 1 muriendo = panic kernel.
376
377**Fix**: agregar `panic=10` al cmdline (ya está en el script) para
378auto-reboot tras 10 s y ver el error. Bajo QEMU, `-no-reboot` lo
379convierte en exit limpio. Capturar el log:
380
381```bash
382HEADLESS=1 scripts/run-arje-qemu.sh 2>&1 | tee /tmp/arje-boot.log
383```
384
385Buscar la línea anterior a `Attempted to kill init`.
386
387### 7c. `seed inválida` al correr build-arje-initrd.sh
388
389**Causa**: el JSON tiene un ULID con caracteres prohibidos (`I L O U`
390están **excluidos** de Crockford base32), un `Payload::Native.exec`
391vacío, o un campo del schema rechazado por `brahman_card::Card::validate`.
392
393**Fix**: correr el validador directo para ver el error completo:
394
395```bash
396seeds/validate.sh seeds/mi-seed.card.json
397# Si falla, las primeras 40 líneas del log van a stderr.
398```
399
400### 7d. `cannot find -lcrypt` o `cannot find -lpam` al compilar musl
401
402**Causa**: algún crate del workspace pulled in una dep C que no
403encontró su lib bajo musl.
404
405**Fix**: para los crates de arje no debería pasar (revisamos las deps).
406Si ocurre con un crate nuevo, suele resolverse con `RUSTFLAGS="-C
407target-feature=+crt-static"` o aislando el feature problemático.
408
409---
410
411## Apéndice — checklist pre-deploy
412
413- [ ] `rustup target add x86_64-unknown-linux-musl` instalado en el host build.
414- [ ] `musl-tools` (Debian/Ubuntu) o equivalente disponible.
415- [ ] `busybox-static` disponible para vendorear userspace.
416- [ ] `cargo build --release --target x86_64-unknown-linux-musl -p ente-zero`
417      compila sin warnings.
418- [ ] `file target/x86_64-unknown-linux-musl/release/ente-zero` reporta
419      "statically linked".
420- [ ] `seeds/validate.sh seeds/arje-prod.card.json` → OK.
421- [ ] `scripts/build-arje-initrd.sh` produce `out/arje.initrd.cpio.gz`.
422- [ ] `scripts/run-arje-qemu.sh` arranca y muestra `Tarjeta Semilla cargada
423      y validada` + `instanciando genesis count=16` (o el count que toque).
424- [ ] Si vas a bare metal: tener un kernel `vmlinuz` rescue (ej. Alpine
425      netboot) en `/boot/` por si arje no levanta.
426- [ ] Para VMs Proxmox/libvirt: serial console habilitada para ver el
427      arranque sin display.

arje-boot.md

Booteando arje — initramfs, QEMU y bare metal

arje es el init absorbido por brahman: ente-zero corre como PID 1, lee la Tarjeta Semilla (/ente/seed.card.json), monta /proc /sys /dev /sys/fs/cgroup, y encarna recursivamente cada Card declarada en genesis vía ente-incarnate::Incarnator (clone(2) + namespaces + cgroup v2).

Este documento describe el ciclo completo:

  1. Layout del initramfs.
  2. Build del initrd (scripts/build-arje-initrd.sh).
  3. Boot en QEMU (scripts/run-arje-qemu.sh).
  4. Boot en bare metal / VM persistente (entrada de GRUB).
  5. Tarjeta Semilla — qué pone, qué valida, cómo customizar.
  6. Debugging: tracing, sockets de introspección, snapshot/restore.

1. Layout del initramfs

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 (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
/etc, /dev, /proc, /sys, /run   mountpoints vacíos
/sys/fs/cgroup                  mountpoint cgroup v2

Path canónico de la semilla en prod: /ente/seed.card.json (ver crates/core/ente-zero/src/seed.rs). En dev (no PID 1): ./seed.card.json en el cwd, o se sintetiza una mínima.

2. Build del initrd

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

2c. Invocación

# Default: musl-static, semilla de prod, sale en out/arje.initrd.cpio.gz
scripts/build-arje-initrd.sh

# Customizar seed/salida:
scripts/build-arje-initrd.sh seeds/arje-minimal.card.json out/min.cpio.gz

# busybox-static no está en $PATH:
BUSYBOX_BIN=/path/to/busybox 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

2d. Qué hace el script

  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 (musl): ~10-15 MB descomprimido, 4-6 MB el .cpio.gz.

3. Boot en QEMU

Requisito: un kernel Linux + qemu-system-x86_64.

3a. Con kernel del host

# Default: usa /boot/vmlinuz-$(uname -r)
scripts/run-arje-qemu.sh

# Con kernel explícito:
scripts/run-arje-qemu.sh out/arje.initrd.cpio.gz /boot/vmlinuz-6.6.0

El script invoca:

qemu-system-x86_64
  -accel kvm                    (si /dev/kvm está, fallback tcg)
  -m 1024 -smp 2
  -kernel <vmlinuz>
  -initrd out/arje.initrd.cpio.gz
  -append "rdinit=/init console=ttyS0,115200 panic=10"
  -nographic -serial mon:stdio
  -no-reboot

rdinit=/init le dice al kernel que el primer programa a ejecutar es nuestro /init, no /sbin/init. console=ttyS0,115200 redirige el log del kernel + nuestro tracing a la serial → stdio del host. Salida con Ctrl-A X.

3b. Sin kernel del host

Bajar un kernel mínimo (ej. Ubuntu cloud kernel):

wget -O /tmp/vmlinuz \
  https://cloud-images.ubuntu.com/jammy/current/unpacked/jammy-server-cloudimg-amd64-vmlinuz-generic
scripts/run-arje-qemu.sh out/arje.initrd.cpio.gz /tmp/vmlinuz

O usar el kernel de Alpine (más liviano, ~10 MB):

wget -O /tmp/vmlinuz \
  https://dl-cdn.alpinelinux.org/alpine/v3.20/releases/x86_64/netboot/vmlinuz-lts

3c. Override del cmdline

KERNEL_CMD="ente.log=debug" se concatena al cmdline. Cosas útiles:

flagefecto
panic=10reboot 10 s tras kernel panic (debug)
loglevel=7log del kernel hasta debug
quietsilencia banner kernel
RUST_LOG=trace(no se interpreta; usar env en /init)
init=/sbin/ente-zerosalta /init, ejecuta directo (no recomendado)

ente-zero lee RUST_LOG y BRAHMAN_* del env. Para setearlas, editar /init antes de empaquetar, o agregar -fw_cfg name=opt/foo,... a qemu.

3d. Smoke test esperado

Con seeds/arje-prod.card.json:

ente-zero despierta como PID 1
Tarjeta Semilla cargada y validada path=/ente/seed.card.json
bus interno escuchando path=/run/ente-bus.sock
brahman handshake escuchando (Unix) socket=/run/brahman-init.sock
brahman admin escuchando socket=/run/brahman-admin.sock
instanciando genesis seed=arje.seed.prod count=16
Ente compat-logind encarnado pid=...
Ente compat-hostnamed encarnado pid=...
...
arje#                          ← shell en tty

Con seeds/arje-minimal.card.json: solo el shell.

4. Boot en bare metal / VM persistente

El mismo .cpio.gz sirve como initramfs estándar de Linux. Entrada GRUB típica (/etc/grub.d/40_custom + update-grub):

menuentry "arje" {
    linux  /boot/vmlinuz-6.6.0 rdinit=/init console=tty1 panic=10
    initrd /boot/arje.initrd.cpio.gz
}

Copiar a /boot/:

sudo cp out/arje.initrd.cpio.gz /boot/arje.initrd.cpio.gz
sudo update-grub

Para una VM (Proxmox, libvirt, etc.) basta con apuntar el "Direct Kernel Boot" al vmlinuz y al initrd. En Proxmox: editar /etc/pve/qemu-server/<vmid>.conf:

args: -kernel /boot/vmlinuz-6.6.0 -initrd /boot/arje.initrd.cpio.gz \
      -append "rdinit=/init console=ttyS0,115200 panic=10"
serial0: socket

4a. Sin pivot_root (initramfs es el rootfs final)

El initrd actual no hace pivot_root — se queda como rootfs. Esto es intencional: arje no asume nada del disco. Para persistencia, las Cards hijas deben montar el FS de disco a demanda (ej. genesis: [{ label: "mount-data", payload: Native("/bin/mount", ["/dev/sda1", "/mnt"]), … }]).

Cuando necesites pivot_root a un FS real (instalación full-disk), agregar un Ente que haga switch_root antes de instanciar el resto — pendiente de implementar como Capability::SwitchRoot.

5. Tarjeta Semilla — detalles

5a. Seeds estándar

seeduso
seeds/arje-minimal.card.jsonPID 1 + 1 shell /bin/sh. Smoke test para QEMU.
seeds/arje-prod.card.jsonConstelación completa: 14 compat shims + getty.

5b. Validación

seeds/validate.sh seeds/arje-prod.card.json

El script carga la Card vía ente_brain::load_card_file() (que llama a brahman_card::Card::validate()) y verifica que ente-zero la encarne hasta instanciando genesis.

5c. Customizar

Estructura de un genesis-child:

{
  "schema_version": 1,
  "id": "<ULID Crockford base32 — no I L O U>",
  "label": "mi-servicio",
  "provides": ["Journal"],
  "requires": [],
  "permissions": {
    "networking": "loopback",
    "filesystem": "read-write",
    "ipc": { "allow": ["wit-v1"] },
    "processes": false
  },
  "soma": {
    "namespaces": { "mount": true, "pid": true, "net": false, ...},
    "rlimits": { "mem_bytes": 268435456 },
    "cgroup": { "path": "ente.slice/mi-servicio", "cpu_weight": 100 }
  },
  "payload": { "Native": { "exec": "/usr/sbin/mi-bin", "argv": [], "envp": [] } },
  "supervision": { "Restart": { "initial": 100, "max": 30000 } },
  "lifecycle": "daemon",
  "priority": "normal",
  "flow": { "input": [], "output": [] },
  "genesis": []
}

Validación clave (brahman_card::Card::validate):

6. Debugging

6a. Tracing

ente-zero usa tracing-subscriber. Default: ente_zero=debug,info. Override con RUST_LOG:

# En el initrd: editar /init antes de empaquetar
echo 'export RUST_LOG="trace"' >> wrapper

6b. Sockets de introspección (Unix, dentro del initrd)

socketservicio
/run/ente-bus.sockbus interno (postcard, BusRequest::Invoke/...)
/run/brahman-init.sockhandshake brahman
/run/brahman-admin.socksnapshots de sesiones + matches
/run/ente-brain.sockintrospección del cerebro

Conectar desde un Ente hijo: socat - UNIX-CONNECT:/run/brahman-admin.sock. (El initrd debe tener socat o equivalente; agregalo con EXTRA_BINS.)

6c. Snapshot / restore

ente-zero --checkpoint /ente/checkpoint.json    # escribe al cerrar
ente-zero --restore    /ente/checkpoint.json    # reconstruye al boot

Snapshot adjunto del cerebro: /ente/checkpoint.brain.json.

6d. Metrics

ente-zero --metrics-addr 127.0.0.1:9911

Endpoint Prometheus desde dentro de la VM. Para exponerlo al host bajo QEMU, agregar -netdev user,hostfwd=tcp::9911-:9911 -device virtio-net,netdev=….

6e. Modo DEV en host (sin PID 1)

ente-zero detecta si su PID != 1 y entra en DEV MODE: no monta kernel surface, no se vuelve subreaper, sale tras 4 s. Útil para iterar Cards en el host:

mkdir /tmp/arje-test && cp seeds/arje-minimal.card.json /tmp/arje-test/seed.card.json
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):

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:

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:

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:

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