Skip to main content

max / alloy

Drop Go from the default LANGS set Ruled by Max 2026-08-17, reversing the same day's "the default is what compiles the shipped stack". That rule already stopped short of the C tier (sway, mako and swaylock want a buildroot, not a toggle), so it was true for two thirds of the stack and quietly false for the rest, and the exclusive-closure pass priced the remainder at 356 MB against seven Go programs nobody has rebuilt. Nothing in ~/Code is Go either: a full find turns up four tree-sitter fixtures in an archived repo and no go.mod. The new rule is what the image itself requires, which is Rust alone, because sandod shells out to cargo and refuses to compile off its configured host. Both rulings are recorded at `ARG LANGS` rather than only the surviving one. `ARG LANGS` and `Choices::default` move together; the existing the_langs_default_matches_the_builder test is what holds them together. The builder's Go row now carries its price, since it is a thing to opt into. Also fixes the cursor assertion from 068d6cb, which shipped with no else branch and had been failing every_profile_conditional_asserts_on_both_branches since. The server claim is the stronger one: bibata-cursor-theme is named only in the client branch, so no Bibata directory should exist at all.
Author: Max Johnson <me@maxj.phd> · 2026-08-17 21:36 UTC
Signed with PGP, not checked
Commit: 67c101469402f728543ce8d2fc240e1d88f2ddb8
Parent: ffe25b4
3 files changed, +76 insertions, -38 deletions
M Containerfile +48 -19
@@ -596,33 +596,48 @@
596 596 # treated as a package list — the builder is not a package manager, and
597 597 # the gate lives here so it holds even when the TUI is bypassed.
598 598 #
599 - # THE DEFAULT IS WHAT COMPILES THE SHIPPED STACK, ruled by Max 2026-08-17:
600 - # someone who wants to rebuild a program Alloy ships should not have to
601 - # install a toolchain first. Measured against the client image rather than
602 - # assumed, by reading each shipped binary for a Go build id or a rustc
603 - # path:
599 + # THE DEFAULT IS WHAT THE IMAGE ITSELF REQUIRES, ruled by Max 2026-08-17.
600 + # So `rust`, alone. Rust is not a preference here: sandod shells out to
601 + # `cargo build --release` and refuses to compile anywhere but its
602 + # configured build host, so an image that quietly stopped carrying cargo
603 + # would break Sando rather than merely save disk. Nothing else in the
604 + # image makes that claim.
605 + #
606 + # GO WAS IN THIS DEFAULT AND CAME BACK OUT THE SAME DAY. Recording both
607 + # rulings, because the second reverses the first and a bare `rust` reads
608 + # like the question was never asked.
609 + #
610 + # The first rule was "the default is what compiles the shipped stack":
611 + # someone rebuilding a program Alloy ships should not have to install a
612 + # toolchain first. It was measured rather than assumed, by reading each
613 + # shipped binary for a Go build id or a rustc path:
604 614 #
605 615 # Rust hx yazi nu starship zoxide btm dua rg fd bat eza satty, plus
606 616 # alloy and shop themselves. 14.
607 617 # Go direnv fzf gopass syncthing restic tailscale cliphist. 7.
608 618 # C sway mako swaylock, and the hardware-health group.
609 619 #
610 - # So `rust,go`. Go is the cheaper half at 95 MiB inbound against rust's
611 - # 176, and it covers the whole continuity tier — syncthing, restic and
612 - # tailscale are the programs most likely to be wanted a version ahead of
613 - # what Fedora carries.
620 + # What killed it is that the rule was never applied evenly. It already
621 + # stopped short of the C tier — sway and mako want meson, ninja,
622 + # wayland-protocols and a spread of -devel packages that no language
623 + # toggle models — so "you can rebuild what we ship" was true for two
624 + # thirds of the stack and quietly false for the rest. A rule with a hole
625 + # that size is a preference. Then the exclusive-closure pass priced it:
626 + # Go's marginal cost is 356 MB, six percent of the image, against seven
627 + # programs nobody has rebuilt. Nothing in ~/Code is Go either — a full
628 + # find turns up four tree-sitter fixtures in an archived repo and no
629 + # go.mod — so the toolchain was not earning its place from the build-host
630 + # side either.
614 631 #
615 - # The C tier is deliberately NOT covered and the rule stops short of it.
616 - # `gcc` arrives with rust, so a single-file C program builds, but sway and
617 - # mako want meson, ninja, wayland-protocols and a spread of -devel
618 - # packages that no language toggle models. Naming that here rather than
619 - # pretending the rule is uniform: the three C programs in the stack are
620 - # the ones a user rebuilds by layering a buildroot, not by flipping a
621 - # toggle in the builder.
632 + # THE COST, STATED SO IT IS NOT DISCOVERED: rebuilding syncthing, restic,
633 + # tailscale, gopass, direnv, fzf or cliphist from source on a stock image
634 + # now needs `LANGS=rust,go` at mint time, or a toolchain in $HOME. That is
635 + # the trade, and it is reversible at zero cost, which most things here are
636 + # not.
622 637 #
623 - # python and zig stay off for the same reason go is now on: nothing
624 - # shipped is written in either.
625 - ARG LANGS=rust,go
638 + # python and zig stay off for the reason go now is: nothing in the image
639 + # requires them.
640 + ARG LANGS=rust
626 641
627 642 # TRIM is the one builder choice about the *base* rather than about Alloy.
628 643 # fedora-bootc is a general-purpose server base, and three of the things it
@@ -1731,6 +1746,20 @@
1731 1746 || { echo "$(basename "$theme") survived and skel does not name it; the prune in the package block stopped running and the image is carrying ~12-27 MB per unused theme" >&2; exit 1; }; \
1732 1747 done; \
1733 1748 echo "cursor: $(echo "$names" | tr '\n' ' ')kept, every other Bibata theme pruned"; \
1749 + else \
1750 + # The asserting else, same rule as the package block: a check that runs
1751 + # on one profile and silently passes on the other is not a check. The
1752 + # server claim is the stronger one — bibata-cursor-theme is named only
1753 + # inside the client branch, so a server image should carry no Bibata
1754 + # directory at all. If one is here, the graphical group leaked across
1755 + # the profile split and 179 MB of cursors came with it, on an image with
1756 + # no session to draw a cursor in.
1757 + for theme in /usr/share/icons/Bibata-*; do \
1758 + [ -d "$theme" ] || continue; \
1759 + echo "$(basename "$theme") is in the server image; bibata-cursor-theme is named only in the client branch, so the profile split leaked" >&2; \
1760 + exit 1; \
1761 + done; \
1762 + echo "cursor: none, as the server profile draws none"; \
1734 1763 fi
1735 1764
1736 1765 # =====================================================================
M docs/IMAGE.md +5 -1
@@ -116,6 +116,10 @@
116 116 `BROWSER=helium LANGS=rust,go TRIM=unused`. It was 6.19 GB as an image and 5.26 GB
117 117 deployed, and the 0.9 GB between those two numbers is the subject of half this section.
118 118
119 + Every figure below is from that build, so they describe an image built with Go in it.
120 + The default became `LANGS=rust` later the same day, which takes the language layer down
121 + with it; the numbers are left as measured rather than adjusted on paper.
122 +
119 123 Two sizes, and they answer different questions. **Image size** is the sum of the layers:
120 124 what a registry stores and what `bootc upgrade` moves. **Deployed size** is the final
121 125 filesystem: what the disk holds and what the ISO squashes. A file written in one layer
@@ -128,7 +132,7 @@
128 132 | Size | Layer | Why it is that size |
129 133 |------|-------|---------------------|
130 134 | 1.44 GB | sway, `xdg-desktop-portal{,-gtk,-wlr}`, the session | The product. This is the desktop. |
131 - | 778 MB | `LANGS=rust,go` | Chosen, and argued at `ARG LANGS`. Go is 230 MB of it. |
135 + | 778 MB | `LANGS=rust,go` | As measured. Go is 230 MB of this layer, and 356 MB by exclusive closure once the packages only it pulls in are counted; that gap is the two questions the measurements answer, not a disagreement. Go left the default on 2026-08-17, so a stock image builds this layer at `LANGS=rust`. Argued at `ARG LANGS`. |
132 136 | 674 MB | `helium-browser-bin` | The product, and the one stack pick Alloy declines to make. |
133 137 | 663 MB | Base package list | The stack, per [STACK.md](STACK.md). |
134 138 | 170 MB | initramfs + `rpmostree-unpackaged-content` | Bootable image. |
@@ -279,10 +279,15 @@
279 279 /// Rust's figure is the Containerfile's own measurement, and it is the
280 280 /// reason this row exists: 610 MiB is an order of magnitude above the
281 281 /// hardware-health group and by a distance the largest thing in the image.
282 + ///
283 + /// Go's is its marginal cost from the 2026-08-17 exclusive-closure pass —
284 + /// what turning this row on actually adds, rather than what `golang`
285 + /// declares. It is priced here because it is off by default now, so the
286 + /// number is what someone is deciding to spend.
282 287 const fn label(self) -> &'static str {
283 288 match self {
284 289 Lang::Rust => "rust (610 MiB; needed for the build-host role)",
285 - Lang::Go => "go",
290 + Lang::Go => "go (356 MB; rebuilds syncthing, restic, tailscale)",
286 291 Lang::Python => "python",
287 292 Lang::Zig => "zig",
288 293 }
@@ -410,19 +415,19 @@
410 415 Self {
411 416 profile: Profile::default(),
412 417 browser: Browser::default(),
413 - // The default is whatever compiles the stack Alloy ships (Max,
414 - // 2026-08-17): someone rebuilding a program that came with the
415 - // image should not have to install a toolchain to do it. Measured
416 - // off the client image, 14 shipped binaries are Rust (helix, yazi,
417 - // nushell, starship, the whole CLI tier, plus alloy and shop) and 7
418 - // are Go (syncthing, restic, tailscale, gopass, direnv, fzf,
419 - // cliphist), so both are on and python and zig are not.
418 + // The default is what the image itself requires (Max,
419 + // 2026-08-17), which is Rust and nothing else: sandod refuses to
420 + // compile anywhere but its configured host, so an image that
421 + // silently stopped being able to build Rust would break Sando
422 + // rather than merely save disk.
420 423 //
421 - // Rust also carries the build-host role, which is a hard
422 - // requirement on its own: sandod refuses to compile anywhere but
423 - // its configured host, so an image that silently stopped being able
424 - // to build Rust would break Sando rather than merely save disk.
425 - langs: BTreeSet::from([Lang::Rust, Lang::Go]),
424 + // Go was in this default earlier the same day, under the rule "the
425 + // default is what compiles the shipped stack" — 7 of the shipped
426 + // binaries are Go (syncthing, restic, tailscale, gopass, direnv,
427 + // fzf, cliphist). That rule already stopped short of the C tier, so
428 + // it was two-thirds true, and Go's marginal cost measured 356 MB.
429 + // Reversed. `ARG LANGS` in the Containerfile carries both rulings.
430 + langs: BTreeSet::from([Lang::Rust]),
426 431 hostname: String::new(),
427 432 pubkey: String::new(),
428 433 artifact: Artifact::default(),
@@ -1392,13 +1397,13 @@
1392 1397 use super::*;
1393 1398
1394 1399 #[test]
1395 - fn the_default_is_the_desktop_with_what_compiles_the_stack() {
1400 + fn the_default_is_the_desktop_with_what_the_image_requires() {
1396 1401 let choices = Choices::default();
1397 1402 assert_eq!(choices.profile, Profile::Client);
1398 1403 assert_eq!(choices.browser, Browser::Helium);
1399 - // Both, and only both: the shipped stack is Rust and Go, and nothing
1400 - // in it is python or zig. See the comment on `Choices::default`.
1401 - assert_eq!(choices.langs, BTreeSet::from([Lang::Rust, Lang::Go]));
1404 + // Rust, and only Rust: it is the one toolchain the image itself needs,
1405 + // for the build-host role. See the comment on `Choices::default`.
1406 + assert_eq!(choices.langs, BTreeSet::from([Lang::Rust]));
1402 1407 assert_eq!(choices.artifact, Artifact::Iso);
1403 1408 // Trimmed by default. What it costs is foreign-architecture emulation,
1404 1409 // which the house rules forbid using in the first place.
@@ -1419,7 +1424,7 @@
1419 1424 // the two defaults are one decision written in two files, and a build
1420 1425 // that bypasses the TUI must get the same stack the TUI would have
1421 1426 // asked for.
1422 - assert_eq!(args[2].1, "rust,go");
1427 + assert_eq!(args[2].1, "rust");
1423 1428 assert_eq!(args[3].1, "unused");
1424 1429 }
1425 1430