ca5dd04176
Flujo seguro de adopción: arje se instala como entrada GRUB
alternativa, no toca systemd ni /sbin/init. Booteás arje cuando
querés, volvés a systemd si rompe (rollback instantáneo desde el
menú).
Artefactos nuevos:
- scripts/install-arje-as-init.sh: instala binarios musl-static a
/usr/sbin/ y /usr/bin/, copia seed a /ente/seed.card.json, agrega
menuentry "arje" a /etc/grub.d/40_custom usando init=/sbin/ente-zero
con kernel + initrd nativos. NO cambia GRUB_DEFAULT. Idempotente
(regenera el bloque ARJE-MENUENTRY si existe).
- scripts/uninstall-arje.sh: revierte binarios + menuentry. Conserva
/ente/seed.card.json por si la editaste.
- seeds/arje-host.card.json: seed para máquina real con 15 cards:
tmpfiles + mount-fstab + swap-on + dbus-system + 11 compat shims +
dhcpcd + sshd + agetty. Validada.
- docs/arje-replace-systemd.md: filosofía vs systemd ("no acapara
porque no genera, sólo arranca lo declarado"), lista exhaustiva de
servicios systemd que NO deben migrarse (ModemManager, snapd, cups,
unattended-upgrades, etc.), tabla diferencial de UX vs systemd
(systemctl restart → kill PID, systemctl enable → editar seed),
checklist pre-primer-boot, instrucciones de rollback y cómo hacer
arje default sólo cuando estés seguro.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
222 lines
11 KiB
Markdown
222 lines
11 KiB
Markdown
# Reemplazar systemd con arje en una máquina real
|
||
|
||
> **Importante**: este flujo deja **systemd intacto** y agrega arje como
|
||
> entrada GRUB alternativa. No es "destruir y reemplazar"; es "convivir
|
||
> hasta confiar". Booteás arje cuando querés, volvés a systemd si rompe.
|
||
|
||
## 1. Estrategia
|
||
|
||
| componente | qué hacemos |
|
||
| --------------------- | -------------------------------------------------------- |
|
||
| `/sbin/init` | NO se toca (sigue siendo systemd). |
|
||
| `/lib/systemd/*` | NO se toca. |
|
||
| `kernel + initramfs` | reusamos los del host (firmware, módulos, root FS). |
|
||
| `init=` en GRUB | nueva entrada apunta a `/sbin/ente-zero`. |
|
||
| `GRUB_DEFAULT` | NO se cambia. Sigue arrancando systemd por default. |
|
||
| `/ente/seed.card.json`| nueva — define qué arranca arje. |
|
||
| `/usr/sbin/ente-*` | binarios musl-static del fractal. |
|
||
|
||
Booteo del kernel → initramfs nativo hace su trabajo (mountea root,
|
||
carga módulos) → cuando llega el `switch_root`, en lugar de exec
|
||
`/lib/systemd/systemd`, exec `/sbin/ente-zero`. Esto deja firmware y
|
||
udev fuera del scope de arje (todavía).
|
||
|
||
## 2. Instalación
|
||
|
||
```bash
|
||
# 1. (opcional) revisá la seed default que se va a copiar a /ente/
|
||
$EDITOR seeds/arje-host.card.json
|
||
|
||
# 2. instalar
|
||
sudo scripts/install-arje-as-init.sh
|
||
|
||
# 3. (opcional) bootear arje SÓLO en el próximo reboot (no cambia default)
|
||
sudo grub-reboot "arje (init=/sbin/ente-zero) — kernel $(uname -r)"
|
||
sudo reboot
|
||
```
|
||
|
||
Si algo rompe en arje: reset/poweroff → en el menú GRUB elegir la
|
||
entrada default (systemd) → volvés a un sistema funcional. `arje` no
|
||
toca configuración de systemd, así que el rollback es instantáneo.
|
||
|
||
Para **desinstalar**:
|
||
|
||
```bash
|
||
sudo scripts/uninstall-arje.sh
|
||
```
|
||
|
||
## 3. Filosofía vs systemd — y por qué arje no acapara
|
||
|
||
systemd arranca, por default, **todo lo que tiene un `.service` con
|
||
`WantedBy=multi-user.target`**, recursivamente vía dependencias. Eso es
|
||
opaco y crece con cada paquete instalado.
|
||
|
||
arje no tiene generadores, ni `WantedBy`, ni `Wants`, ni target trees.
|
||
**Arranca exactamente lo declarado en `genesis: [...]` de la
|
||
`seed.card.json`**, en el orden declarativo, con la `supervision` que
|
||
declarás vos. Si no lo declaraste, no corre. Punto.
|
||
|
||
### 3a. Servicios que systemd arranca y arje NO debería migrar
|
||
|
||
Estos son los sospechosos típicos en Debian/Ubuntu/Arch. **No agregues
|
||
ninguno a tu seed** salvo que sepas que lo usás:
|
||
|
||
| systemd unit | por qué saltearlo |
|
||
| ------------------------------------ | ---------------------------------------- |
|
||
| `ModemManager.service` | no tenés módem 3G/4G. |
|
||
| `bluetooth.service` | si no usás bluetooth. |
|
||
| `cups.service`, `cups-browsed` | si no imprimís. |
|
||
| `avahi-daemon.service` | mDNS local. Ruido si no lo necesitás. |
|
||
| `snapd.service` | si no usás snaps. |
|
||
| `apt-daily.service`, `apt-daily-upgrade` | autoupdates — no son init. |
|
||
| `unattended-upgrades.service` | corre tras boot, no init. |
|
||
| `accounts-daemon.service` | UI helpers GNOME; no init. |
|
||
| `colord.service` | calibración de color. No init. |
|
||
| `geoclue.service` | localización. No init. |
|
||
| `gdm.service`, `lightdm.service` | DM gráfico — agregalo SÓLO si querés GUI.|
|
||
| `NetworkManager-wait-online.service` | espera red. arje no necesita "esperar". |
|
||
| `systemd-timesyncd.service` | reemplaza con cron-like + ntpd one-shot. |
|
||
| `systemd-machined.service` | reemplazado por `ente-machined-compat`. |
|
||
| `systemd-logind.service` | reemplazado por `ente-logind-compat`. |
|
||
| `systemd-resolved.service` | reemplazado por `ente-resolved-compat`. |
|
||
| `systemd-hostnamed.service` | reemplazado por `ente-hostnamed-compat`. |
|
||
| `systemd-timedated.service` | reemplazado por `ente-timedated-compat`. |
|
||
| `systemd-localed.service` | reemplazado por `ente-localed-compat`. |
|
||
| `systemd-journald.service` | reemplazado por `ente-journald-compat`. |
|
||
| `systemd-tmpfiles-*.service` | reemplazado por `ente-tmpfiles-compat`. |
|
||
| `systemd-binfmt.service` | reemplazado por `ente-binfmt-compat`. |
|
||
| `polkitd.service` | reemplazado por `ente-polkit-compat`. |
|
||
|
||
Las últimas 12 ya están como Cards en `seeds/arje-host.card.json`. Las
|
||
de la mitad de arriba **no** — no las agregues.
|
||
|
||
### 3b. Servicios que SÍ necesitás declarar (y que ya están en `arje-host.card.json`)
|
||
|
||
| Card label | reemplaza | binario |
|
||
| ------------------ | -------------------------- | -------------------------------------------- |
|
||
| `tmpfiles-boot` | `systemd-tmpfiles-setup` | `/usr/sbin/ente-tmpfiles-compat --boot` |
|
||
| `mount-fstab` | `local-fs.target` | `/bin/mount -a` (one-shot) |
|
||
| `swap-on` | `swap.target` | `/sbin/swapon -a` (one-shot) |
|
||
| `dbus-system` | `dbus.service` | `/usr/bin/dbus-daemon --system --nofork` |
|
||
| `compat-*` (×11) | shims D-Bus de systemd | `/usr/sbin/ente-*-compat` |
|
||
| `network-dhcpcd` | `NetworkManager` / `networkd` | `/usr/sbin/dhcpcd -B` |
|
||
| `sshd` | `ssh.service` | `/usr/sbin/sshd -D` |
|
||
| `getty-tty1` | `getty@tty1.service` | `/sbin/agetty --noclear tty1 linux` |
|
||
|
||
15 entes. Compará: una instalación Debian fresh con GNOME desktop tiene
|
||
**>180 unidades activas** tras `systemctl list-units --state=running`.
|
||
|
||
### 3c. Cómo agregar un servicio que falta
|
||
|
||
1. Encontrá el binario y los argumentos. Mirá el `.service` original:
|
||
```
|
||
systemctl cat <unit>
|
||
```
|
||
Mirá `ExecStart=` y reproducí.
|
||
|
||
2. Agregá una Card al `genesis` de `/ente/seed.card.json`:
|
||
```json
|
||
{
|
||
"schema_version": 1,
|
||
"id": "<ULID nuevo, Crockford base32, no I L O U>",
|
||
"label": "mi-servicio",
|
||
"provides": [], "requires": [],
|
||
"permissions": { "networking": "outbound", "filesystem": "read-write", "ipc": { "allow": [] }, "processes": true },
|
||
"soma": { ... idéntico al patrón de los otros ... },
|
||
"payload": { "Native": { "exec": "/usr/sbin/mi-bin", "argv": ["--foreground"], "envp": [] } },
|
||
"supervision": { "Restart": { "initial": 500, "max": 30000 } },
|
||
"lifecycle": "daemon",
|
||
"priority": "normal",
|
||
"flow": { "input": [], "output": [] },
|
||
"genesis": []
|
||
}
|
||
```
|
||
**Importante**: `argv` tiene que tener el equivalente a `--foreground` o
|
||
`-D` o `--no-fork`. Si el binario se daemoniza solo (fork+exit), arje
|
||
no lo puede supervisar y caerá en loop de restart.
|
||
|
||
3. Validar:
|
||
```bash
|
||
seeds/validate.sh /ente/seed.card.json
|
||
```
|
||
|
||
4. Reboot a arje. Verificar:
|
||
```sh
|
||
arje# brahman-status
|
||
```
|
||
|
||
## 4. Diferencias de UX con systemd
|
||
|
||
| acción | systemd | arje |
|
||
| ---------------------------- | ------------------------------------ | ------------------------------------------------------- |
|
||
| Ver servicios activos | `systemctl list-units --state=running` | `brahman-status` + `busctl list-entes` |
|
||
| Ver logs | `journalctl -u foo` | `ente-journalctl` (TODO en CLI; por ahora `dmesg`) |
|
||
| Restart un servicio | `systemctl restart foo` | matar el PID (`brain` lo reanuda por `Supervision`) |
|
||
| Habilitar/Deshabilitar | `systemctl enable/disable` | editar `/ente/seed.card.json` + reboot |
|
||
| Ver dependencias | `systemctl list-dependencies foo` | `brahman-status` muestra references y flow matches |
|
||
| Detectar fallas reincidentes | `systemctl status foo` → "failed" | `brainctl crystals` muestra patrones `EnteDied → …` |
|
||
| socket activation | `.socket` unit | (no implementado todavía; ver flow + broker matching) |
|
||
| user services | `--user` | (no implementado; arje es system-wide por ahora) |
|
||
|
||
## 5. Cosas que arje todavía NO hace que sí hace systemd
|
||
|
||
- **udev / hotplug**: arje captura uevents (vía `spawn_uevent_stream`),
|
||
pero no hay reglas declarativas todavía. Para boot inicial, el
|
||
initramfs nativo ya hizo el cold-plug, así que no es bloqueante.
|
||
- **Timers** (cron-like): `ente-timer-compat` existe pero no está en la
|
||
seed host por default. Agregalo si querés `*.timer` units migradas.
|
||
- **Service generators**: no hay equivalente a `systemd-fstab-generator`
|
||
ni `systemd-getty-generator`. Tu seed los declara explícitamente.
|
||
- **Socket activation**: parcialmente — el broker matchea flow
|
||
consumer↔producer, pero no hay aún listen-on-fd-handoff como
|
||
`sd_listen_fds()`.
|
||
- **User instances**: no hay todavía `arje --user`. Los procesos de
|
||
usuario los lanza un getty/login estándar.
|
||
|
||
## 6. Checklist pre-primer-boot
|
||
|
||
- [ ] `seeds/arje-host.card.json` ajustada a tu hardware/red/users.
|
||
- [ ] `seeds/validate.sh` → OK.
|
||
- [ ] `/usr/bin/dbus-daemon` existe (de lo contrario los shims no se
|
||
anuncian — `apt install dbus`).
|
||
- [ ] `/usr/sbin/dhcpcd` o tu network stack preferido está instalado.
|
||
- [ ] `/usr/sbin/sshd` instalado y `/etc/ssh/sshd_config` ok si querés
|
||
poder entrar remoto si la consola falla.
|
||
- [ ] `/sbin/agetty` instalado (`util-linux`) para tty1.
|
||
- [ ] `sudo scripts/install-arje-as-init.sh` ejecutado.
|
||
- [ ] `grub-reboot` apuntado a la entrada arje **sólo para el próximo
|
||
boot** — no `grub-set-default`.
|
||
- [ ] Listo para revertir vía menú GRUB si algo falla.
|
||
|
||
## 7. Cuando arje sea estable: hacerlo default
|
||
|
||
Cuando ya hayas booteado arje N veces sin problemas y querés que sea
|
||
default:
|
||
|
||
```bash
|
||
# Listar entradas
|
||
sudo grep -E "^\s*menuentry " /boot/grub/grub.cfg | nl
|
||
|
||
# Ponerlo como default (usa el índice del menú o el title exacto)
|
||
sudo grub-set-default "arje (init=/sbin/ente-zero) — kernel $(uname -r)"
|
||
```
|
||
|
||
Mantené la entrada systemd como rescue. Si querés acortar la lista de
|
||
units que systemd arrancaría si bootea de nuevo: `systemctl disable
|
||
ModemManager bluetooth cups …` — los nombres de §3a son una guía.
|
||
|
||
## Apéndice — depurar el primer boot fallido
|
||
|
||
Si arje no levanta el primer boot:
|
||
|
||
1. Volver a GRUB → entrada systemd → bootea normal.
|
||
2. `journalctl --boot=-1` (logs del intento previo).
|
||
3. Buscar `ente-zero` o `Tarjeta Semilla`. Si nada → el ejecutable no
|
||
se lanzó (kernel panic temprano, falta `init=` correcto, etc.).
|
||
4. Si arrancó pero falló alguna Card: la próxima vez, antes de reboot:
|
||
```bash
|
||
sudo sed -i 's/console=tty1/console=tty1 ente.log=trace/' /etc/grub.d/40_custom
|
||
sudo update-grub
|
||
```
|
||
y revisá `dmesg` desde systemd tras el reintento.
|