From aeebb194dc1368739d35ebd08023b0df0ce12373 Mon Sep 17 00:00:00 2001 From: Sergio Date: Sat, 5 Sep 2026 04:12:55 +0000 Subject: [PATCH] atuq 0.1.0: la receta del envoltorio, derivada de firefox MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Primera receta del navegador de la distro (SDD 26). No compila nada: depende de `firefox`, copia su árbol y le pone encima cuatro ficheros. Hash b3:2d6e4dcf. LA CAPA SON CUATRO FICHEROS Y CADA UNO ESTÁ DONDE ESTÁ POR UNA RAZÓN: - `defaults/pref/autoconfig.js` — el único gancho que corre ANTES de que exista un perfil. - `atuq.cfg` — prefs de fábrica + carga del chrome. Con `defaultPref` y no `lockPref`: son valores de arranque, no una cárcel; lo que de verdad se bloquea va en policies.json. - `chrome/atuq.css` — el aspecto. - `distribution/policies.json` — telemetría, updates, primer arranque. POR QUÉ EL CSS NO ES `userChrome.css`: ese fichero vive en el PERFIL y exige que el usuario prenda `toolkit.legacyUserProfileCustomizations.stylesheets`. atuq tiene que verse como atuq en el primer arranque, con un perfil recién creado, sin que nadie prenda nada. El único gancho a nivel de APLICACIÓN es nsIStyleSheetService desde autoconfig — y llegar a él es la razón de `sandbox_enabled = false`. Se registra como USER_SHEET, el mismo nivel de cascada que userChrome, para que el usuario pueda seguir pisándolo. DOS TRAMPAS DEL FORMATO, ESCRITAS EN EL PROPIO FICHERO: - la PRIMERA LÍNEA de un autoconfig se ignora siempre, por diseño (herencia de Netscape). Código en la línea 1 no corre y no da error: el fallo más caro de ese fichero es el que no dice nada. - el CSS va acotado con @-moz-document a browser.xhtml. Una hoja USER sin acotar alcanza CUALQUIER documento chrome —visor de PDF, inspector, diálogos— y un selector como `toolbar` termina pintando ventanas que nadie miró. `extensions.autoDisableScopes = 0` es la otra mitad del `--with-unsigned-addon-scopes` que ya viaja en el build de firefox: sin los dos, las extensiones de la distro se instalan DESACTIVADAS. LAS ASERCIONES VAN PRIMERO Y FALLAN RUIDOSAS. Un derivado que no encuentra su base produciría un directorio con cuatro ficheros de config, `Store::has` lo daría por presente y el fallo aparecería el día que alguien abra el navegador (regla 3 del CLAUDE.md). Y al final imprime el inventario de la capa con sus tamaños, que es la primera pregunta del diagnóstico cuando «atuq se ve como Firefox». LO QUE LA v0.1 NO HACE, A PROPÓSITO: no renombra el binario ni toca application.ini, y no re-empaqueta omni.ja. Las dos cosas necesitan mirar la forma real del árbol que selle `firefox`, y adivinarla desde acá daría un artefacto que arranca en una máquina y en ninguna otra. No se puede construir todavía: su dep `firefox` está en vuelo. --- recipes/atuq.toml | 89 +++++++++++++++++++++++++ recipes/atuq/README.md | 33 +++++++++ recipes/atuq/atuq.cfg | 66 ++++++++++++++++++ recipes/atuq/chrome/atuq.css | 29 ++++++++ recipes/atuq/distribution/policies.json | 11 +++ recipes/atuq/prefs/autoconfig.js | 10 +++ 6 files changed, 238 insertions(+) create mode 100644 recipes/atuq.toml create mode 100644 recipes/atuq/README.md create mode 100644 recipes/atuq/atuq.cfg create mode 100644 recipes/atuq/chrome/atuq.css create mode 100644 recipes/atuq/distribution/policies.json create mode 100644 recipes/atuq/prefs/autoconfig.js diff --git a/recipes/atuq.toml b/recipes/atuq.toml new file mode 100644 index 00000000..6a243c75 --- /dev/null +++ b/recipes/atuq.toml @@ -0,0 +1,89 @@ +# atuq 0.1.0 — el navegador de la distro, como ARTEFACTO DERIVADO de firefox. +# +# ══ POR QUÉ DERIVADO Y NO UN FORK DE FUENTE ════════════════════════════════════════════════════ +# Zen —el fork de Firefox al que esto reemplaza en nuestro catálogo— parchea el árbol de Gecko y +# recompila. Nosotros no distribuimos un binario: sellamos artefactos. Entonces atuq puede DEPENDER +# de `firefox` y poner su capa encima, y eso cambia el precio de todo: +# · una iteración de chrome cuesta SEGUNDOS, no las cuatro horas de un build de Gecko; +# · no mueve el `ArtifactHash` de `firefox` ⇒ el corpus no se invalida; +# · las CVE se heredan gratis: cuando sube `firefox`, atuq se reconstruye solo. +# El razonamiento completo, con lo que se promete y lo que NO (spoiler: no habrá «modo Tor»), está +# en `docs/26-atuq-envoltorio-gecko.md`. +# +# ══ SU FUENTE ES ESTE REPO ═════════════════════════════════════════════════════════════════════ +# `source.dir` es el modo de fuente que se agregó para esto (hammer-core/src/recipe.rs): el árbol +# vive en `recipes/atuq/` y se hashea por CONTENIDO con `ArtifactHash::of_tree`, igual que ya se +# hacía con los `patches`. No hay commit que pinear y no hay fetch: editar un CSS mueve el hash. +# +# ══ LO QUE ESTA v0.1 NO HACE, A PROPÓSITO ══════════════════════════════════════════════════════ +# No renombra el binario ni toca `application.ini`. El branding necesita mirar la forma real del +# árbol que sella `firefox` —qué es `firefox-bin`, cómo se resuelve el appdir— y adivinarlo desde +# acá produciría un artefacto que arranca en la máquina de quien lo escribió y en ninguna otra. +# Tampoco re-empaqueta `omni.ja`, que es donde vive el chrome de verdad; eso es la v0.2 y trae dos +# condiciones ya sabidas (re-empacar determinista y preservar el orden del `jarlog` del PGO). +name = "atuq" +version = "0.1.0" +# El artefacto CONTIENE Firefox, así que hereda su licencia. El overlay de `recipes/atuq/` es +# nuestro, pero eso no cambia lo que se distribuye. +license = "MPL-2.0" + +[source] +dir = "atuq" + +[build] +# No se compila nada: esta receta copia y edita ficheros. `compiler` y `link` entran igual en el +# hash, así que se dejan en el default del corpus para no inventar una identidad que no significa +# nada. +target = "x86_64-linux-musl" + +[build.phases] +configure = "echo 'atuq: nada que configurar — es un artefacto derivado'" +compile = "echo 'atuq: nada que compilar — el motor lo pone la dep firefox'" +install = ''' +set -e +SRC=/usr/lib/firefox +DST=/out/usr/lib/atuq + +# ── LAS ASERCIONES VAN PRIMERO, Y FALLAN RUIDOSAS ───────────────────────────────────────────── +# Un derivado que no encuentra su base no debe producir un artefacto flaco: produciría un +# directorio con cuatro ficheros de configuración, `Store::has` lo daría por presente y el fallo +# aparecería el día que alguien intente abrir el navegador. Un ausente falla fuerte; un vacío llega +# hasta el final diciendo que todo fue bien (regla 3 del CLAUDE.md). +[ -d "$SRC" ] || { echo "atuq: no está el árbol de firefox en $SRC — ¿la dep se materializó?" >&2; exit 1; } +[ -x "$SRC/firefox" ] || { echo "atuq: $SRC/firefox no existe o no es ejecutable" >&2; exit 1; } +[ -d "$SRC/browser" ] || { echo "atuq: falta $SRC/browser — el árbol no tiene la forma esperada" >&2; exit 1; } + +mkdir -p "$DST" +cp -a "$SRC"/. "$DST"/ + +# ── LA CAPA DE atuq ─────────────────────────────────────────────────────────────────────────── +# `defaults/pref/` es el único sitio desde el que se puede pedir el autoconfig ANTES de que exista +# un perfil; el resto cuelga de ahí. +mkdir -p "$DST/defaults/pref" "$DST/chrome" "$DST/distribution" +cp /src/prefs/autoconfig.js "$DST/defaults/pref/autoconfig.js" +cp /src/atuq.cfg "$DST/atuq.cfg" +cp /src/chrome/atuq.css "$DST/chrome/atuq.css" +cp /src/distribution/policies.json "$DST/distribution/policies.json" + +# El comando que teclea la gente. Apunta al binario SIN renombrar (ver la nota de la cabecera): +# renombrarlo es branding y branding es la v0.2. +mkdir -p /out/usr/bin +ln -sf ../lib/atuq/firefox /out/usr/bin/atuq + +# ── INVENTARIO: LO QUE SE MIRA CUANDO «ATUQ SE VE COMO FIREFOX» ─────────────────────────────── +# Los cuatro ficheros de la capa son el 100% de lo que distingue este artefacto de su base, y son +# los que un `cp` silencioso podría no haber puesto. Se listan para que el log del build responda +# solo la primera pregunta del diagnóstico. +echo "atuq: capa aplicada —" +for f in defaults/pref/autoconfig.js atuq.cfg chrome/atuq.css distribution/policies.json; do + [ -s "$DST/$f" ] || { echo "atuq: $f quedó VACÍO o ausente" >&2; exit 1; } + echo " $f ($(wc -c < "$DST/$f") bytes)" +done +''' + +[deps] +# `firefox` como dep de BUILD y no de runtime: su árbol se materializa en el sandbox, se copia +# dentro del artefacto de atuq y a partir de ahí atuq no lo necesita más. El precio es que el +# artefacto pesa lo que pesa Firefox otra vez; la salida barata (una granja de symlinks) rompe la +# resolución del appdir por /proc/self/exe, así que la v0.1 paga el disco y lo dice. +build = ["firefox"] diff --git a/recipes/atuq/README.md b/recipes/atuq/README.md new file mode 100644 index 00000000..aac8c05c --- /dev/null +++ b/recipes/atuq/README.md @@ -0,0 +1,33 @@ +# atuq — el envoltorio Gecko de la distro + +`atuq` (quechua: *zorro*) es el navegador de la distro: **un artefacto DERIVADO de `firefox`**, no un +fork de su fuente. El diseño completo está en [`docs/26-atuq-envoltorio-gecko.md`](../../docs/26-atuq-envoltorio-gecko.md). + +Este directorio ES la fuente de atuq — `recipes/atuq.toml` lo declara con `source.dir` y hammer lo +hashea por CONTENIDO. Editar un fichero de acá mueve el `ArtifactHash`; no hay commit que pinear. + +## Qué hay acá y dónde cae + +| Acá | En el artefacto | Qué hace | +|---|---|---| +| `prefs/autoconfig.js` | `defaults/pref/autoconfig.js` | Le dice a Gecko que lea `atuq.cfg`. Es el único gancho posible antes de que arranque el perfil. | +| `atuq.cfg` | `/atuq.cfg` | Prefs de fábrica + carga la hoja de estilo del chrome. | +| `chrome/atuq.css` | `/chrome/atuq.css` | El aspecto. Se inyecta como AGENT_SHEET desde `atuq.cfg`. | +| `distribution/policies.json` | `/distribution/policies.json` | Política de fábrica: telemetría, updates, scope de extensiones. | + +## Por qué el CSS no es `userChrome.css` + +`userChrome.css` vive en el PERFIL del usuario y exige que el usuario prenda +`toolkit.legacyUserProfileCustomizations.stylesheets`. Una distro no puede depender de eso: el +navegador tiene que verse como atuq la primera vez que arranca, con un perfil recién creado. La vía +que sí funciona a nivel de aplicación es el `nsIStyleSheetService` desde autoconfig, que es lo que +hace `atuq.cfg`. + +## Lo que TODAVÍA no está + +- **Branding** (nombre, iconos, `application.ini`): v0.2. Necesita mirar el artefacto real de + `firefox` para no adivinar la forma del árbol. +- **Re-empaque de `omni.ja`**: es donde va el chrome de verdad (split view, dientes propios). Trae + dos condiciones ya sabidas: re-empacar determinista, y **preservar el orden del `jarlog`** cuando + el PGO exista, o se tira a la basura la optimización de arranque. +- **La extensión de `sct`** y el host de native messaging (SDD 26 §6.1 y §7). diff --git a/recipes/atuq/atuq.cfg b/recipes/atuq/atuq.cfg new file mode 100644 index 00000000..31124a25 --- /dev/null +++ b/recipes/atuq/atuq.cfg @@ -0,0 +1,66 @@ +// ⚠ LA PRIMERA LÍNEA DE UN FICHERO AUTOCONFIG SE IGNORA — SIEMPRE, POR DISEÑO. +// El parser de Gecko salta la línea 1 sin mirarla (herencia del formato de Netscape). Si acá +// hubiera código en vez de este comentario, no correría y no habría ningún error: el fallo más caro +// de este fichero es el que no dice nada. Por eso la línea 1 es, deliberadamente, un comentario. + +// ── PREFS DE FÁBRICA ─────────────────────────────────────────────────────────────────────────── +// `defaultPref` y no `lockPref`: son valores de arranque, no una cárcel. Una distro que decide POR +// el usuario y no lo deja cambiar de idea es exactamente lo que atuq no quiere ser. Lo que sí se +// bloquea con política de verdad va en `distribution/policies.json`. + +// Los dientes: pestañas verticales, que Firefox trae nativas desde la 133 y vienen apagadas. +defaultPref("sidebar.revamp", true); +defaultPref("sidebar.verticalTabs", true); + +// La distro se actualiza con hammer. Un navegador que se auto-actualiza por su cuenta rompería el +// invariante entero: el binario dejaría de ser el que el artefacto dice que es. +defaultPref("app.update.auto", false); +defaultPref("browser.shell.checkDefaultBrowser", false); + +// Telemetría. No es «apagar una casilla»: es que la distro no reporta, punto. Lo verificable de +// verdad —que la clausura no tenga a dónde reportar— es otro frente (SDD 26 §6); esto es el suelo. +defaultPref("datareporting.healthreport.uploadEnabled", false); +defaultPref("datareporting.policy.dataSubmissionEnabled", false); +defaultPref("toolkit.telemetry.enabled", false); +defaultPref("toolkit.telemetry.unified", false); +defaultPref("toolkit.telemetry.archive.enabled", false); + +// Extensiones que la distro deja en `distribution/extensions/`: sin esto Gecko las instala +// DESACTIVADAS y el usuario ve una lista de complementos apagados que nunca pidió apagar. +// El otro lado de esto es `--with-unsigned-addon-scopes=app,system`, que va en el CONFIGURE de +// `recipes/firefox.toml` y por eso no se puede resolver desde acá. +defaultPref("extensions.autoDisableScopes", 0); + +// ── EL CHROME ────────────────────────────────────────────────────────────────────────────────── +// Por qué acá y no en `userChrome.css`: ese fichero vive en el PERFIL y exige que el usuario prenda +// `toolkit.legacyUserProfileCustomizations.stylesheets`. atuq tiene que verse como atuq en el +// primer arranque, con un perfil recién creado, sin que nadie prenda nada. El único gancho a nivel +// de APLICACIÓN es nsIStyleSheetService, y llegar a él es la razón de `sandbox_enabled = false`. +// +// USER_SHEET y no AGENT_SHEET: es el mismo nivel de cascada que usa `userChrome.css`, así que lo +// que se escriba en `chrome/atuq.css` se comporta como la gente espera y el usuario puede seguir +// pisándolo con el suyo. AGENT_SHEET ganaría siempre y además alcanzaría al CONTENIDO de las +// páginas, que no es lo que queremos tocar. +try { + const { classes: Cc, interfaces: Ci } = Components; + const sss = Cc["@mozilla.org/content/style-sheet-service;1"] + .getService(Ci.nsIStyleSheetService); + const ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService); + const dirs = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties); + // "GreD" = el directorio de la aplicación (donde vive el binario). En una instalación normal es + // el mismo que "XCurProcD"; se usa GreD porque es el que no cambia si algún día atuq se lanza + // con un `-app`. + const css = dirs.get("GreD", Ci.nsIFile); + css.append("chrome"); + css.append("atuq.css"); + if (css.exists()) { + const uri = ios.newFileURI(css); + if (!sss.sheetRegistered(uri, sss.USER_SHEET)) { + sss.loadAndRegisterSheet(uri, sss.USER_SHEET); + } + } +} catch (e) { + // Un fallo acá NO debe impedir que el navegador arranque: sin estilo es feo, sin navegador no hay + // nada. Queda en la consola de errores, que es donde se mira cuando «atuq se ve como Firefox». + Components.utils.reportError("atuq.cfg: no pude registrar chrome/atuq.css: " + e); +} diff --git a/recipes/atuq/chrome/atuq.css b/recipes/atuq/chrome/atuq.css new file mode 100644 index 00000000..98f23bf3 --- /dev/null +++ b/recipes/atuq/chrome/atuq.css @@ -0,0 +1,29 @@ +/* atuq.css — el aspecto de atuq. Se registra como USER_SHEET desde `atuq.cfg`. + * + * ⚠ TODO lo de acá va acotado con @-moz-document a la ventana del navegador. Una hoja de nivel + * USER sin acotar alcanza CUALQUIER documento chrome —el visor de PDF, el inspector, los diálogos— + * y un selector genérico como `toolbar` termina pintando ventanas que nadie miró nunca. + * + * v0.1: deliberadamente conservador. El chrome de verdad (dientes propios, split view) vive en el + * re-empaque de `omni.ja`, que es la v0.2 y necesita el artefacto real de firefox delante. + */ + +@-moz-document url("chrome://browser/content/browser.xhtml") { + :root { + /* El acento de la distro. Un solo token para que el resto cuelgue de acá y no haya seis + * literales de color desperdigados que se desincronizan en la primera edición. */ + --atuq-acento: #b5651d; + --atuq-radio: 8px; + } + + /* La pestaña activa: identidad visible sin reescribir la barra entera. */ + #tabbrowser-tabs .tabbrowser-tab[selected] .tab-background { + border-radius: var(--atuq-radio); + box-shadow: inset 0 -2px 0 0 var(--atuq-acento); + } + + /* La barra de direcciones enfocada usa el acento en vez del azul de Firefox. */ + #urlbar[focused="true"] > #urlbar-background { + outline-color: var(--atuq-acento); + } +} diff --git a/recipes/atuq/distribution/policies.json b/recipes/atuq/distribution/policies.json new file mode 100644 index 00000000..d929a12d --- /dev/null +++ b/recipes/atuq/distribution/policies.json @@ -0,0 +1,11 @@ +{ + "policies": { + "DisableTelemetry": true, + "DisableFirefoxStudies": true, + "DisableAppUpdate": true, + "DontCheckDefaultBrowser": true, + "DisableProfileImport": false, + "OverrideFirstRunPage": "", + "OverridePostUpdatePage": "" + } +} diff --git a/recipes/atuq/prefs/autoconfig.js b/recipes/atuq/prefs/autoconfig.js new file mode 100644 index 00000000..96712f7d --- /dev/null +++ b/recipes/atuq/prefs/autoconfig.js @@ -0,0 +1,10 @@ +// Le dice a Gecko que lea `atuq.cfg` del directorio de la aplicación. +// +// `general.config.obscure_value = 0` porque el formato histórico esperaba el fichero "ofuscado" +// con un ROT-13 que no protege nada; ponerlo en 0 es lo que permite que `atuq.cfg` sea texto plano +// legible y revisable, que es lo que queremos para una política de distro. +// `sandbox_enabled = false` da acceso a los componentes XPCOM desde el .cfg: sin eso no se puede +// registrar la hoja de estilo del chrome, que es la razón principal de que este fichero exista. +pref("general.config.filename", "atuq.cfg"); +pref("general.config.obscure_value", 0); +pref("general.config.sandbox_enabled", false);