Files
Sergio a51d49a342 servicios de paquete: la receta ya sabe declarar su demonio — y el card sale IGUAL al hardcodeado
Un paquete con servicio no tenía dónde decirlo: los dos del producto (hammerd,
sshd) vivían en constantes de Rust y los ocho de una sesión GNOME se lanzaban
con `&` desde un script, sin supervisión ni backoff ni el CRASHED real — o sea
sin nada de lo que arje es PID 1 para dar.

`[[service]]` en la receta, fuera de `hash_inputs` como license/slots/evidence:
declarar el servicio de openssh NO movió su hash, medido con el binario viejo
(que ignora el bloque) contra el nuevo, b3:938835e6… en los dos.

La prueba que autoriza el cambio no es que "parezca bien": el card generado
desde recipes/openssh.toml se compara ENTERO contra SSHD_SERVICE_CARD, que es
el que ya bootea en QEMU. Empatan ⇒ mover el servicio a la receta no cambiaría
un byte de la seed ni del product_rootfs_hash.

Por qué no alcanzaba `arje-absorb` (que existe y traduce systemd/openrc/runit/
dinit/sysvinit): sólo absorbe lo HABILITADO, los symlinks de <target>.wants/.
Un rootfs nuestro no tiene ese estado — medido: 62 .service sellados en el
store y CERO directorios .wants. Absorber devuelve vacío, y es la respuesta
correcta a la pregunta que absorb contesta. El enable de una distro construida
desde fuente no se lee del árbol: se declara.
2026-09-12 10:33:40 +00:00

102 lines
6.9 KiB
TOML

