syncthing/syncthing

9 workflows · maturity 50% · 6 patterns · GitHub ↗

Security 31.11/100

Practices

✓ Matrix✓ Permissions○ Security scan○ AI review✓ Cache○ Concurrency✓ Reusable workflows

Detected patterns

Security dimensions

permissions
11.1
security scan
0
supply chain
20
secret handling
0
harden runner
0

Workflows (9)

build-infra-dockers matrix perms .github/workflows/build-infra-dockers.yaml
Triggers
push
Runs on
ubuntu-latest
Jobs
docker-syncthing
Matrix
pkg→ stcrashreceiver, strelaypoolsrv, stupgrades, ursrv
Actions
docker/login-action, docker/login-action, docker/setup-qemu-action, docker/setup-buildx-action, docker/build-push-action
Commands
  • for arch in arm64 amd64; do go run build.go -goos linux -goarch "$arch" build ${{ matrix.pkg }} mv ${{ matrix.pkg }} ${{ matrix.pkg }}-linux-"$arch" done
  • tags=docker.io/syncthing/${{ matrix.pkg }}:${{ github.sha }},ghcr.io/syncthing/infra/${{ matrix.pkg }}:${{ github.sha }} echo "TAGS=$tags" >> $GITHUB_ENV
  • tags=docker.io/syncthing/${{ matrix.pkg }}:latest,ghcr.io/syncthing/infra/${{ matrix.pkg }}:latest,${{ env.TAGS }} echo "TAGS=$tags" >> $GITHUB_ENV
View raw YAML
name: Build Infrastructure Images

on:
  push:
    branches:
      - infrastructure
      - infra-*

env:
  GO_VERSION: "~1.26.0"
  CGO_ENABLED: "0"
  BUILD_USER: docker
  BUILD_HOST: github.syncthing.net

permissions:
  contents: read
  packages: write

jobs:
  docker-syncthing:
    name: Build and push Docker images
    if: github.repository == 'syncthing/syncthing'
    runs-on: ubuntu-latest
    environment: docker
    strategy:
      matrix:
        pkg:
          - stcrashreceiver
          - strelaypoolsrv
          - stupgrades
          - ursrv
    steps:
      - uses: actions/checkout@v5
        with:
          fetch-depth: 0

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

      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

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

      - name: Build binaries
        run: |
          for arch in arm64 amd64; do
            go run build.go -goos linux -goarch "$arch" build ${{ matrix.pkg }}
            mv ${{ matrix.pkg }} ${{ matrix.pkg }}-linux-"$arch"
          done

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3

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

      - name: Set Docker tags (all branches)
        run: |
          tags=docker.io/syncthing/${{ matrix.pkg }}:${{ github.sha }},ghcr.io/syncthing/infra/${{ matrix.pkg }}:${{ github.sha }}
          echo "TAGS=$tags" >> $GITHUB_ENV

      - name: Set Docker tags (latest)
        if: github.ref == 'refs/heads/infrastructure'
        run: |
          tags=docker.io/syncthing/${{ matrix.pkg }}:latest,ghcr.io/syncthing/infra/${{ matrix.pkg }}:latest,${{ env.TAGS }}
          echo "TAGS=$tags" >> $GITHUB_ENV

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          file: ./Dockerfile.${{ matrix.pkg }}
          platforms: linux/amd64,linux/arm64
          push: true
          tags: ${{ env.TAGS }}
          labels: |
            org.opencontainers.image.revision=${{ github.sha }}
build-nightly perms .github/workflows/build-nightly.yaml
Triggers
schedule, workflow_dispatch
Runs on
Jobs
build-syncthing
View raw YAML
name: Build Syncthing (Nightly)

on:
  schedule:
    # Run nightly build at 05:00 UTC
    - cron: '00 05 * * *'
  workflow_dispatch:

permissions:
  contents: write
  packages: write
  artifact-metadata: write
  attestations: write
  id-token: write

jobs:
  build-syncthing:
    uses: ./.github/workflows/build-syncthing.yaml
    # if we only want nightlies to run for specific users:
    # if: contains(fromJSON('["syncthing", "calmh"]'), github.repository_owner)
    secrets: inherit
