Skip to content

fix(ujust): use bootc status as source of truth for live image tag - #942

Open
castrojo wants to merge 2 commits into
mainfrom
fix/image-tag-bootc-status
Open

fix(ujust): use bootc status as source of truth for live image tag#942
castrojo wants to merge 2 commits into
mainfrom
fix/image-tag-bootc-status

Conversation

@castrojo

@castrojo castrojo commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Problem

/usr/share/ublue-os/image-info.json is 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 its image-ref carries 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 --json requires root, so the schema was verified from bootc source (crates/lib/src/spec.rs, v1.16.6): HostStatus.bootedBootEntry.imageImageStatus.imageImageReference.image. The jq path is .status.booted.image.image.image.

Note: the existing bonedigger-report uses .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:tag and repo:tag@sha256:... parse correctly. This also fixes a latent bug in the old sed transport-strip (s|^[a-z-]+:|| would have eaten registry:).

Output contract — unchanged

ublue-image-info.sh still emits name:tag plus 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 in test_ublue_image_info.bats. 43/43 pass across affected files. Wired into Justfile, unit-tests.yml, and docs/TESTING.md.

Reviewer notes

  • image-path instead of image-ref. bootc refs include a tag; the baked image-ref does not. Feeding a bootc ref through the old sed would produce …/bluefin-lts:stable:testing in bootc switch. image-path returns a tagless registry path and centralizes that.
  • Drive-by: test_changelog.bats filtered only ~/.local/bin for a host bctl, but brew ships one too — all 15 tests were failing on main locally. Now filters any PATH dir providing bctl.
  • Pre-existing, untouched: 4 failures in tests/test_update_just.bats (fail identically on main).
  • Follow-up, not in scope: bonedigger-report reads 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 main at d5b56d85 after #926 landed. The only collision was a
one-line conflict in the docs/TESTING.md test table — both PRs append a row.
Resolved by keeping both rows.

Re-verified after that merge: test_ublue_image_resolve.bats 23/23 pass
and test_theming_hook.bats (from #926) 5/5 pass.

Overlap with the sibling PRs in this batch

Whichever of #941/#942 merges second will need one more docs/TESTING.md
resolve. That is inherent to the shared table, not a defect in either branch.

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>

@hanthor hanthor left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

3-clanker-queue Work admitted to the agent-maintained queue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bctl shows the wrong image tag

2 participants