Files
brahman/docs/arje-replace-systemd.md
T
sergio ca5dd04176 feat(arje): reemplaza systemd en máquina real con coexistencia GRUB
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>
2026-05-18 20:01:12 +00:00

11 KiB
Raw Blame History

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

# 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:

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:

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

    seeds/validate.sh /ente/seed.card.json
    
  4. Reboot a arje. Verificar:

    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:

# 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:
    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.