Build-it-yourself model: bluebuild switch, no published image, drop CI workflow

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-29 14:11:09 -04:00
co-authored by Claude Fable 5
parent 7f0a30c01f
commit 2d392f9d69
3 changed files with 51 additions and 145 deletions
-42
View File
@@ -1,42 +0,0 @@
# Gitea Actions workflow. Uses the BlueBuild CLI directly — the blue-build/github-action
# is a GitHub-context composite and is not supported outside GitHub/GitLab CI.
# Runner requirement: act_runner must allow privileged job containers
# (container.privileged: true in the runner's config.yaml) — buildah needs it.
# NOTE: manual-only for now — no Actions runner is registered on the instance, and the
# image is currently built/pushed locally (see README "Building locally"). When a runner
# exists, restore the schedule + push triggers below to make builds fully automatic:
# schedule:
# - cron: "00 06 * * *" # daily rebuild pulls in base-image updates
# push:
# branches: [main]
# paths-ignore: ["**.md"]
name: bluebuild
on:
workflow_dispatch:
jobs:
bluebuild:
name: Build Custom Image
runs-on: ubuntu-latest
container:
image: ghcr.io/blue-build/cli:v0.9
options: --privileged
env:
# Auto-signs on push when this + cosign.pub (repo root) are present.
COSIGN_PRIVATE_KEY: ${{ secrets.SIGNING_SECRET }}
# Gitea's auto job token cannot push packages (documented limitation),
# so registry auth uses a PAT with write:package scope.
BB_REGISTRY: git.lazypugs.com
BB_REGISTRY_NAMESPACE: ckoch
BB_USERNAME: ckoch
BB_PASSWORD: ${{ secrets.REGISTRY_TOKEN }}
steps:
# Plain clone instead of actions/checkout: the CLI job container isn't
# guaranteed to have node, and the repo is public.
- name: Clone repo
run: |
git config --global --add safe.directory "$(pwd)"
git clone --depth 1 --branch "${GITHUB_REF_NAME:-main}" "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git" .
- name: Build and push signed image
run: bluebuild build --verbose --push --retry-push recipes/recipe.yml
+42 -96
View File
@@ -1,9 +1,8 @@
# main-desktop # main-desktop
Custom [Bazzite DX](https://github.com/ublue-os/bazzite-dx) image for my desktop, built with Custom [Bazzite DX](https://github.com/ublue-os/bazzite-dx) image for my desktop, defined with
[BlueBuild](https://blue-build.org), published signed (cosign) to the container registry on my [BlueBuild](https://blue-build.org). **There is no published image** — clone this repo and build
Gitea instance (**git.lazypugs.com**) — built locally today, Gitea Actions-ready when a runner it yourself; `bluebuild switch` rebases the running system straight onto the local build.
is registered.
- **Base:** `ghcr.io/ublue-os/bazzite-dx-nvidia` — Bazzite DX, KDE Plasma, **NVIDIA open kernel - **Base:** `ghcr.io/ublue-os/bazzite-dx-nvidia` — Bazzite DX, KDE Plasma, **NVIDIA open kernel
modules**. There is no separate `-open` DX image anymore: DX NVIDIA is open-driver-only, which modules**. There is no separate `-open` DX image anymore: DX NVIDIA is open-driver-only, which
@@ -14,110 +13,56 @@ is registered.
- **This image layers almost nothing.** The base already includes Steam and the gaming stack, - **This image layers almost nothing.** The base already includes Steam and the gaming stack,
Flatpak, Docker CE + CLI, Podman, VS Code, Homebrew, **and nvidia-container-toolkit with Flatpak, Docker CE + CLI, Podman, VS Code, Homebrew, **and nvidia-container-toolkit with
automatic CDI setup** (`ublue-nvctk-cdi.service` regenerates `/etc/cdi/nvidia.yaml` each boot). automatic CDI setup** (`ublue-nvctk-cdi.service` regenerates `/etc/cdi/nvidia.yaml` each boot).
What this recipe adds: the cosign signature-verification policy and a declarative first-boot What this recipe adds: a declarative first-boot Flatpak set (and a dormant signing policy).
Flatpak set. That's it — by design. That's it — by design.
Why BlueBuild instead of the raw Containerfile template: with the container toolkit already Why BlueBuild instead of a raw Containerfile: the whole custom layer is "install these Flatpaks
in-image, the whole custom layer is "install these Flatpaks on first boot + trust my signature", on first boot", which the `default-flatpaks` module expresses declaratively — and
which the `default-flatpaks` and `signing` modules express declaratively in ~40 lines of YAML — `bluebuild switch` gives a one-command build-and-rebase with no registry involved.
no hand-rolled first-boot systemd unit needed.
## Building & publishing ## Getting onto this image (fresh desktop rebuild)
### Locally (current path) 1. Install stock **Bazzite DX (NVIDIA)** from the official ISO at
[bazzite.gg](https://bazzite.gg) and boot into it.
The image is built and pushed from a workstation with the BlueBuild CLI driving Docker. From 2. Install the BlueBuild CLI and switch:
the repo root (needs `cosign.key` present there, and ~25 GB free for layers):
```bash ```bash
# One-time CLI install (extracts a static binary to /usr/local/bin/bluebuild): git clone https://git.lazypugs.com/ckoch/main-desktop.git && cd main-desktop
docker run --pull always --rm ghcr.io/blue-build/cli:latest-installer | bash podman run --pull always --rm ghcr.io/blue-build/cli:latest-installer | bash
bluebuild switch recipes/recipe.yml # add --reboot to reboot when done
# Build, push, and sign in one shot:
BB_USERNAME=ckoch BB_PASSWORD=<gitea-PAT-with-write:package> \
bluebuild build --push --retry-push \
--registry git.lazypugs.com --registry-namespace ckoch \
recipes/recipe.yml
``` ```
Tags are pinned in the recipe (`alt-tags: latest, stable-44`), so the pushed refs are the same `switch` builds the image locally, stores it as an oci-archive under `/etc/bluebuild/`, and
no matter where the build runs. Signing happens automatically because `cosign.pub` is in the rebases `rpm-ostree` onto it. First build pulls the multi-GB base — give it time and disk
repo root and the key is available. **Rebuild cadence is manual under this model** — run the (~25 GB free).
build when you want base-image updates rolled in; the OS then picks them up on its normal 3. Reboot. Done — the first-boot service then installs the Flatpak list below (needs a few
update timer. minutes on first login; check with `flatpak list --system`).
### Gitea Actions (optional, needs a runner) Alternatively, `bluebuild generate-iso recipes/recipe.yml` can produce installer media with the
custom image baked in, skipping step 1's stock install.
[.gitea/workflows/build.yml](.gitea/workflows/build.yml) runs the same **BlueBuild CLI** build **Updating later** (pulls the newest Bazzite `stable-44` base plus any recipe changes):
(the `blue-build/github-action` is GitHub-only). It is currently `workflow_dispatch`-only
because no Actions runner is registered on the instance; when one exists, restore the
commented-out `schedule` + `push` triggers in the workflow to make builds fully automatic
(daily rebuilds = true self-updating).
Repo secrets (already configured):
- `SIGNING_SECRET` — contents of `cosign.key` (generated with an empty password; the public half
is committed as `cosign.pub`). The CLI signs automatically on push when both are present.
- `REGISTRY_TOKEN` — a Gitea personal access token with **write:package** scope, used to push to
the registry. Gitea's automatic per-job token *cannot* publish packages (documented Gitea
limitation), hence the PAT. **Currently seeded with a temporary token — before revoking it,
create a durable PAT (Settings → Applications → Generate token, `write:package`) and update
the secret** (repo Settings → Actions → Secrets, or
`PUT /api/v1/repos/ckoch/main-desktop/actions/secrets/REGISTRY_TOKEN`).
Runner requirements when you set one up (act_runner):
- **Privileged job containers must be allowed** — the build runs buildah inside the job
container (`ghcr.io/blue-build/cli`). In the runner's `config.yaml`:
`container: { privileged: true }`. Without it the build fails at the buildah stage.
- Internet access (pulls the multi-GB Bazzite base from ghcr.io) and ~25+ GB free scratch disk.
## Rebasing the desktop onto this image
From the existing Bazzite install, rebase in two steps — first unsigned (this installs the image,
which contains the signing policy and public key), then signed:
```bash ```bash
# Step 1: unsigned rebase, then reboot cd main-desktop && git pull && bluebuild update recipes/recipe.yml --reboot
rpm-ostree rebase ostree-unverified-registry:git.lazypugs.com/ckoch/main-desktop:latest
systemctl reboot
# Step 2: switch to the signed ref, then reboot
rpm-ostree rebase ostree-image-signed:docker://git.lazypugs.com/ckoch/main-desktop:latest
systemctl reboot
``` ```
After step 2, every update is signature-verified. `:latest` is fine here — the Fedora pin lives
in the recipe, so `latest` never crosses a major until the recipe says so.
Roll back anytime with `rpm-ostree rollback` (or pick the previous deployment in the boot menu). Roll back anytime with `rpm-ostree rollback` (or pick the previous deployment in the boot menu).
Notes for a self-hosted registry: Note on signing: local oci-archive rebases are inherently unsigned (`ostree-unverified-image`),
so cosign doesn't apply here. The recipe keeps the `signing` module and `cosign.pub` so that
- The repo/package is public, so anonymous pulls work and the desktop needs no auth config. If publishing signed images to a registry later is just a `bluebuild build --push` away.
you ever make it private, the *host* needs credentials in `/etc/ostree/auth.json`
(containers-auth.json format) — `podman login` alone does not cover OS updates.
- Updates only flow while git.lazypugs.com is reachable; if the box is down, updates pause and
retry later. The running system is unaffected.
## Install-time checklist ## Install-time checklist
1. **Driver sanity:** `nvidia-smi` reports driver **≥ 580** (currently 610.x) and the RTX 5090 is 1. **Driver sanity:** `nvidia-smi` reports driver **≥ 580** (currently 610.x) and the RTX 5090 is
listed. Confirm open kernel modules: `modinfo -F license nvidia` should say `Dual MIT/GPL` listed. Confirm open kernel modules: `modinfo -F license nvidia` should say `Dual MIT/GPL`
(the proprietary module says `NVIDIA`). Blackwell only works on the open modules. (the proprietary module says `NVIDIA`). Blackwell only works on the open modules.
2. **Signature:** verify the published image against the committed public key: 2. **Deployment:** `rpm-ostree status` shows the booted deployment is the local build
(`ostree-unverified-image:oci-archive:/etc/bluebuild/...`) with the expected base version.
```bash 3. **Flatpaks:** all installed (`flatpak list --system` — 19 apps from the list below).
cosign verify --key cosign.pub git.lazypugs.com/ckoch/main-desktop:latest 4. **Docker group:** run `ujust dx-group` (adds you to `docker` and friends), then log out/in.
``` 5. **GPU in containers:** run the one-liner test in the next section.
3. **Deployment:** `rpm-ostree status` shows the booted deployment is
`ostree-image-signed:docker://git.lazypugs.com/ckoch/main-desktop` and the base version
matches a recent build.
4. **Flatpaks:** the first-boot service installs the whole list below; give it a few minutes on
first login (it notifies when done). Check with `flatpak list --system`.
5. **Docker group:** run `ujust dx-group` (adds you to `docker` and friends), then log out/in.
6. **GPU in containers:** see the section below — run the one-liner test.
## GPU in containers (CUDA on the 5090) ## GPU in containers (CUDA on the 5090)
@@ -212,13 +157,14 @@ no local hypervisor tooling in this image.
## Maintenance ## Maintenance
- **Change the Flatpak list / packages:** edit [recipes/recipe.yml](recipes/recipe.yml), push, - **Change the Flatpak list / packages:** edit [recipes/recipe.yml](recipes/recipe.yml), commit,
rebuild (see "Building & publishing"), and the next OS update picks it up. (Removing an app and re-run `bluebuild switch`. (Removing an app from the list does not uninstall it from the
from the list does not uninstall it from the machine; `flatpak uninstall` it once by hand.) machine; `flatpak uninstall` it once by hand.)
- **Jump Fedora majors:** change `image-version: stable-44` → `stable-45` in the recipe when - **Jump Fedora majors:** change `image-version: stable-44` → `stable-45` in the recipe when
ready, rebuild, then update normally. ready, then `bluebuild switch`.
- **Pull in base-image updates:** just rebuild — the base tag is `stable-44`, so each rebuild - **Reclaim build disk:** `bluebuild prune` cleans build caches.
picks up the newest Bazzite build within Fedora 44. - **Publishing later:** if this ever moves to a registry + signed rebases, the pieces are in
- **Key hygiene:** `cosign.key` is git-ignored and lives only on the workstation + in the place — `cosign.pub` is committed, the signing module is in the recipe, and
`SIGNING_SECRET` secret — keep a backup (password manager). Losing it means generating a new `bluebuild build --push --registry <host> --registry-namespace <ns>` does the rest (note:
pair, updating the secret, and re-doing the two-step rebase to re-establish trust. as of CLI v0.9.36, local `build` runs bake the signing policy for `localhost` — a registry
push should be done from CI or with a `bluebuild generate`-based two-step).
+4 -2
View File
@@ -46,6 +46,8 @@ modules:
- org.godotengine.Godot - org.godotengine.Godot
- org.videolan.VLC - org.videolan.VLC
# Installs the cosign public key + container signature-verification policy # Installs the cosign public key + container signature-verification policy.
# so `bootc`/`rpm-ostree` signed rebases work. Requires cosign.pub at repo root. # Unused in the current build-locally model (`bluebuild switch` rebases from a
# local oci-archive, unsigned) — kept so publishing to a registry later only
# requires a push, not a recipe change. Requires cosign.pub at repo root.
- type: signing - type: signing