max / alloy
4 files changed,
+62 insertions,
-3 deletions
| @@ -3653,6 +3653,38 @@ | |||
| 3653 | 3653 | test "$(cat /usr/lib/alloy/profile)" = "$PROFILE" \ | |
| 3654 | 3654 | || { echo "the profile marker did not land" >&2; exit 1; } | |
| 3655 | 3655 | ||
| 3656 | + | # The profile, recorded where every os-release reader can see it. | |
| 3657 | + | # | |
| 3658 | + | # The committed file carries `<profile>` on both fields, the same | |
| 3659 | + | # placeholder idiom as `build <n>`: a value that never got rewritten reads | |
| 3660 | + | # as obviously broken rather than as a plausible wrong answer. What stood | |
| 3661 | + | # there before was `VARIANT_ID=base`, which every image of both profiles | |
| 3662 | + | # shipped, so anything keying off it (a dotfile, a support question, a | |
| 3663 | + | # `systemd-analyze` dump) was told nothing. | |
| 3664 | + | # | |
| 3665 | + | # Downstream of the PROFILE `case` near the top, so the value is already | |
| 3666 | + | # known to be one of the two words and the display half can be written by | |
| 3667 | + | # hand rather than derived. | |
| 3668 | + | # | |
| 3669 | + | # The profile alone, never the tag variants. `firewall-server` and | |
| 3670 | + | # `usbgate-server` are image tags chosen by whoever runs the build; the | |
| 3671 | + | # Containerfile is never told which one it is making, so there is nothing | |
| 3672 | + | # here to stamp them from. | |
| 3673 | + | RUN set -eu; \ | |
| 3674 | + | case "$PROFILE" in \ | |
| 3675 | + | client) variant="Client" ;; \ | |
| 3676 | + | server) variant="Server" ;; \ | |
| 3677 | + | esac; \ | |
| 3678 | + | sed -i "s/^VARIANT=.*/VARIANT=\"$variant\"/; \ | |
| 3679 | + | s/^VARIANT_ID=.*/VARIANT_ID=$PROFILE/" /usr/lib/os-release; \ | |
| 3680 | + | grep -q "^VARIANT_ID=$PROFILE$" /usr/lib/os-release \ | |
| 3681 | + | || { echo "the profile did not land in os-release" >&2; exit 1; }; \ | |
| 3682 | + | grep -q "^VARIANT=\"$variant\"$" /usr/lib/os-release \ | |
| 3683 | + | || { echo "the profile display name did not land in os-release" >&2; exit 1; }; \ | |
| 3684 | + | if grep -q '<profile>' /usr/lib/os-release; then \ | |
| 3685 | + | echo "the profile placeholder is still in os-release" >&2; exit 1; \ | |
| 3686 | + | fi | |
| 3687 | + | ||
| 3656 | 3688 | RUN systemctl preset-all | |
| 3657 | 3689 | ||
| 3658 | 3690 | # ===================================================================== |
| @@ -103,7 +103,7 @@ | |||
| 103 | 103 | 5. **Config tree:** the tree at `etc/skel/.config/*` (new-user defaults), `etc/*` (system-wide), and `usr/*`, including `usr/share/polkit-1/rules.d/*` (which system settings the console may change without a prompt) in the repo maps 1:1 into the image. The polkit rule is asserted at build time against the actions the image actually defines, since a grant naming a renamed action is inert and silent about it. | |
| 104 | 104 | 6. **Rendered tree:** everything in the image that carries a color is not in the repo as a finished file. `templates/` holds it with the palette left as tokens, and `skelgen` renders it against the two Akari themes into a second tree that mirrors `/` the same way the config tree does. Themed skeleton files render twice: the light one lands at `etc/skel/<rel>`, the dark one at `usr/share/alloy/skel-night/<rel>`, and `alloy theme apply` copies whichever the user's mode file names into `$HOME` at login. The build asserts the two trees are a bijection and that they do not overlap the repo's own `etc/skel`, because a themed file that quietly loses its dark render leaves a light sway border on a dark desktop and nothing else. | |
| 105 | 105 | 7. **Systemd presets:** which services are enabled by default (syncthing off by default, gammastep off until enrolled, alloy-hinged conditionally on FW12, etc.). | |
| 106 | - | 8. **Branding:** os-release, plymouth splash. The build stamps the image's build number into os-release here; see [Version fields](#version-fields). | |
| 106 | + | 8. **Branding:** os-release, plymouth splash. The build stamps the image's build number into os-release here; see [Version fields](#version-fields). It stamps the profile in the same way, rewriting the committed `VARIANT="<profile>"` and `VARIANT_ID=<profile>` placeholders to `Client`/`client` or `Server`/`server`, and failing if either placeholder survives. Only the profile: the tag variants (`firewall-server`, `usbgate-server`) are names chosen by whoever runs the build, and the Containerfile is never told which one it is making. | |
| 107 | 107 | 9. **Validation:** `bootc container lint` runs at build. | |
| 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. |
| @@ -5,8 +5,8 @@ | |||
| 5 | 5 | ALLOY_BASE="43" | |
| 6 | 6 | ID=alloy | |
| 7 | 7 | ID_LIKE=fedora | |
| 8 | - | VARIANT="Base" | |
| 9 | - | VARIANT_ID=base | |
| 8 | + | VARIANT="<profile>" | |
| 9 | + | VARIANT_ID=<profile> | |
| 10 | 10 | ANSI_COLOR="0;38;2;138;69;48" | |
| 11 | 11 | LOGO=alloy | |
| 12 | 12 | DEFAULT_HOSTNAME=alloy |
| @@ -217,6 +217,33 @@ | |||
| 217 | 217 | // there would be a plausible-looking number on any machine where the stamping | |
| 218 | 218 | // stopped working, which is worse than an absent field: `alloy --version` drops | |
| 219 | 219 | // the build clause and says so by saying less. | |
| 220 | + | // `VARIANT`/`VARIANT_ID` are the same shape of rewrite and failed the same | |
| 221 | + | // way for longer: the committed file said `base` on both, no build step ever | |
| 222 | + | // touched them, and every image of both profiles shipped claiming to be a | |
| 223 | + | // variant that does not exist. A placeholder makes the unrewritten state | |
| 224 | + | // visible, and this pairs the two halves so an edit that drops either one | |
| 225 | + | // fails here rather than in an image nobody inspects. | |
| 226 | + | #[test] | |
| 227 | + | fn the_committed_os_release_carries_the_profile_placeholder() { | |
| 228 | + | let os_release = read("usr/lib/os-release"); | |
| 229 | + | for line in ["VARIANT=\"<profile>\"", "VARIANT_ID=<profile>"] { | |
| 230 | + | assert!( | |
| 231 | + | os_release.lines().any(|candidate| candidate == line), | |
| 232 | + | "os-release has no `{line}` for the profile step to rewrite", | |
| 233 | + | ); | |
| 234 | + | } | |
| 235 | + | ||
| 236 | + | // The other half: a placeholder with nothing rewriting it is worse than | |
| 237 | + | // the wrong answer it replaced. | |
| 238 | + | let containerfile = read("Containerfile"); | |
| 239 | + | for field in ["VARIANT", "VARIANT_ID"] { | |
| 240 | + | assert!( | |
| 241 | + | containerfile.contains(&format!("s/^{field}=.*/")), | |
| 242 | + | "nothing in the Containerfile rewrites {field}, so the placeholder would ship", | |
| 243 | + | ); | |
| 244 | + | } | |
| 245 | + | } | |
| 246 | + | ||
| 220 | 247 | #[test] | |
| 221 | 248 | fn the_committed_os_release_carries_what_the_build_stamp_rewrites() { | |
| 222 | 249 | let os_release = read("usr/lib/os-release"); |