docs(arje): organiza core/ + seeds canónicas + boot reproducible
- crates/core/README.md: agrupamiento lógico de los 31 crates absorbidos
de arje (ente-*) y del protocolo brahman (brahman-*) en 6 grupos —
Init/PID 1, contratos, discovery, IPC+CAS, cerebro, 14 shims compat
systemd. No se movieron crates físicamente (rompería paths
cross-workspace).
- seeds/arje-minimal.card.json: PID1 + /bin/sh, smoke test QEMU.
- seeds/arje-prod.card.json: PID1 + 14 shims compat + tmpfiles/binfmt
one-shots + echo + getty (16 children). Validados con
brahman_card::Card::validate.
- seeds/validate.sh: carga la seed vía ente-zero en dev mode.
- scripts/build-arje-initrd.sh: empaqueta CPIO+gzip newc layout
/init→/sbin/ente-zero, /usr/sbin/ente-*-compat, /ente/seed.card.json,
/bin/{sh,...} (busybox o glibc+ldd). Tested: produce 20 MB initrd OK.
- scripts/run-arje-qemu.sh: qemu-system-x86_64 con KVM auto-detect,
-kernel/-initrd/-append "rdinit=/init console=ttyS0,115200 panic=10".
- docs/arje-boot.md: doc end-to-end — layout initramfs, QEMU (con kernel
del host o externo), GRUB bare metal, Proxmox/libvirt args:, schema
de Card con todas las validaciones, debugging (sockets de
introspección, snapshot/restore, metrics), checklist pre-deploy.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,317 @@
|
||||
# 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
|
||||
/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)
|
||||
/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
|
||||
|
||||
Requisitos host: `cargo`, `cpio`, `gzip`, y opcionalmente `busybox-static`
|
||||
(en Debian/Ubuntu: `apt install busybox-static`).
|
||||
|
||||
```bash
|
||||
# Default: semilla de prod, sale en out/arje.initrd.cpio.gz
|
||||
scripts/build-arje-initrd.sh
|
||||
|
||||
# Customizar:
|
||||
scripts/build-arje-initrd.sh seeds/arje-minimal.card.json out/min.cpio.gz
|
||||
|
||||
# Sin busybox del sistema, apuntar a uno propio:
|
||||
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
|
||||
```
|
||||
|
||||
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`.
|
||||
|
||||
Tamaño típico: ~15-25 MB descomprimido, 5-8 MB el `.cpio.gz`.
|
||||
|
||||
## 3. Boot en QEMU
|
||||
|
||||
Requisito: un kernel Linux + qemu-system-x86_64.
|
||||
|
||||
### 3a. Con kernel del host
|
||||
|
||||
```bash
|
||||
# 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):
|
||||
|
||||
```bash
|
||||
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):
|
||||
|
||||
```bash
|
||||
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:
|
||||
|
||||
| flag | efecto |
|
||||
| -------------------------- | --------------------------------------------------- |
|
||||
| `panic=10` | reboot 10 s tras kernel panic (debug) |
|
||||
| `loglevel=7` | log del kernel hasta debug |
|
||||
| `quiet` | silencia banner kernel |
|
||||
| `RUST_LOG=trace` | (no se interpreta; usar env en `/init`) |
|
||||
| `init=/sbin/ente-zero` | salta `/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/`:
|
||||
|
||||
```bash
|
||||
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
|
||||
|
||||
| seed | uso |
|
||||
| ----------------------------- | ----------------------------------------------------- |
|
||||
| `seeds/arje-minimal.card.json` | PID 1 + 1 shell `/bin/sh`. Smoke test para QEMU. |
|
||||
| `seeds/arje-prod.card.json` | Constelación completa: 14 compat shims + getty. |
|
||||
|
||||
### 5b. Validación
|
||||
|
||||
```bash
|
||||
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:
|
||||
|
||||
```json
|
||||
{
|
||||
"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`):
|
||||
|
||||
- `schema_version == 1`.
|
||||
- `label` no vacío, ≤ 256 bytes.
|
||||
- ULID válido (Crockford base32, **sin `I L O U`**).
|
||||
- `provides ∩ requires == ∅`.
|
||||
- `Payload::Native.exec` no vacío.
|
||||
- `Payload::Wasm.module_sha256` no todo ceros.
|
||||
- rlimits: `mem_bytes > 0 && < 1 TiB`, `nproc ∈ [1, 65535]`, `nofile ∈ [1, 1M]`.
|
||||
- `cgroup.cpu_weight`, `io_weight ∈ [1, 10000]`.
|
||||
- `flow.input` y `flow.output` con nombres únicos.
|
||||
- Recursivo sobre `genesis`.
|
||||
|
||||
## 6. Debugging
|
||||
|
||||
### 6a. Tracing
|
||||
|
||||
`ente-zero` usa `tracing-subscriber`. Default: `ente_zero=debug,info`.
|
||||
Override con `RUST_LOG`:
|
||||
|
||||
```bash
|
||||
# En el initrd: editar /init antes de empaquetar
|
||||
echo 'export RUST_LOG="trace"' >> wrapper
|
||||
```
|
||||
|
||||
### 6b. Sockets de introspección (Unix, dentro del initrd)
|
||||
|
||||
| socket | servicio |
|
||||
| ---------------------------- | ------------------------------------------------- |
|
||||
| `/run/ente-bus.sock` | bus interno (postcard, `BusRequest::Invoke/...`) |
|
||||
| `/run/brahman-init.sock` | handshake brahman |
|
||||
| `/run/brahman-admin.sock` | snapshots de sesiones + matches |
|
||||
| `/run/ente-brain.sock` | introspecció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:
|
||||
|
||||
```bash
|
||||
mkdir /tmp/arje-test && cp seeds/arje-minimal.card.json /tmp/arje-test/seed.card.json
|
||||
cd /tmp/arje-test && target/release/ente-zero
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Apéndice — checklist pre-deploy
|
||||
|
||||
- [ ] `cargo build --release -p ente-zero` sin warnings.
|
||||
- [ ] `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
|
||||
y validada` + `instanciando genesis count=16` (o el count que toque).
|
||||
- [ ] Si vas a bare metal: tener un kernel `vmlinuz` rescue (ej. Alpine
|
||||
netboot) en `/boot/` por si arje no levanta.
|
||||
- [ ] Para VMs Proxmox/libvirt: serial console habilitada para ver el
|
||||
arranque sin display.
|
||||
Reference in New Issue
Block a user