Skip to main content

max / alloy

Record the two hotfix-channel rulings where the code makes the claim Both were settled by Max on 2026-08-17 and both were still described in the tree as open questions, which is the state a comment sits in for a year. The network repo ships opt-in. STACK.md's Hotfixes section says so, gives the one command that turns it on, and states the cost plainly: a hotfix reaches only machines whose owner went looking for the channel. The carried file:// repo gets signed, over the recommendation to leave it explicitly unsigned. So gpgcheck=0 in build/rpm/build.sh and the Containerfile is unfinished work rather than a position, and both comments now say what is left: the key and where it lives, then gpgcheck=1, the public key in the image, and a test that fails the build on a repo that does not verify.
Author: Max Johnson <me@maxj.phd> · 2026-08-18 21:16 UTC
Signed with PGP, not checked
Commit: 2c4221323597d9d708266adcf7ef12b48b5acbef
Parent: 6ba01f2
3 files changed, +41 insertions, -14 deletions
M Containerfile +18 -9
@@ -2941,16 +2941,25 @@
2941 2941 # booted system talks to rpm-ostreed over D-Bus, and its --enablerepo is
2942 2942 # "only supported in a container build" — so a repo shipped disabled is a
2943 2943 # repo the first-boot unit cannot turn on for its own transaction, and the
2944 - # machine comes up with no console. The standing "repo enabled by default
2945 - # or opt-in" question (GO d866e125) is about the future NETWORK repo, where
2946 - # the answer is a real choice; this one reaches no network and answers to
2947 - # nothing but the local filesystem.
2944 + # machine comes up with no console. The "enabled by default or opt-in"
2945 + # question was about the future NETWORK repo, where the answer was a real
2946 + # choice, and it is answered: opt-in, `enabled=0`, ruled 2026-08-17. This one
2947 + # reaches no network and answers to nothing but the local filesystem, so the
2948 + # ruling does not reach it.
2948 2949 #
2949 - # gpgcheck=0 is a marker for signing being unfinished (GO d866e125 subtask
2950 - # 4), not a decision. It is a weaker exposure than an unsigned network repo
2951 - # — these files came in with the image and are only as trustworthy as it is
2952 - # — but "the image is the trust boundary" is a claim signing should make
2953 - # explicitly rather than one this file should assume.
2950 + # gpgcheck=0 is unfinished work rather than a position, and the position is
2951 + # now decided: this repo gets signed too. Max ruled it 2026-08-17 over the
2952 + # recommendation, which was to leave the carried repo explicitly unsigned —
2953 + # these files came in with the image and are only as trustworthy as it is, so
2954 + # a signature defends nothing the image's own integrity does not already
2955 + # defend. What the ruling buys is one code path with the network repo, so
2956 + # there is no branch only that path exercises and the key exists before it is
2957 + # needed under pressure. "The image is the trust boundary" is a claim signing
2958 + # should make explicitly rather than one this file should assume.
2959 + #
2960 + # What is left: the key, then `gpgcheck=1` and a `gpgkey` here, the public key
2961 + # shipped in the image, and a test that fails the build if this repo does not
2962 + # verify. GoingsOn alloy d866e125.
2954 2963 RUN printf '[alloy-local]\nname=Alloy components, carried with the image\nbaseurl=file:///usr/share/alloy/rpm\nenabled=1\ngpgcheck=0\n' \
2955 2964 > /etc/yum.repos.d/alloy-local.repo
2956 2965
@@ -44,6 +44,12 @@
44 44
45 45 That repo is enabled, and it is forced rather than chosen: `rpm-ostree install` on a booted system supports `--enablerepo` only in a container build, so a repo shipped disabled is one the first-boot unit could not turn on for its own transaction. It reaches no network, which is why this does not touch the position below.
46 46
47 + **The network repo ships disabled, and that is chosen rather than forced.** The carried `file://` repo above had to be enabled; this one did not, and it is `enabled=0` on every installed machine. A fresh Alloy has no line pointing at makenot.work that its owner did not add. Turning it on is one command and needs no extra package:
48 +
49 + sudo sed -i 's/^enabled=0$/enabled=1/' /etc/yum.repos.d/alloy-hotfix.repo
50 +
51 + The cost is real and is not papered over here: **a hotfix reaches only machines whose owner went looking for the channel.** Every other machine stays on what it installed until its owner rebuilds. That is the trade the opt-in default makes, and anyone announcing a fix has to assume most machines will not take it on their own. Decided by Max 2026-08-17.
52 +
47 53 **Our layers are disposable.** A layered package whose version the base later overtakes wedges the machine: every subsequent update fails to depsolve, permanently, and that is the normal life of a hotfix rather than an edge case. The rule that avoids it is to carry no layer of ours across an upgrade. Drop them, upgrade, re-apply only if the base still lacks the fix. It costs one reboot rather than two, because the two transactions compose into one deployment, and it hands `bootc upgrade` back a deployment it will consent to work on.
48 54
49 55 On the path Alloy takes today the rule costs nothing to apply, because `bootc switch` applies it for free. `switch` is not `upgrade`: only `upgrade` consults local modifications and refuses, and a switch discards the layer outright rather than carrying it. The machine comes up once with no console, `alloy-layer-components.service` sees no `/usr/bin/alloy` and lays the new image's copies down, and it reboots a second time into them. So a component tracks the image it shipped with, and nothing has to own the drop. The second boot is a blank screen on a machine that was working when its owner typed the command, so it says what it is doing: `alloy-layer-notice` writes to `/dev/console`, which `quiet` does not silence, and `alloy update` says the same thing before the command is typed.
@@ -29,10 +29,22 @@
29 29 # fix. Measured, in build/layertest — read its README before changing where
30 30 # this package lands.
31 31 #
32 - # Unsigned so far. Signing is its own subtask and its own key story, and a repo
33 - # that can reach every installed machine has no business being unsigned for
34 - # long. `gpgcheck=0` in the generated .repo file is the marker for that being
35 - # unfinished, not a decision.
32 + # Unsigned so far, and the decision is in: **both repos get signed**, the
33 + # carried one included. Ruled by Max 2026-08-17 over the recommendation, which
34 + # was to leave the carried repo explicitly unsigned on the grounds that it
35 + # reaches no network and is only as trustworthy as the image around it. What
36 + # the ruling buys is one code path: the carried repo and the network repo are
37 + # built, configured and verified the same way, so no branch exists that only
38 + # the network path exercises, and the key and its handling exist before
39 + # anything is under pressure to ship.
40 + #
41 + # So `gpgcheck=0` in the generated .repo file is unfinished work, not a
42 + # position. What is left is the key: it is generated under the same rule as
43 + # the sops identities (never in the repo it signs, never synced by anything),
44 + # and where it lives is settled before it is generated. Then this script signs
45 + # what it builds, the public key ships in the image, the .repo snippets carry
46 + # `gpgcheck=1` and a `gpgkey`, and a test fails the build if the carried repo
47 + # does not verify. GoingsOn alloy d866e125.
36 48 set -euo pipefail
37 49
38 50 HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
@@ -156,4 +168,4 @@
156 168 say " name=Alloy components"
157 169 say " baseurl=https://<host>/rpm/"
158 170 say " enabled=1"
159 - say " gpgcheck=0 # until signing lands"
171 + say " gpgcheck=0 # until signing lands; both repos are to be signed"