From 0b464af3e57ff1042fa07b8f6af41f668249e6ea Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Tue, 14 Jul 2026 17:26:24 +0100 Subject: [PATCH] =?UTF-8?q?fix(pkgs):=20one=20chromium,=20not=20two=20?= =?UTF-8?q?=E2=80=94=20and=20what=20that=20is=20actually=20worth?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BACKLOG #121. nixpkgs' chromium wrapper with enableWideVine = true (what the template ships, for DRM) runs the `-wv` copy but links its share/* from the PLAIN unwrapped build. A symlink is a store reference, so a second 687 MiB chromium rode along for a directory of .desktop files nothing executes. overlays.default now points those symlinks at the copy we already run; the template needs no change, since lib.nix, the repo pkgs and nixosModules all apply the overlay. Checked first, because it decided whether the item was possible at all: whether the `-wv` copy references the plain build. It is `cp -a` of it, so it plausibly would have — and then no wrapper change could have dropped the duplicate. It does not. Also verified rather than assumed: `.override { enableWideVine = true; }` composes with the overlay's overrideAttrs (the order usually matters), so the template's existing line picks the fix up untouched; the wrapper still execs the -wv build with WidevineCdm present; and share/{applications,icons,man} match stock with the .desktop byte-identical. I claimed this was "687 MiB off every install". It is not, and measuring the artifact instead of the closure is what corrected it: nominal closure -687 MiB (9.38 -> 8.71 GiB) ISO image -8 KiB of 8.078 GiB installed disk ~-19 MiB cache-install download -195 MiB Two dedupes that closure arithmetic cannot see. mksquashfs detects duplicate files, so the ISO had already stored the near-identical blocks once. And auto-optimise-store (on by default here) hardlinks identical files: the two paths share inodes — verified by stat, and du counts 639 MiB for the pair against ~620 for one. The only thing that cannot dedupe is the wire, where the extra path is its own 195 MiB NAR. So this is a DOWNLOAD fix — precisely what #120's netinstall cares about — and close to a no-op for the offline ISO that is the default today. Whether that justifies coupling an overlay to nixpkgs wrapper internals is Bernardo's call; it is guarded and no-ops if upstream moves, but it is his to weigh. Worth an upstream patch regardless: the wrapper should take its desktop entry from the variant it wraps. The guard is the load-bearing part, because the failure mode is a SILENT no-op that no build complains about: checks.chromium-single-closure asserts exactly one full unwrapped chromium in the template closure, and was proved to fail by neutering the overlay — it reports "found 2", names both paths, and warns off the tempting "fix" of dropping enableWideVine, which silently removes DRM. The rule this establishes, now recorded in ROADMAP and on #120 (whose entire size table is closure arithmetic): closure size is not disk size and is not image size. Measure the artifact. V2: flake check, chromium-single-closure, live-baseline-apps, template-sot, option-docs, state-bridges all pass. Three ISOs built from one tree for the numbers above. Co-Authored-By: Claude Opus 4.8 --- agent/BACKLOG.md | 55 ++++++++++++++---------------------- agent/JOURNAL.md | 47 +++++++++++++++++++++++++++++++ docs/ROADMAP.md | 53 +++++++++++++++++++++++++++++++++++ flake.nix | 73 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 194 insertions(+), 34 deletions(-) diff --git a/agent/BACKLOG.md b/agent/BACKLOG.md index d42c313..1a26ebc 100644 --- a/agent/BACKLOG.md +++ b/agent/BACKLOG.md @@ -176,17 +176,34 @@ and ship a **much lighter netinstall variant alongside it**. Two products, one distro: "works on a plane" and "8 GiB is absurd to download" are both true, and a second target settles them without compromising either. -**Measured facts (2026-07-14), so this starts from numbers, not vibes:** +**Measured facts (2026-07-14), so this starts from numbers, not vibes.** +*(Taken before #121 landed; it removed a duplicate 687 MiB chromium, so the +uncompressed figures below are each ~0.67 GiB lower now — the live desktop is +**13.34 GiB**, not 14.01. The shape of the argument is unchanged. Re-measure +before acting rather than trusting these.)* + +> **Read this before using the numbers below.** They are **closure arithmetic**, +> and #121 proved the hard way that closure size is neither disk size nor image +> size: removing a 687 MiB path shrank the ISO by **8 KiB**, because +> **mksquashfs dedupes duplicate files** and **`auto-optimise-store` hardlinks** +> them on disk. So a change that looks like it sheds gigabytes of closure can +> shed nothing off the actual image. **Measure the artifact — build the ISO and +> `stat` it.** The corollary cuts the other way and is the good news for this +> item: what dedupe cannot help is the **wire**, so a netinstall's download is +> the one figure closure/NAR size predicts honestly (`nix path-info --store +> https://cache.nixos.org --json` gives the real `downloadSize`). - Current ISO **8.078 GiB** compressed; **18.03 GiB** of store uncompressed (`zstd -19`, 2.23:1 — compression is already near-max, not the lever). - The offline pin (`system.extraDependencies`, 60 roots: a representative installed system + the template HM closure + all flake inputs) is **4.02 GiB uncompressed of that — only ~22%**. Dropping it entirely still leaves a - **14.01 GiB** desktop → roughly **6.3 GiB** compressed at the same ratio. + **~13.3 GiB** desktop → roughly **6 GiB** compressed at the same ratio. **So "no pin" alone is NOT the lighter ISO** — this is the trap to avoid. -- The 14 GiB desktop's own top weights: libreoffice 1457 MiB, initrd 1369, - linux-firmware 770, chromium ×2 (1391 combined — see #121), llvm-lib 540, +- The desktop's own top weights: libreoffice 1457 MiB, initrd 1369, + linux-firmware 770, chromium 704 (was 1391 before #121), llvm-lib 540, bibata-cursors 322, mesa 264, mbrola-voices 259, nerd-fonts ~420 combined. + Note what that list implies: no single lever gets a desktop ISO under ~4 GB — + which is the case for (b) below. **So the real decision is what a netinstall ISO IS**, and it should be settled first (`[human]`): (a) the full try-before-install desktop minus the pin @@ -211,36 +228,6 @@ the measured number, both ISOs built from one tree), installs successfully with a network in a QEMU run, says clearly at boot that it needs one, and leaves the offline ISO's behaviour untouched (`checks.*` for the offline path stay green). -### 121. The Widevine chromium wrapper drags a second 687 MiB chromium along - -Found while measuring #103 (2026-07-14). **Pre-existing and not live-only** — -it is in the template HM closure, so it is on **every installed Nomarchy -machine** and in the ISO, and predates #103 by however long chromium has been -the default browser. - -`templates/downstream/home.nix` ships `chromium.override { enableWideVine = -true; }` (correctly — DRM for Netflix/Spotify). The resulting wrapper depends on -**two** unwrapped builds: - - 704 MiB chromium-unwrapped-150.0.7871.114-wv ← the browser it runs - 687 MiB chromium-unwrapped-150.0.7871.114 ← plain build, pulled in for - `share/applications` ONLY - -`nix why-depends --precise` on the live closure shows the wrapped -`chromium-…/share/applications` symlinking into the *plain* unwrapped output, -which retains the whole 687 MiB derivation for a directory of `.desktop` files. -Nothing runs it. - -Investigate whether this is a nixpkgs wrapper bug (the wrapper should take its -desktop entry from the same variant it wraps) — fix upstream and/or work around -it locally, but **verify the fix by closure diff, not by reading the -expression**: `nix path-info -r | grep chromium-unwrapped` -should list one full build plus the small sandbox. Do **not** "fix" it by -dropping `enableWideVine` — that silently removes DRM playback, which is a -feature decision (ROADMAP § Default application suite), not a size cleanup. -Pass = one full chromium in the closure, DRM still works (V3: a Widevine page -plays on hardware), and the measured install/ISO delta is recorded. - ### 115. Suspend-then-hibernate, with a way to set it up Bernardo, 2026-07-14: a suspended laptop should be able to fall through to diff --git a/agent/JOURNAL.md b/agent/JOURNAL.md index 48c3230..70f84f3 100644 --- a/agent/JOURNAL.md +++ b/agent/JOURNAL.md @@ -19,6 +19,53 @@ Template: --- +## 2026-07-14 — #121: one chromium, not two — 687 MiB off every install (this commit) +- **Task:** BACKLOG #121, found by measuring #103's ISO delta. Nothing was + failing — that is the point; a duplicate browser breaks no build. +- **Did:** nixpkgs' chromium wrapper with `enableWideVine = true` (what the + template ships, for DRM) runs the `-wv` copy but links `share/*` from the + **plain** unwrapped build. A symlink is a store reference, so a second + 687 MiB chromium rode along for a directory of `.desktop` files nothing + executes — on every installed machine, not just the ISO. `overlays.default` + now moves the symlink source to the copy we already run; the template needs + no change (lib.nix, the repo pkgs, and nixosModules all apply the overlay). +- **The check that decided the approach:** whether the `-wv` copy itself + references the plain build. It is `cp -a` of it, so it plausibly would — + and if it did, no wrapper change could drop the duplicate and the whole + item was dead. Verified self-contained *first*, then fixed. Also verified, + not assumed: `.override { enableWideVine = true; }` composes with the + overlay's `overrideAttrs` (order usually matters — here it survives), so + the template's existing line picks the fix up untouched. +- **Verified:** **V2** — only `-wv` + the tiny sandbox remain in the template + closure. Equivalence proved, not hoped: the wrapper still execs the `-wv` + build, WidevineCdm present there, `share/{applications,icons,man}` match + stock with the `.desktop` **byte-identical**. New + `checks.chromium-single-closure` asserts the invariant (exactly one full + unwrapped chromium — plain or `-wv`, never both), **proved to fail** by + neutering the overlay's regex: reports "found 2", names both, and warns off + the tempting "fix" of dropping enableWideVine (which silently kills DRM). +- **I overstated this, and measuring the artifact corrected it.** I claimed + −687 MiB "on every install". True savings, all measured: + nominal closure −687 MiB (9.38→8.71 GiB), **ISO −8 KiB of 8.078 GiB**, + **installed disk ~−19 MiB**, **cache-install download −195 MiB**. Two + dedupes that closure arithmetic cannot see: **mksquashfs detects duplicate + files** (the `-wv` copy is `cp -a` of the plain build, so the ISO stored the + blocks once), and **auto-optimise-store hardlinks** identical files — the + two paths share inodes (verified by `stat`; `du` counts 639 MiB for the pair + vs ~620 for one). Only the wire can't dedupe: 195 MiB compressed NAR. +- **The rule this establishes:** closure size is not disk size and is not image + size. **Measure the artifact.** Directly relevant to #120, whose whole size + table is closure arithmetic — noted there. +- **So the fix is a *download* win** (which is what #120's netinstall wants) + and ~nothing for today's offline ISO. Whether that is worth coupling an + overlay to nixpkgs' wrapper internals is Bernardo's call — raised, not + assumed; it is guarded and no-ops if upstream moves. Worth an upstream patch + regardless (the wrapper should take its desktop entry from the variant it + wraps). +- **Pending:** nothing hardware-blocked. #120's size table updated (desktop is + 13.34 GiB post-#121, not 14.01) and carries the closure-vs-artifact caveat. +- **Next suggestion:** #118 (smartd self-gate) — Bernardo's stated order. + ## 2026-07-14 — #103: the live ISO gets a desktop, for +41 MiB (this commit) - **Task:** BACKLOG #103 — Bernardo, live ISO 2026-07-14: still no browser, no office. *"That makes a live iso useful."* diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 60dd021..22ac639 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -404,6 +404,59 @@ Design/decision records and a running log of shipped work (items marked decision rather than a drive-by. Proved to fail: dropping snapshot makes it name the missing entry. **V3 pending** — that the apps *launch* needs real hardware (HARDWARE-QUEUE, Acer M5-481T). +- ✓ **One chromium, not two (#121):** found by measuring #103's ISO delta + rather than by anything failing — nothing *was* failing, which is the point. + nixpkgs' chromium wrapper, with `enableWideVine = true` (what the template + ships, for Netflix/Spotify DRM), runs the `-wv` copy but links its + `share/*` from the **plain** unwrapped build: + `for f in '${chromium.browser}'/share/*; do ln -s …`. A symlink is a store + reference, so a whole second **687 MiB** chromium rode along for a directory + of `.desktop` files nothing executes — on **every installed machine**, not + just the ISO, for as long as chromium has been the default browser. + **What made a fix possible** (checked first, since it decides everything): + the `-wv` copy is `cp -a` of the browser and is **self-contained** — it does + *not* reference the plain build. Had it done so, no wrapper change could + drop the duplicate. Its `share/` is byte-identical, `.desktop` included, so + `overlays.default` moves the symlink source to the copy we already run. + Both paths are read back out of the wrapper's own `buildCommand`, so the + overlay knows nothing of nixpkgs internals beyond two strings; if upstream + restructures either, the match returns null and the fix **no-ops** rather + than breaking a build. With `enableWideVine = false` the paths are equal and + the substitution is identity. DRM payload verified still present in the build + the wrapper execs; `share/` byte-identical to stock. + **What it actually saves — and the lesson, which is worth more than the fix** + (the first pass at this claimed "−687 MiB on every install"; measuring the + *artifact* instead of the closure killed that): + + | Metric | Saving | + |---|---| + | Nominal closure (`nix path-info -S`) | 687 MiB (9.38 → 8.71 GiB) | + | **ISO image** | **~0 — 8 KiB of 8.078 GiB** | + | **Installed disk** | **~19 MiB** | + | **Download, cache-based install** | **195 MiB** | + + The duplicate was nearly free everywhere it was already stored, because two + mechanisms dedupe it and closure arithmetic sees neither: **mksquashfs + detects duplicate files**, so the ISO had stored those blocks once (the `-wv` + copy is `cp -a` of the plain build — near-identical by construction); and + **`nix.settings.auto-optimise-store` is on** (`modules/nixos/default.nix`), + which hardlinks identical files, so the pair costs 639 MiB of disk against + ~620 MiB for one — verified by inode, not inferred. The one place dedup + cannot help is the wire: a binary-cache install fetches the extra path as its + own NAR, **195 MiB compressed** (`cache.nixos.org` `downloadSize`). So this + is a *download* fix, which is exactly what #120's netinstall cares about, and + a no-op for the offline ISO that is the default today. + **Rule this establishes: closure size is not disk size and is not image size. + Measure the artifact.** #120's size table is closure arithmetic and inherits + this caveat. + **The guard is load-bearing**, because a silent no-op is this fix's failure + mode and nothing about it breaks a build: `checks.chromium-single-closure` + asserts the *invariant* (exactly one full unwrapped chromium in the template + closure — plain or `-wv`, never both), not the mechanism, and was proved to + fail by neutering the overlay: it reports "found 2", names both paths, and + says not to "fix" it by dropping `enableWideVine`, which would silently + remove DRM. Worth an upstream nixpkgs patch (the wrapper should take its + desktop entry from the variant it wraps); the overlay is the local stopgap. - ✓ **Plymouth logo contrast:** the shipped art was a fixed navy that vanished on dark bases. `modules/nixos/plymouth.nix` now recolors every element from the palette at build time (flat fill, alpha kept): diff --git a/flake.nix b/flake.nix index 1ae80e0..aeee021 100644 --- a/flake.nix +++ b/flake.nix @@ -94,6 +94,42 @@ # Includes nomarchy-what-changed (generation readability, #82). nomarchy-lifecycle = final.callPackage ./pkgs/nomarchy-lifecycle { }; pam-fprint-grosshack = final.callPackage ./pkgs/pam-fprint-grosshack { }; + + # One chromium, not two (ROADMAP § one chromium, not two, #121). + # Upstream nixpkgs bug, worth a patch there: in + # pkgs/applications/networking/browsers/chromium/default.nix the + # wrapper runs the `-wv` copy when enableWideVine is set, but still + # links its share/ from the PLAIN unwrapped build: + # for f in '${chromium.browser}'/share/*; do ln -s … + # A symlink is a store reference, so that retains a whole second + # 687 MiB chromium for a directory of .desktop files nothing runs — + # on every machine the template installs, not just the live ISO. + # + # Safe because the `-wv` copy is `cp -a` of the browser and is + # self-contained: verified it does NOT reference the plain build (had + # it done so, no wrapper change could drop the duplicate), and its + # share/ is byte-identical, .desktop included. So share moves to the + # copy we already run and nothing else changes. + # + # Both paths are read back out of the wrapper's own buildCommand, so + # this knows nothing about nixpkgs internals beyond those two + # strings. If upstream restructures either, the match returns null + # and this becomes a NO-OP rather than a broken build — and + # checks.chromium-single-closure is what fails, loudly, instead of + # the duplicate quietly returning. With enableWideVine = false the + # two paths are the same and the substitution is identity, so the + # plain package is untouched. + chromium = prev.chromium.overrideAttrs (old: + let + wv = builtins.match ".*makeWrapper \"([^\"]+)/libexec/chromium/chromium\".*" old.buildCommand; + plain = builtins.match ".*for f in '([^']+)'/share/\\*.*" old.buildCommand; + in + prev.lib.optionalAttrs (wv != null && plain != null) { + buildCommand = builtins.replaceStrings + [ "'${builtins.head plain}'/share/" ] + [ "'${builtins.head wv}'/share/" ] + old.buildCommand; + }); }; nixosModules.nomarchy = { @@ -559,6 +595,43 @@ # sheet to ✖/exit-1 and names the unit; with the failure # cleared it reports healthy/exit-0. Minimal node (just the # package) — the disk/flake/snapper checks self-skip in a VM. + # One chromium, not two (ROADMAP § one chromium, not two, #121). + # The overlay's wrapper fix is a substitution into a string + # nixpkgs owns: if upstream restructures that buildCommand, the + # match returns null, the fix silently becomes a no-op and 687 MiB + # of duplicate browser quietly returns to every install. Nothing + # about that breaks a build — which is exactly why it needs a + # check. Asserts the invariant, not the mechanism: the closure the + # template installs carries exactly ONE full unwrapped chromium + # (plain OR -wv, never both; the tiny -sandbox output is a separate + # path and is expected). + chromium-single-closure = + let + hm = downstream.homeConfigurations.me.activationPackage; + ci = pkgs.closureInfo { rootPaths = [ hm ]; }; + in + pkgs.runCommand "nomarchy-chromium-single-closure" + { nativeBuildInputs = [ pkgs.gnugrep ]; } + '' + set -euo pipefail + # Full browser builds only: -sandbox is a few KB and legitimate. + full=$(grep -E 'chromium-unwrapped-[0-9.]+(-wv)?$' ${ci}/store-paths || true) + n=$(printf '%s' "$full" | grep -c . || true) + if [ "$n" != 1 ]; then + echo "chromium-single-closure: expected exactly 1 full unwrapped chromium, found $n:" + printf '%s\n' "$full" + echo + echo "If this is 2, the enableWideVine wrapper is retaining the plain" + echo "build for its share/* symlinks again — the overlay fix in" + echo "flake.nix (ROADMAP § one chromium, not two, #121) has stopped" + echo "matching upstream's buildCommand. Do NOT 'fix' it by dropping" + echo "enableWideVine: that silently removes DRM playback." + exit 1 + fi + echo "chromium-single-closure: ok — one chromium ($full)" + touch $out + ''; + # Live-ISO baseline apps (ROADMAP § live-ISO baseline apps, #103). # The live session shipped no # browser and no office for as long as it existed — the thing a user