build-syncthing matrix .github/workflows/build-syncthing.yaml
Triggers
pull_request, push, workflow_call, workflow_dispatch
Runs on
ubuntu-latest, ${{ matrix.runner }}, ubuntu-latest, ubuntu-latest, windows-latest, ubuntu-latest, ubuntu-latest, macos-latest, macos-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest
Jobs
facts, build-test, basics, package-windows, codesign-windows, package-linux, package-illumos, package-macos, notarize-macos, package-cross, package-source, sign-for-upgrade, package-debian, publish-nightly, publish-release-files, publish-apt, docker-ghcr, docker-hub, govulncheck, golangci, meta
Matrix
go, include, include.dockerfile, include.image, include.pkg, pkg, runner→ Dockerfile, Dockerfile.stdiscosrv, Dockerfile.strelaysrv, discosrv, macos-latest, relaysrv, stdiscosrv, strelaysrv, syncthing, ubuntu-latest, windows-latest, ~1.25.0, ~1.26.0
Actions
mlugg/setup-zig, azure/trusted-signing-action, mlugg/setup-zig, vmactions/omnios-vm, docker://ghcr.io/kastelo/ezapt:latest, docker://ghcr.io/kastelo/ezapt:latest, ruby/setup-ruby, mlugg/setup-zig, docker://docker.io/rclone/rclone:latest, actions/attest-build-provenance, docker://docker.io/rclone/rclone:latest, docker://docker.io/rclone/rclone:latest, docker://docker.io/rclone/rclone:latest, docker://ghcr.io/kastelo/ezapt:latest, docker://docker.io/rclone/rclone:latest, mlugg/setup-zig, docker/login-action, docker/setup-qemu-action, docker/setup-buildx-action, docker/build-push-action, docker://docker.io/regclient/regsync:latest, golangci/golangci-lint-action
Commands
  • version=$(go run build.go version) echo "version=$version" >> "$GITHUB_OUTPUT" echo "Version: $version" kind=stable if [[ $version == *-rc.[0-9] || $version == *-rc.[0-9][0-9] ]] ; then kind=candidate elif [[ $version == *-* ]] ; then kind=nightly fi echo "release-kind=$kind" >> "$GITHUB_OUTPUT" echo "Release kind: $kind" generation=v1 if [[ $version == v2.* ]] ; then generation=v2 fi echo "release-generation=$generation" >> "$GITHUB_OUTPUT" echo "Release generation: $generation"
  • go version echo "go-version=$(go version | sed 's#^.*go##;s# .*##')" >> $GITHUB_OUTPUT
  • git config --global core.autocrlf false git config --global core.eol lf
  • go run build.go
  • go install calmh.dev/go-test-json-to-loki@latest
  • go version go run build.go test | go-test-json-to-loki
  • go install github.com/josephspurrier/goversioninfo/cmd/goversioninfo@v1.4.0
  • for tgt in syncthing stdiscosrv strelaysrv ; do go run build.go -tags "${{env.TAGS}}" -goos windows -goarch amd64 -cc "zig cc -target x86_64-windows" zip $tgt go run build.go -tags "${{env.TAGS}}" -goos windows -goarch 386 -cc "zig cc -target x86-windows" zip $tgt go run build.go -tags "${{env.TAGS}}" -goos windows -goarch arm64 -cc "zig cc -target aarch64-windows" zip $tgt # go run build.go -tags "${{env.TAGS}}" -goos windows -goarch arm -cc "zig cc -target thumb-windows" zip $tgt # fails with linker errors done
View raw YAML
name: Build Syncthing

on:
  pull_request:
  push:
    branches-ignore:
      - release
      - release-rc*
  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.26.0"

  # Optimize compatibility on the slow architectures.
  GOMIPS: softfloat
  GOARM: "6"

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

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

  TAGS: "sqlite_omit_load_extension sqlite_dbstat"
  TAGS_LINUX: "sqlite_omit_load_extension sqlite_dbstat netgo osusergo"

