From bc8551d66572350f5141d34f50eff4153f617da9 Mon Sep 17 00:00:00 2001 From: Ben Smith Date: Sat, 18 Apr 2026 10:06:28 -0700 Subject: [PATCH 1/6] Update image signing --- .github/workflows/build-hypervisor.yml | 44 +++++++++++------------ .github/workflows/build-minimal-bootc.yml | 17 ++++----- cosign.pub | 4 +++ fedora-bootc-minimal.Containerfile | 28 +-------------- hypervisor.Containerfile | 1 + policy-hypervisor.json.template | 19 +--------- policy-minimal.json.template | 7 +--- 7 files changed, 38 insertions(+), 82 deletions(-) create mode 100644 cosign.pub diff --git a/.github/workflows/build-hypervisor.yml b/.github/workflows/build-hypervisor.yml index 8f21777..7bfa852 100644 --- a/.github/workflows/build-hypervisor.yml +++ b/.github/workflows/build-hypervisor.yml @@ -42,19 +42,9 @@ jobs: - name: Generate policy.json from template run: | - # owner needs to be lowercase, but workflow ref needs to preserve case OWNER=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]') - REPO_NAME="${{ github.event.repository.name }}" - - # Generate the minimal workflow ref (predictable pattern) - MINIMAL_WORKFLOW_REF="${{ github.repository }}/.github/workflows/build-minimal-bootc.yml@${{ github.ref }}" - - # Use the current workflow ref for hypervisor - HYPERVISOR_WORKFLOW_REF="${{ github.workflow_ref }}" sed -e "s|__REGISTRY_NAMESPACE__|${OWNER}|g" \ - -e "s|__MINIMAL_WORKFLOW_REF__|${MINIMAL_WORKFLOW_REF}|g" \ - -e "s|__HYPERVISOR_WORKFLOW_REF__|${HYPERVISOR_WORKFLOW_REF}|g" \ policy-hypervisor.json.template > policy.json echo "Generated policy.json:" @@ -99,6 +89,11 @@ jobs: run: | echo "${{ secrets.GITHUB_TOKEN }}" | sudo podman login ghcr.io -u ${{ github.actor }} --password-stdin + - name: Login to GitHub Container Registry for cosign + if: steps.should_build.outputs.run == 'true' + run: | + echo "${{ secrets.GITHUB_TOKEN }}" | cosign login ghcr.io -u ${{ github.actor }} --password-stdin + - name: Determine variants to build if: steps.should_build.outputs.run == 'true' id: variants @@ -189,18 +184,15 @@ jobs: echo "Remaining localhost images:" sudo podman images | grep hypervisor-bootc - - name: Login to GitHub Container Registry for cosign - if: steps.should_build.outputs.run == 'true' - run: | - echo "${{ secrets.GITHUB_TOKEN }}" | cosign login ghcr.io -u ${{ github.actor }} --password-stdin - - name: Sign base hypervisor images id: base_complete if: steps.should_build.outputs.run == 'true' + env: + COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_SECRET }} run: | OWNER=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]') - cosign sign --yes ghcr.io/${OWNER}/hypervisor-bootc:${{ env.base_tag }} - cosign sign --yes ghcr.io/${OWNER}/hypervisor-bootc:latest + cosign sign -y --key env://COSIGN_PRIVATE_KEY ghcr.io/${OWNER}/hypervisor-bootc:${{ env.base_tag }} + cosign sign -y --key env://COSIGN_PRIVATE_KEY ghcr.io/${OWNER}/hypervisor-bootc:latest - name: Build AMD variant continue-on-error: true @@ -260,10 +252,12 @@ jobs: - name: Sign AMD images continue-on-error: true if: steps.should_build.outputs.run == 'true' && steps.base_complete.outcome == 'success' && (contains(steps.variants.outputs.variants, 'amd') || contains(steps.variants.outputs.variants, 'all')) + env: + COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_SECRET }} run: | OWNER=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]') - cosign sign --yes ghcr.io/${OWNER}/hypervisor-amd:${{ env.base_tag }} - cosign sign --yes ghcr.io/${OWNER}/hypervisor-amd:latest + cosign sign -y --key env://COSIGN_PRIVATE_KEY ghcr.io/${OWNER}/hypervisor-amd:${{ env.base_tag }} + cosign sign -y --key env://COSIGN_PRIVATE_KEY ghcr.io/${OWNER}/hypervisor-amd:latest - name: Build NVIDIA negativo17 variant continue-on-error: true @@ -322,10 +316,12 @@ jobs: - name: Sign NVIDIA negativo17 images continue-on-error: true if: steps.should_build.outputs.run == 'true' && steps.base_complete.outcome == 'success' && (contains(steps.variants.outputs.variants, 'nvidia-negativo17') || contains(steps.variants.outputs.variants, 'all')) + env: + COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_SECRET }} run: | OWNER=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]') - cosign sign --yes ghcr.io/${OWNER}/hypervisor-nvidia:negativo17-${{ env.base_tag }} - cosign sign --yes ghcr.io/${OWNER}/hypervisor-nvidia:negativo17 + cosign sign -y --key env://COSIGN_PRIVATE_KEY ghcr.io/${OWNER}/hypervisor-nvidia:negativo17-${{ env.base_tag }} + cosign sign -y --key env://COSIGN_PRIVATE_KEY ghcr.io/${OWNER}/hypervisor-nvidia:negativo17 - name: Build NVIDIA RPMFusion variant continue-on-error: true @@ -387,10 +383,12 @@ jobs: - name: Sign NVIDIA RPMFusion images continue-on-error: true if: steps.should_build.outputs.run == 'true' && steps.base_complete.outcome == 'success' && (contains(steps.variants.outputs.variants, 'nvidia-rpmfusion') || contains(steps.variants.outputs.variants, 'all')) + env: + COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_SECRET }} run: | OWNER=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]') - cosign sign --yes ghcr.io/${OWNER}/hypervisor-nvidia:rpmfusion-${{ env.base_tag }} - cosign sign --yes ghcr.io/${OWNER}/hypervisor-nvidia:rpmfusion + cosign sign -y --key env://COSIGN_PRIVATE_KEY ghcr.io/${OWNER}/hypervisor-nvidia:rpmfusion-${{ env.base_tag }} + cosign sign -y --key env://COSIGN_PRIVATE_KEY ghcr.io/${OWNER}/hypervisor-nvidia:rpmfusion - name: Summary if: steps.should_build.outputs.run == 'true' diff --git a/.github/workflows/build-minimal-bootc.yml b/.github/workflows/build-minimal-bootc.yml index 29bdb10..af18490 100644 --- a/.github/workflows/build-minimal-bootc.yml +++ b/.github/workflows/build-minimal-bootc.yml @@ -68,13 +68,11 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 - - name: Generate /etc/containers/policy.json for github keyless signing - # owner needs to be lowercase, but workflow ref needs to preserve case + - name: Generate /etc/containers/policy.json run: | OWNER=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]') sed -e "s|__REGISTRY_NAMESPACE__|${OWNER}|g" \ - -e "s|__MINIMAL_WORKFLOW_REF__|${{ github.workflow_ref }}|g" \ policy-minimal.json.template > policy.json echo "Generated policy.json for minimal:" @@ -92,10 +90,11 @@ jobs: git log -1 echo "Using local podman 4-compatible Containerfile with upstream manifests" - - name: Copy policy.json into build context + - name: Copy policy.json and cosign.pub into build context run: | cp policy.json manifests/policy.json - echo "Copied policy.json to manifests/ directory" + cp cosign.pub manifests/cosign.pub + echo "Copied policy.json and cosign.pub to manifests/ directory" - name: Build minimal bootc container run: | @@ -177,15 +176,17 @@ jobs: echo "${{ secrets.GITHUB_TOKEN }}" | cosign login ghcr.io -u ${{ github.actor }} --password-stdin - name: Sign images with cosign + env: + COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_SECRET }} run: | OWNER=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]') - cosign sign --yes ghcr.io/${OWNER}/fedora-bootc-minimal:${{ steps.set_version.outputs.version }}-${{ steps.timestamp.outputs.tag }} + cosign sign -y --key env://COSIGN_PRIVATE_KEY ghcr.io/${OWNER}/fedora-bootc-minimal:${{ steps.set_version.outputs.version }}-${{ steps.timestamp.outputs.tag }} - cosign sign --yes ghcr.io/${OWNER}/fedora-bootc-minimal:${{ steps.set_version.outputs.version }} + cosign sign -y --key env://COSIGN_PRIVATE_KEY ghcr.io/${OWNER}/fedora-bootc-minimal:${{ steps.set_version.outputs.version }} if [ "${{ steps.set_version.outputs.version }}" == "43" ]; then - cosign sign --yes ghcr.io/${OWNER}/fedora-bootc-minimal:latest + cosign sign -y --key env://COSIGN_PRIVATE_KEY ghcr.io/${OWNER}/fedora-bootc-minimal:latest fi - name: Cleanup local images diff --git a/cosign.pub b/cosign.pub new file mode 100644 index 0000000..a2e34e2 --- /dev/null +++ b/cosign.pub @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1tsMEtSTv5qyeHuDzrsH1OKi5+9q +pTwpSBwtGluq8NyAkoNxCJHR0TuL9UbdMjHd37mk6AI4bVyPskxbeD6WIg== +-----END PUBLIC KEY----- diff --git a/fedora-bootc-minimal.Containerfile b/fedora-bootc-minimal.Containerfile index c668ed3..6df991c 100644 --- a/fedora-bootc-minimal.Containerfile +++ b/fedora-bootc-minimal.Containerfile @@ -27,35 +27,9 @@ RUN --mount=type=cache,id=bootc-base-image-cache,target=/cache sh -c 'set -xeuo /usr/libexec/bootc-base-imagectl list >/dev/null && \ /usr/libexec/bootc-base-imagectl build-rootfs --cachedir=/cache --reinject --manifest=${MANIFEST} /repos /target-rootfs' -# get the keys for github keyless signing -FROM alpine AS keyless-keys - -RUN apk add curl jq openssl - -# writes to ~/.sigstore/root/ -RUN curl -o cosign -L "https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64" && \ - chmod +x cosign && \ - ./cosign initialize - -# Extract the base64-encoded cosign public key from trusted_root.json, -# decode it from base64, convert from DER to PEM format -RUN mkdir -p /etc/pki/rekor && \ - cat ~/.sigstore/root/tuf-repo-cdn.sigstore.dev/targets/trusted_root.json | \ - jq -r '.tlogs[0].publicKey.rawBytes' | \ - base64 -d > rekor_temp.pub && \ - openssl pkey -pubin -inform DER -in rekor_temp.pub -outform PEM -out /etc/pki/rekor/rekor.pub - -# same with the fulcio cert -RUN mkdir -p /etc/pki/fulcio && \ - cat ~/.sigstore/root/tuf-repo-cdn.sigstore.dev/targets/trusted_root.json | \ - jq -r '.certificateAuthorities[0].certChain.certificates[0].rawBytes' | \ - base64 -d > fulcio_temp.crt && \ - openssl x509 -inform DER -in fulcio_temp.crt -outform PEM -out /etc/pki/fulcio/fulcio.crt.pem - - FROM scratch COPY --from=builder /target-rootfs/ / -COPY --from=keyless-keys /etc/pki /etc/pki +COPY cosign.pub /etc/pki/containers/cosign.pub COPY policy.json /etc/containers/policy.json # Bootc labels and metadata diff --git a/hypervisor.Containerfile b/hypervisor.Containerfile index 6fb42b3..af609dc 100644 --- a/hypervisor.Containerfile +++ b/hypervisor.Containerfile @@ -21,6 +21,7 @@ LABEL org.opencontainers.image.title="Hypervisor Bootc Image" LABEL org.opencontainers.image.description="Bootc-based hypervisor with podman/lxc/libvirt/QEMU/KVM" COPY policy.json /etc/containers/policy.json +COPY cosign.pub /etc/pki/containers/cosign.pub COPY security/pwquality-no-dictionary.conf /etc/security/pwquality.conf.d/no-dictionary.conf # Break ostree hardlinks on rpmdb: fuse-overlayfs preserves hardlinks during diff --git a/policy-hypervisor.json.template b/policy-hypervisor.json.template index 91fbedb..07a85ef 100644 --- a/policy-hypervisor.json.template +++ b/policy-hypervisor.json.template @@ -5,24 +5,7 @@ "ghcr.io/__REGISTRY_NAMESPACE__/": [ { "type": "sigstoreSigned", - "fulcio": { - "caPath": "/etc/pki/fulcio/fulcio.crt.pem", - "oidcIssuer": "https://token.actions.githubusercontent.com", - "subjectEmail": "https://github.com/__MINIMAL_WORKFLOW_REF__" - }, - "rekorPublicKeyPath": "/etc/pki/rekor/rekor.pub", - "signedIdentity": { - "type": "matchRepository" - } - }, - { - "type": "sigstoreSigned", - "fulcio": { - "caPath": "/etc/pki/fulcio/fulcio.crt.pem", - "oidcIssuer": "https://token.actions.githubusercontent.com", - "subjectEmail": "https://github.com/__HYPERVISOR_WORKFLOW_REF__" - }, - "rekorPublicKeyPath": "/etc/pki/rekor/rekor.pub", + "keyPath": "/etc/pki/containers/cosign.pub", "signedIdentity": { "type": "matchRepository" } diff --git a/policy-minimal.json.template b/policy-minimal.json.template index 3055af2..6750895 100644 --- a/policy-minimal.json.template +++ b/policy-minimal.json.template @@ -5,12 +5,7 @@ "ghcr.io/__REGISTRY_NAMESPACE__/": [ { "type": "sigstoreSigned", - "fulcio": { - "caPath": "/etc/pki/fulcio/fulcio.crt.pem", - "oidcIssuer": "https://token.actions.githubusercontent.com", - "subjectEmail": "https://github.com/__MINIMAL_WORKFLOW_REF__" - }, - "rekorPublicKeyPath": "/etc/pki/rekor/rekor.pub", + "keyPath": "/etc/pki/containers/cosign.pub", "signedIdentity": { "type": "matchRepository" } From f5ba057f10c5be1a714bddc532ca8e332bce2cc8 Mon Sep 17 00:00:00 2001 From: Ben Smith Date: Sat, 18 Apr 2026 11:47:21 -0700 Subject: [PATCH 2/6] remove redundant settings from containers that inherit them --- fedora-bootc-minimal.Containerfile | 6 ++++++ hypervisor-amd.Containerfile | 10 ---------- hypervisor-nvidia-negativo17.Containerfile | 10 ---------- hypervisor-nvidia-rpmfusion.Containerfile | 12 +----------- hypervisor.Containerfile | 12 ++---------- 5 files changed, 9 insertions(+), 41 deletions(-) diff --git a/fedora-bootc-minimal.Containerfile b/fedora-bootc-minimal.Containerfile index 6df991c..3a88551 100644 --- a/fedora-bootc-minimal.Containerfile +++ b/fedora-bootc-minimal.Containerfile @@ -34,7 +34,13 @@ COPY policy.json /etc/containers/policy.json # Bootc labels and metadata LABEL containers.bootc 1 +LABEL ostree.bootable 1 LABEL bootc.diskimage-builder quay.io/centos-bootc/bootc-image-builder + +LABEL org.opencontainers.image.title="Minimal Fedora bootc Image" +LABEL org.opencontainers.image.description="Build of Fedora bootc minimal" + ENV container=oci + STOPSIGNAL SIGRTMIN+3 CMD ["/usr/sbin/init"] diff --git a/hypervisor-amd.Containerfile b/hypervisor-amd.Containerfile index 3d232f1..8fed631 100644 --- a/hypervisor-amd.Containerfile +++ b/hypervisor-amd.Containerfile @@ -31,15 +31,5 @@ RUN mkdir -p /etc/cdi && \ # rocm-smi tries to use libdrm_amdgpu.so, this is a workaround to provide it RUN ln -s /usr/lib64/libdrm_amdgpu.so.1 /usr/lib64/libdrm_amdgpu.so -# Define required labels for this bootc image to be recognized as such -LABEL containers.bootc 1 -LABEL ostree.bootable 1 LABEL org.opencontainers.image.title="Hypervisor Bootc Image - AMD GPU" LABEL org.opencontainers.image.description="Bootc-based hypervisor with AMD GPU support (ROCm)" - -# https://pagure.io/fedora-kiwi-descriptions/pull-request/52 -ENV container=oci - -# Optional labels that only apply when running this image as a container. These keep the default entry point running under systemd. -STOPSIGNAL SIGRTMIN+3 -CMD ["/usr/sbin/init"] diff --git a/hypervisor-nvidia-negativo17.Containerfile b/hypervisor-nvidia-negativo17.Containerfile index 34c2195..f1a1056 100644 --- a/hypervisor-nvidia-negativo17.Containerfile +++ b/hypervisor-nvidia-negativo17.Containerfile @@ -26,15 +26,5 @@ RUN mkdir -p /etc/cdi && \ systemctl enable nvidia-cdi-generator.service && \ bootc container lint -# Define required labels for this bootc image to be recognized as such -LABEL containers.bootc 1 -LABEL ostree.bootable 1 LABEL org.opencontainers.image.title="Hypervisor Bootc Image - NVIDIA (negativo17)" LABEL org.opencontainers.image.description="Bootc-based hypervisor with NVIDIA GPU support via negativo17 repository" - -# https://pagure.io/fedora-kiwi-descriptions/pull-request/52 -ENV container=oci - -# Optional labels that only apply when running this image as a container. These keep the default entry point running under systemd. -STOPSIGNAL SIGRTMIN+3 -CMD ["/usr/sbin/init"] diff --git a/hypervisor-nvidia-rpmfusion.Containerfile b/hypervisor-nvidia-rpmfusion.Containerfile index ff92c65..84ad151 100644 --- a/hypervisor-nvidia-rpmfusion.Containerfile +++ b/hypervisor-nvidia-rpmfusion.Containerfile @@ -27,15 +27,5 @@ RUN mkdir -p /etc/cdi && \ systemctl enable nvidia-cdi-generator.service && \ bootc container lint -# Define required labels for this bootc image to be recognized as such -LABEL containers.bootc 1 -LABEL ostree.bootable 1 LABEL org.opencontainers.image.title="Hypervisor Bootc Image - NVIDIA (RPMFusion)" -LABEL org.opencontainers.image.description="Bootc-based hypervisor with NVIDIA GPU support via RPMFusion (driver 580.105.08)" - -# https://pagure.io/fedora-kiwi-descriptions/pull-request/52 -ENV container=oci - -# Optional labels that only apply when running this image as a container. These keep the default entry point running under systemd. -STOPSIGNAL SIGRTMIN+3 -CMD ["/usr/sbin/init"] +LABEL org.opencontainers.image.description="Bootc-based hypervisor with NVIDIA GPU support via RPMFusion" diff --git a/hypervisor.Containerfile b/hypervisor.Containerfile index af609dc..cc322df 100644 --- a/hypervisor.Containerfile +++ b/hypervisor.Containerfile @@ -213,13 +213,5 @@ RUN chmod 0755 /usr/bin/cosy && \ /usr/bin/cosy completion bash > /usr/share/bash-completion/completions/cosy && \ chmod 0644 /usr/share/bash-completion/completions/cosy -# Define required labels for this bootc image to be recognized as such -LABEL containers.bootc 1 -LABEL ostree.bootable 1 - -# https://pagure.io/fedora-kiwi-descriptions/pull-request/52 -ENV container=oci - -# Optional labels that only apply when running this image as a container. These keep the default entry point running under systemd. -STOPSIGNAL SIGRTMIN+3 -CMD ["/usr/sbin/init"] +LABEL org.opencontainers.image.title="Hypervisor bootc Image" +LABEL org.opencontainers.image.description="generic bootc-based hypervisor" From 3b7576c6febcf2be06da04e5877ba8a061c1e9c9 Mon Sep 17 00:00:00 2001 From: Ben Smith Date: Sat, 18 Apr 2026 16:44:05 -0700 Subject: [PATCH 3/6] update build pipelines --- .github/workflows/build-hypervisor.yml | 530 ++++++++++------------ .github/workflows/build-minimal-bootc.yml | 229 ++++------ 2 files changed, 323 insertions(+), 436 deletions(-) diff --git a/.github/workflows/build-hypervisor.yml b/.github/workflows/build-hypervisor.yml index 7bfa852..7c6edc4 100644 --- a/.github/workflows/build-hypervisor.yml +++ b/.github/workflows/build-hypervisor.yml @@ -1,141 +1,110 @@ name: Build Hypervisor Bootc Images on: - # Trigger after minimal build completes successfully workflow_run: workflows: ["Build Fedora Minimal Bootc"] - types: - - completed - branches: - - main + types: [completed] + branches: [main] - # Manual trigger workflow_dispatch: inputs: variants: - description: 'Which variants to build' + description: 'Variants to build (besides base)' required: true - default: 'base' + default: 'all' type: choice options: - - 'base' - 'all' - - 'base,nvidia-rpmfusion' - - 'base,nvidia-negativo17' - - 'base,amd' + - 'none' + - 'amd' + - 'nvidia' + - 'nvidia-negativo17' + - 'nvidia-rpmfusion' + +concurrency: + group: build-hypervisor-${{ github.ref }} + cancel-in-progress: false + +env: + RECHUNKER_IMAGE: quay.io/fedora/fedora-bootc:43 jobs: - build-hypervisor: - runs-on: ubuntu-24.04 # Required by rechunk action for advanced podman features - # Only run if: - # 1. workflow_run: minimal build succeeded - # 2. Other events: allow (manual, schedule, push) + build-base: + # Skip if upstream minimal build failed; allow all other triggers. if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name != 'workflow_run' }} + runs-on: ubuntu-24.04 + timeout-minutes: 120 permissions: contents: read packages: write id-token: write + outputs: + tag: ${{ steps.params.outputs.tag }} + owner: ${{ steps.params.outputs.owner }} steps: - name: Checkout repository uses: actions/checkout@v4 - - name: Generate policy.json from template + - name: Resolve build parameters + id: params + env: + OWNER_RAW: ${{ github.repository_owner }} run: | - OWNER=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]') - - sed -e "s|__REGISTRY_NAMESPACE__|${OWNER}|g" \ - policy-hypervisor.json.template > policy.json + OWNER=$(echo "$OWNER_RAW" | tr '[:upper:]' '[:lower:]') + TAG=$(date +%Y%m%d-%H%M) + { + echo "owner=${OWNER}" + echo "tag=${TAG}" + echo "image_base=ghcr.io/${OWNER}/hypervisor-bootc" + } >> "$GITHUB_OUTPUT" - echo "Generated policy.json:" - cat policy.json + - name: Free disk space + uses: jlumbroso/free-disk-space@main + with: + tool-cache: true + android: true + dotnet: true + haskell: true + large-packages: true + docker-images: true + swap-storage: true - - name: Check if should skip build - id: should_build - run: | - # For push events, skip if minimal Containerfile changed (will trigger via workflow_run) - if [ "${{ github.event_name }}" == "push" ]; then - FILES=$(git diff --name-only ${{ github.event.before }} ${{ github.sha }}) - echo "Changed files:" - echo "$FILES" - - if echo "$FILES" | grep -q "fedora-bootc-minimal.Containerfile"; then - echo "run=false" >> $GITHUB_OUTPUT - echo "⚠️ Minimal Containerfile changed - skipping push trigger" - echo " Hypervisor will build automatically after minimal completes" - else - echo "run=true" >> $GITHUB_OUTPUT - echo "✅ Proceeding with build" - fi - else - # For non-push events (workflow_run, manual, schedule), always run - echo "run=true" >> $GITHUB_OUTPUT - echo "✅ Event: ${{ github.event_name }} - proceeding with build" - fi + - name: Install cosign + uses: sigstore/cosign-installer@v3.7.0 - - name: Setup podman - if: steps.should_build.outputs.run == 'true' + - name: Install podman run: | sudo apt-get update sudo apt-get install -y podman podman --version - - name: Install cosign - if: steps.should_build.outputs.run == 'true' - uses: sigstore/cosign-installer@v3.7.0 - - - name: Login to GitHub Container Registry - if: steps.should_build.outputs.run == 'true' - run: | - echo "${{ secrets.GITHUB_TOKEN }}" | sudo podman login ghcr.io -u ${{ github.actor }} --password-stdin - - - name: Login to GitHub Container Registry for cosign - if: steps.should_build.outputs.run == 'true' - run: | - echo "${{ secrets.GITHUB_TOKEN }}" | cosign login ghcr.io -u ${{ github.actor }} --password-stdin - - - name: Determine variants to build - if: steps.should_build.outputs.run == 'true' - id: variants + - name: Render policy.json + env: + OWNER: ${{ steps.params.outputs.owner }} run: | - if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then - VARIANTS="${{ inputs.variants }}" - elif [ "${{ github.event_name }}" == "workflow_run" ]; then - VARIANTS="all" - else - VARIANTS="base" - fi - echo "variants=${VARIANTS}" >> $GITHUB_OUTPUT - - - name: Free up disk space - if: steps.should_build.outputs.run == 'true' - uses: jlumbroso/free-disk-space@main - with: - # Remove all unnecessary software (~31 GB freed in ~3 min) - tool-cache: true # ~5.9 GB - Remove Node, Python, Ruby caches (not needed for container builds) - android: true # ~14 GB - Remove Android libraries - dotnet: true # ~2.7 GB - Remove .NET runtime - haskell: true # Remove Haskell runtime (GHC) - large-packages: true # ~5.3 GB - Remove Azure CLI, Google Cloud SDK, etc. - docker-images: true # Remove cached Docker images (doesn't affect podman) - swap-storage: true # ~4 GB - Remove swap file + sed -e "s|__REGISTRY_NAMESPACE__|${OWNER}|g" \ + policy-hypervisor.json.template > policy.json + cat policy.json - name: Fetch cosy - if: steps.should_build.outputs.run == 'true' run: | mkdir -p bin man curl -fsSL https://raw.githubusercontent.com/BenSmith/cosy/main/cosy -o bin/cosy curl -fsSL https://raw.githubusercontent.com/BenSmith/cosy/main/cosy.1 -o man/cosy.1 - - name: Build base hypervisor - if: steps.should_build.outputs.run == 'true' + - name: Login to GHCR + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ACTOR: ${{ github.actor }} run: | - TAG=$(date +%Y%m%d-%H%M) - echo "base_tag=${TAG}" >> $GITHUB_ENV + echo "$GH_TOKEN" | sudo podman login ghcr.io -u "$ACTOR" --password-stdin + echo "$GH_TOKEN" | cosign login ghcr.io -u "$ACTOR" --password-stdin - # Build in root storage (rechunk action accesses root storage) - # --pull=always ensures we get the latest minimal base from GHCR - # Security options needed for dracut/bootc operations + - name: Build base hypervisor + env: + TAG: ${{ steps.params.outputs.tag }} + run: | sudo podman build \ --pull=always \ --security-opt=label=disable \ @@ -143,262 +112,227 @@ jobs: --cap-add=all \ --ipc=host \ -f hypervisor.Containerfile \ - -t localhost/hypervisor-bootc:${TAG} \ - -t localhost/hypervisor-bootc:latest \ + -t localhost/hypervisor-bootc:build \ . - - name: Rechunk base hypervisor image - if: steps.should_build.outputs.run == 'true' + - name: Rechunk base image run: | - # Use bootc-base-imagectl rechunk (official bootc method) sudo podman run --rm --privileged \ -v /var/lib/containers:/var/lib/containers \ - quay.io/fedora/fedora-bootc:rawhide \ + "${RECHUNKER_IMAGE}" \ /usr/libexec/bootc-base-imagectl rechunk \ - localhost/hypervisor-bootc:${{ env.base_tag }} \ + localhost/hypervisor-bootc:build \ localhost/hypervisor-bootc:rechunked - - name: Tag rechunked base image - if: steps.should_build.outputs.run == 'true' - run: | - # Apply all needed tags to rechunked image - sudo podman tag localhost/hypervisor-bootc:rechunked localhost/hypervisor-bootc:${{ env.base_tag }} - sudo podman tag localhost/hypervisor-bootc:rechunked localhost/hypervisor-bootc:latest - sudo podman rmi localhost/hypervisor-bootc:rechunked - - - name: Push base hypervisor to GHCR - if: steps.should_build.outputs.run == 'true' + - name: Tag and push base + env: + IMAGE_BASE: ${{ steps.params.outputs.image_base }} + TAG: ${{ steps.params.outputs.tag }} run: | - OWNER=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]') - - sudo podman tag localhost/hypervisor-bootc:${{ env.base_tag }} ghcr.io/${OWNER}/hypervisor-bootc:${{ env.base_tag }} - sudo podman tag localhost/hypervisor-bootc:${{ env.base_tag }} ghcr.io/${OWNER}/hypervisor-bootc:latest - - sudo podman push ghcr.io/${OWNER}/hypervisor-bootc:${{ env.base_tag }} - sudo podman push ghcr.io/${OWNER}/hypervisor-bootc:latest + for t in "${TAG}" latest; do + sudo podman tag localhost/hypervisor-bootc:rechunked "${IMAGE_BASE}:${t}" + sudo podman push "${IMAGE_BASE}:${t}" + done - sudo podman rmi ghcr.io/${OWNER}/hypervisor-bootc:${{ env.base_tag }} || true - sudo podman rmi ghcr.io/${OWNER}/hypervisor-bootc:latest || true - - # Verify localhost tags still exist for variant builds - echo "Remaining localhost images:" - sudo podman images | grep hypervisor-bootc - - - name: Sign base hypervisor images - id: base_complete - if: steps.should_build.outputs.run == 'true' + - name: Sign base env: COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_SECRET }} + IMAGE_BASE: ${{ steps.params.outputs.image_base }} + TAG: ${{ steps.params.outputs.tag }} run: | - OWNER=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]') - cosign sign -y --key env://COSIGN_PRIVATE_KEY ghcr.io/${OWNER}/hypervisor-bootc:${{ env.base_tag }} - cosign sign -y --key env://COSIGN_PRIVATE_KEY ghcr.io/${OWNER}/hypervisor-bootc:latest + for t in "${TAG}" latest; do + cosign sign -y --key env://COSIGN_PRIVATE_KEY "${IMAGE_BASE}:${t}" + done - - name: Build AMD variant - continue-on-error: true - if: steps.should_build.outputs.run == 'true' && steps.base_complete.outcome == 'success' && (contains(steps.variants.outputs.variants, 'amd') || contains(steps.variants.outputs.variants, 'all')) + - name: Summary + env: + IMAGE_BASE: ${{ steps.params.outputs.image_base }} + TAG: ${{ steps.params.outputs.tag }} run: | - # Build in root storage with only timestamped tag - sudo podman build \ - --pull=always \ - --security-opt=label=disable \ - --security-opt=seccomp=unconfined \ - --cap-add=all \ - --ipc=host \ - -f hypervisor-amd.Containerfile \ - -t localhost/hypervisor-amd:${{ env.base_tag }} \ - . + { + echo "## Base hypervisor build complete" + echo "" + echo "- \`${IMAGE_BASE}:${TAG}\`" + echo "- \`${IMAGE_BASE}:latest\`" + } >> "$GITHUB_STEP_SUMMARY" + + build-variant: + needs: build-base + runs-on: ubuntu-24.04 + timeout-minutes: 120 + permissions: + contents: read + packages: write + id-token: write - - name: Rechunk AMD variant image - continue-on-error: true - if: steps.should_build.outputs.run == 'true' && steps.base_complete.outcome == 'success' && (contains(steps.variants.outputs.variants, 'amd') || contains(steps.variants.outputs.variants, 'all')) - run: | - # Use bootc-base-imagectl rechunk (official bootc method) - sudo podman run --rm --privileged \ - -v /var/lib/containers:/var/lib/containers \ - quay.io/fedora/fedora-bootc:rawhide \ - /usr/libexec/bootc-base-imagectl rechunk \ - localhost/hypervisor-amd:${{ env.base_tag }} \ - localhost/hypervisor-amd:rechunked + strategy: + fail-fast: false + matrix: + variant: + - name: amd + image: hypervisor-amd + containerfile: hypervisor-amd.Containerfile + versioned_tag_prefix: '' + floating_tag: latest + - name: nvidia-negativo17 + image: hypervisor-nvidia + containerfile: hypervisor-nvidia-negativo17.Containerfile + versioned_tag_prefix: 'negativo17-' + floating_tag: negativo17 + - name: nvidia-rpmfusion + image: hypervisor-nvidia + containerfile: hypervisor-nvidia-rpmfusion.Containerfile + versioned_tag_prefix: 'rpmfusion-' + floating_tag: rpmfusion - - name: Tag rechunked AMD image - continue-on-error: true - if: steps.should_build.outputs.run == 'true' && steps.base_complete.outcome == 'success' && (contains(steps.variants.outputs.variants, 'amd') || contains(steps.variants.outputs.variants, 'all')) - run: | - # Apply tags to rechunked image - sudo podman tag localhost/hypervisor-amd:rechunked localhost/hypervisor-amd:${{ env.base_tag }} - sudo podman tag localhost/hypervisor-amd:rechunked localhost/hypervisor-amd:latest - sudo podman rmi localhost/hypervisor-amd:rechunked - - - name: Push AMD variant to GHCR - continue-on-error: true - if: steps.should_build.outputs.run == 'true' && steps.base_complete.outcome == 'success' && (contains(steps.variants.outputs.variants, 'amd') || contains(steps.variants.outputs.variants, 'all')) + steps: + - name: Decide whether to build this variant + id: gate + env: + SELECTION: ${{ inputs.variants }} + EVENT: ${{ github.event_name }} + NAME: ${{ matrix.variant.name }} run: | - OWNER=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]') - - sudo podman tag localhost/hypervisor-amd:${{ env.base_tag }} ghcr.io/${OWNER}/hypervisor-amd:${{ env.base_tag }} - sudo podman tag localhost/hypervisor-amd:${{ env.base_tag }} ghcr.io/${OWNER}/hypervisor-amd:latest - - sudo podman push ghcr.io/${OWNER}/hypervisor-amd:${{ env.base_tag }} - sudo podman push ghcr.io/${OWNER}/hypervisor-amd:latest - - sudo podman rmi localhost/hypervisor-amd:${{ env.base_tag }} || true - sudo podman rmi ghcr.io/${OWNER}/hypervisor-amd:${{ env.base_tag }} || true - sudo podman rmi ghcr.io/${OWNER}/hypervisor-amd:latest || true + # workflow_run and schedule always build all variants. + # workflow_dispatch respects the user's selection. + if [ "$EVENT" != "workflow_dispatch" ] || [ "$SELECTION" = "all" ]; then + BUILD=true + elif [ "$SELECTION" = "none" ]; then + BUILD=false + elif [ "$SELECTION" = "nvidia" ] && [[ "$NAME" == nvidia-* ]]; then + BUILD=true + elif [ "$SELECTION" = "$NAME" ]; then + BUILD=true + else + BUILD=false + fi + echo "build=${BUILD}" >> "$GITHUB_OUTPUT" - # Only prune dangling images (no -a flag) to preserve base image for next variant - sudo podman system prune -f --volumes + - name: Checkout repository + if: steps.gate.outputs.build == 'true' + uses: actions/checkout@v4 - - name: Sign AMD images - continue-on-error: true - if: steps.should_build.outputs.run == 'true' && steps.base_complete.outcome == 'success' && (contains(steps.variants.outputs.variants, 'amd') || contains(steps.variants.outputs.variants, 'all')) + - name: Render policy.json + if: steps.gate.outputs.build == 'true' env: - COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_SECRET }} + OWNER: ${{ needs.build-base.outputs.owner }} run: | - OWNER=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]') - cosign sign -y --key env://COSIGN_PRIVATE_KEY ghcr.io/${OWNER}/hypervisor-amd:${{ env.base_tag }} - cosign sign -y --key env://COSIGN_PRIVATE_KEY ghcr.io/${OWNER}/hypervisor-amd:latest - - - name: Build NVIDIA negativo17 variant - continue-on-error: true - if: steps.should_build.outputs.run == 'true' && steps.base_complete.outcome == 'success' && (contains(steps.variants.outputs.variants, 'nvidia-negativo17') || contains(steps.variants.outputs.variants, 'all')) - run: | - # Build in root storage with only timestamped tag - sudo podman build \ - --pull=always \ - --security-opt=label=disable \ - --security-opt=seccomp=unconfined \ - --cap-add=all \ - --ipc=host \ - -f hypervisor-nvidia-negativo17.Containerfile \ - -t localhost/hypervisor-nvidia:negativo17-${{ env.base_tag }} \ - . + sed -e "s|__REGISTRY_NAMESPACE__|${OWNER}|g" \ + policy-hypervisor.json.template > policy.json - - name: Rechunk NVIDIA negativo17 variant image - continue-on-error: true - if: steps.should_build.outputs.run == 'true' && steps.base_complete.outcome == 'success' && (contains(steps.variants.outputs.variants, 'nvidia-negativo17') || contains(steps.variants.outputs.variants, 'all')) - run: | - # Use bootc-base-imagectl rechunk (official bootc method) - sudo podman run --rm --privileged \ - -v /var/lib/containers:/var/lib/containers \ - quay.io/fedora/fedora-bootc:rawhide \ - /usr/libexec/bootc-base-imagectl rechunk \ - localhost/hypervisor-nvidia:negativo17-${{ env.base_tag }} \ - localhost/hypervisor-nvidia:rechunked + - name: Free disk space + if: steps.gate.outputs.build == 'true' + uses: jlumbroso/free-disk-space@main + with: + tool-cache: true + android: true + dotnet: true + haskell: true + large-packages: true + docker-images: true + swap-storage: true + + - name: Install podman and cosign + if: steps.gate.outputs.build == 'true' + uses: sigstore/cosign-installer@v3.7.0 - - name: Tag rechunked NVIDIA negativo17 image - continue-on-error: true - if: steps.should_build.outputs.run == 'true' && steps.base_complete.outcome == 'success' && (contains(steps.variants.outputs.variants, 'nvidia-negativo17') || contains(steps.variants.outputs.variants, 'all')) + - name: Install podman + if: steps.gate.outputs.build == 'true' run: | - # Apply tag to rechunked image - sudo podman tag localhost/hypervisor-nvidia:rechunked localhost/hypervisor-nvidia:negativo17-${{ env.base_tag }} - sudo podman rmi localhost/hypervisor-nvidia:rechunked + sudo apt-get update + sudo apt-get install -y podman + podman --version - - name: Push NVIDIA negativo17 variant to GHCR - continue-on-error: true - if: steps.should_build.outputs.run == 'true' && steps.base_complete.outcome == 'success' && (contains(steps.variants.outputs.variants, 'nvidia-negativo17') || contains(steps.variants.outputs.variants, 'all')) + - name: Fetch cosy + if: steps.gate.outputs.build == 'true' run: | - OWNER=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]') - - sudo podman tag localhost/hypervisor-nvidia:negativo17-${{ env.base_tag }} ghcr.io/${OWNER}/hypervisor-nvidia:negativo17-${{ env.base_tag }} - sudo podman tag localhost/hypervisor-nvidia:negativo17-${{ env.base_tag }} ghcr.io/${OWNER}/hypervisor-nvidia:negativo17 - - sudo podman push ghcr.io/${OWNER}/hypervisor-nvidia:negativo17-${{ env.base_tag }} - sudo podman push ghcr.io/${OWNER}/hypervisor-nvidia:negativo17 - - sudo podman rmi localhost/hypervisor-nvidia:negativo17-${{ env.base_tag }} || true - sudo podman rmi ghcr.io/${OWNER}/hypervisor-nvidia:negativo17-${{ env.base_tag }} || true - sudo podman rmi ghcr.io/${OWNER}/hypervisor-nvidia:negativo17 || true - - # Only prune dangling images (no -a flag) to preserve base image for next variant - sudo podman system prune -f --volumes + mkdir -p bin man + curl -fsSL https://raw.githubusercontent.com/BenSmith/cosy/main/cosy -o bin/cosy + curl -fsSL https://raw.githubusercontent.com/BenSmith/cosy/main/cosy.1 -o man/cosy.1 - - name: Sign NVIDIA negativo17 images - continue-on-error: true - if: steps.should_build.outputs.run == 'true' && steps.base_complete.outcome == 'success' && (contains(steps.variants.outputs.variants, 'nvidia-negativo17') || contains(steps.variants.outputs.variants, 'all')) + - name: Login to GHCR + if: steps.gate.outputs.build == 'true' env: - COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_SECRET }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ACTOR: ${{ github.actor }} run: | - OWNER=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]') - cosign sign -y --key env://COSIGN_PRIVATE_KEY ghcr.io/${OWNER}/hypervisor-nvidia:negativo17-${{ env.base_tag }} - cosign sign -y --key env://COSIGN_PRIVATE_KEY ghcr.io/${OWNER}/hypervisor-nvidia:negativo17 + echo "$GH_TOKEN" | sudo podman login ghcr.io -u "$ACTOR" --password-stdin + echo "$GH_TOKEN" | cosign login ghcr.io -u "$ACTOR" --password-stdin - - name: Build NVIDIA RPMFusion variant - continue-on-error: true - if: steps.should_build.outputs.run == 'true' && steps.base_complete.outcome == 'success' && (contains(steps.variants.outputs.variants, 'nvidia-rpmfusion') || contains(steps.variants.outputs.variants, 'all')) + - name: Build variant + if: steps.gate.outputs.build == 'true' + env: + IMAGE: ${{ matrix.variant.image }} + CONTAINERFILE: ${{ matrix.variant.containerfile }} run: | - # Build in root storage with only timestamped tag sudo podman build \ --pull=always \ --security-opt=label=disable \ --security-opt=seccomp=unconfined \ --cap-add=all \ --ipc=host \ - -f hypervisor-nvidia-rpmfusion.Containerfile \ - -t localhost/hypervisor-nvidia:rpmfusion-${{ env.base_tag }} \ + -f "${CONTAINERFILE}" \ + -t "localhost/${IMAGE}:build" \ . - - name: Rechunk NVIDIA RPMFusion variant image - continue-on-error: true - if: steps.should_build.outputs.run == 'true' && steps.base_complete.outcome == 'success' && (contains(steps.variants.outputs.variants, 'nvidia-rpmfusion') || contains(steps.variants.outputs.variants, 'all')) + - name: Rechunk variant + if: steps.gate.outputs.build == 'true' + env: + IMAGE: ${{ matrix.variant.image }} run: | - # Use bootc-base-imagectl rechunk (official bootc method) sudo podman run --rm --privileged \ -v /var/lib/containers:/var/lib/containers \ - quay.io/fedora/fedora-bootc:rawhide \ + "${RECHUNKER_IMAGE}" \ /usr/libexec/bootc-base-imagectl rechunk \ - localhost/hypervisor-nvidia:rpmfusion-${{ env.base_tag }} \ - localhost/hypervisor-nvidia:rechunked + "localhost/${IMAGE}:build" \ + "localhost/${IMAGE}:rechunked" - - name: Tag rechunked NVIDIA RPMFusion image - continue-on-error: true - if: steps.should_build.outputs.run == 'true' && steps.base_complete.outcome == 'success' && (contains(steps.variants.outputs.variants, 'nvidia-rpmfusion') || contains(steps.variants.outputs.variants, 'all')) - run: | - # Apply tag to rechunked image - sudo podman tag localhost/hypervisor-nvidia:rechunked localhost/hypervisor-nvidia:rpmfusion-${{ env.base_tag }} - sudo podman rmi localhost/hypervisor-nvidia:rechunked - - - name: Push NVIDIA RPMFusion variant to GHCR - continue-on-error: true - if: steps.should_build.outputs.run == 'true' && steps.base_complete.outcome == 'success' && (contains(steps.variants.outputs.variants, 'nvidia-rpmfusion') || contains(steps.variants.outputs.variants, 'all')) + - name: Tag and push variant + if: steps.gate.outputs.build == 'true' + id: push + env: + OWNER: ${{ needs.build-base.outputs.owner }} + BASE_TAG: ${{ needs.build-base.outputs.tag }} + IMAGE: ${{ matrix.variant.image }} + PREFIX: ${{ matrix.variant.versioned_tag_prefix }} + FLOATING: ${{ matrix.variant.floating_tag }} run: | - OWNER=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]') + IMAGE_BASE="ghcr.io/${OWNER}/${IMAGE}" + VERSIONED_TAG="${PREFIX}${BASE_TAG}" - # Tag for GHCR - sudo podman tag localhost/hypervisor-nvidia:rpmfusion-${{ env.base_tag }} ghcr.io/${OWNER}/hypervisor-nvidia:rpmfusion-${{ env.base_tag }} - sudo podman tag localhost/hypervisor-nvidia:rpmfusion-${{ env.base_tag }} ghcr.io/${OWNER}/hypervisor-nvidia:rpmfusion + for t in "${VERSIONED_TAG}" "${FLOATING}"; do + sudo podman tag "localhost/${IMAGE}:rechunked" "${IMAGE_BASE}:${t}" + sudo podman push "${IMAGE_BASE}:${t}" + done - # Push to GHCR - sudo podman push ghcr.io/${OWNER}/hypervisor-nvidia:rpmfusion-${{ env.base_tag }} - sudo podman push ghcr.io/${OWNER}/hypervisor-nvidia:rpmfusion + { + echo "image_base=${IMAGE_BASE}" + echo "versioned_tag=${VERSIONED_TAG}" + } >> "$GITHUB_OUTPUT" - # Clean up all local tags from root storage - sudo podman rmi localhost/hypervisor-nvidia:rpmfusion-${{ env.base_tag }} || true - sudo podman rmi ghcr.io/${OWNER}/hypervisor-nvidia:rpmfusion-${{ env.base_tag }} || true - sudo podman rmi ghcr.io/${OWNER}/hypervisor-nvidia:rpmfusion || true - - # Final prune can be aggressive since all variants are done - sudo podman system prune -af --volumes - - - name: Sign NVIDIA RPMFusion images - continue-on-error: true - if: steps.should_build.outputs.run == 'true' && steps.base_complete.outcome == 'success' && (contains(steps.variants.outputs.variants, 'nvidia-rpmfusion') || contains(steps.variants.outputs.variants, 'all')) + - name: Sign variant + if: steps.gate.outputs.build == 'true' env: COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_SECRET }} + IMAGE_BASE: ${{ steps.push.outputs.image_base }} + VERSIONED_TAG: ${{ steps.push.outputs.versioned_tag }} + FLOATING: ${{ matrix.variant.floating_tag }} run: | - OWNER=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]') - cosign sign -y --key env://COSIGN_PRIVATE_KEY ghcr.io/${OWNER}/hypervisor-nvidia:rpmfusion-${{ env.base_tag }} - cosign sign -y --key env://COSIGN_PRIVATE_KEY ghcr.io/${OWNER}/hypervisor-nvidia:rpmfusion + for t in "${VERSIONED_TAG}" "${FLOATING}"; do + cosign sign -y --key env://COSIGN_PRIVATE_KEY "${IMAGE_BASE}:${t}" + done - name: Summary - if: steps.should_build.outputs.run == 'true' + if: steps.gate.outputs.build == 'true' + env: + NAME: ${{ matrix.variant.name }} + IMAGE_BASE: ${{ steps.push.outputs.image_base }} + VERSIONED_TAG: ${{ steps.push.outputs.versioned_tag }} + FLOATING: ${{ matrix.variant.floating_tag }} run: | - echo "## Build Complete!" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "Built variants: ${{ steps.variants.outputs.variants }}" >> $GITHUB_STEP_SUMMARY - echo "Tag: ${{ env.base_tag }}" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### Images:" >> $GITHUB_STEP_SUMMARY - OWNER=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]') - echo "- \`ghcr.io/${OWNER}/hypervisor-bootc:${{ env.base_tag }}\` ✅ Signed" >> $GITHUB_STEP_SUMMARY - echo "- \`ghcr.io/${OWNER}/hypervisor-bootc:latest\` ✅ Signed" >> $GITHUB_STEP_SUMMARY + { + echo "## Variant ${NAME} complete" + echo "" + echo "- \`${IMAGE_BASE}:${VERSIONED_TAG}\`" + echo "- \`${IMAGE_BASE}:${FLOATING}\`" + } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/build-minimal-bootc.yml b/.github/workflows/build-minimal-bootc.yml index af18490..e6df73e 100644 --- a/.github/workflows/build-minimal-bootc.yml +++ b/.github/workflows/build-minimal-bootc.yml @@ -1,7 +1,6 @@ name: Build Fedora Minimal Bootc on: - # Manual trigger with version selection workflow_dispatch: inputs: fedora_version: @@ -13,90 +12,83 @@ on: - '43' - '44' - 'rawhide' - rechunk: - description: 'Rechunk the image for better efficiency' - required: false - default: true - type: boolean - # Weekly build (Saturdays at 2am UTC) schedule: - cron: '0 2 * * 6' - # Build on pushes to main that touch these files push: - branches: - - main + branches: [main] paths: - 'fedora-bootc-minimal.Containerfile' + - 'policy-minimal.json.template' + - 'cosign.pub' - '.github/workflows/build-minimal-bootc.yml' +concurrency: + group: build-minimal-bootc-${{ github.ref }} + cancel-in-progress: false + +env: + STABLE_FEDORA_VERSION: '43' + IMAGE_NAME: fedora-bootc-minimal + jobs: - build-minimal-bootc: - runs-on: ubuntu-24.04 # Required by rechunk action for advanced podman features + build: + runs-on: ubuntu-24.04 + timeout-minutes: 90 permissions: contents: read packages: write id-token: write - strategy: - matrix: - # Build for multiple Fedora versions - fedora_version: - - '43' - steps: - - name: Setup podman + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Resolve build parameters + id: params + env: + INPUT_VERSION: ${{ inputs.fedora_version }} + OWNER_RAW: ${{ github.repository_owner }} run: | - sudo apt-get update - sudo apt-get install -y podman - podman --version + VERSION="${INPUT_VERSION:-${STABLE_FEDORA_VERSION}}" + OWNER=$(echo "$OWNER_RAW" | tr '[:upper:]' '[:lower:]') + TAG=$(date +%Y%m%d-%H%M) + { + echo "version=${VERSION}" + echo "owner=${OWNER}" + echo "tag=${TAG}" + echo "image_base=ghcr.io/${OWNER}/${IMAGE_NAME}" + } >> "$GITHUB_OUTPUT" - name: Install cosign uses: sigstore/cosign-installer@v3.7.0 - - name: Set Fedora version - id: set_version + - name: Install podman run: | - # Use manual input if provided, otherwise use matrix version - if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then - echo "version=${{ inputs.fedora_version }}" >> $GITHUB_OUTPUT - else - echo "version=${{ matrix.fedora_version }}" >> $GITHUB_OUTPUT - fi - - - name: Checkout repository - uses: actions/checkout@v4 + sudo apt-get update + sudo apt-get install -y podman + podman --version - - name: Generate /etc/containers/policy.json + - name: Render policy.json + env: + OWNER: ${{ steps.params.outputs.owner }} run: | - OWNER=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]') - sed -e "s|__REGISTRY_NAMESPACE__|${OWNER}|g" \ policy-minimal.json.template > policy.json - - echo "Generated policy.json for minimal:" cat policy.json - - name: Set build timestamp - id: timestamp - run: | - echo "tag=$(date +%Y%m%d-%H%M)" >> $GITHUB_OUTPUT - - name: Clone Fedora bootc manifests run: | git clone --depth 1 https://gitlab.com/fedora/bootc/base-images.git manifests - cd manifests - git log -1 - echo "Using local podman 4-compatible Containerfile with upstream manifests" - - - name: Copy policy.json and cosign.pub into build context - run: | + git -C manifests log -1 cp policy.json manifests/policy.json cp cosign.pub manifests/cosign.pub - echo "Copied policy.json and cosign.pub to manifests/ directory" - - name: Build minimal bootc container + - name: Build minimal bootc image + env: + VERSION: ${{ steps.params.outputs.version }} + TAG: ${{ steps.params.outputs.tag }} run: | sudo podman build \ --security-opt=label=disable \ @@ -104,109 +96,70 @@ jobs: --device /dev/fuse \ -f fedora-bootc-minimal.Containerfile \ --build-arg MANIFEST=minimal \ - --build-arg BUILDER_IMAGE=quay.io/fedora/fedora:${{ steps.set_version.outputs.version }} \ - --build-arg REPOS_IMAGE=quay.io/fedora/fedora:${{ steps.set_version.outputs.version }} \ - -t localhost/fedora-bootc-minimal:${{ steps.set_version.outputs.version }}-${{ steps.timestamp.outputs.tag }} \ + --build-arg BUILDER_IMAGE=quay.io/fedora/fedora:${VERSION} \ + --build-arg REPOS_IMAGE=quay.io/fedora/fedora:${VERSION} \ + -t localhost/${IMAGE_NAME}:build \ manifests + sudo podman inspect localhost/${IMAGE_NAME}:build > /dev/null - - name: Test built image - run: | - sudo podman images | grep fedora-bootc-minimal - sudo podman inspect localhost/fedora-bootc-minimal:${{ steps.set_version.outputs.version }}-${{ steps.timestamp.outputs.tag }} - - - name: Rechunk image with bootc-base-imagectl + - name: Rechunk image + env: + VERSION: ${{ steps.params.outputs.version }} run: | - # Use bootc-base-imagectl rechunk (official bootc method) sudo podman run --rm --privileged \ -v /var/lib/containers:/var/lib/containers \ - quay.io/fedora/fedora-bootc:${{ steps.set_version.outputs.version }} \ + quay.io/fedora/fedora-bootc:${VERSION} \ /usr/libexec/bootc-base-imagectl rechunk \ - localhost/fedora-bootc-minimal:${{ steps.set_version.outputs.version }}-${{ steps.timestamp.outputs.tag }} \ - localhost/fedora-bootc-minimal:rechunked - - # List all images to verify rechunk created the image - echo "All images after rechunk:" - sudo podman images + localhost/${IMAGE_NAME}:build \ + localhost/${IMAGE_NAME}:rechunked - - name: Tag rechunked image - run: | - # Apply all the tags we need to the rechunked image - sudo podman tag localhost/fedora-bootc-minimal:rechunked \ - localhost/fedora-bootc-minimal:${{ steps.set_version.outputs.version }}-${{ steps.timestamp.outputs.tag }} - sudo podman tag localhost/fedora-bootc-minimal:rechunked \ - localhost/fedora-bootc-minimal:${{ steps.set_version.outputs.version }} - sudo podman tag localhost/fedora-bootc-minimal:rechunked \ - localhost/fedora-bootc-minimal:latest - - # Clean up the temporary rechunked tag - sudo podman rmi localhost/fedora-bootc-minimal:rechunked - - - name: Login to GitHub Container Registry + - name: Login to GHCR + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ACTOR: ${{ github.actor }} run: | - echo "${{ secrets.GITHUB_TOKEN }}" | sudo podman login ghcr.io -u ${{ github.actor }} --password-stdin + echo "$GH_TOKEN" | sudo podman login ghcr.io -u "$ACTOR" --password-stdin + echo "$GH_TOKEN" | cosign login ghcr.io -u "$ACTOR" --password-stdin - - name: Push to GitHub Container Registry + - name: Tag and push + id: push + env: + IMAGE_BASE: ${{ steps.params.outputs.image_base }} + VERSION: ${{ steps.params.outputs.version }} + TAG: ${{ steps.params.outputs.tag }} run: | - # Lowercase repository owner for ghcr.io compatibility - OWNER=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]') - - # Tag with timestamp and version - sudo podman tag localhost/fedora-bootc-minimal:${{ steps.set_version.outputs.version }}-${{ steps.timestamp.outputs.tag }} \ - ghcr.io/${OWNER}/fedora-bootc-minimal:${{ steps.set_version.outputs.version }}-${{ steps.timestamp.outputs.tag }} - - sudo podman tag localhost/fedora-bootc-minimal:${{ steps.set_version.outputs.version }} \ - ghcr.io/${OWNER}/fedora-bootc-minimal:${{ steps.set_version.outputs.version }} - - # Also tag 'latest' for the stable version - if [ "${{ steps.set_version.outputs.version }}" == "43" ]; then - sudo podman tag localhost/fedora-bootc-minimal:${{ steps.set_version.outputs.version }} \ - ghcr.io/${OWNER}/fedora-bootc-minimal:latest + TAGS=("${VERSION}-${TAG}" "${VERSION}") + if [ "${VERSION}" = "${STABLE_FEDORA_VERSION}" ]; then + TAGS+=("latest") fi - # Push all tags - sudo podman push ghcr.io/${OWNER}/fedora-bootc-minimal:${{ steps.set_version.outputs.version }}-${{ steps.timestamp.outputs.tag }} - sudo podman push ghcr.io/${OWNER}/fedora-bootc-minimal:${{ steps.set_version.outputs.version }} + for t in "${TAGS[@]}"; do + sudo podman tag localhost/${IMAGE_NAME}:rechunked "${IMAGE_BASE}:${t}" + sudo podman push "${IMAGE_BASE}:${t}" + done - if [ "${{ steps.set_version.outputs.version }}" == "43" ]; then - sudo podman push ghcr.io/${OWNER}/fedora-bootc-minimal:latest - fi - - - name: Login to GitHub Container Registry for cosign - run: | - echo "${{ secrets.GITHUB_TOKEN }}" | cosign login ghcr.io -u ${{ github.actor }} --password-stdin + printf 'tags<> "$GITHUB_OUTPUT" - - name: Sign images with cosign + - name: Sign images env: COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_SECRET }} + IMAGE_BASE: ${{ steps.params.outputs.image_base }} + TAGS: ${{ steps.push.outputs.tags }} run: | - OWNER=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]') + for t in $TAGS; do + cosign sign -y --key env://COSIGN_PRIVATE_KEY "${IMAGE_BASE}:${t}" + done - cosign sign -y --key env://COSIGN_PRIVATE_KEY ghcr.io/${OWNER}/fedora-bootc-minimal:${{ steps.set_version.outputs.version }}-${{ steps.timestamp.outputs.tag }} - - cosign sign -y --key env://COSIGN_PRIVATE_KEY ghcr.io/${OWNER}/fedora-bootc-minimal:${{ steps.set_version.outputs.version }} - - if [ "${{ steps.set_version.outputs.version }}" == "43" ]; then - cosign sign -y --key env://COSIGN_PRIVATE_KEY ghcr.io/${OWNER}/fedora-bootc-minimal:latest - fi - - - name: Cleanup local images + - name: Summary + env: + IMAGE_BASE: ${{ steps.params.outputs.image_base }} + TAGS: ${{ steps.push.outputs.tags }} run: | - OWNER=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]') - - # Remove all local tags from root storage - sudo podman rmi localhost/fedora-bootc-minimal:${{ steps.set_version.outputs.version }}-${{ steps.timestamp.outputs.tag }} || true - sudo podman rmi localhost/fedora-bootc-minimal:${{ steps.set_version.outputs.version }} || true - sudo podman rmi localhost/fedora-bootc-minimal:latest || true - sudo podman rmi ghcr.io/${OWNER}/fedora-bootc-minimal:${{ steps.set_version.outputs.version }}-${{ steps.timestamp.outputs.tag }} || true - sudo podman rmi ghcr.io/${OWNER}/fedora-bootc-minimal:${{ steps.set_version.outputs.version }} || true - - # Also clean up :latest tag if it was pushed - if [ "${{ steps.set_version.outputs.version }}" == "43" ]; then - sudo podman rmi ghcr.io/${OWNER}/fedora-bootc-minimal:latest || true - fi - - # Prune root storage to remove dangling layers - sudo podman system prune -af --volumes - - # Show final disk usage - df -h + { + echo "## Minimal bootc build complete" + echo "" + echo "Pushed and signed:" + for t in $TAGS; do + echo "- \`${IMAGE_BASE}:${t}\`" + done + } >> "$GITHUB_STEP_SUMMARY" From 8ef9b78e6103806f7746fcbe7bddc3089e5669fd Mon Sep 17 00:00:00 2001 From: Ben Smith Date: Sat, 18 Apr 2026 16:56:45 -0700 Subject: [PATCH 4/6] adjusting --- .github/workflows/build-hypervisor.yml | 32 +++++++++++------------ .github/workflows/build-minimal-bootc.yml | 20 +++++++++----- 2 files changed, 29 insertions(+), 23 deletions(-) diff --git a/.github/workflows/build-hypervisor.yml b/.github/workflows/build-hypervisor.yml index 7c6edc4..b8891bc 100644 --- a/.github/workflows/build-hypervisor.yml +++ b/.github/workflows/build-hypervisor.yml @@ -125,24 +125,24 @@ jobs: localhost/hypervisor-bootc:rechunked - name: Tag and push base + id: push_base env: IMAGE_BASE: ${{ steps.params.outputs.image_base }} TAG: ${{ steps.params.outputs.tag }} run: | - for t in "${TAG}" latest; do - sudo podman tag localhost/hypervisor-bootc:rechunked "${IMAGE_BASE}:${t}" - sudo podman push "${IMAGE_BASE}:${t}" - done + sudo podman tag localhost/hypervisor-bootc:rechunked "${IMAGE_BASE}:${TAG}" + sudo podman push --digestfile /tmp/digest.txt "${IMAGE_BASE}:${TAG}" + sudo podman tag localhost/hypervisor-bootc:rechunked "${IMAGE_BASE}:latest" + sudo podman push "${IMAGE_BASE}:latest" + echo "digest=$(cat /tmp/digest.txt)" >> "$GITHUB_OUTPUT" - name: Sign base env: COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_SECRET }} IMAGE_BASE: ${{ steps.params.outputs.image_base }} - TAG: ${{ steps.params.outputs.tag }} + DIGEST: ${{ steps.push_base.outputs.digest }} run: | - for t in "${TAG}" latest; do - cosign sign -y --key env://COSIGN_PRIVATE_KEY "${IMAGE_BASE}:${t}" - done + cosign sign -y --key env://COSIGN_PRIVATE_KEY "${IMAGE_BASE}@${DIGEST}" - name: Summary env: @@ -300,14 +300,15 @@ jobs: IMAGE_BASE="ghcr.io/${OWNER}/${IMAGE}" VERSIONED_TAG="${PREFIX}${BASE_TAG}" - for t in "${VERSIONED_TAG}" "${FLOATING}"; do - sudo podman tag "localhost/${IMAGE}:rechunked" "${IMAGE_BASE}:${t}" - sudo podman push "${IMAGE_BASE}:${t}" - done + sudo podman tag "localhost/${IMAGE}:rechunked" "${IMAGE_BASE}:${VERSIONED_TAG}" + sudo podman push --digestfile /tmp/digest.txt "${IMAGE_BASE}:${VERSIONED_TAG}" + sudo podman tag "localhost/${IMAGE}:rechunked" "${IMAGE_BASE}:${FLOATING}" + sudo podman push "${IMAGE_BASE}:${FLOATING}" { echo "image_base=${IMAGE_BASE}" echo "versioned_tag=${VERSIONED_TAG}" + echo "digest=$(cat /tmp/digest.txt)" } >> "$GITHUB_OUTPUT" - name: Sign variant @@ -315,12 +316,9 @@ jobs: env: COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_SECRET }} IMAGE_BASE: ${{ steps.push.outputs.image_base }} - VERSIONED_TAG: ${{ steps.push.outputs.versioned_tag }} - FLOATING: ${{ matrix.variant.floating_tag }} + DIGEST: ${{ steps.push.outputs.digest }} run: | - for t in "${VERSIONED_TAG}" "${FLOATING}"; do - cosign sign -y --key env://COSIGN_PRIVATE_KEY "${IMAGE_BASE}:${t}" - done + cosign sign -y --key env://COSIGN_PRIVATE_KEY "${IMAGE_BASE}@${DIGEST}" - name: Summary if: steps.gate.outputs.build == 'true' diff --git a/.github/workflows/build-minimal-bootc.yml b/.github/workflows/build-minimal-bootc.yml index e6df73e..19a98d5 100644 --- a/.github/workflows/build-minimal-bootc.yml +++ b/.github/workflows/build-minimal-bootc.yml @@ -133,22 +133,30 @@ jobs: TAGS+=("latest") fi + FIRST=true for t in "${TAGS[@]}"; do sudo podman tag localhost/${IMAGE_NAME}:rechunked "${IMAGE_BASE}:${t}" - sudo podman push "${IMAGE_BASE}:${t}" + if [ "${FIRST}" = "true" ]; then + sudo podman push --digestfile /tmp/digest.txt "${IMAGE_BASE}:${t}" + FIRST=false + else + sudo podman push "${IMAGE_BASE}:${t}" + fi done - printf 'tags<> "$GITHUB_OUTPUT" + DIGEST=$(cat /tmp/digest.txt) + { + printf 'tags<> "$GITHUB_OUTPUT" - name: Sign images env: COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_SECRET }} IMAGE_BASE: ${{ steps.params.outputs.image_base }} - TAGS: ${{ steps.push.outputs.tags }} + DIGEST: ${{ steps.push.outputs.digest }} run: | - for t in $TAGS; do - cosign sign -y --key env://COSIGN_PRIVATE_KEY "${IMAGE_BASE}:${t}" - done + cosign sign -y --key env://COSIGN_PRIVATE_KEY "${IMAGE_BASE}@${DIGEST}" - name: Summary env: From 3b73a7943639d055f7e8238d8477f7de90dd9ffb Mon Sep 17 00:00:00 2001 From: Ben Smith Date: Sat, 18 Apr 2026 17:59:13 -0700 Subject: [PATCH 5/6] add cuda for nvidia-smi --- hypervisor-nvidia-negativo17.Containerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hypervisor-nvidia-negativo17.Containerfile b/hypervisor-nvidia-negativo17.Containerfile index f1a1056..e37b018 100644 --- a/hypervisor-nvidia-negativo17.Containerfile +++ b/hypervisor-nvidia-negativo17.Containerfile @@ -12,6 +12,8 @@ RUN curl -s -L https://nvidia.github.io/libnvidia-container/stable/rpm/nvidia-co RUN dnf install --setopt=install_weak_deps=False -y \ nvidia-container-toolkit \ nvidia-driver \ + nvidia-driver-cuda \ + nvidia-driver-cuda-libs \ nvidia-gpu-firmware \ nvidia-modprobe \ nvidia-persistenced && \ From 129cab1d3b9c27060fc74565717904e7db46c25d Mon Sep 17 00:00:00 2001 From: Ben Smith Date: Sat, 18 Apr 2026 20:28:02 -0700 Subject: [PATCH 6/6] Add a build stage for akmods, since we can't install rpms on bootc systems --- hypervisor-nvidia-negativo17.Containerfile | 36 +++++++++++++++++- hypervisor-nvidia-rpmfusion.Containerfile | 44 ++++++++++++++++++++-- 2 files changed, 76 insertions(+), 4 deletions(-) diff --git a/hypervisor-nvidia-negativo17.Containerfile b/hypervisor-nvidia-negativo17.Containerfile index e37b018..a838da9 100644 --- a/hypervisor-nvidia-negativo17.Containerfile +++ b/hypervisor-nvidia-negativo17.Containerfile @@ -1,4 +1,27 @@ -FROM ghcr.io/bensmith/hypervisor-bootc:latest +ARG BASE=ghcr.io/bensmith/hypervisor-bootc:latest +ARG BASE_DIGEST="" + +# Stage 1: Build the NVIDIA kernel module RPM +FROM ${BASE}${BASE_DIGEST:+@${BASE_DIGEST}} AS kmod-builder + +RUN curl -s -L https://negativo17.org/repos/fedora-nvidia.repo \ + -o /etc/yum.repos.d/fedora-nvidia.repo + +RUN KERNEL_VERSION=$(rpm -q kernel --qf '%{version}-%{release}.%{arch}\n' | tail -1) && \ + dnf install --setopt=install_weak_deps=False -y \ + akmod-nvidia \ + "kernel-devel-${KERNEL_VERSION}" && \ + dnf clean all + +RUN mkdir -p /var/log/akmods && \ + chmod 1777 /tmp /var/tmp && \ + KERNEL_VERSION=$(rpm -q kernel --qf '%{version}-%{release}.%{arch}\n' | tail -1) && \ + akmods --force --kernels "${KERNEL_VERSION}" && \ + find /var/cache/akmods -name '*.rpm' | tee /dev/stderr | grep -q . || \ + { find /var/cache/akmods -name '*.failed.log' -exec cat {} +; exit 1; } + +# Stage 2: Final bootc image +FROM ${BASE}${BASE_DIGEST:+@${BASE_DIGEST}} # Add negativo17 NVIDIA repository (modular alternative to RPMFusion) RUN curl -s -L https://negativo17.org/repos/fedora-nvidia.repo \ @@ -20,6 +43,17 @@ RUN dnf install --setopt=install_weak_deps=False -y \ dnf clean all && \ rm -rf /var/log/* /var/cache/* /var/lib/dnf/* /boot/* +# Install pre-built kmod RPM from builder stage +COPY --from=kmod-builder /var/cache/akmods/ /tmp/akmods/ +RUN find /tmp/akmods -name '*.rpm' -exec rpm -ivh {} + && \ + rm -rf /tmp/akmods + +# Blacklist nouveau and configure proprietary driver for KMS/Wayland +RUN echo -e "blacklist nouveau\noptions nouveau modeset=0" \ + > /etc/modprobe.d/blacklist-nouveau.conf && \ + echo -e "options nvidia-drm modeset=1 fbdev=1\noptions nvidia NVreg_PreserveVideoMemoryAllocations=1" \ + > /etc/modprobe.d/nvidia-kms.conf + # Generate CDI specification for nvidia-container-toolkit (modern approach for podman/crun) # Install service to generate CDI spec on first boot COPY systemd/nvidia-cdi-generator.service /etc/systemd/system/nvidia-cdi-generator.service diff --git a/hypervisor-nvidia-rpmfusion.Containerfile b/hypervisor-nvidia-rpmfusion.Containerfile index 84ad151..a6992a4 100644 --- a/hypervisor-nvidia-rpmfusion.Containerfile +++ b/hypervisor-nvidia-rpmfusion.Containerfile @@ -1,4 +1,28 @@ -FROM ghcr.io/bensmith/hypervisor-bootc:latest +ARG BASE=ghcr.io/bensmith/hypervisor-bootc:latest +ARG BASE_DIGEST="" + +# Stage 1: Build the NVIDIA kernel module RPM +FROM ${BASE}${BASE_DIGEST:+@${BASE_DIGEST}} AS kmod-builder + +RUN dnf install -y \ + https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm \ + https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm + +RUN KERNEL_VERSION=$(rpm -q kernel --qf '%{version}-%{release}.%{arch}\n' | tail -1) && \ + dnf install --setopt=install_weak_deps=False -y \ + akmod-nvidia \ + "kernel-devel-${KERNEL_VERSION}" && \ + dnf clean all + +RUN mkdir -p /var/log/akmods && \ + chmod 1777 /tmp /var/tmp && \ + KERNEL_VERSION=$(rpm -q kernel --qf '%{version}-%{release}.%{arch}\n' | tail -1) && \ + akmods --force --kernels "${KERNEL_VERSION}" && \ + find /var/cache/akmods -name '*.rpm' | tee /dev/stderr | grep -q . || \ + { find /var/cache/akmods -name '*.failed.log' -exec cat {} +; exit 1; } + +# Stage 2: Final bootc image +FROM ${BASE}${BASE_DIGEST:+@${BASE_DIGEST}} # Add RPMFusion repositories for NVIDIA proprietary drivers RUN dnf install -y \ @@ -11,14 +35,28 @@ RUN curl -s -L https://nvidia.github.io/libnvidia-container/stable/rpm/nvidia-co # Install NVIDIA drivers and tools, headless RUN dnf install --setopt=install_weak_deps=False -y \ - akmod-nvidia \ nvidia-container-toolkit \ nvidia-gpu-firmware \ nvidia-modprobe \ - nvidia-persistenced && \ + nvidia-persistenced \ + xorg-x11-drv-nvidia \ + xorg-x11-drv-nvidia-cuda \ + xorg-x11-drv-nvidia-cuda-libs \ + xorg-x11-drv-nvidia-libs && \ dnf clean all && \ rm -rf /var/log/* /var/cache/* /var/lib/dnf/* /boot/* +# Install pre-built kmod RPM from builder stage +COPY --from=kmod-builder /var/cache/akmods/ /tmp/akmods/ +RUN find /tmp/akmods -name '*.rpm' -exec rpm -ivh {} + && \ + rm -rf /tmp/akmods + +# Blacklist nouveau and configure proprietary driver for KMS/Wayland +RUN echo -e "blacklist nouveau\noptions nouveau modeset=0" \ + > /etc/modprobe.d/blacklist-nouveau.conf && \ + echo -e "options nvidia-drm modeset=1 fbdev=1\noptions nvidia NVreg_PreserveVideoMemoryAllocations=1" \ + > /etc/modprobe.d/nvidia-kms.conf + # Generate CDI specification for nvidia-container-toolkit (modern approach for podman/crun) # Install service to generate CDI spec on first boot COPY systemd/nvidia-cdi-generator.service /etc/systemd/system/nvidia-cdi-generator.service