Skip to main content

max / alloy

Report the image version beside the console's in alloy --version Max chose (a) on GO alloy f56d3ca7: one command reports both, so a bug report quoting alloy --version distinguishes a layered machine from a rebuilt one without anyone knowing to ask a second question. The two numbers are allowed to disagree, and the hotfix channel is the mechanism that makes them. image::version reads VERSION_ID from /usr/lib/os-release, with the parse split out so it can be tested. It checks ID=alloy first, which is the load-bearing part: every Linux host ships an os-release, so without it a dev box reports its own distro's version as the image's, in exactly the place a support conversation trusts the answer. Off an Alloy machine the line is omitted rather than filled in with a placeholder. NOTICE becomes notice() plus notice_text(console, image), interned in a OnceLock for the &'static str clap wants. The derive attribute cannot hold it any more now that it reads a file, so long_version is set on the built command in main. The image line goes under the version, never above it. Containerfile greps '^alloy ' to prove it did not ship the stub main from the cache split, and build/rpm/build.sh reads that line's second field to check the packaged binary against the spec; both break silently if anything is inserted first. A test holds the invariant down. build.sh's 0.0.0 guard pointed at 8c5e2838, now closed. It states its own reason instead: an RPM channel ships a higher version, and nothing outranks 0.0.0. The image's own VERSION_ID is still 0.0, which this now makes visible on every machine. Giving it a real one belongs with the release cadence.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-08-15 03:51 UTC
Signed with PGP, not checked
Commit: 403a303c74f4f02ff438586ab0a90fba6c288623
Parent: 774e29a
3 files changed, +163 insertions, -17 deletions
@@ -75,7 +75,11 @@
75 75 if [ "$COMPONENT" = alloy ]; then
76 76 VERSION="$(grep -m1 '^version' "$REPO_ROOT/crates/alloy/Cargo.toml" | cut -d'"' -f2)"
77 77 [ -n "$VERSION" ] || die "no version in crates/alloy/Cargo.toml"
78 - [ "$VERSION" != "0.0.0" ] || die "the console is still at 0.0.0; see GO 8c5e2838"
78 + # 0.0.0 was the placeholder every console build carried until 2026-08-14.
79 + # Kept as a guard rather than deleted with the placeholder: an RPM channel
80 + # ships a HIGHER version of a component, and there is nothing for a hotfix to
81 + # be higher than if this regresses.
82 + [ "$VERSION" != "0.0.0" ] || die "the console is back at 0.0.0, so no hotfix can outrank it"
79 83 else
80 84 [ -x "$BINARY" ] || die "no shop binary at $BINARY"
81 85 VERSION="$("$BINARY" --version 2>/dev/null | head -1 | awk '{print $2}')"
@@ -103,6 +103,47 @@
103 103 /// The same record, in a checkout, where the builder writes it.
104 104 const RECORD_LOCAL: &str = "build/alloy-build.toml";
105 105
106 + /// Where the running system states which Alloy it is.
107 + ///
108 + /// `/usr/lib` and not `/etc`: the Containerfile ships it there and symlinks
109 + /// `/etc/os-release` at it, because bootc reads the deployment's copy when it
110 + /// writes a boot menu entry. Reading the source rather than the link.
111 + const OS_RELEASE: &str = "/usr/lib/os-release";
112 +
113 + /// The image's version, as `VERSION_ID` from os-release.
114 + ///
115 + /// Separate from the console's `CARGO_PKG_VERSION`, and the two diverge on
116 + /// purpose: the hotfix channel exists to put a newer console on an older
117 + /// image, so a support conversation that cannot see both cannot tell a layered
118 + /// machine from a rebuilt one. Max chose 2026-08-14 that `alloy --version`
119 + /// reports both.
120 + ///
121 + /// `None` off an Alloy machine, which is why `ID` is checked rather than
122 + /// assumed: every Linux host has an os-release, and reading a dev box's Fedora
123 + /// or Pop!_OS `VERSION_ID` would report a confident wrong answer. There is no
124 + /// image there to have a version, so the line is omitted rather than filled in
125 + /// with "unknown".
126 + pub(crate) fn version() -> Option<String> {
127 + version_from(&std::fs::read_to_string(OS_RELEASE).ok()?)
128 + }
129 +
130 + /// The parse, split from the read so it can be tested against the os-release
131 + /// this repo actually ships rather than against the host's.
132 + fn version_from(text: &str) -> Option<String> {
133 + // `strip_prefix` on the key and then on `=`, in that order, so a key that
134 + // is a prefix of another does not match it: `ID` against `ID_LIKE=fedora`
135 + // leaves `_LIKE=fedora`, which has no leading `=` and is skipped.
136 + let field = |key: &str| {
137 + text.lines()
138 + .find_map(|line| line.strip_prefix(key)?.strip_prefix('='))
139 + .map(|value| value.trim_matches('"').to_string())
140 + };
141 + if field("ID").as_deref() != Some("alloy") {
142 + return None;
143 + }
144 + field("VERSION_ID")
145 + }
146 +
106 147 /// Which machine is being built.
107 148 #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
108 149 pub(crate) enum Profile {
@@ -1718,4 +1759,30 @@
1718 1759 "the record must not read as one: it pins choices, not resolutions",
1719 1760 );
1720 1761 }
1762 +
1763 + /// Against the file the image actually ships, not a fixture, so an
1764 + /// os-release edit that drops or renames `VERSION_ID` fails here rather
1765 + /// than by silently removing the image line from `alloy --version`.
1766 + #[test]
1767 + fn the_shipped_os_release_states_an_image_version() {
1768 + let shipped = concat!(env!("CARGO_MANIFEST_DIR"), "/../../usr/lib/os-release");
1769 + let text = std::fs::read_to_string(shipped).expect("the repo ships usr/lib/os-release");
1770 + assert_eq!(version_from(&text).as_deref(), Some("0.0"));
1771 + }
1772 +
1773 + /// The reason `ID` is checked. Every Linux host has an os-release, so a
1774 + /// dev box would otherwise report its own distro's version as the image's.
1775 + #[test]
1776 + fn a_foreign_os_release_has_no_image_version() {
1777 + let fedora = "NAME=\"Fedora Linux\"\nID=fedora\nVERSION_ID=43\n";
1778 + assert_eq!(version_from(fedora), None);
1779 + }
1780 +
1781 + /// `ID` is a prefix of `ID_LIKE`, and matching the wrong one would read
1782 + /// every Fedora derivative as Alloy.
1783 + #[test]
1784 + fn id_like_is_not_mistaken_for_id() {
1785 + let derivative = "ID=notalloy\nID_LIKE=alloy\nVERSION_ID=9\n";
1786 + assert_eq!(version_from(derivative), None);
1787 + }
1721 1788 }
@@ -51,25 +51,54 @@
51 51 /// The version line itself stays first and unchanged. The image build asserts on
52 52 /// `alloy --version | grep -q '^alloy '` to prove it did not ship the stub main
53 53 /// from the Containerfile's cache split, and that assertion reads the first line.
54 + /// `build/rpm/build.sh` reads the same line's second field to check the packaged
55 + /// binary against the spec, so nothing may be inserted above it.
54 56 ///
55 57 /// Starts with the version and not the name: clap prints the binary name ahead
56 - /// of this string, so naming it here gets `alloy alloy 0.0.0`.
57 - const NOTICE: &str = concat!(
58 - env!("CARGO_PKG_VERSION"),
59 - "\n",
60 - "Copyright (c) 2026 Make Creative, LLC\n",
61 - "License MIT: <https://opensource.org/licenses/MIT>\n",
62 - "This is free software: you are free to change and redistribute it.\n",
63 - "There is NO WARRANTY, to the extent permitted by law."
64 - );
58 + /// of this string, so naming it here gets `alloy alloy 0.1.0`.
59 + ///
60 + /// # The image line
61 + ///
62 + /// Two versions exist and they are allowed to disagree. This crate's version is
63 + /// the console's; the image carries its own `VERSION_ID`, and the hotfix channel
64 + /// is precisely the mechanism that puts a newer console on an older image. Max
65 + /// chose 2026-08-14 that one command reports both, so a bug report quoting
66 + /// `alloy --version` distinguishes a layered machine from a rebuilt one without
67 + /// anyone having to know to ask a second question.
68 + ///
69 + /// Absent off an Alloy machine, where there is no image to have a version. See
70 + /// [`image::version`].
71 + ///
72 + /// Built at runtime rather than `concat!`ed, which is why this is a function:
73 + /// the image version is read from a file. Interned so the `&'static str` clap
74 + /// wants outlives the call.
75 + fn notice() -> &'static str {
76 + static NOTICE: std::sync::OnceLock<String> = std::sync::OnceLock::new();
77 + NOTICE.get_or_init(|| notice_text(env!("CARGO_PKG_VERSION"), image::version().as_deref()))
78 + }
79 +
80 + /// The assembly, split from the read so both shapes can be tested: the machine
81 + /// that has an image and the dev host that does not.
82 + fn notice_text(console: &str, image: Option<&str>) -> String {
83 + let mut notice = String::from(console);
84 + if let Some(image) = image {
85 + notice.push_str("\nimage ");
86 + notice.push_str(image);
87 + }
88 + notice.push_str(concat!(
89 + "\n",
90 + "Copyright (c) 2026 Make Creative, LLC\n",
91 + "License MIT: <https://opensource.org/licenses/MIT>\n",
92 + "This is free software: you are free to change and redistribute it.\n",
93 + "There is NO WARRANTY, to the extent permitted by law."
94 + ));
95 + notice
96 + }
65 97
66 98 #[derive(Parser)]
67 - #[command(
68 - name = "alloy",
69 - about = "Alloy Console",
70 - version,
71 - long_version = NOTICE
72 - )]
99 + // `long_version` is set on the built command in `main` instead of here: it
100 + // reads a file now, and the derive attribute takes a const.
101 + #[command(name = "alloy", about = "Alloy Console", version)]
73 102 struct Cli {
74 103 /// Theme id to render in (default: Akari, matched to the terminal background)
75 104 #[arg(long, global = true)]
@@ -268,7 +297,8 @@
268 297 // Hidden from `--help`, still resolved. clap would accept the verb either
269 298 // way, and a command that silently works while being undocumented is how
270 299 // documentation becomes wrong; the match arms below say why instead.
271 - let command = profile::hide_unavailable(<Cli as clap::CommandFactory>::command(), profile);
300 + let command = profile::hide_unavailable(<Cli as clap::CommandFactory>::command(), profile)
301 + .long_version(notice());
272 302 let cli = Cli::from_arg_matches(&command.get_matches())?;
273 303
274 304 // Refused as well as hidden, and through the same table, so the two
@@ -462,3 +492,48 @@
462 492 Command::Status { .. } => Ok(()),
463 493 }
464 494 }
495 +
496 + #[cfg(test)]
497 + mod tests {
498 + use super::*;
499 +
500 + /// The invariant two build steps depend on and neither can state here:
501 + /// `Containerfile` asserts `alloy --version | grep -q '^alloy '` to prove
502 + /// it did not ship the stub main, and `build/rpm/build.sh` reads the first
503 + /// line's second field to check the packaged binary against the spec. Both
504 + /// break silently if anything is inserted above the version.
505 + #[test]
506 + fn the_version_stays_alone_on_the_first_line() {
507 + let notice = notice_text("0.1.0", Some("0.0"));
508 + assert_eq!(notice.lines().next(), Some("0.1.0"));
509 + }
510 +
511 + /// A bug report quoting `alloy --version` has to distinguish a layered
512 + /// machine from a rebuilt one, which is the whole reason both are printed.
513 + #[test]
514 + fn a_machine_with_an_image_reports_both_versions() {
515 + let notice = notice_text("0.1.0", Some("0.0"));
516 + let mut lines = notice.lines();
517 + assert_eq!(lines.next(), Some("0.1.0"));
518 + assert_eq!(lines.next(), Some("image 0.0"));
519 + }
520 +
521 + /// A dev host has no image, so the line is omitted rather than filled in
522 + /// with a placeholder that would read as a real answer.
523 + #[test]
524 + fn a_dev_host_reports_the_console_and_says_nothing_about_an_image() {
525 + let notice = notice_text("0.1.0", None);
526 + assert_eq!(notice.lines().next(), Some("0.1.0"));
527 + assert!(!notice.contains("image"), "{notice}");
528 + }
529 +
530 + /// The notice MIT asks to travel with the binary, in both shapes.
531 + #[test]
532 + fn the_copyright_notice_survives_either_shape() {
533 + for image in [Some("0.0"), None] {
534 + let notice = notice_text("0.1.0", image);
535 + assert!(notice.contains("Make Creative, LLC"), "{notice}");
536 + assert!(notice.contains("License MIT"), "{notice}");
537 + }
538 + }
539 + }