refactor: full rewrite of the template - #383
Siddhj2206 wants to merge 64 commits into
Conversation
Assisted-by: OpenAI o3 via Delta
Assisted-by: OpenAI o3 via Delta
Assisted-by: OpenAI o3 via Delta
Assisted-by: OpenAI o3 via Delta
Assisted-by: OpenAI o3 via Delta
Merge projectbluefin/finpilot main (be4b0eb) into the fork so the shared Actions pins and validation tooling stay current. - Bump every projectbluefin/actions pin from 1ca03ea to 2564c7bb, which carries the registry-normalization fix the promotion release gate needs and unblocks Renovate self-update. - Keep the fork's Actions foundation per docs/actions-baseline-assessment.md: a thin reusable-renovate caller instead of upstream's inline steps, and no local check-token-health composite. - Adopt upstream's .shellcheck-scope manifest, Justfile shell-sources recipe, and build/validate-flatpaks.sh. - Drop the upstream-added check-token-health tests and the .shellcheck-scope entry and assertions for the composite the fork removed. Assisted-by: Claude via Delta
Make the Containerfile the visible source of truth for image assembly and separate runtime integration from package installation. - Add build/10-overlay.sh: overlay common/shared (never bluefin/ or nvidia/) and the Brew integration files, copy the template's Brewfiles, Flatpak preinstalls and /etc/skel/.config seeds, merge custom ujust recipes deterministically, and enable the Brew/Flatpak/podman units. - Add build/20-packages-and-services.sh, which owns RPM/COPR installation and for now delegates to build/10-build.sh for the current default set. - Rename the cleanup phase to build/90-cleanup.sh. - Move OCI and ArtifactHub metadata into the Containerfile; the Justfile now passes only dynamic values (IMAGE_CREATED plus explicit overrides). - Rewrite build/00-image-info.sh to replace the base os-release identity instead of appending, keeping the generic ublue image-info.json schema. - Bump the common, brew and silverblue digests to current. Assisted-by: Claude via Delta
Give 20-packages-and-services.sh its real implementation and retire the transitional provider. - Install just, gum, fzf and jq, so ujust and its interactive prompts work on a fresh image. - Install uupd from the ublue-os/packages COPR and enable uupd.timer and uupd-resume.timer, so the update policy Common already ships takes effect. - Remove build/10-build.sh; package installation now lives in 20. - Enable the shared first-boot setup framework (ublue-system-setup and ublue-user-setup) in 10-overlay.sh. Assisted-by: Claude via Delta
Make 90-cleanup.sh the real final phase and fix how Flathub is provided. - Replace the clean-stage.sh wrapper with a real 90-cleanup.sh: disable and validate third-party repositories, keep the Fedora Flatpak remote off the image, disable the base automatic-update timer so uupd owns updates, and prune build artifacts mount-aware. - Add the custom/files/ system-file seam (mirrors / when overlaid) and ship flatpak-add-flathub-repos.service, a first-boot unit that adds the Flathub remote before flatpak-preinstall.service runs. - Drop build/clean-stage.sh. Build-time Flatpak remote state cannot work here: the /var prune removes it and bootc container lint --fatal-warnings rejects it, and flatpak does not read /etc/flatpak/remotes.d on its own. Assisted-by: Claude via Delta
Bump the moving pins to their current upstream heads ahead of CI testing: silverblue:44 (ece43ccc), projectbluefin/actions v1 (b0fbcf6c), and Homebrew/actions main (304fc86d). Assisted-by: Claude via Delta
…er digest to 31849a8 (#21) Co-authored-by: Renovate <renovate@whitesourcesoftware.com>
…t to 2268101 (#19) Co-authored-by: Renovate <renovate@whitesourcesoftware.com>
Replace the stale examples with four that match the rewritten phase scripts and current upstream practice. - 30-tailscale: the third-party RPM repository pattern (add, install, remove), replacing the Chrome/1Password example. - 40-gnome-extensions: install from extensions.gnome.org and from GitHub, compile the schemas they ship, enable them through the org.gnome.shell schema default. - 50-nvidia: follow Bluefin's NVIDIA setup step for step. - 60-desktop-swap: replace GNOME with COSMIC from the official Fedora repos, removing the GNOME session definitions too so the login screen stops offering sessions that can no longer start. Assisted-by: Claude via Delta
The factory reusable enrolls the verified promotion PR with `gh pr merge --auto` and no merge method. GitHub CLI rejects that outside a merge queue, and a merge queue needs an organization-owned repository, so the step failed on every run and the promotion PR was left to be merged by hand. Set enqueue_promotion: false so the run ends green and a maintainer merges the PR explicitly. The stable ruleset now allows squash merges only, so a manual merge still produces the `chore: promote main to stable` commit that execute-release.yml requires to trigger the release. This is a stopgap: the promotion and release workflow is expected to be rethought as part of #14. Assisted-by: Claude via Delta
Make custom/ add to the inherited common/shared layer instead of duplicating or shadowing it, and fix the seams that could not work as shipped. - brew: drop fonts.Brewfile so the curated font set the shared layer ships survives the override, and install-fonts bundles it for real. - flatpaks: replace the comment-only preinstall file with three real entries. The old file could not be parsed at all: ';' is not a GKeyFile comment and flatpak discards the whole file, logging the failure only at g_info. The validator now rejects malformed syntax and group names so it cannot return. - ujust: reduce custom-apps to the two Brewfile shortcuts, and custom-system to configure-dev-groups plus a new install-config. Delete the recipes that duplicated common (benchmark, clean-containers, update-and-reboot) and the placeholder, dropping the last use of the removed /usr/lib/ujust/ujust.sh helper whose Choose() left toggle-example-feature doing nothing. - configure-dev-groups: create the groups with groupadd --system instead of copying a /usr/lib/group entry Fedora does not ship, check membership first, and escalate only the mutating commands. - config: add the custom/config seam (README and an inert environment.d example) and state the overlay order as the contract in 10-overlay.sh. - files: custom/files is an override seam that carries payloads, so exclude its own README, which was landing at /README.md in the image. - flathub: flatpak imports remotes from /etc/flatpak/remotes.d on first use, so ship the descriptor and delete our flatpak-add-flathub-repos.service, its marker file and its enable. Matches projectbluefin/utah. custom/files/ ships no payload by design: it is a seam, not a feature. Assisted-by: DeepSeek V4.1 Flash via OpenCode
…icit The reusable's non-queue enrollment path runs `gh pr merge --auto` with no merge method, which GitHub CLI refuses without a TTY, and merge queues are unavailable to personal-account repos. A local job now arms auto-merge with `--squash` after the whole reusable call succeeds, so the release gate still runs first. The `stable` ruleset keeps requiring one approval -- that is the human step -- and auto-merge supplies the merge click after it. execute-release.yml accepts the subject a promotion actually produces. A squash merge takes its subject from `squash_merge_commit_title` (COMMIT_OR_PR_TITLE here), and the promotion branch is a single commit, so that subject is `chore: promote main to stable`. The rendered PR title is accepted too, because it becomes the subject if the branch ever carries more than one commit. PR #16 was merge-committed and matched neither. A push to stable that is not a promotion now fails instead of reporting success without promoting anything: stable receives no builds, so an image can only reach :stable through the candidate digest this workflow promotes. The release gate runs no e2e suites, by choice for a personal image.
The Justfile has always passed --build-arg SHA_HEAD_SHORT, but the Containerfile never declared it, so podman dropped the value and 00-image-info.sh's BUILD_ID branch could never fire. The commit now ships as org.opencontainers.image.revision. The ARG sits in the late metadata block with the other volatile values, because declaring it before 00-image-info.sh would invalidate the package and overlay layers on every commit. os-release therefore still carries no build commit, and the unreachable BUILD_ID branch is removed rather than left as a no-op. Tests assert the new contract, the label, and that the ARG stays behind the metadata marker.
Four suites were failing because the phases they test were split and renamed, not because the scripts were wrong. The suite was 155 tests with 26 failing; it is now 172 with none. - 10-build_test.bats tested build/10-build.sh, which no longer exists. Its assertions (package install, the no-overlay boundary, helper sourcing, fail-fast, nullglob) move to the package suite, so nothing is lost. - 20-packages-and-services_test.bats asserted delegation to 10-build.sh and fabricated one to test against. The phase installs directly now: the tests cover the default package set in one call, the isolated COPR sequence, both timer enables, the overlay boundary, helper availability and fail-fast. - clean-stage_test.bats tested build/clean-stage.sh, also gone, leaving 90-cleanup.sh with no tests. Its replacement carries over every behaviour assertion (corrected: three systemctl calls, not two) and adds the third-party repository pass: each repo shape, Fedora's repo left alone, an already-disabled repo staying disabled, and the fail-loud path when a repo cannot be rewritten. - 10-overlay_test.bats expected two rsync calls when the ladder makes four, an /etc/skel seed without its .config, and seven enables when nine run. The skel test also asserted filesystem effects a stubbed rsync cannot produce, so it now asserts the log. Adds the descriptor group, a test for the Flathub descriptor's URL and destination, and the seams' own-README exclusion. - justfile-clean_test.bats no longer names a deleted script in its fixture. Assisted-by: DeepSeek V4.1 Flash via OpenCode
The promotion trigger had no test, and its failure mode is silence: a subject that stops matching promotes nothing while reporting success, which is how PR #16 went unreleased. The new suite reads the pattern out of execute-release.yml rather than duplicating it, then asserts it accepts the subject a squash promotion actually produces and the rendered PR title, and rejects the shapes a promotion never produces, including PR #16's merge-commit subject. Setup fails loudly if the workflow line is reformatted, so an empty pattern can never match everything. unit-tests.yml now runs when that workflow changes; without it, editing the trigger would not run the test that guards it. Assisted-by: DeepSeek V4.1 Flash via OpenCode
The Justfile resolves REPO_ORG from GITHUB_REPOSITORY_OWNER, which GitHub Actions sets to the real repository owner, so three tests asserting the template's default vendor (projectbluefin) failed only in CI — a developer shell leaves the variable unset. They were already failing in the 05:25 run, masked by the 26 stale suites repaired in eb9d7b4. Setup unsets the variable, so those assertions test the documented default, and a new test covers the owner-derived behaviour deliberately, since that fallback is what lets a fork build without editing anything. The GITHUB_TOKEN test gets the same treatment: it asserts the unset case but inherited whatever the shell had exported. Verified with GITHUB_REPOSITORY_OWNER, CI, GITHUB_ACTIONS and GITHUB_TOKEN all set: 173 tests, 0 failures. Assisted-by: DeepSeek V4.1 Flash via OpenCode
GitHub holds every check run it creates for a bot-authored pull request in action_required until someone approves it, and the scheduled approver is throttled to hours here, so the promotion PR sat unvalidated for most of a morning. The job that arms auto-merge now also approves those runs. They are scoped to the bot-owned promotion branch and the bot actor, the same trust boundary the scheduled workflow documents, which stays as the backstop. One job instead of two, reusing the PR lookup it already does. The job comment also records why a release does not follow the merge by itself: the merge is performed as github-actions, and a push made with that token creates no workflow runs, so execute-release.yml's push trigger never fires for it. That is a property of auto-merge on a personal repository, not of the trigger configuration — the identical trigger fires when a human merges the promotion PR. Assisted-by: DeepSeek V4.1 Flash via OpenCode
Auto-merge as the bot cannot trigger anything downstream: the merge is performed as github-actions, and a push made with that token creates no workflow runs, so execute-release.yml and sync-stable-to-main.yml never fired and every release needed a manual dispatch. A merge performed by a person fires both. The job keeps the half that is still needed — approving the check runs GitHub holds for a bot-authored pull request — and loses the arming, the release/ready label gate it existed for, and its contents and pull-requests write. The ruleset and the branch protection on stable now require zero approvals while staying squash-only, so a single Squash and merge click is the whole human step. A hand merge cannot produce any other commit shape, and a squash of the single-commit promotion branch matches execute-release's trigger. Assisted-by: DeepSeek V4.1 Flash via OpenCode
validate-flatpaks.yml reimplemented the validation contract inline — its own script's header calls the workflow a thin caller — and missed the GKeyFile syntax rules, so a malformed preinstall file passed CI while failing `just validate-flatpaks`. It now calls build/validate-flatpaks.sh, and its paths include the script and its test so a change to the contract runs the check that enforces it. validate-justfiles.yml duplicated the Justfile's `check` recipe with an inline just --unstable --fmt --check. It now runs `just check`, which covers the root Justfile and every .just file from one definition. Assisted-by: DeepSeek V4.1 Flash via OpenCode
Merging the promotion PR by hand leaves several things without a job: - approve-trusted-promotion-runs.yml cleared held check runs on a schedule the promotion workflow now handles on every run, at roughly 96 jobs a day and one more scheduled workflow that dies after 60 days of inactivity. The promotion job comment no longer points at it. - merge_group triggers in pr-validation.yml and unit-tests.yml can never fire: merge queues require an organization-owned repository. - build-image.yml's PR scaffolding is dead while PR builds are disabled: detect-changes, the eight always-true pull_request guards, the REGISTRY_CACHE_WRITE expression, pr-number, and the commented-out pull_request trigger. The paths-ignore policy is unchanged, so a change to a workflow the build depends on still builds, while the four validate workflows stay ignored. - sync-stable-to-main.yml had no manual trigger despite normally no-oping. Assisted-by: DeepSeek V4.1 Flash via OpenCode
Four places stated one fact twice or three times, and .shellcheck-scope existed to declare a rule git already knows. - shell-sources stops parsing the manifest: the repository's shell scripts are the *.sh files git tracks. CI resolves the same command into validate-pr's shellcheck-glob, so the manifest and its drift tests retire with it. - check and fix share _format-justfiles, so the justfile file set has one definition instead of a find loop each. - format uses that same source set rather than walking every *.sh under the tree, which also stops it descending into nested checkouts. - build resolves IMAGE_VENDOR/REPO_ORG once instead of three times. - test-unit owns the suite invocation and its flags; CI calls it. - clean drops previous.manifest.json, output.env and changelog.md, which nothing in the repo produces. - build joins tag-images under the Image group, lint and format under Just. Also: spawn-vm builds build-<type> instead of passing the rebuild flag where target_image goes, the VM header comments say what the code does, and .hadolint.yaml moves to .github/ because the workflow's hadolint-config input is its only consumer. Assisted-by: DeepSeek V4.1 Flash via OpenCode
iso/iso.toml is rename location 7, and it still switched to ghcr.io/projectbluefin/finpilot:stable, so an ISO built from this fork would install the custom image and then immediately switch the machine to upstream's. Assisted-by: DeepSeek V4.1 Flash via OpenCode
The major-crossing guard matched quay.io/fedora-ostree-desktops/silverblue by name, so swapping the base would silently drop it. Match the dockerfile manager instead, and let a major crossing arrive as a PR a human merges rather than suppressing it entirely. The first-party image rule gains quay.io/hummingbird-community/* and loses its schedule override: schedule only filters when Renovate may open a branch, and "at any time" is already the default, so the line looked like a cadence without being one. The cadence comes from renovate.yml's six-hourly cron. Assisted-by: DeepSeek V4.1 Flash via OpenCode
Bootc Image Builder records the post-install `bootc switch` origin from the image reference it is given, and the hand-written kickstart in iso/iso.toml existed only to override the local build tag with the published GHCR ref. Hand the builder the published ref instead: read image-info.json out of the image, tag the local build under that name, and let BIB's own switch record the origin. The ISO still deploys from local storage; only the recorded origin changes. This drops the last hardcoded owner from the ISO path, so a fork no longer edits iso/iso.toml to rename, and it moves the builder pin off the frozen quay.io/centos-bootc/bootc-image-builder container onto the current ghcr.io/osbuild/bootc-image-builder. Assisted-by: DeepSeek V4.1 Flash via OpenCode
… digest to 536391d (#26) Co-authored-by: Renovate <renovate@whitesourcesoftware.com>
…hed ref iso/iso.toml is now the source of truth for what the installer does, and two things about it were wrong. The kickstart table is load-bearing. bootc-image-builder only injects its automatic partitioning -- clearpart --all, autopart --nohome, reboot --eject -- on the branch where no kickstart content is supplied. Removing the table therefore turns the ISO into an installer that erases the first disk without asking. It stays, with command-free content, and the file now records why it must not be deleted. Bootc Image Builder records the installed system's bootc origin from the image reference it is given, so the ISO is built against the reference in the image's own image-info.json instead of the local build tag. That replaces the hand-written %post switch and the hardcoded owner it carried, so a fork no longer renames anything in iso/. The module list drops the entries bootc-image-builder already enables for every Anaconda ISO; the effective module set is unchanged, and the remaining entries are the reason the partitioning and user screens exist. iso/disk.toml keeps its size but documents the btrfs mountpoint rules. The builder pin moves off the frozen pre-merge quay.io/centos-bootc container onto the current ghcr.io/osbuild/bootc-image-builder. Assisted-by: DeepSeek V4.1 Flash via OpenCode
The six co-located READMEs now state only what is local to their directory: the format, how the build wires it, and the gotchas. Each defers the "where does this belong" decision to the customize skill, so that decision has one home. Every stale reference is gone: build/10-build.sh and build/clean-stage.sh never existed, the example catalogue was wrong in all four places that listed it, and custom/ujust/README.md documented a recipe the image does not ship. custom/flatpaks/README.md now states the real first-boot behaviour: the preinstall service exits successfully when it cannot reach Flathub, so it does not retry that boot. Assisted-by: DeepSeek V4.1 Flash via OpenCode
Each skill now owns one procedure, with no format restated from a directory README: - overview: what finpilot is, the multi-stage assembly, the file map, and the one routing table. - onboarding: every repository setting, each with a gh command and a GitHub-website route, ending in a verification audit. - customize: where a package, app, or command belongs. - build: the Containerfile, Justfile, pinning, and the example scripts. - ci: the workflows, the release model, signing, and Renovate. - troubleshooting: symptoms and the pre-commit checklist. Every stale fact is gone: build/10-build.sh, the wrong example catalogue, the FEDORA_MAJOR_VERSION ARG that does not exist, and the removed token-health and label-enforcement machinery. Assisted-by: DeepSeek V4.1 Flash via OpenCode
Comments keep explaining why, and lose the parts that cannot stay true: - bare issue numbers, which point at a tracker a fork does not have - an observed run id, which means nothing outside that run - the "7 rename locations" framing in the Containerfile, replaced by the three real sites and the contract test that guards them - "shared with Aurora" for projectbluefin/common - tests/unit, replaced by tests/template The default Brewfile header and the NVIDIA example no longer name the project, which leaves the identity sites as the only place a rename has to reach. Assisted-by: DeepSeek V4.1 Flash via OpenCode
Delete the material that existed to plan the rewrite rather than to ship the template: CONTEXT.md, docs/ (the four assessments, the architecture map, the actions catalog, and the Matt Pocock agent config), the setup checklist, the copilot instructions, and the commit convention. Their durable content now lives in the skills that own it. The one surviving reference — 10-overlay.sh pointing at the common-overlay assessment for why common/bluefin is imported but never overlaid — states that reason in place instead. No dead relative links remain, the suite is green, and actionlint, shellcheck, yaml, and the Justfile checks all pass. Assisted-by: DeepSeek V4.1 Flash via OpenCode
The skill named the template in its description and its opening line, which made it another site a fork would have to grep for after a rename. It now describes the repository it lives in. Assisted-by: DeepSeek V4.1 Flash via OpenCode
A first-time reader took the docs at face value and reported where they lie: - README, overview, and build all called the FROM line "the single source for the Fedora major". It is not one read: `just build` parses the tag for the version string, and 00-image-info.sh reads the base's os-release for the metadata. All three now say what actually happens. - AGENTS.md said pushes to `stable` publish `:stable`. `stable` never rebuilds; execute-release promotes the digest `main` already built. - 60-desktop-swap.sh.example told you to set BASE_IMAGE_NAME in the identity block, which no longer exists. - identity_test.bats described custom/ujust/README.md as restating the name, which it no longer does. - The onboarding audit now says a documentation-only push builds nothing. - build/README.md's RUN block used a real script name where a placeholder was meant, which read as if it were NVIDIA-specific. The stale rewrite handoff moved out of the tree; it advertised files and skills that no longer exist. Assisted-by: DeepSeek V4.1 Flash via OpenCode
The cold-start reader flagged three facts stated in more than one place. Each now lives where its reader is, and the skill points at it: - the cosign verify command: README, not the ci skill - the release-gate limitation: README, not the ci skill. The promote workflow's comment keeps the local reason run_e2e is false and drops the restatement. - the first-boot failure modes: README, not the troubleshooting skill, which keeps the repo-side runtime symptom The ci skill also repeated the "stable publishes :stable" error that AGENTS.md had. Assisted-by: DeepSeek V4.1 Flash via OpenCode
The by-hand routes were one-liners that assumed you already knew where GitHub hides each setting. They are now walkthroughs: the menu path, the exact control, what to type, and the trap. - Steps 2-4 name the repository Settings page and the control on it. - Step 5 walks the classic PAT from the profile menu to the copied value, and says why `repo` and `workflow` are both needed. - Steps 7-9 say where the `validate` check comes from and why zero approvals on `stable` is deliberate. - Step 10 admits that creating twelve labels by hand is tedious and says so. - The failure modes now cover a check that has never run. Assisted-by: DeepSeek V4.1 Flash via OpenCode
Co-authored-by: Renovate <renovate@whitesourcesoftware.com>
…er digest to fac5b1d (#32) Co-authored-by: Renovate <renovate@whitesourcesoftware.com>
Co-authored-by: Renovate <renovate@whitesourcesoftware.com>
…t to b7e3487 (#35) Co-authored-by: Renovate <renovate@whitesourcesoftware.com>
The ubuntu v26 bump made every build fail: setup-runner's "Add Ubuntu
resolute apt source" step runs `test "${IDV}" = "ubuntu-24.04"`, which
exits 1 on a 26.04 runner before any build step runs.
Revert runs-on to ubuntu-24.04 and hold Renovate below 26.04 so the
bump cannot immediately return. Drop the hold when upstream supports
26.04.
Assisted-by: DeepSeek V4.1 Flash via OpenCode
The promotion test used this fork's owner in its merge-commit example, and execute-release.yml's comment pointed at a fork PR number. Neither means anything once this tree is the template again. Assisted-by: DeepSeek V4.1 Flash via OpenCode
There was a problem hiding this comment.
Reviewed at head bcee4638 (correctness perspective). The promotion chain holds together: main builds retain :testing (build-image.yml "Finalize branch tags"), execute-release.yml re-verifies the candidate digest's cosign signature and copies it with --preserve-digests, and the caller job's permissions match what the pinned reusable needs. Three findings, worst first:
-
Fork residue in
.github/renovate.json:41— the "Keep first-party and base OCI images fresh immediately" rule matchesquay.io/hummingbird-community/*, which is the author's personal fork's namespace, not anything this template uses. This repo's actual base (quay.io/fedora-ostree-desktops/silverblue, Containerfile:52) is not in the list. Digest bumps still automerge via the general rule at line 31, so the practical effect is small, but the shipped template config references a registry namespace that has nothing to do with it. Should be the base image ref or dropped. -
just buildnow hard-fails on the CentOS base the Containerfile still advertises — Justfile:149–150 derives the Fedora major from the baseFROMline and requires a purely numeric tag;quay.io/centos-bootc/centos-bootc:stream10, still listed as a supported base option at Containerfile:30, doesn't match either sed pattern, so the recipe exits with "Could not read the base image". Before this PR,FEDORA_MAJOR_VERSIONwas an explicit ARG and that base built. Either handle non-numeric tags or drop the CentOS option from the comment. -
Low —
set_os_release_valueappend path writes sed-escape artifacts (build/00-image-info.sh:61–69): the value is escaped for the sed replacement (&→\&,|→\|,\→\\) before the branch, so when a key is absent and appended viaprintf, the literal backslashes land in/usr/lib/os-release. Only reachable with override values containing those characters (e.g. aHOME_URLwith a query string), since all touched keys exist in Fedora's os-release; move the escaping inside the sed branch.
Not verified: behavior of the pinned projectbluefin/actions composite actions beyond reusable-execute-release.yml, and the squash-title dependency in execute-release's trigger check (documented and tested, but contingent on the repo's squash_merge_commit_title setting staying COMMIT_OR_PR_TITLE).
— hive: agent=reviewer backend=copilot model=claude-fable-5 copilot=1.0.78
The "keep first-party and base OCI images fresh immediately" rule matched `quay.io/hummingbird-community/*` and nothing else. Hummingbird is one of the three bases the template supports, but the rule never named the active base, so the namespace it exists for was missing and the one it carried read as residue. List the three base namespaces the Containerfile documents — Fedora, CentOS, Hummingbird — alongside the context images, and add the Hummingbird option to the Containerfile's base list so the namespace has a home. Assisted-by: DeepSeek V4.1 Flash via OpenCode
`just build` derived the version string's major from the base FROM line with a sed that only matched `:([0-9]+)`. The Containerfile documents `quay.io/centos-bootc/centos-bootc:stream10` as a base, so switching to it made the recipe exit with "Could not read the base image" — a regression from dropping the explicit FEDORA_MAJOR_VERSION ARG. Take the tag verbatim instead and use it in the version string. The no-tag case still aborts, which is now the only thing the guard is for. Assisted-by: DeepSeek V4.1 Flash via OpenCode
`set_os_release_value` escaped the value for the sed replacement before choosing a branch, so when a key was absent the append path wrote the backslashes literally. Move the escaping into the sed branch and append the value as it is. Only reachable for a key the base os-release lacks, with a value containing `&`, `|` or `\` — but the append path should not depend on that. Assisted-by: DeepSeek V4.1 Flash via OpenCode
`ubuntu-latest` currently resolves to 24.04, but the label moves underneath the job and `build-image.yml` and `validate-renovate.yml` are already pinned. Pin this one too, so the whole surface follows the same rule the Renovate config holds. Assisted-by: DeepSeek V4.1 Flash via OpenCode
The runner image bakes its apt lists at image build time. `bubblewrap` has rotated off the mirror since, so the baked list named `0.9.0-1ubuntu0.1` and the install died on a 404 — three attempts, three different mirror IPs — before the validator ever ran. Refresh the lists first, which is what the error message asks for. The mirrors still carry the package, just not that version. Assisted-by: DeepSeek V4.1 Flash via OpenCode
|
All three findings are fixed at
Findings 2 and 3 each have a test that fails without its fix. Separately, |
Siddhj2206
left a comment
There was a problem hiding this comment.
Rewrite was done to both simplify unnecessary bulk as well as add useful features and fixes. Moves more stuff upstream such as renovate, removing the need for extra bash scripts like check-token-health.
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
The promotion workflow disables the reusable pre-merge gate and resolves a mutable candidate tag, so stable may publish a different image than the one reviewed.
Get a fresh assessment by requesting another Copilot review.
Review effort: Lite
Findings: 1
What changed in this PR
This PR rewrites the template’s build phases, runtime customization, CI release flow, tests, and documentation.
Changes:
- Splits image assembly into explicit overlay, package, metadata, and cleanup phases.
- Promotes signed candidate digests from
maintostablewithout rebuilding. - Reorganizes runtime seams, validation tests, ISO configuration, and agent documentation.
| File | Description |
|---|---|
Containerfile |
Defines the rewritten build stages. |
Justfile |
Updates build, test, and image recipes. |
README.md |
Documents setup, customization, and releases. |
.github/workflows/promote-main-to-stable.yml |
Configures promotion workflow. |
.github/workflows/execute-release.yml |
Promotes candidate images and creates releases. |
.github/renovate.json |
Changes dependency automation policy. |
build/00-image-info.sh |
Writes image identity metadata. |
build/10-overlay.sh |
Applies runtime overlays and declarations. |
build/20-packages-and-services.sh |
Installs default packages and services. |
build/90-cleanup.sh |
Finalizes repositories and cleans artifacts. |
iso/iso.toml |
Simplifies interactive ISO installation. |
iso/disk.toml |
Documents disk image partitioning. |
custom/ujust/custom-system.just |
Adds group and configuration commands. |
custom/ujust/custom-apps.just |
Defines Homebrew shortcuts. |
custom/flatpaks/default.preinstall |
Adds default Flatpak applications. |
custom/flatpaks/README.md |
Documents Flatpak declarations. |
custom/brew/default.Brewfile |
Updates default Brewfile. |
custom/brew/README.md |
Documents Homebrew integration. |
custom/config/README.md |
Documents per-user configuration. |
custom/config/environment.d/10-example.conf |
Adds an inert environment example. |
custom/files/README.md |
Documents system file overlays. |
build/validate-flatpaks.sh |
Adds Flatpak syntax validation. |
build/README.md |
Documents build phases and examples. |
build/30-tailscale.sh.example |
Adds a Tailscale example. |
build/40-gnome-extensions.sh.example |
Adds GNOME extension setup. |
build/50-nvidia.sh.example |
Adds NVIDIA setup guidance. |
build/60-desktop-swap.sh.example |
Adds COSMIC desktop replacement. |
tests/contract/identity_test.bats |
Tests identity consistency. |
tests/contract/validate-flatpaks_test.bats |
Tests Flatpak validation. |
tests/template/20-packages-and-services_test.bats |
Tests package phase behavior. |
tests/template/execute-release_test.bats |
Tests release trigger matching. |
tests/template/custom-apps-just_test.bats |
Tests application recipes. |
tests/template/90-cleanup_test.bats |
Tests cleanup behavior. |
.github/workflows/pr-validation.yml |
Expands shellcheck validation scope. |
.github/workflows/unit-tests.yml |
Runs the reorganized test suite. |
.github/workflows/validate-flatpaks.yml |
Uses centralized Flatpak validation. |
.github/workflows/validate-brewfiles.yml |
Updates Brewfile test paths. |
.github/workflows/validate-justfiles.yml |
Uses centralized Just validation. |
.github/workflows/validate-renovate.yml |
Switches to reusable validation. |
.github/workflows/renovate.yml |
Switches to reusable Renovate execution. |
.github/workflows/sync-stable-to-main.yml |
Updates branch synchronization. |
.github/workflows/clean.yml |
Updates image cleanup action pin. |
.github/hadolint.yaml |
Adds Containerfile lint configuration. |
.pre-commit-config.yaml |
Updates validation test references. |
AGENTS.md |
Replaces repository guidance with concise gates. |
.agents/skills/README.md |
Simplifies the skill index. |
.agents/skills/overview/SKILL.md |
Adds repository architecture guidance. |
.agents/skills/build/SKILL.md |
Adds build procedures. |
.agents/skills/ci/SKILL.md |
Adds CI and release procedures. |
.agents/skills/customize/SKILL.md |
Adds customization routing. |
.agents/skills/troubleshooting/SKILL.md |
Adds troubleshooting guidance. |
tests/unit/shellcheck-scope_test.bats |
Removes obsolete shellcheck-scope tests. |
tests/unit/clean-stage_test.bats |
Removes obsolete cleanup tests. |
build/10-build.sh |
Removes monolithic build script. |
build/clean-stage.sh |
Removes superseded cleanup script. |
build/20-onepassword.sh.example |
Removes obsolete example. |
build/30-cosmic-desktop.sh.example |
Removes obsolete example. |
build/40-nvidia.sh.example |
Replaces the NVIDIA example. |
custom/brew/fonts.Brewfile |
Removes the empty font declaration. |
.shellcheck-scope |
Removes obsolete scope manifest. |
.github/workflows/label-enforcement.yml |
Removes local label enforcement. |
.github/workflows/approve-trusted-promotion-runs.yml |
Removes superseded approval workflow. |
.github/actions/check-token-health/action.yml |
Removes token health action. |
.github/actions/check-token-health/check_token_health.sh |
Removes token health implementation. |
.github/SETUP_CHECKLIST.md |
Replaces setup checklist with skill guidance. |
.github/copilot-instructions.md |
Removes superseded instructions. |
.github/commit-convention.md |
Removes superseded commit documentation. |
.agents/skills/finpilot-templates/SKILL.md |
Removes superseded skill. |
.agents/skills/finpilot-router/SKILL.md |
Removes superseded skill. |
.agents/skills/finpilot-pr-checklist/SKILL.md |
Removes superseded skill. |
.agents/skills/finpilot-packages/SKILL.md |
Removes superseded skill. |
.agents/skills/finpilot-overview/SKILL.md |
Removes superseded skill. |
.agents/skills/finpilot-onboarding/SKILL.md |
Removes superseded skill. |
.agents/skills/finpilot-maintain/SKILL.md |
Removes superseded skill. |
.agents/skills/finpilot-examples/SKILL.md |
Removes superseded skill. |
.agents/skills/finpilot-custom/SKILL.md |
Removes superseded skill. |
.agents/skills/finpilot-ci/SKILL.md |
Removes superseded skill. |
.agents/skills/finpilot-build/SKILL.md |
Removes superseded skill. |
.agents/skills/skill-improvement/SKILL.md |
Removes superseded skill. |
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| # fire for it and the release becomes a manual dispatch. A merge performed | ||
| # by a person fires both, which is why this PR is merged in the UI rather | ||
| # than by a bot. | ||
| enqueue_promotion: false |
There was a problem hiding this comment.
Reviewed at head f6707c1 — correctness perspective.
1. (medium) just rebuild-iso bakes a broken bootc origin containing localhost/.
rebuild-iso defaults $target_image to localhost/finpilot (Justfile:368) and _rebuild-bib passes that straight into build, which sets --build-arg IMAGE_NAME=${target_image} (Justfile:196). 00-image-info.sh then derives image-ref = ostree-image-signed:docker://ghcr.io/${IMAGE_VENDOR}/${IMAGE_NAME} (build/00-image-info.sh:43), i.e. ghcr.io/<vendor>/localhost/finpilot, and also writes NAME/VARIANT_ID/PRETTY_NAME as localhost/finpilot (build/00-image-info.sh:98-100). This identity pollution is pre-existing, but this PR makes it consequential: the new ISO path reads image-ref from image-info.json and hands it to BIB as the install/switch target (Justfile:316-317), so an ISO produced by just rebuild-iso installs a system whose bootc origin points at a registry path that cannot exist. The PR body sells this exact seam as the fix for "a renamed project does not install upstream's image". Fix: strip a localhost/ prefix before deriving IMAGE_NAME in the build recipe (or when composing image-ref).
2. (low) The promoted digest is not necessarily the gated digest.
execute-release.yml promotes whatever :testing resolves to at merge time (execute-release.yml:79) with run_release_gate: false (execute-release.yml:87); the reusable resolves the tag at run time. The promotion PR is merged by hand, possibly days after promote-main-to-stable.yml gated a candidate, and any main push in between moves :testing. Result: the comment's claim that "promote-main-to-stable.yml already gated the same digest" can be false, and the stable branch tree can diverge from the image published as :stable. Cosign re-verification still holds, so this is an edge case rather than a hole, but it contradicts the stated invariant; consider pinning the candidate digest in the promotion PR and passing it through.
3. (medium) Renovate now automerges GitHub Actions bumps into privileged workflows.
The previous config's last rule explicitly refused automerge for matchManagers: ["github-actions"] because those SHAs run with packages: write, id-token: write, secrets: inherit. The rewrite replaces it with automerge of everything below a major (.github/renovate.json:30-36), and PR builds are disabled, so third-party action bumps (e.g. nick-fields/retry) merge with only shellcheck/hadolint having run. The trade is disclosed in the rule description, but it reverses a documented supply-chain guard — worth an explicit maintainer decision rather than riding in with the refactor.
Not fully verified: the contents of projectbluefin/common:shared (whether every unit enabled in build/10-overlay.sh:118-131 exists there) and bootc-image-builder's kickstart-injection behavior assumed by iso/iso.toml — both taken on the author's research.
— hive: agent=reviewer backend=copilot model=claude-fable-5 copilot=1.0.78
…commit The promotion PR lost its pre-merge release gate. `enqueue_promotion: false` disables the factory reusable's `gate` job as well as its enrollment job — one input, both jobs — and it was set false because a personal repository cannot enroll for merge. Meanwhile `execute-release.yml` resolved the mutable `:testing` tag at merge time with no guard, so a promotion that had fallen behind `main` would publish a newer image than the PR was reviewed against. - `promote-main-to-stable.yml` runs the gate itself, as a `gate` job calling `reusable-release-gate.yml`, so the promotion PR is verified and labelled before a human merges it. A new `locate-promotion-pr` job owns the PR lookup that both the approver and the gate need. - `execute-release.yml` passes `source_branch: main`, which switches on the reusable's own guard: it refuses to resolve `:testing` unless `main`'s tree still matches the promoted commit. A manual dispatch is exempt, because that path is deliberate recovery. - The `ci` skill and the README now describe the gate and the guard, and the workflow comments no longer claim a gate that was not running. Assisted-by: DeepSeek V4.1 Flash via OpenCode
There was a problem hiding this comment.
Reviewed (correctness) at head 899cac7. No blockers; two minor notes.
-
low — ujust recipe discovery is no longer recursive.
build/10-overlay.sh:84collects recipes with the top-level glob/ctx/custom/ujust/*.just, where the deletedbuild/10-build.sh:34usedfind /ctx/custom/ujust -iname '*.just'(recursive, case-insensitive). A fork that organizes recipes into subdirectories undercustom/ujust/now ships a60-custom.justsilently missing them — no build error, the recipes just don't appear. Worth either restoring recursion or stating the flat-directory contract incustom/ujust/README.md. -
info — a bare
podman build .no longer works:Containerfile:61setsARG BASE_IMAGE_NAME=""andbuild/00-image-info.sh:21hard-fails on the empty value. All docs route throughjust build(which derives the value from the FROM line), and the failure message is clear, so this looks intentional — flagging only because it's a behavioral regression from the previousARG BASE_IMAGE_NAME="silverblue"default.
Verified against the diff: the promotion chain is internally consistent (main builds keep the bare :testing tag in build-image.yml's Finalize step, which execute-release.yml's source_tag: "testing" resolves), unit-tests.yml installs just before calling just test-unit, and the pinned projectbluefin/actions@ce1d0c6 reusable exposes the source_branch/fast_forward_sha/run_release_gate inputs the callers use. Not verified: runtime behavior of the reusable workflows and the bats suite were not executed here; the release-flow semantics are judged from the pinned sources only.
— hive: agent=reviewer backend=copilot model=claude-fable-5 copilot=1.0.78

What this is
A full rewrite of the template, developed on my fork and offered back as one
piece. It changes the build substrate, CI and the release flow, the test suite,
the runtime custom layer, the ISO config, and all of the documentation.
It is deliberately one PR. The phases, the release flow, and the docs describe
one design; split apart, the docs would ship describing a tree that does not
exist yet.
Why
Three problems drove it.
build/10-build.shlayered theOCI images, installed packages, enabled services, and cleaned up. Activating
one of the examples meant editing a file that already had five jobs.
README.md,AGENTS.md, 13prefixed skills under
.agents/skills/, and symlinks underdocs/skills/—restating the same facts until they disagreed.
AGENTS.mdalone was 164 linesof instructions, checklist, and policy.
stableran its own build instead of promoting thedigest
mainhad already tested.What changed
Build
build/10-build.shis gone. The Containerfile names each phase in its ownRUNblock, so order comes from the Containerfile and the numbers only communicate
intent:
00-image-info.shos-releaseandimage-info.json.10-overlay.shprojectbluefin/commonand the Brew integration, copies this template's declarations, and enables the units that consume them. Installs no packages.20-packages-and-services.sh90-cleanup.shbootc container lint.Packages sit in their own phase, so an overlay edit cannot invalidate the package
layer.
clean-stage.shis folded into90-cleanup.sh.The base image is now the single source of truth for Fedora identity: the
Containerfile owns
IMAGE_NAME/IMAGE_VENDOR, andjust buildreads the baseimage name and tag from the
FROMline. The Fedora major comes from the baseimage's own
os-releaseduring the build, so aFROM-only bump cannot ship awrong
fedora-version.Four examples ship inactive:
30-tailscale,40-gnome-extensions,50-nvidia,60-desktop-swap.build/README.mdhas the phase table and the activationshape.
CI and the release flow
execute-release.ymlis new. It promotes the exact candidate digestmainalready built and signed, and refuses any push to
stablethat is not apromotion — so
stablenever rebuilds and never receives a hand-edit.The promotion PR is merged by hand and the auto-merge enrollment is gone. The
machinery the manual-merge flow made redundant is deleted:
approve-trusted-promotion-runs.yml,label-enforcement.yml, and thecheck-token-healthcomposite action.Validators now point at a single implementation each.
renovate.jsonfollowswhatever base image the Containerfile uses instead of restating it, and automerges
everything below a major.
Tests
The suite is split by what it protects:
tests/contract/— the template's promises (image identity, the renamecontract, the validators). 34 tests.
tests/template/— the build phases and the Justfile recipes. 175 tests.just test-contractandjust test-templatereplace the singlejust test-unit.The old
tests/unit/files for phases that no longer exist are deleted; the newphase scripts have executed coverage.
Runtime layer
custom/gained a config seam (custom/config/environment.d/) and READMEs foreach directory.
custom-system.justis reworked around the seams the buildactually creates. The
custom-apps.justandcustom-system.justrecipes arecovered by tests.
ISO
iso/iso.tomlno longer hardcodes abootc switchref. The installer staysinteractive, and the Justfile reads the image reference from the built image's
image-info.json, so a renamed project does not install upstream's image.Documentation
README.mddrops from 446 to 182 lines andAGENTS.mdfrom 164 to 53. The 13prefixed skills and the
docs/skills/symlinks are replaced by six skills under.agents/skills/:overview,onboarding,customize,build,ci,troubleshooting.The rule the rewrite follows: one statement per fact. The entry points route, the
skills hold the procedures, and each directory's README owns its local format and
gotchas. The onboarding skill carries both a
ghcommand and a fullGitHub-website walkthrough for every setup step, because not everyone has or
wants the CLI.
CONTEXT.md,docs/,SETUP_CHECKLIST.md,copilot-instructions.md, andcommit-convention.mdare deleted.Validation
just test-unit(contract + template): 209 tests, 0 failures.just check,just lint,actionlint, andhadolintclean.(
Containerfile,Justfile,artifacthub-repo.yml), andjust test-contractfails when they disagree.
mainbuild publishes:stable-testing; the promotion PR promotes thedigest.
Notes for review
is the thing to review. I am happy to squash it if you would rather.
README.mdstill carries the "What Makes this Raptor Different?" placeholdersection; it is the template's, not mine.
Assisted-by: DeepSeek V4.1 Flash via OpenCode