# 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:

  #
  # Source
  #

  facts:
    name: Gather common facts
    runs-on: ubuntu-latest
    outputs:
      version: ${{ steps.get-version.outputs.version }}
      release-kind: ${{ steps.get-version.outputs.release-kind }}
      release-generation: ${{ steps.get-version.outputs.release-generation }}
      go-version: ${{ steps.get-go.outputs.go-version }}
    steps:
      - uses: actions/checkout@v5
        with:
          fetch-depth: 0
          ref: ${{ github.ref }} # https://github.com/actions/checkout/issues/882

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

      - name: Get Syncthing version
        id: get-version
        run: |
          version=$(go run build.go version)
          echo "version=$version" >> "$GITHUB_OUTPUT"
          echo "Version: $version"

          kind=stable
          if [[ $version == *-rc.[0-9] || $version == *-rc.[0-9][0-9] ]] ; then
            kind=candidate
          elif [[ $version == *-* ]] ; then
            kind=nightly
          fi
          echo "release-kind=$kind" >> "$GITHUB_OUTPUT"
          echo "Release kind: $kind"

          generation=v1
          if [[ $version == v2.* ]] ; then
            generation=v2
          fi
          echo "release-generation=$generation" >> "$GITHUB_OUTPUT"
          echo "Release generation: $generation"
      - name: Get Go version
        id: get-go
        run: |
          go version
          echo "go-version=$(go version | sed 's#^.*go##;s# .*##')" >> $GITHUB_OUTPUT

  #
  # 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.25.0", "~1.26.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@v5

      - uses: actions/setup-go@v6
        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 }}"
          CGO_ENABLED: "1"

  #
  # 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
      - package-linux
      - package-illumos
      - package-cross
      - package-source
      - package-debian
      - package-windows
      - govulncheck
      - golangci
      - meta
    steps:
      - uses: actions/checkout@v5

  #
  # Windows
  #

  package-windows:
    name: Package for Windows
    runs-on: ubuntu-latest
    needs:
      - facts
    env:
      VERSION: ${{ needs.facts.outputs.version }}
      RELEASE_KIND: ${{ needs.facts.outputs.release-kind }}
    steps:
      - uses: actions/checkout@v5

      - uses: actions/setup-go@v6
        with:
          go-version: "~1.25.7" # temporarily stay on Go 1.25 due to linker error on Go 1.26
          cache: false

      - uses: mlugg/setup-zig@v2

      - uses: actions/cache@v4
        with:
          path: |
            ~/.cache/go-build
            ~/go/pkg/mod
          key: ${{ runner.os }}-go-${{ needs.facts.outputs.go-version }}-package-windows-${{ hashFiles('go.sum') }}

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

      - name: Create packages
        run: |
          for tgt in syncthing stdiscosrv strelaysrv ; do
            go run build.go -tags "${{env.TAGS}}" -goos windows -goarch amd64 -cc "zig cc -target x86_64-windows" zip $tgt
            go run build.go -tags "${{env.TAGS}}" -goos windows -goarch 386 -cc "zig cc -target x86-windows" zip $tgt
            go run build.go -tags "${{env.TAGS}}" -goos windows -goarch arm64 -cc "zig cc -target aarch64-windows" zip $tgt
            # go run build.go -tags "${{env.TAGS}}" -goos windows -goarch arm -cc "zig cc -target thumb-windows" zip $tgt # fails with linker errors
          done
        env:
          CGO_ENABLED: "1"

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

  #
  # Codesign binaries for Windows. This job runs only when called in the
  # Syncthing repo for release branches and tags, as it requires our
  # specific code signing keys etc.
  #

  codesign-windows:
    name: Codesign for Windows
    if: github.repository_owner == 'syncthing' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release-nightly' || startsWith(github.ref, 'refs/tags/v'))
    environment: release
    runs-on: windows-latest
    needs:
      - package-windows
    steps:
      - name: Download artifacts
        uses: actions/download-artifact@v7
        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
    needs:
      - facts
    env:
      VERSION: ${{ needs.facts.outputs.version }}
      RELEASE_KIND: ${{ needs.facts.outputs.release-kind }}
    steps:
      - uses: actions/checkout@v5

      - uses: actions/setup-go@v6
        with:
          go-version: ${{ needs.facts.outputs.go-version }}
          cache: false

      - uses: mlugg/setup-zig@v2

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

      - name: Create packages
        run: |
          sudo apt-get install -y gcc-mips64-linux-gnuabi64 gcc-mips64el-linux-gnuabi64
          for tgt in syncthing stdiscosrv strelaysrv ; do
            go run build.go -tags "${{env.TAGS_LINUX}}" -goos linux -goarch amd64 -cc "zig cc -target x86_64-linux-musl" tar "$tgt"
            go run build.go -tags "${{env.TAGS_LINUX}}" -goos linux -goarch 386 -cc "zig cc -target x86-linux-musl" tar "$tgt"
            go run build.go -tags "${{env.TAGS_LINUX}}" -goos linux -goarch arm -cc "zig cc -target arm-linux-musleabi -mcpu=arm1136j_s" tar "$tgt"
            go run build.go -tags "${{env.TAGS_LINUX}}" -goos linux -goarch arm64 -cc "zig cc -target aarch64-linux-musl" tar "$tgt"
            go run build.go -tags "${{env.TAGS_LINUX}}" -goos linux -goarch mips -cc "zig cc -target mips-linux-musleabi" tar "$tgt"
            go run build.go -tags "${{env.TAGS_LINUX}}" -goos linux -goarch mipsle -cc "zig cc -target mipsel-linux-musleabi" tar "$tgt"
            go run build.go -tags "${{env.TAGS_LINUX}}" -goos linux -goarch mips64 -cc mips64-linux-gnuabi64-gcc tar "$tgt"
            go run build.go -tags "${{env.TAGS_LINUX}}" -goos linux -goarch mips64le -cc mips64el-linux-gnuabi64-gcc tar "$tgt"
            go run build.go -tags "${{env.TAGS_LINUX}}" -goos linux -goarch riscv64 -cc "zig cc -target riscv64-linux-musl" tar "$tgt"
            go run build.go -tags "${{env.TAGS_LINUX}}" -goos linux -goarch s390x -cc "zig cc -target s390x-linux-musl" tar "$tgt"
            go run build.go -tags "${{env.TAGS_LINUX}}" -goos linux -goarch loong64 -cc "zig cc -target loongarch64-linux-musl" tar "$tgt"
            # go run build.go -tags "${{env.TAGS_LINUX}}" -goos linux -goarch ppc64 -cc "zig cc -target powerpc64-linux-musl" tar "$tgt" # fails with linkmode not supported
            go run build.go -tags "${{env.TAGS_LINUX}}" -goos linux -goarch ppc64le -cc "zig cc -target powerpc64le-linux-musl" tar "$tgt"
          done
        env:
          CGO_ENABLED: "1"
          EXTRA_LDFLAGS: "-linkmode=external -extldflags=-static"

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

  package-illumos:
    runs-on: ubuntu-latest
    name: Package for illumos
    needs:
      - facts
    env:
      VERSION: ${{ needs.facts.outputs.version }}
      GO_VERSION: ${{ needs.facts.outputs.go-version }}
    steps:
      - uses: actions/checkout@v5

      - name: Build syncthing in OmniOS VM
        uses: vmactions/omnios-vm@v1
        with:
          envs: "VERSION GO_VERSION CGO_ENABLED"
          usesh: true
          prepare: |
            pkg install developer/gcc14 web/curl archiver/gnu-tar
          run: |
            curl -L "https://go.dev/dl/go$GO_VERSION.illumos-amd64.tar.gz" | gtar xzf -
            export PATH="$GITHUB_WORKSPACE/go/bin:$PATH"
            go version
            for tgt in syncthing stdiscosrv strelaysrv ; do
              go run build.go -tags "${{env.TAGS}}" tar "$tgt"
            done
        env:
          CGO_ENABLED: "1"

      - name: Archive artifacts
        uses: actions/upload-artifact@v4
        with:
          name: packages-illumos
          path: "*.tar.gz"
  #
  # macOS. The entire build runs in the release environment because code
  # signing is part of the build process, so it is limited to release
  # branches on the Syncthing repo.
  #

  package-macos:
    name: Package for macOS
    if: github.repository_owner == 'syncthing' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release-nightly' || startsWith(github.ref, 'refs/tags/v'))
    environment: release
    runs-on: macos-latest
    needs:
      - facts
    env:
      CODESIGN_IDENTITY: ${{ secrets.CODESIGN_IDENTITY }}
      VERSION: ${{ needs.facts.outputs.version }}
      RELEASE_KIND: ${{ needs.facts.outputs.release-kind }}
    steps:
      - uses: actions/checkout@v5

      - uses: actions/setup-go@v6
        with:
          go-version: ${{ needs.facts.outputs.go-version }}
          cache: false

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

      - name: Import signing certificate
        if: env.CODESIGN_IDENTITY != ''
        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 -tags "${{env.TAGS}}" -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 -tags "${{env.TAGS}}" -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.repository_owner == 'syncthing' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release-nightly' || startsWith(github.ref, 'refs/tags/v'))
    environment: release
    needs:
      - package-macos
    runs-on: macos-latest
    steps:
      - name: Download artifacts
        uses: actions/download-artifact@v7
        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
    needs:
      - facts
    env:
      VERSION: ${{ needs.facts.outputs.version }}
      RELEASE_KIND: ${{ needs.facts.outputs.release-kind }}
    steps:
      - uses: actions/checkout@v5

      - uses: actions/setup-go@v6
        with:
          go-version: ${{ needs.facts.outputs.go-version }}
          cache: false

      - uses: actions/cache@v4
        with:
          path: |
            ~/.cache/go-build
            ~/go/pkg/mod
          key: ${{ runner.os }}-go-${{ needs.facts.outputs.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 illumos/ \
            | 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" ; 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
    needs:
      - facts
    env:
      VERSION: ${{ needs.facts.outputs.version }}
      RELEASE_KIND: ${{ needs.facts.outputs.release-kind }}
    steps:
      - uses: actions/checkout@v5

      - uses: actions/setup-go@v6
        with:
          go-version: ${{ needs.facts.outputs.go-version }}
          cache: false

      - name: Package source
        run: |
          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.repository_owner == 'syncthing' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release-nightly' || startsWith(github.ref, 'refs/tags/v'))
    environment: release
    needs:
      - codesign-windows
      - package-linux
      - package-illumos
      - package-macos
      - package-cross
      - package-source
      - facts
    env:
      VERSION: ${{ needs.facts.outputs.version }}
      RELEASE_KIND: ${{ needs.facts.outputs.release-kind }}
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5

      - uses: actions/checkout@v5
        with:
          repository: syncthing/release-tools
          path: tools

      - name: Download artifacts
        uses: actions/download-artifact@v7
        with:
          pattern: packages-*
          path: packages
          merge-multiple: true

      - uses: actions/setup-go@v6
        with:
          go-version: ${{ needs.facts.outputs.go-version }}
          cache: false

      - 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"
          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

      - 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
    needs:
      - facts
    env:
      VERSION: ${{ needs.facts.outputs.version }}
      RELEASE_KIND: ${{ needs.facts.outputs.release-kind }}
    steps:
      - uses: actions/checkout@v5

      - uses: actions/setup-go@v6
        with:
          go-version: ${{ needs.facts.outputs.go-version }}
          cache: false

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

      - name: Install fpm
        run: |
          gem install fpm

      - uses: mlugg/setup-zig@v2

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

      - name: Package for Debian (CGO)
        run: |
          for tgt in syncthing stdiscosrv strelaysrv ; do
            go run build.go -no-upgrade -installsuffix=no-upgrade -tags "${{env.TAGS_LINUX}}" -goos linux -goarch amd64 -cc "zig cc -target x86_64-linux-musl" deb "$tgt"
            go run build.go -no-upgrade -installsuffix=no-upgrade -tags "${{env.TAGS_LINUX}}" -goos linux -goarch armel -cc "zig cc -target arm-linux-musleabi -mcpu=arm1136j_s" deb "$tgt"
            go run build.go -no-upgrade -installsuffix=no-upgrade -tags "${{env.TAGS_LINUX}}" -goos linux -goarch armhf -cc "zig cc -target arm-linux-musleabi -mcpu=arm1136j_s" deb "$tgt"
            go run build.go -no-upgrade -installsuffix=no-upgrade -tags "${{env.TAGS_LINUX}}" -goos linux -goarch arm64 -cc "zig cc -target aarch64-linux-musl" deb "$tgt"
          done
        env:
          BUILD_USER: debian
          CGO_ENABLED: "1"
          EXTRA_LDFLAGS: "-linkmode=external -extldflags=-static"

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

  #
  # Nightlies
  #

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

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

      - uses: actions/setup-go@v6
        with:
          go-version: ${{ needs.facts.outputs.go-version }}
          cache: false

      - 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 --no-update-modtime packages objstore:nightly

  #
  # Push release artifacts to Spaces
  #

  publish-release-files:
    name: Publish release files
    if: github.repository_owner == 'syncthing' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/tags/v'))
    environment: release
    permissions:
      contents: write
      id-token: write
      attestations: write
      artifact-metadata: write
    needs:
      - sign-for-upgrade
      - package-debian
      - facts
    env:
      VERSION: ${{ needs.facts.outputs.version }}
      RELEASE_KIND: ${{ needs.facts.outputs.release-kind }}
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
        with:
          fetch-depth: 0
          ref: ${{ github.ref }} # https://github.com/actions/checkout/issues/882

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

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

      - name: Create GitHub build provenance attestations
        uses: actions/attest-build-provenance@v3
        with:
          subject-path: packages/*

      - 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 --no-update-modtime 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 --no-update-modtime objstore:release/${{ env.VERSION }} objstore:release/latest

      - name: Create GitHub releases and push binaries
        run: |
          maybePrerelease=""
          if [[ $VERSION == *-* ]]; then
            maybePrerelease="--prerelease"
          fi
          export GH_PROMPT_DISABLED=1
          if ! gh release view --json name "$VERSION" >/dev/null 2>&1 ; then
            gh release create "$VERSION" \
              $maybePrerelease \
              --title "$VERSION" \
              --notes-from-tag
          fi
          gh release upload --clobber "$VERSION" \
            packages/*.asc packages/*.json \
            packages/syncthing-*.tar.gz \
            packages/syncthing-*.zip \
            packages/syncthing_*.deb

          PKGS=$(pwd)/packages
          cd /tmp # gh will not release for repo x while inside repo y
          for repo in relaysrv discosrv ; do
            export GH_REPO="syncthing/$repo"
            if ! gh release view --json name "$VERSION" >/dev/null 2>&1 ; then
              gh release create "$VERSION" \
                $maybePrerelease \
                --title "$VERSION" \
                --notes "https://github.com/syncthing/syncthing/releases/tag/$VERSION"
            fi
            gh release upload --clobber "$VERSION" \
              $PKGS/*.asc \
              $PKGS/*${repo}*
          done
        env:
          GH_TOKEN: ${{ secrets.ACTIONS_GITHUB_TOKEN }}

  #
  # Push Debian/APT archive
  #

  publish-apt:
    name: Publish APT
    if: github.repository_owner == 'syncthing' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release-nightly' || startsWith(github.ref, 'refs/tags/v'))
    environment: release
    needs:
      - package-debian
      - facts
    env:
      VERSION: ${{ needs.facts.outputs.version }}
      RELEASE_KIND: ${{ needs.facts.outputs.release-kind }}
      RELEASE_GENERATION: ${{ needs.facts.outputs.release-generation }}
    runs-on: ubuntu-latest
    steps:
      - name: Download packages
        uses: actions/download-artifact@v7
        with:
          name: debian-packages
          path: packages

      # Decide whether packages should go to stable, candidate or nightly
      - 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 apt

      - name: Prepare packages
        run: |
          sudo chown -R $(id -u) apt/pool
          mv -n packages/*.deb apt/pool

      - name: Update archive
        uses: docker://ghcr.io/kastelo/ezapt:latest
        with:
          args:
            publish --root apt
        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 --no-update-modtime apt objstore:apt

  #
  # Build and push (except for PRs) to GHCR.
  #

  docker-ghcr:
    name: Build and push Docker images (GHCR)
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    needs:
      - facts
    env:
      VERSION: ${{ needs.facts.outputs.version }}
      RELEASE_KIND: ${{ needs.facts.outputs.release-kind }}
    strategy:
      matrix:
        pkg:
          - syncthing
          - strelaysrv
          - stdiscosrv
        include:
          - pkg: syncthing
            dockerfile: Dockerfile
            image: syncthing
          - pkg: strelaysrv
            dockerfile: Dockerfile.strelaysrv
            image: relaysrv
          - pkg: stdiscosrv
            dockerfile: Dockerfile.stdiscosrv
            image: discosrv
    steps:
      - uses: actions/checkout@v5

      - uses: actions/setup-go@v6
        with:
          go-version: ${{ needs.facts.outputs.go-version }}
          cache: false

      - uses: mlugg/setup-zig@v2

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

      - name: Build binaries (CGO)
        run: |
          mkdir bin

          # amd64
          go run build.go -goos linux -goarch amd64 -tags "${{env.TAGS_LINUX}}" -cc "zig cc -target x86_64-linux-musl" -no-upgrade build ${{ matrix.pkg }}
          mv ${{ matrix.pkg }} bin/${{ matrix.pkg }}-linux-amd64

          # arm64
          go run build.go -goos linux -goarch arm64 -tags "${{env.TAGS_LINUX}}" -cc "zig cc -target aarch64-linux-musl" -no-upgrade build ${{ matrix.pkg }}
          mv ${{ matrix.pkg }} bin/${{ matrix.pkg }}-linux-arm64

          # arm
          go run build.go -goos linux -goarch arm -tags "${{env.TAGS_LINUX}}" -cc "zig cc -target arm-linux-musleabi -mcpu=arm1136j_s" -no-upgrade build ${{ matrix.pkg }}
          mv ${{ matrix.pkg }} bin/${{ matrix.pkg }}-linux-arm
        env:
          CGO_ENABLED: "1"
          BUILD_USER: docker
          EXTRA_LDFLAGS: "-linkmode=external -extldflags=-static"

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

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3

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

      - name: Set version tags
        run: |
          version=${VERSION#v}
          repo=ghcr.io/${{ github.repository_owner }}/${{ matrix.image }}
          ref="${{github.ref_name}}"
          ref=${ref//\//-} # slashes to dashes

          # List of tags for ghcr.io
          if [[ $version == @([0-9]|[0-9][0-9]).@([0-9]|[0-9][0-9]).@([0-9]|[0-9][0-9]) ]] ; then
            major=${version%.*.*}
            minor=${version%.*}
            tags=$repo:$version,$repo:$major,$repo:$minor,$repo:latest
          elif [[ $version == *-rc.@([0-9]|[0-9][0-9]) ]] ; then
            tags=$repo:$version,$repo:rc
          elif [[ $ref == "main" ]] ; then
            tags=$repo:edge
          else
            tags=$repo:$ref
          fi

          echo Pushing to $tags
          echo "DOCKER_TAGS=$tags" >> $GITHUB_ENV

      - name: Prepare context dir
        run: |
          mkdir ctx
          mv bin/* script ctx

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

  #
  # Sync images to Docker hub. This takes the images already pushed to GHCR
  # and copies them to Docker hub. Runs for releases only.
  #

  docker-hub:
    name: Sync images to Docker hub
    if: github.repository_owner == 'syncthing' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/release-nightly' || github.ref == 'refs/heads/infrastructure' || startsWith(github.ref, 'refs/tags/v'))
    runs-on: ubuntu-latest
    needs:
      - docker-ghcr
    environment: docker
    env:
      DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
      DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
    steps:
      - uses: actions/checkout@v5
      - name: Sync images
        uses: docker://docker.io/regclient/regsync:latest
        with:
          args:
            -c ./.github/regsync.yml
            once

  #
  # Check for known vulnerabilities in Go dependencies
  #

  govulncheck:
    runs-on: ubuntu-latest
    name: Run govulncheck
    needs:
      - facts
    steps:
      - uses: actions/checkout@v5

      - uses: actions/setup-go@v6
        with:
          go-version: ${{ needs.facts.outputs.go-version }}
          cache: false

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

  #
  # golangci-lint runs a suite of static analysis checks on the code
  #

  golangci:
    runs-on: ubuntu-latest
    name: Run golangci-lint
    if: github.event_name == 'pull_request'
    steps:
      - uses: actions/checkout@v5
      - uses: actions/setup-go@v6
        with:
          go-version: 'stable'

      - name: ensure asset generation
        run: go run build.go assets

      - name: golangci-lint
        uses: golangci/golangci-lint-action@v8
        with:
          only-new-issues: true

  #
  # Meta checks for formatting, copyright, etc
  #

  meta:
    name: Run meta checks
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - uses: actions/setup-go@v6
        with:
          go-version: 'stable'

      - run: |
          go run build.go assets
          go test -v ./meta
mirrors .github/workflows/mirrors.yaml
Triggers
push, delete
Runs on
ubuntu-latest
Jobs
codeberg
Actions
yesolutions/mirror-action
View raw YAML
name: Mirrors

on: [push, delete]

jobs:
  codeberg:
    name: Mirror to Codeberg
    if: github.repository_owner == 'syncthing'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0
      - uses: yesolutions/mirror-action@master
        with:
          REMOTE: ssh://git@codeberg.org/${{ github.repository }}.git
          GIT_SSH_PRIVATE_KEY: ${{ secrets.CODEBERG_PUSH_KEY }}
          GIT_SSH_NO_VERIFY_HOST: "true"
org-members .github/workflows/org-members.yaml
Triggers
workflow_dispatch, schedule
Runs on
ubuntu-latest
Jobs
run-recommendation
Actions
docker://ghcr.io/calmh/github-org-members:latest
View raw YAML
name: Org membership recommendations

on:
  workflow_dispatch:
  schedule:
    - cron: '0 0 1 * *'

jobs:

  run-recommendation:
    runs-on: ubuntu-latest
    name: Check for a recommendation
    steps:

      - uses: docker://ghcr.io/calmh/github-org-members:latest
        env:
          GITHUB_ORGANISATION: syncthing
          GITHUB_TOKEN: ${{ secrets.GOM_GITHUB_TOKEN }}
          GOM_IGNORE_USERS: ${{ secrets.GOM_IGNORE_USERS }}
          GOM_ALSO_REPOS: ${{ secrets.GOM_ALSO_REPOS }}
pr-metadata perms .github/workflows/pr-metadata.yaml
Triggers
pull_request_target
Runs on
ubuntu-latest
Jobs
labels
Actions
srvaroa/labeler
View raw YAML
name: PR metadata

on:
  pull_request_target:
    types:
      - opened
      - reopened
      - edited
      - synchronize

permissions:
  contents: read
  pull-requests: write

jobs:

  #
  # Set labels on PRs, which are then used to categorise release notes
  #

  labels:
    name: Set labels
    runs-on: ubuntu-latest
    steps:
    - uses: srvaroa/labeler@v1
      env:
        GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
release-syncthing perms .github/workflows/release-syncthing.yaml
Triggers
push
Runs on
ubuntu-latest
Jobs
create-release-tag
Actions
benc-uk/workflow-dispatch
Commands
  • if [[ "$GITHUB_REF_NAME" == "release" ]] ; then next=$(go run ./script/next-version.go) else next=$(go run ./script/next-version.go --pre) fi echo "NEXT=$next" >> $GITHUB_ENV echo "Next version is $next" prev=$(git describe --exclude "*-*" --abbrev=0) echo "PREV=$prev" >> $GITHUB_ENV echo "Previous version is $prev"
  • go run ./script/relnotes.go --new-ver "$NEXT" --branch "$GITHUB_REF_NAME" --prev-ver "$PREV" > notes.md
  • git config --global user.name 'Syncthing Release Automation' git config --global user.email 'release@syncthing.net' git tag -a -F notes.md --cleanup=whitespace "$NEXT" git push origin "$NEXT"
View raw YAML
name: Release Syncthing

on:
  push:
    branches:
      - release
      - release-rc*

permissions:
  contents: write

jobs:
  create-release-tag:
    name: Create release tag
    runs-on: ubuntu-latest
    environment: release
    steps:
      - uses: actions/checkout@v5
        with:
          fetch-depth: 0
          ref: ${{ github.ref }} # https://github.com/actions/checkout/issues/882
          token: ${{ secrets.ACTIONS_GITHUB_TOKEN }}

      - uses: actions/setup-go@v6
        with:
          go-version: stable

      - name: Determine version to release
        run: |
          if [[ "$GITHUB_REF_NAME" == "release" ]] ; then
            next=$(go run ./script/next-version.go)
          else
            next=$(go run ./script/next-version.go --pre)
          fi
          echo "NEXT=$next" >> $GITHUB_ENV
          echo "Next version is $next"

          prev=$(git describe --exclude "*-*" --abbrev=0)
          echo "PREV=$prev" >> $GITHUB_ENV
          echo "Previous version is $prev"

      - name: Determine release notes
        run: |
          go run ./script/relnotes.go --new-ver "$NEXT" --branch "$GITHUB_REF_NAME" --prev-ver "$PREV" > notes.md
        env:
          GITHUB_TOKEN: ${{ secrets.ACTIONS_GITHUB_TOKEN }}

      - name: Create and push tag
        run: |
          git config --global user.name 'Syncthing Release Automation'
          git config --global user.email 'release@syncthing.net'
          git tag -a -F notes.md --cleanup=whitespace "$NEXT"
          git push origin "$NEXT"

      - name: Trigger the build
        uses: benc-uk/workflow-dispatch@v1
        with:
          workflow: build-syncthing.yaml
          ref: refs/tags/${{ env.NEXT }}
          token: ${{ secrets.ACTIONS_GITHUB_TOKEN }}
trigger-nightly .github/workflows/trigger-nightly.yaml
Triggers
workflow_dispatch, schedule
Runs on
ubuntu-latest
Jobs
trigger-nightly
Commands
  • git push origin main:release-nightly
View raw YAML
name: Trigger nightly build & release
on:
  workflow_dispatch:
  schedule:
    # Run nightly build at 01:00 UTC
    - cron: '00 01 * * *'

jobs:

  trigger-nightly:
    if: github.repository_owner == 'syncthing'
    runs-on: ubuntu-latest
    name: Push to release-nightly to trigger build
    steps:

      - uses: actions/checkout@v5
        with:
          token: ${{ secrets.ACTIONS_GITHUB_TOKEN }}
          fetch-depth: 0

      - run: |
          git push origin main:release-nightly
update-docs-translations .github/workflows/update-docs-translations.yaml
Triggers
workflow_dispatch, schedule
Runs on
ubuntu-latest
Jobs
update_transifex_docs
Commands
  • set -euo pipefail git config --global user.name 'Syncthing Release Automation' git config --global user.email 'release@syncthing.net' bash build.sh translate bash build.sh prerelease git push
View raw YAML
name: Update translations and documentation
on:
  workflow_dispatch:
  schedule:
    - cron: '42 3 * * 1'

jobs:

  update_transifex_docs:
    runs-on: ubuntu-latest
    name: Update translations and documentation
    steps:
      - uses: actions/checkout@v5
        with:
          fetch-depth: 0
          token: ${{ secrets.ACTIONS_GITHUB_TOKEN }}
      - uses: actions/setup-go@v6
        with:
          go-version: stable
      - run: |
          set -euo pipefail
          git config --global user.name 'Syncthing Release Automation'
          git config --global user.email 'release@syncthing.net'
          bash build.sh translate
          bash build.sh prerelease
          git push
        env:
          WEBLATE_TOKEN: ${{ secrets.WEBLATE_TOKEN }}