Skip to main content

max / alloy

Pick Firefox as the browser, drop Helium Ruled 2026-08-18. Wiki alloy-byo-principle, "The browser stops being BYO" and "The mint-time arm". The browser leaves bring-your-own and becomes a defended pick. Two things ended the previous ruling, a day old. Helium comes from Terra, which is disabled after install, so no machine that chose BROWSER=none could reach it by a supported route: the floor BYO rested on did not hold. And a candidate needs support for image-based systems, stated or inherent, which leaves Fedora's stock chromium as the only other Blink option and that is the un-mitigated defaults Helium exists to fix. One defensible candidate is a pick, not a list. Helium is gone entirely rather than kept as a mint-time arm. It is a maintained patchset over another vendor's bad defaults, and shipping it as Alloy's answer adopts that mitigation as Alloy's position. BROWSER is firefox or none. none stays: it is a real answer, and Firefox being in Fedora's own repos means that machine is one rpm-ostree install from the browser the image would have carried. Picking Firefox costs one file. /etc/firefox/pref/alloy.js removes anti-features (telemetry, sponsored placements, the AI block, tracking protection at standard) and restores one control Mozilla hides (browser.compactmode.show). It sets no appearance: browser.uidensity is Alloy's taste and stays out, and the Containerfile asserts that, asserts the file is present when it is needed, and asserts nothing in it is locked. Every pref is a starting position the user can change. Pocket is not in the list. Firefox 153 has no extensions.pocket pref at all; the service is gone, and the older notes carried it forward. IMAGE.md's size numbers are left as measured against a Helium build rather than adjusted on paper, same as the LANGS change before it.
Author: Max Johnson <me@maxj.phd> · 2026-08-18 16:34 UTC
Signed with PGP, not checked
Commit: cd385969e373734c74038b1b8634886819ebf67b
Parent: 69c3038
13 files changed, +348 insertions, -120 deletions
M Containerfile +91 -24
@@ -582,12 +582,17 @@
582 582 # make, which is why these two are ARGs and the theme, terminal, editor
583 583 # and shell are not.
584 584 #
585 - # BROWSER is the case docs/STACK.md already names a non-endorsement:
586 - # Alloy argues for helix over vim, and cannot make the equivalent
587 - # argument between Blink and Gecko. `none` is a real answer, not an
588 - # error — someone installing their own from Flathub should not pay for
589 - # one they will remove.
590 - ARG BROWSER=helium
585 + # BROWSER is not the non-endorsement it was until 2026-08-18. Alloy
586 + # picks Firefox and defends it (wiki `alloy-byo-principle`), so this ARG
587 + # is no longer "the choice Alloy declines to make" — it survives because
588 + # `none` is a real answer, not an error: someone installing their own
589 + # from Flathub should not pay for one they will remove.
590 + #
591 + # Alloy still makes no claim that Gecko is the better engine. The claim
592 + # is about the candidate. Firefox is the only browser reachable on an
593 + # image-based system whose defaults Alloy can stand behind, and standing
594 + # behind them costs one file: /etc/firefox/pref/alloy.js.
595 + ARG BROWSER=firefox
591 596
592 597 # LANGS is asked for by name in the rust comment further down: 610 MiB
593 598 # across 16 packages, "per-image language selection at mint time is the
@@ -692,8 +697,9 @@
692 697 *) echo "unknown PROFILE '$PROFILE'; expected 'client' or 'server'" >&2; exit 1 ;; \
693 698 esac; \
694 699 case "$BROWSER" in \
695 - helium|firefox|none) ;; \
696 - *) echo "unknown BROWSER '$BROWSER'; expected 'helium', 'firefox' or 'none'" >&2; exit 1 ;; \
700 + firefox|none) ;; \
701 + helium) echo "BROWSER=helium was removed 2026-08-18: Alloy does not ship a third party's patchset over Chromium's defaults. Use 'firefox' or 'none'." >&2; exit 1 ;; \
702 + *) echo "unknown BROWSER '$BROWSER'; expected 'firefox' or 'none'" >&2; exit 1 ;; \
697 703 esac; \
698 704 for lang in $(echo "$LANGS" | tr ',' ' '); do \
699 705 case "$lang" in \
@@ -1313,17 +1319,31 @@
1313 1319 # one thing here a user chooses and a rebuild that changes only the
1314 1320 # browser should not re-resolve the whole stack.
1315 1321 #
1316 - # Helium is ungoogled-chromium with the defaults Alloy used to hand-build
1317 - # in fifty Firefox prefs and a policy file, so it ships with no
1318 - # configuration at all: no policy, no pref seed, no chrome CSS. That is
1319 - # the whole reason it is the default. Terra packages it; upstream ships
1320 - # no Flatpak, and the unofficial repackagings are worse provenance than
1321 - # this for the most attack-exposed program on the box.
1322 + # Firefox, picked and defended as of 2026-08-18. Wiki
1323 + # `alloy-byo-principle`, "The browser stops being BYO".
1322 1324 #
1323 - # Firefox is the Gecko alternative. docs/STACK.md records the browser-
1324 - # engine monoculture argument as overridden rather than withdrawn, and
1325 - # this is the line that makes the alternative real rather than
1326 - # theoretical.
1325 + # It is from Fedora's own repos, which is not incidental: they stay
1326 + # enabled after install, so this is a browser a user can also reach on a
1327 + # machine that chose `none`. That property is now a selection criterion
1328 + # for anything Alloy recommends, and it is what ended the previous
1329 + # ruling.
1330 + #
1331 + # WHAT WAS HERE UNTIL TODAY, and why it is gone. Helium is
1332 + # ungoogled-chromium with the defaults Alloy used to hand-build in fifty
1333 + # Firefox prefs, packaged by Terra as `helium-browser-bin`. It was the
1334 + # default precisely because it needed no configuration. Two things ended
1335 + # it. Terra is disabled post-install (see the repo cleanup layer), so
1336 + # Helium was unreachable on any machine that did not bake it in. And it
1337 + # is a maintained patchset over another vendor's bad defaults, which is
1338 + # not a thing Alloy ships: Helium is doing its best to make a standard
1339 + # approach tolerable, and Alloy is doing its best to improve standard
1340 + # approaches. Anyone who wants it can enable Terra on their own machine
1341 + # and layer it, which is their call and not a shape this image carries.
1342 + #
1343 + # The cost of picking Firefox is that stock Firefox has bad defaults by
1344 + # Alloy's own test, so the pick is paid for in /etc/firefox/pref/alloy.js
1345 + # — anti-features removed, one hidden control restored, nothing about
1346 + # appearance. It arrives with `COPY etc/ /etc/` and is asserted below.
1327 1347 #
1328 1348 # The assertion runs on every path including `none`, and that is the
1329 1349 # point: it proves exactly one browser is installed, or that none is, so
@@ -1332,7 +1352,6 @@
1332 1352 RUN set -eu; \
1333 1353 if [ "$PROFILE" = client ]; then \
1334 1354 case "$BROWSER" in \
1335 - helium) dnf install -y helium-browser-bin && dnf clean all ;; \
1336 1355 firefox) dnf install -y firefox && dnf clean all ;; \
1337 1356 none) echo "no browser in this image, by choice" ;; \
1338 1357 esac; \
@@ -1340,16 +1359,13 @@
1340 1359 echo "no browser: this profile has no session to run one in"; \
1341 1360 fi; \
1342 1361 installed=""; \
1343 - for candidate in helium-browser-bin firefox; do \
1362 + for candidate in firefox helium-browser-bin; do \
1344 1363 rpm -q "$candidate" >/dev/null 2>&1 && installed="$installed $candidate"; \
1345 1364 done; \
1346 1365 installed="$(echo $installed)"; \
1347 1366 expected=""; \
1348 1367 if [ "$PROFILE" = client ] && [ "$BROWSER" != none ]; then \
1349 - case "$BROWSER" in \
1350 - helium) expected="helium-browser-bin" ;; \
1351 - firefox) expected="firefox" ;; \
1352 - esac; \
1368 + expected="firefox"; \
1353 1369 fi; \
1354 1370 [ "$installed" = "$expected" ] \
1355 1371 || { echo "browser mismatch: asked for '${expected:-none}', image has '${installed:-none}'" >&2; exit 1; }; \
@@ -1702,6 +1718,57 @@
1702 1718 COPY usr/ /usr/
1703 1719 COPY --from=rust-build /staged-skel/ /
1704 1720
1721 + # =====================================================================
1722 + # The Firefox configuration is what pays for picking Firefox.
1723 + # =====================================================================
1724 + # Alloy picks a browser and defends it (wiki `alloy-byo-principle`), and a
1725 + # default has to earn that under rule 3 of `alloy-packaging-policy`. Stock
1726 + # Firefox does not earn it: telemetry, sponsored placements and tracking
1727 + # protection at "standard" all serve someone other than the person running
1728 + # it. /etc/firefox/pref/alloy.js is the payment, and without it this image
1729 + # ships the exact thing docs/STACK.md calls indefensible.
1730 + #
1731 + # Fedora's firefox rpm owns /etc/firefox/pref and /etc is writable on a bootc
1732 + # deployment, so this is one file and no write to /usr.
1733 + #
1734 + # THE THIRD CHECK IS THE INTERESTING ONE. The guard on that file is that it
1735 + # only removes anti-features and restores hidden controls, never sets Alloy's
1736 + # taste. `browser.uidensity` is the named example of the taste it may not
1737 + # set, and `browser.compactmode.show` is the honest version of the same
1738 + # want: put the control back and let the user choose. A guard nobody can
1739 + # enforce is a comment, so it is asserted here instead. If this fails, read
1740 + # the ladder in the wiki note before deleting the check.
1741 + RUN set -eu; \
1742 + conf=/etc/firefox/pref/alloy.js; \
1743 + if [ "$PROFILE" = client ]; then \
1744 + if [ "$BROWSER" = firefox ]; then \
1745 + test -f "$conf" \
1746 + || { echo "browser=firefox and $conf is not in the image; the pick is unpaid for and this image ships stock Firefox" >&2; exit 1; }; \
1747 + grep -q '^pref("browser.ml.enable", false);' "$conf" \
1748 + || { echo "$conf no longer turns off browser.ml.enable; the AI block was the substantive half of this file" >&2; exit 1; }; \
1749 + grep -q '^pref("browser.compactmode.show", true);' "$conf" \
1750 + || { echo "$conf no longer restores the density control; rung 1 of the ladder is the cheapest thing this file does" >&2; exit 1; }; \
1751 + echo "firefox: $(grep -c '^pref(' "$conf") prefs, anti-features and one restored control"; \
1752 + else \
1753 + rpm -q firefox >/dev/null 2>&1 \
1754 + && { echo "browser=none and firefox is installed anyway; something pulled in a browser nobody chose" >&2; exit 1; }; \
1755 + echo "firefox: not in this image, by choice; the config file rides along inert"; \
1756 + fi; \
1757 + else \
1758 + rpm -q firefox >/dev/null 2>&1 \
1759 + && { echo "profile=server carries firefox; the profile split leaked a browser onto an image with no session to run one in" >&2; exit 1; }; \
1760 + echo "firefox: correctly absent on a profile with no session"; \
1761 + fi; \
1762 + if [ -f "$conf" ]; then \
1763 + if grep -q '^pref("browser.uidensity"' "$conf"; then \
1764 + echo "$conf sets browser.uidensity; that is rung 3, Alloy's taste in an app it configures only to remove anti-features. Restore the control, do not pick the value." >&2; exit 1; \
1765 + fi; \
1766 + if grep -qE '^[[:space:]]*(lockPref|defaultPref)\(' "$conf"; then \
1767 + echo "$conf locks a pref; every line in it is meant to be a starting position the user can change, and a lock is Alloy hiding a control while objecting to hidden controls" >&2; exit 1; \
1768 + fi; \
1769 + echo "firefox config: no taste, no locks"; \
1770 + fi
1771 +
1705 1772 # =====================================================================
1706 1773 # The cursor theme skel names has to be the one the prune kept.
1707 1774 # =====================================================================
@@ -32,11 +32,11 @@
32 32
33 33 ### The categories, from what the image ships
34 34
35 - Counted 2026-08-06 against the built image rather than invented: `/usr/share/applications` holds 17 desktop entries, of which 7 are not `NoDisplay`. That is the whole visible surface Alloy itself installs.
35 + Counted 2026-08-06 against the built image rather than invented (the browser row is Firefox since 2026-08-18; it was Helium when this was counted, and the entry count is unchanged either way): `/usr/share/applications` holds 17 desktop entries, of which 7 are not `NoDisplay`. That is the whole visible surface Alloy itself installs.
36 36
37 37 | Category | What ships in it | XDG category a third-party app would declare |
38 38 |---|---|---|
39 - | Browser | helium | `WebBrowser` |
39 + | Browser | firefox | `WebBrowser` |
40 40 | Console | shop (no desktop entry; it is the terminal) | `TerminalEmulator` |
41 41 | Editor | helix | `TextEditor` |
42 42 | File manager | yazi | `FileManager` |
M docs/IMAGE.md +8 -2
@@ -108,7 +108,7 @@
108 108
109 109 **A package added here gets a line in `crates/alloy/credits.toml`.** The installer's last screen names the projects Alloy ships and their licenses, off a hand-curated manifest rather than a generated closure, so nothing adds itself. The manifest is embedded in the console binary with `include_str!`, which means the page cannot go missing on installer media or a read-only deployment and also means a manifest edit needs a rebuild. Its own header says which license to record: for anything packaged out of Rust or Go, Fedora's `%{LICENSE}` is the whole vendored closure rather than the project's own terms, so read upstream's LICENSE for those and use `rpm -q --qf '%{LICENSE}'` only for the C packages.
110 110
111 - The browser ships as an RPM baked into the image: one code path, no first-boot delay, and no first-run network dependency. Alloy ships no configuration for it at all, which is why it is the browser Alloy ships (see [STACK.md](STACK.md#browser)). The `flatpak` client is included so users can pull Flathub-only apps on demand post-install; no Flatpaks are provisioned at build or first-boot time.
111 + The browser ships as an RPM baked into the image: one code path, no first-boot delay, and no first-run network dependency. It is Firefox, from Fedora's own repos, and it carries exactly one file of Alloy configuration (`/etc/firefox/pref/alloy.js`, anti-features removed and one hidden control restored; see [STACK.md](STACK.md#browser)). The `flatpak` client is included so users can pull Flathub-only apps on demand post-install; no Flatpaks are provisioned at build or first-boot time.
112 112
113 113 ## Size
114 114
@@ -120,6 +120,12 @@
120 120 The default became `LANGS=rust` later the same day, which takes the language layer down
121 121 with it; the numbers are left as measured rather than adjusted on paper.
122 122
123 + The same applies to the browser, and more sharply. `BROWSER=helium` was removed on
124 + 2026-08-18 and Firefox is the only answer now, so the browser layer below is a
125 + measurement of a build this tree can no longer produce. It is left as measured for the
126 + same reason: an adjusted number is a guess wearing a measurement's clothes. Re-measure
127 + the browser layer on the next full build.
128 +
123 129 Two sizes, and they answer different questions. **Image size** is the sum of the layers:
124 130 what a registry stores and what `bootc upgrade` moves. **Deployed size** is the final
125 131 filesystem: what the disk holds and what the ISO squashes. A file written in one layer
@@ -133,7 +139,7 @@
133 139 |------|-------|---------------------|
134 140 | 1.44 GB | sway, `xdg-desktop-portal{,-gtk,-wlr}`, the session | The product. This is the desktop. |
135 141 | 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`. |
136 - | 674 MB | `helium-browser-bin` | The product, and the one stack pick Alloy declines to make. |
142 + | 674 MB | `helium-browser-bin` | The product. Superseded: this is Helium, removed 2026-08-18, and Firefox's layer is unmeasured. |
137 143 | 663 MB | Base package list | The stack, per [STACK.md](STACK.md). |
138 144 | 170 MB | initramfs + `rpmostree-unpackaged-content` | Bootable image. |
139 145 | 147 MB | cups + cups-filters | Printing, client profile only; the server profile presets it off. |
M docs/STACK.md +31 -17
@@ -188,36 +188,50 @@
188 188
189 189 ## Browser
190 190
191 - **Helium baked into the image as the default, unconfigured.** Helium is ungoogled-chromium with the behavior Alloy used to hand-build on top of Firefox already set upstream: ads, trackers, cookie banners and third-party cookies blocked by default, fingerprinting tampered with, no analytics, and no network request at all on first launch. Alloy ships no policy file, no preference seed, no chrome CSS and no extension pin for it. Shipping nothing is the reason it is the default, not an omission next to it. The packaging rules this follows are in the wiki note `alloy-packaging-policy`; the short form is that an app earns a recommendation by not needing configuration, which is the same test that picked helix over a configured vim.
191 + **Firefox baked into the image as the default, with one file of configuration.** Ruled 2026-08-18. Firefox is a pick Alloy defends, not a shrug: it is the only browser reachable on an image-based system whose defaults Alloy can stand behind, and standing behind them costs `/etc/firefox/pref/alloy.js` and nothing else. No policy file, no autoconfig, no `profiles.ini`, no `userChrome.css`. The wiki note is `alloy-byo-principle`.
192 192
193 - The package is `helium-browser-bin` from Terra, the repo Alloy already enables for satty, currently 0.14.9.1. Helium's own code is GPL-3.0; what it imports from ungoogled-chromium keeps its BSD-3-Clause terms. Nothing in Alloy links either, so this sits on the same aggregation footing as Distrobox, greetd and tuigreet, which are GPL-3.0-only in the image already.
193 + Reachability is the load-bearing word. Firefox comes from Fedora's own repos, and those stay enabled after install, so a machine that chose `BROWSER=none` is one `rpm-ostree install firefox` from the browser the image would have carried. That property is now a selection criterion for anything Alloy recommends: **a candidate needs support for image-based systems, stated or inherent.**
194 194
195 - **What Alloy used to ship here, and no longer does.** Five files went with this change: an enterprise policy pinning uBlock Origin and disabling telemetry, Pocket, studies, sponsored tiles and password saving; a `mozilla.cfg` of around fifty default prefs; the autoconfig pointer that loaded it; a `profiles.ini` naming a fixed-path profile; and roughly two hundred lines of `userChrome.css`. That CSS described its own purpose in its header as "Helium-style ultra-compact horizontal". It was emulating a browser that is packaged, so the emulation had no reason to exist. The fifty prefs were not only cosmetic, and dropping them on a browser with bad defaults would be indefensible. It was affordable while Helium was the only recommendation, because Helium already behaves that way.
195 + ### What the configuration does, and what it may never do
196 196
197 - **Ruled 2026-08-17: the two recommended browsers are stock Firefox and Helium**, and stock Firefox does not behave that way. It arrives with telemetry on, sponsored shortcuts on the new tab page, Pocket integration, and Enhanced Tracking Protection at "standard" rather than "strict", which is most of what the deleted `mozilla.cfg` was setting. Alloy still ships it no configuration, and the estate does not come back.
197 + Three rungs, in order, and a lower one needs an argument for why the one above it did not work:
198 198
199 - What changes is that honest description stops being a courtesy and becomes the obligation. Whatever offers the choice states what each browser does out of the box, so a user picking Gecko knows what they are picking rather than discovering it.
199 + 1. **Restore a control the vendor hid.** `browser.compactmode.show` ships false, which hides the density control in Customize. Alloy sets it true and stops there. The user is one visible click from compact and Alloy has not said which density is right.
200 + 2. **Remove an anti-feature.** The test is who the default serves. Telemetry serves the vendor, sponsored placements serve advertisers, tracking protection at "standard" serves the ad industry, and the AI block (`browser.ml.*`, `browser.tabs.groups.smart.*`) ships on, downloads models, and was never opted into.
201 + 3. **Set a value to Alloy's preference.** Forbidden here. `browser.uidensity` is the named example and the Containerfile asserts it is absent.
200 202
201 - **And it is offered a fix rather than left with the defaults.** Ruled the same day, as the pattern for every bring-your-own role and not a browser exception: where a candidate has bad defaults, offer both a modified configuration and stock. Bad meaning bad, not aesthetic, and the test is who the default serves. Telemetry serves the vendor, sponsored shortcuts serve advertisers, Pocket serves a content business, tracking protection at standard serves the ad industry. Tab layout and density serve the user fine and Alloy has no business touching them on a browser it declined to pick.
203 + Every line is a `pref`, never a `lockPref`: these are starting positions the user can change in Settings. A locked pref would be Alloy hiding a control while objecting to hidden controls, which is the argument in wiki `irreducible-complexity-principle`.
202 204
203 - So the estate does not come back, and this is the line that keeps that true: **the modified configuration only removes anti-features and never adds Alloy's taste.** Two hundred lines of `userChrome.css` were the other half of what was deleted and they stay deleted. A diff with a single line about appearance in it has gone too far. The configuration also enumerates what it changes before it runs, because an opaque "Alloy configuration" is a black box the user cannot judge, which would defeat the describing it exists to improve on.
205 + ### What Alloy used to ship here, and still does not
204 206
205 - This does not weaken the rule that an app earns a recommendation by not needing configuration. **That rule is a test for what Alloy picks as a default, not a bar on what it will offer as a bring-your-own candidate.** The test for a candidate is provenance and an accurate account of its behaviour. Otherwise the rule would force Alloy to either configure a browser it declined to pick or refuse to name the second engine at all, and both defeat the point of declining.
207 + Five files went in the 2026-07-30 change: an enterprise policy pinning uBlock Origin; a `mozilla.cfg` of around fifty prefs; the autoconfig pointer that loaded it; a `profiles.ini` naming a fixed-path profile; and roughly two hundred lines of `userChrome.css`. **The estate does not come back.** What returns is the substantive fraction of those fifty prefs, the ones answering to the who-does-this-serve test, in one enumerated file. The `userChrome.css` half stays dead: a diff to the pref file with a single line about appearance in it has gone too far.
206 208
207 - **This is a non-endorsement, not a pick.** Alloy is not claiming Blink is the better engine, and the monoculture argument that previously rejected Chromium as the default is recorded as overridden rather than withdrawn. Reinforcing one engine's dominance is a real cost and Alloy is paying it. What it lost to is the theming rule: the previous answer was "ship Gecko and fix it", and Alloy no longer fixes browsers. **Firefox remains one line in the Containerfile** for anyone who wants Gecko, which is what a builder-not-artifact distribution means by a choice, and it would ship with no Alloy configuration on it either.
209 + One correction to the older account, measured on Firefox 153: **Pocket is gone from Firefox entirely.** Mozilla shut the service down, and there is no `extensions.pocket` pref left to set. It was on the list from the 2026-07-30 estate and would now be a dead line.
208 210
209 - **What still reaches the browser** is system appearance, through the portal and the toolkit rather than through per-app files: `org.freedesktop.appearance` `color-scheme` for light and dark, the GTK theme for system dialogs, the cursor theme, and the font stack including the Noto coverage packages. That is the whole of it. Anything past what a well-behaved app reads from the system is now the user's to set in the browser's own settings.
211 + ### Helium, and why it is not here
212 +
213 + Helium is ungoogled-chromium with the behaviour Alloy used to hand-build already set upstream, packaged by Terra as `helium-browser-bin`. It was the default from 2026-07-30 and one of two recommendations from 2026-08-17. It is now gone from the image and from the builder, for two reasons that arrived a day apart.
214 +
215 + **It was unreachable.** Terra is disabled after install, because leaving it enabled breaks bootc-image-builder's installer depsolve, and `rpm-ostree install --enablerepo` works only in a container build. So a machine that chose no browser could not add Helium by any supported route, which broke the floor the whole bring-your-own arrangement rested on.
216 +
217 + **And it is someone else's workaround.** Helium is a maintained patchset over another vendor's bad defaults. Shipping it as Alloy's answer adopts that mitigation as Alloy's position, and the reasoning that ended it is worth keeping in one line: Helium is doing its best to make a standard approach tolerable, and Alloy is doing its best to improve standard approaches. Anyone who wants it can enable Terra on a machine they own and layer it. That is their call, and not a shape this image carries.
218 +
219 + ### This is a pick, and it is still not an engine claim
220 +
221 + Alloy does not claim Gecko beats Blink. The monoculture argument that once rejected Chromium as a default is recorded as overridden rather than withdrawn, and the engine question is exactly as unarguable as it was on 2026-07-30. What changed is that the question stopped being the deciding one. Applying the reachability criterion left Fedora's stock `chromium` as the only Blink candidate, which is the un-mitigated defaults Helium exists to fix, so there was no second candidate to defend. A role with one defensible candidate gets a pick.
222 +
223 + **What still reaches the browser** is system appearance, through the portal and the toolkit rather than through per-app files: `org.freedesktop.appearance` `color-scheme` for light and dark, the GTK theme for system dialogs, the cursor theme, and the font stack including the Noto coverage packages. That is the whole of it. Anything past what a well-behaved app reads from the system is the user's to set in the browser's own settings.
210 224
211 225 Accepted costs:
212 - - **The day/night switch reaching the browser is unverified.** The deleted `mozilla.cfg` set `ui.systemUsesDarkTheme = 0`, so Alloy was deliberately fighting the portal and the portal path has never been observed working here. If it turns out not to drive Helium, that is recorded as a defect in the appearance propagation and fixed there. Per-app chrome CSS does not come back for it.
213 - - **No blocker is pinned.** Helium's blocking is a browser feature rather than a forced extension, which also means a user who disables it has disabled it, with nothing in the image arguing.
214 - - **`-bin` provenance, and a pre-1.0 version.** Terra repackages an upstream binary rather than building from source, and this is the most attack-exposed program in the image. Helium is also at 0.14.x, which is young for the piece of the system that faces the whole internet. It is still better provenance than the alternatives: Helium has no official Flatpak and upstream refuses to make one, holding that Chromium cannot be packaged as a Flatpak without breaking its internal sandbox, so every Flatpak of it is an unofficial community repackaging.
215 - - **Updates come with the image.** A browser inside the image moves when the image is rebuilt rather than on its own channel. `alloy update` shows what is booted, what is staged, and how to roll back; reporting how far behind the image's packages have fallen, with the browser called out rather than buried in a bulk count, is filed work and not yet shipped.
226 + - **The day/night switch reaching the browser is unverified.** The deleted `mozilla.cfg` set `ui.systemUsesDarkTheme = 0`, so Alloy was deliberately fighting the portal, and the portal path has never been observed working here. If it turns out not to drive Firefox, that is a defect in the appearance propagation and is fixed there. Per-app chrome CSS does not come back for it.
227 + - **No blocker is pinned.** Strict tracking protection is Firefox's own feature rather than a forced extension, so a user who turns it down has turned it down, with nothing in the image arguing.
228 + - **A pref file is a maintenance surface.** Mozilla renames prefs, and a pref that no longer exists fails silently. The file records that its names were read out of Firefox 153.0's `omni.ja` on 2026-08-18 rather than remembered, and re-measuring is part of taking a Fedora major.
229 + - **Updates come with the image.** A browser inside the image moves when the image is rebuilt rather than on its own channel. `alloy update` shows what is booted, what is staged, and how to roll back.
216 230
217 231 Rejected:
218 - - **Firefox as the default.** Was the previous pick, and upstream Gecko remains the right Gecko. It loses on rule 3 alone: keeping it as the default meant keeping the five files, and every one of them was Alloy configuring an application.
219 - - **Ungoogled-chromium.** Helium is built on it, so these were never two options, and it is packaged for neither Fedora nor Terra. Compiling it in the image costs hours of build time and roughly 100 GB of scratch space for a subset of what the prebuilt package already provides.
220 - - **Helium as a Flatpak.** See the provenance note above.
232 + - **Helium.** See above. Unreachable after install, and a patchset over another project's defaults.
233 + - **Ungoogled-chromium.** Packaged for neither Fedora nor Terra. Compiling it in the image costs hours of build time and roughly 100 GB of scratch space.
234 + - **Stock `chromium` from Fedora.** Reachable, and it is the un-mitigated defaults. Recommending it unconfigured endorses defaults Alloy rejects; configuring it rebuilds the estate on a browser Alloy has no other reason to carry.
221 235 - **LibreWolf.** Ships uBlock Origin preinstalled, but its hardening (`resistFingerprinting`, letterboxing, cookie clears on close, WebGL off) breaks modern sites. Un-hardening it to pass the "runs all modern sites" bar erases the reason to choose it.
222 236 - **Zen Browser.** Gecko-based and design-forward, but its identity centers vertical tabs and sidebar-forward layout. Revisit only if Zen ships a first-class horizontal mode as a supported configuration.
223 237 - **Brave, Vivaldi, Arc.** Each arrives with a business model attached to the browser, which is the class of default Alloy is least willing to hand a user.
@@ -275,16 +275,16 @@
275 275 license = "MIT"
276 276 url = "https://github.com/Byron/dua-cli"
277 277
278 + [[section.project]]
279 + name = "Firefox"
280 + license = "MPL-2.0"
281 + url = "https://www.mozilla.org/firefox"
282 +
278 283 [[section.project]]
279 284 name = "Git"
280 285 license = "GPL-2.0-only"
281 286 url = "https://git-scm.com"
282 287
283 - [[section.project]]
284 - name = "Helium"
285 - license = "GPL-3.0-only AND BSD-3-Clause"
286 - url = "https://helium.computer"
287 -
288 288 [[section.project]]
289 289 name = "jq"
290 290 license = "MIT"
@@ -91,20 +91,31 @@
91 91
92 92 ## The browser
93 93
94 - Helium ships in the image as the default browser, unconfigured. It is
95 - ungoogled-chromium with ad blocking, tracker blocking, cookie-banner handling
96 - and anti-fingerprinting already on upstream, and it makes no network request on
97 - first launch.
94 + Firefox ships in the image as the default browser. It comes from Fedora's own
95 + repositories, which matters more than it sounds: those stay enabled after you
96 + install, so a machine built with no browser at all is one `rpm-ostree install
97 + firefox` away from this one.
98 98
99 - Alloy ships no policy file, no preference seed, no chrome CSS and no pinned
100 - extension for it. That is why it is the default: an app earns a recommendation
101 - here by not needing to be configured. Anything past what a well-behaved app
102 - reads from the system (light or dark, the GTK theme for dialogs, the cursor,
103 - the fonts) is yours to set in the browser's own settings.
99 + Alloy changes exactly one file, `/etc/firefox/pref/alloy.js`, and it is worth
100 + knowing what is in it because the rule it follows is short. Alloy turns off
101 + things that serve somebody other than you: telemetry, sponsored placements on
102 + the new tab page and in the address bar, and the AI features, which arrive
103 + switched on and download models nobody asked for. It sets tracking protection
104 + to strict. It also turns on one thing, `browser.compactmode.show`, which puts
105 + the density control back in Customize where Mozilla hides it.
104 106
105 - Firefox is one line in the Containerfile if you want Gecko, and would ship with
106 - no Alloy configuration on it either.
107 + That last one is the pattern rather than an exception. Alloy would rather hand
108 + you back a control than pick the value for you, so it does not set the density
109 + itself: compact is one click away in Customize and the choice stays yours.
107 110
108 - Two honest notes. The browser is packaged from an upstream binary rather than
109 - built from source, and it is pre-1.0. And because it lives in the image, it
110 - updates when you rebuild the image and not before (chapter 8).
111 + Nothing in that file is locked. Every line is a starting position you can
112 + change in Settings, and if you disagree with all of it, changing all of it is
113 + supported. There is no policy file, no autoconfig, and no chrome CSS. Anything
114 + past what a well-behaved app reads from the system (light or dark, the GTK
115 + theme for dialogs, the cursor, the fonts) is yours to set in the browser's own
116 + settings.
117 +
118 + Two honest notes. Because the browser lives in the image, it updates when you
119 + rebuild the image and not before (chapter 8). And Mozilla renames preferences
120 + between releases, so a line in that file can quietly stop applying; the file
121 + records which Firefox version its names were read from.
@@ -33,12 +33,11 @@
33 33 #
34 34 # It also fixes a smaller thing that worked by luck. The vendor mimeapps.list
35 35 # ships from shared-mime-info and names `org.mozilla.firefox.desktop`, which
36 - # this image does not contain under any BROWSER value: Helium is
37 - # helium.desktop and Fedora's Firefox rpm is firefox.desktop, while that id
38 - # belongs to the Flatpak. It resolved correctly anyway, by falling through to
39 - # mimeinfo.cache where exactly one candidate declared the scheme. One browser
40 - # is not a thing to rely on when the point of the exercise is that the user
41 - # brings their own.
36 + # this image does not contain under any BROWSER value: Fedora's Firefox rpm is
37 + # firefox.desktop, while that id belongs to the Flatpak. It resolved correctly
38 + # anyway, by falling through to mimeinfo.cache where exactly one candidate
39 + # declared the scheme. One browser is not a thing to rely on: a `BROWSER=none`
40 + # image has none, and a user's own Flatpak is a second.
42 41
43 42 set -eu
44 43
@@ -125,7 +124,13 @@
125 124 # telling someone to run a command that is not there is the same silent
126 125 # failure this script was written to remove, one level up. When that verb
127 126 # ships it replaces the flatpak line here.
127 + #
128 + # The layering route is named because it works: Firefox is in Fedora's own
129 + # repos, and those stay enabled after install. That is the whole reason it is
130 + # Alloy's pick (wiki `alloy-byo-principle`), so a machine that chose `none` is
131 + # one command from the browser the image would have carried.
128 132 printf 'No browser is installed, so this link cannot be opened: %s\n' "$url" >&2
129 - say "Install one with: flatpak install fedora org.mozilla.firefox
130 - Or rebuild the image with BROWSER=helium to bake one in."
133 + say "Install one with: rpm-ostree install firefox
134 + Or, sandboxed: flatpak install fedora org.mozilla.firefox
135 + Or rebuild the image with BROWSER=firefox to bake one in."
131 136 exit 3
@@ -378,7 +378,7 @@
378 378 Ok(vec![
379 379 Stream {
380 380 index: 100,
381 - app: "Helium".into(),
381 + app: "firefox".into(),
382 382 direction: Direction::Output,
383 383 device_index: 1,
384 384 volume: 100,