1# `takana-os.net` — el sitio del proyecto
2
3Una sola página, autocontenida, en dos idiomas y dos temas. **No le pide nada a terceros:** las
4tipografías de la marca (Chivo e IBM Plex, las dos SIL OFL) viajan acá en `fuentes/`, subconjuntos
5`latin` y `latin-ext`, 154 KB en total. Un sitio sobre no depender de binarios ajenos que cargara
6las fuentes de Google en cada visita sería una contradicción barata.
7
8| | |
9|---|---|
10| `index.html` | la página entera: CSS y JS adentro, el símbolo como `<symbol>` SVG inline |
11| `fuentes.css` + `fuentes/` | las ocho caras `@font-face`, servidas de acá |
12| `simbolo.svg` | el favicon — el martillo tocapu 7×7, derivado de `docs/marca/simbolo-fondo-claro.svg` |
13
14## Las cuatro secciones
15
16`01 descargar` · `02 las decisiones` · `03 los componentes` · `04 el código`.
17
18**`03` es una ficha de distro, no un semáforo** (2026-09-23, a pedido del usuario: «que no presuma
19inmadurez»). Antes era una tabla con 🟢/🟡/⚪ y una fila que decía «distro redonda — todavía no»:
20una página que le cuenta al visitante lo que le falta antes que lo que tiene. Ahora son tres tablas
21al estilo DistroWatch — el sistema (init, libc, compilador, kernel, gráficos, audio, almacén,
22catálogo), los cinco escritorios con su versión y su clausura sellada, y los paquetes principales
23con su número de versión.
24
25⚠ **Esas versiones salen de las recetas y hay que refrescarlas.** Están a mano en el HTML: si una
26receta sube de versión, la página miente hasta que alguien la toque. Se sacan así:
27
28```sh
29for r in bash coreutils git curl openssl python3 go rust llvm21 firefox …; do
30 printf '%-12s %s\n' "$r" "$(grep -m1 '^version' recipes/$r.toml | sed 's/.*= *"//; s/"//')"
31done
32python3 -c "import json;d=json.load(open('docs/state/build-state.json'));print(d['totals'])"
33ls /store | wc -l
34```
35
36**`04` junta lo que eran dos secciones** («los repositorios» y «cómo se llama»): las dos direcciones,
37el reparto del repo y los tres bloques de comandos son el mismo asunto — dónde vive y cómo se llama.
38
39## La sección «las decisiones»
40
41**Trece fichas plegables** (`<details>`, sin una línea de JavaScript), cada una con tres lecturas de
42lo mismo: contra qué se decide, qué implica por dentro, y qué quiere decir sin jerga. Nacen cerradas
43a pedido del usuario (2026-09-23): trece fichas abiertas son una pared, y la aseveración del título
44basta para elegir cuál abrir.
45
46Las secciones «los dos pisos» y «la frontera» se fundieron aquí como fichas 01 y 03 — eran el mismo
47material contado dos veces, en una sección y en una ficha. Y entraron dos que faltaban: **la
48instalación** (un archivo, sin ISO y sin red) y **el escritorio propio + tawasuyu**, que son lo que
49alguien de fuera pregunta primero. **No son opinión: cada una sale de un ADR o de una decisión ya tomada
50en `docs/`** — fuentes clavadas (ADR 0006), `zig cc` (0003), `arje` de init (0007) y el arranque por
51grafo (0010), multi-origen con índice firmado (0014), imágenes ajenas enjauladas (0015), y las de
52Wayland-only y PipeWire, que viven en los frentes de escritorio y audio.
53
54⚠ **Si una decisión cambia, esta página miente hasta que alguien la toque.** Es el precio de
55explicar el diseño en una landing y no en un doc generado: se gana claridad y se paga en
56mantenimiento. Las fichas se armaron con un guion de un solo uso — la página es la fuente, no hay
57generador que mantener.
58
59La paleta y la tipografía salen de **`docs/marca/README.md`** y no se inventan acá. La única
60desviación, deliberada: en tema claro el texto de acento usa un brasa oscurecido (`#9E3F16`), porque
61el `#DF6B2A` de la marca sobre alpaca da 2,9:1 y no se lee. Las combinaciones de la página están
62medidas: ninguna baja de 4,5:1.
63
64## El instalador: `/descargas/` vive FUERA del repo
65
66`https://takana-os.net/descargas/takana-instalador-<variante>.sh` es **un fichero que trae todo
67adentro**:
68cabecera `sh` + un `tar.gz` pegado detrás con el binario `takana`, el instalador que corre en RAM, su
69kernel y el censo del terreno. Se baja, se le da permiso y se corre; no baja nada más.
70
71⚠ **No está en el repo, y no puede estarlo.** Son 137 MiB: en git serían 137 MiB para siempre en
72cada clon, y además **el espejo de GitHub rechaza cualquier fichero de más de 100 MiB** — o sea que
73el `push` se rompería y nadie relacionaría la causa con el sitio. Vive en `/work/sergio/descargas`,
74que está en `sdc` (255 G) y no en la raíz de 5,8 G, que se llena y tira la caja entera.
75
76```sh
77scripts/instalador-paquete.sh # arma el paquete (y comprueba su coherencia)
78scripts/instalador-autoextraible.sh # lo convierte en UN fichero, y lo desempaqueta para probarlo
79cp work/variantes/*.sh work/variantes/*.img /work/sergio/descargas/
80( cd /work/sergio/descargas && for f in takana-instalador-*; do case "$f" in *.sha256) ;; *) sha256sum "$f" > "$f.sha256";; esac; done )
81```
82
83El `.sha256` que se publica al lado es **el del fichero entero**, para comprobar la descarga
84(`sha256sum -c takana-instalador.sh.sha256`). Es distinto del que el `.sh` lleva adentro, que es
85el **del payload** y lo verifica él solo antes de desempaquetar (`./takana-instalador.sh --sha256`
86lo imprime).
87
88⚠ **El hash NO se escribe en `index.html`, y es deliberado.** La primera versión lo traía pegado en
89la página y habría empezado a mentir en el primer rebuild — que es el modo de fallo que este mismo
90README ya avisa para la sección «las decisiones». La página muestra el comando; el número lo sirve
91el fichero de al lado, que se regenera junto al `.sh`.
92
93⚠ **La extensión es `.sh` y no `.run`, y por eso el vhost fuerza `Content-Disposition: attachment`.**
94`.sh` dice lo que el fichero ES —una cabecera de shell con el sistema pegado detrás, legible con
95`head` antes de correrlo— pero invita al navegador a MOSTRARLO: 580 MiB de binario volcados en una
96pestaña. La cabecera lo cierra para las siete descargas a la vez y no depende de que Caddy adivine
97el tipo (hoy no manda ningún `Content-Type` para estos ficheros).
98
99⚠ `/descargas` queda **fuera del `encode`** del vhost: el `.sh` ya viene comprimido, y volver a
100comprimirlo por HTTP gasta CPU en cada descarga sin ahorrar un byte.
101
102### En la página: los enlaces NO están en el HTML
103
104⚠ **Ninguna URL de descarga aparece en `index.html`** (a pedido del usuario, 2026-09-23): el
105selector de variante las compone en JavaScript al pulsarlo, y el path se arma por trozos
106(`'/de'+'scargas/'`). Un rastreador que lee el documento no encuentra una sola, y estos ficheros
107pesan de 138 MiB a 3,4 GiB — no son para que se los baje un bot. Se comprueba con
108`grep -c "descargas/takana-instalador" index.html` ⇒ **0**.
109
110El precio: **sin JavaScript no hay descargas**. Por eso hay un `<noscript>` con la ruta en TEXTO
111PLANO, no como enlace — un enlace ahí devolvería el problema que esto evita.
112
113**Los pesos se corrigen solos.** La tabla en JS (`PESO`) es el valor medido al publicar; al pintar
114cada variante se dispara un `HEAD` y, si responde, el peso real lo pisa. Así una reconstrucción del
115instalador no deja la página mintiendo — que ya pasó: la tabla anterior decía 3,2 y 3,5 GiB cuando
116los ficheros eran 3,1 y 3,4. Si el `HEAD` falla, queda el medido; nunca en blanco.
117
118**El botón de copiar lo pone el JS**, uno por cada `.codigo`, y copia sin el `$` del prompt. No hay
119que escribirlo diez veces en el HTML. Vive en una **banda propia arriba del bloque**
120(`padding-top:2.7rem`) y no flotando sobre la primera línea: un comando largo hace scrollear el
121`<pre>` por debajo del botón, y ahí sí lo pisaría.
122
123**Todo del mismo tamaño, a propósito** (2026-09-23): el selector es `grid-template-columns:1fr 1fr`
124con `grid-auto-rows:1fr` —siempre dos y dos, las cuatro del mismo alto— y los bloques de comandos
125llevan `min-height:7.8rem`, que es lo que ocupan tres líneas con su relleno. Por eso `full`, que no
126tiene archivo único, **no esconde el bloque**: lo deja del mismo tamaño con el aviso adentro. Si se
127escondiera, la página saltaría al elegirlo.
128
129### La prosa del instalador salió de la página, a propósito
130
131El usuario pidió quitar la sobreexplicación (2026-09-23): la sección de descargas es ahora el
132selector, los comandos y nada más. Lo que se quitó **no se perdió** — vive donde se ejecuta:
133`--check`/`--survey`/`--extraer` y el sha256 del payload en `scripts/instalador-autoextraible.sh`,
134`takana.console` en `scripts/instalador-initramfs.sh`, el kexec y las dos confirmaciones en
135`docs/32-instalador-de-usuario.md`, y «el medio desde el que arrancaste no se ofrece» en
136`scripts/takana-install-partition.sh:291`. La página lo estaba repitiendo, no guardando.
137
138### Las variantes: qué sistema se instala
139
140`scripts/instalador-variantes.sh [servidor|mirada|full]` arma las tres, en sus dos medios.
141La diferencia es **qué rootfs se copia al disco**, no cómo: el instalador y el censo son los mismos.
142
143| variante | composición | `.sh` | USB |
144|---|---|---|---|
145| `base` | sólo el piso de arranque | 81 MiB | 357 MiB |
146| `mirada` | + `[perfil.base]` + `[perfil.escritorio-mirada]` | 310 MiB | 1018 MiB |
147| `servidor` | `.sh`: `base` + `servidor-minimo` · USB: `base` + `servidor` | 386 MiB | 2,4 GiB |
148| `full` | + `base` + `cli` + `servidor` + **los cinco escritorios** | **no hay** | 8,6 GiB |
149
150**Todo lo publicado va SIN SÍMBOLOS DE DEPURACIÓN** (decisión del usuario, 2026-09-23). Medido por
151variante, sobre el rootfs que se instala:
152
153| | antes | después | |
154|---|---|---|---|
155| `base` | 265 MiB | 102 MiB | −61 % |
156| `mirada` | 1348 | 764 | −43 % |
157| `servidor-minimo` | 1456 | 933 | −35 % |
158| `servidor` | 2925 | 2113 | −27 % |
159| `full` | 17814 | 8002 | −55 % |
160
161⚠ **Esto NO contradice al [SDD 23](../../docs/23-plan-rehasheo.md)**, que dice —con razón— que un
162`strip` posterior no sirve: eso vale para el STORE, donde el `ArtifactHash` es de ENTRADA y quedaría
163apuntando a un contenido que un rebuild ya no reproduce. Lo que se stripea acá es una COPIA que no
164es un artefacto: no se sella, nadie verifica su hash contra una receta, y **el store conserva los
165símbolos intactos** para quien depure. `strip` es determinista, así que la imagen sigue siendo
166reproducible. `SIN_STRIP=1` lo apaga.
167
168⚠ Y hay una **guarda** antes de stripear: si el árbol compartiera inodos con el stage, stripear
169mutaría el store —y con él todos los artefactos que enlazan a ese fichero, en silencio y para
170siempre—. `cp -a` copia contenido y no enlaza al origen, pero eso es propiedad del comando de
171arriba, no de este bloque: si alguien lo cambia a `cp -al` por rapidez, el guardián grita antes de
172tocar nada.
173
174⚠ **«base» son DOS cosas con el mismo nombre y confundirlas da un rootfs que no arranca.** El
175*piso de arranque* es `product-rootfs` —arje-zero como PID 1 + busybox— y **no está en ningún
176perfil**; el *userland base* es `[perfil.base]`, 359 binarios, y **no tiene ni busybox ni
177arje-zero adentro** (medido: `ls base/bin`). Cada variante es `product-rootfs ∪ perfil.base ∪ …`.
178El [SDD 27 §1](../../docs/27-perfiles-instalables-y-el-instalador.md) ya había medido la otra
179mitad del mismo hueco: los escritorios no heredan `base`.
180
181⚠ **Cada perfil se hidrata en su propio árbol y después se funden**, y no uno encima de otro: la
182auditoría del bit de privilegio corre contra las declaraciones `[[setuid]]` del perfil que se está
183hidratando, así que al poner `escritorio-mirada` sobre `base` veía el `doas` 4755 que dejó `base`
184—declarado y legítimo— y lo denunciaba como un bit que nadie declaró.
185
186⚠ **`servidor` NO es la misma cosa en los dos medios, y es la misma física.** El `.sh` carga el
187sistema entero en memoria —es lo que le permite soltar el disco— así que cada MiB del rootfs es RAM
188exigida en la máquina de quien instala. Por ahí va `servidor-minimo`: **los mismos servicios uno por
189uno** (sshd, caddy, gitea, cron, hora, cortafuegos, los daemons propios), sin la cadena de compilar
190(`rust` 364 MiB, `lld21` 109) y sin heredar `cli` (54 raíces de toolbox interactivo). **1456 MiB
191contra 2925**, y el fichero baja de 1,2 GiB a 580 MiB. Por USB va `servidor` entero, que es el
192destino de verdad. No es una opinión sobre qué debe llevar un servidor: es una restricción del
193MEDIO, y está escrita en los dos sitios —acá y en el `descripcion` del perfil— para que nadie la
194lea como un recorte arbitrario.
195
196⚠ **GNOME estuvo fuera medio día y ya entra.** Se caía por dos cosas suyas, las dos medidas el
1972026-09-23 y las dos resueltas. `accountsservice` trae `/var/lib/AccountsService/users` en **0700**:
198hidratar como usuario normal no puede leerlo y el nodo no proyectaba — se arma con privilegio, que
199ya hacía falta igual por los 0500 de ModemManager. Y `usr/libexec/libgtop_server2` llega **4555**
200desde el tarball de upstream sin que ningún `[[setuid]]` lo declare: el aviso del auditor decía «o
201entra con su motivo, **o sale**» y hasta ese día sólo existía la primera mitad. Ahora
202`takana setuid --apply` **retira** el bit no declarado rompiendo el enlace duro, deja el store
203intacto y dice cuál quitó. Es la opción segura —quita privilegio, nunca lo da— y la coherente con el
204invariante: `of_tree` no hashea ese bit, así que un artefacto que lo trae está fuera de contrato.
205
206⚠ **La holgura del ext4 se escala con el árbol, no es un número fijo.** `du` mide el contenido, no
207lo que el ext4 NECESITA (inodos, journal, mapas de bloques): con 128 MiB fijos alcanzaba para 1 GiB
208y `mke2fs` murió con «Could not allocate block» a mitad de poblar un árbol de 16 GiB, después de
209veinte minutos de trabajo. Hoy es un 8 % del árbol, nunca menos de 128 MiB.
210
211⚠ **Armar una imagen con escritorios pide privilegio**, y las variantes chicas nunca lo tocaron:
212ModemManager instala sus `fcc-unlock.available.d/*` en **0500** tal como vienen de upstream, así que
213copiar el árbol como usuario normal falla. Se copia con `sudo` y **sin `chown`**: `cp -a` como root
214conserva dueño y modo, que es lo que la imagen tiene que llevar.
215
216⚠ **`full` no tiene `.sh`, y es física.** El fichero único carga el instalador entero en memoria
217—es lo que le permite soltar el disco— y ahí viven a la vez el initramfs comprimido y su contenido
218desempaquetado. Con 3,2 GiB de rootfs eso pide ~8 GiB de RAM en la máquina donde se instale. El USB
219no tiene ese límite: corre desde el propio medio. La guarda que lo detecta **mide la RAM de la
220máquina que construye, no la de la que instala** — acá acierta por casualidad (el hub tiene 7,6 GiB,
221parecido a un portátil) y queda anotado sin barrer.
222
223### La imagen USB, para cuando el salto no se puede
224
225`takana-instalador-usb.img` (519 MiB) es **el mismo instalador por otro camino**: mismo árbol, mismo
226censo, mismas guardas. Se graba con `dd` y arranca por la ruta de medios removibles del firmware.
227
228⚠ **No lleva el instalador en la ESP, y no es un capricho:** el cuelgue del primer viaje a metal fue
229un initrd de ~100 MiB cargado por el EFI-stub ([ADR 0010](../../docs/adr/0010-arranque-grafo-mirada.md)
230paso 2), y el del instalador son ~122 MiB. El USB usa el patrón que **sí** está probado —el de un
231disco instalado—: la ESP lleva el kernel y un pivote de 736 KiB, y el pivote encuentra la ext4 por
232`LABEL=takana-medio` y hace `switch_root`. La etiqueta NO es `hammer-root` a propósito: si el medio
233se llamara igual que la raíz de un sistema instalado, el pivote podría montar el disco del usuario
234en vez del USB, y el que pierde es el disco.
235
236```sh
237scripts/instalador-usb.sh # arma work/takana-instalador-usb.img
238scripts/instalador-usb-test.sh --instalar # lo arranca en OVMF y lo contesta por el serial
239cp work/takana-instalador-usb.img /work/sergio/descargas/
240( cd /work/sergio/descargas && sha256sum takana-instalador-usb.img > takana-instalador-usb.img.sha256 )
241```
242
243**Está probado de punta a punta, no sólo servido:** `VIA_RUN=1 scripts/metal-salto-test.sh` mete el
244fichero publicado en una VM con `linux-metal-kexec`, lo corre, y comprueba sobre el DISCO que se
245verificó solo, se desempaquetó, saltó e instaló — 13 comprobaciones, con el `bootmgfw.efi` del
246vecino byte a byte igual y la tabla de particiones sin cambiar.
247
248Y el USB, `scripts/instalador-usb-test.sh --instalar`: **arranca por firmware de verdad** (OVMF por
249`pflash`, sin `-kernel`, que es la única forma de probar la tabla, la ESP, el stub y el nombre
250`\initramfs.cpio.gz` que el cmdline horneado exige), **contesta el menú tecleando por el serial** y
251comprueba el disco destino. 12 en verde.
252
253⚠ El arnés corre con `-vga none`, y eso decide qué rama del código se prueba: sin pantalla el
254instalador detecta el `vtconsole` `dummy` y se va al serial. **Con** pantalla —el caso del usuario—
255la pregunta sale por `tty0`. Las dos ramas existen porque el cmdline HORNEADO del kernel pone `tty0`
256primero y `ttyS0` último, o sea que `/dev/console` es el serial: en un portátil sin puerto serie, sin
257esta detección, el censo y la pregunta se irían a un sitio que no existe y el arranque parecería
258colgado.
259
260## Las dos direcciones que publica
261
262- **código** → `https://git.gioser.net/sergio/takana` (público; `git clone` por HTTPS)
263- **paquetes** → `https://takana-os.net/repo/`, que es el MISMO `/srv/repo` firmado que sirve
264 `repo.gioser.net`. Comprobado: `takana install age --repo https://repo.gioser.net --trust ./trust`
265 responde `release: trusted (by release)`.
266
267Y **desde el 2026-09-21 se puede mirar el repo sin construir nada**, que antes no se podía:
268`repo list` y `repo verify` aceptan la misma lista de orígenes que `install`. Hasta ese día sólo
269miraban directorios locales y ante una URL respondían «repo vacío» **con salida 0** — 173 paquetes
270leídos como ninguno.
271
272```sh
273takana repo list --repo https://takana-os.net/repo # 173 paquete(s)
274takana repo verify --repo https://takana-os.net/repo --trust ./trust # release: trusted
275```
276
277## Cómo se sirve
278
279Caddy, en la caja `takana`, vhost `takana-os.net, www.takana-os.net`. La raíz es
280**`/work/sergio/takana/web/takana-os.net`**, el árbol de trabajo vivo: *publicar es guardar el
281fichero*. No lo refresca `publicar-webs.sh` a propósito — un `pull` sobre el árbol compartido mueve
282HEAD bajo los pies de otro agente (CLAUDE.md §2 ter).
283
284Este README **no se sirve**: son notas de operación, y el matcher del vhost las 404ea.
285
286Los bloques, por si hay que rehacer la caja (`caddy validate` → `arjectl restart caddy`, **nunca**
287`caddy reload`: el Caddyfile lleva `admin off`, SDD 28 §6.14):
288
289```caddyfile
290takana-os.net, www.takana-os.net {
291 import acceso
292 @comprimible not path /descargas/*
293 encode @comprimible gzip zstd
294
295 redir /descargas /descargas/ permanent
296 handle_path /descargas/* {
297 root * /work/sergio/descargas
298 header Content-Disposition "attachment"
299 file_server browse
300 }
301
302 redir /repo /repo/ permanent
303 handle_path /repo/* {
304 root * /srv/repo
305 file_server browse
306 }
307
308 handle {
309 root * /work/sergio/takana/web/takana-os.net
310 @oculto path /.* */.* /README.md
311 respond @oculto 404
312 file_server
313 }
314}
315
316git.takana-os.net {
317 import acceso
318 redir https://git.gioser.net{uri} permanent
319}
320
321takana.gioser.net {
322 import acceso
323 redir https://takana-os.net{uri} permanent
324}
325```
326
327`scripts/servidor/publicar-webs.sh` comprueba los cuatro: los dos que sirven ficheros por sha
328contra el disco, y los dos que redirigen por destino.
329
330## Los nombres
331
332| nombre | qué hace | estado |
333|---|---|---|
334| `takana-os.net`, `www` | el sitio | ✅ con certificado de Let's Encrypt (2026-09-21) |
335| `takana-os.net/repo/` | el repo de paquetes, el mismo `/srv/repo` | ✅ |
336| `takana-os.net/descargas/` | el instalador en UN fichero, y la imagen USB (`/work/sergio/descargas`) | ✅ (2026-09-23) |
337| `takana.gioser.net` | **301 → `takana-os.net`** | ✅ |
338| `git.takana-os.net` | **301 → `git.gioser.net`** | ⏳ vhost puesto, **falta el registro A** |
339
340`git.takana-os.net` sólo redirige, y es deliberado: gitea tiene UN `ROOT_URL` con el que arma cada
341enlace y cada URL de clon que muestra, así que colgarlo de un segundo nombre daría un sitio que se
342contradice —entrás por uno y te ofrece clonar del otro— y moverlo de verdad rompe los clones y el
343CI que ya apuntan al viejo.
344
345Los registros van a `2.29.29.217`, la IP de la caja:
346
347```
348takana-os.net. A 2.29.29.217 ✅ puesto
349www.takana-os.net. A 2.29.29.217 ✅ puesto
350git.takana-os.net. A 2.29.29.217 ⏳ falta
351```
352
353El certificado sale solo en la primera petición tras el DNS — y **tarda unos segundos más que el
354primer `curl`**: si da error, reintentar antes de ir a mirar logs (SDD 28 §6.14). Si el nombre ya
355resolvía cuando Caddy arrancó pero el certificado no llegó, **reiniciar caddy fuerza el reintento**
356sin esperar el backoff: fue lo que hizo falta acá.
README.md
takana-os.net — el sitio del proyecto
Una sola página, autocontenida, en dos idiomas y dos temas. No le pide nada a terceros: las
tipografías de la marca (Chivo e IBM Plex, las dos SIL OFL) viajan acá en fuentes/, subconjuntos
latin y latin-ext, 154 KB en total. Un sitio sobre no depender de binarios ajenos que cargara
las fuentes de Google en cada visita sería una contradicción barata.
| |
index.html | la página entera: CSS y JS adentro, el símbolo como <symbol> SVG inline |
fuentes.css + fuentes/ | las ocho caras @font-face, servidas de acá |
simbolo.svg | el favicon — el martillo tocapu 7×7, derivado de docs/marca/simbolo-fondo-claro.svg |
Las cuatro secciones
01 descargar · 02 las decisiones · 03 los componentes · 04 el código.
03 es una ficha de distro, no un semáforo (2026-09-23, a pedido del usuario: «que no presuma
inmadurez»). Antes era una tabla con 🟢/🟡/⚪ y una fila que decía «distro redonda — todavía no»:
una página que le cuenta al visitante lo que le falta antes que lo que tiene. Ahora son tres tablas
al estilo DistroWatch — el sistema (init, libc, compilador, kernel, gráficos, audio, almacén,
catálogo), los cinco escritorios con su versión y su clausura sellada, y los paquetes principales
con su número de versión.
⚠ Esas versiones salen de las recetas y hay que refrescarlas. Están a mano en el HTML: si una
receta sube de versión, la página miente hasta que alguien la toque. Se sacan así:
for r in bash coreutils git curl openssl python3 go rust llvm21 firefox …; do
printf '%-12s %s\n' "$r" "$(grep -m1 '^version' recipes/$r.toml | sed 's/.*= *"//; s/"//')"
done
python3 -c "import json;d=json.load(open('docs/state/build-state.json'));print(d['totals'])"
ls /store | wc -l
04 junta lo que eran dos secciones («los repositorios» y «cómo se llama»): las dos direcciones,
el reparto del repo y los tres bloques de comandos son el mismo asunto — dónde vive y cómo se llama.
La sección «las decisiones»
Trece fichas plegables (<details>, sin una línea de JavaScript), cada una con tres lecturas de
lo mismo: contra qué se decide, qué implica por dentro, y qué quiere decir sin jerga. Nacen cerradas
a pedido del usuario (2026-09-23): trece fichas abiertas son una pared, y la aseveración del título
basta para elegir cuál abrir.
Las secciones «los dos pisos» y «la frontera» se fundieron aquí como fichas 01 y 03 — eran el mismo
material contado dos veces, en una sección y en una ficha. Y entraron dos que faltaban: la
instalación (un archivo, sin ISO y sin red) y el escritorio propio + tawasuyu, que son lo que
alguien de fuera pregunta primero. No son opinión: cada una sale de un ADR o de una decisión ya tomada
en docs/ — fuentes clavadas (ADR 0006), zig cc (0003), arje de init (0007) y el arranque por
grafo (0010), multi-origen con índice firmado (0014), imágenes ajenas enjauladas (0015), y las de
Wayland-only y PipeWire, que viven en los frentes de escritorio y audio.
⚠ Si una decisión cambia, esta página miente hasta que alguien la toque. Es el precio de
explicar el diseño en una landing y no en un doc generado: se gana claridad y se paga en
mantenimiento. Las fichas se armaron con un guion de un solo uso — la página es la fuente, no hay
generador que mantener.
La paleta y la tipografía salen de docs/marca/README.md y no se inventan acá. La única
desviación, deliberada: en tema claro el texto de acento usa un brasa oscurecido (#9E3F16), porque
el #DF6B2A de la marca sobre alpaca da 2,9:1 y no se lee. Las combinaciones de la página están
medidas: ninguna baja de 4,5:1.
El instalador: /descargas/ vive FUERA del repo
https://takana-os.net/descargas/takana-instalador-<variante>.sh es un fichero que trae todo
adentro:
cabecera sh + un tar.gz pegado detrás con el binario takana, el instalador que corre en RAM, su
kernel y el censo del terreno. Se baja, se le da permiso y se corre; no baja nada más.
⚠ No está en el repo, y no puede estarlo. Son 137 MiB: en git serían 137 MiB para siempre en
cada clon, y además el espejo de GitHub rechaza cualquier fichero de más de 100 MiB — o sea que
el push se rompería y nadie relacionaría la causa con el sitio. Vive en /work/sergio/descargas,
que está en sdc (255 G) y no en la raíz de 5,8 G, que se llena y tira la caja entera.
scripts/instalador-paquete.sh # arma el paquete (y comprueba su coherencia)
scripts/instalador-autoextraible.sh # lo convierte en UN fichero, y lo desempaqueta para probarlo
cp work/variantes/*.sh work/variantes/*.img /work/sergio/descargas/
( cd /work/sergio/descargas && for f in takana-instalador-*; do case "$f" in *.sha256) ;; *) sha256sum "$f" > "$f.sha256";; esac; done )
El .sha256 que se publica al lado es el del fichero entero, para comprobar la descarga
(sha256sum -c takana-instalador.sh.sha256). Es distinto del que el .sh lleva adentro, que es
el del payload y lo verifica él solo antes de desempaquetar (./takana-instalador.sh --sha256
lo imprime).
⚠ El hash NO se escribe en index.html, y es deliberado. La primera versión lo traía pegado en
la página y habría empezado a mentir en el primer rebuild — que es el modo de fallo que este mismo
README ya avisa para la sección «las decisiones». La página muestra el comando; el número lo sirve
el fichero de al lado, que se regenera junto al .sh.
⚠ La extensión es .sh y no .run, y por eso el vhost fuerza Content-Disposition: attachment.
.sh dice lo que el fichero ES —una cabecera de shell con el sistema pegado detrás, legible con
head antes de correrlo— pero invita al navegador a MOSTRARLO: 580 MiB de binario volcados en una
pestaña. La cabecera lo cierra para las siete descargas a la vez y no depende de que Caddy adivine
el tipo (hoy no manda ningún Content-Type para estos ficheros).
⚠ /descargas queda fuera del encode del vhost: el .sh ya viene comprimido, y volver a
comprimirlo por HTTP gasta CPU en cada descarga sin ahorrar un byte.
En la página: los enlaces NO están en el HTML
⚠ Ninguna URL de descarga aparece en index.html (a pedido del usuario, 2026-09-23): el
selector de variante las compone en JavaScript al pulsarlo, y el path se arma por trozos
('/de'+'scargas/'). Un rastreador que lee el documento no encuentra una sola, y estos ficheros
pesan de 138 MiB a 3,4 GiB — no son para que se los baje un bot. Se comprueba con
grep -c "descargas/takana-instalador" index.html ⇒ 0.
El precio: sin JavaScript no hay descargas. Por eso hay un <noscript> con la ruta en TEXTO
PLANO, no como enlace — un enlace ahí devolvería el problema que esto evita.
Los pesos se corrigen solos. La tabla en JS (PESO) es el valor medido al publicar; al pintar
cada variante se dispara un HEAD y, si responde, el peso real lo pisa. Así una reconstrucción del
instalador no deja la página mintiendo — que ya pasó: la tabla anterior decía 3,2 y 3,5 GiB cuando
los ficheros eran 3,1 y 3,4. Si el HEAD falla, queda el medido; nunca en blanco.
El botón de copiar lo pone el JS, uno por cada .codigo, y copia sin el $ del prompt. No hay
que escribirlo diez veces en el HTML. Vive en una banda propia arriba del bloque
(padding-top:2.7rem) y no flotando sobre la primera línea: un comando largo hace scrollear el
<pre> por debajo del botón, y ahí sí lo pisaría.
Todo del mismo tamaño, a propósito (2026-09-23): el selector es grid-template-columns:1fr 1fr
con grid-auto-rows:1fr —siempre dos y dos, las cuatro del mismo alto— y los bloques de comandos
llevan min-height:7.8rem, que es lo que ocupan tres líneas con su relleno. Por eso full, que no
tiene archivo único, no esconde el bloque: lo deja del mismo tamaño con el aviso adentro. Si se
escondiera, la página saltaría al elegirlo.
La prosa del instalador salió de la página, a propósito
El usuario pidió quitar la sobreexplicación (2026-09-23): la sección de descargas es ahora el
selector, los comandos y nada más. Lo que se quitó no se perdió — vive donde se ejecuta:
--check/--survey/--extraer y el sha256 del payload en scripts/instalador-autoextraible.sh,
takana.console en scripts/instalador-initramfs.sh, el kexec y las dos confirmaciones en
docs/32-instalador-de-usuario.md, y «el medio desde el que arrancaste no se ofrece» en
scripts/takana-install-partition.sh:291. La página lo estaba repitiendo, no guardando.
Las variantes: qué sistema se instala
scripts/instalador-variantes.sh [servidor|mirada|full] arma las tres, en sus dos medios.
La diferencia es qué rootfs se copia al disco, no cómo: el instalador y el censo son los mismos.
| variante | composición | .sh | USB |
base | sólo el piso de arranque | 81 MiB | 357 MiB |
mirada | + [perfil.base] + [perfil.escritorio-mirada] | 310 MiB | 1018 MiB |
servidor | .sh: base + servidor-minimo · USB: base + servidor | 386 MiB | 2,4 GiB |
full | + base + cli + servidor + los cinco escritorios | no hay | 8,6 GiB |
Todo lo publicado va SIN SÍMBOLOS DE DEPURACIÓN (decisión del usuario, 2026-09-23). Medido por
variante, sobre el rootfs que se instala:
| antes | después | |
base | 265 MiB | 102 MiB | −61 % |
mirada | 1348 | 764 | −43 % |
servidor-minimo | 1456 | 933 | −35 % |
servidor | 2925 | 2113 | −27 % |
full | 17814 | 8002 | −55 % |
⚠ Esto NO contradice al SDD 23, que dice —con razón— que un
strip posterior no sirve: eso vale para el STORE, donde el ArtifactHash es de ENTRADA y quedaría
apuntando a un contenido que un rebuild ya no reproduce. Lo que se stripea acá es una COPIA que no
es un artefacto: no se sella, nadie verifica su hash contra una receta, y el store conserva los
símbolos intactos para quien depure. strip es determinista, así que la imagen sigue siendo
reproducible. SIN_STRIP=1 lo apaga.
⚠ Y hay una guarda antes de stripear: si el árbol compartiera inodos con el stage, stripear
mutaría el store —y con él todos los artefactos que enlazan a ese fichero, en silencio y para
siempre—. cp -a copia contenido y no enlaza al origen, pero eso es propiedad del comando de
arriba, no de este bloque: si alguien lo cambia a cp -al por rapidez, el guardián grita antes de
tocar nada.
⚠ «base» son DOS cosas con el mismo nombre y confundirlas da un rootfs que no arranca. El
piso de arranque es product-rootfs —arje-zero como PID 1 + busybox— y no está en ningún
perfil; el userland base es [perfil.base], 359 binarios, y no tiene ni busybox ni
arje-zero adentro (medido: ls base/bin). Cada variante es product-rootfs ∪ perfil.base ∪ ….
El SDD 27 §1 ya había medido la otra
mitad del mismo hueco: los escritorios no heredan base.
⚠ Cada perfil se hidrata en su propio árbol y después se funden, y no uno encima de otro: la
auditoría del bit de privilegio corre contra las declaraciones [[setuid]] del perfil que se está
hidratando, así que al poner escritorio-mirada sobre base veía el doas 4755 que dejó base
—declarado y legítimo— y lo denunciaba como un bit que nadie declaró.
⚠ servidor NO es la misma cosa en los dos medios, y es la misma física. El .sh carga el
sistema entero en memoria —es lo que le permite soltar el disco— así que cada MiB del rootfs es RAM
exigida en la máquina de quien instala. Por ahí va servidor-minimo: los mismos servicios uno por
uno (sshd, caddy, gitea, cron, hora, cortafuegos, los daemons propios), sin la cadena de compilar
(rust 364 MiB, lld21 109) y sin heredar cli (54 raíces de toolbox interactivo). 1456 MiB
contra 2925, y el fichero baja de 1,2 GiB a 580 MiB. Por USB va servidor entero, que es el
destino de verdad. No es una opinión sobre qué debe llevar un servidor: es una restricción del
MEDIO, y está escrita en los dos sitios —acá y en el descripcion del perfil— para que nadie la
lea como un recorte arbitrario.
⚠ GNOME estuvo fuera medio día y ya entra. Se caía por dos cosas suyas, las dos medidas el
2026-09-23 y las dos resueltas. accountsservice trae /var/lib/AccountsService/users en 0700:
hidratar como usuario normal no puede leerlo y el nodo no proyectaba — se arma con privilegio, que
ya hacía falta igual por los 0500 de ModemManager. Y usr/libexec/libgtop_server2 llega 4555
desde el tarball de upstream sin que ningún [[setuid]] lo declare: el aviso del auditor decía «o
entra con su motivo, o sale» y hasta ese día sólo existía la primera mitad. Ahora
takana setuid --apply retira el bit no declarado rompiendo el enlace duro, deja el store
intacto y dice cuál quitó. Es la opción segura —quita privilegio, nunca lo da— y la coherente con el
invariante: of_tree no hashea ese bit, así que un artefacto que lo trae está fuera de contrato.
⚠ La holgura del ext4 se escala con el árbol, no es un número fijo. du mide el contenido, no
lo que el ext4 NECESITA (inodos, journal, mapas de bloques): con 128 MiB fijos alcanzaba para 1 GiB
y mke2fs murió con «Could not allocate block» a mitad de poblar un árbol de 16 GiB, después de
veinte minutos de trabajo. Hoy es un 8 % del árbol, nunca menos de 128 MiB.
⚠ Armar una imagen con escritorios pide privilegio, y las variantes chicas nunca lo tocaron:
ModemManager instala sus fcc-unlock.available.d/* en 0500 tal como vienen de upstream, así que
copiar el árbol como usuario normal falla. Se copia con sudo y sin chown: cp -a como root
conserva dueño y modo, que es lo que la imagen tiene que llevar.
⚠ full no tiene .sh, y es física. El fichero único carga el instalador entero en memoria
—es lo que le permite soltar el disco— y ahí viven a la vez el initramfs comprimido y su contenido
desempaquetado. Con 3,2 GiB de rootfs eso pide ~8 GiB de RAM en la máquina donde se instale. El USB
no tiene ese límite: corre desde el propio medio. La guarda que lo detecta mide la RAM de la
máquina que construye, no la de la que instala — acá acierta por casualidad (el hub tiene 7,6 GiB,
parecido a un portátil) y queda anotado sin barrer.
La imagen USB, para cuando el salto no se puede
takana-instalador-usb.img (519 MiB) es el mismo instalador por otro camino: mismo árbol, mismo
censo, mismas guardas. Se graba con dd y arranca por la ruta de medios removibles del firmware.
⚠ No lleva el instalador en la ESP, y no es un capricho: el cuelgue del primer viaje a metal fue
un initrd de ~100 MiB cargado por el EFI-stub (ADR 0010
paso 2), y el del instalador son ~122 MiB. El USB usa el patrón que sí está probado —el de un
disco instalado—: la ESP lleva el kernel y un pivote de 736 KiB, y el pivote encuentra la ext4 por
LABEL=takana-medio y hace switch_root. La etiqueta NO es hammer-root a propósito: si el medio
se llamara igual que la raíz de un sistema instalado, el pivote podría montar el disco del usuario
en vez del USB, y el que pierde es el disco.
scripts/instalador-usb.sh # arma work/takana-instalador-usb.img
scripts/instalador-usb-test.sh --instalar # lo arranca en OVMF y lo contesta por el serial
cp work/takana-instalador-usb.img /work/sergio/descargas/
( cd /work/sergio/descargas && sha256sum takana-instalador-usb.img > takana-instalador-usb.img.sha256 )
Está probado de punta a punta, no sólo servido: VIA_RUN=1 scripts/metal-salto-test.sh mete el
fichero publicado en una VM con linux-metal-kexec, lo corre, y comprueba sobre el DISCO que se
verificó solo, se desempaquetó, saltó e instaló — 13 comprobaciones, con el bootmgfw.efi del
vecino byte a byte igual y la tabla de particiones sin cambiar.
Y el USB, scripts/instalador-usb-test.sh --instalar: arranca por firmware de verdad (OVMF por
pflash, sin -kernel, que es la única forma de probar la tabla, la ESP, el stub y el nombre
\initramfs.cpio.gz que el cmdline horneado exige), contesta el menú tecleando por el serial y
comprueba el disco destino. 12 en verde.
⚠ El arnés corre con -vga none, y eso decide qué rama del código se prueba: sin pantalla el
instalador detecta el vtconsole dummy y se va al serial. Con pantalla —el caso del usuario—
la pregunta sale por tty0. Las dos ramas existen porque el cmdline HORNEADO del kernel pone tty0
primero y ttyS0 último, o sea que /dev/console es el serial: en un portátil sin puerto serie, sin
esta detección, el censo y la pregunta se irían a un sitio que no existe y el arranque parecería
colgado.
Las dos direcciones que publica
- código →
https://git.gioser.net/sergio/takana (público; git clone por HTTPS)
- paquetes →
https://takana-os.net/repo/, que es el MISMO /srv/repo firmado que sirve
repo.gioser.net. Comprobado: takana install age --repo https://repo.gioser.net --trust ./trust
responde release: trusted (by release).
Y desde el 2026-09-21 se puede mirar el repo sin construir nada, que antes no se podía:
repo list y repo verify aceptan la misma lista de orígenes que install. Hasta ese día sólo
miraban directorios locales y ante una URL respondían «repo vacío» con salida 0 — 173 paquetes
leídos como ninguno.
takana repo list --repo https://takana-os.net/repo # 173 paquete(s)
takana repo verify --repo https://takana-os.net/repo --trust ./trust # release: trusted
Cómo se sirve
Caddy, en la caja takana, vhost takana-os.net, www.takana-os.net. La raíz es
/work/sergio/takana/web/takana-os.net, el árbol de trabajo vivo: publicar es guardar el
fichero. No lo refresca publicar-webs.sh a propósito — un pull sobre el árbol compartido mueve
HEAD bajo los pies de otro agente (CLAUDE.md §2 ter).
Este README no se sirve: son notas de operación, y el matcher del vhost las 404ea.
Los bloques, por si hay que rehacer la caja (caddy validate → arjectl restart caddy, nunca
caddy reload: el Caddyfile lleva admin off, SDD 28 §6.14):
takana-os.net, www.takana-os.net {
import acceso
@comprimible not path /descargas/*
encode @comprimible gzip zstd
redir /descargas /descargas/ permanent
handle_path /descargas/* {
root * /work/sergio/descargas
header Content-Disposition "attachment"
file_server browse
}
redir /repo /repo/ permanent
handle_path /repo/* {
root * /srv/repo
file_server browse
}
handle {
root * /work/sergio/takana/web/takana-os.net
@oculto path /.* */.* /README.md
respond @oculto 404
file_server
}
}
git.takana-os.net {
import acceso
redir https://git.gioser.net{uri} permanent
}
takana.gioser.net {
import acceso
redir https://takana-os.net{uri} permanent
}
scripts/servidor/publicar-webs.sh comprueba los cuatro: los dos que sirven ficheros por sha
contra el disco, y los dos que redirigen por destino.
Los nombres
| nombre | qué hace | estado |
takana-os.net, www | el sitio | ✅ con certificado de Let's Encrypt (2026-09-21) |
takana-os.net/repo/ | el repo de paquetes, el mismo /srv/repo | ✅ |
takana-os.net/descargas/ | el instalador en UN fichero, y la imagen USB (/work/sergio/descargas) | ✅ (2026-09-23) |
takana.gioser.net | 301 → takana-os.net | ✅ |
git.takana-os.net | 301 → git.gioser.net | ⏳ vhost puesto, falta el registro A |
git.takana-os.net sólo redirige, y es deliberado: gitea tiene UN ROOT_URL con el que arma cada
enlace y cada URL de clon que muestra, así que colgarlo de un segundo nombre daría un sitio que se
contradice —entrás por uno y te ofrece clonar del otro— y moverlo de verdad rompe los clones y el
CI que ya apuntan al viejo.
Los registros van a 2.29.29.217, la IP de la caja:
takana-os.net. A 2.29.29.217 ✅ puesto
www.takana-os.net. A 2.29.29.217 ✅ puesto
git.takana-os.net. A 2.29.29.217 ⏳ falta
El certificado sale solo en la primera petición tras el DNS — y tarda unos segundos más que el
primer curl: si da error, reintentar antes de ir a mirar logs (SDD 28 §6.14). Si el nombre ya
resolvía cuando Caddy arrancó pero el certificado no llegó, reiniciar caddy fuerza el reintento
sin esperar el backoff: fue lo que hizo falta acá.