name: Release

on:
  push:
    tags: ['v*']

permissions: read-all

env:
  REGISTRY: ghcr.io
  NORA: localhost:5000
  IMAGE_NAME: ${{ github.repository }}

jobs:
  build:
    name: Build & Push
    runs-on: [self-hosted, nora]
    permissions:
      contents: read
      packages: write
      id-token: write  # Sigstore cosign keyless signing

    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

      - name: Set up Rust
        run: |
          echo "/home/github-runner/.cargo/bin" >> $GITHUB_PATH
          echo "RUSTUP_HOME=/home/github-runner/.rustup" >> $GITHUB_ENV
          echo "CARGO_HOME=/home/github-runner/.cargo" >> $GITHUB_ENV

      - name: Build release binary (musl static)
        run: |
          cargo build --release --target x86_64-unknown-linux-musl --package nora-registry
          cp target/x86_64-unknown-linux-musl/release/nora ./nora

      - name: Upload binary artifact
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
        with:
          name: nora-binary-${{ github.run_id }}
          path: ./nora
          retention-days: 1

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
        with:
          driver-opts: network=host

      - name: Log in to GitHub Container Registry
        uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      # ── Alpine ───────────────────────────────────────────────────────────────
      - name: Extract metadata (alpine)
        id: meta-alpine
        uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6
        with:
          images: |
            ${{ env.NORA }}/${{ env.IMAGE_NAME }}
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            type=raw,value=latest

      - name: Build and push (alpine)
        uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7
        with:
          context: .
          file: Dockerfile
          platforms: linux/amd64
          push: true
          tags: ${{ steps.meta-alpine.outputs.tags }}
          labels: ${{ steps.meta-alpine.outputs.labels }}
          cache-from: type=registry,ref=${{ env.NORA }}/${{ env.IMAGE_NAME }}-cache:alpine,ignore-error=true
          cache-to: type=registry,ref=${{ env.NORA }}/${{ env.IMAGE_NAME }}-cache:alpine,mode=max

      # ── RED OS ───────────────────────────────────────────────────────────────
      - name: Extract metadata (redos)
        id: meta-redos
        uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6
        with:
          images: |
            ${{ env.NORA }}/${{ env.IMAGE_NAME }}
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          flavor: suffix=-redos,onlatest=true
          tags: |
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            type=raw,value=latest

      - name: Build and push (redos)
        uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7
        with:
          context: .
          file: Dockerfile.redos
          platforms: linux/amd64
          push: true
          tags: ${{ steps.meta-redos.outputs.tags }}
          labels: ${{ steps.meta-redos.outputs.labels }}
          cache-from: type=registry,ref=${{ env.NORA }}/${{ env.IMAGE_NAME }}-cache:redos,ignore-error=true
          cache-to: type=registry,ref=${{ env.NORA }}/${{ env.IMAGE_NAME }}-cache:redos,mode=max

      # ── Astra Linux SE ───────────────────────────────────────────────────────
      - name: Extract metadata (astra)
        id: meta-astra
        uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6
        with:
          images: |
            ${{ env.NORA }}/${{ env.IMAGE_NAME }}
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          flavor: suffix=-astra,onlatest=true
          tags: |
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            type=raw,value=latest

      - name: Build and push (astra)
        uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7
        with:
          context: .
          file: Dockerfile.astra
          platforms: linux/amd64
          push: true
          tags: ${{ steps.meta-astra.outputs.tags }}
          labels: ${{ steps.meta-astra.outputs.labels }}
          cache-from: type=registry,ref=${{ env.NORA }}/${{ env.IMAGE_NAME }}-cache:astra,ignore-error=true
          cache-to: type=registry,ref=${{ env.NORA }}/${{ env.IMAGE_NAME }}-cache:astra,mode=max

      # ── Smoke test ──────────────────────────────────────────────────────────
      - name: Install cosign
        uses: sigstore/cosign-installer@c56c2d3e59e4281cc41dea2217323ba5694b171e # v3

      - name: Sign Docker images (keyless Sigstore)
        run: |
          TAGS=($(echo "${{ steps.meta-alpine.outputs.tags }}" | tr "\n" " "))
          for tag in "${TAGS[@]}"; do
            [[ "$tag" == *"localhost"* ]] && continue
            cosign sign --yes "$tag"
          done

      - name: Smoke test — verify alpine image starts and responds
        run: |
          docker rm -f nora-smoke 2>/dev/null || true
          docker run --rm -d --name nora-smoke -p 5555:4000 -e NORA_HOST=0.0.0.0 \
            ${{ env.NORA }}/${{ env.IMAGE_NAME }}:latest
          for i in $(seq 1 10); do
            curl -sf http://localhost:5555/health && break || sleep 2
          done
          curl -sf http://localhost:5555/health
          docker stop nora-smoke

  scan:
    name: Scan (${{ matrix.name }})
    runs-on: [self-hosted, nora]
    needs: build
    permissions:
      contents: read
      packages: read
      security-events: write

    strategy:
      fail-fast: false
      matrix:
        include:
          - name: alpine
            suffix: ""
          - name: redos
            suffix: "-redos"
          - name: astra
            suffix: "-astra"

    steps:
      - name: Set version tag (strip leading v)
        id: ver
        run: echo "tag=${GITHUB_REF_NAME#v}" >> $GITHUB_OUTPUT

      - name: Trivy — image scan (${{ matrix.name }})
        uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0
        with:
          scan-type: image
          image-ref: ${{ env.NORA }}/${{ env.IMAGE_NAME }}:${{ steps.ver.outputs.tag }}${{ matrix.suffix }}
          format: sarif
          output: trivy-image-${{ matrix.name }}.sarif
          severity: HIGH,CRITICAL
          exit-code: 1

      - name: Upload Trivy image results to GitHub Security tab
        uses: github/codeql-action/upload-sarif@a60c4df7a135c7317c1e9ddf9b5a9b07a910dda9 # v4
        if: always()
        with:
          sarif_file: trivy-image-${{ matrix.name }}.sarif
          category: trivy-image-${{ matrix.name }}

  release:
    name: GitHub Release
    runs-on: [self-hosted, nora]
    needs: [build, scan]
    permissions:
      contents: write
      id-token: write  # Sigstore cosign keyless signing
      packages: write  # cosign needs push for signatures

    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

      - name: Set version tag (strip leading v)
        id: ver
        run: echo "tag=${GITHUB_REF_NAME#v}" >> $GITHUB_OUTPUT

      - name: Download binary artifact
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
        with:
          name: nora-binary-${{ github.run_id }}
          path: ./artifacts

      - name: Prepare binary
        run: |
          cp ./artifacts/nora ./nora-linux-amd64
          chmod +x ./nora-linux-amd64
          sha256sum ./nora-linux-amd64 > nora-linux-amd64.sha256
          echo "Binary size: $(du -sh nora-linux-amd64 | cut -f1)"
          cat nora-linux-amd64.sha256

      - name: Generate SLSA provenance
        uses: slsa-framework/slsa-github-generator/.github/actions/generate-builder@v2.1.0
        id: provenance-generate
        continue-on-error: true

      - name: Upload provenance attestation
        if: always()
        run: |
          # Generate provenance using gh attestation (built-in GitHub feature)
          gh attestation create ./nora-linux-amd64             --repo ${{ github.repository }}             --signer-workflow ${{ github.server_url }}/${{ github.repository }}/.github/workflows/release.yml 2>/dev/null || true
          # Also create a simple provenance file for scorecard
          cat > nora-v${{ github.ref_name }}.provenance.json << 'PROVEOF'
          {
            "_type": "https://in-toto.io/Statement/v0.1",
            "predicateType": "https://slsa.dev/provenance/v0.2",
            "subject": [{"name": "nora-linux-amd64"}],
            "predicate": {
              "builder": {"id": "${{ github.server_url }}/${{ github.repository }}/.github/workflows/release.yml"},
              "buildType": "https://github.com/slsa-framework/slsa-github-generator/generic@v2",
              "invocation": {
                "configSource": {
                  "uri": "${{ github.server_url }}/${{ github.repository }}",
                  "digest": {"sha1": "${{ github.sha }}"},
                  "entryPoint": ".github/workflows/release.yml"
                }
              },
              "metadata": {
                "buildInvocationID": "${{ github.run_id }}",
                "completeness": {"parameters": true, "environment": false, "materials": false}
              }
            }
          }
          PROVEOF
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Generate SBOM (SPDX)
        uses: anchore/sbom-action@57aae528053a48a3f6235f2d9461b05fbcb7366d # v0
        with:
          image: ${{ env.NORA }}/${{ env.IMAGE_NAME }}:${{ steps.ver.outputs.tag }}
          format: spdx-json
          output-file: nora-${{ github.ref_name }}.sbom.spdx.json

      - name: Generate SBOM (CycloneDX)
        uses: anchore/sbom-action@57aae528053a48a3f6235f2d9461b05fbcb7366d # v0
        with:
          image: ${{ env.NORA }}/${{ env.IMAGE_NAME }}:${{ steps.ver.outputs.tag }}
          format: cyclonedx-json
          output-file: nora-${{ github.ref_name }}.sbom.cdx.json

      - name: Install cosign
        uses: sigstore/cosign-installer@c56c2d3e59e4281cc41dea2217323ba5694b171e # v3

      - name: Sign binary with cosign (keyless Sigstore)
        run: cosign sign-blob --yes --output-signature nora-linux-amd64.sig --output-certificate nora-linux-amd64.pem ./nora-linux-amd64

      - name: Create Release
        uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2
        with:
          generate_release_notes: true
          files: |
            nora-linux-amd64
            nora-linux-amd64.sha256
            nora-linux-amd64.sig
            nora-linux-amd64.pem
            nora-${{ github.ref_name }}.sbom.spdx.json
            nora-${{ github.ref_name }}.sbom.cdx.json
            nora-${{ github.ref_name }}.provenance.json
          body: |
            ## Install

            ```bash
            curl -fsSL https://getnora.io/install.sh | sh
            ```

            Or download the binary directly:

            ```bash
            curl -LO https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/nora-linux-amd64
            chmod +x nora-linux-amd64
            sudo mv nora-linux-amd64 /usr/local/bin/nora
            ```

            ## Docker

            **Alpine (standard):**
            ```bash
            docker pull ghcr.io/${{ github.repository }}:${{ github.ref_name }}
            ```

            **RED OS:**
            ```bash
            docker pull ghcr.io/${{ github.repository }}:${{ github.ref_name }}-redos
            ```

            **Astra Linux SE:**
            ```bash
            docker pull ghcr.io/${{ github.repository }}:${{ github.ref_name }}-astra
            ```

            ## Changelog

            See [CHANGELOG.md](https://github.com/${{ github.repository }}/blob/main/CHANGELOG.md)
