name: Build Syncthing

on:
  pull_request:
  push:
  workflow_call:
  workflow_dispatch:

env:
  # The go version to use for builds. We set check-latest to true when
  # installing, so we get the latest patch version that matches the
  # expression.
  GO_VERSION: "~1.24.0"

  # Optimize compatibility on the slow archictures.
  GO386: softfloat
  GOARM: "5"
  GOMIPS: softfloat

  # Avoid hilarious amounts of obscuring log output when running tests.
  LOGGER_DISCARD: "1"

  # Our build metadata
  BUILD_USER: builder
  BUILD_HOST: github.syncthing.net

# A note on actions and third party code... The actions under actions/ (like
# `uses: actions/checkout`) are maintained by GitHub, and we need to trust
# GitHub to maintain their code and infrastructure or we're in deep shit in
# general. The same doesn't necessarily apply to other actions authors, so
# some care needs to be taken when adding steps, especially in the paths
# that lead up to code being packaged and signed.

jobs:

  #
  # Tests for all platforms. Runs a matrix build on Windows, Linux and Mac,
  # with the list of expected supported Go versions (current, previous).
  #

  build-test:
    name: Build and test
    strategy:
      fail-fast: false
      matrix:
        runner: ["windows-latest", "ubuntu-latest", "macos-latest"]
        # The oldest version in this list should match what we have in our go.mod.
        # Variables don't seem to be supported here, or we could have done something nice.
        go: ["~1.23.0", "~1.24.0"]
    runs-on: ${{ matrix.runner }}
    steps:
      - name: Set git to use LF
        if: matrix.runner == 'windows-latest'
        # Without this, the Windows checkout will happen with CRLF line
        # endings, which is fine for the source code but messes up tests
        # that depend on data on disk being as expected. Ideally, those
        # tests should be fixed, but not today.
        run: |
          git config --global core.autocrlf false
          git config --global core.eol lf

      - uses: actions/checkout@v4

      - uses: actions/setup-go@v5
        with:
          go-version: ${{ matrix.go }}
          cache: true
          check-latest: true

      - name: Build
        run: |
          go run build.go

      - name: Install go-test-json-to-loki
        run: |
          go install calmh.dev/go-test-json-to-loki@latest

      - name: Test
        run: |
          go version
          go run build.go test | go-test-json-to-loki
        env:
          GOFLAGS: "-json"
          LOKI_URL: ${{ secrets.LOKI_URL }}
          LOKI_USER: ${{ secrets.LOKI_USER }}
          LOKI_PASSWORD: ${{ secrets.LOKI_PASSWORD }}
          LOKI_LABELS: "go=${{ matrix.go }},runner=${{ matrix.runner }},repo=${{ github.repository }},ref=${{ github.ref }}"

  #
  # Meta checks for formatting, copyright, etc
  #

  correctness:
    name: Check correctness
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-go@v5
        with:
          go-version: ${{ env.GO_VERSION }}
          cache: false
          check-latest: true

      - name: Check correctness
        run: |
          go test -v ./meta

  #
  # The basic checks job is a virtual one that depends on the matrix tests,
  # the correctness checks, and various builds that we always do. This makes
  # it easy to have the PR process have a single test as a gatekeeper for
  # merging, instead of having to add all the matrix tests and update them
  # each time the version changes. (The top level test is not available for
  # choosing there, only the matrix "children".)
  #

  basics:
    name: Basic checks passed
    runs-on: ubuntu-latest
    needs:
      - build-test
      - correctness
      - package-linux
      - package-cross
      - package-source
      - package-debian
      - package-windows
      - govulncheck
    steps:
      - uses: actions/checkout@v4

  #
  # Windows
  #

  package-windows:
    name: Package for Windows
    runs-on: windows-latest
    steps:
      - name: Set git to use LF
        # Without this, the checkout will happen with CRLF line endings,
        # which is fine for the source code but messes up tests that depend
        # on data on disk being as expected. Ideally, those tests should be
        # fixed, but not today.
        run: |
          git config --global core.autocrlf false
          git config --global core.eol lf

      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
          ref: ${{ github.ref }} # https://github.com/actions/checkout/issues/882

      - uses: actions/setup-go@v5
        with:
          go-version: ${{ env.GO_VERSION }}
          cache: false
          check-latest: true

      - name: Get actual Go version
        run: |
          go version
          echo "GO_VERSION=$(go version | sed 's#^.*go##;s# .*##')" >> $GITHUB_ENV

      - uses: actions/cache@v4
        with:
          path: |
            ~\AppData\Local\go-build
            ~\go\pkg\mod
          key: ${{ runner.os }}-go-${{ env.GO_VERSION }}-package-${{ hashFiles('**/go.sum') }}

      - name: Install dependencies
        run: |
          go install github.com/josephspurrier/goversioninfo/cmd/goversioninfo@v1.4.0

      - name: Create packages
        run: |
          $targets = 'syncthing', 'stdiscosrv', 'strelaysrv'
          $archs = 'amd64', 'arm', 'arm64', '386'
          foreach ($arch in $archs) {
            foreach ($tgt in $targets) {
              go run build.go -goarch $arch zip $tgt
            }
          }
        env:
          CGO_ENABLED: "0"

      - name: Archive artifacts
        uses: actions/upload-artifact@v4
        with:
          name: unsigned-packages-windows
          path: "*.zip"

  codesign-windows:
    name: Codesign for Windows
    if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/heads/release-') || startsWith(github.ref, 'refs/tags/v'))
    environment: release
    runs-on: windows-latest
    needs:
      - package-windows
    steps:
      - name: Download artifacts
        uses: actions/download-artifact@v4
        with:
          name: unsigned-packages-windows
          path: packages

      - name: Extract packages
        working-directory: packages
        run: |
          $files = Get-ChildItem "." -Filter *.zip
          foreach ($file in $files) {
            7z x $file.Name
          }

      - name: Sign files with Trusted Signing
        uses: azure/trusted-signing-action@v0.5.1
        with:
          azure-tenant-id: ${{ secrets.AZURE_TRUSTED_SIGNING_TENANT_ID }}
          azure-client-id: ${{ secrets.AZURE_TRUSTED_SIGNING_CLIENT_ID }}
          azure-client-secret: ${{ secrets.AZURE_TRUSTED_SIGNING_CLIENT_SECRET }}
          endpoint: ${{ secrets.AZURE_TRUSTED_SIGNING_ENDPOINT }}
          trusted-signing-account-name: ${{ secrets.AZURE_TRUSTED_SIGNING_ACCOUNT }}
          certificate-profile-name: ${{ secrets.AZURE_TRUSTED_SIGNING_PROFILE }}
          files-folder: ${{ github.workspace }}\packages
          files-folder-filter: exe
          files-folder-recurse: true
          file-digest: SHA256
          timestamp-rfc3161: http://timestamp.acs.microsoft.com
          timestamp-digest: SHA256

      - name: Repackage packages
        working-directory: packages
        run: |
          $files = Get-ChildItem "." -Filter *.zip
          foreach ($file in $files) {
            Remove-Item $file.Name
            7z a -tzip $file.Name $file.BaseName
          }

      - name: Archive artifacts
        uses: actions/upload-artifact@v4
        with:
          name: packages-windows
          path: "packages/*.zip"

  #
  # Linux
  #

  package-linux:
    name: Package for Linux
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
          ref: ${{ github.ref }} # https://github.com/actions/checkout/issues/882

      - uses: actions/setup-go@v5
        with:
          go-version: ${{ env.GO_VERSION }}
          cache: false
          check-latest: true

      - name: Get actual Go version
        run: |
          go version
          echo "GO_VERSION=$(go version | sed 's#^.*go##;s# .*##')" >> $GITHUB_ENV

      - uses: actions/cache@v4
        with:
          path: |
            ~/.cache/go-build
            ~/go/pkg/mod
          key: ${{ runner.os }}-go-${{ env.GO_VERSION }}-package-${{ hashFiles('**/go.sum') }}

      - name: Create packages
        run: |
          archs=$(go tool dist list | grep linux | sed 's#linux/##')
          for goarch in $archs ; do
            for tgt in syncthing stdiscosrv strelaysrv ; do
              go run build.go -goarch "$goarch" tar "$tgt"
            done
          done
        env:
          CGO_ENABLED: "0"

      - name: Archive artifacts
        uses: actions/upload-artifact@v4
        with:
          name: packages-linux
          path: |
            *.tar.gz
            compat.json

  #
  # macOS
  #

  package-macos:
    name: Package for macOS
    if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/heads/release-') || startsWith(github.ref, 'refs/tags/v'))
    environment: release
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
          ref: ${{ github.ref }} # https://github.com/actions/checkout/issues/882

      - uses: actions/setup-go@v5
        with:
          go-version: ${{ env.GO_VERSION }}
          cache: false
          check-latest: true

      - name: Get actual Go version
        run: |
          go version
          echo "GO_VERSION=$(go version | sed 's#^.*go##;s# .*##')" >> $GITHUB_ENV

      - uses: actions/cache@v4
        with:
          path: |
            ~/.cache/go-build
            ~/go/pkg/mod
          key: ${{ runner.os }}-go-${{ env.GO_VERSION }}-package-${{ hashFiles('**/go.sum') }}

      - name: Import signing certificate
        run: |
          # Set up a run-specific keychain, making it available for the
          # `codesign` tool.
          umask 066
          KEYCHAIN_PATH=$RUNNER_TEMP/codesign.keychain
          KEYCHAIN_PASSWORD=$(uuidgen)
          security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
          security default-keychain -s "$KEYCHAIN_PATH"
          security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
          security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"

          # Import the certificate
          CERTIFICATE_PATH=$RUNNER_TEMP/codesign.p12
          echo "$DEVELOPER_ID_CERTIFICATE_BASE64" | base64 -d -o "$CERTIFICATE_PATH"
          security import "$CERTIFICATE_PATH" -k "$KEYCHAIN_PATH" -P "$DEVELOPER_ID_CERTIFICATE_PASSWORD" -T /usr/bin/codesign -T /usr/bin/productsign
          security set-key-partition-list -S apple-tool:,apple: -s -k actions "$KEYCHAIN_PATH"

          # Set the codesign identity for following steps
          echo "CODESIGN_IDENTITY=$CODESIGN_IDENTITY" >> $GITHUB_ENV
        env:
          DEVELOPER_ID_CERTIFICATE_BASE64: ${{ secrets.DEVELOPER_ID_CERTIFICATE_BASE64 }}
          DEVELOPER_ID_CERTIFICATE_PASSWORD: ${{ secrets.DEVELOPER_ID_CERTIFICATE_PASSWORD }}
          CODESIGN_IDENTITY: ${{ secrets.CODESIGN_IDENTITY }}

      - name: Create package (amd64)
        run: |
          for tgt in syncthing stdiscosrv strelaysrv ; do
            go run build.go -goarch amd64 zip "$tgt"
          done
        env:
          CGO_ENABLED: "1"

      - name: Create package (arm64 cross)
        run: |
          cat <<EOT > xgo.sh
          #!/bin/bash
          CGO_ENABLED=1 \
            CGO_CFLAGS="-target arm64-apple-macos10.15" \
            CGO_LDFLAGS="-target arm64-apple-macos10.15" \
            go "\$@"
          EOT
          chmod 755 xgo.sh
          for tgt in syncthing stdiscosrv strelaysrv ; do
            go run build.go -gocmd ./xgo.sh -goarch arm64 zip "$tgt"
          done
        env:
          CGO_ENABLED: "1"

      - name: Create package (universal)
        run: |
          rm -rf _tmp
          mkdir _tmp
          pushd _tmp

          unzip ../syncthing-macos-amd64-*.zip
          unzip ../syncthing-macos-arm64-*.zip
          lipo -create syncthing-macos-amd64-*/syncthing syncthing-macos-arm64-*/syncthing -o syncthing

          amd64=(syncthing-macos-amd64-*)
          universal="${amd64/amd64/universal}"
          mv "$amd64" "$universal"
          mv syncthing "$universal"
          zip -r "../$universal.zip" "$universal"

      - name: Archive artifacts
        uses: actions/upload-artifact@v4
        with:
          name: packages-macos
          path: "*.zip"

  notarize-macos:
    name: Notarize for macOS
    if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/heads/release-') || startsWith(github.ref, 'refs/tags/v'))
    environment: release
    needs:
      - package-macos
    runs-on: macos-latest
    steps:
      - name: Download artifacts
        uses: actions/download-artifact@v4
        with:
          name: packages-macos

      - name: Notarize binaries
        run: |
          APPSTORECONNECT_API_KEY_PATH="$RUNNER_TEMP/apikey.p8"
          echo "$APPSTORECONNECT_API_KEY" | base64 -d -o "$APPSTORECONNECT_API_KEY_PATH"
          for file in *-macos-*.zip ; do
            xcrun notarytool submit \
              -k "$APPSTORECONNECT_API_KEY_PATH" \
              -d "$APPSTORECONNECT_API_KEY_ID" \
              -i "$APPSTORECONNECT_API_KEY_ISSUER" \
              $file
          done
        env:
          APPSTORECONNECT_API_KEY: ${{ secrets.APPSTORECONNECT_API_KEY }}
          APPSTORECONNECT_API_KEY_ID: ${{ secrets.APPSTORECONNECT_API_KEY_ID }}
          APPSTORECONNECT_API_KEY_ISSUER: ${{ secrets.APPSTORECONNECT_API_KEY_ISSUER }}

  #
  # Cross compile other unixes
  #

  package-cross:
    name: Package cross compiled
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
          ref: ${{ github.ref }} # https://github.com/actions/checkout/issues/882

      - uses: actions/setup-go@v5
        with:
          go-version: ${{ env.GO_VERSION }}
          cache: false
          check-latest: true

      - name: Get actual Go version
        run: |
          go version
          echo "GO_VERSION=$(go version | sed 's#^.*go##;s# .*##')" >> $GITHUB_ENV

      - uses: actions/cache@v4
        with:
          path: |
            ~/.cache/go-build
            ~/go/pkg/mod
          key: ${{ runner.os }}-go-${{ env.GO_VERSION }}-cross-${{ hashFiles('**/go.sum') }}

      - name: Create packages
        run: |
          platforms=$(go tool dist list \
            | grep -v aix/ppc64 \
            | grep -v android/ \
            | grep -v darwin/ \
            | grep -v ios/ \
            | grep -v js/ \
            | grep -v linux/ \
            | grep -v nacl/ \
            | grep -v plan9/ \
            | grep -v windows/ \
            | grep -v /wasm \
          )

          # Build for each platform with errors silenced, because we expect
          # some oddball platforms to fail. This avoids a bunch of errors in
          # the GitHub Actions output, instead summarizing each build
          # failure as a warning.
          for plat in $platforms; do
            goos="${plat%/*}"
            goarch="${plat#*/}"
            echo "::group ::$plat"
            for tgt in syncthing stdiscosrv strelaysrv ; do
              if ! go run build.go -goos "$goos" -goarch "$goarch" tar "$tgt" 2>/dev/null; then
                echo "::warning ::Failed to build $tgt for $plat"
              fi
            done
            echo "::endgroup::"
          done
        env:
          CGO_ENABLED: "0"

      - name: Archive artifacts
        uses: actions/upload-artifact@v4
        with:
          name: packages-other
          path: "*.tar.gz"

  #
  # Source
  #

  package-source:
    name: Package source code
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
          ref: ${{ github.ref }} # https://github.com/actions/checkout/issues/882

      - uses: actions/setup-go@v5
        with:
          go-version: ${{ env.GO_VERSION }}
          cache: false
          check-latest: true

      - name: Package source
        run: |
          version=$(go run build.go version)
          echo "$version" > RELEASE

          go mod vendor
          go run build.go assets

          cd ..

          tar c -z -f "syncthing-source-$version.tar.gz" \
            --exclude .git \
            syncthing

          mv "syncthing-source-$version.tar.gz" syncthing

      - name: Archive artifacts
        uses: actions/upload-artifact@v4
        with:
          name: packages-source
          path: syncthing-source-*.tar.gz

  #
  # Sign binaries for auto upgrade, generate ASC signature files
  #

  sign-for-upgrade:
    name: Sign for upgrade
    if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/heads/release-')  || startsWith(github.ref, 'refs/tags/v'))
    environment: release
    needs:
      - codesign-windows
      - package-linux
      - package-macos
      - package-cross
      - package-source
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
          ref: ${{ github.ref }} # https://github.com/actions/checkout/issues/882

      - uses: actions/checkout@v4
        with:
          repository: syncthing/release-tools
          path: tools
          fetch-depth: 0

      - name: Download artifacts
        uses: actions/download-artifact@v4

      - uses: actions/setup-go@v5
        with:
          go-version: ${{ env.GO_VERSION }}
          cache: false
          check-latest: true

      - name: Install signing tool
        run: |
          go install ./cmd/dev/stsigtool

      - name: Sign archives
        run: |
          export PRIVATE_KEY="$RUNNER_TEMP/privkey.pem"
          export PATH="$PATH:$(go env GOPATH)/bin"
          echo "$STSIGTOOL_PRIVATE_KEY" | base64 -d > "$PRIVATE_KEY"
          mkdir packages
          mv packages-*/* packages
          pushd packages
          "$GITHUB_WORKSPACE/tools/sign-only"
          rm -f "$PRIVATE_KEY"
        env:
          STSIGTOOL_PRIVATE_KEY: ${{ secrets.STSIGTOOL_PRIVATE_KEY }}

      - name: Create shasum files
        run: |
          pushd packages
          files=(*.tar.gz *.zip)
          sha1sum "${files[@]}" > sha1sum.txt
          sha256sum "${files[@]}" > sha256sum.txt
          popd

          version=$(go run build.go version)
          echo "VERSION=$version" >> $GITHUB_ENV

      - name: Sign shasum files
        uses: docker://ghcr.io/kastelo/ezapt:latest
        with:
          args:
            sign
            packages/sha1sum.txt packages/sha256sum.txt
        env:
          EZAPT_KEYRING_BASE64: ${{ secrets.APT_GPG_KEYRING_BASE64 }}

      - name: Sign source
        uses: docker://ghcr.io/kastelo/ezapt:latest
        with:
          args:
            sign --detach --ascii
            packages/syncthing-source-${{ env.VERSION }}.tar.gz
        env:
          EZAPT_KEYRING_BASE64: ${{ secrets.APT_GPG_KEYRING_BASE64 }}

      - name: Archive artifacts
        uses: actions/upload-artifact@v4
        with:
          name: packages-signed
          path: |
            packages/*.tar.gz
            packages/*.zip
            packages/*.asc
            packages/*.json

  #
  # Debian
  #

  package-debian:
    name: Package for Debian
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
          ref: ${{ github.ref }} # https://github.com/actions/checkout/issues/882

      - uses: actions/setup-go@v5
        with:
          go-version: ${{ env.GO_VERSION }}
          cache: false
          check-latest: true

      - name: Get actual Go version
        run: |
          go version
          echo "GO_VERSION=$(go version | sed 's#^.*go##;s# .*##')" >> $GITHUB_ENV

      - uses: ruby/setup-ruby@v1
        with:
          ruby-version: '3.0'

      - name: Install fpm
        run: |
          gem install fpm

      - uses: actions/cache@v4
        with:
          path: |
            ~/.cache/go-build
            ~/go/pkg/mod
          key: ${{ runner.os }}-go-${{ env.GO_VERSION }}-debian-${{ hashFiles('**/go.sum') }}

      - name: Package for Debian
        run: |
          for arch in amd64 i386 armhf armel arm64 ; do
            for tgt in syncthing stdiscosrv strelaysrv ; do
              go run build.go -no-upgrade -installsuffix=no-upgrade -goarch "$arch" deb "$tgt"
            done
          done
        env:
          BUILD_USER: debian

      - name: Archive artifacts
        uses: actions/upload-artifact@v4
        with:
          name: debian-packages
          path: "*.deb"

  #
  # Nightlies
  #

  publish-nightly:
    name: Publish nightly build
    if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && startsWith(github.ref, 'refs/heads/release-nightly')
    environment: release
    needs:
      - sign-for-upgrade
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          repository: syncthing/release-tools
          path: tools
          fetch-depth: 0

      - name: Download artifacts
        uses: actions/download-artifact@v4
        with:
          name: packages-signed
          path: packages

      - uses: actions/setup-go@v5
        with:
          go-version: ${{ env.GO_VERSION }}
          cache: false
          check-latest: true

      - name: Create release json
        run: |
          cd packages
          "$GITHUB_WORKSPACE/tools/generate-release-json" "$BASE_URL" > nightly.json
        env:
          BASE_URL: ${{ secrets.NIGHTLY_BASE_URL }}

      - name: Push artifacts
        uses: docker://docker.io/rclone/rclone:latest
        env:
          RCLONE_CONFIG_OBJSTORE_TYPE: s3
          RCLONE_CONFIG_OBJSTORE_PROVIDER: ${{ secrets.S3_PROVIDER }}
          RCLONE_CONFIG_OBJSTORE_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY_ID }}
          RCLONE_CONFIG_OBJSTORE_SECRET_ACCESS_KEY: ${{ secrets.S3_SECRET_ACCESS_KEY }}
          RCLONE_CONFIG_OBJSTORE_ENDPOINT: ${{ secrets.S3_ENDPOINT }}
          RCLONE_CONFIG_OBJSTORE_REGION: ${{ secrets.S3_REGION }}
          RCLONE_CONFIG_OBJSTORE_ACL: public-read
        with:
          args: sync -v packages objstore:nightly

  #
  # Push release artifacts to Spaces
  #

  publish-release-files:
    name: Publish release files
    if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/tags/v'))
    environment: release
    needs:
      - sign-for-upgrade
      - package-debian
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
          ref: ${{ github.ref }} # https://github.com/actions/checkout/issues/882

      - name: Download signed packages
        uses: actions/download-artifact@v4
        with:
          name: packages-signed
          path: packages

      - name: Download debian packages
        uses: actions/download-artifact@v4
        with:
          name: debian-packages
          path: packages

      - uses: actions/setup-go@v5
        with:
          go-version: ${{ env.GO_VERSION }}
          cache: false
          check-latest: true

      - name: Set version
        run: |
          version=$(go run build.go version)
          echo "VERSION=$version" >> $GITHUB_ENV

      - name: Push to object store (${{ env.VERSION }})
        uses: docker://docker.io/rclone/rclone:latest
        env:
          RCLONE_CONFIG_OBJSTORE_TYPE: s3
          RCLONE_CONFIG_OBJSTORE_PROVIDER: ${{ secrets.S3_PROVIDER }}
          RCLONE_CONFIG_OBJSTORE_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY_ID }}
          RCLONE_CONFIG_OBJSTORE_SECRET_ACCESS_KEY: ${{ secrets.S3_SECRET_ACCESS_KEY }}
          RCLONE_CONFIG_OBJSTORE_ENDPOINT: ${{ secrets.S3_ENDPOINT }}
          RCLONE_CONFIG_OBJSTORE_REGION: ${{ secrets.S3_REGION }}
          RCLONE_CONFIG_OBJSTORE_ACL: public-read
        with:
          args: sync -v packages objstore:release/${{ env.VERSION }}

      - name: Push to object store (latest)
        uses: docker://docker.io/rclone/rclone:latest
        env:
          RCLONE_CONFIG_OBJSTORE_TYPE: s3
          RCLONE_CONFIG_OBJSTORE_PROVIDER: ${{ secrets.S3_PROVIDER }}
          RCLONE_CONFIG_OBJSTORE_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY_ID }}
          RCLONE_CONFIG_OBJSTORE_SECRET_ACCESS_KEY: ${{ secrets.S3_SECRET_ACCESS_KEY }}
          RCLONE_CONFIG_OBJSTORE_ENDPOINT: ${{ secrets.S3_ENDPOINT }}
          RCLONE_CONFIG_OBJSTORE_REGION: ${{ secrets.S3_REGION }}
          RCLONE_CONFIG_OBJSTORE_ACL: public-read
        with:
          args: sync -v objstore:release/${{ env.VERSION }} objstore:release/latest

  #
  # Push Debian/APT archive
  #

  publish-apt:
    name: Publish APT
    if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/heads/release-') || startsWith(github.ref, 'refs/tags/v'))
    environment: release
    needs:
      - package-debian
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
          ref: ${{ github.ref }} # https://github.com/actions/checkout/issues/882

      - name: Download packages
        uses: actions/download-artifact@v4
        with:
          name: debian-packages
          path: packages

      - name: Set version
        run: |
          version=$(go run build.go version)
          echo "Version: $version"
          echo "VERSION=$version" >> $GITHUB_ENV

      # Decide whether packages should go to stable, candidate or nightly
      - name: Prepare packages
        run: |
          kind=stable
          if [[ $VERSION == *-rc.[0-9] ]] ; then
            kind=candidate
          elif [[ $VERSION == *-* ]] ; then
            kind=nightly
          fi
          echo "Kind: $kind"
          mkdir -p packages/syncthing/$kind
          mv packages/*.deb packages/syncthing/$kind

      - name: Pull archive
        uses: docker://docker.io/rclone/rclone:latest
        env:
          RCLONE_CONFIG_OBJSTORE_TYPE: s3
          RCLONE_CONFIG_OBJSTORE_PROVIDER: ${{ secrets.S3_PROVIDER }}
          RCLONE_CONFIG_OBJSTORE_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY_ID }}
          RCLONE_CONFIG_OBJSTORE_SECRET_ACCESS_KEY: ${{ secrets.S3_SECRET_ACCESS_KEY }}
          RCLONE_CONFIG_OBJSTORE_ENDPOINT: ${{ secrets.S3_ENDPOINT }}
          RCLONE_CONFIG_OBJSTORE_REGION: ${{ secrets.S3_REGION }}
          RCLONE_CONFIG_OBJSTORE_ACL: public-read
        with:
          args: sync objstore:apt/dists dists

      - name: Update archive
        uses: docker://ghcr.io/kastelo/ezapt:latest
        with:
          args:
            publish
            --add packages
            --dists dists
        env:
          EZAPT_KEYRING_BASE64: ${{ secrets.APT_GPG_KEYRING_BASE64 }}

      - name: Push archive
        uses: docker://docker.io/rclone/rclone:latest
        env:
          RCLONE_CONFIG_OBJSTORE_TYPE: s3
          RCLONE_CONFIG_OBJSTORE_PROVIDER: ${{ secrets.S3_PROVIDER }}
          RCLONE_CONFIG_OBJSTORE_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY_ID }}
          RCLONE_CONFIG_OBJSTORE_SECRET_ACCESS_KEY: ${{ secrets.S3_SECRET_ACCESS_KEY }}
          RCLONE_CONFIG_OBJSTORE_ENDPOINT: ${{ secrets.S3_ENDPOINT }}
          RCLONE_CONFIG_OBJSTORE_REGION: ${{ secrets.S3_REGION }}
          RCLONE_CONFIG_OBJSTORE_ACL: public-read
        with:
          args: sync -v dists objstore:apt/dists

  #
  # Build and push to Docker Hub
  #

  docker-syncthing:
    name: Build and push Docker images
    runs-on: ubuntu-latest
    if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/release' || github.ref == 'refs/heads/infrastructure' || startsWith(github.ref, 'refs/heads/release-') || startsWith(github.ref, 'refs/tags/v'))
    environment: docker
    permissions:
      contents: read
      packages: write
    strategy:
      matrix:
        pkg:
          - syncthing
          - strelaysrv
          - stdiscosrv
        include:
          - pkg: syncthing
            dockerfile: Dockerfile
            image: syncthing/syncthing
          - pkg: strelaysrv
            dockerfile: Dockerfile.strelaysrv
            image: syncthing/relaysrv
          - pkg: stdiscosrv
            dockerfile: Dockerfile.stdiscosrv
            image: syncthing/discosrv
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
          ref: ${{ github.ref }} # https://github.com/actions/checkout/issues/882

      - uses: actions/setup-go@v5
        with:
          go-version: ${{ env.GO_VERSION }}
          cache: false
          check-latest: true

      - name: Get actual Go version
        run: |
          go version
          echo "GO_VERSION=$(go version | sed 's#^.*go##;s# .*##')" >> $GITHUB_ENV

      - uses: actions/cache@v4
        with:
          path: |
            ~/.cache/go-build
            ~/go/pkg/mod
          key: ${{ runner.os }}-go-${{ env.GO_VERSION }}-docker-${{ matrix.pkg }}-${{ hashFiles('**/go.sum') }}

      - name: Build binaries
        run: |
          for arch in amd64 arm64 arm; do
            go run build.go -goos linux -goarch "$arch" -no-upgrade build ${{ matrix.pkg }}
            mv ${{ matrix.pkg }} ${{ matrix.pkg }}-linux-"$arch"
          done
        env:
          CGO_ENABLED: "0"
          BUILD_USER: docker

      - name: Check if we will be able to push images
        run: |
          if [[ "${{ secrets.DOCKERHUB_TOKEN }}" != "" ]]; then
            echo "DOCKER_PUSH=true" >> $GITHUB_ENV;
          fi

      - name: Login to Docker Hub
        uses: docker/login-action@v3
        if: env.DOCKER_PUSH == 'true'
        with:
          registry: docker.io
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Login to GHCR
        uses: docker/login-action@v3
        if: env.DOCKER_PUSH == 'true'
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

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

      - name: Set version tags
        run: |
          version=$(go run build.go version)
          version=${version#v}
          if [[ $version == @([0-9]|[0-9][0-9]).@([0-9]|[0-9][0-9]).@([0-9]|[0-9][0-9]) ]] ; then
            echo Release version, pushing to :latest and version tags
            major=${version%.*.*}
            minor=${version%.*}
            tags=docker.io/${{ matrix.image }}:$version,ghcr.io/${{ matrix.image }}:$version,docker.io/${{ matrix.image }}:$major,ghcr.io/${{ matrix.image }}:$major,docker.io/${{ matrix.image }}:$minor,ghcr.io/${{ matrix.image }}:$minor,docker.io/${{ matrix.image }}:latest,ghcr.io/${{ matrix.image }}:latest
          elif [[ $version == *-rc.@([0-9]|[0-9][0-9]) ]] ; then
            echo Release candidate, pushing to :rc and version tags
            tags=docker.io/${{ matrix.image }}:$version,ghcr.io/${{ matrix.image }}:$version,docker.io/${{ matrix.image }}:rc,ghcr.io/${{ matrix.image }}:rc
          else
            echo Development version, pushing to :edge
            tags=docker.io/${{ matrix.image }}:edge,ghcr.io/${{ matrix.image }}:edge
          fi
          echo "DOCKER_TAGS=$tags" >> $GITHUB_ENV
          echo "VERSION=$version" >> $GITHUB_ENV

      - name: Build and push Docker image
        uses: docker/build-push-action@v5
        with:
          context: .
          file: ${{ matrix.dockerfile }}
          platforms: linux/amd64,linux/arm64,linux/arm/7
          push: ${{ env.DOCKER_PUSH == 'true' }}
          tags: ${{ env.DOCKER_TAGS }}
          labels: |
            org.opencontainers.image.version=${{ env.VERSION }}
            org.opencontainers.image.revision=${{ github.sha }}

  #
  # Check for known vulnerabilities in Go dependencies
  #

  govulncheck:
    runs-on: ubuntu-latest
    name: Run govulncheck
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-go@v5
        with:
          go-version: ${{ env.GO_VERSION }}
          cache: false
          check-latest: true

      - name: run govulncheck
        run: |
          go run build.go assets
          go install golang.org/x/vuln/cmd/govulncheck@latest
          govulncheck ./...
