name: Release

on:
  push:
    tags:
      - "v*"
    branches:
      - main
  pull_request:

env:
  SIGN_PIPE_VER: "v0.1.8"
  GORELEASER_VER: "v2.16.0"
  PRODUCT_NAME: "NetBird"
  COPYRIGHT: "NetBird GmbH"
  flags: ""
  SKIP_PUBLISH: "true"
  SKIP_DOCKER_PUSH: "false"

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref || github.actor_id }}
  cancel-in-progress: true

jobs:
  release_freebsd_port:
    name: "FreeBSD Port / Build & Test"
    runs-on: ubuntu-22.04
    steps:
      - name: Checkout
        uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          persist-credentials: false

      - name: Generate FreeBSD port diff
        run: bash -x release_files/freebsd-port-diff.sh

      - name: Generate FreeBSD port issue body
        run: bash -x release_files/freebsd-port-issue-body.sh

      - name: Check if diff was generated
        id: check_diff
        run: |
          if ls netbird-*.diff 1> /dev/null 2>&1; then
            echo "diff_exists=true" >> $GITHUB_OUTPUT
          else
            echo "diff_exists=false" >> $GITHUB_OUTPUT
            echo "No diff file generated (port may already be up to date)"
          fi

      - name: Extract version
        if: steps.check_diff.outputs.diff_exists == 'true'
        id: version
        run: |
          VERSION=$(ls netbird-*.diff | sed 's/netbird-\(.*\)\.diff/\1/')
          echo "version=$VERSION" >> $GITHUB_OUTPUT
          echo "Generated files for version: $VERSION"
          cat netbird-*.diff

      - name: Read Go version from go.mod
        id: goversion
        run: echo "version=$(awk '/^go / {print $2}' go.mod)" >> "$GITHUB_OUTPUT"

      - name: Test FreeBSD port
        if: steps.check_diff.outputs.diff_exists == 'true'
        env:
          GO_VERSION: ${{ steps.goversion.outputs.version }}
        uses: vmactions/freebsd-vm@b84ab5559b5a1bb4b8ee2737d2506a16e1737636 # v1.4.8
        with:
          usesh: true
          copyback: false
          release: "15.0"
          envs: "GO_VERSION"
          prepare: |
            # Install required packages
            pkg install -y git curl portlint

            # Install Go for building
            GO_TARBALL="go${GO_VERSION}.freebsd-amd64.tar.gz"
            GO_URL="https://go.dev/dl/$GO_TARBALL"
            curl -LO "$GO_URL"
            tar -C /usr/local -xzf "$GO_TARBALL"

            # Clone ports tree (shallow, only what we need)
            git clone --depth 1 --filter=blob:none https://git.FreeBSD.org/ports.git /usr/ports
            cd /usr/ports

          run: |
            set -e -x
            export PATH=$PATH:/usr/local/go/bin

            # Find the diff file
            echo "Finding diff file..."
            DIFF_FILE=$(find $PWD -name "netbird-*.diff" -type f 2>/dev/null | head -1)
            echo "Found: $DIFF_FILE"

            if [[ -z "$DIFF_FILE" ]]; then
              echo "ERROR: Could not find diff file"
              find ~ -name "*.diff" -type f 2>/dev/null || true
              exit 1
            fi

            # Apply the generated diff from /usr/ports (diff has a/security/netbird/... paths)
            cd /usr/ports
            patch -p1 -V none < "$DIFF_FILE"

            # Show patched Makefile
            version=$(cat security/netbird/Makefile | grep -E '^DISTVERSION=' | awk '{print $NF}')

            cd /usr/ports/security/netbird
            export BATCH=yes
            make package
            pkg add ./work/pkg/netbird-*.pkg

            netbird version | grep "$version"

            echo "FreeBSD port test completed successfully!"

      - name: Upload FreeBSD port files
        if: steps.check_diff.outputs.diff_exists == 'true'
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a #v7.0.1
        with:
          name: freebsd-port-files
          path: |
            ./netbird-*-issue.txt
            ./netbird-*.diff
          retention-days: 30

  release:
    runs-on: ubuntu-24.04-8-core
    outputs:
      release_artifact_url: ${{ steps.upload_release.outputs.artifact-url }}
      linux_packages_artifact_url: ${{ steps.upload_linux_packages.outputs.artifact-url }}
      windows_packages_artifact_url: ${{ steps.upload_windows_packages.outputs.artifact-url }}
      macos_packages_artifact_url: ${{ steps.upload_macos_packages.outputs.artifact-url }}
      ghcr_images: ${{ steps.tag_and_push_images.outputs.images_markdown }}
    steps:
      - name: Checkout
        uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          fetch-depth: 0 # It is required for GoReleaser to work properly
          persist-credentials: false

      - name: Parse semver string
        id: semver_parser
        uses: netbirdio/shared-actions/actions/parse-semver@be5df6047383da2236e02243cceb857d8567c27e # v0.0.2

      - name: Set snapshot flag
        if: ${{ !startsWith(github.ref, 'refs/tags/v') }}
        run: | 
          echo "flags=--snapshot" >> $GITHUB_ENV

      - name: Set build vars
        if: ${{ startsWith(github.ref, 'refs/tags/v') }}
        run: |
          if [[ "x-${{ steps.semver_parser.outputs.prerelease }}" == "x-" && "x-${{ github.repository }}" == "x-netbirdio/netbird" ]]; then
            echo "x-${{ github.repository }}" 
            echo "x-${{ steps.semver_parser.outputs.prerelease }}"
            echo "SKIP_PUBLISH=false" >> $GITHUB_ENV
          else
            echo "x-${{ github.repository }}" 
            echo "x-${{ steps.semver_parser.outputs.prerelease }}"
          fi
          
          if [[ "x-${{ github.repository }}" != "x-netbirdio/netbird" ]]; then
            echo "SKIP_DOCKER_PUSH=true" >> $GITHUB_ENV
          fi

      - name: Set up Go
        uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
        with:
          go-version-file: "go.mod"
          cache: false
      - name: Cache Go modules
        uses: actions/cache/restore@2c8a9bd7457de244a408f35966fab2fb45fda9c8 # v6.0.0
        with:
          path: |
            ~/go/pkg/mod
            ~/.cache/go-build
          key: ${{ runner.os }}-go-releaser-${{ hashFiles('**/go.sum') }}
          restore-keys: |
            ${{ runner.os }}-go-releaser-
      - name: Install modules
        run: go mod tidy
      - name: run openapi generator
        run: bash shared/management/http/api/generate.sh
      - name: check git status
        run: git --no-pager diff --exit-code
      - name: Set up QEMU
        uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 #v4.1.0
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 #v4.1.0
      - name: Login to Docker hub
        if: github.event_name != 'pull_request'
        uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
        with:
          username: ${{ secrets.DOCKER_USER }}
          password: ${{ secrets.DOCKER_TOKEN }}
      - name: Log in to the GitHub container registry
        if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
        uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.CI_DOCKER_PUSH_GITHUB_TOKEN }}
      - name: Install OS build dependencies
        run: sudo apt update && sudo apt install -y -q gcc-arm-linux-gnueabihf gcc-aarch64-linux-gnu

      - name: Decode GPG signing key
        if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
        env:
          GPG_RPM_PRIVATE_KEY: ${{ secrets.GPG_RPM_PRIVATE_KEY }}
        run: |
          echo "$GPG_RPM_PRIVATE_KEY" | base64 -d > /tmp/gpg-rpm-signing-key.asc
          echo "GPG_RPM_KEY_FILE=/tmp/gpg-rpm-signing-key.asc" >> $GITHUB_ENV

      - name: Install goversioninfo
        run: go install github.com/josephspurrier/goversioninfo/cmd/goversioninfo@233067e
      - name: Generate windows syso amd64
        run: goversioninfo  -icon client/ui/build/windows/icon.ico -manifest client/manifest.xml -product-name ${{ env.PRODUCT_NAME }} -copyright "${{ env.COPYRIGHT }}" -ver-major ${{ steps.semver_parser.outputs.major }} -ver-minor ${{ steps.semver_parser.outputs.minor }} -ver-patch ${{ steps.semver_parser.outputs.patch }} -ver-build 0 -file-version ${{ steps.semver_parser.outputs.fullversion }}.0 -product-version ${{ steps.semver_parser.outputs.fullversion }}.0 -o client/resources_windows_amd64.syso
      - name: Generate windows syso arm64
        run: goversioninfo -arm -64 -icon client/ui/build/windows/icon.ico -manifest client/manifest.xml -product-name ${{ env.PRODUCT_NAME }} -copyright "${{ env.COPYRIGHT }}" -ver-major ${{ steps.semver_parser.outputs.major }} -ver-minor ${{ steps.semver_parser.outputs.minor }} -ver-patch ${{ steps.semver_parser.outputs.patch }} -ver-build 0 -file-version ${{ steps.semver_parser.outputs.fullversion }}.0 -product-version ${{ steps.semver_parser.outputs.fullversion }}.0 -o client/resources_windows_arm64.syso
      - name: Run GoReleaser
        id: goreleaser
        uses: goreleaser/goreleaser-action@5daf1e915a5f0af01ddbcd89a43b8061ff4f1a89 # v7.2.2
        with:
          version: ${{ env.GORELEASER_VER }}
          args: release --clean ${{ env.flags }}
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }}
          UPLOAD_DEBIAN_SECRET: ${{ secrets.PKG_UPLOAD_SECRET }}
          UPLOAD_YUM_SECRET: ${{ secrets.PKG_UPLOAD_SECRET }}
          GPG_RPM_KEY_FILE: ${{ env.GPG_RPM_KEY_FILE }}
          NFPM_NETBIRD_RPM_PASSPHRASE: ${{ secrets.GPG_RPM_PASSPHRASE }}
          SKIP_PUBLISH: ${{ env.SKIP_PUBLISH }}
          SKIP_DOCKER_PUSH: ${{ env.SKIP_DOCKER_PUSH }}
      - name: Verify RPM signatures
        run: |
          docker run --rm -v $(pwd)/dist:/dist fedora:41 bash -c '
            dnf install -y -q rpm-sign curl >/dev/null 2>&1
            curl -sSL https://pkgs.netbird.io/yum/repodata/repomd.xml.key -o /tmp/rpm-pub.key
            rpm --import /tmp/rpm-pub.key
            echo "=== Verifying RPM signatures ==="
            for rpm_file in /dist/*amd64*.rpm; do
              [ -f "$rpm_file" ] || continue
              echo "--- $(basename $rpm_file) ---"
              rpm -K "$rpm_file"
            done
          '
      - name: Clean up GPG key
        if: always()
        run: rm -f /tmp/gpg-rpm-signing-key.asc
      - name: Tag and push images (amd64 only)
        id: tag_and_push_images
        if: |
          (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) ||
          (github.event_name == 'push' && github.ref == 'refs/heads/main')
        run: |
          set -euo pipefail

          resolve_tags() {
            if [[ "${{ github.event_name }}" == "pull_request" ]]; then
              echo "pr-${{ github.event.pull_request.number }}"
            else
              echo "main sha-$(git rev-parse --short HEAD)"
            fi
          }

          ghcr_package_url() {
            local image="$1" package encoded_package
            package="${image#ghcr.io/}"
            package="${package#*/}"
            package="${package%%:*}"
            encoded_package="${package//\//%2F}"
            echo "https://github.com/orgs/netbirdio/packages/container/package/${encoded_package}"
          }

          image_refs=()

          tag_and_push() {
            local src="$1" img_name tag dst
            img_name="${src%%:*}"
            for tag in $(resolve_tags); do
              dst="${img_name}:${tag}"
              echo "Tagging ${src} -> ${dst}"
              docker tag "$src" "$dst"
              docker push "$dst"
              image_refs+=("$dst")
            done
          }

          cat > /tmp/goreleaser-artifacts.json <<'JSON'
          ${{ steps.goreleaser.outputs.artifacts }}
          JSON

          # dockers_v2 artifacts have no top-level goarch field, so match the
          # per-platform -amd64 tag suffix instead; it works for both the old
          # dockers and the new dockers_v2 image naming.
          mapfile -t src_images < <(
            jq -r '.[] | select(.type == "Docker Image") | .name | select(startswith("ghcr.io/") and endswith("-amd64"))' /tmp/goreleaser-artifacts.json
          )

          for src in "${src_images[@]}"; do
            tag_and_push "$src"
          done

          {
            echo "images_markdown<<EOF"
            if [[ ${#image_refs[@]} -eq 0 ]]; then
              echo "_No GHCR images were pushed._"
            else
              printf '%s\n' "${image_refs[@]}" | sort -u | while read -r image; do
                printf -- '- [`%s`](%s)\n' "$image" "$(ghcr_package_url "$image")"
              done
            fi
            echo "EOF"
          } >> "$GITHUB_OUTPUT"
      - name: upload non tags for debug purposes
        id: upload_release
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a #v7.0.1
        with:
          name: release
          path: dist/
          retention-days: 7
      - name: upload linux packages
        id: upload_linux_packages
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a #v7.0.1
        with:
          name: linux-packages
          path: dist/netbird_linux**
          retention-days: 7
      - name: upload windows packages
        id: upload_windows_packages
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a #v7.0.1
        with:
          name: windows-packages
          path: dist/netbird_windows**
          retention-days: 7
      - name: upload macos packages
        id: upload_macos_packages
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a #v7.0.1
        with:
          name: macos-packages
          path: dist/netbird_darwin**
          retention-days: 7

  release_ui:
    runs-on: ubuntu-latest
    outputs:
      release_ui_artifact_url: ${{ steps.upload_release_ui.outputs.artifact-url }}
    steps:
      - name: Checkout
        uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          fetch-depth: 0 # It is required for GoReleaser to work properly
          persist-credentials: false

      - name: Parse semver string
        id: semver_parser
        uses: netbirdio/shared-actions/actions/parse-semver@be5df6047383da2236e02243cceb857d8567c27e # v0.0.2

      - name: Set snapshot flag
        if: ${{ !startsWith(github.ref, 'refs/tags/v') }}
        run: |
          echo "flags=--snapshot" >> $GITHUB_ENV

      - name: Set build vars
        if: ${{ startsWith(github.ref, 'refs/tags/v') }}
        run: |
          if [[ "x-${{ steps.semver_parser.outputs.prerelease }}" == "x-" && "x-${{ github.repository }}" == "x-netbirdio/netbird" ]]; then
            echo "x-${{ github.repository }}" 
            echo "x-${{ steps.semver_parser.outputs.prerelease }}"
            echo "SKIP_PUBLISH=false" >> $GITHUB_ENV
          else
            echo "x-${{ github.repository }}" 
            echo "x-${{ steps.semver_parser.outputs.prerelease }}"
          fi

      - name: Set up Go
        uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
        with:
          go-version-file: "go.mod"
          cache: false
      - name: Cache Go modules
        uses: actions/cache@2c8a9bd7457de244a408f35966fab2fb45fda9c8 # v6.0.0
        with:
          path: |
            ~/go/pkg/mod
            ~/.cache/go-build
          key: ${{ runner.os }}-ui-go-releaser-${{ hashFiles('**/go.sum') }}
          restore-keys: |
            ${{ runner.os }}-ui-go-releaser-

      - name: Install modules
        run: go mod tidy

      - name: check git status
        run: git --no-pager diff --exit-code

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '22'

      - name: Set up pnpm
        uses: pnpm/action-setup@a3252b78c470c02df07e9d59298aecedc3ccdd6d # v3.0.0
        with:
          version: 11

      - name: Install dependencies
        run: sudo apt update && sudo apt install -y -q libgtk-4-dev libwebkitgtk-6.0-dev libsoup-3.0-dev gcc-mingw-w64-x86-64

      - name: Decode GPG signing key
        if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
        env:
          GPG_RPM_PRIVATE_KEY: ${{ secrets.GPG_RPM_PRIVATE_KEY }}
        run: |
          echo "$GPG_RPM_PRIVATE_KEY" | base64 -d > /tmp/gpg-rpm-signing-key.asc
          echo "GPG_RPM_KEY_FILE=/tmp/gpg-rpm-signing-key.asc" >> $GITHUB_ENV

      - name: Install LLVM-MinGW for ARM64 cross-compilation
        run: |
          cd /tmp
          wget -q https://github.com/mstorsjo/llvm-mingw/releases/download/20250709/llvm-mingw-20250709-ucrt-ubuntu-22.04-x86_64.tar.xz
          echo "60cafae6474c7411174cff1d4ba21a8e46cadbaeb05a1bace306add301628337  llvm-mingw-20250709-ucrt-ubuntu-22.04-x86_64.tar.xz" | sha256sum -c
          tar -xf llvm-mingw-20250709-ucrt-ubuntu-22.04-x86_64.tar.xz
          echo "/tmp/llvm-mingw-20250709-ucrt-ubuntu-22.04-x86_64/bin" >> $GITHUB_PATH
      - name: Install goversioninfo
        run: go install github.com/josephspurrier/goversioninfo/cmd/goversioninfo@233067e
      - name: Install wails3 CLI
        # Version derived from go.mod so the binding generator always matches
        # the wails runtime the binary links against.
        run: |
          WAILS_VERSION=$(go list -m -f '{{.Version}}' github.com/wailsapp/wails/v3)
          go install github.com/wailsapp/wails/v3/cmd/wails3@$WAILS_VERSION
      - name: Generate windows syso amd64
        run: goversioninfo -64 -icon client/ui/build/windows/icon.ico -manifest client/ui/build/windows/wails.exe.manifest -product-name ${{ env.PRODUCT_NAME }}-"UI" -copyright "${{ env.COPYRIGHT }}" -ver-major ${{ steps.semver_parser.outputs.major }} -ver-minor ${{ steps.semver_parser.outputs.minor }} -ver-patch ${{ steps.semver_parser.outputs.patch }} -ver-build 0 -file-version ${{ steps.semver_parser.outputs.fullversion }}.0 -product-version ${{ steps.semver_parser.outputs.fullversion }}.0 -o client/ui/resources_windows_amd64.syso
      - name: Generate windows syso arm64
        run: goversioninfo -arm -64 -icon client/ui/build/windows/icon.ico -manifest client/ui/build/windows/wails.exe.manifest -product-name ${{ env.PRODUCT_NAME }}-"UI" -copyright "${{ env.COPYRIGHT }}" -ver-major ${{ steps.semver_parser.outputs.major }} -ver-minor ${{ steps.semver_parser.outputs.minor }} -ver-patch ${{ steps.semver_parser.outputs.patch }} -ver-build 0 -file-version ${{ steps.semver_parser.outputs.fullversion }}.0 -product-version ${{ steps.semver_parser.outputs.fullversion }}.0 -o client/ui/resources_windows_arm64.syso

      - name: Run GoReleaser
        uses: goreleaser/goreleaser-action@5daf1e915a5f0af01ddbcd89a43b8061ff4f1a89 # v7.2.2
        with:
          version: ${{ env.GORELEASER_VER }}
          args: release --config .goreleaser_ui.yaml --clean ${{ env.flags }}
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }}
          UPLOAD_DEBIAN_SECRET: ${{ secrets.PKG_UPLOAD_SECRET }}
          UPLOAD_YUM_SECRET: ${{ secrets.PKG_UPLOAD_SECRET }}
          GPG_RPM_KEY_FILE: ${{ env.GPG_RPM_KEY_FILE }}
          NFPM_NETBIRD_UI_RPM_PASSPHRASE: ${{ secrets.GPG_RPM_PASSPHRASE }}
          SKIP_PUBLISH: ${{ env.SKIP_PUBLISH }}
      - name: Verify RPM signatures
        run: |
          docker run --rm -v $(pwd)/dist:/dist fedora:41 bash -c '
            dnf install -y -q rpm-sign curl >/dev/null 2>&1
            curl -sSL https://pkgs.netbird.io/yum/repodata/repomd.xml.key -o /tmp/rpm-pub.key
            rpm --import /tmp/rpm-pub.key
            echo "=== Verifying RPM signatures ==="
            for rpm_file in /dist/*.rpm; do
              [ -f "$rpm_file" ] || continue
              echo "--- $(basename $rpm_file) ---"
              rpm -K "$rpm_file"
            done
          '
      - name: Clean up GPG key
        if: always()
        run: rm -f /tmp/gpg-rpm-signing-key.asc
      - name: upload non tags for debug purposes
        id: upload_release_ui
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a #v7.0.1
        with:
          name: release-ui
          path: dist/
          retention-days: 3

  release_ui_darwin:
    runs-on: macos-latest
    outputs:
      release_ui_darwin_artifact_url: ${{ steps.upload_release_ui_darwin.outputs.artifact-url }}
    steps:
      - if: ${{ !startsWith(github.ref, 'refs/tags/v') }}
        run: echo "flags=--snapshot" >> $GITHUB_ENV
      - name: Checkout
        uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          fetch-depth: 0 # It is required for GoReleaser to work properly
          persist-credentials: false
      - name: Set up Go
        uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
        with:
          go-version-file: "go.mod"
          cache: false
      - name: Cache Go modules
        uses: actions/cache@2c8a9bd7457de244a408f35966fab2fb45fda9c8 # v6.0.0
        with:
          path: |
            ~/go/pkg/mod
            ~/.cache/go-build
          key: ${{ runner.os }}-ui-go-releaser-darwin-${{ hashFiles('**/go.sum') }}
          restore-keys: |
            ${{ runner.os }}-ui-go-releaser-darwin-
      - name: Install modules
        run: go mod tidy
      - name: check git status
        run: git --no-pager diff --exit-code
      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '22'
      - name: Set up pnpm
        uses: pnpm/action-setup@a3252b78c470c02df07e9d59298aecedc3ccdd6d # v3.0.0
        with:
          version: 11
      - name: Install wails3 CLI
        # Version derived from go.mod so the binding generator always matches
        # the wails runtime the binary links against.
        run: |
          WAILS_VERSION=$(go list -m -f '{{.Version}}' github.com/wailsapp/wails/v3)
          go install github.com/wailsapp/wails/v3/cmd/wails3@$WAILS_VERSION
      - name: Run GoReleaser
        id: goreleaser
        uses: goreleaser/goreleaser-action@5daf1e915a5f0af01ddbcd89a43b8061ff4f1a89 # v7.2.2
        with:
          version: ${{ env.GORELEASER_VER }}
          args: release --config .goreleaser_ui_darwin.yaml --clean ${{ env.flags }}
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      - name: upload non tags for debug purposes
        id: upload_release_ui_darwin
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a #v7.0.1
        with:
          name: release-ui-darwin
          path: dist/
          retention-days: 3

  test_windows_installer:
    name: "Windows Installer / Build Test"
    runs-on: windows-2022
    needs: [release, release_ui]
    strategy:
      fail-fast: false
      matrix:
        include:
          - arch: amd64
            wintun_arch: amd64
          - arch: arm64
            wintun_arch: arm64
    defaults:
      run:
        shell: powershell
    env:
      PackageWorkdir: netbird_windows_${{ matrix.arch }}
      downloadPath: '${{ github.workspace }}\temp'
    steps:
      - name: Checkout
        uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          persist-credentials: false

      - name: Parse semver string
        id: semver_parser
        uses: netbirdio/shared-actions/actions/parse-semver@be5df6047383da2236e02243cceb857d8567c27e # v0.0.2

      - name: Add 7-Zip to PATH
        run: echo "C:\Program Files\7-Zip" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append

      - name: Download release artifacts
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          name: release
          path: release

      - name: Download UI release artifacts
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          name: release-ui
          path: release-ui

      - name: Stage binaries into dist
        run: |
          $workdir = "dist\${{ env.PackageWorkdir }}"
          New-Item -ItemType Directory -Force -Path $workdir | Out-Null
          $client = Get-ChildItem -Recurse -Path release -Filter "netbird_*_windows_${{ matrix.arch }}.tar.gz" | Select-Object -First 1
          $ui = Get-ChildItem -Recurse -Path release-ui -Filter "netbird-ui-windows_*_windows_${{ matrix.arch }}.tar.gz" | Select-Object -First 1
          if (-not $client) { Write-Host "::error::client tarball not found for ${{ matrix.arch }}"; exit 1 }
          if (-not $ui) { Write-Host "::error::ui tarball not found for ${{ matrix.arch }}"; exit 1 }
          Write-Host "Client: $($client.FullName)"
          Write-Host "UI:     $($ui.FullName)"
          tar -zvxf $client.FullName -C $workdir
          tar -zvxf $ui.FullName -C $workdir
          Get-ChildItem $workdir

      - name: Download wintun
        id: download-wintun
        uses: netbirdio/shared-actions/actions/win-download-and-verify@be5df6047383da2236e02243cceb857d8567c27e # v0.0.2
        with:
          url: https://pkgs.netbird.io/wintun/wintun-0.14.1.zip
          destination: ${{ env.downloadPath }}\wintun.zip
          sha256: 07c256185d6ee3652e09fa55c0b673e2624b565e02c4b9091c79ca7d2f24ef51

      - name: Decompress wintun files
        run: tar -xvf "${{ env.downloadPath }}\wintun.zip" -C ${{ env.downloadPath }}

      - name: Move wintun.dll into dist
        run: mv ${{ env.downloadPath }}\wintun\bin\${{ matrix.wintun_arch }}\wintun.dll ${{ github.workspace }}\dist\${{ env.PackageWorkdir }}\

      - name: Download EnVar plugin for NSIS
        uses: netbirdio/shared-actions/actions/win-download-and-verify@be5df6047383da2236e02243cceb857d8567c27e # v0.0.2
        with:
          url: https://pkgs.netbird.io/nsis/EnVar_plugin.zip
          destination: ${{ github.workspace }}\envar_plugin.zip
          sha256: e9aa92de351345ed82795251d838f1ae9041ba35af9d381a5780c7843b01f56a

      - name: Extract EnVar plugin
        run: 7z x -o"${{ github.workspace }}/NSIS_Plugins" "${{ github.workspace }}/envar_plugin.zip"

      - name: Download ShellExecAsUser plugin for NSIS (amd64 only)
        if: matrix.arch == 'amd64'
        uses: netbirdio/shared-actions/actions/win-download-and-verify@be5df6047383da2236e02243cceb857d8567c27e # v0.0.2
        with:
          url: https://pkgs.netbird.io/nsis/ShellExecAsUser_amd64-Unicode.7z
          destination: ${{ github.workspace }}\ShellExecAsUser_amd64-Unicode.7z
          sha256: 0a55ea25c7330a92cec028eda8afcaf1b1a7092e0dfb77c21c8f654564b4ff9d

      - name: Extract ShellExecAsUser plugin (amd64 only)
        if: matrix.arch == 'amd64'
        run: 7z x -o"${{ github.workspace }}/NSIS_Plugins" "${{ github.workspace }}/ShellExecAsUser_amd64-Unicode.7z"

      - name: Set up Go for wails3 CLI
        uses: actions/setup-go@v5
        with:
          go-version-file: "go.mod"
          cache: false

      - name: Install wails3 CLI
        # Version derived from go.mod so the bootstrapper payload always
        # matches the wails runtime the binary links against.
        shell: bash
        run: |
          WAILS_VERSION=$(go list -m -f '{{.Version}}' github.com/wailsapp/wails/v3)
          go install github.com/wailsapp/wails/v3/cmd/wails3@$WAILS_VERSION

      - name: Stage WebView2 bootstrapper for installers
        # Both client/installer.nsis and client/netbird.wxs reference
        # client/MicrosoftEdgeWebview2Setup.exe. wails3 writes it there.
        # The signing pipeline (netbirdio/sign-pipelines) does the same
        # step for release builds; this mirrors it for PR sanity testing.
        shell: bash
        run: wails3 generate webview2bootstrapper -dir client

      - name: Build NSIS installer
        shell: pwsh
        env:
          APPVER: ${{ steps.semver_parser.outputs.major }}.${{ steps.semver_parser.outputs.minor }}.${{ steps.semver_parser.outputs.patch }}.${{ github.run_id }}
        run: |
          $nsisPluginDir = "C:\Program Files (x86)\NSIS\Plugins\x86-unicode"
          $srcPlugins    = "${{ github.workspace }}\NSIS_Plugins\Plugins"
          Get-ChildItem -Path $srcPlugins -Recurse -Filter *.dll |
            Copy-Item -Destination $nsisPluginDir -Force
          & "C:\Program Files (x86)\NSIS\makensis.exe" /V4 "/DARCH=${{ matrix.arch }}" client\installer.nsis
          if ($LASTEXITCODE -ne 0) { throw "makensis failed with exit code $LASTEXITCODE" }

      - name: Rename NSIS installer
        run: mv netbird-installer.exe netbird_installer_test_windows_${{ matrix.arch }}.exe

      - name: Install WiX
        run: |
          dotnet tool install --global wix --version 6.0.2
          wix extension add WixToolset.Util.wixext/6.0.2

      - name: Build MSI installer
        env:
          NETBIRD_VERSION: "${{ steps.semver_parser.outputs.fullversion }}"
        run: wix build -arch ${{ matrix.arch == 'amd64' && 'x64' || 'arm64' }} -ext WixToolset.Util.wixext -o netbird_installer_test_windows_${{ matrix.arch }}.msi .\client\netbird.wxs -d ProcessorArchitecture=${{ matrix.arch == 'amd64' && 'x64' || 'arm64' }} -d ArchSuffix=${{ matrix.arch }}

      - name: Upload installer artifacts
        if: always()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a #v7.0.1
        with:
          name: windows-installer-test-${{ matrix.arch }}
          path: |
            netbird_installer_test_windows_${{ matrix.arch }}.exe
            netbird_installer_test_windows_${{ matrix.arch }}.msi
          retention-days: 3

  comment_release_artifacts:
    name: Comment release artifacts
    runs-on: ubuntu-latest
    needs: [release, release_ui, release_ui_darwin]
    if: ${{ always() && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository }}
    permissions:
      contents: read
      issues: write
      pull-requests: write
    steps:
      - name: Create or update PR comment
        uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
        env:
          RELEASE_RESULT: ${{ needs.release.result }}
          RELEASE_UI_RESULT: ${{ needs.release_ui.result }}
          RELEASE_UI_DARWIN_RESULT: ${{ needs.release_ui_darwin.result }}
          RELEASE_ARTIFACT_URL: ${{ needs.release.outputs.release_artifact_url }}
          LINUX_PACKAGES_ARTIFACT_URL: ${{ needs.release.outputs.linux_packages_artifact_url }}
          WINDOWS_PACKAGES_ARTIFACT_URL: ${{ needs.release.outputs.windows_packages_artifact_url }}
          MACOS_PACKAGES_ARTIFACT_URL: ${{ needs.release.outputs.macos_packages_artifact_url }}
          RELEASE_UI_ARTIFACT_URL: ${{ needs.release_ui.outputs.release_ui_artifact_url }}
          RELEASE_UI_DARWIN_ARTIFACT_URL: ${{ needs.release_ui_darwin.outputs.release_ui_darwin_artifact_url }}
          GHCR_IMAGES_MARKDOWN: ${{ needs.release.outputs.ghcr_images }}
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            const marker = '<!-- netbird-release-artifacts -->';
            const { owner, repo } = context.repo;
            const issue_number = context.payload.pull_request.number;
            const runUrl = `${context.serverUrl}/${owner}/${repo}/actions/runs/${context.runId}`;
            const shortSha = context.payload.pull_request.head.sha.slice(0, 7);

            const artifactCell = (url, result) => {
              if (url) return `[Download](${url})`;
              return result && result !== 'success' ? `_Not available (${result})_` : '_Not available_';
            };

            const artifacts = [
              ['All release artifacts', process.env.RELEASE_ARTIFACT_URL, process.env.RELEASE_RESULT],
              ['Linux packages', process.env.LINUX_PACKAGES_ARTIFACT_URL, process.env.RELEASE_RESULT],
              ['Windows packages', process.env.WINDOWS_PACKAGES_ARTIFACT_URL, process.env.RELEASE_RESULT],
              ['macOS packages', process.env.MACOS_PACKAGES_ARTIFACT_URL, process.env.RELEASE_RESULT],
              ['UI artifacts', process.env.RELEASE_UI_ARTIFACT_URL, process.env.RELEASE_UI_RESULT],
              ['UI macOS artifacts', process.env.RELEASE_UI_DARWIN_ARTIFACT_URL, process.env.RELEASE_UI_DARWIN_RESULT],
            ];

            const artifactRows = artifacts
              .map(([name, url, result]) => `| ${name} | ${artifactCell(url, result)} |`)
              .join('\n');

            const ghcrImages = (process.env.GHCR_IMAGES_MARKDOWN || '').trim() || '_No GHCR images were pushed._';

            const body = [
              marker,
              '## Release artifacts',
              '',
              `Built for PR head \`${shortSha}\` in [workflow run #${process.env.GITHUB_RUN_NUMBER}](${runUrl}).`,
              '',
              '| Artifact | Link |',
              '| --- | --- |',
              artifactRows,
              '',
              '### GHCR images (amd64)',
              ghcrImages,
              '',
              '_This comment is updated by the Release workflow. Artifact links expire according to the workflow retention policy._',
            ].join('\n');

            const comments = await github.paginate(github.rest.issues.listComments, {
              owner,
              repo,
              issue_number,
              per_page: 100,
            });

            const previous = comments.find(comment =>
              comment.user?.type === 'Bot' && comment.body?.includes(marker)
            );

            if (previous) {
              await github.rest.issues.updateComment({
                owner,
                repo,
                comment_id: previous.id,
                body,
              });
              core.info(`Updated release artifacts comment ${previous.id}`);
            } else {
              const { data } = await github.rest.issues.createComment({
                owner,
                repo,
                issue_number,
                body,
              });
              core.info(`Created release artifacts comment ${data.id}`);
            }

  trigger_signer:
    runs-on: ubuntu-latest
    needs: [release, release_ui, release_ui_darwin, test_windows_installer]
    if: startsWith(github.ref, 'refs/tags/')
    steps:
      - name: Trigger binaries sign pipelines
        uses: benc-uk/workflow-dispatch@31e2b3319479a63f0ab15bf800eff9e913504e26 # v1.3.2
        with:
          workflow: Sign bin and installer
          repo: netbirdio/sign-pipelines
          ref: ${{ env.SIGN_PIPE_VER }}
          token: ${{ secrets.SIGN_GITHUB_TOKEN }}
          inputs: '{ "tag": "${{ github.ref }}", "skipRelease": false }'
