Skip to main content

max / alloy

Point installed machines at the update stream Where the image is installed from and where updates come from are two different places, and omitting --target-imgref conflated them. bootc defaults that flag to the source, --target-transport separately defaults to registry, and the pair of defaults wrote ostree-unverified-registry:/run/initramfs/live/source/alloy:local into the deployment origin: a registry reference whose name is a filesystem path that exists only while the installer stick is plugged in. Measured on the one installed machine, where the path is absent as it must be, and skopeo answers `invalid reference format` for the equivalent reference. So bootc upgrade there does not fail with anything naming the real problem; it fails at parsing. :43 rather than :latest, per docs/IMAGE.md. A Fedora major bump is a deliberate act that Alloy tests and ships, so it must not arrive through the same channel as an ordinary update. The registry does not exist yet. Naming it anyway is the point: an unpublished reference degrades to "no update found", and the state it replaces degrades to "updates can never work". Two tests pin it, because the flag's absence is invisible at install time and total months later, and because a value that still looked like a path would reproduce the bug while satisfying a naive check.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-07-30 03:59 UTC
Signed with PGP, not checked
Commit: d2dfeca315a1d0d75c04f0fff494c84d2709203e
Parent: c5ebf41
1 file changed, +87 insertions, -1 deletion
@@ -904,6 +904,36 @@
904 904 /// working the day that stops being true.
905 905 const LIVE_SOURCE_TAG: &str = "local";
906 906
907 + /// What the installed machine fetches for every update after this one.
908 + ///
909 + /// **Where the image is installed *from* and where updates come *from* are two
910 + /// different places, and omitting this conflated them.** bootc defaults
911 + /// `--target-imgref` to the source, and `--target-transport` separately defaults
912 + /// to `registry`, so leaving it out did not produce a sane fallback: it paired
913 + /// the *source path* with the *registry transport* and wrote
914 + ///
915 + /// ```text
916 + /// ostree-unverified-registry:/run/initramfs/live/source/alloy:local
917 + /// ```
918 + ///
919 + /// into the deployment origin. A registry reference whose name is a filesystem
920 + /// path. Measured on the one installed machine, where that path is necessarily
921 + /// absent because it is the live medium's mount point, and `skopeo inspect` on
922 + /// the equivalent reference answers `invalid reference format`. So every machine
923 + /// installed before this constant existed cannot take an update, and fails at
924 + /// reference parsing rather than with anything that names the real problem.
925 + ///
926 + /// `:43` and not `:latest`, matching docs/IMAGE.md: a Fedora major bump is a
927 + /// deliberate act that Alloy tests and ships, so it must not arrive through the
928 + /// same channel as ordinary updates. `:latest` is for casual users who opt into
929 + /// it; a fresh install follows the version it was built against.
930 + ///
931 + /// The registry does not exist yet (GO task 1372b159 provisions it with cosign
932 + /// signing). Naming it anyway is deliberate: an unpublished reference degrades to
933 + /// "no update found", which is recoverable and legible, while the alternative
934 + /// degrades to "updates can never work".
935 + const UPDATE_IMAGE: &str = "quay.io/alloy/alloy:43";
936 +
907 937 /// The `--source-imgref` for this run, or `None` when bootc's own default
908 938 /// applies.
909 939 ///
@@ -970,6 +1000,12 @@
970 1000 if let Some(source) = live_source_image() {
971 1001 install = install.args(["--source-imgref", &source]);
972 1002 }
1003 + // Unconditional, unlike --source-imgref above. That one is detected
1004 + // because the source genuinely differs between the ISO and a dev
1005 + // container; where updates come from does not, and a machine
1006 + // installed during development wanting the same update stream as
1007 + // everyone else is correct rather than a side effect.
1008 + install = install.args(["--target-imgref", UPDATE_IMAGE]);
973 1009 install.arg(disk)
974 1010 }),
975 1011 // bootc returns when the install is done, not when the kernel and
@@ -2738,7 +2774,13 @@
2738 2774 let shown = view.plan_display();
2739 2775
2740 2776 assert_eq!(shown.len(), 4, "{shown:#?}");
2741 - assert_eq!(shown[0], "bootc install to-disk --wipe /dev/sda");
2777 + // The update stream is on the line the confirm screen shows, which is
2778 + // where a flag that decides how the machine behaves for its whole life
2779 + // belongs.
2780 + assert_eq!(
2781 + shown[0],
2782 + format!("bootc install to-disk --wipe --target-imgref {UPDATE_IMAGE} /dev/sda"),
2783 + );
2742 2784 assert_eq!(shown[1], "udevadm settle");
2743 2785 assert_eq!(shown[2], "mkdir -p /mnt/alloy-target");
2744 2786 assert!(shown[3].starts_with("lsblk"), "{}", shown[3]);
@@ -3441,6 +3483,50 @@
3441 3483 );
3442 3484 }
3443 3485
3486 + // The install names where updates come from, and this is the only place that
3487 + // can. Its absence is invisible at install time and total afterwards: bootc
3488 + // succeeds, the machine boots, and `bootc upgrade` fails months later on a
3489 + // reference nobody wrote deliberately. It was
3490 + // `ostree-unverified-registry:/run/initramfs/live/source/alloy:local` on the
3491 + // one machine installed without it, which is a registry reference naming a
3492 + // path that only exists while the installer stick is plugged in.
3493 + #[test]
3494 + fn the_plan_names_where_updates_come_from() {
3495 + let plan = install_plan("/dev/sda", "host", "user", "pw", false);
3496 + let install = plan
3497 + .iter()
3498 + .map(Stage::display)
3499 + .find(|line| line.contains("install to-disk"))
3500 + .expect("the plan installs");
3501 +
3502 + assert!(
3503 + install.contains("--target-imgref"),
3504 + "without this the origin records the install medium: {install}",
3505 + );
3506 + assert!(install.contains(UPDATE_IMAGE), "{install}");
3507 + }
3508 +
3509 + // A registry reference, not a path and not a transport prefix. The bug this
3510 + // guards against was bootc pairing a source *path* with the default
3511 + // *registry* transport, so a value that still looks like a path would
3512 + // reproduce it exactly while passing the test above.
3513 + #[test]
3514 + fn the_update_image_is_a_registry_reference() {
3515 + assert!(
3516 + !UPDATE_IMAGE.starts_with('/'),
3517 + "a path is what the bug wrote: {UPDATE_IMAGE}",
3518 + );
3519 + assert!(
3520 + !UPDATE_IMAGE.contains(':') || UPDATE_IMAGE.split(':').count() == 2,
3521 + "one colon, separating image from tag: {UPDATE_IMAGE}",
3522 + );
3523 + let (image, tag) = UPDATE_IMAGE.split_once(':').expect("a tag is pinned");
3524 + assert!(image.contains('.'), "a registry host: {image}");
3525 + // Not `latest`: docs/IMAGE.md makes a Fedora major bump a deliberate act,
3526 + // and `latest` would deliver one through the ordinary update channel.
3527 + assert_ne!(tag, "latest", "a fresh install follows its own version");
3528 + }
3529 +
3444 3530 // Missing answers cannot happen from the summary — every step gates on its
3445 3531 // own validation — but a partial plan would render a command with a hole in
3446 3532 // it, so it returns nothing instead.