🏡 index : quine-global-labs/mirror-ublue-os-akmods.git

name: build-akmod
run-name: "Build ${{ inputs.akmods_target != 'common' && format('akmods-{0}', inputs.akmods_target) || 'akmods' }}:${{ inputs.kernel_flavor }}-${{ inputs.version }}"
on:
  workflow_call:
    inputs:
      akmods_target:
        description: "The akmods to build: common, zfs, nvidia-lts, nvidia-open"
        required: true
        type: string
      architecture:
        description: "JSON string of architectures to build, '[aarch64, x86_64]'"
        default: "['x86_64']"
        required: false
        type: string
      kernel_flavor:
        description: "The Kernel flavor: main, coreos-stable, longterm-6.12, etc"
        required: true
        type: string
      version:
        description: "Release Version: 10, 41, 42, etc"
        required: true
        type: string
      kernel_cache_key:
        description: "The Key for the Kernel Cache"
        required: true
        type: string
      force:
        description: "Force a build, ignoring the fingerprint guard"
        default: false
        required: false
        type: boolean
  workflow_dispatch:
    inputs:
      akmods_target:
        description: "The akmods to build: common, zfs, nvidia-lts, nvidia-open"
        required: true
        type: string
      architecture:
        description: "JSON string of architectures to build, '[aarch64, x86_64]'"
        default: "['x86_64']"
        required: false
        type: string
      kernel_flavor:
        description: "The Kernel flavor: main, coreos-stable, longterm-6.12, etc"
        required: true
        type: string
      version:
        description: "Release Version: 10, 41, 42, etc"
        required: true
        type: string
      kernel_cache_key:
        description: "The Key for the Kernel Cache"
        required: true
        type: string
      force:
        description: "Force a build, ignoring the fingerprint guard"
        default: false
        required: false
        type: boolean
env:
  AKMODS_KERNEL: ${{ inputs.kernel_flavor }}
  AKMODS_TARGET: ${{ inputs.akmods_target }}
  AKMODS_VERSION: ${{ inputs.version }}
  KCKEY: ${{ inputs.kernel_cache_key }}

concurrency:
  group: ${{ github.workflow }}-${{ github.ref || github.run_id }}-${{ inputs.version }}-${{ inputs.kernel_flavor }}-${{ inputs.akmods_target }}
  cancel-in-progress: true

