Move to Gitea: Gitea Actions workflow (BlueBuild CLI), registry git.lazypugs.com
bluebuild / Build Custom Image (push) Has been cancelled

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-29 12:29:48 -04:00
co-authored by Claude Fable 5
parent 0562266a7e
commit a54cb8df30
3 changed files with 84 additions and 62 deletions
+41
View File
@@ -0,0 +1,41 @@
# 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.
name: bluebuild
on:
schedule:
- cron: "00 06 * * *" # daily rebuild pulls in base-image updates (self-updating)
push:
branches:
- main
paths-ignore:
- "**.md"
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
-37
View File
@@ -1,37 +0,0 @@
name: bluebuild
on:
schedule:
- cron: "00 06 * * *" # daily rebuild pulls in base-image updates (self-updating)
push:
branches:
- main
paths-ignore:
- "**.md"
pull_request:
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
bluebuild:
name: Build Custom Image
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
id-token: write
strategy:
fail-fast: false
matrix:
recipe:
- recipe.yml
steps:
- name: Build Custom Image
uses: blue-build/github-action@v1.12.0
with:
recipe: ${{ matrix.recipe }}
cosign_private_key: ${{ secrets.SIGNING_SECRET }}
registry_token: ${{ github.token }}
pull_request_number: ${{ github.event.number }}
+43 -25
View File
@@ -1,7 +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, built with
[BlueBuild](https://blue-build.org), published to GHCR by GitHub Actions, and signed with cosign. [BlueBuild](https://blue-build.org), published to the container registry on my Gitea instance
(**git.lazypugs.com**) by Gitea Actions, and signed with cosign.
- **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
@@ -20,29 +21,35 @@ in-image, the whole custom layer is "install these Flatpaks on first boot + trus
which the `default-flatpaks` and `signing` modules express declaratively in ~40 lines of YAML — which the `default-flatpaks` and `signing` modules express declaratively in ~40 lines of YAML —
no hand-rolled first-boot systemd unit needed. no hand-rolled first-boot systemd unit needed.
## One-time setup (before first push) ## CI setup (Gitea Actions)
1. Create a GitHub repo and push this directory to it (`main` branch). The workflow lives at [.gitea/workflows/build.yml](.gitea/workflows/build.yml) and runs the
2. Generate the signing keypair in the repo root (**press Enter for an empty password** — the **BlueBuild CLI** directly (the `blue-build/github-action` is GitHub-only). It builds on push,
build action cannot use an encrypted key): manual dispatch, and daily at 06:00 UTC so base-image updates flow through automatically — that
schedule is what makes the image self-updating.
```bash Repo secrets (already configured):
cosign generate-key-pair
```
3. Store the private key as an Actions secret and commit only the public half: - `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`).
```bash Runner requirements (act_runner on the instance):
gh secret set SIGNING_SECRET < cosign.key
git add cosign.pub && git commit -m "Add cosign public key" && git push
```
`cosign.key` is git-ignored — never commit it. - **Privileged job containers must be allowed** — the build runs buildah inside the job
4. The workflow (`.github/workflows/build.yml`) now builds on every push, on manual dispatch, and container (`ghcr.io/blue-build/cli`). In the runner's `config.yaml`:
daily at 06:00 UTC (so base-image updates flow through automatically — this is what makes the `container: { privileged: true }`. Without it the build fails at the buildah stage.
image self-updating). Images publish to `ghcr.io/<your-github-user>/main-desktop`. - Internet access (pulls the multi-GB Bazzite base from ghcr.io) and ~25+ GB free scratch disk.
5. If the GHCR package ends up private, make it public in the package settings (Packages →
main-desktop → Package settings → Change visibility), or rebasing will fail to pull. Known first-run watch-items (verified docs, but no public precedent for this exact stack):
cosign signature push to a Gitea registry and the CLI's tag generation under act_runner's
GitHub-compat env are both expected to work but unproven in the wild — if the first run fails
at signing or tagging, that's where to look, not the recipe.
## Rebasing the desktop onto this image ## Rebasing the desktop onto this image
@@ -51,11 +58,11 @@ which contains the signing policy and public key), then signed:
```bash ```bash
# Step 1: unsigned rebase, then reboot # Step 1: unsigned rebase, then reboot
rpm-ostree rebase ostree-unverified-registry:ghcr.io/<your-github-user>/main-desktop:latest rpm-ostree rebase ostree-unverified-registry:git.lazypugs.com/ckoch/main-desktop:latest
systemctl reboot systemctl reboot
# Step 2: switch to the signed ref, then reboot # Step 2: switch to the signed ref, then reboot
rpm-ostree rebase ostree-image-signed:docker://ghcr.io/<your-github-user>/main-desktop:latest rpm-ostree rebase ostree-image-signed:docker://git.lazypugs.com/ckoch/main-desktop:latest
systemctl reboot systemctl reboot
``` ```
@@ -64,6 +71,14 @@ 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:
- The repo/package is public, so anonymous pulls work and the desktop needs no auth config. If
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
@@ -72,11 +87,11 @@ Roll back anytime with `rpm-ostree rollback` (or pick the previous deployment in
2. **Signature:** verify the published image against the committed public key: 2. **Signature:** verify the published image against the committed public key:
```bash ```bash
cosign verify --key cosign.pub ghcr.io/<your-github-user>/main-desktop:latest cosign verify --key cosign.pub git.lazypugs.com/ckoch/main-desktop:latest
``` ```
3. **Deployment:** `rpm-ostree status` shows the booted deployment is 3. **Deployment:** `rpm-ostree status` shows the booted deployment is
`ostree-image-signed:docker://ghcr.io/<your-github-user>/main-desktop` and the base version `ostree-image-signed:docker://git.lazypugs.com/ckoch/main-desktop` and the base version
matches a recent build. matches a recent build.
4. **Flatpaks:** the first-boot service installs the whole list below; give it a few minutes on 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`. first login (it notifies when done). Check with `flatpak list --system`.
@@ -181,5 +196,8 @@ no local hypervisor tooling in this image.
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, push, then update normally. ready, push, then update normally.
- **Check build status:** the Actions tab; builds also run nightly, so a broken base shows up - **Check build status:** the repo's Actions tab; builds also run nightly, so a broken base
there before it reaches the machine. shows up there before it reaches the machine.
- **Key hygiene:** `cosign.key` is git-ignored and lives only on the workstation + in the
`SIGNING_SECRET` secret — keep a backup (password manager). Losing it means generating a new
pair, updating the secret, and re-doing the two-step rebase to re-establish trust.