fix(pkgs): one chromium, not two — and what that is actually worth
All checks were successful
Check / eval (push) Successful in 3m32s

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 <noreply@anthropic.com>
This commit is contained in:
2026-07-14 17:26:24 +01:00
parent 00b1e10fc3
commit 0b464af3e5
4 changed files with 194 additions and 34 deletions

View File

@@ -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 distro: "works on a plane" and "8 GiB is absurd to download" are both true, and
a second target settles them without compromising either. 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 - 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). (`zstd -19`, 2.23:1 — compression is already near-max, not the lever).
- The offline pin (`system.extraDependencies`, 60 roots: a representative - The offline pin (`system.extraDependencies`, 60 roots: a representative
installed system + the template HM closure + all flake inputs) is **4.02 GiB installed system + the template HM closure + all flake inputs) is **4.02 GiB
uncompressed of that — only ~22%**. Dropping it entirely still leaves a 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. **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, - The desktop's own top weights: libreoffice 1457 MiB, initrd 1369,
linux-firmware 770, chromium ×2 (1391 combined — see #121), llvm-lib 540, linux-firmware 770, chromium 704 (was 1391 before #121), llvm-lib 540,
bibata-cursors 322, mesa 264, mbrola-voices 259, nerd-fonts ~420 combined. 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 **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 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 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). 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 <hm-generation> | 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 ### 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 Bernardo, 2026-07-14: a suspended laptop should be able to fall through to

View File

@@ -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) ## 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, - **Task:** BACKLOG #103 — Bernardo, live ISO 2026-07-14: still no browser,
no office. *"That makes a live iso useful."* no office. *"That makes a live iso useful."*

View File

@@ -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 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 name the missing entry. **V3 pending** — that the apps *launch* needs real
hardware (HARDWARE-QUEUE, Acer M5-481T). 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 - ✓ **Plymouth logo contrast:** the shipped art was a fixed navy that
vanished on dark bases. `modules/nixos/plymouth.nix` now recolors every vanished on dark bases. `modules/nixos/plymouth.nix` now recolors every
element from the palette at build time (flat fill, alpha kept): element from the palette at build time (flat fill, alpha kept):

View File

@@ -94,6 +94,42 @@
# Includes nomarchy-what-changed (generation readability, #82). # Includes nomarchy-what-changed (generation readability, #82).
nomarchy-lifecycle = final.callPackage ./pkgs/nomarchy-lifecycle { }; nomarchy-lifecycle = final.callPackage ./pkgs/nomarchy-lifecycle { };
pam-fprint-grosshack = final.callPackage ./pkgs/pam-fprint-grosshack { }; 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 = { nixosModules.nomarchy = {
@@ -559,6 +595,43 @@
# sheet to ✖/exit-1 and names the unit; with the failure # sheet to ✖/exit-1 and names the unit; with the failure
# cleared it reports healthy/exit-0. Minimal node (just the # cleared it reports healthy/exit-0. Minimal node (just the
# package) — the disk/flake/snapper checks self-skip in a VM. # 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). # Live-ISO baseline apps (ROADMAP § live-ISO baseline apps, #103).
# The live session shipped no # The live session shipped no
# browser and no office for as long as it existed — the thing a user # browser and no office for as long as it existed — the thing a user