jobs:
  build-akmods:
    name: Build ${{ matrix.architecture }}
    runs-on: ${{ matrix.architecture == 'x86_64' && 'ubuntu-26.04' || 'ubuntu-26.04-arm' }}
    permissions:
      actions: read
      contents: read
      packages: write
    container:
      image: "ghcr.io/ublue-os/devcontainer:latest@sha256:ec1046d2cb7d16e3cefaaae0915764f9ed4d4d74eabc25d1931923ce6670858d"
      options: "--privileged --volume /:/host-sys --volume /var/lib/containers:/var/lib/containers --security-opt seccomp=unconfined --security-opt label=disable --user 0:0"
    strategy:
      fail-fast: false
      matrix:
        architecture: ${{ fromJson(inputs.architecture) }}
    steps:
      - name: Checkout Repo
        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

      - name: Mark Directory as Safe
        run: git config --system --add safe.directory "$GITHUB_WORKSPACE"

      - name: Export GITHUB_WORKSPACE
        id: gh-workspace
        run: echo "GITHUB_WORKSPACE=$GITHUB_WORKSPACE" >> "$GITHUB_OUTPUT"
        
      - name: Cache Kernel RPMs
        id: cache-kernel
        uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
        with:
          path: ${{ steps.gh-workspace.outputs.GITHUB_WORKSPACE }}/build/${{ env.AKMODS_KERNEL }}-${{ env.AKMODS_VERSION }}/KCWD/rpms
          fail-on-cache-miss: true
          key: ${{ env.KCKEY }}-${{ matrix.architecture }}
          restore-keys: |
            ${{ matrix.architecture }}-${{ env.KCKEY }}
            ${{ matrix.architecture }}-${{ env.AKMODS_KERNEL }}

      - name: Install Cosign
        continue-on-error: true
        uses: sigstore/cosign-installer@756fe2d284c7292c4e9b62c8a28f1e5629c59532 # v4.1.3
        with:
          cosign-release: "v3.1.4"

      # Every event prepares the snapshot, so test and build pin the same inputs
      - name: Prepare Fingerprint
        continue-on-error: true
        run: just fingerprint || just fingerprint-clear

      - name: Check if build needed
        id: guard
        continue-on-error: true
        env:
          AKMODS_FORCE_BUILD: ${{ inputs.force || github.event_name == 'pull_request' }}
          AKMODS_MAX_AGE_DAYS: ${{ vars.AKMODS_MAX_AGE_DAYS }}
        run: just build-needed

      - name: Maximize build space
        if: ${{ !(steps.guard.outcome == 'success' && steps.guard.outputs.skip == 'true') }}
        shell: bash
        run: |
          set -xeuo pipefail
          df -h
          # Remove host toolchains, browsers
          shopt -s nullglob
          printf '/host-sys%s\0' /opt/PyPy /opt/az /opt/node/ /opt/pipx /opt/go /opt/Ruby \
              /usr/lib/llvm-* /usr/local/julia* /usr/local/lib/android /usr/share/dotnet \
              /usr/share/swift /usr/local/.ghcup /usr/lib/firefox /opt/google/chrome \
              /opt/microsoft/msedge \
              | xargs -0 -n1 -P4 rm -rf
          df -h

      - name: Retrieve Signing Key
        if: contains(fromJson('["schedule", "workflow_dispatch", "merge_group"]'), github.event_name) && !(steps.guard.outcome == 'success' && steps.guard.outputs.skip == 'true')
        shell: bash
        run: |
          mkdir -p certs
          echo "${{ secrets.KERNEL_PRIVKEY }}" > certs/private_key.priv
          echo "${{ secrets.AKMOD_PRIVKEY_20230518 }}" > certs/private_key_2.priv
          # DEBUG: get character count of key
          wc -c certs/private_key.priv
          wc -c certs/private_key_2.priv

      - name: Test Image
        if: ${{ !(steps.guard.outcome == 'success' && steps.guard.outputs.skip == 'true') }}
        run: just test
        
      - name: Build Image
        if: ${{ !(steps.guard.outcome == 'success' && steps.guard.outputs.skip == 'true') }}
        run: just build

      - name: Push and Sign Image
        if: (contains(fromJson('["schedule", "merge_group"]'), github.event_name) || (github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main')) && !(steps.guard.outcome == 'success' && steps.guard.outputs.skip == 'true')
        run: |
          just login
          just push
        env:
          COSIGN_PRIVATE_KEY: ${{ secrets.SIGNING_SECRET }}
          REGISTRY_ACTOR: ${{ github.actor }}
          REGISTRY_TOKEN: ${{ github.token }}

  manifest:
    name: "Generate ${{ inputs.akmods_target != 'common' && format('akmods-{0}', inputs.akmods_target) || 'akmods' }}:${{ inputs.kernel_flavor }}-${{ inputs.version }} Manifest"
    runs-on: ubuntu-26.04
    needs: [build-akmods]
    outputs:
      image_name: ${{ steps.digest.outputs.image_name }}
      manifest: ${{ steps.digest.outputs.manifest }}
      manifest_kernel: ${{ steps.digest.outputs.manifest_kernel }}
    container:
      image: "ghcr.io/ublue-os/devcontainer:latest@sha256:ec1046d2cb7d16e3cefaaae0915764f9ed4d4d74eabc25d1931923ce6670858d"
      options: "--privileged --volume /var/lib/containers:/var/lib/containers --security-opt seccomp=unconfined --security-opt label=disable --user 0:0"
    permissions:
      actions: read
      attestations: write
      contents: read
      id-token: write
      packages: write
    steps:
      - name: Exit on failure
        if: needs.build-akmods.result != 'success'
        run: exit 1
      
      - name: Checkout Repo
        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

      - name: Mark Directory as Safe
        run: git config --system --add safe.directory "$GITHUB_WORKSPACE"

      - name: Export GITHUB_WORKSPACE
        id: gh-workspace
        run: echo "GITHUB_WORKSPACE=$GITHUB_WORKSPACE" >> "$GITHUB_OUTPUT"

      - name: Cache Kernel RPMs
        id: cache-kernel
        uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
        with:
          path: ${{ steps.gh-workspace.outputs.GITHUB_WORKSPACE }}/build/${{ env.AKMODS_KERNEL }}-${{ env.AKMODS_VERSION }}/KCWD/rpms
          key: ${{ env.KCKEY }}-x86_64
          restore-keys: |
            ${{ env.KCKEY }}-aarch64

      - name: Install Cosign
        if: contains(fromJson('["schedule", "workflow_dispatch", "merge_group"]'), github.event_name)
        continue-on-error: true
        uses: sigstore/cosign-installer@756fe2d284c7292c4e9b62c8a28f1e5629c59532 # v4.1.3
        with:
          cosign-release: "v3.1.4"

      # Attestations and digests always run, so provenance gets repaired even when this skips
      - name: Check if manifest needed
        id: manifest-guard
        if: contains(fromJson('["schedule", "workflow_dispatch", "merge_group"]'), github.event_name)
        continue-on-error: true
        run: just manifest-needed

      - name: Generate Manifest
        if: contains(fromJson('["schedule", "workflow_dispatch", "merge_group"]'), github.event_name) && !(steps.manifest-guard.outcome == 'success' && steps.manifest-guard.outputs.skip == 'true')
        run: |
          just login
          just manifest
        env:
          COSIGN_PRIVATE_KEY: ${{ secrets.SIGNING_SECRET }}
          REGISTRY_ACTOR: ${{ github.actor }}
          REGISTRY_TOKEN: ${{ github.token }}

      - name: Get Manifest Digests
        id: digest
        if: contains(fromJson('["schedule", "workflow_dispatch", "merge_group"]'), github.event_name)
        run: |
          IMAGE_NAME="${{ inputs.akmods_target != 'common' && format('akmods-{0}', inputs.akmods_target) || 'akmods' }}"
          MANIFEST_TAG="${{ inputs.kernel_flavor }}-${{ inputs.version }}"
          KERNEL_RELEASE="$(jq -r '.kernel_release' < "$GITHUB_WORKSPACE/build/${{ env.AKMODS_KERNEL }}-${{ env.AKMODS_VERSION }}/KCWD/rpms/cache.json" | sed 's/\.x86_64\|\.aarch64//g')"
          MANIFEST_TAG_KERNEL="${MANIFEST_TAG}-${KERNEL_RELEASE}"

          DIGEST="$(skopeo inspect --format '{{.Digest}}' "docker://ghcr.io/${{ github.repository_owner }}/${IMAGE_NAME}:${MANIFEST_TAG}")"
          echo "manifest=${DIGEST}" >> "$GITHUB_OUTPUT"

          DIGEST_KERNEL="$(skopeo inspect --format '{{.Digest}}' "docker://ghcr.io/${{ github.repository_owner }}/${IMAGE_NAME}:${MANIFEST_TAG_KERNEL}")"
          echo "manifest_kernel=${DIGEST_KERNEL}" >> "$GITHUB_OUTPUT"

          echo "image_name=${IMAGE_NAME}" >> "$GITHUB_OUTPUT"

      - name: Attest Manifest Image
        if: contains(fromJson('["schedule", "workflow_dispatch", "merge_group"]'), github.event_name)
        uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4
        with:
          subject-name: ghcr.io/${{ github.repository_owner }}/${{ steps.digest.outputs.image_name }}
          subject-digest: ${{ steps.digest.outputs.manifest }}
          push-to-registry: false

      - name: Attest Manifest Kernel Image
        if: contains(fromJson('["schedule", "workflow_dispatch", "merge_group"]'), github.event_name)
        uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4
        with:
          subject-name: ghcr.io/${{ github.repository_owner }}/${{ steps.digest.outputs.image_name }}
          subject-digest: ${{ steps.digest.outputs.manifest_kernel }}
          push-to-registry: false

  verify-publication:
    name: Verify Published Signatures and Provenance
    runs-on: ubuntu-26.04
    needs: [manifest]
    if: contains(fromJson('["schedule", "workflow_dispatch", "merge_group"]'), github.event_name)
    permissions:
      actions: read
      attestations: write
      contents: read
      id-token: write
      packages: read
    steps:
      - name: Checkout Repo
        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

      - name: Install Skopeo
        shell: bash
        run: |
          sudo apt-get update
          sudo apt-get install --yes skopeo
          command -v skopeo
          skopeo --version

      - name: Install Cosign
        uses: sigstore/cosign-installer@756fe2d284c7292c4e9b62c8a28f1e5629c59532 # v4.1.3
        with:
          cosign-release: "v3.1.4"

      - name: Verify Published Subjects
        env:
          GH_TOKEN: ${{ github.token }}
          IMAGE: ghcr.io/${{ github.repository_owner }}/${{ needs.manifest.outputs.image_name }}
          MANIFEST_DIGEST: ${{ needs.manifest.outputs.manifest }}
          MANIFEST_KERNEL_DIGEST: ${{ needs.manifest.outputs.manifest_kernel }}
        shell: bash
        run: |
          set -euo pipefail

          cosign version
          command -v gh
          gh --version

          retry() {
            local attempt
            for attempt in 1 2 3 4 5; do
              "$@" && return 0
              if [[ "$attempt" -eq 5 ]]; then
                return 1
              fi
              sleep $((attempt * 5))
            done
          }

          verify_cosign() {
            local image="$1"
            retry cosign verify --key cosign.pub "$image"
            retry cosign verify --new-bundle-format=false --key cosign.pub "$image"
          }

          policy_dir="$(mktemp -d)"
          trap 'rm -rf "$policy_dir"' EXIT
          cat > "$policy_dir/policy.json" <<'EOF'
          {
            "default": [{"type": "reject"}],
            "transports": {
              "docker": {
                "ghcr.io/ublue-os/akmods": [{"type": "sigstoreSigned", "keyPath": "cosign.pub", "signedIdentity": {"type": "matchRepository"}}],
                "ghcr.io/ublue-os/akmods-nvidia-lts": [{"type": "sigstoreSigned", "keyPath": "cosign.pub", "signedIdentity": {"type": "matchRepository"}}],
                "ghcr.io/ublue-os/akmods-nvidia-open": [{"type": "sigstoreSigned", "keyPath": "cosign.pub", "signedIdentity": {"type": "matchRepository"}}],
                "ghcr.io/ublue-os/akmods-zfs": [{"type": "sigstoreSigned", "keyPath": "cosign.pub", "signedIdentity": {"type": "matchRepository"}}],
                "ghcr.io/ublue-os/akmods-extra": [{"type": "sigstoreSigned", "keyPath": "cosign.pub", "signedIdentity": {"type": "matchRepository"}}]
              }
            }
          }
          EOF
          mkdir "$policy_dir/registries.d"
          cat > "$policy_dir/registries.d/ghcr.yaml" <<'EOF'
          docker:
            ghcr.io:
              use-sigstore-attachments: true
          EOF

          verify_policy() {
            local image="$1"
            local destination
            destination="$(mktemp -d)"
            retry skopeo copy --policy "$policy_dir/policy.json" --registries.d "$policy_dir/registries.d" "$image" "dir:$destination"
            rm -rf "$destination"
          }

          manifest_tag="${{ inputs.kernel_flavor }}-${{ inputs.version }}"
          subjects=(
            "$IMAGE@$MANIFEST_DIGEST"
            "$IMAGE@$MANIFEST_KERNEL_DIGEST"
          )
          for architecture in $(jq -r '.[]' <<'EOF'
          ${{ inputs.architecture }}
          EOF
          ); do
            release_digest="$(retry skopeo inspect --format '{{.Digest}}' "docker://$IMAGE:${manifest_tag}-${architecture}")"
            subjects+=("$IMAGE@$release_digest")
          done

          for subject in "${subjects[@]}"; do
            verify_cosign "$subject"
            verify_policy "docker://$subject"
          done

          unsigned="quay.io/fedora/fedora-bootc@sha256:0cb32268d1a864801a6edc4032506f50f385d9f49c0b68a4eebecaaa555ecce6"
          if cosign verify --key cosign.pub "$unsigned"; then
            exit 1
          fi
          if cosign verify --new-bundle-format=false --key cosign.pub "$unsigned"; then
            exit 1
          fi
          unsigned_destination="$(mktemp -d)"
          if skopeo copy --policy "$policy_dir/policy.json" --registries.d "$policy_dir/registries.d" "docker://$unsigned" "dir:$unsigned_destination"; then
            rm -rf "$unsigned_destination"
            exit 1
          fi
          rm -rf "$unsigned_destination"

          retry gh attestation verify "oci://$IMAGE@$MANIFEST_DIGEST" --repo ublue-os/akmods
          retry gh attestation verify "oci://$IMAGE@$MANIFEST_KERNEL_DIGEST" --repo ublue-os/akmods