# Builds the multi-arch image and publishes to GHCR (+ DockerHub if configured).
#
# Triggered by git tag creation, which publishes to the same docker tag.
# Each platform is built on its own runner in parallel and pushed by digest;
# a final merge job stitches the digests into one manifest list per registry,
# then attaches provenance + SBOM attestations.
#
# Platforms: amd64 (native), arm64 (native arm runner), and arm/v7, 386,
# ppc64le, s390x (glibc, cross-compiled on amd64). No emulation is used, so
# QEMU is not needed.

name: 🐳 Docker

on:
  workflow_dispatch:
    inputs:
      tag:
        description: 'Docker tag to publish. Leave empty to update latest'
        required: false
        type: string
  push:
    branches:
      - main
    tags:
      - '*'

concurrency:
  group: docker-${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

permissions:
  contents: read

env:
  IMAGE_NAME: adguardian
  DOCKER_USER: lissy93
  GHCR_REGISTRY: ghcr.io
  DOCKERHUB_REGISTRY: docker.io

jobs:
  # Resolve the tag + target registries once, shared by every build leg + merge
  prepare:
    name: 🧭 Prepare
    runs-on: ubuntu-latest
    timeout-minutes: 5
    outputs:
      tag: ${{ steps.meta.outputs.tag }}
      ghcr_image: ${{ steps.meta.outputs.ghcr_image }}
      publish_images: ${{ steps.meta.outputs.publish_images }}
      dockerhub_enabled: ${{ steps.meta.outputs.dockerhub_enabled }}
    steps:
      - name: Compute image metadata
        id: meta
        shell: bash
        env:
          EVENT_NAME: ${{ github.event_name }}
          MANUAL_TAG: ${{ inputs.tag }}
          DOCKERHUB_PASSWORD: ${{ secrets.DOCKERHUB_PASSWORD }}
        run: |
          set -euo pipefail
          if [[ "$EVENT_NAME" == "workflow_dispatch" ]]; then
            if [[ -n "$MANUAL_TAG" ]]; then TAG="$MANUAL_TAG"; else TAG="latest"; fi
          elif [[ "$GITHUB_REF" == refs/tags/* ]]; then
            TAG="${GITHUB_REF#refs/tags/}"
          else
            TAG="latest"
          fi

          TAG="$(echo "$TAG" | sed -E 's/[^A-Za-z0-9_.-]+/-/g; s/^[.-]+//' | cut -c1-128)"
          if [[ -z "$TAG" ]]; then
            echo "::error::Computed Docker tag is empty"
            exit 1
          fi

          GHCR_IMAGE="${GHCR_REGISTRY}/${DOCKER_USER}/${IMAGE_NAME}"
          PUBLISH_IMAGES="$GHCR_IMAGE"
          DOCKERHUB_ENABLED="false"
          if [[ -n "$DOCKERHUB_PASSWORD" ]]; then
            PUBLISH_IMAGES="${PUBLISH_IMAGES},${DOCKERHUB_REGISTRY}/${DOCKER_USER}/${IMAGE_NAME}"
            DOCKERHUB_ENABLED="true"
          fi

          {
            echo "tag=$TAG"
            echo "ghcr_image=$GHCR_IMAGE"
            echo "publish_images=$PUBLISH_IMAGES"
            echo "dockerhub_enabled=$DOCKERHUB_ENABLED"
          } >> "$GITHUB_OUTPUT"

  # Build each platform on its own runner, in parallel, pushing by digest
  build:
    name: 🏗️ Build ${{ matrix.platform }}
    needs: prepare
    runs-on: ${{ matrix.runner }}
    timeout-minutes: 60
    permissions:
      contents: read
      packages: write
    strategy:
      fail-fast: false
      matrix:
        include:
          # 64-bit: ultra-light scratch/musl image (Dockerfile), built natively
          - platform: linux/amd64
            runner: ubuntu-latest
            dockerfile: ./Dockerfile
          - platform: linux/arm64
            runner: ubuntu-24.04-arm
            dockerfile: ./Dockerfile
          # glibc/Debian image (Dockerfile.full), cross-compiled on amd64
          - platform: linux/arm/v7
            runner: ubuntu-latest
            dockerfile: ./Dockerfile.full
          - platform: linux/386
            runner: ubuntu-latest
            dockerfile: ./Dockerfile.full
          - platform: linux/ppc64le
            runner: ubuntu-latest
            dockerfile: ./Dockerfile.full
          - platform: linux/s390x
            runner: ubuntu-latest
            dockerfile: ./Dockerfile.full
    steps:
      - name: 🛎️ Checkout
        uses: actions/checkout@v6
        with:
          persist-credentials: false

      - name: 🏷️ Prepare platform name
        env:
          PLATFORM: ${{ matrix.platform }}
        run: echo "PLATFORM_PAIR=${PLATFORM//\//-}" >> "$GITHUB_ENV"

      - name: 🧱 Set up Docker Buildx
        uses: docker/setup-buildx-action@v4

      - name: 🔑 Login to GitHub Container Registry
        uses: docker/login-action@v4
        with:
          registry: ${{ env.GHCR_REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: 🔑 Login to DockerHub
        if: needs.prepare.outputs.dockerhub_enabled == 'true'
        uses: docker/login-action@v4
        with:
          registry: ${{ env.DOCKERHUB_REGISTRY }}
          username: ${{ env.DOCKER_USER }}
          password: ${{ secrets.DOCKERHUB_PASSWORD }}

      - name: 🏗️ Build and push by digest
        id: build
        uses: docker/build-push-action@v7
        with:
          context: .
          file: ${{ matrix.dockerfile }}
          platforms: ${{ matrix.platform }}
          provenance: false
          sbom: false
          cache-from: type=gha,scope=${{ env.PLATFORM_PAIR }}
          cache-to: type=gha,mode=max,scope=${{ env.PLATFORM_PAIR }}
          outputs: type=image,"name=${{ needs.prepare.outputs.publish_images }}",push-by-digest=true,name-canonical=true,push=true

      - name: 📤 Export digest
        env:
          DIGEST: ${{ steps.build.outputs.digest }}
        run: |
          set -euo pipefail
          mkdir -p /tmp/digests
          touch "/tmp/digests/${DIGEST#sha256:}"

      - name: ⬆️ Upload digest
        uses: actions/upload-artifact@v7
        with:
          name: digests-${{ env.PLATFORM_PAIR }}
          path: /tmp/digests/*
          if-no-files-found: error
          retention-days: 1

  # Combine the per-platform digests into one multi-arch manifest per registry
  merge:
    name: 🧩 Merge manifest
    needs: [prepare, build]
    runs-on: ubuntu-latest
    timeout-minutes: 15
    permissions:
      contents: read
      packages: write
      id-token: write
      attestations: write
    steps:
      - name: 📥 Download digests
        uses: actions/download-artifact@v8
        with:
          path: /tmp/digests
          pattern: digests-*
          merge-multiple: true

      - name: 🧱 Set up Docker Buildx
        uses: docker/setup-buildx-action@v4

      - name: 🔑 Login to GitHub Container Registry
        uses: docker/login-action@v4
        with:
          registry: ${{ env.GHCR_REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: 🔑 Login to DockerHub
        if: needs.prepare.outputs.dockerhub_enabled == 'true'
        uses: docker/login-action@v4
        with:
          registry: ${{ env.DOCKERHUB_REGISTRY }}
          username: ${{ env.DOCKER_USER }}
          password: ${{ secrets.DOCKERHUB_PASSWORD }}

      - name: 🧩 Create combined manifest list
        shell: bash
        env:
          TAG: ${{ needs.prepare.outputs.tag }}
          PUBLISH_IMAGES: ${{ needs.prepare.outputs.publish_images }}
        run: |
          set -euo pipefail
          cd /tmp/digests
          shopt -s nullglob
          DIGEST_FILES=(*)
          if [ ${#DIGEST_FILES[@]} -eq 0 ]; then
            echo "::error::No digests found to merge"
            exit 1
          fi
          IFS=',' read -ra IMAGES <<< "$PUBLISH_IMAGES"
          for IMG in "${IMAGES[@]}"; do
            REFS=()
            for f in "${DIGEST_FILES[@]}"; do
              REFS+=("${IMG}@sha256:${f}")
            done
            docker buildx imagetools create -t "${IMG}:${TAG}" "${REFS[@]}"
          done

      - name: 🔎 Inspect and capture GHCR digest
        id: digest
        shell: bash
        env:
          GHCR_IMAGE: ${{ needs.prepare.outputs.ghcr_image }}
          TAG: ${{ needs.prepare.outputs.tag }}
        run: |
          set -euo pipefail
          GHCR_DIGEST=$(docker buildx imagetools inspect "${GHCR_IMAGE}:${TAG}" --format '{{.Manifest.Digest}}')
          echo "ghcr_digest=${GHCR_DIGEST}" >> "$GITHUB_OUTPUT"
          docker buildx imagetools inspect "${GHCR_IMAGE}:${TAG}"

      - name: 🪪 Attest build provenance
        uses: actions/attest-build-provenance@v4
        continue-on-error: true
        with:
          subject-name: ${{ needs.prepare.outputs.ghcr_image }}
          subject-digest: ${{ steps.digest.outputs.ghcr_digest }}
          push-to-registry: true

      - name: 📋 Generate SBOM
        uses: anchore/sbom-action@v0.24.0
        continue-on-error: true
        with:
          image: ${{ needs.prepare.outputs.ghcr_image }}@${{ steps.digest.outputs.ghcr_digest }}
          format: spdx-json
          output-file: sbom.spdx.json

      - name: 🪪 Attest SBOM
        uses: actions/attest@v4
        continue-on-error: true
        with:
          subject-name: ${{ needs.prepare.outputs.ghcr_image }}
          subject-digest: ${{ steps.digest.outputs.ghcr_digest }}
          predicate-type: https://spdx.dev/Document
          predicate-path: sbom.spdx.json
          push-to-registry: true
