From 5d46070561acbc59d9b80381299d971d7e044d88 Mon Sep 17 00:00:00 2001 From: ckoch Date: Wed, 29 Jul 2026 12:05:12 -0400 Subject: [PATCH] Custom Bazzite DX NVIDIA (open) image: BlueBuild recipe, GHCR workflow, cosign setup Co-Authored-By: Claude Fable 5 --- .github/workflows/build.yml | 37 ++++++++ .gitignore | 2 + README.md | 185 ++++++++++++++++++++++++++++++++++++ recipes/recipe.yml | 46 +++++++++ 4 files changed, 270 insertions(+) create mode 100644 .github/workflows/build.yml create mode 100644 .gitignore create mode 100644 README.md create mode 100644 recipes/recipe.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..01c642b --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,37 @@ +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/.gitignore b/.gitignore new file mode 100644 index 0000000..91b197a --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# NEVER commit the cosign private key — it goes in the SIGNING_SECRET GitHub secret. +cosign.key diff --git a/README.md b/README.md new file mode 100644 index 0000000..498d333 --- /dev/null +++ b/README.md @@ -0,0 +1,185 @@ +# 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. + +- **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 + is exactly what the RTX 5090 (Blackwell) requires. Current stable ships driver **610.x** (open). +- **Pinned** to Fedora 44 via `image-version: stable-44` in + [recipes/recipe.yml](recipes/recipe.yml). No surprise major upgrades; bump to `stable-45` + deliberately. +- **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 + 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 + Flatpak set. That's it — by design. + +Why BlueBuild instead of the raw Containerfile template: with the container toolkit already +in-image, the whole custom layer is "install these Flatpaks on first boot + trust my signature", +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) + +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): + + ```bash + cosign generate-key-pair + ``` + +3. Store the private key as an Actions secret and commit only the public half: + + ```bash + 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. +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. + +## 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 +# Step 1: unsigned rebase, then reboot +rpm-ostree rebase ostree-unverified-registry:ghcr.io//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 +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). + +## Install-time checklist + +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` + (the proprietary module says `NVIDIA`). Blackwell only works on the open modules. +2. **Signature:** verify the published image against the committed public key: + + ```bash + cosign verify --key cosign.pub ghcr.io//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 + 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) + +`nvidia-container-toolkit` is already in the image and a boot service generates the CDI spec +automatically. CDI is the shipped mechanism: + +```bash +# Podman — works out of the box: +podman run --rm --device nvidia.com/gpu=all docker.io/nvidia/cuda:12.8.0-base-ubuntu24.04 nvidia-smi + +# Docker — CDI device syntax also works on current Docker CE: +docker run --rm --device nvidia.com/gpu=all nvidia/cuda:12.8.0-base-ubuntu24.04 nvidia-smi +``` + +If you want the classic `docker run --gpus all` syntax, that needs a one-time runtime hookup +(this writes to `/etc/docker/daemon.json`, which is mutable and survives updates): + +```bash +sudo nvidia-ctk runtime configure --runtime=docker +sudo systemctl restart docker +``` + +## First-boot Flatpaks + +Installed system-wide from Flathub by the `default-flatpaks` module. All IDs verified against +Flathub (2026-07). + +**Official / verified listings:** Bambu Studio, Bitwarden, Discord, Firefox, Thunderbird, +FreeCAD, GIMP, HandBrake, LibreOffice, Moonlight, PCSX2 (`net.pcsx2.PCSX2` — the official +build), Podman Desktop, Warzone 2100, XIVLauncher (`dev.goats.xivlauncher`). + +**Community / unverified listings — know what you're running:** + +| App | ID | Note | +|---|---|---| +| GitHub Desktop | `io.github.shiftey.Desktop` | Community Linux fork; the "shiftey" spelling is the real ID | +| MakeMKV | `com.makemkv.MakeMKV` | Community packaging of the proprietary app | +| GeForce NOW | `io.github.hmlendea.geforcenow-electron` | **Unofficial** Electron wrapper, not an NVIDIA product | +| VLC | `org.videolan.VLC` | No verified badge on Flathub (still the VideoLAN-maintained build) | +| Godot | `org.godotengine.Godot` | No verified badge on Flathub | + +**GeForce NOW on the 32:9 monitor:** the service only streams 16:9 (and some 21:9) aspect +ratios, so expect pillarboxing at 5120x1440 — that's a service limitation, not a config problem. + +## Post-install steps (manual by design — don't automate these) + +### PIA VPN — native WireGuard, not the app + +The PIA desktop app installs into `/usr` and breaks on the read-only filesystem. Skip it: + +1. Log in at PIA's [OpenVPN config generator](https://www.privateinternetaccess.com/pages/ovpn-config-generator) + (or generate a WireGuard config via their API/support flow) and download configs for your + preferred regions. +2. KDE **System Settings → Network → Connections → Add (+) → Import VPN connection…**, pick the + downloaded file, and enter your PIA credentials. +3. This is plain NetworkManager — it lives in `/etc`, survives every update, and gets a + connect/disconnect toggle in the system tray. + +### Microsoft Teams — PWA via Edge + +No native Linux client. Install **Microsoft Edge specifically** (corporate conditional +access / Intune compatibility): `flatpak install flathub com.microsoft.Edge`, sign in to +, then menu → **Apps → Install this site as an app**. + +### Claude desktop — PWA + +No official Linux app. Open in Edge (or any Chromium browser) and use +**Install this site as an app** the same way. + +### Proton-GE + +Already handled by Bazzite: use the preinstalled **ProtonUp-Qt** GUI (or browse `ujust` — run +`ujust` with no arguments to list recipes). Don't install anything extra. + +## Node / toolchains — not in the image + +Node, npm, etc. are deliberately **not layered**. Use per-project versions instead: + +```bash +brew install mise # Homebrew ships in the base image +mise use node@22 # per-project .mise.toml; `mise use -g node@22` for a global default +``` + +(Plain `brew install node` also works if you just want one global Node.) + +## Display note + +The 49" 32:9 (5120x1440) ultrawide needs nothing at the image layer. Post-install: set scale and +refresh rate in KDE System Settings → Display; KDE's window tiling (Meta+drag, or a tiler like +Polonium) is worth setting up at this width. VMs are remote via Remmina — there is intentionally +no local hypervisor tooling in this image. + +## Maintenance + +- **Change the Flatpak list / packages:** edit [recipes/recipe.yml](recipes/recipe.yml), push, + and the next update picks it up. (Removing an app from the list does not uninstall it from the + 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. diff --git a/recipes/recipe.yml b/recipes/recipe.yml new file mode 100644 index 0000000..8b4f5c1 --- /dev/null +++ b/recipes/recipe.yml @@ -0,0 +1,46 @@ +# yaml-language-server: $schema=https://schema.blue-build.org/recipe-v1.json +# Custom desktop image: Bazzite DX (KDE) + NVIDIA open kernel modules (RTX 5090 / Blackwell). +# +# The base image already ships: Steam + gaming stack, Flatpak, Docker CE + CLI, +# Podman, VS Code, Homebrew, the NVIDIA 610+ open driver, AND nvidia-container-toolkit +# with automatic CDI generation (ublue-nvctk-cdi.service). Nothing GPU/dev-related +# needs to be layered here — this recipe only adds signature-verification policy +# and the declarative first-boot Flatpak set. +name: main-desktop +description: Bazzite DX NVIDIA (open, KDE) with my first-boot Flatpak set, signed. + +base-image: ghcr.io/ublue-os/bazzite-dx-nvidia +# Pinned to Fedora 44. Bump deliberately (stable-45, ...) when ready to jump majors. +image-version: stable-44 + +modules: + - type: default-flatpaks + configurations: + - scope: system + notify: true + install: + # -- Official / verified Flathub listings -- + - com.bambulab.BambuStudio + - com.bitwarden.desktop + - com.discordapp.Discord + - com.moonlight_stream.Moonlight + - dev.goats.xivlauncher + - fr.handbrake.ghb + - io.podman_desktop.PodmanDesktop + - net.pcsx2.PCSX2 # official PCSX2 build — do not swap for community forks + - net.wz2100.wz2100 + - org.freecad.FreeCAD # current ID; old org.freecadweb.FreeCAD is dead + - org.gimp.GIMP + - org.libreoffice.LibreOffice + - org.mozilla.Thunderbird + - org.mozilla.firefox + # -- Community / unverified listings (see README) -- + - com.makemkv.MakeMKV + - io.github.hmlendea.geforcenow-electron # unofficial wrapper; 16:9/21:9 streams only + - io.github.shiftey.Desktop # GitHub Desktop Linux fork; "shiftey" is correct + - org.godotengine.Godot + - org.videolan.VLC + + # Installs the cosign public key + container signature-verification policy + # so `bootc`/`rpm-ostree` signed rebases work. Requires cosign.pub at repo root. + - type: signing