Compare commits

..
Author SHA1 Message Date
Syncthing .stignore Fork d46d87fac0 apply stignore synchronization patch
custom release / build-custom-release (push) Successful in 3m12s
2026-08-11 11:17:09 +00:00
55 changed files with 1504 additions and 270 deletions
+142
View File
@@ -0,0 +1,142 @@
name: custom release
permissions:
contents: write
releases: write
on:
push:
branches:
- main
paths:
- ".gitea/workflows/custom-release.yml"
- "patches/**"
- "scripts/update-custom-release.sh"
- "scripts/sync-upstream.sh"
workflow_dispatch:
inputs:
upstream_tag:
description: "Optional upstream Syncthing tag, for example v2.1.0"
required: false
suffix:
description: "Optional custom release suffix, for example stignore.7"
required: false
schedule:
- cron: "17 04 * * *"
jobs:
build-custom-release:
runs-on: ffmini_macos_arm64
steps:
- name: Check out repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: false
- name: Configure Git author
run: |
git config user.name "Gitea Actions"
git config user.email "actions@git.felixfoertsch.de"
- name: Mirror upstream and rebuild patched main
run: ./scripts/sync-upstream.sh
env:
SYNC_REMOTE: origin
- name: Set up tea
run: |
go install code.gitea.io/tea@v0.14.1
echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH"
"$(go env GOPATH)/bin/tea" logins delete actions >/dev/null 2>&1 || true
"$(go env GOPATH)/bin/tea" logins add --name actions --url https://git.felixfoertsch.de --token "$GITEA_TOKEN" --no-version-check
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
- name: Import Developer ID certificate
run: |
set -euo pipefail
keychain_dir="$HOME/Library/Keychains"
mkdir -p "$keychain_dir"
keychain_path="$keychain_dir/syncthing-release-signing-${GITHUB_RUN_ID:-$$}.keychain-db"
keychain_password="$(openssl rand -hex 24)"
certificate_path="$RUNNER_TEMP/developer-id-application.p12"
previous_default_keychain="$(security default-keychain -d user 2>/dev/null | sed 's/[ "]//g' || true)"
echo "CUSTOM_RELEASE_KEYCHAIN_PATH=$keychain_path" >> "$GITHUB_ENV"
echo "CUSTOM_RELEASE_KEYCHAIN_PASSWORD=$keychain_password" >> "$GITHUB_ENV"
echo "CUSTOM_RELEASE_PREVIOUS_DEFAULT_KEYCHAIN=$previous_default_keychain" >> "$GITHUB_ENV"
if [ -z "$DEVELOPER_ID_APPLICATION_P12_BASE64" ]; then
echo "DEVELOPER_ID_APPLICATION_P12_BASE64 secret is required" >&2
exit 1
fi
printf '%s' "$DEVELOPER_ID_APPLICATION_P12_BASE64" | base64 -D > "$certificate_path"
rm -f "$keychain_path"
security create-keychain -p "$keychain_password" "$keychain_path"
security set-keychain-settings -lut 21600 "$keychain_path"
security unlock-keychain -p "$keychain_password" "$keychain_path"
security import "$certificate_path" -k "$keychain_path" -P "$DEVELOPER_ID_APPLICATION_P12_PASSWORD" -A -T /usr/bin/codesign -T /usr/bin/security
existing_keychains=()
while IFS= read -r existing_keychain; do
existing_keychain="$(printf '%s' "$existing_keychain" | sed 's/[ "]//g')"
if [ -n "$existing_keychain" ] && [ -e "$existing_keychain" ] && [[ "$existing_keychain" != *"/syncthing-release-signing-"*".keychain-db" ]]; then
existing_keychains+=("$existing_keychain")
fi
done < <(security list-keychains)
security list-keychains -s "$keychain_path" "${existing_keychains[@]}"
security list-keychains -d user -s "$keychain_path" "${existing_keychains[@]}" || true
security default-keychain -d user -s "$keychain_path" || true
security list-keychains
security list-keychains -d user || true
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$keychain_password" "$keychain_path"
identity_output="$(security find-identity -v -p codesigning "$keychain_path")"
printf '%s\n' "$identity_output"
security find-identity -v -p codesigning
codesign_identity_sha1="$(printf '%s\n' "$identity_output" | awk '/"Developer ID Application:/ { print $2; exit }')"
codesign_identity="$(printf '%s\n' "$identity_output" | sed -n 's/.*"\(Developer ID Application:[^"]*\)".*/\1/p' | head -n 1)"
if [ -z "$codesign_identity" ]; then
echo "Developer ID Application signing identity is required in DEVELOPER_ID_APPLICATION_P12_BASE64" >&2
exit 1
fi
probe_binary="$RUNNER_TEMP/codesign-probe"
cp /usr/bin/true "$probe_binary"
codesign --force --dryrun --sign "$codesign_identity" --keychain "$keychain_path" --options runtime --timestamp "$probe_binary"
echo "CUSTOM_RELEASE_CODESIGN_IDENTITY=$codesign_identity" >> "$GITHUB_ENV"
echo "CUSTOM_RELEASE_CODESIGN_IDENTITY_SHA1=$codesign_identity_sha1" >> "$GITHUB_ENV"
env:
DEVELOPER_ID_APPLICATION_P12_BASE64: ${{ secrets.DEVELOPER_ID_APPLICATION_P12_BASE64 }}
DEVELOPER_ID_APPLICATION_P12_PASSWORD: ${{ secrets.DEVELOPER_ID_APPLICATION_P12_PASSWORD }}
- name: Build patched Syncthing release
run: ./scripts/update-custom-release.sh
env:
CUSTOM_RELEASE_UPSTREAM_TAG: ${{ github.event.inputs.upstream_tag }}
CUSTOM_RELEASE_SUFFIX: ${{ github.event.inputs.suffix }}
CUSTOM_RELEASE_PUSH: "1"
CUSTOM_RELEASE_PUSH_BRANCH: "0"
CUSTOM_RELEASE_REMOTE: origin
CUSTOM_RELEASE_BUILDS: "darwin/arm64/zip/1 linux/amd64/tar/0 linux/arm64/tar/0"
CUSTOM_RELEASE_CODESIGN_TEAM_ID: "NG5W75WE8U"
CUSTOM_RELEASE_SIGN_DARWIN: "1"
CUSTOM_RELEASE_REQUIRE_GATEKEEPER_ASSESSMENT: "0"
CUSTOM_RELEASE_CREATE_GITEA_RELEASE: "1"
CUSTOM_RELEASE_TEA_REPO: felixfoertsch/syncthing
- name: Delete temporary keychain
if: always()
run: |
if [ -n "${CUSTOM_RELEASE_PREVIOUS_DEFAULT_KEYCHAIN:-}" ] && [ -e "$CUSTOM_RELEASE_PREVIOUS_DEFAULT_KEYCHAIN" ]; then
security default-keychain -d user -s "$CUSTOM_RELEASE_PREVIOUS_DEFAULT_KEYCHAIN" || true
fi
if [ -n "${CUSTOM_RELEASE_KEYCHAIN_PATH:-}" ]; then
security delete-keychain "$CUSTOM_RELEASE_KEYCHAIN_PATH" || true
fi
+3 -13
View File
@@ -7,7 +7,7 @@ on:
- infra-*
env:
GO_VERSION: "~1.27.0"
GO_VERSION: "~1.26.0"
CGO_ENABLED: "0"
BUILD_USER: docker
BUILD_HOST: github.syncthing.net
@@ -15,7 +15,6 @@ env:
permissions:
contents: read
packages: write
id-token: write
jobs:
docker-syncthing:
@@ -31,11 +30,11 @@ jobs:
- stupgrades
- ursrv
steps:
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # was: actions/checkout@v5
- uses: actions/checkout@v5
with:
fetch-depth: 0
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # was: actions/setup-go@v6
- uses: actions/setup-go@v6
with:
go-version: ${{ env.GO_VERSION }}
check-latest: true
@@ -78,7 +77,6 @@ jobs:
echo "TAGS=$tags" >> $GITHUB_ENV
- name: Build and push
id: build
uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5
with:
context: .
@@ -88,11 +86,3 @@ jobs:
tags: ${{ env.TAGS }}
labels: |
org.opencontainers.image.revision=${{ github.sha }}
- name: Install Cosign
if: github.ref == 'refs/heads/infrastructure'
uses: sigstore/cosign-installer@ba7bc0a3fef59531c69a25acd34668d6d3fe6f22 # v4.1.0
- name: Sign image
if: github.ref == 'refs/heads/infrastructure'
run: cosign sign --yes --recursive ghcr.io/syncthing/infra/${{ matrix.pkg }}:latest@${{ steps.build.outputs.digest }}
+3 -21
View File
@@ -18,7 +18,7 @@ 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.27.0"
GO_VERSION: "~1.26.0"
# Optimize compatibility on the slow architectures.
GOMIPS: softfloat
@@ -108,7 +108,7 @@ jobs:
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.26.0", "~1.27.0"]
go: ["~1.25.0", "~1.26.0"]
runs-on: ${{ matrix.runner }}
steps:
- name: Set git to use LF
@@ -445,7 +445,7 @@ jobs:
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 "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
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
@@ -1012,7 +1012,6 @@ jobs:
permissions:
contents: read
packages: write
id-token: write
needs:
- facts
env:
@@ -1116,7 +1115,6 @@ jobs:
mv bin/* script ctx
- name: Build and push Docker image
id: build
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
with:
context: ctx
@@ -1128,22 +1126,6 @@ jobs:
org.opencontainers.image.version=${{ env.VERSION }}
org.opencontainers.image.revision=${{ github.sha }}
- name: Install Cosign
if: github.event_name == 'push'
uses: sigstore/cosign-installer@ba7bc0a3fef59531c69a25acd34668d6d3fe6f22 # v4.1.0
- name: Sign image
if: github.event_name == 'push'
env:
DIGEST: ${{ steps.build.outputs.digest }}
run: |
IFS="," read -ra tags <<< $DOCKER_TAGS
images=""
for tag in ${tags[@]}; do
images+="${tag}@${DIGEST} "
done
cosign sign --yes --recursive ${images}
#
# Sync images to Docker hub. This takes the images already pushed to GHCR
# and copies them to Docker hub. Runs for releases only.
+150
View File
@@ -0,0 +1,150 @@
name: custom release
permissions:
contents: write
on:
push:
branches:
- main
paths:
- ".github/workflows/custom-release.yml"
- "patches/**"
- "scripts/update-custom-release.sh"
- "scripts/sync-upstream.sh"
workflow_dispatch:
inputs:
upstream_tag:
description: "Optional upstream Syncthing tag, for example v2.1.3"
required: false
suffix:
description: "Optional custom release suffix, for example stignore.7"
required: false
schedule:
- cron: "17 04 * * *"
jobs:
build-custom-release:
runs-on: macos-14
steps:
- name: Check out repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: false
- name: Configure Git author
run: |
git config user.name "GitHub Actions"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
- name: Mirror upstream and rebuild patched main
run: ./scripts/sync-upstream.sh
env:
SYNC_REMOTE: origin
- name: Import Developer ID certificate
run: |
set -euo pipefail
keychain_path="$RUNNER_TEMP/syncthing-release-signing.keychain-db"
keychain_password="$(openssl rand -hex 24)"
certificate_path="$RUNNER_TEMP/developer-id-application.p12"
previous_default_keychain="$(security default-keychain -d user 2>/dev/null | sed 's/[ "]//g' || true)"
echo "::add-mask::$keychain_password"
echo "CUSTOM_RELEASE_KEYCHAIN_PATH=$keychain_path" >> "$GITHUB_ENV"
echo "CUSTOM_RELEASE_KEYCHAIN_PASSWORD=$keychain_password" >> "$GITHUB_ENV"
echo "CUSTOM_RELEASE_CERTIFICATE_PATH=$certificate_path" >> "$GITHUB_ENV"
echo "CUSTOM_RELEASE_PREVIOUS_DEFAULT_KEYCHAIN=$previous_default_keychain" >> "$GITHUB_ENV"
if [ -z "$DEVELOPER_ID_APPLICATION_P12_BASE64" ]; then
echo "DEVELOPER_ID_APPLICATION_P12_BASE64 secret is required" >&2
exit 1
fi
if [ -z "$DEVELOPER_ID_APPLICATION_P12_PASSWORD" ]; then
echo "DEVELOPER_ID_APPLICATION_P12_PASSWORD secret is required" >&2
exit 1
fi
printf '%s' "$DEVELOPER_ID_APPLICATION_P12_BASE64" | base64 -D > "$certificate_path"
security create-keychain -p "$keychain_password" "$keychain_path"
security set-keychain-settings -lut 21600 "$keychain_path"
security unlock-keychain -p "$keychain_password" "$keychain_path"
security import "$certificate_path" -k "$keychain_path" -P "$DEVELOPER_ID_APPLICATION_P12_PASSWORD" -A -T /usr/bin/codesign -T /usr/bin/security
security list-keychains -d user -s "$keychain_path"
security default-keychain -d user -s "$keychain_path"
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$keychain_password" "$keychain_path"
identity_output="$(security find-identity -v -p codesigning "$keychain_path")"
printf '%s\n' "$identity_output"
codesign_identity="$(printf '%s\n' "$identity_output" | sed -n 's/.*"\(Developer ID Application:[^"]*\)".*/\1/p' | head -n 1)"
if [ -z "$codesign_identity" ]; then
echo "Developer ID Application signing identity is required in DEVELOPER_ID_APPLICATION_P12_BASE64" >&2
exit 1
fi
probe_binary="$RUNNER_TEMP/codesign-probe"
cp /usr/bin/true "$probe_binary"
codesign --force --dryrun --sign "$codesign_identity" --keychain "$keychain_path" --options runtime --timestamp "$probe_binary"
echo "CUSTOM_RELEASE_CODESIGN_IDENTITY=$codesign_identity" >> "$GITHUB_ENV"
env:
DEVELOPER_ID_APPLICATION_P12_BASE64: ${{ secrets.DEVELOPER_ID_APPLICATION_P12_BASE64 }}
DEVELOPER_ID_APPLICATION_P12_PASSWORD: ${{ secrets.DEVELOPER_ID_APPLICATION_P12_PASSWORD }}
- name: Build patched Syncthing release
run: |
case "$(uname -m)" in
arm64) darwin_arch=arm64 ;;
x86_64) darwin_arch=amd64 ;;
*) echo "Unsupported macOS runner architecture: $(uname -m)" >&2; exit 1 ;;
esac
export CUSTOM_RELEASE_BUILDS="darwin/$darwin_arch/zip/1 linux/amd64/tar/0 linux/arm64/tar/0"
./scripts/update-custom-release.sh
env:
CUSTOM_RELEASE_UPSTREAM_TAG: ${{ github.event.inputs.upstream_tag }}
CUSTOM_RELEASE_SUFFIX: ${{ github.event.inputs.suffix }}
CUSTOM_RELEASE_PUSH: "1"
CUSTOM_RELEASE_PUSH_BRANCH: "0"
CUSTOM_RELEASE_REMOTE: origin
CUSTOM_RELEASE_CODESIGN_TEAM_ID: "NG5W75WE8U"
CUSTOM_RELEASE_SIGN_DARWIN: "1"
CUSTOM_RELEASE_REQUIRE_GATEKEEPER_ASSESSMENT: "0"
CUSTOM_RELEASE_CREATE_GITEA_RELEASE: "0"
GH_TOKEN: ${{ github.token }}
- name: Publish GitHub release
run: |
tag="${CUSTOM_RELEASE_UPSTREAM_TAG:-$(git ls-remote --refs --tags --sort='version:refname' https://github.com/syncthing/syncthing.git 'v[0-9]*' | awk '{ tag = $2; sub("refs/tags/", "", tag); if (tag ~ /^v[0-9]+\.[0-9]+\.[0-9]+$/) latest = tag } END { print latest }')}-$CUSTOM_RELEASE_SUFFIX"
if gh release view "$tag" >/dev/null 2>&1; then
echo "GitHub release $tag already exists; nothing to do."
exit 0
fi
assets=()
for asset in dist/*; do
[ -f "$asset" ] || continue
[ "$(basename "$asset")" = release-notes.md ] && continue
assets+=("$asset")
done
gh release create "$tag" "${assets[@]}" --title "$tag" --notes-file dist/release-notes.md --verify-tag
env:
CUSTOM_RELEASE_UPSTREAM_TAG: ${{ github.event.inputs.upstream_tag }}
CUSTOM_RELEASE_SUFFIX: ${{ github.event.inputs.suffix || 'stignore.7' }}
GH_TOKEN: ${{ github.token }}
- name: Delete temporary keychain
if: always()
run: |
if [ -n "${CUSTOM_RELEASE_PREVIOUS_DEFAULT_KEYCHAIN:-}" ] && [ -e "$CUSTOM_RELEASE_PREVIOUS_DEFAULT_KEYCHAIN" ]; then
security default-keychain -d user -s "$CUSTOM_RELEASE_PREVIOUS_DEFAULT_KEYCHAIN" || true
fi
if [ -n "${CUSTOM_RELEASE_KEYCHAIN_PATH:-}" ]; then
security delete-keychain "$CUSTOM_RELEASE_KEYCHAIN_PATH" || true
fi
if [ -n "${CUSTOM_RELEASE_CERTIFICATE_PATH:-}" ]; then
rm -f "$CUSTOM_RELEASE_CERTIFICATE_PATH"
fi
+8
View File
@@ -1,3 +1,11 @@
# Syncthing with `.stignore` synchronization
This fork synchronizes the root-level `.stignore` file as regular folder
content, while keeping `.stfolder` and `.stversions` protected as Syncthing
internals. The `upstream` branch mirrors the official Syncthing `main` branch;
this fork's `main` branch and releases apply the `.stignore` synchronization
patch.
[![Syncthing][14]][15]
---
+10 -27
View File
@@ -7,7 +7,6 @@
package main
import (
"bytes"
"context"
"encoding/json"
"fmt"
@@ -21,7 +20,6 @@ import (
"strconv"
"strings"
"sync"
"text/template"
"time"
"github.com/alecthomas/kong"
@@ -33,12 +31,11 @@ import (
)
type cli struct {
Listen string `default:":8080" help:"Listen address"`
MetricsListen string `default:":8082" help:"Listen address for metrics"`
URL string `short:"u" default:"https://api.github.com/repos/syncthing/syncthing/releases?per_page=25" help:"GitHub releases url"`
Forward []string `short:"f" help:"Forwarded pages, format: /path->https://example/com/url"`
CacheTime time.Duration `default:"15m" help:"Cache time"`
AssetURLTemplate string `help:"Template for asset URLs, blank to use GitHub default" env:"ASSET_URL_TEMPLATE"`
Listen string `default:":8080" help:"Listen address"`
MetricsListen string `default:":8082" help:"Listen address for metrics"`
URL string `short:"u" default:"https://api.github.com/repos/syncthing/syncthing/releases?per_page=25" help:"GitHub releases url"`
Forward []string `short:"f" help:"Forwarded pages, format: /path->https://example/com/url"`
CacheTime time.Duration `default:"15m" help:"Cache time"`
}
func main() {
@@ -72,7 +69,7 @@ func server(params *cli) error {
}()
}
cache := &cachedReleases{url: params.URL, assetURLTemplate: params.AssetURLTemplate}
cache := &cachedReleases{url: params.URL}
if err := cache.Update(context.Background()); err != nil {
return fmt.Errorf("initial cache update: %w", err)
} else {
@@ -227,7 +224,7 @@ func filterForLatest(rels []upgrade.Release) []upgrade.Release {
return filtered
}
var userAgentOSArchExp = regexp.MustCompile(`^[Ss]yncthing.*\(.+ (\w+)-(\w+)\)$`)
var userAgentOSArchExp = regexp.MustCompile(`^syncthing.*\(.+ (\w+)-(\w+)\)$`)
func filterForCompatibility(rels []upgrade.Release, ua, osv string) []upgrade.Release {
osArch := userAgentOSArchExp.FindStringSubmatch(ua)
@@ -269,7 +266,6 @@ func filterForCompatibility(rels []upgrade.Release, ua, osv string) []upgrade.Re
type cachedReleases struct {
url string
assetURLTemplate string
mut sync.RWMutex
current []upgrade.Release
latestRel, latestPre string
@@ -282,7 +278,7 @@ func (c *cachedReleases) Releases() []upgrade.Release {
}
func (c *cachedReleases) Update(ctx context.Context) error {
rels, err := fetchGithubReleases(ctx, c.url, c.assetURLTemplate)
rels, err := fetchGithubReleases(ctx, c.url)
if err != nil {
return err
}
@@ -310,7 +306,7 @@ func (c *cachedReleases) Update(ctx context.Context) error {
return nil
}
func fetchGithubReleases(ctx context.Context, url, assetURLTemplate string) ([]upgrade.Release, error) {
func fetchGithubReleases(ctx context.Context, url string) ([]upgrade.Release, error) {
req, err := http.NewRequestWithContext(context.TODO(), http.MethodGet, url, nil)
if err != nil {
metricHTTPRequests.WithLabelValues("github-releases", "error").Inc()
@@ -329,25 +325,12 @@ func fetchGithubReleases(ctx context.Context, url, assetURLTemplate string) ([]u
}
metricHTTPRequests.WithLabelValues("github-releases", "success").Inc()
tpl, err := template.New("asset").Parse(assetURLTemplate)
if err != nil {
return nil, err
}
// Move the URL used for browser downloads to the URL field, and remove
// the browser URL field. This avoids going via the GitHub API for
// downloads, since Syncthing uses the URL field.
for _, rel := range rels {
for j, asset := range rel.Assets {
if assetURLTemplate != "" {
buf := new(bytes.Buffer)
if err := tpl.Execute(buf, map[string]any{"Release": rel, "Asset": asset}); err != nil {
return nil, err
}
rel.Assets[j].URL = buf.String()
} else {
rel.Assets[j].URL = asset.BrowserURL
}
rel.Assets[j].URL = asset.BrowserURL
rel.Assets[j].BrowserURL = ""
}
}
+2 -2
View File
@@ -311,7 +311,7 @@ func (s *server) handleNewData(w http.ResponseWriter, r *http.Request) {
bs, _ := io.ReadAll(lr)
if err := json.Unmarshal(bs, &rep); err != nil {
log.Error("Failed to decode JSON", slogutil.Error(err))
http.Error(w, "JSON Decode Error", http.StatusBadRequest)
http.Error(w, "JSON Decode Error", http.StatusInternalServerError)
return
}
@@ -321,7 +321,7 @@ func (s *server) handleNewData(w http.ResponseWriter, r *http.Request) {
if err := rep.Validate(); err != nil {
log.Error("Failed to validate report", slogutil.Error(err))
http.Error(w, "Validation Error", http.StatusBadRequest)
http.Error(w, "Validation Error", http.StatusInternalServerError)
return
}
+15 -25
View File
@@ -23,7 +23,6 @@ import (
"net"
"net/http"
"net/url"
"os"
"slices"
"strconv"
"strings"
@@ -155,10 +154,21 @@ func (s *apiSrv) handler(w http.ResponseWriter, req *http.Request) {
slog.Debug("Handling request", "id", reqID, "method", req.Method, "url", req.URL, "proto", req.Proto)
var remoteAddr *net.TCPAddr
remoteAddr := &net.TCPAddr{
IP: nil,
Port: -1,
}
if s.useHTTP {
// X-Forwarded-For can have multiple client IPs; split using the comma separator
remoteAddr = forwardedRemoteAddr(req)
forwardIP, _, _ := strings.Cut(req.Header.Get("X-Forwarded-For"), ",")
// net.ParseIP will return nil if leading/trailing whitespace exists; use strings.TrimSpace()
remoteAddr.IP = net.ParseIP(strings.TrimSpace(forwardIP))
if parsedPort, err := strconv.ParseInt(req.Header.Get("X-Client-Port"), 10, 0); err == nil {
remoteAddr.Port = int(parsedPort)
}
} else {
var err error
remoteAddr, err = net.ResolveTCPAddr("tcp", req.RemoteAddr)
@@ -181,21 +191,6 @@ func (s *apiSrv) handler(w http.ResponseWriter, req *http.Request) {
}
}
func forwardedRemoteAddr(req *http.Request) *net.TCPAddr {
forwardIP, _, _ := strings.Cut(req.Header.Get("X-Forwarded-For"), ",")
remoteAddr := &net.TCPAddr{
IP: net.ParseIP(strings.TrimSpace(forwardIP)),
Port: -1,
}
if parsedPort, err := strconv.ParseInt(req.Header.Get("X-Client-Port"), 10, 0); err == nil {
remoteAddr.Port = int(parsedPort)
}
return remoteAddr
}
func (s *apiSrv) handleGET(w http.ResponseWriter, req *http.Request) {
reqID := req.Context().Value(idKey).(requestID)
@@ -211,7 +206,6 @@ func (s *apiSrv) handleGET(w http.ResponseWriter, req *http.Request) {
rec, err := s.db.get(&deviceID)
if err != nil {
// some sort of internal error
slog.Warn("Failed to handle GET request", "id", reqID, "error", err)
lookupRequestsTotal.WithLabelValues("internal_error").Inc()
w.Header().Set("Retry-After", errorRetryAfterString())
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
@@ -290,7 +284,7 @@ func (s *apiSrv) handlePOST(remoteAddr *net.TCPAddr, w http.ResponseWriter, req
}
if err := s.handleAnnounce(deviceID, addresses); err != nil {
slog.Warn("Failed to handle POST request", "id", reqID, "error", err)
slog.Debug("Failed to handle request", "id", reqID, "error", err)
announceRequestsTotal.WithLabelValues("internal_error").Inc()
w.Header().Set("Retry-After", errorRetryAfterString())
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
@@ -332,11 +326,7 @@ func (s *apiSrv) handleAnnounce(deviceID protocol.DeviceID, addresses []string)
return s.db.merge(&deviceID, dbAddrs, seen)
}
func handlePing(w http.ResponseWriter, req *http.Request) {
hostname, _ := os.Hostname()
w.Header().Set("Discovery-Server-Instance", hostname)
w.Header().Set("Discovery-Client-Address", req.RemoteAddr)
w.Header().Set("Discovery-Client-Remote", forwardedRemoteAddr(req).String())
func handlePing(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusNoContent)
}
+4 -4
View File
@@ -35,10 +35,10 @@ const (
// Reannounce-After is set to reannounceAfterSeconds +
// random(reannounzeFuzzSeconds), similar for Retry-After
reannounceAfterSeconds = 2400
reannounzeFuzzSeconds = 1800
errorRetryAfterSeconds = 1800
errorRetryFuzzSeconds = 900
reannounceAfterSeconds = 3300
reannounzeFuzzSeconds = 300
errorRetryAfterSeconds = 1500
errorRetryFuzzSeconds = 300
// Retry for not found is notFoundRetrySeenSeconds for records we have
// seen an announcement for (but it's not active right now) and
-2
View File
@@ -18,7 +18,6 @@ import (
"net/http"
"strings"
"github.com/syncthing/syncthing/lib/build"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/locations"
@@ -116,7 +115,6 @@ func (c *apiClient) Endpoint() string {
func (c *apiClient) Do(req *http.Request) (*http.Response, error) {
req.Header.Set("X-Api-Key", c.apikey)
req.Header.Set("User-Agent", build.UserAgent())
resp, err := c.Client.Do(req)
if err != nil {
return nil, err
-3
View File
@@ -20,7 +20,6 @@ import (
"time"
"github.com/syncthing/syncthing/internal/slogutil"
"github.com/syncthing/syncthing/lib/build"
)
const (
@@ -82,7 +81,6 @@ func uploadPanicLog(ctx context.Context, urlBase, file string) error {
if err != nil {
return err
}
headReq.Header.Set("User-Agent", build.UserAgent())
// Set a reasonable timeout on the HEAD request
headCtx, headCancel := context.WithTimeout(ctx, headRequestTimeout)
@@ -103,7 +101,6 @@ func uploadPanicLog(ctx context.Context, urlBase, file string) error {
if err != nil {
return err
}
putReq.Header.Set("User-Agent", build.UserAgent())
// Set a reasonable timeout on the PUT request
putCtx, putCancel := context.WithTimeout(ctx, putRequestTimeout)
-2
View File
@@ -374,7 +374,6 @@ func upgradeViaRest() error {
target := u.String()
r, _ := http.NewRequest(http.MethodPost, target, nil)
r.Header.Set("X-Api-Key", cfg.GUI().APIKey)
r.Header.Set("User-Agent", build.UserAgent())
tr := &http.Transport{
DialContext: dialer.DialContext,
@@ -956,7 +955,6 @@ func (c browserCmd) Run() error {
if err != nil {
return err
}
req.Header.Set("User-Agent", build.UserAgent())
_, err = http.DefaultClient.Do(req) //nolint:bodyclose // we're exiting in a millisecond
if err != nil {
slog.Error("GUI not available", slogutil.Error(err))
-5
View File
@@ -46,8 +46,3 @@
linux: "3.2"
windows: "10.0"
- runtime: go1.27
requirements:
darwin: "22" # macOS 13 (Ventura)
linux: "3.2"
windows: "10.0"
+29 -22
View File
@@ -1,10 +1,10 @@
module github.com/syncthing/syncthing
go 1.26.2
go 1.25.0
require (
github.com/AudriusButkevicius/recli v0.0.7
github.com/alecthomas/kong v1.16.1
github.com/alecthomas/kong v1.16.0
github.com/aws/aws-sdk-go v1.55.8
github.com/calmh/incontainer v1.0.0
github.com/calmh/xdr v1.2.0
@@ -16,23 +16,23 @@ require (
github.com/gobwas/glob v0.2.3
github.com/gofrs/flock v0.13.0
github.com/hashicorp/golang-lru/v2 v2.0.7
github.com/jackpal/gateway v1.2.0
github.com/jackpal/gateway v1.1.1
github.com/jackpal/go-nat-pmp v1.0.2
github.com/jmoiron/sqlx v1.4.0
github.com/julienschmidt/httprouter v1.3.0
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/maruel/panicparse/v2 v2.5.0
github.com/mattn/go-sqlite3 v1.14.50
github.com/mattn/go-sqlite3 v1.14.48
github.com/maxmind/geoipupdate/v6 v6.1.0
github.com/miscreant/miscreant.go v0.0.0-20200214223636-26d376326b75
github.com/oschwald/geoip2-golang v1.13.0
github.com/pierrec/lz4/v4 v4.1.29
github.com/prometheus/client_golang v1.24.1
github.com/pierrec/lz4/v4 v4.1.27
github.com/prometheus/client_golang v1.24.0
github.com/puzpuzpuz/xsync/v3 v3.5.1
github.com/quic-go/quic-go v0.61.0
github.com/rabbitmq/amqp091-go v1.14.0
github.com/quic-go/quic-go v0.60.0
github.com/rabbitmq/amqp091-go v1.13.0
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9
github.com/shirou/gopsutil/v4 v4.26.7
github.com/shirou/gopsutil/v4 v4.26.6
github.com/syncthing/notify v0.0.0-20250528144937-c7027d4f7465
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d
github.com/thejerf/suture/v4 v4.0.6
@@ -40,14 +40,14 @@ require (
github.com/vitrun/qart v0.0.0-20160531060029-bf64b92db6b0
github.com/willabides/kongplete v0.4.0
github.com/wlynxg/anet v0.0.5
golang.org/x/crypto v0.55.0
golang.org/x/exp v0.0.0-20260813180055-c1d0aacb2297
golang.org/x/net v0.58.0
golang.org/x/crypto v0.54.0
golang.org/x/exp v0.0.0-20260718201538-764159d718ef
golang.org/x/net v0.57.0
golang.org/x/sys v0.47.0
golang.org/x/text v0.41.0
golang.org/x/text v0.40.0
golang.org/x/time v0.15.0
google.golang.org/protobuf v1.36.12
modernc.org/sqlite v1.57.0
google.golang.org/protobuf v1.36.11
modernc.org/sqlite v1.54.0
sigs.k8s.io/yaml v1.6.0
)
@@ -58,41 +58,48 @@ require (
github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/ebitengine/purego v0.10.2 // indirect
github.com/ebitengine/purego v0.10.0 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.8 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/pprof v0.0.0-20250423184734-337e5dd93bb4 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect
github.com/mattn/go-isatty v0.0.24 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/maxbrunsfeld/counterfeiter/v6 v6.12.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/ncruces/go-strftime v1.0.0 // indirect
github.com/nxadm/tail v1.4.11 // indirect
github.com/oschwald/maxminddb-golang v1.13.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/posener/complete v1.2.3 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.70.1 // indirect
github.com/prometheus/common v0.70.0 // indirect
github.com/prometheus/procfs v0.21.1 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/riywo/loginshell v0.0.0-20200815045211-7d26008be1ab // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/stretchr/testify v1.11.1 // indirect
github.com/tklauser/go-sysconf v0.3.16 // indirect
github.com/tklauser/numcpus v0.11.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.yaml.in/yaml/v2 v2.4.4 // indirect
golang.org/x/mod v0.39.0 // indirect
golang.org/x/mod v0.38.0 // indirect
golang.org/x/sync v0.22.0 // indirect
golang.org/x/telemetry v0.0.0-20260811182544-a038080d80e5 // indirect
golang.org/x/tools v0.49.0 // indirect
modernc.org/libc v1.74.4 // indirect
golang.org/x/telemetry v0.0.0-20260708182218-49f421fb7959 // indirect
golang.org/x/tools v0.48.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/libc v1.74.1 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
)
+58 -50
View File
@@ -7,8 +7,8 @@ github.com/Azure/go-ntlmssp v0.1.1/go.mod h1:NYqdhxd/8aAct/s4qSYZEerdPuH1liG2/X9
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/kong v1.16.1 h1:ixhCt93XkJ98kGposQ54+bl0IK6XwqB40AsMynU7Z8E=
github.com/alecthomas/kong v1.16.1/go.mod h1:wrlbXem1CWqUV5Vbmss5ISYhsVPkBb1Yo7YKJghju2I=
github.com/alecthomas/kong v1.16.0 h1:g92/kUxBcdcTPOM79yE63viJgtcp5dNyrB3/O2cjYT4=
github.com/alecthomas/kong v1.16.0/go.mod h1:wrlbXem1CWqUV5Vbmss5ISYhsVPkBb1Yo7YKJghju2I=
github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs=
github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e h1:4dAU9FXIyQktpoUAgOJK3OTFc/xug0PCXYCqU0FgDKI=
@@ -45,8 +45,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/ebitengine/purego v0.10.2 h1:W809HbnvzAxgdm+aOvlSekrM16wGCdT/e76+9tS7gzE=
github.com/ebitengine/purego v0.10.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU=
github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
@@ -86,8 +86,8 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3 h1:LMLX+LgTNWpfvCBdFebv6EsYotImrt/Ppc5cXIriCSo=
github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3/go.mod h1:jl5iWTm0/hd5PjEYEOuwAJ57L/CibdZfrqZ5XA5GrCk=
github.com/google/pprof v0.0.0-20250423184734-337e5dd93bb4 h1:gD0vax+4I+mAj+jEChEf25Ia07Jq7kYOFO5PPhAxFl4=
github.com/google/pprof v0.0.0-20250423184734-337e5dd93bb4/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@@ -104,8 +104,6 @@ github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUq
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jackpal/gateway v1.2.0 h1:euPRe4t7JfTaqC5Lr78HXl2wSHo54XndTtiAcIxkb5g=
github.com/jackpal/gateway v1.2.0/go.mod h1:/jchvRi4HukAqV24da70iaBMFcSrX3rNWdR5K9VHd0A=
github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus=
github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
@@ -130,21 +128,27 @@ github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4d
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/klauspost/compress v1.19.1 h1:VsB4HPswih7mmZ8WleSFQ75c/Ui1M4trX5oAsJnhSlk=
github.com/klauspost/compress v1.19.1/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
github.com/klauspost/compress v1.19.0 h1:sXLILfc9jV2QYWkzFOPWStmcUVH2RHEB1JCdY2oVvCQ=
github.com/klauspost/compress v1.19.0/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 h1:7UMa6KCCMjZEMDtTVdcGu0B1GmmC7QJKiCCjyTAWQy0=
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
github.com/marbens-arch/gateway v1.1.2-0.20260308173556-c567cc04e7d4 h1:8TAc8a26Pp0+8I6rj43BqhMi84D1zvtY0sPzO3IKnBw=
github.com/marbens-arch/gateway v1.1.2-0.20260308173556-c567cc04e7d4/go.mod h1:Tl1vZVtUaXx5j6P5HFmv45alhEi4yHHLfT4PRbB7eyw=
github.com/maruel/panicparse/v2 v2.5.0 h1:yCtuS0FWjfd0RTYMXGpDvWcb0kINm8xJGu18/xMUh00=
github.com/maruel/panicparse/v2 v2.5.0/go.mod h1:DA2fDiBk63bKfBf4CVZP9gb4fuvzdPbLDsSI873hweQ=
github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI=
github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mattn/go-sqlite3 v1.14.50 h1:dmdFvo1XG4MPzA4IkAmE9upVz/Nj31uRoM5+jC8hYbY=
github.com/mattn/go-sqlite3 v1.14.50/go.mod h1:6JTjA44L93a0QCyJef5YvlPoKXntQPjzWv5gtm9sB6w=
github.com/mattn/go-sqlite3 v1.14.48 h1:7XHIgl0a8HwOaiK4E47ozLkST78rR9+OtNGx27D/TFs=
github.com/mattn/go-sqlite3 v1.14.48/go.mod h1:6JTjA44L93a0QCyJef5YvlPoKXntQPjzWv5gtm9sB6w=
github.com/maxbrunsfeld/counterfeiter/v6 v6.12.0 h1:aOeI7xAOVdK+R6xbVsZuU9HmCZYmQVmZgPf9xJUd2Sg=
github.com/maxbrunsfeld/counterfeiter/v6 v6.12.0/go.mod h1:0hZWbtfeCYUQeAQdPLUzETiBhUSns7O6LDj9vH88xKA=
github.com/maxmind/geoipupdate/v6 v6.1.0 h1:sdtTHzzQNJlXF5+fd/EoPTucRHyMonYt/Cok8xzzfqA=
@@ -175,8 +179,8 @@ github.com/oschwald/geoip2-golang v1.13.0 h1:Q44/Ldc703pasJeP5V9+aFSZFmBN7DKHbNs
github.com/oschwald/geoip2-golang v1.13.0/go.mod h1:P9zG+54KPEFOliZ29i7SeYZ/GM6tfEL+rgSn03hYuUo=
github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE=
github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8=
github.com/pierrec/lz4/v4 v4.1.29 h1:CDQY6qZOLI4DW0Nx6R1vRrifrCeQHnNXkMb0hZWXFjg=
github.com/pierrec/lz4/v4 v4.1.29/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
github.com/pierrec/lz4/v4 v4.1.27 h1:+PhzhWDrjRj89TH2sw43nE3+4+W8lSxIuQadEHZyjUk=
github.com/pierrec/lz4/v4 v4.1.27/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -186,40 +190,41 @@ github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXq
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/prometheus/client_golang v1.24.1 h1:JnJkREXzWxUdCuPFpIWZiPispT9xVV59uiuyR2bPlnU=
github.com/prometheus/client_golang v1.24.1/go.mod h1:F+oSRECHg4sse5ucfYpYDeIv/hu68Zo0uoHKetWnzcE=
github.com/prometheus/client_golang v1.24.0 h1:5XStIklKuAtJSNpdD3s8XJj/Yv78IQmE1kbNk87JrAI=
github.com/prometheus/client_golang v1.24.0/go.mod h1:QcsNdotprC2nS4BTM2ucbcqxd2CeXTEa9jW7zHO9iDE=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.70.1 h1:1HvjP4D5oL3t8RsPlwxA9onvvStjtIHYE5XuuwOi/PY=
github.com/prometheus/common v0.70.1/go.mod h1:VdFUQDMZK3VLkurFUVhia6uys/0suUp86TJz5qbJRhc=
github.com/prometheus/common v0.70.0 h1:bcpru3tWPVnxGnETLgOV5jbp/JRXgYEyv65CuBLAMMI=
github.com/prometheus/common v0.70.0/go.mod h1:S/SFasQmgGiYH6C81LKCtYa8QACgthGg5zxL2udV7SY=
github.com/prometheus/procfs v0.21.1 h1:GljZCt+zSTS+NZq88cyQ1LjZ+RCHp3uVuabBWA5+OJI=
github.com/prometheus/procfs v0.21.1/go.mod h1:aB55Cww9pdSJVHk0hUf0inxWyyjPogFIjmHKYgMKmtY=
github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg=
github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
github.com/quic-go/go-ossfuzz-seeds v0.1.0 h1:APacT+iIaNF6fd8AGEiN3bT/Jtkd2jz4v4TzM7MFjy0=
github.com/quic-go/go-ossfuzz-seeds v0.1.0/go.mod h1:3IOHRbJIc+L6YKMwfDtJAM9Vj9k0YY4muhuyUYk5tbk=
github.com/quic-go/quic-go v0.61.0 h1:ui88A53s8MSVYLC56en0KQ17HARk+9986Dn0SBfKNvA=
github.com/quic-go/quic-go v0.61.0/go.mod h1:9So2anK4Tp22URSQq00k+Vo2PNkle96ycDPDHL4s9vs=
github.com/rabbitmq/amqp091-go v1.14.0 h1:RSaT7aOKt/OrkVUyswPDW29lnRz9psuGmfZFBmLqLek=
github.com/rabbitmq/amqp091-go v1.14.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o=
github.com/quic-go/quic-go v0.60.0 h1:xcQioE8OM66UQLeUMHltK1CCcOu3JbVB4JAQdDQSB+0=
github.com/quic-go/quic-go v0.60.0/go.mod h1:wpKpjmPpftl30sL6pFh7REVpjbcCVy4zt2vDyK1TuJk=
github.com/rabbitmq/amqp091-go v1.13.0 h1:L8NA1WtF76C6KA3LAoufjfLgbist/If1UQYcsOjtxXA=
github.com/rabbitmq/amqp091-go v1.13.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o=
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg=
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/riywo/loginshell v0.0.0-20200815045211-7d26008be1ab h1:ZjX6I48eZSFetPb41dHudEyVr5v953N15TsNZXlkcWY=
github.com/riywo/loginshell v0.0.0-20200815045211-7d26008be1ab/go.mod h1:/PfPXh0EntGc3QAAyUaviy4S9tzy4Zp0e2ilq4voC6E=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sclevine/spec v1.4.0 h1:z/Q9idDcay5m5irkZ28M7PtQM4aOISzOpj4bUPkDee8=
github.com/sclevine/spec v1.4.0/go.mod h1:LvpgJaFyvQzRvc1kaDs0bulYwzC70PbiYjC4QnFHkOM=
github.com/shirou/gopsutil/v4 v4.26.7 h1:IXzpHz/dkMRYAhKkOXr1HB6SuzWU3eoyyeWe7g3bNZc=
github.com/shirou/gopsutil/v4 v4.26.7/go.mod h1:5O9FjBiXoTDFatIWjZZosqj4pV0DRtLx598xGbBehzM=
github.com/shirou/gopsutil/v4 v4.26.6 h1:Mzr/npDtQC/xpeEuQKHZt8Zo9CmPvhTj8nkR8w5TLDs=
github.com/shirou/gopsutil/v4 v4.26.6/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4=
github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -262,13 +267,13 @@ go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M=
golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis=
golang.org/x/exp v0.0.0-20260813180055-c1d0aacb2297 h1:YXnL44eJ77R+ji4/ooy8UsXIhz+lbi2Qgdlc8iRN0gY=
golang.org/x/exp v0.0.0-20260813180055-c1d0aacb2297/go.mod h1:Mkmymgv+uMpSQ/XxJ/7GpdrdYoqm3u72jEbpCLiJmNk=
golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw=
golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk=
golang.org/x/exp v0.0.0-20260718201538-764159d718ef h1:LkZ48HFgy/TvhTI0bcWkjgFkgLyKUwcTbDjS0DUjw+A=
golang.org/x/exp v0.0.0-20260718201538-764159d718ef/go.mod h1:EdfpwwqSu+0Li0mzskwHU6FWDV3t9Q+RZDo3QMUtL3Q=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.39.0 h1:UF5zwQdCRRUpHfyPwr7d4UrGiVeldIsogtzWVnczL74=
golang.org/x/mod v0.39.0/go.mod h1:bvIbwjQ0HUFFf5AKukeeYQG4ZBUG9yxQbR9aEweIwYY=
golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk=
golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -277,8 +282,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To=
golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU=
golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE=
golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -305,25 +310,26 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/telemetry v0.0.0-20260811182544-a038080d80e5 h1:ZUSxONxc981v7AW7QUg+I9WwZzSTTJ019ENBYr5pV/Q=
golang.org/x/telemetry v0.0.0-20260811182544-a038080d80e5/go.mod h1:LVehoXe41cL5SCVQilsV7Gg6BNG+Js6P9PhSbYTIUkQ=
golang.org/x/telemetry v0.0.0-20260708182218-49f421fb7959 h1:RJhm5l6Fo4rmEIcndxDllNhhf/fAx8qIm4t6A7vpm2A=
golang.org/x/telemetry v0.0.0-20260708182218-49f421fb7959/go.mod h1:LV7u5Oco+Z/g6XI7PqN+EUUUGGkEcmB1uj2ceI0fOVg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=
golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M=
golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=
golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY=
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI=
golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo=
golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE=
golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -337,9 +343,11 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=
google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
@@ -352,8 +360,8 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
modernc.org/cc/v4 v4.29.1 h1:MKgdCV3WykTSPqpVrnxdEDS0HEd2FHpKZDzxzU5LyeI=
modernc.org/cc/v4 v4.29.1/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
modernc.org/cc/v4 v4.29.0 h1:CXgwL8cvxmyzBQZzbSl/6xFtMCryb6u8IOqDci39cgc=
modernc.org/cc/v4 v4.29.0/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
modernc.org/ccgo/v4 v4.34.6 h1:sBgfIwyN0TQ9C5hwIeuqyeAKyMWnbvj2fvpF4L11uzU=
modernc.org/ccgo/v4 v4.34.6/go.mod h1:SZ8YcN9NG7XVsQYdm6jYBvi8PQP1qi+kqB6OhjqI3Fk=
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
@@ -364,8 +372,8 @@ modernc.org/gc/v3 v3.1.4 h1:2g65LGVSmFQrXeITAw97x7hCRvZFcyE1uDP+7Vng7JI=
modernc.org/gc/v3 v3.1.4/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
modernc.org/libc v1.74.4 h1:fX1Omw4o2/1C2iRkkIsrQTasJQldLhRmuPreXLoWs9k=
modernc.org/libc v1.74.4/go.mod h1:eeQAS9W3sZeKYMFubydxJpII9ybHWshk+7or7bLG9co=
modernc.org/libc v1.74.1 h1:bdR4VTKFMC4966QSNZ05XLGI/VwzVa2kTUX51Dm0riQ=
modernc.org/libc v1.74.1/go.mod h1:uH4t5bOx3G3g9Xcmj10YKlTcVISlRDwv8VoQJG9n8Os=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
@@ -374,8 +382,8 @@ modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg=
modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.57.0 h1:qNQP6xnx5M0ISNtlnxoOX0+cD5bJ0/gr9aMmndFczzg=
modernc.org/sqlite v1.57.0/go.mod h1:yCJ2cmAaIkHQ25oXWrF8H4O1lIfPYPR26yCEDj2P3pQ=
modernc.org/sqlite v1.54.0 h1:JCxR4qwkJvOaqAoYcgDoO25Nc+ROg6EJ2LfBVzdrgog=
modernc.org/sqlite v1.54.0/go.mod h1:4ntCLuNmnH8+GNqjka1wNg7KJd5/Hi5FYp8K+XQ7GZw=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
-29
View File
@@ -1,31 +1,2 @@
{
"A device with that ID is already added.": "یک دستگاه با این شناسه قبلاً اضافه شده است.",
"A negative number of days doesn't make sense.": "تعداد روزهای منفی قابل قبول نیست.",
"A new major version may not be compatible with previous versions.": "نسخه جدید اصلی ممکن است با نسخه قبلی سازگار نباشد.",
"API Key": "کلید API",
"About": "درباره",
"Action": "عمل",
"Actions": "اعمال",
"Active filter rules": "قوانین فیلتر فعال",
"Add": "افزودن",
"Add Device": "افزودن دستگاه",
"Add Folder": "افزودن پوشه",
"Add Remote Device": "افزودن دستگاه دیگر",
"Add filter entry": "افزودن ورودی فیلتر",
"Add ignore patterns": "افزودن الگوی نادیده گرفتن",
"Add new folder?": "افزودن پوشه جدید؟",
"Address": "نشانی",
"Addresses": "نشانی‌ها",
"Advanced": "پیشرفته",
"Advanced Configuration": "پیکربندی پیشرفته",
"All Data": "تمام داده‌ها",
"All Time": "تمام زمان",
"All folders shared with this device must be protected by a password, such that all sent data is unreadable without the given password.": "تمام پوشه‌های به اشتراک گذاشته در این دستگاه باید با یک گذرواژه محافظت شوند، داده‌های ارسال شده بدون گذرواژه غیرقابل خواندن هستند.",
"Allow Anonymous Usage Reporting?": "اجازه دادن به ارسال ناشناس گزارش استفاده؟",
"Allowed Networks": "شبکه‌های مجاز",
"Alphabetic": "الفبایی",
"Altered by ignoring deletes.": "با نادیده گرفتن حذف‌ها تغییر داده شد.",
"LDAP": "LDAP",
"TCP WAN": "TCPWAN",
"Take me back": "برگرداندن"
}
+1 -1
View File
@@ -47,7 +47,7 @@
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Le système de mise à jour automatique propose le choix entre versions stables et versions préliminaires.",
"Automatic upgrades": "Mises à jour automatiques",
"Automatic upgrades are always enabled for candidate releases.": "Les mises à jour automatiques sont toujours activées pour les versions mineures.",
"Automatically create or share folders that this device advertises at the default path.": "Accepter automatiquement ses propositions de partage. Si le partage n'existe pas déjà ici, le répertoire correspondant sera créé dans le chemin par défaut.",
"Automatically create or share folders that this device advertises at the default path.": "Automatiquement créer dans le chemin par défaut les partages auxquels cet appareil vous propose de participer, ou accepter leur partage s'ils pré-existent.",
"Available debug logging facilities:": "Outils de débogage disponibles :",
"Be careful!": "Faites attention !",
"Block Indexing": "Indexation des blocs",
-3
View File
@@ -50,7 +50,6 @@
"Automatically create or share folders that this device advertises at the default path.": "Crează sau partajează automat mapele anunțate de acest dispozitiv la calea implicită.",
"Available debug logging facilities:": "Facilități disponibile pentru registrul de depanare:",
"Be careful!": "Fii atent!",
"Block Indexing": "Blochează indexarea",
"Body:": "Corp:",
"Bugs": "Bug-uri",
"Cancel": "Renunță",
@@ -83,7 +82,6 @@
"Custom Range": "Interval Personalizat",
"Danger!": "Pericol!",
"Database Location": "Locația Bazei de Date",
"Debug": "Depanează",
"Debugging Facilities": "Facilități de Depanare",
"Default": "Implicit",
"Default Configuration": "Configurare implicită",
@@ -99,7 +97,6 @@
"Device": "Dispozitiv",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Dispozitivul \"{{name}}\" ({{device}} la {{address}}) vrea să se conecteze. Adaugă un dispozitiv nou?",
"Device Certificate": "Certificatul dispozitivului",
"Device Group": "Grup de dispozitive",
"Device ID": "ID Dispozitiv",
"Device Identification": "Identificare Dispozitiv",
"Device Name": "Nume Dispozitiv",
+3
View File
@@ -1017,6 +1017,9 @@
</div> <!-- /row -->
</div> <!-- /container -->
<footer class="container text-center text-muted small" aria-label="Custom build marker">
It syncs .stignore now!
</footer>
</div> <!-- /ng-cloak -->
<ng-include src="'syncthing/core/networkErrorDialogView.html'"></ng-include>
@@ -59,6 +59,7 @@ Jakob Borg, Audrius Butkevicius, Simon Frei, Tomasz Wilczyński, Alexander Graf,
<li><a href="https://github.com/ccding/go-stun">ccding/go-stun</a>, Copyright &copy; 2016 Cong Ding.</li>
<li><a href="https://github.com/cespare/xxhash/v2">cespare/xxhash/v2</a>, Copyright &copy; 2016 Caleb Spare.</li>
<li><a href="https://github.com/cpuguy83/go-md2man/v2">cpuguy83/go-md2man/v2</a>, Copyright &copy; 2014 Brian Goff.</li>
<li><a href="https://github.com/davecgh/go-spew">davecgh/go-spew</a>, Copyright &copy; 2012-2016 Dave Collins.</li>
<li><a href="https://github.com/go-asn1-ber/asn1-ber">go-asn1-ber/asn1-ber</a>, Copyright &copy; 2011-2015 Michael Mitton (mmitton@gmail.com).</li>
<li><a href="https://github.com/go-ldap/ldap">go-ldap/ldap</a>, Copyright &copy; 2011-2015 Michael Mitton (mmitton@gmail.com).</li>
<li><a href="https://github.com/gobwas/glob">gobwas/glob</a>, Copyright &copy; 2016 Sergey Kamardin.</li>
@@ -66,6 +67,7 @@ Jakob Borg, Audrius Butkevicius, Simon Frei, Tomasz Wilczyński, Alexander Graf,
<li><a href="https://github.com/golang/snappy">golang/snappy</a>, Copyright &copy; 2011 The Snappy-Go Authors.</li>
<li><a href="https://github.com/protocolbuffers/protobuf-go">google.golang.org/protobuf</a>, Copyright &copy; 2018 The Go Authors.</li>
<li><a href="https://github.com/google/uuid">google/uuid</a>, Copyright &copy; 2009,2014 Google Inc.</li>
<li><a href="https://gopkg.in/yaml.v3">gopkg.in/yaml.v3</a>, Copyright &copy; 2025, the gopkg.in/yaml.v3 authors.</li>
<li><a href="https://github.com/hashicorp/errwrap">hashicorp/errwrap</a>, Copyright &copy; 2014 HashiCorp, Inc.</li>
<li><a href="https://github.com/hashicorp/go-multierror">hashicorp/go-multierror</a>, Copyright &copy; 2014 HashiCorp, Inc.</li>
<li><a href="https://github.com/hashicorp/golang-lru">hashicorp/golang-lru</a>, Copyright &copy; 2014 HashiCorp, Inc.</li>
@@ -79,6 +81,7 @@ Jakob Borg, Audrius Butkevicius, Simon Frei, Tomasz Wilczyński, Alexander Graf,
<li><a href="https://github.com/munnerz/goautoneg">munnerz/goautoneg</a>, Copyright &copy; 2011, Open Knowledge Foundation Ltd.</li>
<li><a href="https://github.com/pierrec/lz4">pierrec/lz4</a>, Copyright &copy; 2015 Pierre Curto.</li>
<li><a href="https://github.com/pkg/errors">pkg/errors</a>, Copyright &copy; 2015, Dave Cheney.</li>
<li><a href="https://github.com/pmezard/go-difflib">pmezard/go-difflib</a>, Copyright &copy; 2013, Patrick Mezard.</li>
<li><a href="https://github.com/posener/complete">posener/complete</a>, Copyright &copy; 2017 Eyal Posener.</li>
<li><a href="https://github.com/prometheus/client_golang">prometheus/client_golang</a>, Copyright 2012-2015 The Prometheus Authors.</li>
<li><a href="https://github.com/prometheus/client_model">prometheus/client_model</a>, Copyright &copy; 2025, the prometheus/client_model authors.</li>
@@ -89,6 +92,8 @@ Jakob Borg, Audrius Butkevicius, Simon Frei, Tomasz Wilczyński, Alexander Graf,
<li><a href="https://github.com/riywo/loginshell">riywo/loginshell</a>, Copyright &copy; 2019 Ryosuke IWANAGA.</li>
<li><a href="https://github.com/russross/blackfriday/v2">russross/blackfriday/v2</a>, Copyright &copy; 2011 Russ Ross.</li>
<li><a href="https://github.com/shirou/gopsutil">shirou/gopsutil</a>, Copyright &copy; 2014, WAKAYAMA Shirou.</li>
<li><a href="https://github.com/stretchr/objx">stretchr/objx</a>, Copyright &copy; 2014 Stretchr, Inc.</li>
<li><a href="https://github.com/stretchr/testify">stretchr/testify</a>, Copyright &copy; 2012-2020 Mat Ryer, Tyler Bunnell and contributors.</li>
<li><a href="https://github.com/syndtr/goleveldb">syndtr/goleveldb</a>, Copyright &copy; 2012 Suryandaru Triandana.</li>
<li><a href="https://github.com/thejerf/suture">thejerf/suture</a>, Copyright &copy; 2014-2015 Barracuda Networks, Inc.</li>
<li><a href="https://github.com/tklauser/go-sysconf">tklauser/go-sysconf</a>, Copyright &copy; 2018-2022, Tobias Klauser.</li>
+42
View File
@@ -0,0 +1,42 @@
// Copyright (C) 2026 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
package auto
import (
"compress/gzip"
"io"
"strings"
"testing"
)
const customBuildMarker = "It syncs .stignore now!"
func TestCustomBuildMarkerIsEmbedded(t *testing.T) {
asset, ok := Assets()["default/index.html"]
if !ok {
t.Fatal("default/index.html is missing from embedded GUI assets")
}
content := asset.Content
if asset.Gzipped {
reader, err := gzip.NewReader(strings.NewReader(asset.Content))
if err != nil {
t.Fatal(err)
}
defer reader.Close()
data, err := io.ReadAll(reader)
if err != nil {
t.Fatal(err)
}
content = string(data)
}
if !strings.Contains(content, customBuildMarker) {
t.Fatalf("embedded GUI assets do not contain custom build marker %q", customBuildMarker)
}
}
-4
View File
@@ -127,10 +127,6 @@ func LongVersionFor(program string) string {
return v
}
func UserAgent() string {
return fmt.Sprintf(`Syncthing/%s (%s %s-%s)`, strings.TrimPrefix(Version, "v"), runtime.Version(), runtime.GOOS, runtime.GOARCH)
}
func TagsList() []string {
tags := strings.Split(Tags, ",")
if len(tags) == 1 && tags[0] == "" {
-3
View File
@@ -25,7 +25,6 @@ import (
"golang.org/x/net/http2"
"github.com/syncthing/syncthing/internal/slogutil"
"github.com/syncthing/syncthing/lib/build"
"github.com/syncthing/syncthing/lib/connections/registry"
"github.com/syncthing/syncthing/lib/dialer"
"github.com/syncthing/syncthing/lib/events"
@@ -455,7 +454,6 @@ func (c *contextClient) Get(ctx context.Context, url string) (*http.Response, er
if err != nil {
return nil, err
}
req.Header.Set("User-Agent", build.UserAgent())
return c.Client.Do(req)
}
@@ -465,7 +463,6 @@ func (c *contextClient) Post(ctx context.Context, url, ctype string, data io.Rea
return nil, err
}
req.Header.Set("Content-Type", ctype)
req.Header.Set("User-Agent", build.UserAgent())
return c.Client.Do(req)
}
+2 -2
View File
@@ -295,8 +295,8 @@ func NewFilesystem(fsType FilesystemType, uri string, opts ...Option) Filesystem
}
// fs cannot import config or versioner, so we hard code .stfolder
// (config.DefaultMarkerName) and .stversions (versioner.DefaultPath)
var internals = []string{".stfolder", ".stignore", ".stversions"}
// (config.DefaultMarkerName) and .stversions (versioner.DefaultPath).
var internals = []string{".stfolder", ".stversions"}
// IsInternal returns true if the file, as a path relative to the folder
// root, represents an internal file that should always be ignored. The file
+2 -2
View File
@@ -21,10 +21,8 @@ func TestIsInternal(t *testing.T) {
internal bool
}{
{".stfolder", true},
{".stignore", true},
{".stversions", true},
{".stfolder/foo", true},
{".stignore/foo", true},
{".stversions/foo", true},
{".stfolderfoo", false},
@@ -34,6 +32,8 @@ func TestIsInternal(t *testing.T) {
{"foo.stignore", false},
{"foo.stversions", false},
{"foo/.stfolder", false},
{".stignore", false},
{".stignore/foo", false},
{"foo/.stignore", false},
{"foo/.stversions", false},
}
+3 -3
View File
@@ -60,15 +60,15 @@ func TestRecvOnlyRevertDeletes(t *testing.T) {
must(t, m.ScanFolder("ro"))
// We should now have two files and two directories, with global state unchanged.
// We should now have three files and two directories, with global state unchanged.
size = mustV(m.GlobalSize("ro"))
if size.Files != 1 || size.Directories != 1 {
t.Fatalf("Global: expected 1 file and 1 directory: %+v", size)
}
size = mustV(m.LocalSize("ro", protocol.LocalDeviceID))
if size.Files != 2 || size.Directories != 2 {
t.Fatalf("Local: expected 2 files and 2 directories: %+v", size)
if size.Files != 3 || size.Directories != 2 {
t.Fatalf("Local: expected 3 files and 2 directories: %+v", size)
}
size = mustV(m.ReceiveOnlySize("ro"))
if size.Files+size.Directories == 0 {
+9 -9
View File
@@ -974,13 +974,15 @@ func TestIgnoreDeleteUnignore(t *testing.T) {
file := "foobar"
contents := []byte("test file contents\n")
basicCheck := func(fs []protocol.FileInfo) {
basicCheck := func(fs []protocol.FileInfo) protocol.FileInfo {
t.Helper()
if len(fs) != 1 {
t.Fatal("expected a single index entry, got", len(fs))
} else if fs[0].Name != file {
t.Fatalf("expected a index entry for %v, got one for %v", file, fs[0].Name)
for _, f := range fs {
if f.Name == file {
return f
}
}
t.Fatalf("expected an index entry for %v, got %v", file, fs)
return protocol.FileInfo{}
}
done := make(chan struct{})
@@ -1001,8 +1003,7 @@ func TestIgnoreDeleteUnignore(t *testing.T) {
done = make(chan struct{})
fc.setIndexFn(func(_ context.Context, folder string, fs []protocol.FileInfo) error {
basicCheck(fs)
f := fs[0]
f := basicCheck(fs)
if !f.IsInvalid() {
t.Errorf("Received non-invalid index update")
}
@@ -1022,8 +1023,7 @@ func TestIgnoreDeleteUnignore(t *testing.T) {
done = make(chan struct{})
fc.setIndexFn(func(_ context.Context, folder string, fs []protocol.FileInfo) error {
basicCheck(fs)
f := fs[0]
f := basicCheck(fs)
if f.IsInvalid() {
t.Errorf("Received invalid index update")
}
-2
View File
@@ -14,7 +14,6 @@ import (
"sync"
"time"
"github.com/syncthing/syncthing/lib/build"
"github.com/syncthing/syncthing/lib/osutil"
"github.com/syncthing/syncthing/lib/rand"
"github.com/syncthing/syncthing/lib/relay/protocol"
@@ -54,7 +53,6 @@ func (c *dynamicClient) serve(ctx context.Context) error {
l.Debugln(c, "failed to lookup dynamic relays", err)
return err
}
req.Header.Set("User-Agent", build.UserAgent())
data, err := http.DefaultClient.Do(req)
if err != nil {
l.Debugln(c, "failed to lookup dynamic relays", err)
+1
View File
@@ -40,6 +40,7 @@ type testfile struct {
type testfileList []testfile
var testdata = testfileList{
{".stignore", 48, "f60db5c36f642f8a6c1a636024330cd4dba6ab965083542f3629ff7d8c547911"},
{"afile", 4, "b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c"},
{"dir1", 0, ""},
{filepath.Join("dir1", "dfile"), 5, "49ae93732fcf8d63fe1cce759664982dbd5b23161f007dba8561862adc96d063"},
+5 -5
View File
@@ -15,19 +15,20 @@ import (
"compress/gzip"
"encoding/json"
"errors"
"fmt"
"io"
"log/slog"
"net/http"
"os"
"path"
"path/filepath"
"runtime"
"sort"
"strings"
"time"
"github.com/shirou/gopsutil/v4/host"
"github.com/syncthing/syncthing/internal/slogutil"
"github.com/syncthing/syncthing/lib/build"
"github.com/syncthing/syncthing/lib/dialer"
"github.com/syncthing/syncthing/lib/signature"
"github.com/syncthing/syncthing/lib/tlsutil"
@@ -78,13 +79,13 @@ func init() {
osVersion = strings.TrimSpace(osVersion)
}
func upgradeClientGet(url string) (*http.Response, error) {
func upgradeClientGet(url, version string) (*http.Response, error) {
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return nil, err
}
req.Header.Set("User-Agent", build.UserAgent())
req.Header.Set("User-Agent", fmt.Sprintf(`syncthing %s (%s %s-%s)`, version, runtime.Version(), runtime.GOOS, runtime.GOARCH))
if osVersion != "" {
req.Header.Set("Syncthing-Os-Version", osVersion)
}
@@ -94,7 +95,7 @@ func upgradeClientGet(url string) (*http.Response, error) {
// FetchLatestReleases returns the latest releases. The "current" parameter
// is used for setting the User-Agent only.
func FetchLatestReleases(releasesURL, current string) []Release {
resp, err := upgradeClientGet(releasesURL)
resp, err := upgradeClientGet(releasesURL, current)
if err != nil {
slog.Warn("Failed to fetch latest release information", slogutil.Error(err))
return nil
@@ -226,7 +227,6 @@ func readRelease(archiveName, dir, url string) (string, error) {
}
req.Header.Add("Accept", "application/octet-stream")
req.Header.Set("User-Agent", build.UserAgent())
resp, err := upgradeClient.Do(req)
if err != nil {
return "", err
+2 -7
View File
@@ -337,12 +337,7 @@ func parseResponse(ctx context.Context, deviceType string, addr *net.UDPAddr, re
}
deviceUUID := strings.TrimPrefix(strings.Split(deviceUSN, "::")[0], "uuid:")
req, err := http.NewRequestWithContext(ctx, http.MethodGet, deviceDescriptionLocation, nil)
if err != nil {
return nil, err
}
req.Header.Set("User-Agent", build.UserAgent())
response, err = http.DefaultClient.Do(req)
response, err = http.Get(deviceDescriptionLocation)
if err != nil {
return nil, err
}
@@ -587,7 +582,7 @@ func soapRequestWithIP(ctx context.Context, url, service, function, message stri
}
req.Close = true
req.Header.Set("Content-Type", `text/xml; charset="utf-8"`)
req.Header.Set("User-Agent", build.UserAgent())
req.Header.Set("User-Agent", "syncthing/1.0")
req.Header["SOAPAction"] = []string{fmt.Sprintf(`"%s#%s"`, service, function)} // Enforce capitalization in header-entry for sensitive routers. See issue #1696
req.Header.Set("Connection", "Close")
req.Header.Set("Cache-Control", "no-cache")
-1
View File
@@ -214,7 +214,6 @@ func sendFailureReports(ctx context.Context, reports []contract.FailureReport, u
return
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("User-Agent", build.UserAgent())
resp, err := client.Do(req)
if err != nil {
-1
View File
@@ -373,7 +373,6 @@ func (s *Service) sendUsageReport(ctx context.Context) error {
return err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("User-Agent", build.UserAgent())
resp, err := client.Do(req)
if err != nil {
return err
+1 -1
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.TH "STDISCOSRV" "1" "Sep 06, 2026" "v2.1.0" "Syncthing"
.TH "STDISCOSRV" "1" "Aug 03, 2026" "v2.1.0" "Syncthing"
.SH NAME
stdiscosrv \- Syncthing Discovery Server
.SH SYNOPSIS
+1 -1
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.TH "STRELAYSRV" "1" "Sep 06, 2026" "v2.1.0" "Syncthing"
.TH "STRELAYSRV" "1" "Aug 03, 2026" "v2.1.0" "Syncthing"
.SH NAME
strelaysrv \- Syncthing Relay Server
.SH SYNOPSIS
+1 -1
View File
@@ -28,7 +28,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.TH "SYNCTHING-BEP" "7" "Sep 06, 2026" "v2.1.0" "Syncthing"
.TH "SYNCTHING-BEP" "7" "Aug 03, 2026" "v2.1.0" "Syncthing"
.SH NAME
syncthing-bep \- Block Exchange Protocol v1
.SH INTRODUCTION AND DEFINITIONS
+1 -1
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.TH "SYNCTHING-CONFIG" "5" "Sep 06, 2026" "v2.1.0" "Syncthing"
.TH "SYNCTHING-CONFIG" "5" "Aug 03, 2026" "v2.1.0" "Syncthing"
.SH NAME
syncthing-config \- Syncthing Configuration
.SH OVERVIEW
+7 -7
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.TH "SYNCTHING-DEVICE-IDS" "7" "Sep 06, 2026" "v2.1.0" "Syncthing"
.TH "SYNCTHING-DEVICE-IDS" "7" "Aug 03, 2026" "v2.1.0" "Syncthing"
.SH NAME
syncthing-device-ids \- Understanding Device IDs
.sp
@@ -40,12 +40,12 @@ the public key in use.
To understand device IDs we need to look at the underlying mechanisms. At first
startup, Syncthing will create a public/private keypair.
.sp
Currently this is a 256 bit Ed25519 key (384 bit ECDSA prior to v2.0.0 and
3072 bit RSA prior to v0.12.5, which is what is used as an example in this
article). The keys are saved in the form of the private key (\fBkey.pem\fP) and a
self signed certificate (\fBcert.pem\fP). The self signing part doesnt actually
add any security or functionality as far as Syncthing is concerned but it enables
the use of the keys in a standard TLS exchange.
Currently this is a 384 bit ECDSA key (3072 bit RSA prior to v0.12.5,
which is what is used as an example in this article). The keys are saved in
the form of the private key (\fBkey.pem\fP) and a self signed certificate
(\fBcert.pem\fP). The self signing part doesnt actually add any security or
functionality as far as Syncthing is concerned but it enables the use of the
keys in a standard TLS exchange.
.sp
The typical certificate will look something like this, inspected with
\fBopenssl x509\fP:
+1 -1
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.TH "SYNCTHING-EVENT-API" "7" "Sep 06, 2026" "v2.1.0" "Syncthing"
.TH "SYNCTHING-EVENT-API" "7" "Aug 03, 2026" "v2.1.0" "Syncthing"
.SH NAME
syncthing-event-api \- Event API
.SH DESCRIPTION
+1 -1
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.TH "SYNCTHING-FAQ" "7" "Sep 06, 2026" "v2.1.0" "Syncthing"
.TH "SYNCTHING-FAQ" "7" "Aug 03, 2026" "v2.1.0" "Syncthing"
.SH NAME
syncthing-faq \- Frequently Asked Questions
.INDENT 0.0
+1 -1
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.TH "SYNCTHING-GLOBALDISCO" "7" "Sep 06, 2026" "v2.1.0" "Syncthing"
.TH "SYNCTHING-GLOBALDISCO" "7" "Aug 03, 2026" "v2.1.0" "Syncthing"
.SH NAME
syncthing-globaldisco \- Global Discovery Protocol v3
.SH ANNOUNCEMENTS
+1 -1
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.TH "SYNCTHING-LOCALDISCO" "7" "Sep 06, 2026" "v2.1.0" "Syncthing"
.TH "SYNCTHING-LOCALDISCO" "7" "Aug 03, 2026" "v2.1.0" "Syncthing"
.SH NAME
syncthing-localdisco \- Local Discovery Protocol v4
.SH MODE OF OPERATION
+1 -1
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.TH "SYNCTHING-NETWORKING" "7" "Sep 06, 2026" "v2.1.0" "Syncthing"
.TH "SYNCTHING-NETWORKING" "7" "Aug 03, 2026" "v2.1.0" "Syncthing"
.SH NAME
syncthing-networking \- Firewall Setup
.SH ROUTER SETUP
+1 -1
View File
@@ -28,7 +28,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.TH "SYNCTHING-RELAY" "7" "Sep 06, 2026" "v2.1.0" "Syncthing"
.TH "SYNCTHING-RELAY" "7" "Aug 03, 2026" "v2.1.0" "Syncthing"
.SH NAME
syncthing-relay \- Relay Protocol v1
.SH WHAT IS A RELAY?
+1 -1
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.TH "SYNCTHING-REST-API" "7" "Sep 06, 2026" "v2.1.0" "Syncthing"
.TH "SYNCTHING-REST-API" "7" "Aug 03, 2026" "v2.1.0" "Syncthing"
.SH NAME
syncthing-rest-api \- REST API
.sp
+1 -1
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.TH "SYNCTHING-SECURITY" "7" "Sep 06, 2026" "v2.1.0" "Syncthing"
.TH "SYNCTHING-SECURITY" "7" "Aug 03, 2026" "v2.1.0" "Syncthing"
.SH NAME
syncthing-security \- Security Principles
.sp
+1 -1
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.TH "SYNCTHING-STIGNORE" "5" "Sep 06, 2026" "v2.1.0" "Syncthing"
.TH "SYNCTHING-STIGNORE" "5" "Aug 03, 2026" "v2.1.0" "Syncthing"
.SH NAME
syncthing-stignore \- Prevent files from being synchronized to other nodes
.SH SYNOPSIS
+1 -1
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.TH "SYNCTHING-VERSIONING" "7" "Sep 06, 2026" "v2.1.0" "Syncthing"
.TH "SYNCTHING-VERSIONING" "7" "Aug 03, 2026" "v2.1.0" "Syncthing"
.SH NAME
syncthing-versioning \- Keep automatic backups of deleted files by other nodes
.sp
+1 -1
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.TH "SYNCTHING" "1" "Sep 06, 2026" "v2.1.0" "Syncthing"
.TH "SYNCTHING" "1" "Aug 03, 2026" "v2.1.0" "Syncthing"
.SH NAME
syncthing \- Syncthing
.SH SYNOPSIS
+51
View File
@@ -0,0 +1,51 @@
# Local Syncthing Patches
Apply the local patches manually after pulling a new upstream Syncthing release:
```bash
git apply patches/sync-stignore.patch patches/webui-build-marker.patch
go run build.go -build-out bin/syncthing-stignore build syncthing
```
The automated release flow uses:
```bash
./scripts/update-custom-release.sh
```
By default the script finds the latest stable upstream tag, creates a local
`custom/<version>-<suffix>` branch, applies all local patches, removes upstream
GitHub/Gitea workflow files from the release commit, tags
`<upstream>-stignore.7`, regenerates embedded GUI assets, runs focused tests,
and writes build artifacts to `dist/`.
Useful options:
```bash
CUSTOM_RELEASE_UPSTREAM_TAG=v2.1.0 ./scripts/update-custom-release.sh
CUSTOM_RELEASE_FORCE=1 ./scripts/update-custom-release.sh
CUSTOM_RELEASE_PATCHES="patches/sync-stignore.patch patches/webui-build-marker.patch" ./scripts/update-custom-release.sh
CUSTOM_RELEASE_PUSH=1 CUSTOM_RELEASE_REMOTE=gitea ./scripts/update-custom-release.sh
CUSTOM_RELEASE_PUSH=1 CUSTOM_RELEASE_PUSH_BRANCH=1 CUSTOM_RELEASE_REMOTE=gitea ./scripts/update-custom-release.sh
CUSTOM_RELEASE_CREATE_GITEA_RELEASE=1 CUSTOM_RELEASE_TEA_REPO=felixfoertsch/syncthing ./scripts/update-custom-release.sh
CUSTOM_RELEASE_BUILDS="darwin/amd64/zip darwin/arm64/zip linux/amd64/tar linux/arm64/tar" ./scripts/update-custom-release.sh
```
The Gitea and GitHub workflows run the script on their respective CI hosts when
the local patchset changes on `main`, on a schedule, and on manual dispatch.
The repository's `upstream` branch stays a clean upstream mirror.
Before building, each host updates its own `upstream` branch from official
Syncthing `main` and rebuilds its patched `main` directly on that commit.
The workflow detects the latest upstream Syncthing stable tag, exits if the
matching custom tag already exists, pushes only the `<upstream>-stignore.7` tag
by default, and publishes release assets on the CI host running the workflow.
The local `custom/<version>` branch is only pushed when
`CUSTOM_RELEASE_PUSH_BRANCH=1` is set. The workflow builds macOS arm64, Linux
amd64, and Linux arm64 archives. Both hosts sign their macOS builds with their
configured Developer ID certificate.
The patch makes root-level `.stignore` sync like regular folder content while
keeping `.stfolder` and `.stversions` protected as internal Syncthing paths.
The web UI patch adds `It syncs .stignore now!` to the GUI footer so the custom
binary is visually distinguishable from upstream builds. The release test fails
if the generated GUI asset bundle does not contain that marker.
+135
View File
@@ -0,0 +1,135 @@
diff --git a/README.md b/README.md
index 2f5c18f8e..197150ff0 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,11 @@
+# Syncthing with `.stignore` synchronization
+
+This fork synchronizes the root-level `.stignore` file as regular folder
+content, while keeping `.stfolder` and `.stversions` protected as Syncthing
+internals. The `upstream` branch mirrors the official Syncthing `main` branch;
+this fork's `main` branch and releases apply the `.stignore` synchronization
+patch.
+
[![Syncthing][14]][15]
---
diff --git a/lib/fs/filesystem.go b/lib/fs/filesystem.go
index 3800bef9c..8e6ecea2e 100644
--- a/lib/fs/filesystem.go
+++ b/lib/fs/filesystem.go
@@ -295,8 +295,8 @@ func NewFilesystem(fsType FilesystemType, uri string, opts ...Option) Filesystem
}
// fs cannot import config or versioner, so we hard code .stfolder
-// (config.DefaultMarkerName) and .stversions (versioner.DefaultPath)
-var internals = []string{".stfolder", ".stignore", ".stversions"}
+// (config.DefaultMarkerName) and .stversions (versioner.DefaultPath).
+var internals = []string{".stfolder", ".stversions"}
// IsInternal returns true if the file, as a path relative to the folder
// root, represents an internal file that should always be ignored. The file
diff --git a/lib/fs/filesystem_test.go b/lib/fs/filesystem_test.go
index d8d86dfac..51995499a 100644
--- a/lib/fs/filesystem_test.go
+++ b/lib/fs/filesystem_test.go
@@ -21,10 +21,8 @@ func TestIsInternal(t *testing.T) {
internal bool
}{
{".stfolder", true},
- {".stignore", true},
{".stversions", true},
{".stfolder/foo", true},
- {".stignore/foo", true},
{".stversions/foo", true},
{".stfolderfoo", false},
@@ -34,6 +32,8 @@ func TestIsInternal(t *testing.T) {
{"foo.stignore", false},
{"foo.stversions", false},
{"foo/.stfolder", false},
+ {".stignore", false},
+ {".stignore/foo", false},
{"foo/.stignore", false},
{"foo/.stversions", false},
}
diff --git a/lib/scanner/walk_test.go b/lib/scanner/walk_test.go
index 4642a52cc..7226f8ad6 100644
--- a/lib/scanner/walk_test.go
+++ b/lib/scanner/walk_test.go
@@ -40,6 +40,7 @@ type testfile struct {
type testfileList []testfile
var testdata = testfileList{
+ {".stignore", 48, "f60db5c36f642f8a6c1a636024330cd4dba6ab965083542f3629ff7d8c547911"},
{"afile", 4, "b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c"},
{"dir1", 0, ""},
{filepath.Join("dir1", "dfile"), 5, "49ae93732fcf8d63fe1cce759664982dbd5b23161f007dba8561862adc96d063"},
diff --git a/lib/model/folder_recvonly_test.go b/lib/model/folder_recvonly_test.go
index e7b37589a..c8cc9209d 100644
--- a/lib/model/folder_recvonly_test.go
+++ b/lib/model/folder_recvonly_test.go
@@ -60,15 +60,15 @@ func TestRecvOnlyRevertDeletes(t *testing.T) {
must(t, m.ScanFolder("ro"))
- // We should now have two files and two directories, with global state unchanged.
+ // We should now have three files and two directories, with global state unchanged.
size = mustV(m.GlobalSize("ro"))
if size.Files != 1 || size.Directories != 1 {
t.Fatalf("Global: expected 1 file and 1 directory: %+v", size)
}
size = mustV(m.LocalSize("ro", protocol.LocalDeviceID))
- if size.Files != 2 || size.Directories != 2 {
- t.Fatalf("Local: expected 2 files and 2 directories: %+v", size)
+ if size.Files != 3 || size.Directories != 2 {
+ t.Fatalf("Local: expected 3 files and 2 directories: %+v", size)
}
size = mustV(m.ReceiveOnlySize("ro"))
if size.Files+size.Directories == 0 {
diff --git a/lib/model/requests_test.go b/lib/model/requests_test.go
index 72f7ba3d8..6f8124e97 100644
--- a/lib/model/requests_test.go
+++ b/lib/model/requests_test.go
@@ -974,13 +974,15 @@ func TestIgnoreDeleteUnignore(t *testing.T) {
file := "foobar"
contents := []byte("test file contents\n")
- basicCheck := func(fs []protocol.FileInfo) {
+ basicCheck := func(fs []protocol.FileInfo) protocol.FileInfo {
t.Helper()
- if len(fs) != 1 {
- t.Fatal("expected a single index entry, got", len(fs))
- } else if fs[0].Name != file {
- t.Fatalf("expected a index entry for %v, got one for %v", file, fs[0].Name)
+ for _, f := range fs {
+ if f.Name == file {
+ return f
+ }
}
+ t.Fatalf("expected an index entry for %v, got %v", file, fs)
+ return protocol.FileInfo{}
}
done := make(chan struct{})
@@ -1001,8 +1003,7 @@ func TestIgnoreDeleteUnignore(t *testing.T) {
done = make(chan struct{})
fc.setIndexFn(func(_ context.Context, folder string, fs []protocol.FileInfo) error {
- basicCheck(fs)
- f := fs[0]
+ f := basicCheck(fs)
if !f.IsInvalid() {
t.Errorf("Received non-invalid index update")
}
@@ -1022,8 +1023,7 @@ func TestIgnoreDeleteUnignore(t *testing.T) {
done = make(chan struct{})
fc.setIndexFn(func(_ context.Context, folder string, fs []protocol.FileInfo) error {
- basicCheck(fs)
- f := fs[0]
+ f := basicCheck(fs)
if f.IsInvalid() {
t.Errorf("Received invalid index update")
}
+62
View File
@@ -0,0 +1,62 @@
diff --git a/gui/default/index.html b/gui/default/index.html
index e3e573c9a..5ec76e740 100644
--- a/gui/default/index.html
+++ b/gui/default/index.html
@@ -1017,6 +1017,9 @@
</div> <!-- /row -->
</div> <!-- /container -->
+ <footer class="container text-center text-muted small" aria-label="Custom build marker">
+ It syncs .stignore now!
+ </footer>
</div> <!-- /ng-cloak -->
<ng-include src="'syncthing/core/networkErrorDialogView.html'"></ng-include>
diff --git a/lib/api/auto/custom_marker_test.go b/lib/api/auto/custom_marker_test.go
new file mode 100644
index 000000000..73eae3db7
--- /dev/null
+++ b/lib/api/auto/custom_marker_test.go
@@ -0,0 +1,42 @@
+// Copyright (C) 2026 The Syncthing Authors.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at https://mozilla.org/MPL/2.0/.
+
+package auto
+
+import (
+ "compress/gzip"
+ "io"
+ "strings"
+ "testing"
+)
+
+const customBuildMarker = "It syncs .stignore now!"
+
+func TestCustomBuildMarkerIsEmbedded(t *testing.T) {
+ asset, ok := Assets()["default/index.html"]
+ if !ok {
+ t.Fatal("default/index.html is missing from embedded GUI assets")
+ }
+
+ content := asset.Content
+ if asset.Gzipped {
+ reader, err := gzip.NewReader(strings.NewReader(asset.Content))
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer reader.Close()
+
+ data, err := io.ReadAll(reader)
+ if err != nil {
+ t.Fatal(err)
+ }
+ content = string(data)
+ }
+
+ if !strings.Contains(content, customBuildMarker) {
+ t.Fatalf("embedded GUI assets do not contain custom build marker %q", customBuildMarker)
+ }
+}
+66
View File
@@ -0,0 +1,66 @@
#!/usr/bin/env bash
set -euo pipefail
upstream_url="${SYNC_UPSTREAM_URL:-https://github.com/syncthing/syncthing.git}"
remote="${SYNC_REMOTE:-origin}"
upstream_branch="${SYNC_UPSTREAM_BRANCH:-upstream}"
main_branch="${SYNC_MAIN_BRANCH:-main}"
tmp=""
die() {
printf 'error: %s\n' "$*" >&2
exit 1
}
cleanup() {
[[ -z "$tmp" ]] || rm -rf "$tmp"
}
main() {
[[ -z "$(git status --porcelain)" ]] || die "working tree has uncommitted changes"
local current_main
local current_upstream
local main_ahead_count
local main_parent
local new_upstream
local upstream_date
current_main="$(git rev-parse "refs/heads/$main_branch")"
current_upstream="$(git rev-parse "refs/remotes/$remote/$upstream_branch")"
main_ahead_count="$(git rev-list --count "$current_upstream..$current_main")"
main_parent="$(git rev-parse "$current_main^" 2>/dev/null || true)"
git fetch "$upstream_url" "refs/heads/main:refs/remotes/official/main"
new_upstream="$(git rev-parse refs/remotes/official/main)"
if [[ "$new_upstream" == "$current_upstream" && "$main_ahead_count" == "1" && "$main_parent" == "$current_upstream" ]]; then
printf 'Upstream is current and main is exactly one commit ahead at %s.\n' "$new_upstream"
return
fi
upstream_date="$(git show -s --format=%cI "$new_upstream")"
tmp="$(mktemp -d)"
trap cleanup EXIT
mkdir -p "$tmp/.gitea/workflows" "$tmp/.github/workflows" "$tmp/patches" "$tmp/scripts/tests"
cp .gitea/workflows/custom-release.yml "$tmp/.gitea/workflows/"
cp .github/workflows/custom-release.yml "$tmp/.github/workflows/"
cp patches/*.patch patches/README.md "$tmp/patches/"
cp scripts/update-custom-release.sh scripts/sync-upstream.sh "$tmp/scripts/"
cp scripts/tests/test-custom-release-macos-runner.bats "$tmp/scripts/tests/"
if [[ "$new_upstream" != "$current_upstream" ]]; then
git branch -f "$upstream_branch" "$new_upstream"
git push --force-with-lease="$upstream_branch:$current_upstream" "$remote" "$upstream_branch"
fi
git checkout -B "$main_branch" "$new_upstream"
cp -R "$tmp/.gitea" "$tmp/.github" "$tmp/patches" "$tmp/scripts" .
git apply patches/sync-stignore.patch patches/webui-build-marker.patch
git add -A
GIT_AUTHOR_DATE="$upstream_date" GIT_COMMITTER_DATE="$upstream_date" \
git -c user.name="Syncthing .stignore Fork" \
-c user.email="actions@felixfoertsch.de" \
commit -m "apply stignore synchronization patch"
git push --force-with-lease="$main_branch:$current_main" "$remote" "$main_branch"
}
main "$@"
@@ -0,0 +1,252 @@
#!/usr/bin/env bats
setup() {
REPO_ROOT="$(git rev-parse --show-toplevel)"
WORKFLOW="$REPO_ROOT/.gitea/workflows/custom-release.yml"
GITHUB_WORKFLOW="$REPO_ROOT/.github/workflows/custom-release.yml"
RELEASE_SCRIPT="$REPO_ROOT/scripts/update-custom-release.sh"
SYNC_SCRIPT="$REPO_ROOT/scripts/sync-upstream.sh"
}
@test "both hosts mirror upstream before building their own release" {
run rg -n 'run: ./scripts/sync-upstream.sh' "$WORKFLOW" "$GITHUB_WORKFLOW"
[ "$status" -eq 0 ]
[ "${#lines[@]}" -eq 2 ]
run rg -n 'git push --force-with-lease=.*upstream|git push --force-with-lease=.*main' "$SYNC_SCRIPT"
[ "$status" -eq 0 ]
run rg -n 'git apply patches/sync-stignore.patch patches/webui-build-marker.patch' "$SYNC_SCRIPT"
[ "$status" -eq 0 ]
run rg -n 'main_parent=.*current_main\^' "$SYNC_SCRIPT"
[ "$status" -eq 0 ]
run rg -n 'main_parent.*current_upstream' "$SYNC_SCRIPT"
[ "$status" -eq 0 ]
run rg -n 'main_ahead_count=.*rev-list --count' "$SYNC_SCRIPT"
[ "$status" -eq 0 ]
run rg -n 'main_ahead_count.*== "1"' "$SYNC_SCRIPT"
[ "$status" -eq 0 ]
run rg -n 'GIT_AUTHOR_DATE=.*GIT_COMMITTER_DATE=' "$SYNC_SCRIPT"
[ "$status" -eq 0 ]
}
@test "github custom release uses a github macos runner and github releases" {
run rg -n 'runs-on:[[:space:]]*macos-14' "$GITHUB_WORKFLOW"
[ "$status" -eq 0 ]
run rg -n 'gh release create' "$GITHUB_WORKFLOW"
[ "$status" -eq 0 ]
run rg -n 'CUSTOM_RELEASE_CREATE_GITEA_RELEASE: "0"' "$GITHUB_WORKFLOW"
[ "$status" -eq 0 ]
run rg -n 'CUSTOM_RELEASE_SIGN_DARWIN: "1"' "$GITHUB_WORKFLOW"
[ "$status" -eq 0 ]
run rg -n 'tea|ffmini_macos_arm64' "$GITHUB_WORKFLOW"
[ "$status" -ne 0 ]
}
@test "github imports signing secrets into a disposable keychain" {
run rg -n 'DEVELOPER_ID_APPLICATION_P12_BASE64:.*secrets.DEVELOPER_ID_APPLICATION_P12_BASE64' "$GITHUB_WORKFLOW"
[ "$status" -eq 0 ]
run rg -n 'DEVELOPER_ID_APPLICATION_P12_PASSWORD:.*secrets.DEVELOPER_ID_APPLICATION_P12_PASSWORD' "$GITHUB_WORKFLOW"
[ "$status" -eq 0 ]
run rg -n 'security import .* -P "\$DEVELOPER_ID_APPLICATION_P12_PASSWORD"' "$GITHUB_WORKFLOW"
[ "$status" -eq 0 ]
run rg -n '::add-mask::\$keychain_password' "$GITHUB_WORKFLOW"
[ "$status" -eq 0 ]
run rg -n 'if: always\(\)' "$GITHUB_WORKFLOW"
[ "$status" -eq 0 ]
run rg -n 'security delete-keychain "\$CUSTOM_RELEASE_KEYCHAIN_PATH"' "$GITHUB_WORKFLOW"
[ "$status" -eq 0 ]
run rg -n 'rm -f "\$CUSTOM_RELEASE_CERTIFICATE_PATH"' "$GITHUB_WORKFLOW"
[ "$status" -eq 0 ]
}
@test "custom release runs as one job on ffmini macos runner" {
run rg -n 'runs-on:[[:space:]]*ffmini_macos_arm64' "$WORKFLOW"
[ "$status" -eq 0 ]
run rg -n 'ubuntu-latest' "$WORKFLOW"
[ "$status" -ne 0 ]
run rg -n 'actions/upload-artifact' "$WORKFLOW"
[ "$status" -ne 0 ]
}
@test "custom release tea login setup is idempotent on persistent host runner" {
run rg -n 'tea" logins delete actions >/dev/null 2>&1 \|\| true' "$WORKFLOW"
[ "$status" -eq 0 ]
run rg -n 'tea" logins add --name actions' "$WORKFLOW"
[ "$status" -eq 0 ]
}
@test "custom release workflow imports developer id signing material into temporary keychain" {
run rg -n 'DEVELOPER_ID_APPLICATION_P12_BASE64' "$WORKFLOW"
[ "$status" -eq 0 ]
run rg -n 'DEVELOPER_ID_APPLICATION_P12_PASSWORD' "$WORKFLOW"
[ "$status" -eq 0 ]
run rg -n 'DEVELOPER_ID_APPLICATION_P12_BASE64 secret is required' "$WORKFLOW"
[ "$status" -eq 0 ]
run rg -n 'DEVELOPER_ID_APPLICATION_P12_PASSWORD secret is required' "$WORKFLOW"
[ "$status" -ne 0 ]
run rg -n 'security import .* -P "\$DEVELOPER_ID_APPLICATION_P12_PASSWORD"' "$WORKFLOW"
[ "$status" -eq 0 ]
run rg -n 'security import .* -A ' "$WORKFLOW"
[ "$status" -eq 0 ]
run rg -n 'security find-identity -v -p codesigning "\$keychain_path"' "$WORKFLOW"
[ "$status" -eq 0 ]
run rg -n 'security default-keychain -d user -s "\$keychain_path"' "$WORKFLOW"
[ "$status" -eq 0 ]
run rg -n 'previous_default_keychain=' "$WORKFLOW"
[ "$status" -eq 0 ]
run rg -n 'CUSTOM_RELEASE_PREVIOUS_DEFAULT_KEYCHAIN=\$previous_default_keychain' "$WORKFLOW"
[ "$status" -eq 0 ]
run rg -n 'CUSTOM_RELEASE_PREVIOUS_DYNAMIC_DEFAULT_KEYCHAIN' "$WORKFLOW"
[ "$status" -ne 0 ]
run rg -n 'security default-keychain -s "\$CUSTOM_RELEASE_PREVIOUS_DEFAULT_KEYCHAIN"' "$WORKFLOW"
[ "$status" -ne 0 ]
run rg -n 'security default-keychain -d dynamic' "$WORKFLOW"
[ "$status" -ne 0 ]
run rg -n 'security find-identity -v -p codesigning$' "$WORKFLOW" "$RELEASE_SCRIPT"
[ "$status" -eq 0 ]
run rg -n 'codesign_identity_sha1=' "$WORKFLOW"
[ "$status" -eq 0 ]
run rg -n 'sed -n .*Developer ID Application' "$WORKFLOW"
[ "$status" -eq 0 ]
run rg -n 'CUSTOM_RELEASE_CODESIGN_IDENTITY=\$codesign_identity' "$WORKFLOW"
[ "$status" -eq 0 ]
run rg -n 'CUSTOM_RELEASE_CODESIGN_IDENTITY_SHA1=\$codesign_identity_sha1' "$WORKFLOW"
[ "$status" -eq 0 ]
run rg -n 'CUSTOM_RELEASE_KEYCHAIN_PASSWORD=\$keychain_password' "$WORKFLOW"
[ "$status" -eq 0 ]
run rg -n 'codesign --force --dryrun --sign "\$codesign_identity" --keychain "\$keychain_path" --options runtime --timestamp "\$probe_binary"' "$WORKFLOW"
[ "$status" -eq 0 ]
run rg -n 'CUSTOM_RELEASE_CODESIGN_IDENTITY: "Developer ID Application' "$WORKFLOW"
[ "$status" -ne 0 ]
run rg -n 'security create-keychain' "$WORKFLOW"
[ "$status" -eq 0 ]
run rg -n 'keychain_dir="\$HOME/Library/Keychains"' "$WORKFLOW"
[ "$status" -eq 0 ]
run rg -n 'rm -f "\$keychain_path"' "$WORKFLOW"
[ "$status" -eq 0 ]
run rg -n 'existing_keychains=\(\)' "$WORKFLOW"
[ "$status" -eq 0 ]
run rg -n 'security list-keychains -s "\$keychain_path"' "$WORKFLOW"
[ "$status" -eq 0 ]
run rg -n 'security list-keychains -d dynamic' "$WORKFLOW"
[ "$status" -ne 0 ]
run rg -n 'security import' "$WORKFLOW"
[ "$status" -eq 0 ]
run rg -n 'security delete-keychain' "$WORKFLOW"
[ "$status" -eq 0 ]
}
@test "custom release carries per-target cgo mode" {
run rg -n 'darwin/arm64/zip/1' "$WORKFLOW" "$RELEASE_SCRIPT"
[ "$status" -eq 0 ]
run rg -n 'linux/amd64/tar/0' "$WORKFLOW" "$RELEASE_SCRIPT"
[ "$status" -eq 0 ]
run rg -n 'CUSTOM_RELEASE_CGO_ENABLED' "$WORKFLOW"
[ "$status" -ne 0 ]
}
@test "custom release signs darwin assets with hardened runtime and timestamp" {
run rg -n 'codesign_args=\(--force --sign "\$codesign_identity"\)' "$RELEASE_SCRIPT"
[ "$status" -eq 0 ]
run rg -n -- '--keychain "\$CUSTOM_RELEASE_KEYCHAIN_PATH"' "$RELEASE_SCRIPT"
[ "$status" -eq 0 ]
run rg -n 'codesign_args\+=\(--options runtime --timestamp\)' "$RELEASE_SCRIPT"
[ "$status" -eq 0 ]
run rg -n 'security unlock-keychain -p "\$CUSTOM_RELEASE_KEYCHAIN_PASSWORD" "\$CUSTOM_RELEASE_KEYCHAIN_PATH"' "$RELEASE_SCRIPT"
[ "$status" -eq 0 ]
run rg -n 'security find-identity -v -p codesigning "\$CUSTOM_RELEASE_KEYCHAIN_PATH"' "$RELEASE_SCRIPT"
[ "$status" -eq 0 ]
run awk '
/codesign_args=\(--force --sign "\$codesign_identity"\)/ { sign = NR }
/codesign_args\+=\(--keychain "\$CUSTOM_RELEASE_KEYCHAIN_PATH"\)/ { keychain = NR }
/codesign_args\+=\(--options runtime --timestamp\)/ { options = NR }
END { exit !(sign && keychain && options && sign < keychain && keychain < options) }
' "$RELEASE_SCRIPT"
[ "$status" -eq 0 ]
run rg -n 'Developer ID Application' "$WORKFLOW" "$RELEASE_SCRIPT"
[ "$status" -eq 0 ]
run rg -n 'CUSTOM_RELEASE_CODESIGN_IDENTITY' "$WORKFLOW" "$RELEASE_SCRIPT"
[ "$status" -eq 0 ]
}
@test "custom release validates signed darwin binaries before publishing" {
run rg -n 'modernc-sqlite' "$RELEASE_SCRIPT"
[ "$status" -eq 0 ]
run rg -n 'codesign --verify --strict --verbose=2' "$RELEASE_SCRIPT"
[ "$status" -eq 0 ]
run rg -n 'TeamIdentifier=NG5W75WE8U|TeamIdentifier.*NG5W75WE8U' "$RELEASE_SCRIPT"
[ "$status" -eq 0 ]
run rg -n 'CUSTOM_RELEASE_REQUIRE_GATEKEEPER_ASSESSMENT: "0"' "$WORKFLOW"
[ "$status" -eq 0 ]
run rg -n 'CUSTOM_RELEASE_SIGN_DARWIN: "1"' "$WORKFLOW"
[ "$status" -eq 0 ]
run rg -n 'require_gatekeeper_assessment="\$\{CUSTOM_RELEASE_REQUIRE_GATEKEEPER_ASSESSMENT:-0\}"' "$RELEASE_SCRIPT"
[ "$status" -eq 0 ]
run rg -n 'if \[\[ "\$require_gatekeeper_assessment" == "1" \]\]; then' "$RELEASE_SCRIPT"
[ "$status" -eq 0 ]
run rg -n 'spctl -a -vv --type execute' "$RELEASE_SCRIPT"
[ "$status" -eq 0 ]
}
+417
View File
@@ -0,0 +1,417 @@
#!/usr/bin/env bash
set -euo pipefail
# Description: create a patched Syncthing release from the latest upstream tag.
# Usage: ./scripts/update-custom-release.sh
upstream_url="${CUSTOM_RELEASE_UPSTREAM_URL:-https://github.com/syncthing/syncthing.git}"
upstream_tag="${CUSTOM_RELEASE_UPSTREAM_TAG:-}"
suffix="${CUSTOM_RELEASE_SUFFIX:-stignore.7}"
branch_prefix="${CUSTOM_RELEASE_BRANCH_PREFIX:-custom}"
dist_dir="${CUSTOM_RELEASE_DIST_DIR:-dist}"
target="${CUSTOM_RELEASE_TARGET:-syncthing}"
archive_kind="${CUSTOM_RELEASE_ARCHIVE:-tar}"
build_specs="${CUSTOM_RELEASE_BUILDS:-}"
default_cgo_enabled="${CUSTOM_RELEASE_CGO_ENABLED:-0}"
codesign_identity="${CUSTOM_RELEASE_CODESIGN_IDENTITY:-}"
codesign_team_id="${CUSTOM_RELEASE_CODESIGN_TEAM_ID:-NG5W75WE8U}"
sign_darwin="${CUSTOM_RELEASE_SIGN_DARWIN:-1}"
require_gatekeeper_assessment="${CUSTOM_RELEASE_REQUIRE_GATEKEEPER_ASSESSMENT:-0}"
push_release="${CUSTOM_RELEASE_PUSH:-0}"
push_branch="${CUSTOM_RELEASE_PUSH_BRANCH:-0}"
push_remote="${CUSTOM_RELEASE_REMOTE:-origin}"
run_tests="${CUSTOM_RELEASE_TEST:-1}"
force="${CUSTOM_RELEASE_FORCE:-0}"
publish_gitea_release="${CUSTOM_RELEASE_CREATE_GITEA_RELEASE:-0}"
tea_repo="${CUSTOM_RELEASE_TEA_REPO:-}"
patch_tmp_dir=""
patch_files=()
assets_rebuilt=0
if [[ -n "${CUSTOM_RELEASE_PATCHES:-}" ]]; then
read -r -a patch_files <<< "$CUSTOM_RELEASE_PATCHES"
elif [[ -n "${CUSTOM_RELEASE_PATCH:-}" ]]; then
patch_files=("$CUSTOM_RELEASE_PATCH")
else
patch_files=(
patches/sync-stignore.patch
patches/webui-build-marker.patch
)
fi
log() {
printf '%s\n' "$*"
}
die() {
printf 'error: %s\n' "$*" >&2
exit 1
}
require_clean_worktree() {
if [[ -n "$(git status --porcelain)" ]]; then
die "working tree has uncommitted changes"
fi
}
latest_stable_tag() {
git ls-remote --refs --tags --sort='version:refname' "$upstream_url" 'v[0-9]*' \
| awk '{ tag = $2; sub("refs/tags/", "", tag); if (tag ~ /^v[0-9]+\.[0-9]+\.[0-9]+$/) latest = tag } END { print latest }'
}
tag_exists() {
local tag="$1"
if git rev-parse --quiet --verify "refs/tags/$tag" >/dev/null; then
return 0
fi
git ls-remote --quiet --exit-code --tags "$push_remote" "refs/tags/$tag" >/dev/null 2>&1
}
copy_patches_to_temp() {
local patch_tmp_dir="$1"
local patch_file
local patch_name
local patch_index=0
mkdir -p "$patch_tmp_dir"
for patch_file in "${patch_files[@]}"; do
[[ -f "$patch_file" ]] || die "patch file not found: $patch_file"
printf -v patch_name '%03d-%s' "$patch_index" "$(basename "$patch_file")"
cp "$patch_file" "$patch_tmp_dir/$patch_name"
patch_index=$((patch_index + 1))
done
}
fetch_upstream_tag() {
local tag="$1"
git fetch --force "$upstream_url" "refs/tags/$tag:refs/tags/$tag"
}
create_release_commit() {
local tag="$1"
local custom_tag="$2"
local branch="$3"
local patch_tmp_dir="$4"
local patch_file
git checkout -B "$branch" "$tag"
rm -rf "$dist_dir"
for patch_file in "$patch_tmp_dir"/*; do
[[ -f "$patch_file" ]] || continue
log "Applying $(basename "$patch_file")"
git apply --3way "$patch_file"
done
remove_upstream_workflows
git add -A
git commit -m "apply local Syncthing patches for $tag"
git tag -a "$custom_tag" -m "Syncthing $tag with local patches"
}
remove_upstream_workflows() {
local workflow_dir
for workflow_dir in .github/workflows .gitea/workflows; do
if [[ -e "$workflow_dir" ]]; then
log "Removing upstream workflow directory $workflow_dir from release commit"
rm -rf "$workflow_dir"
fi
done
}
delete_local_tag_if_forced() {
local tag="$1"
if [[ "$force" != "1" ]]; then
return
fi
if git rev-parse --quiet --verify "refs/tags/$tag" >/dev/null; then
git tag -d "$tag"
fi
}
test_release() {
if [[ "$run_tests" != "1" ]]; then
log "Skipping tests because CUSTOM_RELEASE_TEST=$run_tests"
return
fi
rebuild_assets_once
go test ./lib/api/auto ./lib/fs ./lib/ignore ./lib/scanner ./lib/model
}
rebuild_assets_once() {
if [[ "$assets_rebuilt" == "1" ]]; then
return
fi
log "Regenerating embedded GUI assets"
go run build.go assets
assets_rebuilt=1
}
format_build_specs() {
local spec
for spec in $build_specs; do
printf -- '- %s\n' "$spec"
done
}
build_release() {
local custom_tag="$1"
rebuild_assets_once
rm -rf "$dist_dir"
mkdir -p "$dist_dir"
if [[ -z "$build_specs" ]]; then
build_specs="$(go env GOOS)/$(go env GOARCH)/$archive_kind"
fi
local spec
for spec in $build_specs; do
build_one "$custom_tag" "$spec"
done
cat > "$dist_dir/release-notes.md" <<EOF
# $custom_tag
Syncthing $upstream_tag with the local patches applied.
Builds:
$(format_build_specs)
Patches:
$(printf -- '- %s\n' "${patch_files[@]}")
Patch behavior:
- Root-level .stignore syncs like regular folder content.
- .stfolder and .stversions stay protected as Syncthing internals.
- The web GUI footer includes "It syncs .stignore now!" as a custom build marker.
EOF
(
cd "$dist_dir"
if command -v sha256sum >/dev/null 2>&1; then
sha256sum ./* > SHA256SUMS
else
shasum -a 256 ./* > SHA256SUMS
fi
)
}
build_one() {
local custom_tag="$1"
local spec="$2"
local goos
local goarch
local kind
local cgo_enabled
IFS=/ read -r goos goarch kind cgo_enabled <<< "$spec"
[[ -n "$goos" && -n "$goarch" && -n "$kind" ]] || die "invalid build spec: $spec"
cgo_enabled="${cgo_enabled:-$default_cgo_enabled}"
log "Building $target for $goos/$goarch as $kind with CGO_ENABLED=$cgo_enabled"
case "$kind" in
tar|zip)
local archive
archive="$(CGO_ENABLED="$cgo_enabled" go run build.go -version "$custom_tag" -goos "$goos" -goarch "$goarch" "$kind" "$target" | tail -n 1)"
if [[ "$goos" == "darwin" && "$sign_darwin" == "1" ]]; then
sign_and_validate_darwin_archive "$archive" "$kind"
fi
mv "$archive" "$dist_dir/"
;;
binary)
CGO_ENABLED="$cgo_enabled" go run build.go -version "$custom_tag" -goos "$goos" -goarch "$goarch" -build-out "$dist_dir/$target-$goos-$goarch" build "$target"
if [[ "$goos" == "darwin" && "$sign_darwin" == "1" ]]; then
sign_and_validate_darwin_binary "$dist_dir/$target-$goos-$goarch"
fi
;;
*)
die "unknown build archive kind in $spec"
;;
esac
}
find_release_binary() {
local root="$1"
local candidate
while IFS= read -r candidate; do
if [[ -x "$candidate" ]]; then
printf '%s\n' "$candidate"
return
fi
done < <(find "$root" -type f -name "$target")
die "could not find executable $target in $root"
}
sign_and_validate_darwin_archive() {
local archive="$1"
local kind="$2"
local tmp
local archive_abs
local binary
[[ -n "$codesign_identity" ]] || die "CUSTOM_RELEASE_CODESIGN_IDENTITY is required for darwin builds"
tmp="$(mktemp -d)"
archive_abs="$(cd "$(dirname "$archive")" && pwd -P)/$(basename "$archive")"
case "$kind" in
zip)
unzip -q "$archive_abs" -d "$tmp"
;;
tar)
tar -xf "$archive_abs" -C "$tmp"
;;
*)
die "cannot sign archive kind $kind"
;;
esac
binary="$(find_release_binary "$tmp")"
sign_and_validate_darwin_binary "$binary"
rm -f "$archive_abs"
case "$kind" in
zip)
(
cd "$tmp"
zip -qr "$archive_abs" .
)
;;
tar)
(
cd "$tmp"
tar -czf "$archive_abs" .
)
;;
esac
rm -rf "$tmp"
}
sign_and_validate_darwin_binary() {
local binary="$1"
local version_output
local codesign_details
local codesign_args
[[ -n "$codesign_identity" ]] || die "CUSTOM_RELEASE_CODESIGN_IDENTITY is required for darwin builds"
codesign_args=(--force --sign "$codesign_identity")
if [[ -n "${CUSTOM_RELEASE_KEYCHAIN_PATH:-}" ]]; then
if [[ -n "${CUSTOM_RELEASE_KEYCHAIN_PASSWORD:-}" ]]; then
security unlock-keychain -p "$CUSTOM_RELEASE_KEYCHAIN_PASSWORD" "$CUSTOM_RELEASE_KEYCHAIN_PATH"
fi
security find-identity -v -p codesigning "$CUSTOM_RELEASE_KEYCHAIN_PATH"
security find-identity -v -p codesigning
codesign_args+=(--keychain "$CUSTOM_RELEASE_KEYCHAIN_PATH")
fi
codesign_args+=(--options runtime --timestamp)
codesign "${codesign_args[@]}" "$binary"
version_output="$("$binary" --version)"
if [[ "$version_output" == *modernc-sqlite* ]]; then
die "darwin build unexpectedly reports [modernc-sqlite]: $version_output"
fi
codesign --verify --strict --verbose=2 "$binary"
codesign_details="$(codesign -dv --verbose=4 "$binary" 2>&1)"
if [[ "$codesign_team_id" == "NG5W75WE8U" && "$codesign_details" != *"TeamIdentifier=NG5W75WE8U"* ]]; then
printf '%s\n' "$codesign_details" >&2
die "darwin build is not signed by TeamIdentifier=NG5W75WE8U"
fi
if [[ "$codesign_details" != *"TeamIdentifier=$codesign_team_id"* ]]; then
printf '%s\n' "$codesign_details" >&2
die "darwin build is not signed by TeamIdentifier=$codesign_team_id"
fi
if [[ "$require_gatekeeper_assessment" == "1" ]]; then
spctl -a -vv --type execute "$binary"
else
log "Skipping Gatekeeper assessment because CUSTOM_RELEASE_REQUIRE_GATEKEEPER_ASSESSMENT=$require_gatekeeper_assessment"
fi
}
push_refs() {
local branch="$1"
local custom_tag="$2"
if [[ "$push_release" != "1" ]]; then
log "Skipping push because CUSTOM_RELEASE_PUSH=$push_release"
return
fi
if [[ "$push_branch" == "1" ]]; then
git push "$push_remote" "$branch"
else
log "Skipping release branch push because CUSTOM_RELEASE_PUSH_BRANCH=$push_branch"
fi
git push "$push_remote" "$custom_tag"
}
publish_release() {
local custom_tag="$1"
if [[ "$publish_gitea_release" != "1" ]]; then
log "Skipping Gitea release publishing because CUSTOM_RELEASE_CREATE_GITEA_RELEASE=$publish_gitea_release"
return
fi
command -v tea >/dev/null 2>&1 || die "tea is required for Gitea release publishing"
local args=(releases create "$custom_tag" --title "$custom_tag" --note-file "$dist_dir/release-notes.md")
if [[ -n "$tea_repo" ]]; then
args+=(--repo "$tea_repo")
else
args+=(--remote "$push_remote")
fi
local asset
for asset in "$dist_dir"/*; do
[[ -f "$asset" ]] || continue
[[ "$(basename "$asset")" == "release-notes.md" ]] && continue
args+=(--asset "$asset")
done
tea "${args[@]}"
}
main() {
require_clean_worktree
if [[ -z "$upstream_tag" ]]; then
upstream_tag="$(latest_stable_tag)"
fi
[[ -n "$upstream_tag" ]] || die "could not determine latest upstream tag"
local custom_tag="${upstream_tag}-${suffix}"
local branch="${branch_prefix}/${upstream_tag#v}-${suffix}"
if tag_exists "$custom_tag" && [[ "$force" != "1" ]]; then
log "Custom release $custom_tag already exists; nothing to do."
return
fi
patch_tmp_dir="$(mktemp -d)"
trap 'rm -rf "$patch_tmp_dir"' EXIT
copy_patches_to_temp "$patch_tmp_dir"
fetch_upstream_tag "$upstream_tag"
delete_local_tag_if_forced "$custom_tag"
create_release_commit "$upstream_tag" "$custom_tag" "$branch" "$patch_tmp_dir"
test_release
build_release "$custom_tag"
push_refs "$branch" "$custom_tag"
publish_release "$custom_tag"
log "Built custom release $custom_tag from upstream $upstream_tag"
log "Artifacts are in $dist_dir/"
}
main "$@"