diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml new file mode 100644 index 0000000..eb8b5ae --- /dev/null +++ b/.gitea/workflows/build.yml @@ -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 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 01c642b..0000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -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 }} diff --git a/README.md b/README.md index 498d333..ac6dd74 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ # main-desktop 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 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 — 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). -2. Generate the signing keypair in the repo root (**press Enter for an empty password** — the - build action cannot use an encrypted key): +The workflow lives at [.gitea/workflows/build.yml](.gitea/workflows/build.yml) and runs the +**BlueBuild CLI** directly (the `blue-build/github-action` is GitHub-only). It builds on push, +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 - cosign generate-key-pair - ``` +Repo secrets (already configured): -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 - gh secret set SIGNING_SECRET < cosign.key - git add cosign.pub && git commit -m "Add cosign public key" && git push - ``` +Runner requirements (act_runner on the instance): - `cosign.key` is git-ignored — never commit it. -4. The workflow (`.github/workflows/build.yml`) now builds on every push, on manual dispatch, and - daily at 06:00 UTC (so base-image updates flow through automatically — this is what makes the - image self-updating). Images publish to `ghcr.io//main-desktop`. -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. +- **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. + +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 @@ -51,11 +58,11 @@ which contains the signing policy and public key), then signed: ```bash # Step 1: unsigned rebase, then reboot -rpm-ostree rebase ostree-unverified-registry:ghcr.io//main-desktop:latest +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://ghcr.io//main-desktop:latest +rpm-ostree rebase ostree-image-signed:docker://git.lazypugs.com/ckoch/main-desktop:latest 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). +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 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: ```bash - cosign verify --key cosign.pub ghcr.io//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 - `ostree-image-signed:docker://ghcr.io//main-desktop` and the base version + `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`. @@ -181,5 +196,8 @@ no local hypervisor tooling in this image. machine; `flatpak uninstall` it once by hand.) - **Jump Fedora majors:** change `image-version: stable-44` → `stable-45` in the recipe when ready, push, then update normally. -- **Check build status:** the Actions tab; builds also run nightly, so a broken base shows up - there before it reaches the machine. +- **Check build status:** the repo's Actions tab; builds also run nightly, so a broken base + 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.