# OpenSSH 10.3p1 — servidor/cliente SSH desde fuente (Etapa C, LA excepción C del userland Rust).
#
# El recorrido de la Etapa C reemplaza busybox pieza por pieza con reimplementaciones Rust maduras
# (coreutils/findutils/diffutils/ripgrep/netup). OpenSSH es la EXCEPCIÓN deliberada: NO hay servidor
# SSH en Rust con la madurez/auditoría de OpenSSH (russh/thrussh son librerías cliente, no un sshd de
# producción), así que la red de servicios se apoya en el OpenSSH portable canónico, construido desde
# fuente con takana (no `apk add openssh`). Provee sshd + ssh + ssh-keygen + scp/sftp.
#
# DE-ALPINIZADO: build vainilla del tarball portable upstream (cdn.openbsd.org), sin los parches de
# Alpine (que son para PAM/login.conf/utmp, que aquí NO se usan). Es un SERVICIO del producto, no un
# input del 4/4 (of_tree) ni una herramienta del toolchain ⇒ no necesita casar byte-a-byte con nada;
# el criterio es "construye y corre".
#
# DEPS (deps.build → materialize como capas overlay en /usr del sandbox):
# - openssl (recipes/openssl.toml): libcrypto.a estática + headers. sshd enlaza la cripto.
# - zlib (recipes/zlib.toml): libz.a estática. compresión opcional del transporte.
# Ambas son no-shared (sólo .a) ⇒ el enlace de OpenSSH es estático por construcción contra ellas; se
# fuerza LDFLAGS=-static para un sshd/ssh completamente estáticos musl (sin .so que resolver, robusto
# para un daemon de arranque temprano). getpwnam/getservbyname en musl estático funcionan sin NSS.
#
# COMPILADOR: zig cc con zig 0.13.0 (NO 0.16). Igual que openssl/binutils/python3: zig 0.14+ MISCOMPILA
# binarios musl (regresión 0.14, bisect con flex) y 0.13.0 la esquiva. Coherente además con que
# libcrypto.a se compiló con el mismo zig 0.13.0.
#
# RUNTIME (NO build — se cablea en la card de arje / init, ver Etapa C "servicios"):
# - usuario de privsep `sshd` en /etc/passwd y el dir /var/empty (root:root, 0711). La privsep es
# OBLIGATORIA en OpenSSH 10.x.
# - host keys: generarlas con `ssh-keygen -A` en el primer arranque (por eso el install es
# `install-nokeys`: NO generamos claves en build-time — serían no-reproducibles y secretas).
#
# GOTCHA install: `make install` normal corre host-key + check-config (invoca ssh-keygen/sshd y haría
# chown a un usuario inexistente fuera de root) ⇒ se usa `install-nokeys`. STRIP_OPT="" desactiva el
# stripping en la instalación (no dependemos de un `strip` concreto en el sandbox; el binario zig ya
# sale razonable, y de-strippear es cosmético para un servicio).
name = "openssh"
version = "10.3p1"
# licencia: LICENCE del tarball pineado. Es una MEZCLA que el propio fichero enumera —BSD-2/3-Clause, ISC, MIT y dominio público— con el resumen «all components are under a BSD licence, or a licence more free than that». Se entrega el fichero entero en vez de una expresión compuesta: la enumeración de upstream no necesita interpretación nuestra
license = "LicenseRef-openssh"
[source]
tarball = "https://cdn.openbsd.org/pub/OpenBSD/OpenSSH/portable/openssh-10.3p1.tar.gz"
sha256 = "56682a36bb92dcf4b4f016fd8ec8e74059b79a8de25c15d670d731e7d18e45f4"
[build]
compiler = "zig-cc"
target = "x86_64-linux-musl"
link = "static"
zig_version = "0.13.0"
[deps]
build = ["zlib", "openssl", "perl"]
[build.phases]
# --sysconfdir=/etc/ssh: sshd_config/ssh_config viven en /etc/ssh. --with-ssl-dir/--with-zlib=/usr:
# donde las deps overlayean sus .a + headers. --without-pam/--without-selinux: musl sin PAM, sin SELinux.
# --with-privsep-path=/var/empty: jaula de privsep. LDFLAGS=-static: binarios musl ESTÁTICOS completos
# (el target sólo trae el loader musl, no .so de zlib/openssl). --without-hardening: OBLIGATORIO con
# -static. OpenSSH activa PIE (-fPIE/-pie) por defecto vía "toolchain hardening"; `-pie` GANA sobre
# `-static` en zig cc/lld ⇒ sin esto el binario sale dinámico (PIE). configure desactiva el PIE
# automático justo cuando el hardening está off (configure ~L16057: "Turn off automatic PIE when
# toolchain hardening is off"). Se sacrifica stack-protector/RELRO del servicio — re-endurecer luego.
#
# GOTCHA ZIG -static + .so del devfs: el devfs Alpine trae `libz.so` en /usr/lib (zlib runtime base),
# y `zig cc -static -lz`, con .a y .so presentes, PREFIERE el .so y enlaza DINÁMICO igual (NEEDED
# libz.so.1) — verificado: `-l:libz.a` da estático puro, `-lz` no. libcrypto se libra sólo porque el
# devfs no trae libcrypto.so. Para robustez explícita forzamos AMBOS al archivo estático exacto vía
# `sed` sobre `CHANNELLIBS` del Makefile (único sitio con `-lcrypto`/`-lz`): los .a vienen de las deps
# overlay en /usr/lib.
#
# GOTCHA ZIG -static + libc NATIVO: el sandbox exporta CC="zig cc -mcpu=baseline" SIN `-target`, o sea
# zig nativo (Alpine musl). En modo nativo `zig cc -static` enlaza el `libc.so` del SISTEMA igual
# (NEEDED libc.so) en vez del musl embebido de zig — todo lo demás queda estático pero libc no.
# Override del CC con `-target x86_64-linux-musl`: fuerza el musl bundle de zig en modo estático ⇒
# binario 100% estático (sin interpreter, sin NEEDED). Mismo triple ABI ⇒ enlaza con las .a nativas
# (libcrypto.a/libz.a). Se fija en configure (autotools sustituye CC en el Makefile ⇒ compile+link).
configure = "./configure --prefix=/usr --sysconfdir=/etc/ssh --with-ssl-dir=/usr --with-zlib=/usr --with-privsep-path=/var/empty --without-pam --without-selinux --without-hardening CC='zig cc -mcpu=baseline -target x86_64-linux-musl' LDFLAGS=-static && sed -i '/^CHANNELLIBS=/{s/-lcrypto/-l:libcrypto.a/g;s/-lz/-l:libz.a/g}' Makefile"
compile = "make -j\"$(nproc)\""
install = "make install-nokeys DESTDIR=/out STRIP_OPT="
# ── EL SERVICIO QUE ESTE PAQUETE TRAE ───────────────────────────────────────────────────────────
# Hasta hoy esta Card vivía HARDCODEADA en `takana_bootstrap::SSHD_SERVICE_CARD`, una constante de
# Rust: el paquete que trae el demonio no tenía dónde decirlo, así que lo decía el binario de takana.
# El `id` es el ULID que esa constante ya usaba — **cambiarlo movería la seed de producto y con ella
# el `product_rootfs_hash`**, así que se preserva tal cual, no se regenera.
#
# El `argv` hace tres cosas antes del `exec` porque arje NO TIENE readiness por unidad (ver
# `takana_core::service`): levanta la red, genera las host keys al primer boot, y recién ahí encarna
# sshd. Eso no es una maña de esta receta — es el patrón obligado mientras el hueco siga abierto, y
# por eso está a la vista en el argv en vez de escondido en un wrapper.
#
# Fuera de `hash_inputs`: declarar esto NO re-hashea openssh.
[[service]]
label = "sshd"
id = "01HQAR53D4M2NBV8KZTYXFQA12"
exec = "/bin/busybox"
argv = ["sh", "-c", "/usr/bin/netup; /usr/bin/ssh-keygen -A; exec /usr/sbin/sshd -D -e"]
envp = [["HOME", "/root"]]
networking = "full"
cgroup = "arje.slice/sshd"
restart = { initial_ms = 500, max_ms = 20000 }