name: "publish"

on:
  release:
    types: [published]

jobs:
  publish-windows:
    name: Build and publish Windows installer
    permissions:
      contents: write
    environment: production
    env:
      # --- Gitea (unchanged) ---
      GITEA_API: https://git.cbsk-tech.de/api/v1
      OWNER: Christoph
      REPO: GitLite
      GITEA_TOKEN: ${{ secrets.ACTIONS_TOKEN }}
      GITEA_FALLBACK_TOKEN: ${{ github.token }}

      # --- MinIO / S3-compatible upload ---
      MINIO_ENDPOINT: ${{ vars.MINIO_ENDPOINT }} # e.g. https://updates.example.com
      S3_BUCKET: ${{ vars.S3_BUCKET }} # e.g. updates
      AWS_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY }} # used by aws cli
      AWS_SECRET_ACCESS_KEY: ${{ secrets.S3_SECRET_KEY }} # used by aws cli
      ARTIFACT_BASE_URL: ${{ vars.ARTIFACT_BASE_URL }} # e.g. https://updates.example.com
      S3_ACCESS_KEY: ${{ secrets.S3_ACCESS_KEY }} # used by cicd_tool
      S3_SECRET_KEY: ${{ secrets.S3_SECRET_KEY }} # used by cicd_tool

    defaults:
      run:
        working-directory: GitLite

    runs-on: windows-latest

    steps:
      # cicd_tool/main.py expects the repo at <workspace>/GitLite, so check out there.
      - uses: actions/checkout@v5
        with:
          path: GitLite

      - name: setup node
        uses: actions/setup-node@v4
        with:
          node-version: lts/*

      - name: install frontend dependencies
        run: npm ci

      - name: Update package.json Version
        run: npm version ${{ github.ref_name }} --no-git-tag-version --allow-same-version

      - name: Update tauri.conf.json Version
        env:
          RELEASE_VERSION: ${{ github.ref_name }}
        run: |
          node -e "const fs=require('fs'); const path='src-tauri/tauri.conf.json'; const config=JSON.parse(fs.readFileSync(path,'utf8')); config.version=process.env.RELEASE_VERSION; fs.writeFileSync(path, JSON.stringify(config,null,2)+'\n');"

      - name: Commit and Push Changes
        shell: powershell
        run: |
          git config user.name '${{ vars.USERNAME_GIT }}'
          git config user.email '${{ vars.EMAIL_GIT }}'
          # npm version also bumps the version inside package-lock.json, so stage it
          # too -- otherwise it stays as an unstaged change and blocks the rebase.
          git add package.json package-lock.json src-tauri/tauri.conf.json

          git commit -m 'Update version to ${{ github.ref_name }}'
          if ($LASTEXITCODE -ne 0) {
            # Version files already match the tag -> nothing to push, succeed quietly.
            Write-Host 'No version changes to commit; skipping push.'
            exit 0
          }

          # The release build runs in detached HEAD on the tag. Resolve the branch
          # that should receive the version bump.
          $branch = '${{ github.event.release.target_commitish }}'
          if ([string]::IsNullOrEmpty($branch) -or $branch -match '^[0-9a-f]{40}$') {
            $branch = (
              git branch -r --contains $env:GITHUB_SHA |
              ForEach-Object { $_.Trim() } |
              Where-Object { $_ -and ($_ -notmatch '->') } |
              ForEach-Object { $_ -replace '^(?:remotes/)?[^/]+/', '' } |
              Select-Object -First 1
            )
          }

          if ([string]::IsNullOrEmpty($branch)) {
            Write-Host 'Could not determine branch for push.'
            git branch -a
            exit 1
          }

          # The tag may be behind the branch tip. Replay the bump commit on top of
          # the current remote branch so the push is a clean fast-forward.
          git fetch origin $branch
          git rebase "origin/$branch"
          if ($LASTEXITCODE -ne 0) {
            git rebase --abort
            Write-Host "Rebase onto origin/$branch failed."
            exit 1
          }

          git push origin "HEAD:refs/heads/$branch"

      - name: Build Tauri App
        env:
          TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
          TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD}}
          NO_STRIP: ""
        run: npm run tauri -- build --bundles nsis
      # ----------------------
      # Upload to MinIO (via mc) and create latest.json
      # ----------------------
      - name: Upload artifacts to MinIO with cicd_tool
        working-directory: GitLite/cicd_tool
        env:
          PUBLISH_PLATFORM: windows-x86_64
        run: |
          uv sync
          uv run main.py

  publish-arch:
    name: Build and publish AUR packages
    needs: publish-ubuntu
    permissions:
      contents: write
    runs-on: archlinux
    environment: production
    env:
      AUR_SSH_PRIVATE_KEY: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
      GITEA_API: https://git.cbsk-tech.de/api/v1
      OWNER: Christoph
      REPO: GitLite
      GITEA_TOKEN: ${{ secrets.ACTIONS_TOKEN }}
      GITEA_FALLBACK_TOKEN: ${{ github.token }}

    defaults:
      run:
        working-directory: GitLite

    steps:
      - name: Prepare Arch job container
        working-directory: /
        run: |
          set -euo pipefail

          # The runner image may contain only a single geo mirror. Seed several
          # currently active HTTPS mirrors and retain the image's list as a
          # fallback so one slow CDN cannot fail the complete release.
          ARCH_MIRROR_BACKUP="$(mktemp)"
          cp /etc/pacman.d/mirrorlist "$ARCH_MIRROR_BACKUP"
          printf '%s\n' \
            'Server = https://frankfurt.mirror.pkgbuild.com/$repo/os/$arch' \
            'Server = https://mirror.osbeck.com/archlinux/$repo/os/$arch' \
            'Server = https://mirror.ubrco.de/archlinux/$repo/os/$arch' \
            'Server = https://ftp.halifax.rwth-aachen.de/archlinux/$repo/os/$arch' \
            'Server = https://geo.mirror.pkgbuild.com/$repo/os/$arch' \
            > /etc/pacman.d/mirrorlist
          sed -n '/^[[:space:]]*Server[[:space:]]*=/p' "$ARCH_MIRROR_BACKUP" \
            >> /etc/pacman.d/mirrorlist

          ARCH_PACKAGES=(
            base-devel curl git nodejs npm openssh rust
            webkit2gtk-4.1 gtk3 hicolor-icon-theme
            libappindicator-gtk3 librsvg xdotool
          )

          for ARCH_INSTALL_ATTEMPT in 1 2 3; do
            echo "pacman attempt $ARCH_INSTALL_ATTEMPT of 3"
            timeout --signal=TERM 30m \
              pacman -Syu --needed --noconfirm --noprogressbar \
              "${ARCH_PACKAGES[@]}" \
              </dev/null &
            ARCH_PACMAN_PID=$!
            (
              ARCH_WAIT_SECONDS=0
              while true; do
                sleep 30
                ((ARCH_WAIT_SECONDS += 30))
                echo "pacman is still running (${ARCH_WAIT_SECONDS}s elapsed)"
              done
            ) &
            ARCH_HEARTBEAT_PID=$!

            ARCH_PACMAN_STATUS=0
            wait "$ARCH_PACMAN_PID" || ARCH_PACMAN_STATUS=$?
            kill "$ARCH_HEARTBEAT_PID" 2>/dev/null || true
            wait "$ARCH_HEARTBEAT_PID" 2>/dev/null || true

            if [ "$ARCH_PACMAN_STATUS" -eq 0 ]; then
              break
            fi

            if [ "$ARCH_PACMAN_STATUS" -eq 124 ]; then
              echo "pacman attempt timed out after 30 minutes" >&2
            fi

            if [ "$ARCH_INSTALL_ATTEMPT" -eq 3 ]; then
              echo "pacman failed after 3 attempts" >&2
              exit 1
            fi

            sleep "$((ARCH_INSTALL_ATTEMPT * 5))"
          done

      - uses: actions/checkout@v5
        with:
          path: GitLite

      - name: Set release version
        env:
          RELEASE_VERSION: ${{ github.ref_name }}
        run: |
          npm version "$RELEASE_VERSION" --no-git-tag-version --allow-same-version
          PACKAGE_VERSION="$(node -p "require('./package.json').version")"
          echo "PACKAGE_VERSION=$PACKAGE_VERSION" >> "$GITHUB_ENV"
          echo "RELEASE_TAG=$RELEASE_VERSION" >> "$GITHUB_ENV"

      - name: Generate and build AUR package
        run: |
          if ! id -u builder >/dev/null 2>&1; then
            useradd --create-home builder
          fi

          AUR_SOURCE_DIR="$(mktemp -d)"
          cp PKGBUILD "$AUR_SOURCE_DIR/PKGBUILD"
          SOURCE_ARCHIVE="$AUR_SOURCE_DIR/gitty-desktop-$PACKAGE_VERSION.tar.gz"
          curl --fail --location --silent --show-error \
            --output "$SOURCE_ARCHIVE" \
            "https://git.cbsk-tech.de/Christoph/GitLite/archive/$RELEASE_TAG.tar.gz"
          CHECKSUM="$(sha256sum "$SOURCE_ARCHIVE" | cut -d ' ' -f 1)"

          sed -i \
            -e "s/^pkgver=.*/pkgver=$PACKAGE_VERSION/" \
            -e "s/^pkgrel=.*/pkgrel=1/" \
            -e "s/^_tag=.*/_tag=$RELEASE_TAG/" \
            -e "s/^sha256sums=.*/sha256sums=('$CHECKSUM')/" \
            "$AUR_SOURCE_DIR/PKGBUILD"

          chown -R builder:builder "$AUR_SOURCE_DIR"
          runuser -u builder -- \
            bash -lc "cd '$AUR_SOURCE_DIR' && makepkg --cleanbuild --noconfirm && makepkg --printsrcinfo > .SRCINFO"

          ARCH_PACKAGE_PATH="$(find "$AUR_SOURCE_DIR" -maxdepth 1 -type f -name 'gitty-desktop-*.pkg.tar.zst' -print -quit)"
          if [ -z "$ARCH_PACKAGE_PATH" ]; then
            echo "The native Arch package was not created" >&2
            exit 1
          fi

          echo "AUR_SOURCE_DIR=$AUR_SOURCE_DIR" >> "$GITHUB_ENV"
          echo "ARCH_PACKAGE_PATH=$ARCH_PACKAGE_PATH" >> "$GITHUB_ENV"

      - name: Attach native Arch package to Gitea release
        run: |
          set -euo pipefail

          TOKEN="${GITEA_TOKEN:-${GITEA_FALLBACK_TOKEN:-}}"
          if [ -z "$TOKEN" ]; then
            echo "A Gitea token is required to upload the native Arch package" >&2
            exit 1
          fi

          RELEASE_JSON="$(curl --fail --location --silent --show-error \
            --header "Authorization: token $TOKEN" \
            "$GITEA_API/repos/$OWNER/$REPO/releases/tags/$RELEASE_TAG")"
          export RELEASE_JSON
          RELEASE_ID="$(node -e "const r=JSON.parse(process.env.RELEASE_JSON); if (!r.id) process.exit(1); process.stdout.write(String(r.id))")"
          PACKAGE_NAME="${ARCH_PACKAGE_PATH##*/}"
          export PACKAGE_NAME

          if node -e "const r=JSON.parse(process.env.RELEASE_JSON); process.exit((r.assets || []).some(a => a.name === process.env.PACKAGE_NAME) ? 0 : 1)"; then
            echo "Release asset $PACKAGE_NAME already exists; skipping upload."
          else
            curl --fail --location --silent --show-error \
              --request POST \
              --header "Authorization: token $TOKEN" \
              --form "attachment=@$ARCH_PACKAGE_PATH;type=application/octet-stream" \
              "$GITEA_API/repos/$OWNER/$REPO/releases/$RELEASE_ID/assets?name=$PACKAGE_NAME"
            echo
            echo "Attached $PACKAGE_NAME to Gitea release $RELEASE_TAG."
          fi

      - name: Generate and build binary AUR package
        run: |
          set -euo pipefail

          TOKEN="${GITEA_TOKEN:-${GITEA_FALLBACK_TOKEN:-}}"
          if [ -z "$TOKEN" ]; then
            echo "A Gitea token is required to locate the native Arch package" >&2
            exit 1
          fi

          RELEASE_JSON="$(curl --fail --location --silent --show-error \
            --header "Authorization: token $TOKEN" \
            "$GITEA_API/repos/$OWNER/$REPO/releases/tags/$RELEASE_TAG")"
          export RELEASE_JSON
          PACKAGE_NAME="${ARCH_PACKAGE_PATH##*/}"
          export PACKAGE_NAME
          PACKAGE_URL="$(node -e "const r=JSON.parse(process.env.RELEASE_JSON); const a=(r.assets || []).find(a => a.name === process.env.PACKAGE_NAME); if (!a?.browser_download_url) process.exit(1); process.stdout.write(a.browser_download_url)")"

          AUR_BIN_DIR="$(mktemp -d)"
          cp PKGBUILD-bin "$AUR_BIN_DIR/PKGBUILD"
          PACKAGE_PATH="$AUR_BIN_DIR/$PACKAGE_NAME"
          curl --fail --location --silent --show-error \
            --header "Authorization: token $TOKEN" \
            --output "$PACKAGE_PATH" "$PACKAGE_URL"
          PACKAGE_CHECKSUM="$(sha256sum "$PACKAGE_PATH" | cut -d ' ' -f 1)"

          sed -i \
            -e "s/^pkgver=.*/pkgver=$PACKAGE_VERSION/" \
            -e "s/^pkgrel=.*/pkgrel=1/" \
            -e "s|^_package=.*|_package=\"$PACKAGE_NAME\"|" \
            -e "s|^_artifact_url=.*|_artifact_url=\"$PACKAGE_URL\"|" \
            -e "s/^sha256sums=.*/sha256sums=('$PACKAGE_CHECKSUM')/" \
            "$AUR_BIN_DIR/PKGBUILD"

          chown -R builder:builder "$AUR_BIN_DIR"
          runuser -u builder -- \
            bash -lc "cd '$AUR_BIN_DIR' && makepkg --cleanbuild --noconfirm && makepkg --printsrcinfo > .SRCINFO"

          ARCH_BIN_PACKAGE_PATH="$(find "$AUR_BIN_DIR" -maxdepth 1 -type f -name 'gitty-desktop-bin-*.pkg.tar.zst' -print -quit)"
          if [ -z "$ARCH_BIN_PACKAGE_PATH" ]; then
            echo "The binary Arch package was not created" >&2
            exit 1
          fi

          echo "AUR_BIN_DIR=$AUR_BIN_DIR" >> "$GITHUB_ENV"
          echo "ARCH_BIN_PACKAGE_PATH=$ARCH_BIN_PACKAGE_PATH" >> "$GITHUB_ENV"

      - name: Attach binary Arch package to Gitea release
        run: |
          set -euo pipefail

          TOKEN="${GITEA_TOKEN:-${GITEA_FALLBACK_TOKEN:-}}"
          if [ -z "$TOKEN" ]; then
            echo "A Gitea token is required to upload the Arch package" >&2
            exit 1
          fi

          RELEASE_JSON="$(curl --fail --location --silent --show-error \
            --header "Authorization: token $TOKEN" \
            "$GITEA_API/repos/$OWNER/$REPO/releases/tags/$RELEASE_TAG")"
          export RELEASE_JSON
          RELEASE_ID="$(node -e "const r=JSON.parse(process.env.RELEASE_JSON); if (!r.id) process.exit(1); process.stdout.write(String(r.id))")"
          PACKAGE_NAME="${ARCH_BIN_PACKAGE_PATH##*/}"
          export PACKAGE_NAME

          if node -e "const r=JSON.parse(process.env.RELEASE_JSON); process.exit((r.assets || []).some(a => a.name === process.env.PACKAGE_NAME) ? 0 : 1)"; then
            echo "Release asset $PACKAGE_NAME already exists; skipping upload."
          else
            curl --fail --location --silent --show-error \
              --request POST \
              --header "Authorization: token $TOKEN" \
              --form "attachment=@$ARCH_BIN_PACKAGE_PATH;type=application/octet-stream" \
              "$GITEA_API/repos/$OWNER/$REPO/releases/$RELEASE_ID/assets?name=$PACKAGE_NAME"
            echo
            echo "Attached $PACKAGE_NAME to Gitea release $RELEASE_TAG."
          fi

      - name: Publish PKGBUILD to AUR
        env:
          AUR_GIT_NAME: ${{ vars.USERNAME_GIT }}
          AUR_GIT_EMAIL: ${{ vars.EMAIL_GIT }}
        run: |
          set -euo pipefail

          if [ -z "$AUR_SSH_PRIVATE_KEY" ]; then
            echo "AUR_SSH_PRIVATE_KEY is required" >&2
            exit 1
          fi

          install -d -m 0700 "$HOME/.ssh"
          printf '%s\n' "$AUR_SSH_PRIVATE_KEY" > "$HOME/.ssh/aur"
          chmod 0600 "$HOME/.ssh/aur"

          AUR_RETRY_DELAYS=(30 60 120 240)
          AUR_KNOWN_HOSTS_TEMP="$(mktemp)"
          AUR_HOST_KEY_READY=0
          for AUR_ATTEMPT in 1 2 3 4 5; do
            echo "AUR host-key attempt $AUR_ATTEMPT of 5"
            if timeout 30s ssh-keyscan -T 15 -H aur.archlinux.org > "$AUR_KNOWN_HOSTS_TEMP" \
              && [ -s "$AUR_KNOWN_HOSTS_TEMP" ]; then
              AUR_HOST_KEY_READY=1
              break
            fi
            if [ "$AUR_ATTEMPT" -lt 5 ]; then
              sleep "${AUR_RETRY_DELAYS[$((AUR_ATTEMPT - 1))]}"
            fi
          done
          if [ "$AUR_HOST_KEY_READY" -ne 1 ]; then
            echo "Could not retrieve the AUR SSH host key after 5 attempts" >&2
            exit 1
          fi
          install -m 0600 "$AUR_KNOWN_HOSTS_TEMP" "$HOME/.ssh/known_hosts"
          export GIT_SSH_COMMAND="ssh -i $HOME/.ssh/aur -o IdentitiesOnly=yes -o BatchMode=yes -o ConnectTimeout=30 -o ServerAliveInterval=15 -o ServerAliveCountMax=2"

          publish_aur_package() {
            local package_name="$1"
            local package_source_dir="$2"
            local checkout_root checkout clone_candidate pushed

            checkout_root="$(mktemp -d)"
            checkout=""
            for AUR_ATTEMPT in 1 2 3 4 5; do
              clone_candidate="$checkout_root/attempt-$AUR_ATTEMPT"
              echo "$package_name clone attempt $AUR_ATTEMPT of 5"
              if timeout 3m git -c init.defaultBranch=master clone \
                "ssh://aur@aur.archlinux.org/$package_name.git" "$clone_candidate"; then
                checkout="$clone_candidate"
                break
              fi
              if [ "$AUR_ATTEMPT" -lt 5 ]; then
                echo "AUR is unavailable; retrying after backoff"
                sleep "${AUR_RETRY_DELAYS[$((AUR_ATTEMPT - 1))]}"
              fi
            done
            if [ -z "$checkout" ]; then
              echo "Could not clone $package_name after 5 attempts" >&2
              return 1
            fi

            cp "$package_source_dir/PKGBUILD" "$package_source_dir/.SRCINFO" "$checkout/"
            git -C "$checkout" config user.name "${AUR_GIT_NAME:-Gitty Release Bot}"
            git -C "$checkout" config user.email "${AUR_GIT_EMAIL:-aur@localhost}"
            git -C "$checkout" add PKGBUILD .SRCINFO

            if git -C "$checkout" diff --cached --quiet; then
              echo "$package_name already matches release $PACKAGE_VERSION"
              return 0
            fi

            git -C "$checkout" commit -m "Update to $PACKAGE_VERSION"
            pushed=0
            for AUR_ATTEMPT in 1 2 3 4 5; do
              echo "$package_name push attempt $AUR_ATTEMPT of 5"
              if timeout 3m git -C "$checkout" push origin HEAD:master; then
                pushed=1
                break
              fi
              if [ "$AUR_ATTEMPT" -lt 5 ]; then
                echo "AUR push failed; retrying after backoff"
                sleep "${AUR_RETRY_DELAYS[$((AUR_ATTEMPT - 1))]}"
              fi
            done
            if [ "$pushed" -ne 1 ]; then
              echo "Could not publish $package_name after 5 attempts" >&2
              return 1
            fi
          }

          publish_aur_package gitty-desktop "$AUR_SOURCE_DIR"
          publish_aur_package gitty-desktop-bin "$AUR_BIN_DIR"

  publish-ubuntu:
    name: Build and publish Ubuntu AppImage
    permissions:
      contents: write
    environment: production
    env:
      GITEA_API: https://git.cbsk-tech.de/api/v1
      OWNER: Christoph
      REPO: GitLite
      GITEA_TOKEN: ${{ secrets.ACTIONS_TOKEN }}
      GITEA_FALLBACK_TOKEN: ${{ github.token }}

      MINIO_ENDPOINT: ${{ vars.MINIO_ENDPOINT }}
      S3_BUCKET: ${{ vars.S3_BUCKET }}
      AWS_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY }}
      AWS_SECRET_ACCESS_KEY: ${{ secrets.S3_SECRET_KEY }}
      ARTIFACT_BASE_URL: ${{ vars.ARTIFACT_BASE_URL }}
      S3_ACCESS_KEY: ${{ secrets.S3_ACCESS_KEY }}
      S3_SECRET_KEY: ${{ secrets.S3_SECRET_KEY }}

    defaults:
      run:
        working-directory: GitLite

    runs-on: ubuntu-22.04

    steps:
      - uses: actions/checkout@v5
        with:
          path: GitLite

      - name: Install Ubuntu dependencies
        run: |
          sudo apt-get update
          sudo apt-get install -y build-essential curl wget file libssl-dev libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf libxdo-dev libfuse2

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: lts/*

      - name: Install uv
        uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b

      - name: Install Rust toolchain
        uses: dtolnay/rust-toolchain@stable

      - name: Install frontend dependencies
        run: npm ci

      - name: Update package.json version
        run: npm version ${{ github.ref_name }} --no-git-tag-version --allow-same-version

      - name: Update tauri.conf.json version
        env:
          RELEASE_VERSION: ${{ github.ref_name }}
        run: |
          node -e "const fs=require('fs'); const path='src-tauri/tauri.conf.json'; const config=JSON.parse(fs.readFileSync(path,'utf8')); config.version=process.env.RELEASE_VERSION; fs.writeFileSync(path, JSON.stringify(config,null,2)+'\n');"

      - name: Build Tauri App
        env:
          TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
          TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD}}
          NO_STRIP: "1"
        run: npm run tauri -- build --bundles appimage

      - name: Upload artifacts to MinIO with cicd_tool
        working-directory: GitLite/cicd_tool
        env:
          PUBLISH_PLATFORM: linux-x86_64
        run: |
          uv sync
          uv run main.py
