name: Build

on:
  push:
  pull_request:
  workflow_dispatch:

env:
  MAKEFLAGS: "-j4 GITVERSION=${{ github.sha }}"

jobs:

  bios:
    name: BIOS / ${{ matrix.arch }}
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        arch:
          - i386
          - x86_64
    container:
      image: ghcr.io/ipxe/ipxe-builder-${{ matrix.arch }}
    env:
      bindir: >-
        ${{ matrix.arch == 'i386' && 'bin' || 'bin-x86_64-pcbios' }}
    steps:

      - name: Check out code
        uses: actions/checkout@v6

      - name: Build
        working-directory: src
        run: |
          make ${{ env.bindir }}/10ec8139.rom \
               ${{ env.bindir }}/8086100e.mrom \
               ${{ env.bindir }}/ipxe.dsk \
               ${{ env.bindir }}/ipxe.iso \
               ${{ env.bindir }}/ipxe.lkrn \
               ${{ env.bindir }}/ipxe.pxe \
               ${{ env.bindir }}/ipxe.usb \
               ${{ env.bindir }}/ipxe-legacy.lkrn \
               ${{ env.bindir }}/ipxe-legacy.pxe \
               ${{ env.bindir }}/undionly.kpxe \
               ${{ env.bindir }}/errors

      - name: Hardware list
        working-directory: src
        run: |
          ./util/niclist.pl \
              --format dokuwiki \
              --sort bus-,vendor_id,device_id,device_name \
              --output ${{ env.bindir }}/niclist.txt

      - name: Upload
        uses: actions/upload-artifact@v6
        with:
          name: ${{ env.bindir }}
          if-no-files-found: error
          path: |
            src/${{ env.bindir }}/ipxe.lkrn
            src/${{ env.bindir }}/ipxe.pxe
            src/${{ env.bindir }}/ipxe-legacy.lkrn
            src/${{ env.bindir }}/ipxe-legacy.pxe
            src/${{ env.bindir }}/undionly.kpxe
            src/${{ env.bindir }}/errors
            src/${{ env.bindir }}/niclist.txt

  sbi:
    name: SBI / ${{ matrix.arch }}
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        arch:
          - riscv32
          - riscv64
    container:
      image: ghcr.io/ipxe/ipxe-builder-${{ matrix.arch }}
    env:
      bindir: bin-${{ matrix.arch }}
    steps:

      - name: Check out code
        uses: actions/checkout@v6

      - name: Build
        working-directory: src
        run: |
          make ${{ env.bindir }}/ipxe.pf32 \
               ${{ env.bindir }}/ipxe.lkrn \
               ${{ env.bindir }}/ipxe.sbi \
               ${{ env.bindir }}/errors

      - name: Upload
        uses: actions/upload-artifact@v6
        with:
          name: ${{ env.bindir }}
          if-no-files-found: error
          path: |
            src/${{ env.bindir }}/ipxe.lkrn
            src/${{ env.bindir }}/ipxe.sbi
            src/${{ env.bindir }}/errors

  uefi:
    name: UEFI / ${{ matrix.arch }}
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        arch:
          - arm32
          - arm64
          - i386
          - loong64
          - riscv32
          - riscv64
          - x86_64
    container:
      image: ghcr.io/ipxe/ipxe-builder-${{ matrix.arch }}
    env:
      bindir: bin-${{ matrix.arch }}-efi
    steps:

      - name: Check out code
        uses: actions/checkout@v6

      - name: Build
        working-directory: src
        run: |
          make ${{ env.bindir }}/ipxe.efi \
               ${{ env.bindir }}/ipxe.iso \
               ${{ env.bindir }}/ipxe.usb \
               ${{ env.bindir }}/ipxe-legacy.efi \
               ${{ env.bindir }}/snponly.efi \
               ${{ env.bindir }}/errors

      - name: Upload
        uses: actions/upload-artifact@v6
        with:
          name: ${{ env.bindir }}
          if-no-files-found: error
          path: |
            src/${{ env.bindir }}/ipxe.efi
            src/${{ env.bindir }}/ipxe-legacy.efi
            src/${{ env.bindir }}/snponly.efi
            src/${{ env.bindir }}/errors

  uefi-sb:
    name: UEFI SB / ${{ matrix.arch }}
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        arch:
          - arm64
          - x86_64
    container:
      image: ghcr.io/ipxe/ipxe-builder-${{ matrix.arch }}
    env:
      bindir: bin-${{ matrix.arch }}-efi-sb
    outputs:
      sbsignenv: ${{ steps.sbsignenv.outputs.sbsignenv }}
    steps:

      - name: Check out code
        uses: actions/checkout@v6

      - name: Build
        working-directory: src
        run: |
          make ${{ env.bindir }}/ipxe.efi \
               ${{ env.bindir }}/snponly.efi

      - name: Upload
        uses: actions/upload-artifact@v6
        with:
          name: unsigned-${{ env.bindir }}
          if-no-files-found: error
          path: |
            src/${{ env.bindir }}/ipxe.efi
            src/${{ env.bindir }}/snponly.efi

      - name: Select environment
        id: sbsignenv
        if: >-
          github.ref == 'refs/heads/sbsign' ||
          startsWith ( github.ref, 'refs/tags/v' )
        run: |
          echo "sbsignenv=${{ vars.SBSIGN_ENVIRONMENT }}" >> $GITHUB_OUTPUT

  sbsign:
    name: SB Sign / ${{ matrix.arch }}
    runs-on: ${{ needs.uefi-sb.outputs.sbsignenv || 'ubuntu-latest' }}
    needs:
      - uefi-sb
    strategy:
      fail-fast: false
      matrix:
        arch:
          - arm64
          - x86_64
    container:
      image: ghcr.io/ipxe/ipxe-signer
      volumes:
        - run-pcscd:/run/pcscd
    services:
      pcscd:
        image: ghcr.io/ipxe/ipxe-signer-pcscd
        volumes:
          - run-pcscd:/run/pcscd
        options: >-
          ${{ needs.uefi-sb.outputs.sbsignenv && '--device /dev/bus/usb' }}
          --label OPTIONS_VALUE_CANNOT_BE_EMPTY=1
    env:
      binaries: >-
        ipxe.efi
        snponly.efi
      bindir: bin-${{ matrix.arch }}-efi-sb
      cacert: ${{ vars.SBSIGN_CA_CERT || 'testsign.crt' }}
      pkcs11: ${{ secrets.SBSIGN_PASSWORD && 'true' }}
      signcerts: ${{ vars.SBSIGN_CERTS || 'testsign.crt' }}
      signkey: ${{ vars.SBSIGN_KEY || 'testsign.key' }}
      signpass: ${{ secrets.SBSIGN_PASSWORD || 'testpw' }}
    environment: ${{ needs.uefi-sb.outputs.sbsignenv }}
    steps:

      - name: Check out code
        uses: actions/checkout@v6
        with:
          repository: ipxe/secure-boot-ca

      - name: Download
        uses: actions/download-artifact@v7
        with:
          name: unsigned-${{ env.bindir }}
          path: unsigned

      - name: Test certificate
        run: |
          COMMITID=$(echo ${{ github.sha }} | cut -c1-9)
          SUBJECT="iPXE Test Signing (${COMMITID}, ${{ matrix.arch }})"
          openssl req \
              -newkey rsa:2048 -passout 'pass:testpw' -keyout testsign.key \
              -subj "/CN=${SUBJECT}/" -x509 -out testsign.crt

      - name: Certificate chain
        run: |
          for cert in ${{ env.signcerts }} ; do
              openssl x509 -in ${cert} -noout -text
              cat ${cert} >> chain.crts
          done

      - name: Sign
        run: |
          for binary in ${{ env.binaries }} ; do
              osslsigncode sign \
                  ${{ env.pkcs11 && '-pkcs11module' }} \
                  ${{ env.pkcs11 && '/usr/lib64/opensc-pkcs11.so' }} \
                  -certs chain.crts \
                  -key ${{ env.signkey }} \
                  -pass ${{ env.signpass }} \
                  -ts http://timestamp.digicert.com \
                  -in unsigned/${binary} \
                  -out ${binary}
          done

      - name: Verify
        run: |
          for binary in ${{ env.binaries }} ; do
              osslsigncode verify -CAfile ${{ env.cacert }} ${binary}
          done

      - name: Upload
        uses: actions/upload-artifact@v6
        with:
          name: ${{ env.bindir }}
          if-no-files-found: error
          path: |
            ${{ env.cacert }}
            ipxe.efi
            snponly.efi

  linux:
    name: Linux / ${{ matrix.arch }}
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        include:
          - arch: arm32
            exec: qemu-arm-static
          - arch: arm64
            exec: qemu-aarch64-static
          - arch: i386
            exec: valgrind
          - arch: loong64
            exec: qemu-loongarch64-static
          - arch: riscv32
            exec: qemu-riscv32-static
          - arch: riscv64
            exec: qemu-riscv64-static
          - arch: x86_64
            exec: valgrind
    container:
      image: ghcr.io/ipxe/ipxe-builder-${{ matrix.arch }}
    env:
      bindir: bin-${{ matrix.arch }}-linux
    steps:

      - name: Check out code
        uses: actions/checkout@v6

      - name: Build
        working-directory: src
        run: |
          make ${{ env.bindir }}/ipxe.linux \
               ${{ env.bindir }}/tests.linux \
               ${{ env.bindir }}/errors

      - name: Upload
        uses: actions/upload-artifact@v6
        with:
          name: ${{ env.bindir }}
          if-no-files-found: error
          path: |
            src/${{ env.bindir }}/ipxe.linux
            src/${{ env.bindir }}/tests.linux
            src/${{ env.bindir }}/errors

      - name: Test
        working-directory: src
        run: |
          ${{ matrix.exec }} ${{ env.bindir }}/tests.linux

  shim:
    name: UEFI shim
    runs-on: ubuntu-latest
    env:
      shims: >-
        shimaa64.efi
        shimx64.efi
      shimurl: >-
        https://github.com/ipxe/shim/releases/latest/download
    steps:

      - name: Download
        run: |
          for shim in ${{ env.shims }} ; do
              curl -L ${{ env.shimurl }}/ipxe-${shim} -o ${shim}
          done

      - name: Upload
        uses: actions/upload-artifact@v6
        with:
          name: shim
          if-no-files-found: error
          path: |
            shimaa64.efi
            shimx64.efi

  combine:
    name: Combine
    runs-on: ubuntu-latest
    needs:
      - bios
      - uefi
      - sbsign
      - shim
    container:
      image: ghcr.io/ipxe/ipxe-builder-utils
    env:
      efishims: >-
        -e shim/shimaa64.efi
        -e shim/shimx64.efi
      fsbinaries: >-
        bin-x86_64-pcbios/${DRIVERS}.lkrn
        bin-arm32-efi/${DRIVERS}.efi
        bin-arm64-efi/${DRIVERS}.efi
        bin-i386-efi/${DRIVERS}.efi
        bin-loong64-efi/${DRIVERS}.efi
        bin-riscv32-efi/${DRIVERS}.efi
        bin-riscv64-efi/${DRIVERS}.efi
        bin-x86_64-efi/${DRIVERS}.efi
      sbarchs: >-
        arm64
        x86_64
      sbbinaries: >-
        bin-${ARCH}-efi-sb/ipxe.efi
      srvbinaries: >-
        bin/ipxe.pxe
        bin/ipxe-legacy.pxe
        bin/undionly.kpxe
        bin-arm32-efi/ipxe.efi
        bin-arm32-efi/ipxe-legacy.efi
        bin-arm32-efi/snponly.efi
        bin-arm64-efi/ipxe.efi
        bin-arm64-efi/ipxe-legacy.efi
        bin-arm64-efi/snponly.efi
        bin-arm64-efi-sb/ipxe.efi
        bin-arm64-efi-sb/snponly.efi
        bin-i386-efi/ipxe.efi
        bin-i386-efi/ipxe-legacy.efi
        bin-i386-efi/snponly.efi
        bin-loong64-efi/ipxe.efi
        bin-loong64-efi/ipxe-legacy.efi
        bin-loong64-efi/snponly.efi
        bin-riscv32-efi/ipxe.efi
        bin-riscv32-efi/ipxe-legacy.efi
        bin-riscv32-efi/snponly.efi
        bin-riscv64-efi/ipxe.efi
        bin-riscv64-efi/ipxe-legacy.efi
        bin-riscv64-efi/snponly.efi
        bin-x86_64-efi/ipxe.efi
        bin-x86_64-efi/ipxe-legacy.efi
        bin-x86_64-efi/snponly.efi
        bin-x86_64-efi-sb/ipxe.efi
        bin-x86_64-efi-sb/snponly.efi
        bin-x86_64-pcbios/ipxe.pxe
        bin-x86_64-pcbios/ipxe-legacy.pxe
        bin-x86_64-pcbios/undionly.kpxe
    steps:

      - name: Check out code
        uses: actions/checkout@v6

      - name: Download
        uses: actions/download-artifact@v7
        with:
          pattern: "{bin,bin-x86_64-pcbios,bin-*-efi,bin-*-efi-sb,shim}"

      - name: Autoexec
        run: |
          # Provide an editable placeholder autoexec.ipxe for the USB image
          cat > autoexec.ipxe <<'EOF'
          #!ipxe
          echo
          prompt --key 0x02 --timeout 2000 \
              Press Ctrl-B for the iPXE command line... \
              && shell || autoboot
          EOF

      - name: ISO + USB
        run: |
          for DRIVERS in ipxe ipxe-legacy ; do
              ./src/util/genfsimg -o ${DRIVERS}.iso ${{ env.fsbinaries }}
              ./src/util/genfsimg -o ${DRIVERS}.usb -s autoexec.ipxe \
                                  ${{ env.fsbinaries }}
          done

      - name: ISO + USB (SB)
        run: |
          for ARCH in ${{ env.sbarchs }} ; do
            ./src/util/genfsimg -o ipxe-${ARCH}-sb.iso \
                                ${{ env.efishims }} ${{ env.sbbinaries }}
            ./src/util/genfsimg -o ipxe-${ARCH}-sb.usb -s autoexec.ipxe \
                                ${{ env.efishims }} ${{ env.sbbinaries }}
          done

      - name: Server
        run: |
          ./src/util/gensrvimg -o ipxeboot.tar.gz ${{ env.efishims }} \
              ${{ env.srvbinaries }}

      - name: Upload
        uses: actions/upload-artifact@v6
        with:
          name: bin-combi
          if-no-files-found: error
          path: |
            ipxe.iso
            ipxe.usb
            ipxe-legacy.iso
            ipxe-legacy.usb
            ipxe-arm64-sb.iso
            ipxe-arm64-sb.usb
            ipxe-x86_64-sb.iso
            ipxe-x86_64-sb.usb
            ipxeboot.tar.gz

  version:
    name: Version
    runs-on: ubuntu-latest
    container:
      image: ghcr.io/ipxe/ipxe-builder-utils
    env:
      bindir: bin
    steps:

      - name: Check out code
        uses: actions/checkout@v6

      - name: Build
        working-directory: src
        run: |
          make REPOURL=${{ github.server_url }}/${{ github.repository }} \
               ${{ env.bindir }}/version.txt \
               ${{ env.bindir }}/relname.txt \
               ${{ env.bindir }}/reltitle.txt \
               ${{ env.bindir }}/relnotes.md

      - name: Upload
        uses: actions/upload-artifact@v6
        with:
          name: version
          if-no-files-found: error
          path: |
            src/${{ env.bindir }}/version.txt
            src/${{ env.bindir }}/relname.txt
            src/${{ env.bindir }}/reltitle.txt
            src/${{ env.bindir }}/relnotes.md

  publish:
    name: Publish
    runs-on: ubuntu-latest
    needs:
      - bios
      - sbi
      - uefi
      - sbsign
      - linux
      - shim
      - combine
      - version
    if: >-
      github.ref == 'refs/heads/master' &&
      vars.PAGES_REPO_NAME
    env:
      workflow_url: >-
        ${{ github.server_url }}/${{ vars.PAGES_REPO_OWNER }}/${{ ''
        }}${{ vars.PAGES_REPO_NAME }}/actions/workflows/build.yml
    environment:
      name: publish
      url: ${{ env.workflow_url }}
    steps:

      - name: Get token
        id: token
        uses: actions/create-github-app-token@v2
        with:
          app-id: ${{ vars.WORKFLOW_DISPATCHER_ID }}
          private-key: ${{ secrets.WORKFLOW_DISPATCHER_KEY }}
          owner: ${{ vars.PAGES_REPO_OWNER }}
          repositories: ${{ vars.PAGES_REPO_NAME }}

      - name: Dispatch
        env:
          GH_REPO: ${{ vars.PAGES_REPO_OWNER }}/${{ vars.PAGES_REPO_NAME }}
          GH_TOKEN: ${{ steps.token.outputs.token }}
        run: |
          gh workflow run build.yml -f run_id=${{ github.run_id }}
          echo "Results at ${{ env.workflow_url }}"

  release:
    name: Release
    runs-on: ubuntu-latest
    needs:
      - bios
      - sbi
      - uefi
      - sbsign
      - linux
      - shim
      - combine
      - version
    if: >-
      startsWith ( github.ref, 'refs/tags/v' )
    steps:

      - name: Download
        uses: actions/download-artifact@v7
        with:
          pattern: "{bin-combi,version}"

      - name: Tag check
        run: |
          RELNAME=$(cat version/relname.txt)
          if [ "${{ github.ref_name }}" != "${RELNAME}" ] ; then
              echo "ERROR: Tag does not match release name ${RELNAME}" >&2
              exit 1
          fi

      - name: Release
        env:
          GH_REPO: ${{ github.repository }}
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          gh release create "${{ github.ref_name }}" --verify-tag --draft \
              --title "$(cat version/relname.txt)" \
              --notes-file version/relnotes.md \
              bin-combi/ipxe.iso bin-combi/ipxe.usb \
              bin-combi/ipxe-arm64-sb.iso bin-combi/ipxe-arm64-sb.usb \
              bin-combi/ipxe-x86_64-sb.iso bin-combi/ipxe-x86_64-sb.usb \
              bin-combi/ipxeboot.tar.gz
