fix(ujust): use bootc status as source of truth for live image tag - #942
fix(ujust): use bootc status as source of truth for live image tag#942castrojo wants to merge 2 commits into
Conversation
image-info.json is baked at build time and describes the image the filesystem was built as, not what is booted. After a rebase it goes stale, so ujust reported a different stream than bootc status. Add /usr/libexec/ublue-image-resolve, which reads .status.booted.image.image.image from `bootc status --json` and falls back to image-info.json when bootc is absent, exits non-zero, emits unusable JSON, or reports no booted image. Ref parsing splits on the final path component so registry ports and @sha256 digests are handled. ublue-image-info.sh, changelog.just, and toggle-testing now consume the resolver instead of parsing image-info.json directly. The ublue-image-info.sh output contract (name:tag + lock glyph) is unchanged. Closes #820 Assisted-by: Claude Opus 5 via GitHub Copilot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…tatus # Conflicts: # docs/TESTING.md
hanthor
left a comment
There was a problem hiding this comment.
Approving — the diagnosis is right, the centralization is the correct shape, and I traced the ref-parsing logic case by case rather than trusting the description. Three notes, one of which I'd like confirmed before this lands.
Parsing verified. I walked ref_path / ref_basename / strip_transport through the awkward inputs:
| input | result |
|---|---|
registry:5000/foo/bar:tag |
path registry:5000/foo/bar, tag tag ✅ |
registry:5000/foo/bar (no tag) |
path unchanged, port intact ✅ |
repo:tag@sha256:… |
digest stripped before split ✅ |
bluefin:latest (no slash) |
path bluefin, tag latest ✅ |
ostree-image-signed:docker://ghcr.io/… |
##*:// handles it ✅ |
ostree-unverified-registry:ghcr.io/foo |
transport regex strips it ✅ |
The [^0-9] guard in strip_transport is what keeps registry:5000/… from being eaten — that's a subtle case and it's handled correctly. Agreed this is a real improvement on the old s|^[a-z-]+:||.
1. Please confirm bootc status --json works unprivileged — the whole fix rests on it.
The PR body says it "requires root". If that's true on a real host, the headline benefit doesn't reach the most visible consumer: fastfetch.jsonc:23 calls /usr/bin/ublue-image-info.sh in the user's shell, so bootc status would fail there, silently fall through to the baked file, and post-rebase fastfetch would keep reporting the stale stream — exactly the bug being fixed.
For what it's worth, bootc-status.8 documents no root requirement and explicitly recommends bootc status --json as the way to programmatically detect a bootc deployment, which suggests it is unprivileged-safe. But since you verified the schema from source specifically because you couldn't run it, this is the one thing I can't confirm from here. A single bootc status --json as a normal user on a Bluefin box settles it.
2. Two resolver invocations means two bootc status --json per shell.
IMAGE_NAME="$("${IMAGE_RESOLVE}" image-name ...)"
IMAGE_TAG="$("${IMAGE_RESOLVE}" image-tag ...)"Each call independently runs bootc status --json and pipes through jq. Since fastfetch runs this on every new terminal, that's 2× bootc + 2× jq per shell on top of the existing rpm-ostree status --booted. Worth collapsing — either a combined mode (ublue-image-resolve image-name-tag emitting name:tag), or have the resolver cache the JSON in an env var the caller can pass through. Not blocking, but it's cheap to fix now and annoying to notice later as "terminals got slower".
3. Minor: half-resolved output.
if [[ -n "${IMAGE_NAME}" || -n "${IMAGE_TAG}" ]]; then
echo -n "${IMAGE_NAME}:${IMAGE_TAG}"|| means one empty side still prints, yielding bluefin: or :stable. Reachable with a digest-pinned bootc ref on a host with no baked file (tag unresolvable, name fine). && — or emitting just the non-empty part — would be tidier.
Good call flagging bonedigger-report as the same staleness class rather than scope-creeping into it, and the test_changelog.bats bctl drive-by is a real fix (brew ships one, so that filter was wrong for anyone with brew — which is everyone on Bluefin).
Housekeeping: currently DIRTY. Your branch-state note anticipates the docs/TESTING.md collision with #941, so that's understood — just needs the resolve.
Problem
/usr/share/ublue-os/image-info.jsonis baked at build time and describes the image the filesystem was built as. After a rebase it no longer matches what is booted, so every consumer reports the build-time stream instead of the live one.Confirmed on this host: the baked file reads
{"image-name":"dakota-gaming","image-tag":"latest",...}and itsimage-refcarries no tag at all.Changes
New
system_files/shared/usr/libexec/ublue-image-resolve <image-name|image-tag|image-path>— bootc-first, baked-file fallback. Consumers updated:ublue-image-info.sh,changelog.just,system.just(toggle-testing).Centralizing avoids three divergent copies of bootc-JSON parsing.
Schema verification
bootc status --jsonrequires root, so the schema was verified from bootc source (crates/lib/src/spec.rs, v1.16.6):HostStatus.booted→BootEntry.image→ImageStatus.image→ImageReference.image. The jq path is.status.booted.image.image.image.Note: the existing
bonedigger-reportuses.status.booted.imageDigest, a different field — not a usable precedent for the ref.Ref parsing
Splits on the final path component, so both
registry:5000/foo/bar:tagandrepo:tag@sha256:...parse correctly. This also fixes a latent bug in the oldsedtransport-strip (s|^[a-z-]+:||would have eatenregistry:).Output contract — unchanged
ublue-image-info.shstill emitsname:tagplus the lock glyph; verified live (dakota-gaming:latest 🔓). Consumers checked:fastfetch.jsonc,tests/test_ublue_image_info.bats,docs/TESTING.md,docs/contributing/style-guide.md. Only stderr noise on a missing file/jq changed.Tests
tests/test_ublue_image_resolve.bats— 23 tests: live-beats-stale, tag+digest, digest-only, ported registry, bootc absent / non-zero / empty / invalid JSON /booted:null, transport stripping, jq absent, bad field. Plus an integration test intest_ublue_image_info.bats. 43/43 pass across affected files. Wired intoJustfile,unit-tests.yml, anddocs/TESTING.md.Reviewer notes
image-pathinstead ofimage-ref. bootc refs include a tag; the bakedimage-refdoes not. Feeding a bootc ref through the old sed would produce…/bluefin-lts:stable:testinginbootc switch.image-pathreturns a tagless registry path and centralizes that.test_changelog.batsfiltered only~/.local/binfor a hostbctl, but brew ships one too — all 15 tests were failing onmainlocally. Now filters any PATH dir providingbctl.tests/test_update_just.bats(fail identically onmain).bonedigger-reportreads the same baked file and has the same staleness class, affecting bug-repo routing.Blast radius
system_files/shared/→ bluefin + bluefin-lts + dakota.Closes #820
Branch state
Merged
mainatd5b56d85after #926 landed. The only collision was aone-line conflict in the
docs/TESTING.mdtest table — both PRs append a row.Resolved by keeping both rows.
Re-verified after that merge:
test_ublue_image_resolve.bats23/23 passand
test_theming_hook.bats(from #926) 5/5 pass.Overlap with the sibling PRs in this batch
brew tap --trustwithbrew tap+brew trust#941 touchessystem_files/bluefin/.../just/system.justat lines 149/152(
brew tap); this PR touches line 249 (toggle-testing). Git auto-mergesthose hunks cleanly — verified with a real sequential merge, not
merge-tree.docs/skills/shell-scripts.md, but adds differentsections (
Bash DEBUG traps…,POSIX-sh files…vs this PR'simage-info.json is build-time state…). No semantic overlap.Whichever of #941/#942 merges second will need one more
docs/TESTING.mdresolve. That is inherent to the shared table, not a defect in either branch.