plan: botar busybox — 401 applets repartidos, y el trabajo real son 23
Medido contra el artefacto sellado (401 symlinks) cruzado con build-state.json: 148 applets ya tienen dueño sellado Y en perfil, 125 tienen dueño sellado que no viaja en ninguna imagen (uutils, brush, findutils, diffutils, arje-zero, hammerd), y de los 128 sin dueño 105 son basura que se borra. Dos hallazgos que cambian el plan: busybox entra a los 7 perfiles como dep de BUILD de 24 recetas, no como raíz de producto en targets.toml; y brush (shell Rust) ya está sealed, sin validar y sin perfil. docs/state/busybox-applets.tsv es la tabla applet-por-applet (generada), con 24 parejas marcadas NO-drop-in. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
# Plan — botar busybox, MEDIDO
|
||||
|
||||
**Fecha:** 2026-09-21 · **Instrumento:** los 401 symlinks de applet del artefacto sellado
|
||||
`2a2b1280…-busybox` (contados en el store del worker), cruzados con el estado y la membresía de
|
||||
perfil de cada receta en `docs/state/build-state.json` y con `grep '[deps]' recipes/*.toml`.
|
||||
**Tabla applet-por-applet:** `docs/state/busybox-applets.tsv` (401 filas, generada, no escrita a mano).
|
||||
|
||||
## Por qué existe este documento
|
||||
|
||||
La pregunta fue «¿qué hay que hacer para botar busybox?», con la sospecha de que faltaba escribir
|
||||
mucho userland en Rust. **El número dice lo contrario, y el hueco está en otro lado:**
|
||||
|
||||
> De los 401 applets, **148 ya tienen dueño sellado Y en perfil** (el applet es redundante en la
|
||||
> imagen), **125 tienen dueño sellado que NO viaja en ninguna imagen** (trabajo hecho y no cobrado),
|
||||
> y de los **128 sin dueño, 105 son basura que se borra**. El trabajo de escritura real son
|
||||
> **23 applets** repartidos en 8 piezas.
|
||||
|
||||
Es la lección de `foot` y del §D de `plan-barrido-perfiles-y-servicios.md` otra vez: `uutils`,
|
||||
`findutils`, `findutils-xargs`, `diffutils` y **`brush`** están todos `sealed` con `perfiles: []`.
|
||||
Se construyeron, se sellaron, y ninguna imagen los lleva.
|
||||
|
||||
## Son TRES frentes, no uno — y esto es lo que hace que «botar busybox» se lea como imposible
|
||||
|
||||
Mezclarlos es el error que cuesta el trabajo. Tienen dificultad, dueño y riesgo distintos:
|
||||
|
||||
| frente | qué es busybox ahí | dificultad |
|
||||
|---|---|---|
|
||||
| **A · Sandbox de build** | `[deps] build = [… "busybox" …]` en **24 recetas** — es el `sh`+coreutils que el sandbox del lab trae (`recipes/jq.toml:5-7`, `recipes/zlib-ng.toml:11`). Más el `/toolchain` mixto de Alpine con el swap de `docs/11-bootstrap.md:234-240` | media — no es runtime, no toca el producto |
|
||||
| **B · Runtime del producto** | los applets de `/bin` y `/sbin` de las imágenes. `USERLAND_COMPONENTS` (`crates/takana-bootstrap/src/lib.rs:406`) ya los ensombrece con los binarios Rust y retira el homónimo de los otros dirs (`lib.rs:676`) | **baja — la máquina ya está construida, falta munición** |
|
||||
| **C · Stage 1 / self-host / atestación** | `STAGE1_COMPONENTS` (`lib.rs:172`, el 4/4), `ATTEST_PATHS` como raíz-de-confianza (`lib.rs:465`), y el `exec` de las cards de `getty` y `sshd` (`lib.rs:429`, `:240`) | **alta — es el ADR 0008, y toca la evidencia de reproducibilidad `of_tree=9adefb82…`** |
|
||||
|
||||
⚠ **La membresía de perfil de busybox es del frente A, no del B.** `busybox` figura en `cli`,
|
||||
`servidor`, los cuatro escritorios y `metal-tigerlake`, pero **no está declarado como raíz en
|
||||
`docs/state/targets.toml`** (los únicos hits ahí son comentarios). Llega por la clausura, porque 24
|
||||
recetas lo declaran build-dep: `abseil-cpp chrony cronie crun dwarves firmware-tigerlake
|
||||
ia-modelo-embeddings intel-ucode jq libmnl libnftnl linux linux-firmware llama-cpp logrotate
|
||||
nftables opensmtpd popt protobuf qdrant sof-firmware valkey wireless-regdb zlib-ng`. **Sacarlo de
|
||||
las imágenes empieza por ahí y no requiere escribir ni una línea de userland.**
|
||||
|
||||
## El reparto de los 401
|
||||
|
||||
| estado del dueño | n | qué significa |
|
||||
|---|---|---|
|
||||
| **sellada + en perfil** | 148 | el reemplazo ya viaja en la imagen: el applet es redundante hoy |
|
||||
| **sellada, sin perfil** | 125 | construido, sellado, y no viaja: falta declararlo |
|
||||
| **sin dueño** | 128 | de los cuales **105 son `BORRAR`** y **23 son trabajo real** |
|
||||
|
||||
### 148 · Ya cubiertos y en la imagen — el applet sobra
|
||||
|
||||
`util-linux` 48 · `iproute2` 19 · `procps-ng` 12 · `shadow` 14 · `kbd` 12 · `xz` 6 · `iputils` 5 ·
|
||||
`e2fsprogs` 5 · `bzip2` 3 · `cronie` 3 · `dhcpcd` 3 · `ncurses` 2 · `dosfstools` 2 · `chrony` 2 ·
|
||||
`vim` 2 · `less` `lsof` `pciutils` `usbutils` `mandoc` `socat` `patch` `binutils` `tar` `wget` 1 c/u.
|
||||
|
||||
⚠ **24 de estos 148 NO son drop-in**, y están marcados en el TSV. El caso grande es la red:
|
||||
`iproute2` da `ip`/`ss`, **no** `ifconfig`/`route`/`arp`/`netstat` — eso es net-tools, que no está en
|
||||
el corpus. Igual con `addgroup`/`adduser` (shadow da `groupadd`/`useradd`), `udhcpc` (dhcpcd es otro
|
||||
cliente con otro contrato de hooks) y `nc` (socat tiene otra sintaxis). **Cada uno de esos 24 es una
|
||||
migración de llamadores, no un borrado.** Mirar quién los invoca en `scripts/` y en las cards antes.
|
||||
|
||||
### 125 · Sellados y sin viajar — la munición que ya está fabricada
|
||||
|
||||
| dueño | n | qué cubre |
|
||||
|---|---|---|
|
||||
| `uutils` | 90 | todo el núcleo POSIX: `ls cp mv rm cat head tail sort cut tr wc stat date dd chmod chown …` |
|
||||
| `arje-zero` | 20 | `init linuxrc runlevel halt poweroff reboot run-init switch_root` + los 12 de runit (`runsv sv svc svlogd chpst envdir setuidgid softlimit start-stop-daemon …`) |
|
||||
| `hammerd` | 4 | `syslogd klogd logger logread` |
|
||||
| `brush` | 3 | **`sh ash hush`** |
|
||||
| `gzip` | 3 | `gzip gunzip zcat` |
|
||||
| `diffutils` | 2 | `diff cmp` |
|
||||
| `findutils` / `findutils-xargs` / `libarchive` | 3 | `find` · `xargs` · `cpio` |
|
||||
|
||||
**Los 24 de `arje-zero` y `hammerd` no cuestan nada**: el reemplazo ya corre en el producto. Son
|
||||
applets instalados que nadie ejecuta. Salen del `defconfig` y se prueba que la imagen arranque igual.
|
||||
|
||||
### 128 · Sin dueño
|
||||
|
||||
**105 se borran** (no se reemplazan, salen del `defconfig`): `dpkg dpkg-deb rpm rpm2cpio httpd ftpd
|
||||
ftpget ftpput tftp tftpd telnet telnetd inetd dnsd sendmail popmaildir lpd lpq lpr ubi* nand*
|
||||
nbd-client i2c* hdparm fdformat setserial powertop bootchartd fbsplash conspy ether-wake udhcpd
|
||||
udpsvd tcpsvd watchdog whois tree which unzip …` (lista completa en el TSV).
|
||||
|
||||
**23 son el trabajo real:**
|
||||
|
||||
| pieza | applets | qué es |
|
||||
|---|---|---|
|
||||
| **kmod** | `insmod rmmod lsmod modprobe depmod modinfo` (6) | no hay receta kmod en el corpus. En Rust: `finit_module` + parseo de `modules.dep`. Bien acotado |
|
||||
| **psmisc** | `killall killall5 pstree fuser` (4) | **`procps-ng` NO los trae.** Traer receta psmisc (C) o escribir |
|
||||
| **grep POSIX** | `grep egrep fgrep` (3) | `grep` GNU está sellado (sin perfil) como puente. El Rust nativo se escribe sobre `grep-matcher`/`grep-regex`/`grep-searcher` |
|
||||
| **gestor de /dev** | `mdev makedevs uevent` (3) | decisión: lo absorbe arje, o entra una receta eudev/mdevd |
|
||||
| **bc** | `bc dc` (2) | traer receta |
|
||||
| **getty** | `getty cttyhack` (2) | hoy la card de getty ejecuta `/bin/busybox` (`lib.rs:429`) |
|
||||
| **awk** | `awk` (1) | decisión abierta, ver abajo |
|
||||
| **sed** | `sed` (1) | `sed` GNU sellado y en los 7 perfiles; el Rust nativo está por escribir |
|
||||
| **dns** | `nslookup` (1) | traer receta bind-tools/doggo |
|
||||
|
||||
## Las tres clases de reemplazo — y cuándo forkear (nunca)
|
||||
|
||||
La pregunta era si hay que forkear los que «existen pero no son exactos». **No, y hay tres clases
|
||||
distintas que conviene no mezclar:**
|
||||
|
||||
**1 · Adopción exacta.** `uutils/coreutils`, `uutils/findutils`, `uutils/diffutils`: reimplementación
|
||||
madura, MIT, receta Cargo con commit pinneado (ADR 0006), multicall. El patrón está probado y
|
||||
documentado en la cabecera de `recipes/uutils.toml`. Lo que falta de esta clase es
|
||||
**`uutils/procps`** y **`uutils/util-linux`** — receta calcada, coste bajo. ⚠ **Verificar su estado
|
||||
upstream antes de prometer los 60 applets que cubrirían**: `util-linux` en Rust era embrionario.
|
||||
|
||||
**2 · «Existe pero no es exacto» — y forkear es la trampa.** `ripgrep` no es `grep` POSIX, `sd` no es
|
||||
`sed`, `fd` no es `find` (por eso la receta buena es `uutils/findutils` y no `fd`), `procs` no es
|
||||
`ps`, `bottom` no es `top`, `helix` no es `vi`. Todas están selladas y en los 7 perfiles, y **ninguna
|
||||
sirve de reemplazo**: cambian las banderas. Forkear ripgrep para que hable POSIX es peor que
|
||||
escribir — heredás su arquitectura de recursión+ignore y peleás contra ella para siempre. Lo correcto
|
||||
es usarlas **como librerías**: `grep-matcher`/`grep-regex`/`grep-searcher` están publicadas
|
||||
justamente para eso, y un `grep` POSIX encima son unos cientos de líneas.
|
||||
|
||||
**3 · `awk` es el caso feo, y hay que decidirlo, no resolverlo de paso.** `recipes/goawk.toml` existe
|
||||
y está sellada, pero **goawk es Go, no Rust** — no cumple la premisa de soberanía de cadena de la
|
||||
Etapa C. `frawk` es Rust pero es otro dialecto (JIT, semántica propia). `gawk` es C con extensiones
|
||||
GNU que los `configure` del corpus sí usan. Las tres opciones son legítimas y ninguna es gratis.
|
||||
|
||||
## El bloqueante, y la buena noticia
|
||||
|
||||
**Sin `sh` POSIX no se bota busybox en ningún frente**: es el `exec` de las cards, el `/bin/sh` del
|
||||
producto y el shell del sandbox. Lo esperado era que fuera un proyecto de meses.
|
||||
|
||||
**`recipes/brush.toml` (0.4.0, reubeno/brush, MIT, Rust, compat bash) está `sealed`.** Compila
|
||||
estático musl con zig cc y produce `/usr/bin/brush`.
|
||||
|
||||
⚠ **Y está sin validar como `sh`.** La receta es un import crudo de nixpkgs y lo dice en su primera
|
||||
línea: «PUNTO DE PARTIDA, no final». No está en ningún perfil, nadie lo ejecutó como `/bin/sh`, y el
|
||||
sandbox de build es el consumidor más duro que existe (`configure` de autotools). **La unidad de
|
||||
trabajo #1 es medirlo, no adoptarlo.**
|
||||
|
||||
## Orden de ataque
|
||||
|
||||
Cada paso es una unidad de trabajo con su criterio de terminado. Los tres primeros **no tocan el
|
||||
frente C** y se pueden hacer hoy.
|
||||
|
||||
1. **Recortar el `defconfig`.** Sacar los 105 de `BORRAR` + los 24 que `arje-zero`/`hammerd` ya
|
||||
reemplazan = **129 applets menos, cero código escrito**. Terminado cuando la imagen arranca, el
|
||||
E2E de QEMU pasa y `find . -type l | wc -l` baja de 401 a ~272.
|
||||
2. **Declarar la munición que ya existe.** `uutils`, `findutils`, `findutils-xargs`, `diffutils`,
|
||||
`brush`, `gzip`, `libarchive` a los perfiles de `targets.toml`. Terminado cuando `build-state.py`
|
||||
los marca con perfil y la imagen los lleva.
|
||||
3. **Medir `brush` como `/bin/sh`.** Correr con él los `configure` de tres recetas autotools del
|
||||
corpus y el `product-boot-test.sh`. Terminado con un veredicto escrito: sirve, o falla en X.
|
||||
4. **Las dos recetas de adopción baratas** (`uutils/procps`, `uutils/util-linux`), previa
|
||||
verificación de madurez upstream. Cubren ~60 applets de los 148 «ya cubiertos por C».
|
||||
5. **`grep` y `sed` POSIX en Rust.** Es la escritura de mejor relación coste/beneficio y desbloquea
|
||||
el `/toolchain` del frente A.
|
||||
6. **Migrar los 24 build-deps.** Cambiar `busybox` por el conjunto real en `[deps] build` de las 24
|
||||
recetas. Terminado cuando `busybox` sale de los 7 perfiles.
|
||||
7. **La cola larga:** kmod, psmisc, gestor de /dev, getty, bc, dns. Después del shell, no antes.
|
||||
8. **ADR — `awk`**, y **ADR — el frente C** (sustituir busybox en `STAGE1_COMPONENTS` y
|
||||
`ATTEST_PATHS`). No antes de que 1–7 estén cerrados.
|
||||
|
||||
## La métrica, y el guardián que falta
|
||||
|
||||
`docs/state/busybox-applets.tsv` convierte esto en un número que baja: **401 → 272 → … → 0**.
|
||||
Regenerarlo es contar los symlinks del artefacto sellado y cruzarlos con `build-state.json`.
|
||||
|
||||
**Falta un guardián**, y sin él el paso 1 se deshace solo: un script que falle si alguna receta,
|
||||
script o card invoca un applet que ya salió del `defconfig`. Hoy nada lo detectaría — el fallo
|
||||
aparecería en boot, en otra máquina, semanas después.
|
||||
|
||||
## Lo que este documento NO midió (y hay que decirlo así)
|
||||
|
||||
- **El reparto applet→destino es juicio, no medida.** Lo medido son los 401 symlinks, el estado
|
||||
`sealed`/perfiles de cada receta y las 24 recetas con `busybox` en `[deps] build`. La asignación de
|
||||
dueño la escribí yo y hay que revisarla receta a receta.
|
||||
- **No se verificó la equivalencia de banderas de ninguna pareja.** Los 24 marcados `NO-drop-in` son
|
||||
los que sé que difieren; el resto dice `sin-verificar` y eso es literal, no una forma de decir «va
|
||||
a andar».
|
||||
- **No se midió qué applets usa el producto de verdad.** El `defconfig` instala 401 porque nadie
|
||||
eligió; cuáles se ejecutan en una imagen viva no está medido. Eso abarataría el paso 1.
|
||||
- **`uutils/procps` y `uutils/util-linux` no se verificaron upstream.** Los ~60 applets que les
|
||||
atribuyo son una hipótesis de trabajo.
|
||||
Reference in New Issue
Block a user