Compare commits
158
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c5a29b5b26 | ||
|
|
4c64843d60 | ||
|
|
b4ff96d754 | ||
|
|
21c5ac2161 | ||
|
|
6fc0b41f97 | ||
|
|
0b0b2143ed | ||
|
|
af64140c61 | ||
|
|
1c68062231 | ||
|
|
4d92855d76 | ||
|
|
1c6f542cb7 | ||
|
|
b28066c85d | ||
|
|
71c8a2c36f | ||
|
|
e4ab7b4ff3 | ||
|
|
8b978d4712 | ||
|
|
7b319111d3 | ||
|
|
cb7cea93a2 | ||
|
|
20257faf54 | ||
|
|
c14abebd68 | ||
|
|
b1a1a90045 | ||
|
|
8afc9855f2 | ||
|
|
4215058911 | ||
|
|
9fb1a18dbf | ||
|
|
064213ceb8 | ||
|
|
0211251b34 | ||
|
|
1903da569b | ||
|
|
b6a7beca1f | ||
|
|
7b83e7403e | ||
|
|
1915a470e9 | ||
|
|
0b100296e1 | ||
|
|
e6ed3acf5f | ||
|
|
9f95bf3573 | ||
|
|
b05ece0681 | ||
|
|
5381178c46 | ||
|
|
e7f4f8306c | ||
|
|
9922a3abd9 | ||
|
|
40ab668a73 | ||
|
|
10d20c4800 | ||
|
|
700bb75016 | ||
|
|
e25de22705 | ||
|
|
ef6d561c66 | ||
|
|
3c92999406 | ||
|
|
e61b8a1ae8 | ||
|
|
706409d2f3 | ||
|
|
e9133ef82b | ||
|
|
67ba20d777 | ||
|
|
21da0d7890 | ||
|
|
ebbe57d0ab | ||
|
|
9cf8fca03f | ||
|
|
f4abc71dcc | ||
|
|
2712f566ab | ||
|
|
8aa02da93a | ||
|
|
ac3f390afa | ||
|
|
0e560486db | ||
|
|
57d413099d | ||
|
|
8d37e8f307 | ||
|
|
1fdf07933c | ||
|
|
d49df1e44c | ||
|
|
50480f6ccb | ||
|
|
15f693d93c | ||
|
|
8e934a8c69 | ||
|
|
c50678618f | ||
|
|
8094b459e4 | ||
|
|
6765867a2e | ||
|
|
4fb8ee6a6f | ||
|
|
79bac43800 | ||
|
|
674834ccf4 | ||
|
|
3bd2bff23b | ||
|
|
40660c5fb7 | ||
|
|
d940d094a1 | ||
|
|
9d67727989 | ||
|
|
6f51700a7f | ||
|
|
43d33dbeb5 | ||
|
|
bb91f53641 | ||
|
|
598915193a | ||
|
|
905e5ec07f | ||
|
|
5945a8c5bd | ||
|
|
4075b886d0 | ||
|
|
e39dcc5c58 | ||
|
|
46d2c59b24 | ||
|
|
54f6b5c2ee | ||
|
|
cade790198 | ||
|
|
98555a9a80 | ||
|
|
48b757cac1 | ||
|
|
58c85fc9db | ||
|
|
ddd98a818a | ||
|
|
99b707c141 | ||
|
|
39d6692109 | ||
|
|
64b5a1b738 | ||
|
|
1a131a56f2 | ||
|
|
beda37f28b | ||
|
|
1b8a8032f0 | ||
|
|
3423de24ea | ||
|
|
6532715641 | ||
|
|
2532ac35cf | ||
|
|
832fa094a3 | ||
|
|
bcd30ceaec | ||
|
|
78bfe643a8 | ||
|
|
0a58747eb2 | ||
|
|
96b03fac04 | ||
|
|
085455d72e | ||
|
|
72849690c9 | ||
|
|
f09cca52f2 | ||
|
|
9a3493c2f4 | ||
|
|
fa404d5a0d | ||
|
|
73ad18fbfb | ||
|
|
a4db309b39 | ||
|
|
4bc17bc588 | ||
|
|
964c8d7d65 | ||
|
|
bacf506e90 | ||
|
|
1dd264894a | ||
|
|
8c3d2f3bc5 | ||
|
|
702ed8ecc1 | ||
|
|
70bb4459be | ||
|
|
b038650810 | ||
|
|
821d6f43ac | ||
|
|
a16bf555c0 | ||
|
|
fa7b81e1cf | ||
|
|
516f1aa0c7 | ||
|
|
6b94599467 | ||
|
|
f183d1cbec | ||
|
|
cd6ea60fa1 | ||
|
|
58bf2b5515 | ||
|
|
d28be1b711 | ||
|
|
47e3147d0b | ||
|
|
2159dfd27d | ||
|
|
0bf21d9db2 | ||
|
|
f61843ef2e | ||
|
|
23e8366f8d | ||
|
|
ed252ed6d7 | ||
|
|
93e72cc83f | ||
|
|
abe34fc1f6 | ||
|
|
be002362b3 | ||
|
|
190dff142c | ||
|
|
c667ada63a | ||
|
|
50480b89fc | ||
|
|
93ae30d889 | ||
|
|
486eebc4ac | ||
|
|
ff33d976d1 | ||
|
|
69890b4282 | ||
|
|
533c9a6ab0 | ||
|
|
9521bb3931 | ||
|
|
25e03ef9ab | ||
|
|
e46a0f99c3 | ||
|
|
ed6575411f | ||
|
|
ed97e365b2 | ||
|
|
b4776ea4e0 | ||
|
|
b5ffd0a796 | ||
|
|
c74299b59a | ||
|
|
8b6d837483 | ||
|
|
3e74b3dee2 | ||
|
|
2902da996c | ||
|
|
f6f144bf17 | ||
|
|
ab5c42f4a0 | ||
|
|
780b8fd3bc | ||
|
|
7db3f7eaac | ||
|
|
f0b666269b | ||
|
|
190a59842c | ||
|
|
40888c1a66 |
@@ -1,6 +1,7 @@
|
||||
name: Feature request
|
||||
description: File a new feature request
|
||||
labels: ["enhancement", "needs-triage"]
|
||||
type: Feature
|
||||
body:
|
||||
|
||||
- type: textarea
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
name: Bug report
|
||||
description: If you're actually looking for support instead, see "I need help / I have a question".
|
||||
labels: ["bug", "needs-triage"]
|
||||
type: Bug
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
version: 1
|
||||
labels:
|
||||
|
||||
- label: enhancement
|
||||
title: ^feat\b
|
||||
|
||||
- label: bug
|
||||
title: ^fix\b
|
||||
|
||||
- label: documentation
|
||||
title: ^docs\b
|
||||
|
||||
- label: chore
|
||||
title: ^chore\b
|
||||
|
||||
- label: chore
|
||||
title: ^refactor\b
|
||||
|
||||
- label: build
|
||||
title: ^build\b
|
||||
|
||||
- label: dependencies
|
||||
title: ^build\(deps\)\b
|
||||
@@ -0,0 +1,52 @@
|
||||
version: 1
|
||||
creds:
|
||||
- registry: docker.io
|
||||
user: "{{env \"DOCKERHUB_USERNAME\"}}"
|
||||
pass: "{{env \"DOCKERHUB_TOKEN\"}}"
|
||||
|
||||
defaults:
|
||||
ratelimit:
|
||||
min: 100
|
||||
retry: 1m
|
||||
parallel: 4
|
||||
|
||||
sync:
|
||||
|
||||
- source: ghcr.io/syncthing/syncthing
|
||||
target: docker.io/syncthing/syncthing
|
||||
type: repository
|
||||
tags:
|
||||
allow:
|
||||
- latest
|
||||
- rc
|
||||
- edge
|
||||
- \d+
|
||||
- \d+\.\d+
|
||||
- \d+\.\d+\.\d+
|
||||
- \d+\.\d+\.\d+-rc\.\d+
|
||||
|
||||
- source: ghcr.io/syncthing/relaysrv
|
||||
target: docker.io/syncthing/relaysrv
|
||||
type: repository
|
||||
tags:
|
||||
allow:
|
||||
- latest
|
||||
- rc
|
||||
- edge
|
||||
- \d+
|
||||
- \d+\.\d+
|
||||
- \d+\.\d+\.\d+
|
||||
- \d+\.\d+\.\d+-rc\.\d+
|
||||
|
||||
- source: ghcr.io/syncthing/discosrv
|
||||
target: docker.io/syncthing/discosrv
|
||||
type: repository
|
||||
tags:
|
||||
allow:
|
||||
- latest
|
||||
- rc
|
||||
- edge
|
||||
- \d+
|
||||
- \d+\.\d+
|
||||
- \d+\.\d+\.\d+
|
||||
- \d+\.\d+\.\d+-rc\.\d+
|
||||
@@ -0,0 +1,17 @@
|
||||
changelog:
|
||||
exclude:
|
||||
labels:
|
||||
- dependencies
|
||||
|
||||
categories:
|
||||
- title: Fixes
|
||||
labels:
|
||||
- bug
|
||||
|
||||
- title: Features
|
||||
labels:
|
||||
- enhancement
|
||||
|
||||
- title: Other
|
||||
labels:
|
||||
- '*'
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
name: Build and push Docker images
|
||||
if: github.repository == 'syncthing/syncthing'
|
||||
runs-on: ubuntu-latest
|
||||
environment: release
|
||||
environment: docker
|
||||
strategy:
|
||||
matrix:
|
||||
pkg:
|
||||
|
||||
@@ -3,6 +3,9 @@ name: Build Syncthing
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches-ignore:
|
||||
- release
|
||||
- release-rc*
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
|
||||
@@ -14,6 +17,7 @@ env:
|
||||
|
||||
# Optimize compatibility on the slow archictures.
|
||||
GOMIPS: softfloat
|
||||
GOARM: "6"
|
||||
|
||||
# Avoid hilarious amounts of obscuring log output when running tests.
|
||||
LOGGER_DISCARD: "1"
|
||||
@@ -22,7 +26,7 @@ env:
|
||||
BUILD_USER: builder
|
||||
BUILD_HOST: github.syncthing.net
|
||||
|
||||
TAGS: "netgo osusergo sqlite_omit_load_extension"
|
||||
TAGS: "netgo osusergo sqlite_omit_load_extension sqlite_dbstat"
|
||||
|
||||
# A note on actions and third party code... The actions under actions/ (like
|
||||
# `uses: actions/checkout`) are maintained by GitHub, and we need to trust
|
||||
@@ -33,6 +37,51 @@ env:
|
||||
|
||||
jobs:
|
||||
|
||||
#
|
||||
# Source
|
||||
#
|
||||
|
||||
facts:
|
||||
name: Gather common facts
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
version: ${{ steps.get-version.outputs.version }}
|
||||
release-kind: ${{ steps.get-version.outputs.release-kind }}
|
||||
go-version: ${{ steps.get-go.outputs.go-version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.ref }} # https://github.com/actions/checkout/issues/882
|
||||
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache: false
|
||||
check-latest: true
|
||||
|
||||
- name: Get Syncthing version
|
||||
id: get-version
|
||||
run: |
|
||||
version=$(go run build.go version)
|
||||
echo "version=$version" >> "$GITHUB_OUTPUT"
|
||||
echo "Version: $version"
|
||||
|
||||
kind=stable
|
||||
if [[ $version == *-rc.[0-9] || $version == *-rc.[0-9][0-9] ]] ; then
|
||||
kind=candidate
|
||||
elif [[ $version == *-* ]] ; then
|
||||
kind=nightly
|
||||
fi
|
||||
echo "release-kind=$kind" >> "$GITHUB_OUTPUT"
|
||||
echo "Release kind: $kind"
|
||||
|
||||
- name: Get Go version
|
||||
id: get-go
|
||||
run: |
|
||||
go version
|
||||
echo "go-version=$(go version | sed 's#^.*go##;s# .*##')" >> $GITHUB_OUTPUT
|
||||
|
||||
#
|
||||
# Tests for all platforms. Runs a matrix build on Windows, Linux and Mac,
|
||||
# with the list of expected supported Go versions (current, previous).
|
||||
@@ -87,26 +136,6 @@ jobs:
|
||||
LOKI_LABELS: "go=${{ matrix.go }},runner=${{ matrix.runner }},repo=${{ github.repository }},ref=${{ github.ref }}"
|
||||
CGO_ENABLED: "1"
|
||||
|
||||
#
|
||||
# Meta checks for formatting, copyright, etc
|
||||
#
|
||||
|
||||
correctness:
|
||||
name: Check correctness
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache: false
|
||||
check-latest: true
|
||||
|
||||
- name: Check correctness
|
||||
run: |
|
||||
go test -v ./meta
|
||||
|
||||
#
|
||||
# The basic checks job is a virtual one that depends on the matrix tests,
|
||||
# the correctness checks, and various builds that we always do. This makes
|
||||
@@ -121,13 +150,14 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build-test
|
||||
- correctness
|
||||
- package-linux
|
||||
- package-cross
|
||||
- package-source
|
||||
- package-debian
|
||||
- package-windows
|
||||
- govulncheck
|
||||
- golangci
|
||||
- meta
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
@@ -138,26 +168,27 @@ jobs:
|
||||
package-windows:
|
||||
name: Package for Windows
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- facts
|
||||
env:
|
||||
VERSION: ${{ needs.facts.outputs.version }}
|
||||
RELEASE_KIND: ${{ needs.facts.outputs.release-kind }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.ref }} # https://github.com/actions/checkout/issues/882
|
||||
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
go-version: ${{ needs.facts.outputs.go-version }}
|
||||
cache: false
|
||||
check-latest: true
|
||||
|
||||
- uses: mlugg/setup-zig@v1
|
||||
- uses: mlugg/setup-zig@v2
|
||||
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cache/go-build
|
||||
~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ env.GO_VERSION }}-package-windows-${{ hashFiles('**/go.sum') }}
|
||||
key: ${{ runner.os }}-go-${{ needs.facts.outputs.go-version }}-package-windows-${{ hashFiles('**/go.sum') }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
@@ -180,9 +211,15 @@ jobs:
|
||||
name: unsigned-packages-windows
|
||||
path: "*.zip"
|
||||
|
||||
#
|
||||
# Codesign binaries for Windows. This job runs only when called in the
|
||||
# Syncthing repo for release branches and tags, as it requires our
|
||||
# specific code signing keys etc.
|
||||
#
|
||||
|
||||
codesign-windows:
|
||||
name: Codesign for Windows
|
||||
if: github.repository_owner == 'syncthing' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/heads/release-') || startsWith(github.ref, 'refs/tags/v'))
|
||||
if: github.repository_owner == 'syncthing' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release-nightly' || startsWith(github.ref, 'refs/tags/v'))
|
||||
environment: release
|
||||
runs-on: windows-latest
|
||||
needs:
|
||||
@@ -240,31 +277,27 @@ jobs:
|
||||
package-linux:
|
||||
name: Package for Linux
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- facts
|
||||
env:
|
||||
VERSION: ${{ needs.facts.outputs.version }}
|
||||
RELEASE_KIND: ${{ needs.facts.outputs.release-kind }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.ref }} # https://github.com/actions/checkout/issues/882
|
||||
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
go-version: ${{ needs.facts.outputs.go-version }}
|
||||
cache: false
|
||||
check-latest: true
|
||||
|
||||
- name: Get actual Go version
|
||||
run: |
|
||||
go version
|
||||
echo "GO_VERSION=$(go version | sed 's#^.*go##;s# .*##')" >> $GITHUB_ENV
|
||||
|
||||
- uses: mlugg/setup-zig@v1
|
||||
- uses: mlugg/setup-zig@v2
|
||||
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cache/go-build
|
||||
~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ env.GO_VERSION }}-package-${{ hashFiles('**/go.sum') }}
|
||||
key: ${{ runner.os }}-go-${{ needs.facts.outputs.go-version }}-package-${{ hashFiles('**/go.sum') }}
|
||||
|
||||
- name: Create packages
|
||||
run: |
|
||||
@@ -272,7 +305,7 @@ jobs:
|
||||
for tgt in syncthing stdiscosrv strelaysrv ; do
|
||||
go run build.go -tags "${{env.TAGS}}" -goos linux -goarch amd64 -cc "zig cc -target x86_64-linux-musl" tar "$tgt"
|
||||
go run build.go -tags "${{env.TAGS}}" -goos linux -goarch 386 -cc "zig cc -target x86-linux-musl" tar "$tgt"
|
||||
go run build.go -tags "${{env.TAGS}}" -goos linux -goarch arm -cc "zig cc -target arm-linux-musleabi" tar "$tgt"
|
||||
go run build.go -tags "${{env.TAGS}}" -goos linux -goarch arm -cc "zig cc -target arm-linux-musleabi -mcpu=arm1136j_s" tar "$tgt"
|
||||
go run build.go -tags "${{env.TAGS}}" -goos linux -goarch arm64 -cc "zig cc -target aarch64-linux-musl" tar "$tgt"
|
||||
go run build.go -tags "${{env.TAGS}}" -goos linux -goarch mips -cc "zig cc -target mips-linux-musleabi" tar "$tgt"
|
||||
go run build.go -tags "${{env.TAGS}}" -goos linux -goarch mipsle -cc "zig cc -target mipsel-linux-musleabi" tar "$tgt"
|
||||
@@ -297,39 +330,36 @@ jobs:
|
||||
compat.json
|
||||
|
||||
#
|
||||
# macOS
|
||||
# macOS. The entire build runs in the release environment because code
|
||||
# signing is part of the build process, so it is limited to release
|
||||
# branches on the Syncthing repo.
|
||||
#
|
||||
|
||||
package-macos:
|
||||
name: Package for macOS
|
||||
if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/heads/release-') || startsWith(github.ref, 'refs/tags/v'))
|
||||
if: github.repository_owner == 'syncthing' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release-nightly' || startsWith(github.ref, 'refs/tags/v'))
|
||||
environment: release
|
||||
runs-on: macos-latest
|
||||
needs:
|
||||
- facts
|
||||
env:
|
||||
CODESIGN_IDENTITY: ${{ secrets.CODESIGN_IDENTITY }}
|
||||
runs-on: macos-latest
|
||||
VERSION: ${{ needs.facts.outputs.version }}
|
||||
RELEASE_KIND: ${{ needs.facts.outputs.release-kind }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.ref }} # https://github.com/actions/checkout/issues/882
|
||||
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
go-version: ${{ needs.facts.outputs.go-version }}
|
||||
cache: false
|
||||
check-latest: true
|
||||
|
||||
- name: Get actual Go version
|
||||
run: |
|
||||
go version
|
||||
echo "GO_VERSION=$(go version | sed 's#^.*go##;s# .*##')" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cache/go-build
|
||||
~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ env.GO_VERSION }}-package-${{ hashFiles('**/go.sum') }}
|
||||
key: ${{ runner.os }}-go-${{ needs.facts.outputs.go-version }}-package-${{ hashFiles('**/go.sum') }}
|
||||
|
||||
- name: Import signing certificate
|
||||
if: env.CODESIGN_IDENTITY != ''
|
||||
@@ -405,7 +435,7 @@ jobs:
|
||||
|
||||
notarize-macos:
|
||||
name: Notarize for macOS
|
||||
if: github.repository_owner == 'syncthing' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/heads/release-') || startsWith(github.ref, 'refs/tags/v'))
|
||||
if: github.repository_owner == 'syncthing' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release-nightly' || startsWith(github.ref, 'refs/tags/v'))
|
||||
environment: release
|
||||
needs:
|
||||
- package-macos
|
||||
@@ -439,29 +469,25 @@ jobs:
|
||||
package-cross:
|
||||
name: Package cross compiled
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- facts
|
||||
env:
|
||||
VERSION: ${{ needs.facts.outputs.version }}
|
||||
RELEASE_KIND: ${{ needs.facts.outputs.release-kind }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.ref }} # https://github.com/actions/checkout/issues/882
|
||||
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
go-version: ${{ needs.facts.outputs.go-version }}
|
||||
cache: false
|
||||
check-latest: true
|
||||
|
||||
- name: Get actual Go version
|
||||
run: |
|
||||
go version
|
||||
echo "GO_VERSION=$(go version | sed 's#^.*go##;s# .*##')" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cache/go-build
|
||||
~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ env.GO_VERSION }}-cross-${{ hashFiles('**/go.sum') }}
|
||||
key: ${{ runner.os }}-go-${{ needs.facts.outputs.go-version }}-cross-${{ hashFiles('**/go.sum') }}
|
||||
|
||||
- name: Create packages
|
||||
run: |
|
||||
@@ -509,33 +535,33 @@ jobs:
|
||||
package-source:
|
||||
name: Package source code
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- facts
|
||||
env:
|
||||
VERSION: ${{ needs.facts.outputs.version }}
|
||||
RELEASE_KIND: ${{ needs.facts.outputs.release-kind }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.ref }} # https://github.com/actions/checkout/issues/882
|
||||
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
go-version: ${{ needs.facts.outputs.go-version }}
|
||||
cache: false
|
||||
check-latest: true
|
||||
|
||||
- name: Package source
|
||||
run: |
|
||||
version=$(go run build.go version)
|
||||
echo "$version" > RELEASE
|
||||
echo "$VERSION" > RELEASE
|
||||
|
||||
go mod vendor
|
||||
go run build.go assets
|
||||
|
||||
cd ..
|
||||
|
||||
tar c -z -f "syncthing-source-$version.tar.gz" \
|
||||
tar c -z -f "syncthing-source-$VERSION.tar.gz" \
|
||||
--exclude .git \
|
||||
syncthing
|
||||
|
||||
mv "syncthing-source-$version.tar.gz" syncthing
|
||||
mv "syncthing-source-$VERSION.tar.gz" syncthing
|
||||
|
||||
- name: Archive artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
@@ -549,7 +575,7 @@ jobs:
|
||||
|
||||
sign-for-upgrade:
|
||||
name: Sign for upgrade
|
||||
if: github.repository_owner == 'syncthing' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/heads/release-') || startsWith(github.ref, 'refs/tags/v'))
|
||||
if: github.repository_owner == 'syncthing' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release-nightly' || startsWith(github.ref, 'refs/tags/v'))
|
||||
environment: release
|
||||
needs:
|
||||
- codesign-windows
|
||||
@@ -557,27 +583,26 @@ jobs:
|
||||
- package-macos
|
||||
- package-cross
|
||||
- package-source
|
||||
- facts
|
||||
env:
|
||||
VERSION: ${{ needs.facts.outputs.version }}
|
||||
RELEASE_KIND: ${{ needs.facts.outputs.release-kind }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.ref }} # https://github.com/actions/checkout/issues/882
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
repository: syncthing/release-tools
|
||||
path: tools
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
go-version: ${{ needs.facts.outputs.go-version }}
|
||||
cache: false
|
||||
check-latest: true
|
||||
|
||||
- name: Install signing tool
|
||||
run: |
|
||||
@@ -604,9 +629,6 @@ jobs:
|
||||
sha256sum "${files[@]}" > sha256sum.txt
|
||||
popd
|
||||
|
||||
version=$(go run build.go version)
|
||||
echo "VERSION=$version" >> $GITHUB_ENV
|
||||
|
||||
- name: Sign shasum files
|
||||
uses: docker://ghcr.io/kastelo/ezapt:latest
|
||||
with:
|
||||
@@ -642,22 +664,18 @@ jobs:
|
||||
package-debian:
|
||||
name: Package for Debian
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- facts
|
||||
env:
|
||||
VERSION: ${{ needs.facts.outputs.version }}
|
||||
RELEASE_KIND: ${{ needs.facts.outputs.release-kind }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.ref }} # https://github.com/actions/checkout/issues/882
|
||||
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
go-version: ${{ needs.facts.outputs.go-version }}
|
||||
cache: false
|
||||
check-latest: true
|
||||
|
||||
- name: Get actual Go version
|
||||
run: |
|
||||
go version
|
||||
echo "GO_VERSION=$(go version | sed 's#^.*go##;s# .*##')" >> $GITHUB_ENV
|
||||
|
||||
- uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
@@ -667,20 +685,21 @@ jobs:
|
||||
run: |
|
||||
gem install fpm
|
||||
|
||||
- uses: mlugg/setup-zig@v1
|
||||
- uses: mlugg/setup-zig@v2
|
||||
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cache/go-build
|
||||
~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ env.GO_VERSION }}-debian-${{ hashFiles('**/go.sum') }}
|
||||
key: ${{ runner.os }}-go-${{ needs.facts.outputs.go-version }}-debian-${{ hashFiles('**/go.sum') }}
|
||||
|
||||
- name: Package for Debian (CGO)
|
||||
run: |
|
||||
for tgt in syncthing stdiscosrv strelaysrv ; do
|
||||
go run build.go -no-upgrade -installsuffix=no-upgrade -tags "${{env.TAGS}}" -goos linux -goarch amd64 -cc "zig cc -target x86_64-linux-musl" deb "$tgt"
|
||||
go run build.go -no-upgrade -installsuffix=no-upgrade -tags "${{env.TAGS}}" -goos linux -goarch arm -cc "zig cc -target arm-linux-musleabi" deb "$tgt"
|
||||
go run build.go -no-upgrade -installsuffix=no-upgrade -tags "${{env.TAGS}}" -goos linux -goarch armel -cc "zig cc -target arm-linux-musleabi -mcpu=arm1136j_s" deb "$tgt"
|
||||
go run build.go -no-upgrade -installsuffix=no-upgrade -tags "${{env.TAGS}}" -goos linux -goarch armhf -cc "zig cc -target arm-linux-musleabi -mcpu=arm1136j_s" deb "$tgt"
|
||||
go run build.go -no-upgrade -installsuffix=no-upgrade -tags "${{env.TAGS}}" -goos linux -goarch arm64 -cc "zig cc -target aarch64-linux-musl" deb "$tgt"
|
||||
done
|
||||
env:
|
||||
@@ -704,13 +723,13 @@ jobs:
|
||||
environment: release
|
||||
needs:
|
||||
- sign-for-upgrade
|
||||
- facts
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
repository: syncthing/release-tools
|
||||
path: tools
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
@@ -720,9 +739,8 @@ jobs:
|
||||
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
go-version: ${{ needs.facts.outputs.go-version }}
|
||||
cache: false
|
||||
check-latest: true
|
||||
|
||||
- name: Create release json
|
||||
run: |
|
||||
@@ -734,12 +752,15 @@ jobs:
|
||||
- name: Push artifacts
|
||||
uses: docker://docker.io/rclone/rclone:latest
|
||||
env:
|
||||
RCLONE_CONFIG_OBJSTORE_TYPE: ${{ secrets.AZUREBLOB_TYPE }}
|
||||
RCLONE_CONFIG_OBJSTORE_ACCOUNT: ${{ secrets.AZUREBLOB_ACCOUNT }}
|
||||
RCLONE_CONFIG_OBJSTORE_KEY: ${{ secrets.AZUREBLOB_KEY }}
|
||||
RCLONE_AZUREBLOB_ACCESS_TIER: hot
|
||||
RCLONE_CONFIG_OBJSTORE_TYPE: s3
|
||||
RCLONE_CONFIG_OBJSTORE_PROVIDER: ${{ secrets.S3_PROVIDER }}
|
||||
RCLONE_CONFIG_OBJSTORE_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY_ID }}
|
||||
RCLONE_CONFIG_OBJSTORE_SECRET_ACCESS_KEY: ${{ secrets.S3_SECRET_ACCESS_KEY }}
|
||||
RCLONE_CONFIG_OBJSTORE_ENDPOINT: ${{ secrets.S3_ENDPOINT }}
|
||||
RCLONE_CONFIG_OBJSTORE_REGION: ${{ secrets.S3_REGION }}
|
||||
RCLONE_CONFIG_OBJSTORE_ACL: public-read
|
||||
with:
|
||||
args: sync -v packages objstore:nightly
|
||||
args: sync -v --no-update-modtime packages objstore:nightly
|
||||
|
||||
#
|
||||
# Push release artifacts to Spaces
|
||||
@@ -749,9 +770,15 @@ jobs:
|
||||
name: Publish release files
|
||||
if: github.repository_owner == 'syncthing' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/tags/v'))
|
||||
environment: release
|
||||
permissions:
|
||||
contents: write
|
||||
needs:
|
||||
- sign-for-upgrade
|
||||
- package-debian
|
||||
- facts
|
||||
env:
|
||||
VERSION: ${{ needs.facts.outputs.version }}
|
||||
RELEASE_KIND: ${{ needs.facts.outputs.release-kind }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -773,34 +800,70 @@ jobs:
|
||||
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
go-version: ${{ needs.facts.outputs.go-version }}
|
||||
cache: false
|
||||
check-latest: true
|
||||
|
||||
- name: Set version
|
||||
run: |
|
||||
version=$(go run build.go version)
|
||||
echo "VERSION=$version" >> $GITHUB_ENV
|
||||
|
||||
- name: Push to object store (${{ env.VERSION }})
|
||||
uses: docker://docker.io/rclone/rclone:latest
|
||||
env:
|
||||
RCLONE_CONFIG_OBJSTORE_TYPE: ${{ secrets.AZUREBLOB_TYPE }}
|
||||
RCLONE_CONFIG_OBJSTORE_ACCOUNT: ${{ secrets.AZUREBLOB_ACCOUNT }}
|
||||
RCLONE_CONFIG_OBJSTORE_KEY: ${{ secrets.AZUREBLOB_KEY }}
|
||||
RCLONE_AZUREBLOB_ACCESS_TIER: cool
|
||||
RCLONE_CONFIG_OBJSTORE_TYPE: s3
|
||||
RCLONE_CONFIG_OBJSTORE_PROVIDER: ${{ secrets.S3_PROVIDER }}
|
||||
RCLONE_CONFIG_OBJSTORE_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY_ID }}
|
||||
RCLONE_CONFIG_OBJSTORE_SECRET_ACCESS_KEY: ${{ secrets.S3_SECRET_ACCESS_KEY }}
|
||||
RCLONE_CONFIG_OBJSTORE_ENDPOINT: ${{ secrets.S3_ENDPOINT }}
|
||||
RCLONE_CONFIG_OBJSTORE_REGION: ${{ secrets.S3_REGION }}
|
||||
RCLONE_CONFIG_OBJSTORE_ACL: public-read
|
||||
with:
|
||||
args: sync -v packages objstore:release/${{ env.VERSION }}
|
||||
args: sync -v --no-update-modtime packages objstore:release/${{ env.VERSION }}
|
||||
|
||||
- name: Push to object store (latest)
|
||||
uses: docker://docker.io/rclone/rclone:latest
|
||||
env:
|
||||
RCLONE_CONFIG_OBJSTORE_TYPE: ${{ secrets.AZUREBLOB_TYPE }}
|
||||
RCLONE_CONFIG_OBJSTORE_ACCOUNT: ${{ secrets.AZUREBLOB_ACCOUNT }}
|
||||
RCLONE_CONFIG_OBJSTORE_KEY: ${{ secrets.AZUREBLOB_KEY }}
|
||||
RCLONE_AZUREBLOB_ACCESS_TIER: hot
|
||||
RCLONE_CONFIG_OBJSTORE_TYPE: s3
|
||||
RCLONE_CONFIG_OBJSTORE_PROVIDER: ${{ secrets.S3_PROVIDER }}
|
||||
RCLONE_CONFIG_OBJSTORE_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY_ID }}
|
||||
RCLONE_CONFIG_OBJSTORE_SECRET_ACCESS_KEY: ${{ secrets.S3_SECRET_ACCESS_KEY }}
|
||||
RCLONE_CONFIG_OBJSTORE_ENDPOINT: ${{ secrets.S3_ENDPOINT }}
|
||||
RCLONE_CONFIG_OBJSTORE_REGION: ${{ secrets.S3_REGION }}
|
||||
RCLONE_CONFIG_OBJSTORE_ACL: public-read
|
||||
with:
|
||||
args: sync -v objstore:release/${{ env.VERSION }} objstore:release/latest
|
||||
args: sync -v --no-update-modtime objstore:release/${{ env.VERSION }} objstore:release/latest
|
||||
|
||||
- name: Create GitHub releases and push binaries
|
||||
run: |
|
||||
maybePrerelease=""
|
||||
if [[ $VERSION == *-* ]]; then
|
||||
maybePrerelease="--prerelease"
|
||||
fi
|
||||
export GH_PROMPT_DISABLED=1
|
||||
if ! gh release view --json name "$VERSION" >/dev/null 2>&1 ; then
|
||||
gh release create "$VERSION" \
|
||||
$maybePrerelease \
|
||||
--title "$VERSION" \
|
||||
--notes-from-tag
|
||||
fi
|
||||
gh release upload --clobber "$VERSION" \
|
||||
packages/*.asc packages/*.json \
|
||||
packages/syncthing-*.tar.gz \
|
||||
packages/syncthing-*.zip \
|
||||
packages/syncthing_*.deb
|
||||
|
||||
PKGS=$(pwd)/packages
|
||||
cd /tmp # gh will not release for repo x while inside repo y
|
||||
for repo in relaysrv discosrv ; do
|
||||
export GH_REPO="syncthing/$repo"
|
||||
if ! gh release view --json name "$VERSION" >/dev/null 2>&1 ; then
|
||||
gh release create "$VERSION" \
|
||||
$maybePrerelease \
|
||||
--title "$VERSION" \
|
||||
--notes "https://github.com/syncthing/syncthing/releases/tag/$VERSION"
|
||||
fi
|
||||
gh release upload --clobber "$VERSION" \
|
||||
$PKGS/*.asc \
|
||||
$PKGS/*${repo}*
|
||||
done
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.ACTIONS_GITHUB_TOKEN }}
|
||||
|
||||
#
|
||||
# Push Debian/APT archive
|
||||
@@ -808,16 +871,17 @@ jobs:
|
||||
|
||||
publish-apt:
|
||||
name: Publish APT
|
||||
if: github.repository_owner == 'syncthing' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/heads/release-') || startsWith(github.ref, 'refs/tags/v'))
|
||||
if: github.repository_owner == 'syncthing' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release-nightly' || startsWith(github.ref, 'refs/tags/v'))
|
||||
environment: release
|
||||
needs:
|
||||
- package-debian
|
||||
- facts
|
||||
env:
|
||||
VERSION: ${{ needs.facts.outputs.version }}
|
||||
RELEASE_KIND: ${{ needs.facts.outputs.release-kind }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.ref }} # https://github.com/actions/checkout/issues/882
|
||||
|
||||
- name: Download packages
|
||||
uses: actions/download-artifact@v4
|
||||
@@ -825,33 +889,22 @@ jobs:
|
||||
name: debian-packages
|
||||
path: packages
|
||||
|
||||
- name: Set version
|
||||
run: |
|
||||
version=$(go run build.go version)
|
||||
echo "Version: $version"
|
||||
echo "VERSION=$version" >> $GITHUB_ENV
|
||||
|
||||
# Decide whether packages should go to stable, candidate or nightly
|
||||
- name: Prepare packages
|
||||
run: |
|
||||
kind=stable
|
||||
if [[ $VERSION == v2* ]] ; then
|
||||
kind=v2
|
||||
elif [[ $VERSION == *-rc.[0-9] ]] ; then
|
||||
kind=candidate
|
||||
elif [[ $VERSION == *-* ]] ; then
|
||||
kind=nightly
|
||||
fi
|
||||
echo "Kind: $kind"
|
||||
mkdir -p packages/syncthing/$kind
|
||||
mv packages/*.deb packages/syncthing/$kind
|
||||
mkdir -p packages/syncthing/$RELEASE_KIND
|
||||
mv packages/*.deb packages/syncthing/$RELEASE_KIND
|
||||
|
||||
- name: Pull archive
|
||||
uses: docker://docker.io/rclone/rclone:latest
|
||||
env:
|
||||
RCLONE_CONFIG_OBJSTORE_TYPE: ${{ secrets.AZUREBLOB_TYPE }}
|
||||
RCLONE_CONFIG_OBJSTORE_ACCOUNT: ${{ secrets.AZUREBLOB_ACCOUNT }}
|
||||
RCLONE_CONFIG_OBJSTORE_KEY: ${{ secrets.AZUREBLOB_KEY }}
|
||||
RCLONE_CONFIG_OBJSTORE_TYPE: s3
|
||||
RCLONE_CONFIG_OBJSTORE_PROVIDER: ${{ secrets.S3_PROVIDER }}
|
||||
RCLONE_CONFIG_OBJSTORE_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY_ID }}
|
||||
RCLONE_CONFIG_OBJSTORE_SECRET_ACCESS_KEY: ${{ secrets.S3_SECRET_ACCESS_KEY }}
|
||||
RCLONE_CONFIG_OBJSTORE_ENDPOINT: ${{ secrets.S3_ENDPOINT }}
|
||||
RCLONE_CONFIG_OBJSTORE_REGION: ${{ secrets.S3_REGION }}
|
||||
RCLONE_CONFIG_OBJSTORE_ACL: public-read
|
||||
with:
|
||||
args: sync objstore:apt/dists dists
|
||||
|
||||
@@ -868,27 +921,31 @@ jobs:
|
||||
- name: Push archive
|
||||
uses: docker://docker.io/rclone/rclone:latest
|
||||
env:
|
||||
RCLONE_CONFIG_OBJSTORE_TYPE: ${{ secrets.AZUREBLOB_TYPE }}
|
||||
RCLONE_CONFIG_OBJSTORE_ACCOUNT: ${{ secrets.AZUREBLOB_ACCOUNT }}
|
||||
RCLONE_CONFIG_OBJSTORE_KEY: ${{ secrets.AZUREBLOB_KEY }}
|
||||
RCLONE_AZUREBLOB_ACCESS_TIER: hot
|
||||
RCLONE_CONFIG_OBJSTORE_TYPE: s3
|
||||
RCLONE_CONFIG_OBJSTORE_PROVIDER: ${{ secrets.S3_PROVIDER }}
|
||||
RCLONE_CONFIG_OBJSTORE_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY_ID }}
|
||||
RCLONE_CONFIG_OBJSTORE_SECRET_ACCESS_KEY: ${{ secrets.S3_SECRET_ACCESS_KEY }}
|
||||
RCLONE_CONFIG_OBJSTORE_ENDPOINT: ${{ secrets.S3_ENDPOINT }}
|
||||
RCLONE_CONFIG_OBJSTORE_REGION: ${{ secrets.S3_REGION }}
|
||||
RCLONE_CONFIG_OBJSTORE_ACL: public-read
|
||||
with:
|
||||
args: sync -v dists objstore:apt/dists
|
||||
args: sync -v --no-update-modtime dists objstore:apt/dists
|
||||
|
||||
#
|
||||
# Build and push to Docker Hub
|
||||
# Build and push (except for PRs) to GHCR.
|
||||
#
|
||||
|
||||
docker-syncthing:
|
||||
name: Build and push Docker images
|
||||
docker-ghcr:
|
||||
name: Build and push Docker images (GHCR)
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
|
||||
environment: docker
|
||||
env:
|
||||
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
needs:
|
||||
- facts
|
||||
env:
|
||||
VERSION: ${{ needs.facts.outputs.version }}
|
||||
RELEASE_KIND: ${{ needs.facts.outputs.release-kind }}
|
||||
strategy:
|
||||
matrix:
|
||||
pkg:
|
||||
@@ -907,29 +964,20 @@ jobs:
|
||||
image: discosrv
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.ref }} # https://github.com/actions/checkout/issues/882
|
||||
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
go-version: ${{ needs.facts.outputs.go-version }}
|
||||
cache: false
|
||||
check-latest: true
|
||||
|
||||
- name: Get actual Go version
|
||||
run: |
|
||||
go version
|
||||
echo "GO_VERSION=$(go version | sed 's#^.*go##;s# .*##')" >> $GITHUB_ENV
|
||||
|
||||
- uses: mlugg/setup-zig@v1
|
||||
- uses: mlugg/setup-zig@v2
|
||||
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cache/go-build
|
||||
~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ env.GO_VERSION }}-docker-${{ matrix.pkg }}-${{ hashFiles('**/go.sum') }}
|
||||
key: ${{ runner.os }}-go-${{ needs.facts.outputs.go-version }}-docker-${{ matrix.pkg }}-${{ hashFiles('**/go.sum') }}
|
||||
|
||||
- name: Build binaries (CGO)
|
||||
run: |
|
||||
@@ -942,21 +990,13 @@ jobs:
|
||||
mv ${{ matrix.pkg }} ${{ matrix.pkg }}-linux-arm64
|
||||
|
||||
# arm
|
||||
go run build.go -goos linux -goarch arm -tags "${{env.TAGS}}" -cc "zig cc -target arm-linux-musleabi" -no-upgrade build ${{ matrix.pkg }}
|
||||
go run build.go -goos linux -goarch arm -tags "${{env.TAGS}}" -cc "zig cc -target arm-linux-musleabi -mcpu=arm1136j_s" -no-upgrade build ${{ matrix.pkg }}
|
||||
mv ${{ matrix.pkg }} ${{ matrix.pkg }}-linux-arm
|
||||
env:
|
||||
CGO_ENABLED: "1"
|
||||
BUILD_USER: docker
|
||||
EXTRA_LDFLAGS: "-linkmode=external -extldflags=-static"
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
if: env.DOCKERHUB_USERNAME != ''
|
||||
with:
|
||||
registry: docker.io
|
||||
username: ${{ env.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
@@ -969,8 +1009,7 @@ jobs:
|
||||
|
||||
- name: Set version tags
|
||||
run: |
|
||||
version=$(go run build.go version)
|
||||
version=${version#v}
|
||||
version=${VERSION#v}
|
||||
repo=ghcr.io/${{ github.repository_owner }}/${{ matrix.image }}
|
||||
ref="${{github.ref_name}}"
|
||||
ref=${ref//\//-} # slashes to dashes
|
||||
@@ -988,16 +1027,8 @@ jobs:
|
||||
tags=$repo:$ref
|
||||
fi
|
||||
|
||||
# If we have a Docker Hub secret, also push to there.
|
||||
if [[ $DOCKERHUB_USERNAME != "" ]] ; then
|
||||
dockerhubtags="${tags//ghcr.io\/syncthing/docker.io\/syncthing}"
|
||||
tags="$tags,$dockerhubtags"
|
||||
fi
|
||||
|
||||
echo Pushing to $tags
|
||||
|
||||
echo "DOCKER_TAGS=$tags" >> $GITHUB_ENV
|
||||
echo "VERSION=$version" >> $GITHUB_ENV
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
@@ -1006,11 +1037,35 @@ jobs:
|
||||
file: ${{ matrix.dockerfile }}
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/7
|
||||
tags: ${{ env.DOCKER_TAGS }}
|
||||
push: true
|
||||
push: ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' }}
|
||||
labels: |
|
||||
org.opencontainers.image.version=${{ env.VERSION }}
|
||||
org.opencontainers.image.revision=${{ github.sha }}
|
||||
|
||||
#
|
||||
# Sync images to Docker hub. This takes the images already pushed to GHCR
|
||||
# and copies them to Docker hub. Runs for releases only.
|
||||
#
|
||||
|
||||
docker-hub:
|
||||
name: Sync images to Docker hub
|
||||
if: github.repository_owner == 'syncthing' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/release-nightly' || github.ref == 'refs/heads/infrastructure' || startsWith(github.ref, 'refs/tags/v'))
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- docker-ghcr
|
||||
environment: docker
|
||||
env:
|
||||
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Sync images
|
||||
uses: docker://docker.io/regclient/regsync:latest
|
||||
with:
|
||||
args:
|
||||
-c ./.github/regsync.yml
|
||||
once
|
||||
|
||||
#
|
||||
# Check for known vulnerabilities in Go dependencies
|
||||
#
|
||||
@@ -1018,17 +1073,57 @@ jobs:
|
||||
govulncheck:
|
||||
runs-on: ubuntu-latest
|
||||
name: Run govulncheck
|
||||
needs:
|
||||
- facts
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
go-version: ${{ needs.facts.outputs.go-version }}
|
||||
cache: false
|
||||
check-latest: true
|
||||
|
||||
- name: run govulncheck
|
||||
run: |
|
||||
go run build.go assets
|
||||
go install golang.org/x/vuln/cmd/govulncheck@latest
|
||||
govulncheck ./...
|
||||
|
||||
#
|
||||
# golangci-lint runs a suite of static analysis checks on the code
|
||||
#
|
||||
|
||||
golangci:
|
||||
runs-on: ubuntu-latest
|
||||
name: Run golangci-lint
|
||||
if: github.event_name == 'pull_request'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 'stable'
|
||||
|
||||
- name: ensure asset generation
|
||||
run: go run build.go assets
|
||||
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v8
|
||||
with:
|
||||
only-new-issues: true
|
||||
|
||||
#
|
||||
# Meta checks for formatting, copyright, etc
|
||||
#
|
||||
|
||||
meta:
|
||||
name: Run meta checks
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 'stable'
|
||||
|
||||
- run: |
|
||||
go run build.go assets
|
||||
go test -v ./meta
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
name: PR metadata
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- edited
|
||||
- synchronize
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
|
||||
#
|
||||
# Set labels on PRs, which are then used to categorise release notes
|
||||
#
|
||||
|
||||
labels:
|
||||
name: Set labels
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: srvaroa/labeler@v1
|
||||
env:
|
||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
@@ -0,0 +1,60 @@
|
||||
name: Release Syncthing
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- release
|
||||
- release-rc*
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
create-release-tag:
|
||||
name: Create release tag
|
||||
runs-on: ubuntu-latest
|
||||
environment: release
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.ref }} # https://github.com/actions/checkout/issues/882
|
||||
token: ${{ secrets.ACTIONS_GITHUB_TOKEN }}
|
||||
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: stable
|
||||
|
||||
- name: Determine version to release
|
||||
run: |
|
||||
if [[ "$GITHUB_REF_NAME" == "release" ]] ; then
|
||||
next=$(go run ./script/next-version.go)
|
||||
else
|
||||
next=$(go run ./script/next-version.go --pre)
|
||||
fi
|
||||
echo "NEXT=$next" >> $GITHUB_ENV
|
||||
echo "Next version is $next"
|
||||
|
||||
prev=$(git describe --exclude "*-*" --abbrev=0)
|
||||
echo "PREV=$prev" >> $GITHUB_ENV
|
||||
echo "Previous version is $prev"
|
||||
|
||||
- name: Determine release notes
|
||||
run: |
|
||||
go run ./script/relnotes.go --new-ver "$NEXT" --branch "$GITHUB_REF_NAME" --prev-ver "$PREV" > notes.md
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.ACTIONS_GITHUB_TOKEN }}
|
||||
|
||||
- name: Create and push tag
|
||||
run: |
|
||||
git config --global user.name 'Syncthing Release Automation'
|
||||
git config --global user.email 'release@syncthing.net'
|
||||
git tag -a -F notes.md --cleanup=whitespace "$NEXT"
|
||||
git push origin "$NEXT"
|
||||
|
||||
- name: Trigger the build
|
||||
uses: benc-uk/workflow-dispatch@v1
|
||||
with:
|
||||
workflow: build-syncthing.yaml
|
||||
ref: refs/tags/${{ env.NEXT }}
|
||||
token: ${{ secrets.ACTIONS_GITHUB_TOKEN }}
|
||||
+38
-11
@@ -1,13 +1,15 @@
|
||||
version: "2"
|
||||
linters:
|
||||
enable-all: true
|
||||
default: all
|
||||
disable:
|
||||
- cyclop
|
||||
- depguard
|
||||
- err113
|
||||
- exhaustive
|
||||
- exhaustruct
|
||||
- forbidigo
|
||||
- funcorder
|
||||
- funlen
|
||||
- gci
|
||||
- gochecknoglobals
|
||||
- gochecknoinits
|
||||
- gocognit
|
||||
@@ -15,31 +17,56 @@ linters:
|
||||
- gocyclo
|
||||
- godot
|
||||
- godox
|
||||
- gofmt
|
||||
- goimports
|
||||
- gomoddirectives
|
||||
- inamedparam
|
||||
- interfacebloat
|
||||
- ireturn
|
||||
- lll
|
||||
- maintidx
|
||||
- mnd
|
||||
- musttag
|
||||
- nestif
|
||||
- nlreturn
|
||||
- nonamedreturns
|
||||
- paralleltest
|
||||
- prealloc
|
||||
- predeclared
|
||||
- protogetter
|
||||
- scopelint
|
||||
- recvcheck
|
||||
- revive
|
||||
- tagalign
|
||||
- tagliatelle
|
||||
- testpackage
|
||||
- usetesting # go 1.24
|
||||
- varnamelen
|
||||
- whitespace
|
||||
- wrapcheck
|
||||
- wsl
|
||||
|
||||
issues:
|
||||
exclude-dirs:
|
||||
- internal/gen
|
||||
- cmd/dev
|
||||
- repos
|
||||
exclusions:
|
||||
generated: lax
|
||||
presets:
|
||||
- comments
|
||||
- common-false-positives
|
||||
- legacy
|
||||
- std-error-handling
|
||||
paths:
|
||||
- internal/gen
|
||||
- internal/db/olddb
|
||||
- cmd/dev
|
||||
- repos
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
- _test\.go$
|
||||
formatters:
|
||||
enable:
|
||||
- gofumpt
|
||||
exclusions:
|
||||
generated: lax
|
||||
paths:
|
||||
- internal/gen
|
||||
- cmd/dev
|
||||
- repos
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
|
||||
@@ -13,119 +13,112 @@
|
||||
# contents of this file.
|
||||
#
|
||||
|
||||
Aaron Bieber (qbit) <qbit@deftly.net>
|
||||
Jakob Borg (calmh) <jakob@nym.se> <jakob@kastelo.net> <jborg@coreweave.com>
|
||||
Audrius Butkevicius (AudriusButkevicius) <audrius.butkevicius@gmail.com> <github@audrius.rocks>
|
||||
Simon Frei (imsodin) <freisim93@gmail.com>
|
||||
Tomasz Wilczyński <5626656+tomasz1986@users.noreply.github.com> <twilczynski@naver.com>
|
||||
Alexander Graf (alex2108) <register-github@alex-graf.de>
|
||||
Alexandre Viau (aviau) <alexandre@alexandreviau.net> <aviau@debian.org>
|
||||
Anderson Mesquita (andersonvom) <andersonvom@gmail.com>
|
||||
André Colomb (acolomb) <src@andre.colomb.de> <github.com@andre.colomb.de>
|
||||
Antony Male (canton7) <antony.male@gmail.com>
|
||||
Ben Schulz (uok) <ueomkail@gmail.com> <uok@users.noreply.github.com>
|
||||
bt90 <btom1990@googlemail.com>
|
||||
Caleb Callaway (cqcallaw) <enlightened.despot@gmail.com>
|
||||
Daniel Harte (norgeous) <daniel@harte.me> <daniel@danielharte.co.uk> <norgeous@users.noreply.github.com>
|
||||
Emil Lundberg <emil@emlun.se>
|
||||
Eric P <eric@kastelo.net>
|
||||
Evgeny Kuznetsov <evgeny@kuznetsov.md>
|
||||
greatroar <61184462+greatroar@users.noreply.github.com>
|
||||
Lars K.W. Gohlke (lkwg82) <lkwg82@gmx.de>
|
||||
Lode Hoste (Zillode) <zillode@zillode.be>
|
||||
Michael Ploujnikov (plouj) <ploujj@gmail.com>
|
||||
Ross Smith II (rasa) <ross@smithii.com>
|
||||
Stefan Tatschner (rumpelsepp) <stefan@sevenbyte.org> <rumpelsepp@sevenbyte.org> <stefan@rumpelsepp.org>
|
||||
Wulf Weich (wweich) <wweich@users.noreply.github.com> <wweich@gmx.de> <wulf@weich-kr.de>
|
||||
Adam Piggott (ProactiveServices) <aD@simplypeachy.co.uk> <simplypeachy@users.noreply.github.com> <ProactiveServices@users.noreply.github.com> <adam@proactiveservices.co.uk>
|
||||
Adel Qalieh (adelq) <aqalieh95@gmail.com> <adelq@users.noreply.github.com>
|
||||
Alan Pope <alan@popey.com>
|
||||
Alberto Donato <albertodonato@users.noreply.github.com>
|
||||
Aleksey Vasenev <margtu-fivt@ya.ru>
|
||||
Alessandro G. (alessandro.g89) <alessandro.g89@gmail.com>
|
||||
Alex Ionescu <github@ionescu.sh>
|
||||
Alex Lindeman <139387+aelindeman@users.noreply.github.com>
|
||||
Alex Xu <alex.hello71@gmail.com>
|
||||
Alexander Graf (alex2108) <register-github@alex-graf.de>
|
||||
Alexander Seiler <seileralex@gmail.com>
|
||||
Alexandre Alves <alexandrealvesdb.contact@gmail.com>
|
||||
Alexandre Viau (aviau) <alexandre@alexandreviau.net> <aviau@debian.org>
|
||||
Aman Gupta <aman@tmm1.net>
|
||||
Anatoli Babenia <anatoli@rainforce.org>
|
||||
Anderson Mesquita (andersonvom) <andersonvom@gmail.com>
|
||||
Andreas Sommer <andreas.sommer87@googlemail.com>
|
||||
andresvia <andres.via@gmail.com>
|
||||
Andrew Dunham (andrew-d) <andrew@du.nham.ca>
|
||||
Andrew Meyer <andrewm.bpi@gmail.com>
|
||||
Andrew Rabert (nvllsvm) <ar@nullsum.net> <6550543+nvllsvm@users.noreply.github.com>
|
||||
Andrey D (scienmind) <scintertech@cryptolab.net> <scienmind@users.noreply.github.com>
|
||||
André Colomb (acolomb) <src@andre.colomb.de> <github.com@andre.colomb.de>
|
||||
andyleap <andyleap@gmail.com>
|
||||
Anjan Momi <anjan@momi.ca>
|
||||
Anthony Goeckner <agoeckner@users.noreply.github.com>
|
||||
Antoine Lamielle (0x010C) <antoine.lamielle@0x010c.fr> <gh@0x010c.fr>
|
||||
Antony Male (canton7) <antony.male@gmail.com>
|
||||
Anur <anurnomeru@163.com>
|
||||
Aranjedeath <Aranjedeath@users.noreply.github.com>
|
||||
ardevd <ardevd@users.noreply.github.com>
|
||||
Arkadiusz Tymiński <gevleeog@gmail.com>
|
||||
Aroun <login@b-vo.fr>
|
||||
Arthur Axel fREW Schmidt (frioux) <frew@afoolishmanifesto.com> <frioux@gmail.com>
|
||||
Artur Zubilewicz <AkaZecik@users.noreply.github.com>
|
||||
Audrius Butkevicius (AudriusButkevicius) <audrius.butkevicius@gmail.com> <github@audrius.rocks>
|
||||
Ashish Bhate <bhate.ashish@gmail.com>
|
||||
Aurélien Rainone <476650+arl@users.noreply.github.com>
|
||||
BAHADIR YILMAZ <bahadiryilmaz32@gmail.com>
|
||||
Bart De Vries (mogwa1) <devriesb@gmail.com>
|
||||
Beat Reichenbach <44111292+beatreichenbach@users.noreply.github.com>
|
||||
Ben Curthoys (bencurthoys) <ben@bencurthoys.com>
|
||||
Ben Schulz (uok) <ueomkail@gmail.com> <uok@users.noreply.github.com>
|
||||
Ben Shepherd (benshep) <bjashepherd@gmail.com>
|
||||
Ben Sidhom (bsidhom) <bsidhom@gmail.com>
|
||||
Benedikt Heine (bebehei) <bebe@bebehei.de>
|
||||
Benedikt Morbach <benedikt.morbach@googlemail.com>
|
||||
Benjamin Nater <17193640+bn4t@users.noreply.github.com>
|
||||
Benno Fünfstück <benno.fuenfstueck@gmail.com>
|
||||
Benny Ng (tpng) <benny.tpng@gmail.com>
|
||||
boomsquared <54829195+boomsquared@users.noreply.github.com>
|
||||
Boqin Qin <bobbqqin@bupt.edu.cn>
|
||||
Boris Rybalkin <ribalkin@gmail.com>
|
||||
Brandon Philips (philips) <brandon@ifup.org>
|
||||
Brendan Long (brendanlong) <self@brendanlong.com>
|
||||
Brian R. Becker (brbecker) <brbecker@gmail.com>
|
||||
bt90 <btom1990@googlemail.com>
|
||||
Caleb Callaway (cqcallaw) <enlightened.despot@gmail.com>
|
||||
Carsten Hagemann (carstenhag) <moter8@gmail.com> <carsten@chagemann.de>
|
||||
Catfriend1 <16361913+Catfriend1@users.noreply.github.com>
|
||||
Cathryne Linenweaver (Cathryne) <cathryne.linenweaver@gmail.com> <Cathryne@users.noreply.github.com> <katrinleinweber@MAC.local>
|
||||
Cedric Staniewski (xduugu) <cedric@gmx.ca>
|
||||
chenrui <rui@meetup.com>
|
||||
Chih-Hsuan Yen <yan12125@gmail.com> <1937689+yan12125@users.noreply.github.com>
|
||||
Choongkyu <choongkyu.kim+gh@gmail.com> <vapidlyrapid+gh@gmail.com>
|
||||
Chris Howie (cdhowie) <me@chrishowie.com>
|
||||
Chris Joel (cdata) <chris@scriptolo.gy>
|
||||
Chris Tonkinson <chris@masterbran.ch>
|
||||
Christian Kujau <ckujau@users.noreply.github.com>
|
||||
Christian Prescott <me@christianprescott.com>
|
||||
chucic <chucic@seznam.cz>
|
||||
cjc7373 <niuchangcun@gmail.com>
|
||||
Colin Kennedy (moshen) <moshen.colin@gmail.com>
|
||||
Cromefire_ <tim.l@nghorst.net> <26320625+cromefire@users.noreply.github.com>
|
||||
cui fliter <imcusg@gmail.com>
|
||||
Cyprien Devillez <cypx@users.noreply.github.com>
|
||||
d-volution <49024624+d-volution@users.noreply.github.com>
|
||||
Dale Visser <dale.visser@live.com>
|
||||
Dan <benda.daniel@gmail.com>
|
||||
Daniel Barczyk <46358936+DanielBarczyk@users.noreply.github.com>
|
||||
Daniel Bergmann (brgmnn) <dan.arne.bergmann@gmail.com> <brgmnn@users.noreply.github.com>
|
||||
Daniel Harte (norgeous) <daniel@harte.me> <daniel@danielharte.co.uk> <norgeous@users.noreply.github.com>
|
||||
Daniel Martí (mvdan) <mvdan@mvdan.cc>
|
||||
Daniel Padrta <64928366+danpadcz@users.noreply.github.com>
|
||||
Darshil Chanpura (dtchanpura) <dtchanpura@gmail.com> <dcprime314@gmail.com>
|
||||
dashangcun <907225865@qq.com>
|
||||
David Rimmer (dinosore) <dinosore@dbrsoftware.co.uk>
|
||||
deepsource-autofix[bot] <62050782+deepsource-autofix[bot]@users.noreply.github.com>
|
||||
DeflateAwning <11021263+DeflateAwning@users.noreply.github.com>
|
||||
Denis A. (dva) <denisva@gmail.com>
|
||||
Dennis Wilson (snnd) <dw@risu.io>
|
||||
dependabot-preview[bot] <dependabot-preview[bot]@users.noreply.github.com> <27856297+dependabot-preview[bot]@users.noreply.github.com>
|
||||
dependabot[bot] <dependabot[bot]@users.noreply.github.com> <49699333+dependabot[bot]@users.noreply.github.com>
|
||||
derekriemer <derek.riemer@colorado.edu>
|
||||
DerRockWolf <50499906+DerRockWolf@users.noreply.github.com>
|
||||
desbma <desbma@users.noreply.github.com>
|
||||
Devon G. Redekopp <devon@redekopp.com>
|
||||
diemade <spamkill@posteo.ch>
|
||||
digital <didev@dinid.net>
|
||||
Dimitri Papadopoulos Orfanos <3234522+DimitriPapadopoulos@users.noreply.github.com>
|
||||
Dmitry Saveliev (dsaveliev) <d.e.saveliev@gmail.com>
|
||||
domain <32405309+szu17dmy@users.noreply.github.com>
|
||||
Domenic Horner <domenic@tgxn.net>
|
||||
Dominik Heidler (asdil12) <dominik@heidler.eu>
|
||||
Elias Jarlebring (jarlebring) <jarlebring@gmail.com>
|
||||
Elliot Huffman <thelich2@gmail.com>
|
||||
Emil Hessman (ceh) <emil@hessman.se>
|
||||
Emil Lundberg <emil@emlun.se>
|
||||
Eng Zer Jun <engzerjun@gmail.com>
|
||||
entity0xfe <109791748+entity0xfe@users.noreply.github.com> <entity0xfe@my.domain>
|
||||
Eric Lesiuta <elesiuta@gmail.com>
|
||||
Eric P <eric@kastelo.net>
|
||||
Erik Meitner (WSGCSysadmin) <e.meitner@willystreet.coop>
|
||||
Evan Spensley <94762716+0evan@users.noreply.github.com>
|
||||
Evgeny Kuznetsov <evgeny@kuznetsov.md>
|
||||
Federico Castagnini (facastagnini) <federico.castagnini@gmail.com>
|
||||
Felix <53702818+f-eliks@users.noreply.github.com>
|
||||
Felix Ableitner (Nutomic) <me@nutomic.com>
|
||||
@@ -139,7 +132,6 @@ ghjklw <malo@jaffre.info>
|
||||
Gilli Sigurdsson (gillisig) <gilli@vx.is>
|
||||
Gleb Sinyavskiy <zhulik.gleb@gmail.com>
|
||||
Graham Miln (grahammiln) <graham.miln@dssw.co.uk> <graham.miln@miln.eu>
|
||||
greatroar <61184462+greatroar@users.noreply.github.com>
|
||||
Greg <gco@jazzhaiku.com>
|
||||
guangwu <guoguangwu@magic-shield.com>
|
||||
gudvinr <gudvinr@gmail.com>
|
||||
@@ -147,56 +139,42 @@ Gusted <postmaster@gusted.xyz> <williamzijl7@hotmail.com>
|
||||
Han Boetes <han@boetes.org>
|
||||
HansK-p <42314815+HansK-p@users.noreply.github.com>
|
||||
Harrison Jones (harrisonhjones) <harrisonhjones@users.noreply.github.com>
|
||||
Hazem Krimi <me@hazemkrimi.tech>
|
||||
Heiko Zuerker (Smiley73) <heiko@zuerker.org>
|
||||
Hireworks <129852174+hireworksltd@users.noreply.github.com>
|
||||
Hugo Locurcio <hugo.locurcio@hugo.pro>
|
||||
Iain Barnett <iainspeed@gmail.com>
|
||||
Ian Johnson (anonymouse64) <ian.johnson@canonical.com> <person.uwsome@gmail.com>
|
||||
ignacy123 <ignacy.buczek@onet.pl>
|
||||
Ikko Ashimine <eltociear@gmail.com>
|
||||
Ilya Brin <464157+ilyabrin@users.noreply.github.com>
|
||||
Iskander Sharipov (Alex) <quasilyte@gmail.com>
|
||||
Jaakko Hannikainen (jgke) <jgke@jgke.fi>
|
||||
Jacek Szafarkiewicz (hadogenes) <szafar@linux.pl>
|
||||
Jack Croft <jccroft1@users.noreply.github.com>
|
||||
Jacob <jyundt@gmail.com>
|
||||
Jake Peterson (acogdev) <jake@acogdev.com>
|
||||
Jakob Borg (calmh) <jakob@nym.se> <jakob@kastelo.net> <jborg@coreweave.com>
|
||||
James O'Beirne <wild-github@au92.org>
|
||||
James Patterson (jpjp) <jamespatterson@operamail.com> <jpjp@users.noreply.github.com>
|
||||
janost <janost@tuta.io>
|
||||
Jaroslav Lichtblau <svetlemodry@users.noreply.github.com>
|
||||
Jaroslav Malec (dzarda) <dzardacz@gmail.com>
|
||||
jaseg <githubaccount@jaseg.net>
|
||||
Jaspitta <ste.scarpitta@gmail.com>
|
||||
Jauder Ho <jauderho@users.noreply.github.com>
|
||||
Jaya Chithra (jayachithra) <s.k.jayachithra@gmail.com>
|
||||
Jaya Kumar <jaya.kumar@ict.nl>
|
||||
Jeffery To <jeffery.to@gmail.com>
|
||||
jelle van der Waa <jelle@vdwaa.nl>
|
||||
Jens Diemer (jedie) <github.com@jensdiemer.de> <git@jensdiemer.de>
|
||||
Jerry Jacobs (xor-gate) <jerry.jacobs@xor-gate.org> <xor-gate@users.noreply.github.com>
|
||||
Jesse Lucas <jesse@jesselucas.com>
|
||||
Jochen Voss (seehuhn) <voss@seehuhn.de>
|
||||
Johan Andersson <j@i19.se>
|
||||
Johan Vromans (sciurius) <jvromans@squirrel.nl>
|
||||
John Rinehart (fuzzybear3965) <johnrichardrinehart@gmail.com>
|
||||
Jonas Thelemann <e-mail@jonas-thelemann.de>
|
||||
Jonathan <artback@protonmail.com> <jonagn@gmail.com>
|
||||
Jonathan Cross <jcross@gmail.com>
|
||||
Jonta <359397+Jonta@users.noreply.github.com>
|
||||
Jose Manuel Delicado (jmdaweb) <jmdaweb@hotmail.com> <jmdaweb@users.noreply.github.com>
|
||||
jtagcat <git-514635f7@jtag.cat> <git-12dbd862@jtag.cat>
|
||||
Julian Lehrhuber <jul13579@users.noreply.github.com>
|
||||
Jörg Thalheim <Mic92@users.noreply.github.com>
|
||||
Jędrzej Kula <kula.jedrek@gmail.com>
|
||||
K.B.Dharun Krishna <kbdharunkrishna@gmail.com>
|
||||
Kalle Laine <pahakalle@protonmail.com>
|
||||
Kapil Sareen <kapilsareen584@gmail.com>
|
||||
Karol Różycki (krozycki) <rozycki.karol@gmail.com>
|
||||
Kebin Liu <lkebin@gmail.com>
|
||||
Keith Harrison <keithh@protonmail.com>
|
||||
Keith Turner <kturner@apache.org>
|
||||
Kelong Cong (kc1212) <kc04bc@gmx.com> <kc1212@users.noreply.github.com>
|
||||
Ken'ichi Kamada (kamadak) <kamada@nanohz.org>
|
||||
Kevin Allen (ironmig) <kma1660@gmail.com>
|
||||
@@ -205,30 +183,24 @@ Kevin White, Jr. (kwhite17) <kevinwhite1710@gmail.com>
|
||||
klemens <ka7@github.com>
|
||||
Kurt Fitzner (Kudalufi) <kurt@va1der.ca> <kurt.fitzner@gmail.com>
|
||||
kylosus <33132401+kylosus@users.noreply.github.com>
|
||||
Lars K.W. Gohlke (lkwg82) <lkwg82@gmx.de>
|
||||
Lars Lehtonen <lars.lehtonen@gmail.com>
|
||||
Laurent Arnoud <laurent@spkdev.net>
|
||||
Laurent Etiemble (letiemble) <laurent.etiemble@gmail.com> <laurent.etiemble@monobjc.net>
|
||||
Leo Arias (elopio) <yo@elopio.net>
|
||||
Liu Siyuan (liusy182) <liusy182@gmail.com> <liusy182@hotmail.com>
|
||||
Lode Hoste (Zillode) <zillode@zillode.be>
|
||||
Lord Landon Agahnim (LordLandon) <lordlandon@gmail.com>
|
||||
LSmithx2 <42276854+lsmithx2@users.noreply.github.com>
|
||||
luchenhan <168071714+luchenhan@users.noreply.github.com>
|
||||
Lukas Lihotzki <lukas@lihotzki.de>
|
||||
Luke Hamburg <1992842+luckman212@users.noreply.github.com>
|
||||
luzpaz <luzpaz@users.noreply.github.com>
|
||||
Majed Abdulaziz (majedev) <majed.alhajry@gmail.com>
|
||||
Marc Laporte (marclaporte) <marc@marclaporte.com> <marc@laporte.name>
|
||||
Marc Pujol (kilburn) <kilburn@la3.org>
|
||||
Marcel Meyer <mm.marcelmeyer@gmail.com>
|
||||
Marcin Dziadus (marcindziadus) <dziadus.marcin@gmail.com>
|
||||
marco-m <marco.molteni@laposte.net>
|
||||
Marcus B Spencer <marcus@marcusspencer.xyz>
|
||||
Marcus B Spencer <marcus@marcusspencer.xyz> <marcus@marcusspencer.us>
|
||||
Marcus Legendre <marcus.legendre@gmail.com>
|
||||
Mario Majila <mariustshipichik@gmail.com>
|
||||
Mark Pulford (mpx) <mark@kyne.com.au>
|
||||
Martchus <martchus@gmx.net>
|
||||
Martin Polehla <p0l0us@users.noreply.github.com>
|
||||
Mateusz Naściszewski (mateon1) <matin1111@wp.pl>
|
||||
Mateusz Ż <thedead4fun@live.com>
|
||||
mathias4833 <67101597+mathias4833@users.noreply.github.com>
|
||||
@@ -239,15 +211,10 @@ Matteo Ruina <matteo.ruina@gmail.com>
|
||||
Maurizio Tomasi <ziotom78@gmail.com>
|
||||
Max <github@germancoding.com>
|
||||
Max Schulze (kralo) <max.schulze@online.de> <kralo@users.noreply.github.com>
|
||||
maxice8 <30738253+maxice8@users.noreply.github.com>
|
||||
MaximAL <almaximal@ya.ru>
|
||||
Maxime Thirouin <m@moox.io>
|
||||
Maximilian <maxi.rostock@outlook.de> <public@complexvector.space>
|
||||
mclang <1721600+mclang@users.noreply.github.com>
|
||||
Michael Jephcote (Rewt0r) <rewt0r@gmx.com> <Rewt0r@users.noreply.github.com>
|
||||
Michael Ploujnikov (plouj) <ploujj@gmail.com>
|
||||
Michael Rienstra <mrienstra@gmail.com>
|
||||
Michael Tilli (pyfisch) <pyfisch@gmail.com>
|
||||
MichaIng <micha@dietpi.com>
|
||||
Migelo <miha@filetki.si>
|
||||
Mike Boone <mike@boonedocks.net>
|
||||
@@ -256,7 +223,6 @@ MikolajTwarog <43782609+MikolajTwarog@users.noreply.github.com>
|
||||
Mingxuan Lin <gdlmx@users.noreply.github.com>
|
||||
mv1005 <49659413+mv1005@users.noreply.github.com>
|
||||
Nate Morrison (nrm21) <natemorrison@gmail.com>
|
||||
Naveen <172697+naveensrinivasan@users.noreply.github.com>
|
||||
nf <nf@wh3rd.net>
|
||||
Nicholas Rishel (PrototypeNM1) <rishel.nick@gmail.com> <PrototypeNM1@users.noreply.github.com>
|
||||
Nick Busey <NickBusey@users.noreply.github.com>
|
||||
@@ -271,14 +237,13 @@ NoLooseEnds <jon.koslung@gmail.com>
|
||||
Oliver Freyermuth <o.freyermuth@googlemail.com>
|
||||
orangekame3 <miya.org.0309@gmail.com>
|
||||
otbutz <tbutz@optitool.de>
|
||||
Otiel <Otiel@users.noreply.github.com>
|
||||
overkill <22098433+0verk1ll@users.noreply.github.com>
|
||||
Oyebanji Jacob Mayowa <oyebanji05@gmail.com>
|
||||
Pablo <pbaeyens31+github@gmail.com>
|
||||
Pascal Jungblut (pascalj) <github@pascalj.com> <mail@pascal-jungblut.com>
|
||||
Paul Brit <paulbrit44@gmail.com>
|
||||
Paul Donald <newtwen+github@gmail.com>
|
||||
Pawel Palenica (qepasa) <pawelpalenica11@gmail.com>
|
||||
Paweł Rozlach <vespian@users.noreply.github.com>
|
||||
perewa <cavalcante.ten@gmail.com>
|
||||
Peter Badida <KeyWeeUsr@users.noreply.github.com>
|
||||
Peter Dave Hello <hsu@peterdavehello.org>
|
||||
@@ -288,19 +253,16 @@ Phani Rithvij <phanirithvij2000@gmail.com>
|
||||
Phil Davis <phil.davis@inf.org>
|
||||
Philippe Schommers (filoozoom) <philippe@schommers.be>
|
||||
Phill Luby (pluby) <phill.luby@newredo.com>
|
||||
Pier Paolo Ramon <ramonpierre@gmail.com>
|
||||
Piotr Bejda (piobpl) <piotrb10@gmail.com>
|
||||
polyfloyd <polyfloyd@users.noreply.github.com>
|
||||
Pramodh KP (pramodhkp) <pramodh.p@directi.com> <1507241+pramodhkp@users.noreply.github.com>
|
||||
pullmerge <166967364+pullmerge@users.noreply.github.com>
|
||||
Quentin Hibon <qh.public@yahoo.com>
|
||||
Rahmi Pruitt <rjpruitt16@gmail.com>
|
||||
red_led <red-led@users.noreply.github.com>
|
||||
Richard Hartmann <RichiH@users.noreply.github.com>
|
||||
Robert Carosi (nov1n) <robert@carosi.nl>
|
||||
Roberto Santalla <roobre@users.noreply.github.com>
|
||||
Robin Schoonover <robin@cornhooves.org>
|
||||
Roman Zaynetdinov (zaynetro) <romanznet@gmail.com>
|
||||
Ross Smith II (rasa) <ross@smithii.com>
|
||||
rubenbe <github-com-00ff86@vandamme.email>
|
||||
Ruslan Yevdokymov <38809160+ruslanye@users.noreply.github.com>
|
||||
Ryan Qian <i@bitbili.net>
|
||||
@@ -312,33 +274,27 @@ Sergey Mishin (ralder) <ralder@yandex.ru>
|
||||
Sertonix <83883937+Sertonix@users.noreply.github.com>
|
||||
Severin von Wnuck-Lipinski <ss7@live.de>
|
||||
Shaarad Dalvi <60266155+shaaraddalvi@users.noreply.github.com> <shdalv@microsoft.com>
|
||||
Simon Frei (imsodin) <freisim93@gmail.com>
|
||||
Simon Mwepu <simonmwepu@gmail.com>
|
||||
Simon Pickup <simon@pickupinfinity.com>
|
||||
Sly_tom_cat <slytomcat@mail.ru>
|
||||
Sonu Kumar Saw <31889738+dev-saw99@users.noreply.github.com>
|
||||
Stefan Kuntz (Stefan-Code) <stefan.github@gmail.com> <Stefan.github@gmail.com>
|
||||
Stefan Tatschner (rumpelsepp) <stefan@sevenbyte.org> <rumpelsepp@sevenbyte.org> <stefan@rumpelsepp.org>
|
||||
Steven Eckhoff <steven.eckhoff.opensource@gmail.com>
|
||||
Suhas Gundimeda (snugghash) <suhas.gundimeda@gmail.com> <snugghash@gmail.com>
|
||||
Sven Bachmann <dev@mcbachmann.de>
|
||||
Syncthing Automation <automation@syncthing.net>
|
||||
Syncthing Release Automation <release@syncthing.net>
|
||||
Sébastien WENSKE <sebastien@wenske.fr>
|
||||
Taylor Khan (nelsonkhan) <nelsonkhan@gmail.com>
|
||||
Terrance <git@terrance.allofti.me>
|
||||
TheCreeper <TheCreeper@users.noreply.github.com>
|
||||
Thomas <9749173+uhthomas@users.noreply.github.com>
|
||||
Thomas Hipp <thomashipp@gmail.com>
|
||||
Tim Abell (timabell) <tim@timwise.co.uk>
|
||||
Tim Howes (timhowes) <timhowes@berkeley.edu>
|
||||
Tim Nordenfur <tim@gurka.se>
|
||||
Tobias Frölich <40638719+tobifroe@users.noreply.github.com>
|
||||
Tobias Klauser <tobias.klauser@gmail.com>
|
||||
Tobias Nygren (tnn2) <tnn@nygren.pp.se>
|
||||
Tobias Tom (tobiastom) <t.tom@succont.de>
|
||||
Tom Jakubowski <tom@crystae.net>
|
||||
Tomasz Wilczyński <5626656+tomasz1986@users.noreply.github.com> <twilczynski@naver.com>
|
||||
Tommy Thorn <tommy-github-email@thorn.ws>
|
||||
Tommy van der Vorst <tommy-github@pixelspark.nl> <tommy@pixelspark.nl>
|
||||
Tully Robinson (tojrobinson) <tully@tojr.org>
|
||||
Tyler Brazier (tylerbrazier) <tyler@tylerbrazier.com>
|
||||
@@ -356,10 +312,9 @@ WangXi <xib1102@icloud.com>
|
||||
Will Rouesnel <wrouesnel@wrouesnel.com>
|
||||
William A. Kennington III (wkennington) <william@wkennington.com>
|
||||
wouter bolsterlee <wouter@bolsterl.ee>
|
||||
Wulf Weich (wweich) <wweich@users.noreply.github.com> <wweich@gmx.de> <wulf@weich-kr.de>
|
||||
xarx00 <xarx00@users.noreply.github.com>
|
||||
Xavier O. (damajor) <damajor@gmail.com>
|
||||
xjtdy888 (xjtdy888) <xjtdy888@163.com>
|
||||
xjtdy888 (xjtdy888) <xjtdy888@163.com> <xjtdy888@gmail.com>
|
||||
Yannic A. (eipiminus1) <eipiminusone+github@gmail.com> <eipiminus1@users.noreply.github.com>
|
||||
佛跳墙 <daoquan@qq.com>
|
||||
落心 <luoxin.ttt@gmail.com>
|
||||
|
||||
@@ -329,7 +329,7 @@ func runCommand(cmd string, target target) {
|
||||
writeCompatJSON()
|
||||
|
||||
case "deb":
|
||||
buildDeb(target)
|
||||
buildDeb(target, tags)
|
||||
|
||||
case "vet":
|
||||
metalintShort()
|
||||
@@ -592,7 +592,7 @@ func buildZip(target target, tags []string) {
|
||||
fmt.Println(filename)
|
||||
}
|
||||
|
||||
func buildDeb(target target) {
|
||||
func buildDeb(target target, tags []string) {
|
||||
os.RemoveAll("deb")
|
||||
|
||||
// "goarch" here is set to whatever the Debian packages expect. We correct
|
||||
@@ -606,7 +606,7 @@ func buildDeb(target target) {
|
||||
goarch = "arm"
|
||||
}
|
||||
|
||||
build(target, []string{"noupgrade"})
|
||||
build(target, append(tags, "noupgrade"))
|
||||
|
||||
for i := range target.installationFiles {
|
||||
target.installationFiles[i].src = strings.Replace(target.installationFiles[i].src, "{{binary}}", target.BinaryName(), 1)
|
||||
@@ -922,6 +922,9 @@ func rmr(paths ...string) {
|
||||
}
|
||||
|
||||
func getReleaseVersion() (string, error) {
|
||||
if ver := os.Getenv("VERSION"); ver != "" {
|
||||
return strings.TrimSpace(ver), nil
|
||||
}
|
||||
bs, err := os.ReadFile("RELEASE")
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -966,7 +969,7 @@ func getGitVersion() (string, error) {
|
||||
}
|
||||
|
||||
func getVersion() string {
|
||||
// First try for a RELEASE file,
|
||||
// First try for a RELEASE file or $VERSION env var,
|
||||
if ver, err := getReleaseVersion(); err == nil {
|
||||
return ver
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ case "${1:-default}" in
|
||||
|
||||
prerelease)
|
||||
script authors
|
||||
script copyrights
|
||||
build weblate
|
||||
pushd man ; ./refresh.sh ; popd
|
||||
git add -A gui man AUTHORS
|
||||
|
||||
@@ -8,6 +8,7 @@ package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"cmp"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"io"
|
||||
@@ -15,7 +16,7 @@ import (
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"slices"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -177,8 +178,8 @@ func (d *diskStore) inventory() error {
|
||||
})
|
||||
return nil
|
||||
})
|
||||
sort.Slice(d.currentFiles, func(i, j int) bool {
|
||||
return d.currentFiles[i].mtime < d.currentFiles[j].mtime
|
||||
slices.SortFunc(d.currentFiles, func(a, b currentFile) int {
|
||||
return cmp.Compare(a.mtime, b.mtime)
|
||||
})
|
||||
var oldest time.Duration
|
||||
if len(d.currentFiles) > 0 {
|
||||
|
||||
@@ -29,6 +29,7 @@ import (
|
||||
_ "github.com/syncthing/syncthing/lib/automaxprocs"
|
||||
"github.com/syncthing/syncthing/lib/geoip"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
"github.com/syncthing/syncthing/lib/rand"
|
||||
"github.com/syncthing/syncthing/lib/relay/client"
|
||||
"github.com/syncthing/syncthing/lib/sync"
|
||||
"github.com/syncthing/syncthing/lib/tlsutil"
|
||||
@@ -110,6 +111,7 @@ var (
|
||||
requestProcessors = 8
|
||||
geoipLicenseKey = os.Getenv("GEOIP_LICENSE_KEY")
|
||||
geoipAccountID, _ = strconv.Atoi(os.Getenv("GEOIP_ACCOUNT_ID"))
|
||||
maxRelaysReturned = 100
|
||||
|
||||
requests chan request
|
||||
|
||||
@@ -141,6 +143,7 @@ func main() {
|
||||
flag.IntVar(&requestQueueLen, "request-queue", requestQueueLen, "Queue length for incoming test requests")
|
||||
flag.IntVar(&requestProcessors, "request-processors", requestProcessors, "Number of request processor routines")
|
||||
flag.StringVar(&geoipLicenseKey, "geoip-license-key", geoipLicenseKey, "License key for GeoIP database")
|
||||
flag.IntVar(&maxRelaysReturned, "max-relays-returned", maxRelaysReturned, "Maximum number of relays returned for a normal endpoint query")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
@@ -331,6 +334,10 @@ func handleEndpointShort(rw http.ResponseWriter, r *http.Request) {
|
||||
relays = append(relays, relayShort{URL: slimURL(r.URL)})
|
||||
}
|
||||
mut.RUnlock()
|
||||
if len(relays) > maxRelaysReturned {
|
||||
rand.Shuffle(relays)
|
||||
relays = relays[:maxRelaysReturned]
|
||||
}
|
||||
|
||||
_ = json.NewEncoder(rw).Encode(map[string][]relayShort{
|
||||
"relays": relays,
|
||||
@@ -613,7 +620,7 @@ func createTestCertificate() tls.Certificate {
|
||||
}
|
||||
|
||||
certFile, keyFile := filepath.Join(tmpDir, "cert.pem"), filepath.Join(tmpDir, "key.pem")
|
||||
cert, err := tlsutil.NewCertificate(certFile, keyFile, "relaypoolsrv", 20*365)
|
||||
cert, err := tlsutil.NewCertificate(certFile, keyFile, "relaypoolsrv", 20*365, false)
|
||||
if err != nil {
|
||||
log.Fatalln("Failed to create test X509 key pair:", err)
|
||||
}
|
||||
|
||||
@@ -173,7 +173,7 @@ func fetchStats(relay *relay) *stats {
|
||||
|
||||
var stats stats
|
||||
|
||||
if json.NewDecoder(response.Body).Decode(&stats); err != nil {
|
||||
if err := json.NewDecoder(response.Body).Decode(&stats); err != nil {
|
||||
return nil
|
||||
}
|
||||
return &stats
|
||||
|
||||
@@ -201,17 +201,21 @@ func (p *proxy) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
// looking for a prerelease at all.
|
||||
func filterForLatest(rels []upgrade.Release) []upgrade.Release {
|
||||
var filtered []upgrade.Release
|
||||
var havePre bool
|
||||
havePre := make(map[string]bool)
|
||||
haveStable := make(map[string]bool)
|
||||
for _, rel := range rels {
|
||||
if !rel.Prerelease {
|
||||
// We found a stable version, we're good now.
|
||||
major, _, _ := strings.Cut(rel.Tag, ".")
|
||||
if !rel.Prerelease && !haveStable[major] {
|
||||
// Remember the first non-pre for each major
|
||||
filtered = append(filtered, rel)
|
||||
break
|
||||
haveStable[major] = true
|
||||
continue
|
||||
}
|
||||
if rel.Prerelease && !havePre {
|
||||
// We remember the first prerelease we find.
|
||||
if rel.Prerelease && !havePre[major] && !haveStable[major] {
|
||||
// We remember the first prerelease we find, unless we've
|
||||
// already found a non-pre of the same major.
|
||||
filtered = append(filtered, rel)
|
||||
havePre = true
|
||||
havePre[major] = true
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
@@ -258,9 +262,10 @@ func filterForCompabitility(rels []upgrade.Release, ua, osv string) []upgrade.Re
|
||||
}
|
||||
|
||||
type cachedReleases struct {
|
||||
url string
|
||||
mut sync.RWMutex
|
||||
current []upgrade.Release
|
||||
url string
|
||||
mut sync.RWMutex
|
||||
current []upgrade.Release
|
||||
latestRel, latestPre string
|
||||
}
|
||||
|
||||
func (c *cachedReleases) Releases() []upgrade.Release {
|
||||
@@ -274,8 +279,26 @@ func (c *cachedReleases) Update(ctx context.Context) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
latestRel, latestPre := "", ""
|
||||
for _, rel := range rels {
|
||||
if !rel.Prerelease && latestRel == "" {
|
||||
latestRel = rel.Tag
|
||||
}
|
||||
if rel.Prerelease && latestPre == "" {
|
||||
latestPre = rel.Tag
|
||||
}
|
||||
if latestRel != "" && latestPre != "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
c.mut.Lock()
|
||||
c.current = rels
|
||||
if latestRel != c.latestRel || latestPre != c.latestPre {
|
||||
metricLatestReleaseInfo.DeleteLabelValues(c.latestRel, c.latestPre)
|
||||
metricLatestReleaseInfo.WithLabelValues(latestRel, latestPre).Set(1)
|
||||
c.latestRel = latestRel
|
||||
c.latestPre = latestPre
|
||||
}
|
||||
c.mut.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -27,4 +27,10 @@ var (
|
||||
Subsystem: "upgrade",
|
||||
Name: "http_requests",
|
||||
}, []string{"target", "result"})
|
||||
metricLatestReleaseInfo = promauto.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Namespace: "syncthing",
|
||||
Subsystem: "upgrade",
|
||||
Name: "latest_release_info",
|
||||
Help: "Release information",
|
||||
}, []string{"latest_release", "latest_pre"})
|
||||
)
|
||||
|
||||
@@ -26,9 +26,11 @@ import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"github.com/puzpuzpuz/xsync/v3"
|
||||
"github.com/syncthing/syncthing/internal/blob"
|
||||
"github.com/syncthing/syncthing/internal/blob/azureblob"
|
||||
"github.com/syncthing/syncthing/internal/blob/s3"
|
||||
"github.com/syncthing/syncthing/lib/build"
|
||||
"github.com/syncthing/syncthing/lib/geoip"
|
||||
"github.com/syncthing/syncthing/lib/s3"
|
||||
"github.com/syncthing/syncthing/lib/ur/contract"
|
||||
)
|
||||
|
||||
@@ -40,11 +42,15 @@ type CLI struct {
|
||||
DumpFile string `env:"UR_DUMP_FILE" default:"reports.jsons.gz"`
|
||||
DumpInterval time.Duration `env:"UR_DUMP_INTERVAL" default:"5m"`
|
||||
|
||||
S3Endpoint string `name:"s3-endpoint" hidden:"true" env:"UR_S3_ENDPOINT"`
|
||||
S3Region string `name:"s3-region" hidden:"true" env:"UR_S3_REGION"`
|
||||
S3Bucket string `name:"s3-bucket" hidden:"true" env:"UR_S3_BUCKET"`
|
||||
S3AccessKeyID string `name:"s3-access-key-id" hidden:"true" env:"UR_S3_ACCESS_KEY_ID"`
|
||||
S3SecretKey string `name:"s3-secret-key" hidden:"true" env:"UR_S3_SECRET_KEY"`
|
||||
S3Endpoint string `name:"s3-endpoint" env:"UR_S3_ENDPOINT"`
|
||||
S3Region string `name:"s3-region" env:"UR_S3_REGION"`
|
||||
S3Bucket string `name:"s3-bucket" env:"UR_S3_BUCKET"`
|
||||
S3AccessKeyID string `name:"s3-access-key-id" env:"UR_S3_ACCESS_KEY_ID"`
|
||||
S3SecretKey string `name:"s3-secret-key" env:"UR_S3_SECRET_KEY"`
|
||||
|
||||
AzureBlobAccount string `name:"azure-blob-account" env:"UR_AZUREBLOB_ACCOUNT"`
|
||||
AzureBlobKey string `name:"azure-blob-key" env:"UR_AZUREBLOB_KEY"`
|
||||
AzureBlobContainer string `name:"azure-blob-container" env:"UR_AZUREBLOB_CONTAINER"`
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -77,6 +83,7 @@ var (
|
||||
{regexp.MustCompile(`\ssyncthing@archlinux`), "Arch (3rd party)"},
|
||||
{regexp.MustCompile(`@debian`), "Debian (3rd party)"},
|
||||
{regexp.MustCompile(`@fedora`), "Fedora (3rd party)"},
|
||||
{regexp.MustCompile(`@openSUSE`), "openSUSE (3rd party)"},
|
||||
{regexp.MustCompile(`\sbrew@`), "Homebrew (3rd party)"},
|
||||
{regexp.MustCompile(`\sroot@buildkitsandbox`), "LinuxServer.io (3rd party)"},
|
||||
{regexp.MustCompile(`\sports@freebsd`), "FreeBSD (3rd party)"},
|
||||
@@ -119,19 +126,25 @@ func (cli *CLI) Run() error {
|
||||
go geo.Serve(context.TODO())
|
||||
}
|
||||
|
||||
// s3
|
||||
// Blob storage
|
||||
|
||||
var s3sess *s3.Session
|
||||
var blobs blob.Store
|
||||
if cli.S3Endpoint != "" {
|
||||
s3sess, err = s3.NewSession(cli.S3Endpoint, cli.S3Region, cli.S3Bucket, cli.S3AccessKeyID, cli.S3SecretKey)
|
||||
blobs, err = s3.NewSession(cli.S3Endpoint, cli.S3Region, cli.S3Bucket, cli.S3AccessKeyID, cli.S3SecretKey)
|
||||
if err != nil {
|
||||
slog.Error("Failed to create S3 session", "error", err)
|
||||
return err
|
||||
}
|
||||
} else if cli.AzureBlobAccount != "" {
|
||||
blobs, err = azureblob.NewBlobStore(cli.AzureBlobAccount, cli.AzureBlobKey, cli.AzureBlobContainer)
|
||||
if err != nil {
|
||||
slog.Error("Failed to create Azure blob store", "error", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := os.Stat(cli.DumpFile); err != nil && s3sess != nil {
|
||||
if err := cli.downloadDumpFile(s3sess); err != nil {
|
||||
if _, err := os.Stat(cli.DumpFile); err != nil && blobs != nil {
|
||||
if err := cli.downloadDumpFile(blobs); err != nil {
|
||||
slog.Error("Failed to download dump file", "error", err)
|
||||
}
|
||||
}
|
||||
@@ -153,7 +166,7 @@ func (cli *CLI) Run() error {
|
||||
|
||||
go func() {
|
||||
for range time.Tick(cli.DumpInterval) {
|
||||
if err := cli.saveDumpFile(srv, s3sess); err != nil {
|
||||
if err := cli.saveDumpFile(srv, blobs); err != nil {
|
||||
slog.Error("Failed to write dump file", "error", err)
|
||||
}
|
||||
}
|
||||
@@ -192,8 +205,8 @@ func (cli *CLI) Run() error {
|
||||
return metricsSrv.Serve(urListener)
|
||||
}
|
||||
|
||||
func (cli *CLI) downloadDumpFile(s3sess *s3.Session) error {
|
||||
latestKey, err := s3sess.LatestKey()
|
||||
func (cli *CLI) downloadDumpFile(blobs blob.Store) error {
|
||||
latestKey, err := blobs.LatestKey(context.Background())
|
||||
if err != nil {
|
||||
return fmt.Errorf("list latest S3 key: %w", err)
|
||||
}
|
||||
@@ -201,7 +214,7 @@ func (cli *CLI) downloadDumpFile(s3sess *s3.Session) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("create dump file: %w", err)
|
||||
}
|
||||
if err := s3sess.Download(fd, latestKey); err != nil {
|
||||
if err := blobs.Download(context.Background(), latestKey, fd); err != nil {
|
||||
_ = fd.Close()
|
||||
return fmt.Errorf("download dump file: %w", err)
|
||||
}
|
||||
@@ -212,7 +225,7 @@ func (cli *CLI) downloadDumpFile(s3sess *s3.Session) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cli *CLI) saveDumpFile(srv *server, s3sess *s3.Session) error {
|
||||
func (cli *CLI) saveDumpFile(srv *server, blobs blob.Store) error {
|
||||
fd, err := os.Create(cli.DumpFile + ".tmp")
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating dump file: %w", err)
|
||||
@@ -233,13 +246,13 @@ func (cli *CLI) saveDumpFile(srv *server, s3sess *s3.Session) error {
|
||||
}
|
||||
slog.Info("Dump file saved")
|
||||
|
||||
if s3sess != nil {
|
||||
if blobs != nil {
|
||||
key := fmt.Sprintf("reports-%s.jsons.gz", time.Now().UTC().Format("2006-01-02"))
|
||||
fd, err := os.Open(cli.DumpFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("opening dump file: %w", err)
|
||||
}
|
||||
if err := s3sess.Upload(fd, key); err != nil {
|
||||
if err := blobs.Upload(context.Background(), key, fd); err != nil {
|
||||
return fmt.Errorf("uploading dump file: %w", err)
|
||||
}
|
||||
_ = fd.Close()
|
||||
@@ -351,6 +364,9 @@ func (s *server) addReport(rep *contract.Report) bool {
|
||||
break
|
||||
}
|
||||
}
|
||||
rep.DistDist = rep.Distribution
|
||||
rep.DistOS = rep.OS
|
||||
rep.DistArch = rep.Arch
|
||||
|
||||
_, loaded := s.reports.LoadAndStore(rep.UniqueID, rep)
|
||||
return loaded
|
||||
|
||||
@@ -66,7 +66,7 @@ type contextKey int
|
||||
|
||||
const idKey contextKey = iota
|
||||
|
||||
func newAPISrv(addr string, cert tls.Certificate, db database, repl replicator, useHTTP, compression bool) *apiSrv {
|
||||
func newAPISrv(addr string, cert tls.Certificate, db database, repl replicator, useHTTP, compression bool, desiredNotFoundRate float64) *apiSrv {
|
||||
return &apiSrv{
|
||||
addr: addr,
|
||||
cert: cert,
|
||||
@@ -77,13 +77,13 @@ func newAPISrv(addr string, cert tls.Certificate, db database, repl replicator,
|
||||
seenTracker: &retryAfterTracker{
|
||||
name: "seenTracker",
|
||||
bucketStarts: time.Now(),
|
||||
desiredRate: 250,
|
||||
desiredRate: desiredNotFoundRate / 2,
|
||||
currentDelay: notFoundRetryUnknownMinSeconds,
|
||||
},
|
||||
notSeenTracker: &retryAfterTracker{
|
||||
name: "notSeenTracker",
|
||||
bucketStarts: time.Now(),
|
||||
desiredRate: 250,
|
||||
desiredRate: desiredNotFoundRate / 2,
|
||||
currentDelay: notFoundRetryUnknownMaxSeconds / 2,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -111,11 +111,11 @@ func BenchmarkAPIRequests(b *testing.B) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
go db.Serve(ctx)
|
||||
api := newAPISrv("127.0.0.1:0", tls.Certificate{}, db, nil, true, true)
|
||||
api := newAPISrv("127.0.0.1:0", tls.Certificate{}, db, nil, true, true, 1000)
|
||||
srv := httptest.NewServer(http.HandlerFunc(api.handler))
|
||||
|
||||
kf := b.TempDir() + "/cert"
|
||||
crt, err := tlsutil.NewCertificate(kf+".crt", kf+".key", "localhost", 7)
|
||||
crt, err := tlsutil.NewCertificate(kf+".crt", kf+".key", "localhost", 7, true)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
+16
-16
@@ -24,11 +24,11 @@ import (
|
||||
"github.com/puzpuzpuz/xsync/v3"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/syncthing/syncthing/internal/blob"
|
||||
"github.com/syncthing/syncthing/internal/gen/discosrv"
|
||||
"github.com/syncthing/syncthing/internal/protoutil"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
"github.com/syncthing/syncthing/lib/rand"
|
||||
"github.com/syncthing/syncthing/lib/s3"
|
||||
)
|
||||
|
||||
type clock interface {
|
||||
@@ -51,12 +51,12 @@ type inMemoryStore struct {
|
||||
m *xsync.MapOf[protocol.DeviceID, *discosrv.DatabaseRecord]
|
||||
dir string
|
||||
flushInterval time.Duration
|
||||
s3 *s3.Session
|
||||
blobs blob.Store
|
||||
objKey string
|
||||
clock clock
|
||||
}
|
||||
|
||||
func newInMemoryStore(dir string, flushInterval time.Duration, s3sess *s3.Session) *inMemoryStore {
|
||||
func newInMemoryStore(dir string, flushInterval time.Duration, blobs blob.Store) *inMemoryStore {
|
||||
hn, err := os.Hostname()
|
||||
if err != nil {
|
||||
hn = rand.String(8)
|
||||
@@ -65,25 +65,25 @@ func newInMemoryStore(dir string, flushInterval time.Duration, s3sess *s3.Sessio
|
||||
m: xsync.NewMapOf[protocol.DeviceID, *discosrv.DatabaseRecord](),
|
||||
dir: dir,
|
||||
flushInterval: flushInterval,
|
||||
s3: s3sess,
|
||||
blobs: blobs,
|
||||
objKey: hn + ".db",
|
||||
clock: defaultClock{},
|
||||
}
|
||||
nr, err := s.read()
|
||||
if os.IsNotExist(err) && s3sess != nil {
|
||||
// Try to read from AWS
|
||||
latestKey, cerr := s3sess.LatestKey()
|
||||
if os.IsNotExist(err) && blobs != nil {
|
||||
// Try to read from blob storage
|
||||
latestKey, cerr := blobs.LatestKey(context.Background())
|
||||
if cerr != nil {
|
||||
log.Println("Error reading database from S3:", err)
|
||||
log.Println("Error finding database from blob storage:", cerr)
|
||||
return s
|
||||
}
|
||||
fd, cerr := os.Create(path.Join(s.dir, "records.db"))
|
||||
if cerr != nil {
|
||||
log.Println("Error creating database file:", err)
|
||||
log.Println("Error creating database file:", cerr)
|
||||
return s
|
||||
}
|
||||
if cerr := s3sess.Download(fd, latestKey); cerr != nil {
|
||||
log.Printf("Error reading database from S3: %v", err)
|
||||
if cerr := blobs.Download(context.Background(), latestKey, fd); cerr != nil {
|
||||
log.Printf("Error downloading database from blob storage: %v", cerr)
|
||||
}
|
||||
_ = fd.Close()
|
||||
nr, err = s.read()
|
||||
@@ -310,16 +310,16 @@ func (s *inMemoryStore) write() (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
// Upload to S3
|
||||
if s.s3 != nil {
|
||||
// Upload to blob storage
|
||||
if s.blobs != nil {
|
||||
fd, err = os.Open(dbf)
|
||||
if err != nil {
|
||||
log.Printf("Error uploading database to S3: %v", err)
|
||||
log.Printf("Error uploading database to blob storage: %v", err)
|
||||
return nil
|
||||
}
|
||||
defer fd.Close()
|
||||
if err := s.s3.Upload(fd, s.objKey); err != nil {
|
||||
log.Printf("Error uploading database to S3: %v", err)
|
||||
if err := s.blobs.Upload(context.Background(), s.objKey, fd); err != nil {
|
||||
log.Printf("Error uploading database to blob storage: %v", err)
|
||||
}
|
||||
log.Println("Finished uploading database")
|
||||
}
|
||||
|
||||
+26
-17
@@ -21,11 +21,13 @@ import (
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"github.com/thejerf/suture/v4"
|
||||
|
||||
"github.com/syncthing/syncthing/internal/blob"
|
||||
"github.com/syncthing/syncthing/internal/blob/azureblob"
|
||||
"github.com/syncthing/syncthing/internal/blob/s3"
|
||||
_ "github.com/syncthing/syncthing/lib/automaxprocs"
|
||||
"github.com/syncthing/syncthing/lib/build"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
"github.com/syncthing/syncthing/lib/rand"
|
||||
"github.com/syncthing/syncthing/lib/s3"
|
||||
"github.com/syncthing/syncthing/lib/tlsutil"
|
||||
)
|
||||
|
||||
@@ -58,12 +60,13 @@ const (
|
||||
var debug = false
|
||||
|
||||
type CLI struct {
|
||||
Cert string `group:"Listen" help:"Certificate file" default:"./cert.pem" env:"DISCOVERY_CERT_FILE"`
|
||||
Key string `group:"Listen" help:"Key file" default:"./key.pem" env:"DISCOVERY_KEY_FILE"`
|
||||
HTTP bool `group:"Listen" help:"Listen on HTTP (behind an HTTPS proxy)" env:"DISCOVERY_HTTP"`
|
||||
Compression bool `group:"Listen" help:"Enable GZIP compression of responses" env:"DISCOVERY_COMPRESSION"`
|
||||
Listen string `group:"Listen" help:"Listen address" default:":8443" env:"DISCOVERY_LISTEN"`
|
||||
MetricsListen string `group:"Listen" help:"Metrics listen address" env:"DISCOVERY_METRICS_LISTEN"`
|
||||
Cert string `group:"Listen" help:"Certificate file" default:"./cert.pem" env:"DISCOVERY_CERT_FILE"`
|
||||
Key string `group:"Listen" help:"Key file" default:"./key.pem" env:"DISCOVERY_KEY_FILE"`
|
||||
HTTP bool `group:"Listen" help:"Listen on HTTP (behind an HTTPS proxy)" env:"DISCOVERY_HTTP"`
|
||||
Compression bool `group:"Listen" help:"Enable GZIP compression of responses" env:"DISCOVERY_COMPRESSION"`
|
||||
Listen string `group:"Listen" help:"Listen address" default:":8443" env:"DISCOVERY_LISTEN"`
|
||||
MetricsListen string `group:"Listen" help:"Metrics listen address" env:"DISCOVERY_METRICS_LISTEN"`
|
||||
DesiredNotFoundRate float64 `group:"Listen" help:"Desired maximum rate of not-found replies (/s)" default:"1000"`
|
||||
|
||||
DBDir string `group:"Database" help:"Database directory" default:"." env:"DISCOVERY_DB_DIR"`
|
||||
DBFlushInterval time.Duration `group:"Database" help:"Interval between database flushes" default:"5m" env:"DISCOVERY_DB_FLUSH_INTERVAL"`
|
||||
@@ -74,6 +77,10 @@ type CLI struct {
|
||||
DBS3AccessKeyID string `name:"db-s3-access-key-id" group:"Database (S3 backup)" hidden:"true" help:"S3 access key ID for database" env:"DISCOVERY_DB_S3_ACCESS_KEY_ID"`
|
||||
DBS3SecretKey string `name:"db-s3-secret-key" group:"Database (S3 backup)" hidden:"true" help:"S3 secret key for database" env:"DISCOVERY_DB_S3_SECRET_KEY"`
|
||||
|
||||
DBAzureBlobAccount string `name:"db-azure-blob-account" env:"DISCOVERY_DB_AZUREBLOB_ACCOUNT"`
|
||||
DBAzureBlobKey string `name:"db-azure-blob-key" env:"DISCOVERY_DB_AZUREBLOB_KEY"`
|
||||
DBAzureBlobContainer string `name:"db-azure-blob-container" env:"DISCOVERY_DB_AZUREBLOB_CONTAINER"`
|
||||
|
||||
AMQPAddress string `group:"AMQP replication" hidden:"true" help:"Address to AMQP broker" env:"DISCOVERY_AMQP_ADDRESS"`
|
||||
|
||||
Debug bool `short:"d" help:"Print debug output" env:"DISCOVERY_DEBUG"`
|
||||
@@ -100,7 +107,7 @@ func main() {
|
||||
cert, err = tls.LoadX509KeyPair(cli.Cert, cli.Key)
|
||||
if os.IsNotExist(err) {
|
||||
log.Println("Failed to load keypair. Generating one, this might take a while...")
|
||||
cert, err = tlsutil.NewCertificate(cli.Cert, cli.Key, "stdiscosrv", 20*365)
|
||||
cert, err = tlsutil.NewCertificate(cli.Cert, cli.Key, "stdiscosrv", 20*365, false)
|
||||
if err != nil {
|
||||
log.Fatalln("Failed to generate X509 key pair:", err)
|
||||
}
|
||||
@@ -117,18 +124,20 @@ func main() {
|
||||
Timeout: 2 * time.Minute,
|
||||
})
|
||||
|
||||
// If configured, use S3 for database backups.
|
||||
var s3c *s3.Session
|
||||
// If configured, use blob storage for database backups.
|
||||
var blobs blob.Store
|
||||
var err error
|
||||
if cli.DBS3Endpoint != "" {
|
||||
var err error
|
||||
s3c, err = s3.NewSession(cli.DBS3Endpoint, cli.DBS3Region, cli.DBS3Bucket, cli.DBS3AccessKeyID, cli.DBS3SecretKey)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create S3 session: %v", err)
|
||||
}
|
||||
blobs, err = s3.NewSession(cli.DBS3Endpoint, cli.DBS3Region, cli.DBS3Bucket, cli.DBS3AccessKeyID, cli.DBS3SecretKey)
|
||||
} else if cli.DBAzureBlobAccount != "" {
|
||||
blobs, err = azureblob.NewBlobStore(cli.DBAzureBlobAccount, cli.DBAzureBlobKey, cli.DBAzureBlobContainer)
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create blob store: %v", err)
|
||||
}
|
||||
|
||||
// Start the database.
|
||||
db := newInMemoryStore(cli.DBDir, cli.DBFlushInterval, s3c)
|
||||
db := newInMemoryStore(cli.DBDir, cli.DBFlushInterval, blobs)
|
||||
main.Add(db)
|
||||
|
||||
// If we have an AMQP broker for replication, start that
|
||||
@@ -141,7 +150,7 @@ func main() {
|
||||
}
|
||||
|
||||
// Start the main API server.
|
||||
qs := newAPISrv(cli.Listen, cert, db, repl, cli.HTTP, cli.Compression)
|
||||
qs := newAPISrv(cli.Listen, cert, db, repl, cli.HTTP, cli.Compression, cli.DesiredNotFoundRate)
|
||||
main.Add(qs)
|
||||
|
||||
// If we have a metrics port configured, start a metrics handler.
|
||||
|
||||
@@ -184,7 +184,7 @@ func protocolConnectionHandler(tcpConn net.Conn, config *tls.Config, token strin
|
||||
continue
|
||||
}
|
||||
// requestedPeer is the server, id is the client
|
||||
ses := newSession(requestedPeer, id, sessionLimiter, globalLimiter)
|
||||
ses := newSession(requestedPeer, id, sessionLimitBps, globalLimiter)
|
||||
|
||||
go ses.Serve()
|
||||
|
||||
|
||||
@@ -51,7 +51,6 @@ var (
|
||||
globalLimitBps int
|
||||
overLimit atomic.Bool
|
||||
descriptorLimit int64
|
||||
sessionLimiter *rate.Limiter
|
||||
globalLimiter *rate.Limiter
|
||||
networkBufferSize int
|
||||
|
||||
@@ -158,7 +157,7 @@ func main() {
|
||||
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||
if err != nil {
|
||||
log.Println("Failed to load keypair. Generating one, this might take a while...")
|
||||
cert, err = tlsutil.NewCertificate(certFile, keyFile, "strelaysrv", 20*365)
|
||||
cert, err = tlsutil.NewCertificate(certFile, keyFile, "strelaysrv", 20*365, false)
|
||||
if err != nil {
|
||||
log.Fatalln("Failed to generate X509 key pair:", err)
|
||||
}
|
||||
@@ -228,9 +227,6 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
if sessionLimitBps > 0 {
|
||||
sessionLimiter = rate.NewLimiter(rate.Limit(sessionLimitBps), 2*sessionLimitBps)
|
||||
}
|
||||
if globalLimitBps > 0 {
|
||||
globalLimiter = rate.NewLimiter(rate.Limit(globalLimitBps), 2*globalLimitBps)
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ var (
|
||||
bytesProxied atomic.Int64
|
||||
)
|
||||
|
||||
func newSession(serverid, clientid syncthingprotocol.DeviceID, sessionRateLimit, globalRateLimit *rate.Limiter) *session {
|
||||
func newSession(serverid, clientid syncthingprotocol.DeviceID, sessionLimitBps int, globalRateLimit *rate.Limiter) *session {
|
||||
serverkey := make([]byte, 32)
|
||||
_, err := rand.Read(serverkey)
|
||||
if err != nil {
|
||||
@@ -40,12 +40,17 @@ func newSession(serverid, clientid syncthingprotocol.DeviceID, sessionRateLimit,
|
||||
return nil
|
||||
}
|
||||
|
||||
var sessionRateLimit *rate.Limiter
|
||||
if sessionLimitBps > 0 {
|
||||
sessionRateLimit = rate.NewLimiter(rate.Limit(sessionLimitBps), 2*sessionLimitBps)
|
||||
}
|
||||
ses := &session{
|
||||
serverkey: serverkey,
|
||||
serverid: serverid,
|
||||
clientkey: clientkey,
|
||||
clientid: clientid,
|
||||
rateLimit: makeRateLimitFunc(sessionRateLimit, globalRateLimit),
|
||||
limiter: sessionRateLimit,
|
||||
connsChan: make(chan net.Conn),
|
||||
conns: make([]net.Conn, 0, 2),
|
||||
}
|
||||
@@ -109,6 +114,7 @@ type session struct {
|
||||
clientid syncthingprotocol.DeviceID
|
||||
|
||||
rateLimit func(bytes int)
|
||||
limiter *rate.Limiter
|
||||
|
||||
connsChan chan net.Conn
|
||||
conns []net.Conn
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
@@ -37,7 +37,9 @@ func uploadPanicLogs(ctx context.Context, urlBase, dir string) {
|
||||
return
|
||||
}
|
||||
|
||||
sort.Sort(sort.Reverse(sort.StringSlice(files)))
|
||||
slices.SortFunc(files, func(a, b string) int {
|
||||
return strings.Compare(b, a)
|
||||
})
|
||||
for _, file := range files {
|
||||
if strings.Contains(file, ".reported.") {
|
||||
// We've already sent this file. It'll be cleaned out at some
|
||||
|
||||
@@ -24,10 +24,9 @@ import (
|
||||
)
|
||||
|
||||
type CLI struct {
|
||||
GUIUser string `placeholder:"STRING" help:"Specify new GUI authentication user name"`
|
||||
GUIPassword string `placeholder:"STRING" help:"Specify new GUI authentication password (use - to read from standard input)"`
|
||||
NoDefaultFolder bool `help:"Don't create the \"default\" folder on first startup" env:"STNODEFAULTFOLDER"`
|
||||
NoPortProbing bool `help:"Don't try to find free ports for GUI and listen addresses on first startup" env:"STNOPORTPROBING"`
|
||||
GUIUser string `placeholder:"STRING" help:"Specify new GUI authentication user name"`
|
||||
GUIPassword string `placeholder:"STRING" help:"Specify new GUI authentication password (use - to read from standard input)"`
|
||||
NoPortProbing bool `help:"Don't try to find free ports for GUI and listen addresses on first startup" env:"STNOPORTPROBING"`
|
||||
}
|
||||
|
||||
func (c *CLI) Run(l logger.Logger) error {
|
||||
@@ -41,13 +40,13 @@ func (c *CLI) Run(l logger.Logger) error {
|
||||
c.GUIPassword = string(password)
|
||||
}
|
||||
|
||||
if err := Generate(l, locations.GetBaseDir(locations.ConfigBaseDir), c.GUIUser, c.GUIPassword, c.NoDefaultFolder, c.NoPortProbing); err != nil {
|
||||
if err := Generate(l, locations.GetBaseDir(locations.ConfigBaseDir), c.GUIUser, c.GUIPassword, c.NoPortProbing); err != nil {
|
||||
return fmt.Errorf("failed to generate config and keys: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Generate(l logger.Logger, confDir, guiUser, guiPassword string, noDefaultFolder, skipPortProbing bool) error {
|
||||
func Generate(l logger.Logger, confDir, guiUser, guiPassword string, skipPortProbing bool) error {
|
||||
dir, err := fs.ExpandTilde(confDir)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -75,7 +74,7 @@ func Generate(l logger.Logger, confDir, guiUser, guiPassword string, noDefaultFo
|
||||
cfgFile := locations.Get(locations.ConfigFile)
|
||||
cfg, _, err := config.Load(cfgFile, myID, events.NoopLogger)
|
||||
if fs.IsNotExist(err) {
|
||||
if cfg, err = syncthing.DefaultConfig(cfgFile, myID, events.NoopLogger, noDefaultFolder, skipPortProbing); err != nil {
|
||||
if cfg, err = syncthing.DefaultConfig(cfgFile, myID, events.NoopLogger, skipPortProbing); err != nil {
|
||||
return fmt.Errorf("create config: %w", err)
|
||||
}
|
||||
} else if err != nil {
|
||||
|
||||
+77
-37
@@ -8,6 +8,7 @@ package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"cmp"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
@@ -22,11 +23,11 @@ import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"sort"
|
||||
"slices"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"github.com/alecthomas/kong"
|
||||
@@ -38,6 +39,7 @@ import (
|
||||
"github.com/syncthing/syncthing/cmd/syncthing/decrypt"
|
||||
"github.com/syncthing/syncthing/cmd/syncthing/generate"
|
||||
"github.com/syncthing/syncthing/internal/db"
|
||||
"github.com/syncthing/syncthing/internal/db/sqlite"
|
||||
_ "github.com/syncthing/syncthing/lib/automaxprocs"
|
||||
"github.com/syncthing/syncthing/lib/build"
|
||||
"github.com/syncthing/syncthing/lib/config"
|
||||
@@ -173,7 +175,6 @@ type serveCmd struct {
|
||||
LogMaxFiles int `name:"log-max-old-files" help:"Number of old files to keep (zero to keep only current)" default:"${logMaxFiles}" placeholder:"N" env:"STLOGMAXOLDFILES"`
|
||||
LogMaxSize int `help:"Maximum size of any file (zero to disable log rotation)" default:"${logMaxSize}" placeholder:"BYTES" env:"STLOGMAXSIZE"`
|
||||
NoBrowser bool `help:"Do not start browser" env:"STNOBROWSER"`
|
||||
NoDefaultFolder bool `help:"Don't create the \"default\" folder on first startup" env:"STNODEFAULTFOLDER"`
|
||||
NoPortProbing bool `help:"Don't try to find free ports for GUI and listen addresses on first startup" env:"STNOPORTPROBING"`
|
||||
NoRestart bool `help:"Do not restart Syncthing when exiting due to API/GUI command, upgrade, or crash" env:"STNORESTART"`
|
||||
NoUpgrade bool `help:"Disable automatic upgrades" env:"STNOUPGRADE"`
|
||||
@@ -294,10 +295,12 @@ func (c *serveCmd) Run() error {
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that our home directory exists.
|
||||
if err := syncthing.EnsureDir(locations.GetBaseDir(locations.ConfigBaseDir), 0o700); err != nil {
|
||||
l.Warnln("Failure on home directory:", err)
|
||||
os.Exit(svcutil.ExitError.AsInt())
|
||||
// Ensure that our config and data directories exist.
|
||||
for _, loc := range []locations.BaseDirEnum{locations.ConfigBaseDir, locations.DataBaseDir} {
|
||||
if err := syncthing.EnsureDir(locations.GetBaseDir(loc), 0o700); err != nil {
|
||||
l.Warnln("Failed to ensure directory exists:", err)
|
||||
os.Exit(svcutil.ExitError.AsInt())
|
||||
}
|
||||
}
|
||||
|
||||
if c.InternalInnerProcess {
|
||||
@@ -335,7 +338,7 @@ func debugFacilities() string {
|
||||
maxLen = len(name)
|
||||
}
|
||||
}
|
||||
sort.Strings(names)
|
||||
slices.Sort(names)
|
||||
|
||||
// Format the choices
|
||||
b := new(bytes.Buffer)
|
||||
@@ -400,9 +403,11 @@ func upgradeViaRest() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
bs, err := io.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -442,7 +447,7 @@ func (c *serveCmd) syncthingMain() {
|
||||
}
|
||||
|
||||
// Ensure we are the only running instance
|
||||
lf := flock.New(locations.Get(locations.CertFile))
|
||||
lf := flock.New(locations.Get(locations.LockFile))
|
||||
locked, err := lf.TryLock()
|
||||
if err != nil {
|
||||
l.Warnln("Failed to acquire lock:", err)
|
||||
@@ -464,12 +469,13 @@ func (c *serveCmd) syncthingMain() {
|
||||
evLogger := events.NewLogger()
|
||||
earlyService.Add(evLogger)
|
||||
|
||||
cfgWrapper, err := syncthing.LoadConfigAtStartup(locations.Get(locations.ConfigFile), cert, evLogger, c.AllowNewerConfig, c.NoDefaultFolder, c.NoPortProbing)
|
||||
cfgWrapper, err := syncthing.LoadConfigAtStartup(locations.Get(locations.ConfigFile), cert, evLogger, c.AllowNewerConfig, c.NoPortProbing)
|
||||
if err != nil {
|
||||
l.Warnln("Failed to initialize config:", err)
|
||||
os.Exit(svcutil.ExitError.AsInt())
|
||||
}
|
||||
earlyService.Add(cfgWrapper)
|
||||
config.RegisterInfoMetrics(cfgWrapper)
|
||||
|
||||
// Candidate builds should auto upgrade. Make sure the option is set,
|
||||
// unless we are in a build where it's disabled or the STNOUPGRADE
|
||||
@@ -535,8 +541,19 @@ func (c *serveCmd) syncthingMain() {
|
||||
Verbose: c.Verbose,
|
||||
DBMaintenanceInterval: c.DBMaintenanceInterval,
|
||||
}
|
||||
if c.Audit {
|
||||
appOpts.AuditWriter = auditWriter(c.AuditFile)
|
||||
|
||||
if c.Audit || cfgWrapper.Options().AuditEnabled {
|
||||
l.Infoln("Auditing is enabled.")
|
||||
|
||||
auditFile := cfgWrapper.Options().AuditFile
|
||||
|
||||
// Ignore config option if command-line option is set
|
||||
if c.AuditFile != "" {
|
||||
l.Debugln("Using the audit file from the command-line parameter.")
|
||||
auditFile = c.AuditFile
|
||||
}
|
||||
|
||||
appOpts.AuditWriter = auditWriter(auditFile)
|
||||
}
|
||||
|
||||
app, err := syncthing.New(cfgWrapper, sdb, evLogger, cert, appOpts)
|
||||
@@ -585,7 +602,10 @@ func (c *serveCmd) syncthingMain() {
|
||||
pprof.StopCPUProfile()
|
||||
}
|
||||
|
||||
runtime.KeepAlive(lf) // ensure lock is still held to this point
|
||||
// Best effort remove lockfile, doesn't matter if it succeeds
|
||||
_ = lf.Unlock()
|
||||
_ = os.Remove(locations.Get(locations.LockFile))
|
||||
|
||||
os.Exit(int(status))
|
||||
}
|
||||
|
||||
@@ -593,8 +613,7 @@ func setupSignalHandling(app *syncthing.App) {
|
||||
// Exit cleanly with "restarting" code on SIGHUP.
|
||||
|
||||
restartSign := make(chan os.Signal, 1)
|
||||
sigHup := syscall.Signal(1)
|
||||
signal.Notify(restartSign, sigHup)
|
||||
signal.Notify(restartSign, syscall.SIGHUP)
|
||||
go func() {
|
||||
<-restartSign
|
||||
app.Stop(svcutil.ExitRestart)
|
||||
@@ -813,24 +832,6 @@ func exitCodeForUpgrade(err error) int {
|
||||
return svcutil.ExitError.AsInt()
|
||||
}
|
||||
|
||||
// convertLegacyArgs returns the slice of arguments with single dash long
|
||||
// flags converted to double dash long flags.
|
||||
func convertLegacyArgs(args []string) []string {
|
||||
// Legacy args begin with a single dash, followed by two or more characters.
|
||||
legacyExp := regexp.MustCompile(`^-\w{2,}`)
|
||||
|
||||
res := make([]string, len(args))
|
||||
for i, arg := range args {
|
||||
if legacyExp.MatchString(arg) {
|
||||
res[i] = "-" + arg
|
||||
} else {
|
||||
res[i] = arg
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
type versionCmd struct{}
|
||||
|
||||
func (versionCmd) Run() error {
|
||||
@@ -887,8 +888,9 @@ func (u upgradeCmd) Run() error {
|
||||
|
||||
release, err := checkUpgrade()
|
||||
if err == nil {
|
||||
lf := flock.New(locations.Get(locations.CertFile))
|
||||
locked, err := lf.TryLock()
|
||||
lf := flock.New(locations.Get(locations.LockFile))
|
||||
var locked bool
|
||||
locked, err = lf.TryLock()
|
||||
if err != nil {
|
||||
l.Warnln("Upgrade:", err)
|
||||
os.Exit(1)
|
||||
@@ -918,7 +920,8 @@ func (browserCmd) Run() error {
|
||||
}
|
||||
|
||||
type debugCmd struct {
|
||||
ResetDatabase resetDatabaseCmd `cmd:"" help:"Reset the database, forcing a full rescan and resync"`
|
||||
ResetDatabase resetDatabaseCmd `cmd:"" help:"Reset the database, forcing a full rescan and resync"`
|
||||
DatabaseStatistics databaseStatsCmd `cmd:"" help:"Display database size statistics"`
|
||||
}
|
||||
|
||||
type resetDatabaseCmd struct{}
|
||||
@@ -933,6 +936,43 @@ func (resetDatabaseCmd) Run() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type databaseStatsCmd struct{}
|
||||
|
||||
func (c databaseStatsCmd) Run() error {
|
||||
db, err := sqlite.Open(locations.Get(locations.Database))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ds, err := db.Statistics()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tw := tabwriter.NewWriter(os.Stdout, 2, 2, 2, ' ', 0)
|
||||
hdr := fmt.Sprintf("%s\t%s\t%s\t%12s\t%7s\n", "DATABASE", "FOLDER ID", "TABLE", "SIZE", "FILL")
|
||||
fmt.Fprint(tw, hdr)
|
||||
fmt.Fprint(tw, regexp.MustCompile(`[A-Z]`).ReplaceAllString(hdr, "="))
|
||||
c.printStat(tw, ds)
|
||||
return tw.Flush()
|
||||
}
|
||||
|
||||
func (c databaseStatsCmd) printStat(w io.Writer, s *sqlite.DatabaseStatistics) {
|
||||
for _, table := range s.Tables {
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%8d KiB\t%5.01f %%\n", s.Name, cmp.Or(s.FolderID, "-"), table.Name, table.Size/1024, float64(table.Size-table.Unused)*100/float64(table.Size))
|
||||
}
|
||||
for _, next := range s.Children {
|
||||
c.printStat(w, &next)
|
||||
s.Total.Size += next.Total.Size
|
||||
s.Total.Unused += next.Total.Unused
|
||||
}
|
||||
|
||||
totalName := s.Name
|
||||
if len(s.Children) > 0 {
|
||||
totalName += " + children"
|
||||
}
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%8d KiB\t%5.01f %%\n", totalName, cmp.Or(s.FolderID, "-"), "(total)", s.Total.Size/1024, float64(s.Total.Size-s.Total.Unused)*100/float64(s.Total.Size))
|
||||
}
|
||||
|
||||
func setConfigDataLocationsFromFlags(homeDir, confDir, dataDir string) error {
|
||||
homeSet := homeDir != ""
|
||||
confSet := confDir != ""
|
||||
|
||||
+23
-25
@@ -238,19 +238,18 @@ func copyStderr(stderr io.Reader, dst io.Writer) {
|
||||
return
|
||||
}
|
||||
|
||||
if panicFd == nil {
|
||||
dst.Write([]byte(line))
|
||||
dst.Write([]byte(line))
|
||||
|
||||
if strings.HasPrefix(line, "panic:") || strings.HasPrefix(line, "fatal error:") {
|
||||
panicFd, err = os.Create(locations.GetTimestamped(locations.PanicLog))
|
||||
if err != nil {
|
||||
l.Warnln("Create panic log:", err)
|
||||
continue
|
||||
}
|
||||
if panicFd == nil && (strings.HasPrefix(line, "panic:") || strings.HasPrefix(line, "fatal error:")) {
|
||||
panicFd, err = os.Create(locations.GetTimestamped(locations.PanicLog))
|
||||
if err != nil {
|
||||
l.Warnln("Create panic log:", err)
|
||||
continue
|
||||
}
|
||||
|
||||
l.Warnf("Panic detected, writing to \"%s\"", panicFd.Name())
|
||||
if strings.Contains(line, "leveldb") && strings.Contains(line, "corrupt") {
|
||||
l.Warnln(`
|
||||
l.Warnf("Panic detected, writing to \"%s\"", panicFd.Name())
|
||||
if strings.Contains(line, "leveldb") && strings.Contains(line, "corrupt") {
|
||||
l.Warnln(`
|
||||
*********************************************************************************
|
||||
* Crash due to corrupt database. *
|
||||
* *
|
||||
@@ -263,22 +262,21 @@ func copyStderr(stderr io.Reader, dst io.Writer) {
|
||||
* https://docs.syncthing.net/users/faq.html#my-syncthing-database-is-corrupt *
|
||||
*********************************************************************************
|
||||
`)
|
||||
} else {
|
||||
l.Warnln("Please check for existing issues with similar panic message at https://github.com/syncthing/syncthing/issues/")
|
||||
l.Warnln("If no issue with similar panic message exists, please create a new issue with the panic log attached")
|
||||
}
|
||||
|
||||
stdoutMut.Lock()
|
||||
for _, line := range stdoutFirstLines {
|
||||
panicFd.WriteString(line)
|
||||
}
|
||||
panicFd.WriteString("...\n")
|
||||
for _, line := range stdoutLastLines {
|
||||
panicFd.WriteString(line)
|
||||
}
|
||||
stdoutMut.Unlock()
|
||||
} else {
|
||||
l.Warnln("Please check for existing issues with similar panic message at https://github.com/syncthing/syncthing/issues/")
|
||||
l.Warnln("If no issue with similar panic message exists, please create a new issue with the panic log attached")
|
||||
}
|
||||
|
||||
stdoutMut.Lock()
|
||||
for _, line := range stdoutFirstLines {
|
||||
panicFd.WriteString(line)
|
||||
}
|
||||
panicFd.WriteString("...\n")
|
||||
for _, line := range stdoutLastLines {
|
||||
panicFd.WriteString(line)
|
||||
}
|
||||
stdoutMut.Unlock()
|
||||
|
||||
panicFd.WriteString("Panic at " + time.Now().Format(time.RFC3339) + "\n")
|
||||
}
|
||||
|
||||
|
||||
@@ -32,3 +32,10 @@
|
||||
darwin: "20"
|
||||
linux: "3.2"
|
||||
windows: "10.0"
|
||||
|
||||
- runtime: go1.25
|
||||
requirements:
|
||||
# macOS 12 (Monterey) per https://tip.golang.org/doc/go1.25#darwin
|
||||
darwin: "21"
|
||||
linux: "3.2"
|
||||
windows: "10.0"
|
||||
|
||||
@@ -18,4 +18,4 @@ env STNORESTART=yes
|
||||
respawn
|
||||
|
||||
# the syncthing command Upstart is to execute when it is started up
|
||||
exec $SYNCTHING_EXE -no-browser
|
||||
exec $SYNCTHING_EXE --no-browser
|
||||
|
||||
@@ -4,14 +4,16 @@ go 1.23.0
|
||||
|
||||
require (
|
||||
github.com/AudriusButkevicius/recli v0.0.7-0.20220911121932-d000ce8fbf0f
|
||||
github.com/alecthomas/kong v1.10.0
|
||||
github.com/aws/aws-sdk-go v1.55.6
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.1
|
||||
github.com/alecthomas/kong v1.11.0
|
||||
github.com/aws/aws-sdk-go v1.55.7
|
||||
github.com/calmh/incontainer v1.0.0
|
||||
github.com/calmh/xdr v1.2.0
|
||||
github.com/ccding/go-stun v0.1.5
|
||||
github.com/coreos/go-semver v0.3.1
|
||||
github.com/d4l3k/messagediff v1.2.1
|
||||
github.com/getsentry/raven-go v0.2.0
|
||||
github.com/go-ldap/ldap/v3 v3.4.10
|
||||
github.com/go-ldap/ldap/v3 v3.4.11
|
||||
github.com/gobwas/glob v0.2.3
|
||||
github.com/gofrs/flock v0.12.1
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7
|
||||
@@ -21,37 +23,40 @@ require (
|
||||
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.27
|
||||
github.com/mattn/go-sqlite3 v1.14.28
|
||||
github.com/maxbrunsfeld/counterfeiter/v6 v6.11.2
|
||||
github.com/maxmind/geoipupdate/v6 v6.1.0
|
||||
github.com/miscreant/miscreant.go v0.0.0-20200214223636-26d376326b75
|
||||
github.com/oschwald/geoip2-golang v1.11.0
|
||||
github.com/pierrec/lz4/v4 v4.1.22
|
||||
github.com/prometheus/client_golang v1.21.1
|
||||
github.com/prometheus/client_golang v1.22.0
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1
|
||||
github.com/quic-go/quic-go v0.50.1
|
||||
github.com/quic-go/quic-go v0.52.0
|
||||
github.com/rabbitmq/amqp091-go v1.10.0
|
||||
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9
|
||||
github.com/shirou/gopsutil/v4 v4.25.3
|
||||
github.com/syncthing/notify v0.0.0-20250207082249-f0fa8f99c2bc
|
||||
github.com/shirou/gopsutil/v4 v4.25.4
|
||||
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
|
||||
github.com/urfave/cli v1.22.16
|
||||
github.com/vitrun/qart v0.0.0-20160531060029-bf64b92db6b0
|
||||
github.com/willabides/kongplete v0.4.0
|
||||
go.uber.org/automaxprocs v1.6.0
|
||||
golang.org/x/crypto v0.36.0
|
||||
golang.org/x/net v0.38.0
|
||||
golang.org/x/sys v0.31.0
|
||||
golang.org/x/text v0.23.0
|
||||
golang.org/x/crypto v0.38.0
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394
|
||||
golang.org/x/net v0.40.0
|
||||
golang.org/x/sys v0.33.0
|
||||
golang.org/x/text v0.25.0
|
||||
golang.org/x/time v0.11.0
|
||||
golang.org/x/tools v0.31.0
|
||||
golang.org/x/tools v0.33.0
|
||||
google.golang.org/protobuf v1.36.6
|
||||
modernc.org/sqlite v1.37.0
|
||||
sigs.k8s.io/yaml v1.4.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
@@ -60,24 +65,23 @@ require (
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/ebitengine/purego v0.8.2 // indirect
|
||||
github.com/ebitengine/purego v0.8.3 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.7 // indirect
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e // 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/klauspost/compress v1.17.11 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/nxadm/tail v1.4.11 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.20.2 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.23.4 // 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
|
||||
@@ -94,10 +98,9 @@ require (
|
||||
github.com/tklauser/go-sysconf v0.3.14 // indirect
|
||||
github.com/tklauser/numcpus v0.9.0 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
go.uber.org/mock v0.5.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
|
||||
go.uber.org/mock v0.5.2 // indirect
|
||||
golang.org/x/mod v0.24.0 // indirect
|
||||
golang.org/x/sync v0.12.0 // indirect
|
||||
golang.org/x/sync v0.14.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
modernc.org/libc v1.62.1 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
@@ -106,3 +109,6 @@ require (
|
||||
|
||||
// https://github.com/gobwas/glob/pull/55
|
||||
replace github.com/gobwas/glob v0.2.3 => github.com/calmh/glob v0.0.0-20220615080505-1d823af5017b
|
||||
|
||||
// https://github.com/mattn/go-sqlite3/pull/1338
|
||||
replace github.com/mattn/go-sqlite3 v1.14.28 => github.com/calmh/go-sqlite3 v1.14.29-0.20250520105817-2e94cda3f7f8
|
||||
|
||||
@@ -2,23 +2,37 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/AudriusButkevicius/recli v0.0.7-0.20220911121932-d000ce8fbf0f h1:GmH5lT+moM7PbAJFBq57nH9WJ+wRnBXr/tyaYWbSAx8=
|
||||
github.com/AudriusButkevicius/recli v0.0.7-0.20220911121932-d000ce8fbf0f/go.mod h1:Nhfib1j/VFnLrXL9cHgA+/n2O6P5THuWelOnbfPNd78=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 h1:Gt0j3wceWMwPmiazCa8MzMA0MfhmPIz0Qp0FJ6qcM0U=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.9.0 h1:OVoM452qUFBrX+URdH3VpR299ma4kfom0yB0URYky9g=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.9.0/go.mod h1:kUjrAo8bgEwLeZ/CmHqNl3Z/kPm7y6FKfxxK0izYUg4=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 h1:FPKJS1T+clwv+OLGt13a8UjqeRuh0O4SJ3lUriThc+4=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.8.0 h1:LR0kAX9ykz8G4YgLCaRDVJ3+n43R8MneB5dTy2konZo=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.8.0/go.mod h1:DWAciXemNf++PQJLeXUB4HHH5OpsAh12HZnu2wXE1jA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.1 h1:lhZdRq7TIx0GJQvSyX2Si406vrYsov2FXGp/RnSEtcs=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.1/go.mod h1:8cl44BDmi+effbARHMQjgOKA2AYvcohNm7KEt42mSV8=
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8=
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
|
||||
github.com/BurntSushi/toml v1.4.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.10.0 h1:8K4rGDpT7Iu+jEXCIJUeKqvpwZHbsFRoebLbnzlmrpw=
|
||||
github.com/alecthomas/kong v1.10.0/go.mod h1:p2vqieVMeTAnaC83txKtXe8FLke2X07aruPWXyMPQrU=
|
||||
github.com/alecthomas/kong v1.11.0 h1:y++1gI7jf8O7G7l4LZo5ASFhrhJvzc+WgF/arranEmM=
|
||||
github.com/alecthomas/kong v1.11.0/go.mod h1:p2vqieVMeTAnaC83txKtXe8FLke2X07aruPWXyMPQrU=
|
||||
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
|
||||
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI=
|
||||
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
|
||||
github.com/aws/aws-sdk-go v1.55.6 h1:cSg4pvZ3m8dgYcgqB97MrcdjUmZ1BeMYKUxMMB89IPk=
|
||||
github.com/aws/aws-sdk-go v1.55.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
|
||||
github.com/aws/aws-sdk-go v1.55.7 h1:UJrkFq7es5CShfBwlWAC8DA077vp8PyVbQd3lqLiztE=
|
||||
github.com/aws/aws-sdk-go v1.55.7/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/calmh/glob v0.0.0-20220615080505-1d823af5017b h1:Fjm4GuJ+TGMgqfGHN42IQArJb77CfD/mAwLbDUoJe6g=
|
||||
github.com/calmh/glob v0.0.0-20220615080505-1d823af5017b/go.mod h1:91K7jfEsgJSyfSrX+gmrRfZMtntx6JsHolWubGXDopg=
|
||||
github.com/calmh/go-sqlite3 v1.14.29-0.20250520105817-2e94cda3f7f8 h1:oNVrBJGXkD334ToEmxJz8G6LhzD1/sMA4twMHsMLzQo=
|
||||
github.com/calmh/go-sqlite3 v1.14.29-0.20250520105817-2e94cda3f7f8/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/calmh/incontainer v1.0.0 h1:g2cTUtZuFGmMGX8GoykPkN1Judj2uw8/3/aEtq4Z/rg=
|
||||
github.com/calmh/incontainer v1.0.0/go.mod h1:eOhqnw15c9X+4RNBe0W3HlUZFfX16O0EDsCOInTndHY=
|
||||
github.com/calmh/xdr v1.2.0 h1:GaGSNH4ZDw9kNdYqle6+RcAENiaQ8/611Ok+jQbBEeU=
|
||||
@@ -34,6 +48,8 @@ github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
|
||||
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/d4l3k/messagediff v1.2.1 h1:ZcAIMYsUg0EAp9X+tt8/enBE/Q8Yd5kzPynLyKptt9U=
|
||||
@@ -43,8 +59,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.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I=
|
||||
github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/ebitengine/purego v0.8.3 h1:K+0AjQp63JEZTEMZiwsI9g0+hAMNohwUOtY0RPGexmc=
|
||||
github.com/ebitengine/purego v0.8.3/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=
|
||||
@@ -53,10 +69,10 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/getsentry/raven-go v0.2.0 h1:no+xWJRb5ZI7eE8TWgIq1jLulQiIoLG0IfYxv5JYMGs=
|
||||
github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.7 h1:DTX+lbVTWaTw1hQ+PbZPlnDZPEIs0SS/GCZAl535dDk=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.7/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||
github.com/go-ldap/ldap/v3 v3.4.10 h1:ot/iwPOhfpNVgB1o+AVXljizWZ9JTp7YF5oeyONmcJU=
|
||||
github.com/go-ldap/ldap/v3 v3.4.10/go.mod h1:JXh4Uxgi40P6E9rdsYqpUtbW46D9UTjJ9QSwGRznplY=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||
github.com/go-ldap/ldap/v3 v3.4.11 h1:4k0Yxweg+a3OyBLjdYn5OKglv18JNvfDykSoI8bW0gU=
|
||||
github.com/go-ldap/ldap/v3 v3.4.11/go.mod h1:bY7t0FLK8OAVpp/vV6sSlpz3EQDGcQwc8pF0ujLgKvM=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
@@ -69,6 +85,8 @@ github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1v
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E=
|
||||
github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
@@ -89,19 +107,16 @@ 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-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
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/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
|
||||
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
@@ -136,8 +151,8 @@ 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.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
||||
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
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=
|
||||
@@ -153,8 +168,6 @@ github.com/maruel/panicparse/v2 v2.5.0/go.mod h1:DA2fDiBk63bKfBf4CVZP9gb4fuvzdPb
|
||||
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.27 h1:drZCnuvf37yPfs95E5jd9s3XhdVWLal+6BOK6qrv6IU=
|
||||
github.com/mattn/go-sqlite3 v1.14.27/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/maxbrunsfeld/counterfeiter/v6 v6.11.2 h1:yVCLo4+ACVroOEr4iFU1iH46Ldlzz2rTuu18Ra7M8sU=
|
||||
github.com/maxbrunsfeld/counterfeiter/v6 v6.11.2/go.mod h1:VzB2VoMh1Y32/QqDfg9ZJYHj99oM4LiGtqPZydTiQSQ=
|
||||
github.com/maxmind/geoipupdate/v6 v6.1.0 h1:sdtTHzzQNJlXF5+fd/EoPTucRHyMonYt/Cok8xzzfqA=
|
||||
@@ -175,20 +188,22 @@ github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vv
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||
github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
|
||||
github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4=
|
||||
github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5coaZoE2ag=
|
||||
github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus=
|
||||
github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
|
||||
github.com/onsi/gomega v1.36.1 h1:bJDPBO7ibjxcbHMgSCoo4Yj18UWbKDlLwX1x9sybDcw=
|
||||
github.com/onsi/gomega v1.36.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
|
||||
github.com/onsi/gomega v1.36.3 h1:hID7cr8t3Wp26+cYnfcjR6HpJ00fdogN6dqZ1t6IylU=
|
||||
github.com/onsi/gomega v1.36.3/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
|
||||
github.com/oschwald/geoip2-golang v1.11.0 h1:hNENhCn1Uyzhf9PTmquXENiWS6AlxAEnBII6r8krA3w=
|
||||
github.com/oschwald/geoip2-golang v1.11.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.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
|
||||
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||
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=
|
||||
@@ -200,8 +215,8 @@ github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
||||
github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk=
|
||||
github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg=
|
||||
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
|
||||
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
|
||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
|
||||
@@ -210,8 +225,8 @@ github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0leargg
|
||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||
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/quic-go v0.50.1 h1:unsgjFIUqW8a2oopkY7YNONpV1gYND6Nt9hnt1PN94Q=
|
||||
github.com/quic-go/quic-go v0.50.1/go.mod h1:Vim6OmUvlYdwBhXP9ZVrtGmCMWa3wEqhq3NgYrI8b4E=
|
||||
github.com/quic-go/quic-go v0.52.0 h1:/SlHrCRElyaU6MaEPKqKr9z83sBg2v4FLLvWM+Z47pA=
|
||||
github.com/quic-go/quic-go v0.52.0/go.mod h1:MFlGGpcpJqRAfmYi6NC2cptDPSxRWTOGNuP4wqrWmzQ=
|
||||
github.com/rabbitmq/amqp091-go v1.10.0 h1:STpn5XsHlHGcecLmMFCtg7mqq0RnD+zFr4uzukfVhBw=
|
||||
github.com/rabbitmq/amqp091-go v1.10.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg=
|
||||
@@ -220,14 +235,14 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94
|
||||
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.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
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.25.3 h1:SeA68lsu8gLggyMbmCn8cmp97V1TI9ld9sVzAUcKcKE=
|
||||
github.com/shirou/gopsutil/v4 v4.25.3/go.mod h1:xbuxyoZj+UsgnZrENu3lQivsngRR5BdjbJwf2fv4szA=
|
||||
github.com/shirou/gopsutil/v4 v4.25.4 h1:cdtFO363VEOOFrUCjZRh4XVJkb548lyF0q0uTeMqYPw=
|
||||
github.com/shirou/gopsutil/v4 v4.25.4/go.mod h1:xbuxyoZj+UsgnZrENu3lQivsngRR5BdjbJwf2fv4szA=
|
||||
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=
|
||||
@@ -238,13 +253,12 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/syncthing/notify v0.0.0-20250207082249-f0fa8f99c2bc h1:xc3UfSFlH/X5hRw3h21RF6WXnRUYKmGRx06FEaVxfkM=
|
||||
github.com/syncthing/notify v0.0.0-20250207082249-f0fa8f99c2bc/go.mod h1:J0q59IWjLtpRIJulohwqEZvjzwOfTEPp8SVhDJl+y0Y=
|
||||
github.com/syncthing/notify v0.0.0-20250528144937-c7027d4f7465 h1:yhxdTGmFkAM2TFA65c3NgGwpnIkUM8oVqPX2e9S7IVg=
|
||||
github.com/syncthing/notify v0.0.0-20250528144937-c7027d4f7465/go.mod h1:J0q59IWjLtpRIJulohwqEZvjzwOfTEPp8SVhDJl+y0Y=
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDdvS342BElfbETmL1Aiz3i2t0zfRj16Hs=
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48=
|
||||
github.com/thejerf/suture/v4 v4.0.6 h1:QsuCEsCqb03xF9tPAsWAj8QOAJBgQI1c0VqJNaingg8=
|
||||
@@ -261,67 +275,39 @@ github.com/vitrun/qart v0.0.0-20160531060029-bf64b92db6b0/go.mod h1:TTbGUfE+cXXc
|
||||
github.com/willabides/kongplete v0.4.0 h1:eivXxkp5ud5+4+NVN9e4goxC5mSh3n1RHov+gsblM2g=
|
||||
github.com/willabides/kongplete v0.4.0/go.mod h1:0P0jtWD9aTsqPSUAl4de35DLghrr57XcayPyvqSi2X8=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
|
||||
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
||||
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
||||
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
|
||||
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
|
||||
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.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
|
||||
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||
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=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
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.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
|
||||
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
|
||||
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
|
||||
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=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
|
||||
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
|
||||
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180926160741-c2ed4eda69e7/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -341,50 +327,26 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
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/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
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.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
||||
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
|
||||
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
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.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
|
||||
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
|
||||
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
|
||||
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
|
||||
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=
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"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",
|
||||
"API Key": "مفتاح واجهة برمجة التطبيقات \"API\"",
|
||||
"About": "حول",
|
||||
"Action": "إجراء",
|
||||
"Actions": "الإجراءات",
|
||||
@@ -27,6 +27,7 @@
|
||||
"Allowed Networks": "الشبكات المسموح بها",
|
||||
"Alphabetic": "أبجدية",
|
||||
"Altered by ignoring deletes.": "تغير بتجاهل عمليات الحذف.",
|
||||
"Always turned on when the folder type is \"{%foldertype%}\".": "مفعل دائمًا عندما يكون نوع المجلد هو \"{{foldertype}}\".",
|
||||
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "الإصدار يعالج بواسطة أمر خارجي. يجب إزالة الملف من المجلدات المشتركة. إذا كان المسار للتطبيق يحتوي على مسافات، يجب وضعها بين علامتي تنصيص دلالة على الاقتباس.",
|
||||
"Anonymous Usage Reporting": "تقارير الإستخدام المجهولة",
|
||||
"Anonymous usage report format has changed. Would you like to move to the new format?": "هل تريد الانتقال الى التصميم الجديد لتقرير الاستخدام المجهول ؟",
|
||||
@@ -52,6 +53,7 @@
|
||||
"Body:": "جسم:",
|
||||
"Bugs": "أخطاء برمجية",
|
||||
"Cancel": "إلغاء",
|
||||
"Cannot be enabled when the folder type is \"{%foldertype%}\".": "لا يمكن تفعيله عندما يكون نوع المجلد هو \"{{foldertype}}\".",
|
||||
"Changelog": "سجل التغيير",
|
||||
"Clean out after": "نظف بعد",
|
||||
"Cleaning Versions": "إصدارات نظيفة",
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"Allowed Networks": "Xarxes permeses",
|
||||
"Alphabetic": "Alfabètic",
|
||||
"Altered by ignoring deletes.": "S'ha alterat ignorant les supressions.",
|
||||
"Always turned on when the folder type is \"{%foldertype%}\".": "Sempre activat quan el tipus de carpeta és \"{{foldertype}}\".",
|
||||
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "Una ordre externa gestiona la versió. Ha d'eliminar el fitxer de la carpeta compartida. Si el camí a l'aplicació conté espais, s'ha de citar.",
|
||||
"Anonymous Usage Reporting": "Informe anònim d'ús",
|
||||
"Anonymous usage report format has changed. Would you like to move to the new format?": "El format de l'informe d'ús anònim ha canviat. Voleu canviar a aquest nou format?",
|
||||
@@ -52,6 +53,7 @@
|
||||
"Body:": "Cos de text:",
|
||||
"Bugs": "Errors (Bugs)",
|
||||
"Cancel": "Cancel·la",
|
||||
"Cannot be enabled when the folder type is \"{%foldertype%}\".": "No es pot habilitar quan el tipus de carpeta és \"{{foldertype}}\".",
|
||||
"Changelog": "Historial de canvis",
|
||||
"Clean out after": "Netejar després",
|
||||
"Cleaning Versions": "Netejant versions",
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"Allowed Networks": "Allowed Networks",
|
||||
"Alphabetic": "Alphabetic",
|
||||
"Altered by ignoring deletes.": "Altered by ignoring deletes.",
|
||||
"Always turned on when the folder type is \"{%foldertype%}\".": "Always turned on when the folder type is \"{{foldertype}}\".",
|
||||
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.",
|
||||
"Anonymous Usage Reporting": "Anonymous Usage Reporting",
|
||||
"Anonymous usage report format has changed. Would you like to move to the new format?": "Anonymous usage report format has changed. Would you like to move to the new format?",
|
||||
@@ -52,6 +53,7 @@
|
||||
"Body:": "Body:",
|
||||
"Bugs": "Bugs",
|
||||
"Cancel": "Cancel",
|
||||
"Cannot be enabled when the folder type is \"{%foldertype%}\".": "Cannot be enabled when the folder type is \"{{foldertype}}\".",
|
||||
"Changelog": "Changelog",
|
||||
"Clean out after": "Clean out after",
|
||||
"Cleaning Versions": "Cleaning Versions",
|
||||
|
||||
@@ -227,6 +227,7 @@
|
||||
"Learn more": "Learn more",
|
||||
"Learn more at {%url%}": "Learn more at {{url}}",
|
||||
"Limit": "Limit",
|
||||
"Limit Bandwidth in LAN": "Limit Bandwidth in LAN",
|
||||
"Listener Failures": "Listener Failures",
|
||||
"Listener Status": "Listener Status",
|
||||
"Listeners": "Listeners",
|
||||
|
||||
@@ -9,9 +9,15 @@
|
||||
"Add Folder": "Lisa kaust",
|
||||
"Add new folder?": "Lisa uus kaust?",
|
||||
"Address": "Aadress",
|
||||
"Addresses": "Aadressid",
|
||||
"All Data": "Kõik andmed",
|
||||
"All Time": "Kõik ajad",
|
||||
"Allowed Networks": "Lubatud võrgud",
|
||||
"Alphabetic": "Tähestikuline",
|
||||
"Automatic upgrades": "Automaatsed uuendused",
|
||||
"Be careful!": "Ettevaatust!",
|
||||
"Cancel": "Loobu",
|
||||
"Changelog": "Muudatuste nimekiri",
|
||||
"Close": "Sulge",
|
||||
"Configured": "Seadistatud",
|
||||
"Connection Error": "Ühenduse viga",
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"Allowed Networks": "Mga Pinapayagang Network",
|
||||
"Alphabetic": "Alpabetiko",
|
||||
"Altered by ignoring deletes.": "Binago sa pamamagitan ng hindi pagpansin sa mga pagtanggal.",
|
||||
"Always turned on when the folder type is \"{%foldertype%}\".": "Palaging nakabukas kung ang uri ng folder ay nakatakda bilang \"{{foldertype}}\".",
|
||||
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "Pinapamahala ng external na command ang file versioning. Kailangan nitong tanggalin ang file mula sa binabahaging folder. Kung may mga space ang path sa application, kailangan itong i-quote.",
|
||||
"Anonymous Usage Reporting": "Anonymous na Pag-uulat ng Paggamit",
|
||||
"Anonymous usage report format has changed. Would you like to move to the new format?": "Nagbago ang pormat ng anonymous na ulat ng paggamit. Gusto mo bang lumipat sa bagong pormat?",
|
||||
@@ -52,6 +53,7 @@
|
||||
"Body:": "Body:",
|
||||
"Bugs": "Mga Bug",
|
||||
"Cancel": "Kanselahin",
|
||||
"Cannot be enabled when the folder type is \"{%foldertype%}\".": "Hindi maaaring paganahin kapag ang uri ng folder ay \"{{foldertype}}\".",
|
||||
"Changelog": "Mga Pagbabago",
|
||||
"Clean out after": "Linisin pagkatapos",
|
||||
"Cleaning Versions": "Mga Bersyon ng Paglinis",
|
||||
@@ -311,7 +313,7 @@
|
||||
"Receive Encrypted": "Makatanggap Naka-Encrypt",
|
||||
"Receive Only": "Makatanggap Lamang",
|
||||
"Received data is already encrypted": "Naka-encrypt na ang natanggap na data",
|
||||
"Recent Changes": "Mga Kamakilang Pagbabago",
|
||||
"Recent Changes": "Mga Kamakailang Pagbabago",
|
||||
"Reduced by ignore patterns": "Binabawasan ng mga ignore pattern",
|
||||
"Relay LAN": "Relay na LAN",
|
||||
"Relay WAN": "Relay na WAN",
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"Allowed Networks": "רשתות מורשות",
|
||||
"Alphabetic": "אלפביתי",
|
||||
"Altered by ignoring deletes.": "השתנה על ידי התעלמות ממחיקות.",
|
||||
"Always turned on when the folder type is \"{%foldertype%}\".": "תמיד מופעל כאשר סוג התיקייה הוא \"{{foldertype}}\".",
|
||||
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "פקודה חיצונית מטפלת בניהול הגרסאות. היא חייבת להסיר את הקובץ מהתיקייה המשותפת. אם הנתיב ליישום מכיל רווחים, יש לצטט אותו.",
|
||||
"Anonymous Usage Reporting": "דיווח שימוש אנונימי",
|
||||
"Anonymous usage report format has changed. Would you like to move to the new format?": "פורמט דוח שימוש אנונימי השתנה. האם ברצונך לעבור לפורמט החדש?",
|
||||
@@ -52,6 +53,7 @@
|
||||
"Body:": "גוף:",
|
||||
"Bugs": "באגים",
|
||||
"Cancel": "ביטול",
|
||||
"Cannot be enabled when the folder type is \"{%foldertype%}\".": "לא ניתן לאפשור כאשר סוג התיקייה הוא \"{{foldertype}}\".",
|
||||
"Changelog": "יומן שינויים",
|
||||
"Clean out after": "נקה לאחר",
|
||||
"Cleaning Versions": "מנקה גרסאות",
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"Allowed Networks": "Toegestane netwerken",
|
||||
"Alphabetic": "Alfabetisch",
|
||||
"Altered by ignoring deletes.": "Veranderd door het negeren van verwijderingen.",
|
||||
"Always turned on when the folder type is \"{%foldertype%}\".": "Altijd aanzetten als het map-type \"{{foldertype}}\" is.",
|
||||
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "Een externe opdracht regelt het versiebeheer. Hij moet het bestand verwijderen uit de gedeelde map. Als het pad naar de toepassing spaties bevat, moet dit tussen aanhalingstekens geplaatst worden.",
|
||||
"Anonymous Usage Reporting": "Anonieme gebruikersstatistieken",
|
||||
"Anonymous usage report format has changed. Would you like to move to the new format?": "Het formaat voor anonieme gebruikersrapporten is gewijzigd. Wil je naar het nieuwe formaat overschakelen?",
|
||||
@@ -52,6 +53,7 @@
|
||||
"Body:": "Inhoud:",
|
||||
"Bugs": "Bugs",
|
||||
"Cancel": "Annuleren",
|
||||
"Cannot be enabled when the folder type is \"{%foldertype%}\".": "Kan niet aangezet worden als het map-type \"{{foldertype}}\" is.",
|
||||
"Changelog": "Wijzigingenlogboek",
|
||||
"Clean out after": "Opruimen na",
|
||||
"Cleaning Versions": "Versies opruimen",
|
||||
@@ -386,6 +388,7 @@
|
||||
"Staggered File Versioning": "Gespreid versiebeheer",
|
||||
"Start Browser": "Browser starten",
|
||||
"Statistics": "Statistieken",
|
||||
"Stay logged in": "Blijf aangemeld",
|
||||
"Stopped": "Gestopt",
|
||||
"Stores and syncs only encrypted data. Folders on all connected devices need to be set up with the same password or be of type \"{%receiveEncrypted%}\" too.": "Bewaart en synchroniseert alleen versleutelde gegevens. Mappen op alle verbonden apparaten moeten met hetzelfde wachtwoord ingesteld worden of ook van het type \"{{receiveEncrypted}}\" zijn.",
|
||||
"Subject:": "Onderwerp:",
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
"Allow Anonymous Usage Reporting?": "Permiteţi raportarea anonimă de folosire a aplicaţiei?",
|
||||
"Allowed Networks": "Rețele permise",
|
||||
"Alphabetic": "Alfabetic",
|
||||
"Altered by ignoring deletes.": "Modificat prin ignorarea ștergerilor.",
|
||||
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "O comandă externă gestionează versiunea. Trebuie să elimine fișierul din mapa partajat. Dacă calea către aplicație conține spații, ar trebui să fie pusă între ghilimele.",
|
||||
"Anonymous Usage Reporting": "Raport Anonim despre Folosirea Aplicației",
|
||||
"Anonymous usage report format has changed. Would you like to move to the new format?": "Formatul raportului de utilizare anonim s-a schimbat. Doriți să vă mutați în noul format?",
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"Allowed Networks": "Разрешённые сети",
|
||||
"Alphabetic": "По алфавиту",
|
||||
"Altered by ignoring deletes.": "Изменено, игнорируя удаления.",
|
||||
"Always turned on when the folder type is \"{%foldertype%}\".": "Всегда включено для папок с типом «{{foldertype}}».",
|
||||
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "Для версионирования используется внешняя программа. Ей нужно удалить файл из общей папки. Если путь к приложению содержит пробелы, его нужно взять в кавычки.",
|
||||
"Anonymous Usage Reporting": "Анонимный отчет об использовании",
|
||||
"Anonymous usage report format has changed. Would you like to move to the new format?": "Формат анонимных отчётов изменился. Хотите переключиться на новый формат?",
|
||||
@@ -52,6 +53,7 @@
|
||||
"Body:": "Тело:",
|
||||
"Bugs": "Ошибки",
|
||||
"Cancel": "Отмена",
|
||||
"Cannot be enabled when the folder type is \"{%foldertype%}\".": "Не может быть включено для папок с типом «{{foldertype}}».",
|
||||
"Changelog": "Журнал изменений",
|
||||
"Clean out after": "Очистить после",
|
||||
"Cleaning Versions": "Очистка версий",
|
||||
@@ -171,8 +173,8 @@
|
||||
"Folder Path": "Путь к папке",
|
||||
"Folder Status": "Статус папки",
|
||||
"Folder Type": "Тип папки",
|
||||
"Folder type \"{%receiveEncrypted%}\" can only be set when adding a new folder.": "Тип папки «{{receiveEncrypted}}» может быть указан только при создании новой папки.",
|
||||
"Folder type \"{%receiveEncrypted%}\" cannot be changed after adding the folder. You need to remove the folder, delete or decrypt the data on disk, and add the folder again.": "Тип папки «{{receiveEncrypted}}» не может быть изменён после добавления. Вам необходимо убрать папку, удалить или дешифровать данные на диске, а затем снова добавить папку.",
|
||||
"Folder type \"{%receiveEncrypted%}\" can only be set when adding a new folder.": "Тип папки «{{receiveEncrypted}}» может быть выбран только при добавлении новой папки.",
|
||||
"Folder type \"{%receiveEncrypted%}\" cannot be changed after adding the folder. You need to remove the folder, delete or decrypt the data on disk, and add the folder again.": "Тип папки «{{receiveEncrypted}}» не может быть изменён после добавления. Вам необходимо убрать папку, удалить или дешифровать данные на диске и затем снова её добавить.",
|
||||
"Folders": "Папки",
|
||||
"For the following folders an error occurred while starting to watch for changes. It will be retried every minute, so the errors might go away soon. If they persist, try to fix the underlying issue and ask for help if you can't.": "Для следующих папок произошла ошибка при запуске отслеживания изменений. Попытки будут повторяться раз в минуту, и ошибки скоро могут быть устранены. Если этого не произойдёт, попробуйте разобраться в причинах и попросите поддержки, если у вас не получится.",
|
||||
"Forever": "Вечно",
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"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": "АПИ кључ",
|
||||
"About": "Информације",
|
||||
"Action": "Радња",
|
||||
"Actions": "Радње",
|
||||
"Active filter rules": "Активна правила филтера",
|
||||
"Add": "Додај",
|
||||
"Add Device": "Додај уређај",
|
||||
"Add Folder": "Додај фасциклу",
|
||||
"Add Remote Device": "Додаај удаљени уређај",
|
||||
"Add devices from the introducer to our device list, for mutually shared folders.": "Додај уређаје од иницијатора на нашу листу уређаја, за међусобно дељене фасцикле.",
|
||||
"Add filter entry": "Додај ставку филтера",
|
||||
"Add ignore patterns": "Додај правила за игнорисање",
|
||||
"Add new folder?": "Додај нову фасциклу?",
|
||||
"Additionally the full rescan interval will be increased (times 60, i.e. new default of 1h). You can also configure it manually for every folder later after choosing No.": "Додатно, интервал потпуног поновног скенирања ће бити повећан (60 пута, тј. нови подразумевани интервал од 1 сат). Такође можете ручно да га подесите за сваку фасциклу касније након што изаберете Не.",
|
||||
"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.": "Промењено због игнорисања брисања.",
|
||||
"Always turned on when the folder type is \"{%foldertype%}\".": "Увек укључено када је тип фасцикле „{{foldertype}}\".",
|
||||
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "Екстерна команда управља верзионирањем. Она мора да уклони фајл из дељене фасцикле. Ако путања до апликације садржи размаке, треба да буде под наводницима.",
|
||||
"Anonymous Usage Reporting": "Анонимно слање података о употреби",
|
||||
"Anonymous usage report format has changed. Would you like to move to the new format?": "Формат анонимног слања података о коришћењу је промењен. Желите ли да пређете на нови формат?",
|
||||
"Applied to LAN": "Важи за локалну мрежу",
|
||||
"Apply": "Примени"
|
||||
}
|
||||
@@ -27,6 +27,7 @@
|
||||
"Allowed Networks": "Tillåtna nätverk",
|
||||
"Alphabetic": "Alfabetisk",
|
||||
"Altered by ignoring deletes.": "Ändrad genom att ignorera borttagningar.",
|
||||
"Always turned on when the folder type is \"{%foldertype%}\".": "Alltid på när mapptypen är \"{{foldertype}}\".",
|
||||
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "Ett externt kommando hanterar versionen. Det måste ta bort filen från den delade mappen. Om sökvägen till applikationen innehåller mellanslag bör den citeras.",
|
||||
"Anonymous Usage Reporting": "Anonym användarstatistiksrapportering",
|
||||
"Anonymous usage report format has changed. Would you like to move to the new format?": "Anonymt användningsrapportformat har ändrats. Vill du flytta till det nya formatet?",
|
||||
@@ -52,6 +53,7 @@
|
||||
"Body:": "Meddelande:",
|
||||
"Bugs": "Felrapporter",
|
||||
"Cancel": "Avbryt",
|
||||
"Cannot be enabled when the folder type is \"{%foldertype%}\".": "Kan inte aktiveras när mapptypen är \"{{foldertype}}\".",
|
||||
"Changelog": "Ändringslogg",
|
||||
"Clean out after": "Rensa efteråt",
|
||||
"Cleaning Versions": "Rensningsversioner",
|
||||
|
||||
@@ -874,11 +874,11 @@
|
||||
<td ng-if="!connections[deviceCfg.deviceID].connected" class="text-right">
|
||||
<span ng-repeat="addr in deviceCfg.addresses">
|
||||
<span tooltip data-original-title="{{'Configured' | translate}}">{{addr}}</span><br>
|
||||
<small ng-if="system.lastDialStatus[addr].error" tooltip data-original-title="{{system.lastDialStatus[addr].error}}" class="text-danger">{{abbreviatedError(addr)}}<br></small>
|
||||
<small ng-if="system.lastDialStatus[addr].error && !deviceCfg.paused" tooltip data-original-title="{{system.lastDialStatus[addr].error}}" class="text-danger">{{abbreviatedError(addr)}}<br></small>
|
||||
</span>
|
||||
<span ng-repeat="addr in discoveryCache[deviceCfg.deviceID].addresses">
|
||||
<span tooltip data-original-title="{{'Discovered' | translate}}">{{addr}}</span><br>
|
||||
<small ng-if="system.lastDialStatus[addr].error" tooltip data-original-title="{{system.lastDialStatus[addr].error}}" class="text-danger">{{abbreviatedError(addr)}}<br></small>
|
||||
<small ng-if="system.lastDialStatus[addr].error && !deviceCfg.paused" tooltip data-original-title="{{system.lastDialStatus[addr].error}}" class="text-danger">{{abbreviatedError(addr)}}<br></small>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<h4 class="text-center" translate>The Syncthing Authors</h4>
|
||||
<div class="row">
|
||||
<div class="col-md-12" id="contributor-list">
|
||||
Jakob Borg, Audrius Butkevicius, Jesse Lucas, Simon Frei, Tomasz Wilczyński, Alexander Graf, Alexandre Viau, Anderson Mesquita, André Colomb, Antony Male, Ben Schulz, Caleb Callaway, Daniel Harte, Emil Lundberg, Eric P, Evgeny Kuznetsov, Lars K.W. Gohlke, Lode Hoste, Michael Ploujnikov, Nate Morrison, Philippe Schommers, Ross Smith II, Ryan Sullivan, Sergey Mishin, Stefan Tatschner, Wulf Weich, bt90, greatroar, Aaron Bieber, Adam Piggott, Adel Qalieh, Alan Pope, Alberto Donato, Aleksey Vasenev, Alessandro G., Alex Ionescu, Alex Lindeman, Alex Xu, Alexander Seiler, Alexandre Alves, Aman Gupta, Anatoli Babenia, Andreas Sommer, Andrew Dunham, Andrew Meyer, Andrew Rabert, Andrey D, Anjan Momi, Anthony Goeckner, Antoine Lamielle, Anur, Aranjedeath, Arkadiusz Tymiński, Aroun, Arthur Axel fREW Schmidt, Artur Zubilewicz, Aurélien Rainone, BAHADIR YILMAZ, Bart De Vries, Beat Reichenbach, Ben Curthoys, Ben Shepherd, Ben Sidhom, Benedikt Heine, Benedikt Morbach, Benjamin Nater, Benno Fünfstück, Benny Ng, Boqin Qin, Boris Rybalkin, Brandon Philips, Brendan Long, Brian R. Becker, Carsten Hagemann, Catfriend1, Cathryne Linenweaver, Cedric Staniewski, Chih-Hsuan Yen, Choongkyu, Chris Howie, Chris Joel, Chris Tonkinson, Christian Kujau, Christian Prescott, Colin Kennedy, Cromefire_, Cyprien Devillez, Dale Visser, Dan, Daniel Barczyk, Daniel Bergmann, Daniel Martí, Daniel Padrta, Darshil Chanpura, David Rimmer, DeflateAwning, Denis A., Dennis Wilson, DerRockWolf, Devon G. Redekopp, Dimitri Papadopoulos Orfanos, Dmitry Saveliev, Domenic Horner, Dominik Heidler, Elias Jarlebring, Elliot Huffman, Emil Hessman, Eng Zer Jun, Eric Lesiuta, Erik Meitner, Evan Spensley, Federico Castagnini, Felix, Felix Ableitner, Felix Lampe, Felix Unterpaintner, Francois-Xavier Gsell, Frank Isemann, Gahl Saraf, Gilli Sigurdsson, Gleb Sinyavskiy, Graham Miln, Greg, Gusted, Han Boetes, HansK-p, Harrison Jones, Heiko Zuerker, Hireworks, Hugo Locurcio, Iain Barnett, Ian Johnson, Ikko Ashimine, Ilya Brin, Iskander Sharipov, Jaakko Hannikainen, Jacek Szafarkiewicz, Jack Croft, Jacob, Jake Peterson, James O'Beirne, James Patterson, Jaroslav Lichtblau, Jaroslav Malec, Jaspitta, Jauder Ho, Jaya Chithra, Jaya Kumar, Jeffery To, Jens Diemer, Jerry Jacobs, Jochen Voss, Johan Andersson, Johan Vromans, John Rinehart, Jonas Thelemann, Jonathan, Jonathan Cross, Jonta, Jose Manuel Delicado, Julian Lehrhuber, Jörg Thalheim, Jędrzej Kula, K.B.Dharun Krishna, Kalle Laine, Kapil Sareen, Karol Różycki, Kebin Liu, Keith Harrison, Keith Turner, Kelong Cong, Ken'ichi Kamada, Kevin Allen, Kevin Bushiri, Kevin White, Jr., Kurt Fitzner, LSmithx2, Lars Lehtonen, Laurent Arnoud, Laurent Etiemble, Leo Arias, Liu Siyuan, Lord Landon Agahnim, Lukas Lihotzki, Luke Hamburg, Majed Abdulaziz, Marc Laporte, Marc Pujol, Marcin Dziadus, Marcus B Spencer, Marcus Legendre, Mario Majila, Mark Pulford, Martchus, Martin Polehla, Mateusz Naściszewski, Mateusz Ż, Matic Potočnik, Matt Burke, Matt Robenolt, Matteo Ruina, Maurizio Tomasi, Max, Max Schulze, MaximAL, Maxime Thirouin, Maximilian, MichaIng, Michael Jephcote, Michael Rienstra, Michael Tilli, Migelo, Mike Boone, MikeLund, MikolajTwarog, Mingxuan Lin, Naveen, Nicholas Rishel, Nick Busey, Nico Stapelbroek, Nicolas Braud-Santoni, Nicolas Perraut, Niels Peter Roest, Nils Jakobi, NinoM4ster, Nitroretro, NoLooseEnds, Oliver Freyermuth, Otiel, Oyebanji Jacob Mayowa, Pablo, Pascal Jungblut, Paul Brit, Pawel Palenica, Paweł Rozlach, Peter Badida, Peter Dave Hello, Peter Hoeg, Peter Marquardt, Phani Rithvij, Phil Davis, Phill Luby, Pier Paolo Ramon, Piotr Bejda, Pramodh KP, Quentin Hibon, Rahmi Pruitt, Richard Hartmann, Robert Carosi, Roberto Santalla, Robin Schoonover, Roman Zaynetdinov, Ruslan Yevdokymov, Ryan Qian, Sacheendra Talluri, Scott Klupfel, Sertonix, Severin von Wnuck-Lipinski, Shaarad Dalvi, Simon Mwepu, Simon Pickup, Sly_tom_cat, Sonu Kumar Saw, Stefan Kuntz, Steven Eckhoff, Suhas Gundimeda, Sven Bachmann, Sébastien WENSKE, Taylor Khan, Terrance, Thomas, Thomas Hipp, Tim Abell, Tim Howes, Tim Nordenfur, Tobias Frölich, Tobias Klauser, Tobias Nygren, Tobias Tom, Tom Jakubowski, Tommy Thorn, Tommy van der Vorst, Tully Robinson, Tyler Brazier, Tyler Kropp, Unrud, Veeti Paananen, Victor Buinsky, Vik, Vil Brekin, Vladimir Rusinov, WangXi, Will Rouesnel, William A. Kennington III, Xavier O., Yannic A., andresvia, andyleap, boomsquared, chenrui, chucic, cjc7373, cui fliter, d-volution, dashangcun, derekriemer, desbma, diemade, digital, entity0xfe, georgespatton, ghjklw, guangwu, gudvinr, ignacy123, janost, jaseg, jelle van der Waa, jtagcat, klemens, kylosus, luchenhan, luzpaz, marco-m, mathias4833, maxice8, mclang, mv1005, nf, orangekame3, otbutz, overkill, perewa, polyfloyd, red_led, rubenbe, sec65, vapatel2, villekalliomaki, wangguoliang, wouter bolsterlee, xarx00, xjtdy888, 佛跳墙, 落心
|
||||
Jakob Borg, Audrius Butkevicius, Simon Frei, Tomasz Wilczyński, Alexander Graf, Alexandre Viau, Anderson Mesquita, André Colomb, Antony Male, Ben Schulz, bt90, Caleb Callaway, Daniel Harte, Emil Lundberg, Eric P, Evgeny Kuznetsov, greatroar, Lars K.W. Gohlke, Lode Hoste, Michael Ploujnikov, Ross Smith II, Stefan Tatschner, Wulf Weich, Adam Piggott, Adel Qalieh, Aleksey Vasenev, Alessandro G., Alex Ionescu, Alex Lindeman, Alex Xu, Alexander Seiler, Alexandre Alves, Aman Gupta, Andreas Sommer, andresvia, Andrew Rabert, Andrey D, andyleap, Anjan Momi, Anthony Goeckner, Antoine Lamielle, Anur, Aranjedeath, ardevd, Arkadiusz Tymiński, Aroun, Arthur Axel fREW Schmidt, Artur Zubilewicz, Ashish Bhate, Aurélien Rainone, BAHADIR YILMAZ, Bart De Vries, Beat Reichenbach, Ben Shepherd, Ben Sidhom, Benedikt Heine, Benno Fünfstück, Benny Ng, boomsquared, Boqin Qin, Boris Rybalkin, Brendan Long, Catfriend1, Cathryne Linenweaver, Cedric Staniewski, Chih-Hsuan Yen, Choongkyu, Chris Howie, Chris Joel, Christian Kujau, Christian Prescott, chucic, cjc7373, Colin Kennedy, Cromefire_, Cyprien Devillez, d-volution, Dan, Daniel Barczyk, Daniel Bergmann, Daniel Martí, Daniel Padrta, Darshil Chanpura, dashangcun, David Rimmer, DeflateAwning, Denis A., Dennis Wilson, derekriemer, DerRockWolf, desbma, Devon G. Redekopp, digital, Dimitri Papadopoulos Orfanos, Dmitry Saveliev, domain, Domenic Horner, Dominik Heidler, Elias Jarlebring, Elliot Huffman, Emil Hessman, Eng Zer Jun, entity0xfe, Eric Lesiuta, Erik Meitner, Evan Spensley, Federico Castagnini, Felix, Felix Ableitner, Felix Lampe, Felix Unterpaintner, Francois-Xavier Gsell, Frank Isemann, Gahl Saraf, georgespatton, ghjklw, Gilli Sigurdsson, Gleb Sinyavskiy, Graham Miln, Greg, guangwu, gudvinr, Gusted, Han Boetes, HansK-p, Harrison Jones, Hazem Krimi, Heiko Zuerker, Hireworks, Hugo Locurcio, Iain Barnett, Ian Johnson, ignacy123, Iskander Sharipov, Jaakko Hannikainen, Jack Croft, Jacob, Jake Peterson, James O'Beirne, James Patterson, Jaroslav Lichtblau, Jaroslav Malec, Jaspitta, Jaya Chithra, Jaya Kumar, Jeffery To, jelle van der Waa, Jens Diemer, Jochen Voss, Johan Vromans, John Rinehart, Jonas Thelemann, Jonathan, Jose Manuel Delicado, jtagcat, Julian Lehrhuber, Jörg Thalheim, Jędrzej Kula, Kapil Sareen, Karol Różycki, Kebin Liu, Keith Harrison, Kelong Cong, Ken'ichi Kamada, Kevin Allen, Kevin Bushiri, Kevin White, Jr., klemens, Kurt Fitzner, kylosus, Lars Lehtonen, Laurent Etiemble, Leo Arias, Liu Siyuan, Lord Landon Agahnim, LSmithx2, Lukas Lihotzki, Luke Hamburg, luzpaz, Majed Abdulaziz, Marc Laporte, Marcel Meyer, Marcin Dziadus, Marcus B Spencer, Marcus Legendre, Mario Majila, Mark Pulford, Martchus, Mateusz Naściszewski, Mateusz Ż, mathias4833, Matic Potočnik, Matt Burke, Matt Robenolt, Matteo Ruina, Maurizio Tomasi, Max, Max Schulze, MaximAL, Maximilian, Michael Jephcote, Michael Rienstra, MichaIng, Migelo, Mike Boone, MikeLund, MikolajTwarog, Mingxuan Lin, mv1005, Nate Morrison, nf, Nicholas Rishel, Nick Busey, Nico Stapelbroek, Nicolas Braud-Santoni, Nicolas Perraut, Niels Peter Roest, Nils Jakobi, NinoM4ster, Nitroretro, NoLooseEnds, Oliver Freyermuth, orangekame3, otbutz, overkill, Oyebanji Jacob Mayowa, Pablo, Pascal Jungblut, Paul Brit, Paul Donald, Pawel Palenica, perewa, Peter Badida, Peter Dave Hello, Peter Hoeg, Peter Marquardt, Phani Rithvij, Phil Davis, Philippe Schommers, Phill Luby, Piotr Bejda, polyfloyd, pullmerge, Quentin Hibon, Rahmi Pruitt, red_led, Robert Carosi, Roberto Santalla, Robin Schoonover, Roman Zaynetdinov, rubenbe, Ruslan Yevdokymov, Ryan Qian, Ryan Sullivan, Sacheendra Talluri, Scott Klupfel, sec65, Sergey Mishin, Sertonix, Severin von Wnuck-Lipinski, Shaarad Dalvi, Simon Mwepu, Simon Pickup, Sly_tom_cat, Sonu Kumar Saw, Stefan Kuntz, Steven Eckhoff, Suhas Gundimeda, Sven Bachmann, Sébastien WENSKE, Taylor Khan, Terrance, TheCreeper, Thomas, Thomas Hipp, Tim Abell, Tim Howes, Tobias Frölich, Tobias Klauser, Tobias Nygren, Tobias Tom, Tom Jakubowski, Tommy van der Vorst, Tully Robinson, Tyler Brazier, Tyler Kropp, Unrud, vapatel2, Veeti Paananen, Victor Buinsky, Vik, Vil Brekin, villekalliomaki, Vladimir Rusinov, wangguoliang, WangXi, Will Rouesnel, William A. Kennington III, wouter bolsterlee, xarx00, Xavier O., xjtdy888, Yannic A., 佛跳墙, 落心
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -38,48 +38,70 @@ Jakob Borg, Audrius Butkevicius, Jesse Lucas, Simon Frei, Tomasz Wilczyński, Al
|
||||
<div id="about-includes" class="tab-pane">
|
||||
<p translate>Syncthing includes the following software or portions thereof:</p>
|
||||
<ul class="list-unstyled two-columns" id="copyright-notices">
|
||||
<li><a href="http://getbootstrap.com/">Bootstrap</a>, Copyright © 2011-2016 Twitter, Inc.</li>
|
||||
<li><a href="https://getbootstrap.com/">Bootstrap</a>, Copyright © 2011-2016 Twitter, Inc.</li>
|
||||
<li><a href="https://angularjs.org/">AngularJS</a>, Copyright © 2010-2014, 2016 Google, Inc.</li>
|
||||
<li><a href="http://www.daterangepicker.com/">Date Range Picker</a>, Copyright © 2012-2018 Dan Grossman.</li>
|
||||
<li><a href="https://www.daterangepicker.com/">Date Range Picker</a>, Copyright © 2012-2018 Dan Grossman.</li>
|
||||
<li><a href="https://github.com/mar10/fancytree">JQuery Fancytree Plugin</a>, Copyright © 2008-2018 Martin Wendt.</li>
|
||||
<li><a href="https://fontawesome.com/">Font Awesome</a>Copyright © 2024 Fonticons, Inc.</li>
|
||||
<li><a href="https://forkaweso.me/Fork-Awesome/">Fork Awesome</a>, Copyright © 2018 Dave Gandy & Fork Awesome.</li>
|
||||
<li><a href="http://jquery.com/">jQuery JavaScript Library</a>, Copyright © jQuery Foundation and other contributors.</li>
|
||||
<li><a href="http://momentjs.com/">moment.js</a>, Copyright © JS Foundation and other contributors.</li>
|
||||
<li><a href="https://evanhahn.github.io/HumanizeDuration.js/">HumanDuration.js</a>, Copyright © 2013-2024 Evan Hahn, portions copyright © 2024 Ross Smith II.</li>
|
||||
<li><a href="https://jquery.com/">jQuery JavaScript Library</a>, Copyright © jQuery Foundation and other contributors.</li>
|
||||
<li><a href="https://leafletjs.com/">leaflet.js</a>, Copyright © 2010-2025 Volodymyr Agafonkin, Copyright © 2010-2011 CloudMade.</li>
|
||||
<li><a href="https://momentjs.com/">moment.js</a>, Copyright © JS Foundation and other contributors.</li>
|
||||
<li><a href="https://golang.org/">The Go Programming Language</a>, Copyright © 2009 The Go Authors.</li>
|
||||
<li><a href="https://prometheus.io/">Prometheus</a>, Copyright © 2012-2015 The Prometheus Authors.</li>
|
||||
<li><a href="https://github.com/AudriusButkevicius/go-nat-pmp">AudriusButkevicius/go-nat-pmp</a>, Copyright © 2013 John Howard Palevich.</li>
|
||||
<li><a href="https://github.com/AudriusButkevicius/recli">AudriusButkevicius/recli</a>, Copyright © 2019 Audrius Butkevicius.</li>
|
||||
<li><a href="https://github.com/Azure/go-ntlmssp">Azure/go-ntlmssp</a>, Copyright © 2016 Microsoft.</li>
|
||||
<li><a href="https://github.com/alecthomas/kong">alecthomas/kong</a>, Copyright © 2018 Alec Thomas.</li>
|
||||
<li><a href="https://github.com/beorn7/perks">beorn7/perks</a>, Copyright © 2013 Blake Mizerany.</li>
|
||||
<li><a href="https://github.com/pierrec/lz4">pierrec/lz4</a>, Copyright © 2015 Pierre Curto.</li>
|
||||
<li><a href="https://github.com/calmh/du">calmh/du</a>, Public domain.</li>
|
||||
<li><a href="https://github.com/calmh/incontainer">calmh/incontainer</a>, Copyright © 2022 calmh.</li>
|
||||
<li><a href="https://github.com/calmh/xdr">calmh/xdr</a>, Copyright © 2014 Jakob Borg.</li>
|
||||
<li><a href="https://github.com/chmduquesne/rollinghash">chmduquesne/rollinghash</a>, Copyright © 2015 Christophe-Marie Duquesne.</li>
|
||||
<li><a href="https://github.com/d4l3k/messagediff">d4l3k/messagediff</a>, Copyright © 2015 Tristan Rice.</li>
|
||||
<li><a href="https://github.com/ccding/go-stun">ccding/go-stun</a>, Copyright © 2016 Cong Ding.</li>
|
||||
<li><a href="https://github.com/cespare/xxhash/v2">cespare/xxhash/v2</a>, Copyright © 2016 Caleb Spare.</li>
|
||||
<li><a href="https://github.com/cpuguy83/go-md2man/v2">cpuguy83/go-md2man/v2</a>, Copyright © 2014 Brian Goff.</li>
|
||||
<li><a href="https://github.com/davecgh/go-spew">davecgh/go-spew</a>, Copyright © 2012-2016 Dave Collins.</li>
|
||||
<li><a href="https://github.com/go-asn1-ber/asn1-ber">go-asn1-ber/asn1-ber</a>, Copyright © 2011-2015 Michael Mitton (mmitton@gmail.com).</li>
|
||||
<li><a href="https://github.com/go-ldap/ldap">go-ldap/ldap</a>, Copyright © 2011-2015 Michael Mitton (mmitton@gmail.com).</li>
|
||||
<li><a href="https://github.com/uber-go/automaxprocs">go.uber.org/automaxprocs</a>, Copyright © 2017 Uber Technologies, Inc.</li>
|
||||
<li><a href="https://github.com/gobwas/glob">gobwas/glob</a>, Copyright © 2016 Sergey Kamardin.</li>
|
||||
<li><a href="https://github.com/golang/groupcache">golang/groupcache</a>, Copyright © 2013 Google Inc.</li>
|
||||
<li><a href="https://github.com/golang/protobuf">golang/protobuf</a>, Copyright © 2010 The Go Authors.</li>
|
||||
<li><a href="https://github.com/gofrs/flock">gofrs/flock</a>, Copyright © 2018-2025, The Gofrs.</li>
|
||||
<li><a href="https://github.com/golang/snappy">golang/snappy</a>, Copyright © 2011 The Snappy-Go Authors.</li>
|
||||
<li><a href="https://github.com/protocolbuffers/protobuf-go">google.golang.org/protobuf</a>, Copyright © 2018 The Go Authors.</li>
|
||||
<li><a href="https://github.com/google/uuid">google/uuid</a>, Copyright © 2009,2014 Google Inc.</li>
|
||||
<li><a href="https://gopkg.in/yaml.v3">gopkg.in/yaml.v3</a>, Copyright © 2025, the gopkg.in/yaml.v3 authors.</li>
|
||||
<li><a href="https://github.com/hashicorp/errwrap">hashicorp/errwrap</a>, Copyright © 2014 HashiCorp, Inc.</li>
|
||||
<li><a href="https://github.com/hashicorp/go-multierror">hashicorp/go-multierror</a>, Copyright © 2014 HashiCorp, Inc.</li>
|
||||
<li><a href="https://github.com/hashicorp/golang-lru">hashicorp/golang-lru</a>, Copyright © 2014 HashiCorp, Inc.</li>
|
||||
<li><a href="https://github.com/jackpal/gateway">jackpal/gateway</a>, Copyright © 2010 Jack Palevich.</li>
|
||||
<li><a href="https://github.com/jmoiron/sqlx">jmoiron/sqlx</a>, Copyright © 2013 Jason Moiron.</li>
|
||||
<li><a href="https://github.com/jackpal/go-nat-pmp">jackpal/go-nat-pmp</a>, Copyright 2013 John Howard Palevich.</li>
|
||||
<li><a href="https://github.com/jmoiron/sqlx">jmoiron/sqlx</a>, Copyright © 2013, Jason Moiron.</li>
|
||||
<li><a href="https://github.com/julienschmidt/httprouter">julienschmidt/httprouter</a>, Copyright © 2013, Julien Schmidt.</li>
|
||||
<li><a href="https://github.com/kballard/go-shellquote">kballard/go-shellquote</a>, Copyright © 2014 Kevin Ballard.</li>
|
||||
<li><a href="https://github.com/mattn/go-isatty">mattn/go-isatty</a>, Copyright © Yasuhiro MATSUMOTO.</li>
|
||||
<li><a href="https://github.com/mattn/go-sqlite3">mattn/go-sqlite3</a>, Copyright © 2014 Yasuhiro Matsumoto</li>
|
||||
<li><a href="https://github.com/matttproud/golang_protobuf_extensions">matttproud/golang_protobuf_extensions</a>, Copyright © 2012 Matt T. Proud.</li>
|
||||
<li><a href="https://modernc.org/sqlite">modernc.org/sqlite</a>, Copyright © 2017 The Sqlite Authors</li>
|
||||
<li><a href="https://github.com/oschwald/geoip2-golang">oschwald/geoip2-golang</a>, Copyright © 2015, Gregory J. Oschwald.</li>
|
||||
<li><a href="https://github.com/oschwald/maxminddb-golang">oschwald/maxminddb-golang</a>, Copyright © 2015, Gregory J. Oschwald.</li>
|
||||
<li><a href="https://github.com/petermattis/goid">petermattis/goid</a>, Copyright © 2015-2016 Peter Mattis.</li>
|
||||
<li><a href="https://github.com/mattn/go-sqlite3">mattn/go-sqlite3</a>, Copyright © 2014 Yasuhiro Matsumoto.</li>
|
||||
<li><a href="https://github.com/miscreant/miscreant.go">miscreant/miscreant.go</a>, Copyright © 2017-2019 The Miscreant Developers.</li>
|
||||
<li><a href="https://github.com/munnerz/goautoneg">munnerz/goautoneg</a>, Copyright © 2011, Open Knowledge Foundation Ltd.</li>
|
||||
<li><a href="https://github.com/pierrec/lz4">pierrec/lz4</a>, Copyright © 2015 Pierre Curto.</li>
|
||||
<li><a href="https://github.com/pkg/errors">pkg/errors</a>, Copyright © 2015, Dave Cheney.</li>
|
||||
<li><a href="https://github.com/pmezard/go-difflib">pmezard/go-difflib</a>, Copyright © 2013, Patrick Mezard.</li>
|
||||
<li><a href="https://github.com/posener/complete">posener/complete</a>, Copyright © 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 © 2025, the prometheus/client_model authors.</li>
|
||||
<li><a href="https://github.com/prometheus/common">prometheus/common</a>, Copyright © 2025, the prometheus/common authors.</li>
|
||||
<li><a href="https://github.com/prometheus/procfs">prometheus/procfs</a>, Copyright © 2025, the prometheus/procfs authors.</li>
|
||||
<li><a href="https://github.com/quic-go/quic-go">quic-go/quic-go</a>, Copyright © 2016 the quic-go authors & Google, Inc.</li>
|
||||
<li><a href="https://github.com/rcrowley/go-metrics">rcrowley/go-metrics</a>, Copyright © 2012 Richard Crowley.</li>
|
||||
<li><a href="https://github.com/sasha-s/go-deadlock">sasha-s/go-deadlock</a>, Copyright © 2016 sasha-s.</li>
|
||||
<li><a href="https://github.com/syncthing/notify">syncthing/notify</a>, Copyright © 2014-2015 The Notify Authors.</li>
|
||||
<li><a href="https://github.com/riywo/loginshell">riywo/loginshell</a>, Copyright © 2019 Ryosuke IWANAGA.</li>
|
||||
<li><a href="https://github.com/russross/blackfriday/v2">russross/blackfriday/v2</a>, Copyright © 2011 Russ Ross.</li>
|
||||
<li><a href="https://github.com/shirou/gopsutil">shirou/gopsutil</a>, Copyright © 2014, WAKAYAMA Shirou.</li>
|
||||
<li><a href="https://github.com/stretchr/objx">stretchr/objx</a>, Copyright © 2014 Stretchr, Inc.</li>
|
||||
<li><a href="https://github.com/stretchr/testify">stretchr/testify</a>, Copyright © 2012-2020 Mat Ryer, Tyler Bunnell and contributors.</li>
|
||||
<li><a href="https://github.com/syndtr/goleveldb">syndtr/goleveldb</a>, Copyright © 2012 Suryandaru Triandana.</li>
|
||||
<li><a href="https://github.com/thejerf/suture">thejerf/suture</a>, Copyright © 2014-2015 Barracuda Networks, Inc.</li>
|
||||
<li><a href="https://github.com/urfave/cli">urfave/cli</a>, Copyright © 2016 Jeremy Saenz & Contributors.</li>
|
||||
<li><a href="https://github.com/tklauser/go-sysconf">tklauser/go-sysconf</a>, Copyright © 2018-2022, Tobias Klauser.</li>
|
||||
<li><a href="https://github.com/tklauser/numcpus">tklauser/numcpus</a>, Copyright © 2018-2024 Tobias Klauser.</li>
|
||||
<li><a href="https://github.com/urfave/cli">urfave/cli</a>, Copyright © 2016 Jeremy Saenz & Contributors.</li>
|
||||
<li><a href="https://github.com/vitrun/qart">vitrun/qart</a>, Copyright © 2010-2011 The Go Authors.</li>
|
||||
<li><a href="https://gopkg.in/asn1-ber.v1">gopkg.in/asn1-ber.v1</a>, Copyright © 2011-2015 Michael Mitton, portions Copyright © 2015-2016 go-asn1-ber Authors.</li>
|
||||
<li><a href="https://gopkg.in/ldap.v2">gopkg.in/ldap.v2</a>, Copyright © 2011-2015 Michael Mitton, portions Copyright © 2015-2016 go-ldap Authors.</li>
|
||||
<li><a href="https://golang.org">The Go Programming Language</a>, Copyright © 2009 The Go Authors.</li>
|
||||
<li>Font Awesome by Dave Gandy - <a href="http://fontawesome.io/">http://fontawesome.io</a></li>
|
||||
<li><a href="https://github.com/willabides/kongplete">willabides/kongplete</a>, Copyright © 2020 WillAbides.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -16,6 +16,14 @@ angular.module('syncthing.core')
|
||||
},
|
||||
link: function (scope, element, attrs) {
|
||||
|
||||
$(element).on('click', function (event) {
|
||||
const closestTabAnchor = event.target.closest('a[data-toggle="tab"]');
|
||||
|
||||
if (closestTabAnchor && closestTabAnchor.href.includes('#')) {
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
// before modal show animation
|
||||
$(element).on('show.bs.modal', function () {
|
||||
|
||||
|
||||
@@ -1,27 +1,39 @@
|
||||
angular.module('syncthing.core')
|
||||
.filter('uncamel', function () {
|
||||
const reservedStrings = [
|
||||
'IDs', 'ID', // substrings must come AFTER longer keywords containing them
|
||||
'URL', 'UR',
|
||||
'API', 'QUIC', 'TCP', 'UDP', 'NAT', 'LAN', 'WAN',
|
||||
'KiB', 'MiB', 'GiB', 'TiB'
|
||||
];
|
||||
return function (input) {
|
||||
input = input.replace(/(.)([A-Z][a-z]+)/g, '$1 $2').replace(/([a-z0-9])([A-Z])/g, '$1 $2');
|
||||
var parts = input.split(' ');
|
||||
var lastPart = parts.splice(-1)[0];
|
||||
if (!input || typeof input !== 'string') return '';
|
||||
const placeholders = {};
|
||||
let counter = 0;
|
||||
reservedStrings.forEach(word => {
|
||||
const placeholder = `__RSV${counter}__`;
|
||||
const re = new RegExp(word, 'g');
|
||||
input = input.replace(re, placeholder);
|
||||
placeholders[placeholder] = word;
|
||||
counter++;
|
||||
});
|
||||
input = input.replace(/([a-z0-9])([A-Z])/g, '$1 $2');
|
||||
Object.entries(placeholders).forEach(([ph, word]) => {
|
||||
input = input.replace(new RegExp(ph, 'g'), ` ${word} `);
|
||||
});
|
||||
let parts = input.split(' ');
|
||||
const lastPart = parts.pop();
|
||||
switch (lastPart) {
|
||||
case "S":
|
||||
parts.push('(seconds)');
|
||||
break;
|
||||
case "M":
|
||||
parts.push('(minutes)');
|
||||
break;
|
||||
case "H":
|
||||
parts.push('(hours)');
|
||||
break;
|
||||
case "Ms":
|
||||
parts.push('(milliseconds)');
|
||||
break;
|
||||
default:
|
||||
parts.push(lastPart);
|
||||
break;
|
||||
case 'S': parts.push('(seconds)'); break;
|
||||
case 'M': parts.push('(minutes)'); break;
|
||||
case 'H': parts.push('(hours)'); break;
|
||||
case 'Ms': parts.push('(milliseconds)'); break;
|
||||
default: parts.push(lastPart); break;
|
||||
}
|
||||
input = parts.join(' ');
|
||||
return input.charAt(0).toUpperCase() + input.slice(1);
|
||||
parts = parts.map(part => {
|
||||
const match = reservedStrings.find(w => w.toUpperCase() === part.toUpperCase());
|
||||
return match || part.charAt(0).toUpperCase() + part.slice(1);
|
||||
});
|
||||
return parts.join(' ').replace(/\s+/g, ' ').trim();
|
||||
};
|
||||
});
|
||||
|
||||
@@ -210,6 +210,17 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input id="LimitBandwidthInLan" type="checkbox" ng-model="tmpOptions.limitBandwidthInLan" /> <span translate>Limit Bandwidth in LAN</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
// Copyright (C) 2025 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 azureblob
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
stblob "github.com/syncthing/syncthing/internal/blob"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blockblob"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/container"
|
||||
)
|
||||
|
||||
var _ stblob.Store = (*BlobStore)(nil)
|
||||
|
||||
type BlobStore struct {
|
||||
client *azblob.Client
|
||||
container string
|
||||
}
|
||||
|
||||
func NewBlobStore(accountName, accountKey, containerName string) (*BlobStore, error) {
|
||||
credential, err := azblob.NewSharedKeyCredential(accountName, accountKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
url := "https://" + accountName + ".blob.core.windows.net/"
|
||||
sc, err := azblob.NewClientWithSharedKeyCredential(url, credential, &azblob.ClientOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// This errors when the container already exists, which we ignore.
|
||||
_, _ = sc.CreateContainer(context.Background(), containerName, &container.CreateOptions{})
|
||||
return &BlobStore{
|
||||
client: sc,
|
||||
container: containerName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *BlobStore) Upload(ctx context.Context, key string, data io.Reader) error {
|
||||
_, err := a.client.UploadStream(ctx, a.container, key, data, &blockblob.UploadStreamOptions{})
|
||||
return err
|
||||
}
|
||||
|
||||
func (a *BlobStore) Download(ctx context.Context, key string, w stblob.Writer) error {
|
||||
resp, err := a.client.DownloadStream(ctx, a.container, key, &blob.DownloadStreamOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
_, err = io.Copy(w, resp.Body)
|
||||
return err
|
||||
}
|
||||
|
||||
func (a *BlobStore) LatestKey(ctx context.Context) (string, error) {
|
||||
opts := &azblob.ListBlobsFlatOptions{}
|
||||
pager := a.client.NewListBlobsFlatPager(a.container, opts)
|
||||
var latest string
|
||||
var lastModified time.Time
|
||||
for pager.More() {
|
||||
page, err := pager.NextPage(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for _, blob := range page.Segment.BlobItems {
|
||||
if latest == "" || blob.Properties.LastModified.After(lastModified) {
|
||||
latest = *blob.Name
|
||||
lastModified = *blob.Properties.LastModified
|
||||
}
|
||||
}
|
||||
}
|
||||
return latest, nil
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright (C) 2025 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 blob
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
)
|
||||
|
||||
type Store interface {
|
||||
Upload(ctx context.Context, key string, r io.Reader) error
|
||||
Download(ctx context.Context, key string, w Writer) error
|
||||
LatestKey(ctx context.Context) (string, error)
|
||||
}
|
||||
|
||||
type Writer interface {
|
||||
io.Writer
|
||||
io.WriterAt
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
package s3
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
@@ -15,8 +16,11 @@ import (
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/s3"
|
||||
"github.com/aws/aws-sdk-go/service/s3/s3manager"
|
||||
"github.com/syncthing/syncthing/internal/blob"
|
||||
)
|
||||
|
||||
var _ blob.Store = (*Session)(nil)
|
||||
|
||||
type Session struct {
|
||||
bucket string
|
||||
s3sess *session.Session
|
||||
@@ -26,9 +30,10 @@ type Object = s3.Object
|
||||
|
||||
func NewSession(endpoint, region, bucket, accessKeyID, secretKey string) (*Session, error) {
|
||||
sess, err := session.NewSession(&aws.Config{
|
||||
Region: aws.String(region),
|
||||
Endpoint: aws.String(endpoint),
|
||||
Credentials: credentials.NewStaticCredentials(accessKeyID, secretKey, ""),
|
||||
Region: aws.String(region),
|
||||
Endpoint: aws.String(endpoint),
|
||||
Credentials: credentials.NewStaticCredentials(accessKeyID, secretKey, ""),
|
||||
S3ForcePathStyle: aws.Bool(true),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -39,7 +44,7 @@ func NewSession(endpoint, region, bucket, accessKeyID, secretKey string) (*Sessi
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Session) Upload(r io.Reader, key string) error {
|
||||
func (s *Session) Upload(_ context.Context, key string, r io.Reader) error {
|
||||
uploader := s3manager.NewUploader(s.s3sess)
|
||||
_, err := uploader.Upload(&s3manager.UploadInput{
|
||||
Bucket: aws.String(s.bucket),
|
||||
@@ -49,7 +54,31 @@ func (s *Session) Upload(r io.Reader, key string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Session) List(fn func(*Object) bool) error {
|
||||
func (s *Session) Download(_ context.Context, key string, w blob.Writer) error {
|
||||
downloader := s3manager.NewDownloader(s.s3sess)
|
||||
_, err := downloader.Download(w, &s3.GetObjectInput{
|
||||
Bucket: aws.String(s.bucket),
|
||||
Key: aws.String(key),
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Session) LatestKey(_ context.Context) (string, error) {
|
||||
var latestKey string
|
||||
var lastModified time.Time
|
||||
if err := s.list(func(obj *Object) bool {
|
||||
if latestKey == "" || obj.LastModified.After(lastModified) {
|
||||
latestKey = *obj.Key
|
||||
lastModified = *obj.LastModified
|
||||
}
|
||||
return true
|
||||
}); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return latestKey, nil
|
||||
}
|
||||
|
||||
func (s *Session) list(fn func(*Object) bool) error {
|
||||
svc := s3.New(s.s3sess)
|
||||
|
||||
opts := &s3.ListObjectsV2Input{
|
||||
@@ -75,27 +104,3 @@ func (s *Session) List(fn func(*Object) bool) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Session) LatestKey() (string, error) {
|
||||
var latestKey string
|
||||
var lastModified time.Time
|
||||
if err := s.List(func(obj *Object) bool {
|
||||
if latestKey == "" || obj.LastModified.After(lastModified) {
|
||||
latestKey = *obj.Key
|
||||
lastModified = *obj.LastModified
|
||||
}
|
||||
return true
|
||||
}); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return latestKey, nil
|
||||
}
|
||||
|
||||
func (s *Session) Download(w io.WriterAt, key string) error {
|
||||
downloader := s3manager.NewDownloader(s.s3sess)
|
||||
_, err := downloader.Download(w, &s3.GetObjectInput{
|
||||
Bucket: aws.String(s.bucket),
|
||||
Key: aws.String(key),
|
||||
})
|
||||
return err
|
||||
}
|
||||
@@ -19,9 +19,9 @@ type Counts struct {
|
||||
Symlinks int
|
||||
Deleted int
|
||||
Bytes int64
|
||||
Sequence int64 // zero for the global state
|
||||
DeviceID protocol.DeviceID // device ID for remote devices, or special values for local/global
|
||||
LocalFlags uint32 // the local flag for this count bucket
|
||||
Sequence int64 // zero for the global state
|
||||
DeviceID protocol.DeviceID // device ID for remote devices, or special values for local/global
|
||||
LocalFlags protocol.FlagLocal // the local flag for this count bucket
|
||||
}
|
||||
|
||||
func (c Counts) Add(other Counts) Counts {
|
||||
|
||||
@@ -41,8 +41,7 @@ type DB interface {
|
||||
AllLocalFilesWithPrefix(folder string, device protocol.DeviceID, prefix string) (iter.Seq[protocol.FileInfo], func() error)
|
||||
AllLocalFilesWithBlocksHash(folder string, h []byte) (iter.Seq[FileMetadata], func() error)
|
||||
AllNeededGlobalFiles(folder string, device protocol.DeviceID, order config.PullOrder, limit, offset int) (iter.Seq[protocol.FileInfo], func() error)
|
||||
AllLocalBlocksWithHash(hash []byte) ([]BlockMapEntry, error)
|
||||
AllLocalFilesWithBlocksHashAnyFolder(hash []byte) (map[string][]FileMetadata, error)
|
||||
AllLocalBlocksWithHash(folder string, hash []byte) (iter.Seq[BlockMapEntry], func() error)
|
||||
|
||||
// Cleanup
|
||||
DropAllFiles(folder string, device protocol.DeviceID) error
|
||||
@@ -88,6 +87,7 @@ type BlockMapEntry struct {
|
||||
Offset int64
|
||||
BlockIndex int
|
||||
Size int
|
||||
FileName string
|
||||
}
|
||||
|
||||
type KeyValue struct {
|
||||
@@ -100,10 +100,9 @@ type FileMetadata struct {
|
||||
Sequence int64
|
||||
ModNanos int64
|
||||
Size int64
|
||||
LocalFlags int64
|
||||
LocalFlags protocol.FlagLocal
|
||||
Type protocol.FileInfoType
|
||||
Deleted bool
|
||||
Invalid bool
|
||||
}
|
||||
|
||||
func (f *FileMetadata) ModTime() time.Time {
|
||||
@@ -121,3 +120,7 @@ func (f *FileMetadata) IsDirectory() bool {
|
||||
func (f *FileMetadata) ShouldConflict() bool {
|
||||
return f.LocalFlags&protocol.LocalConflictFlags != 0
|
||||
}
|
||||
|
||||
func (f *FileMetadata) IsInvalid() bool {
|
||||
return f.LocalFlags.IsInvalid()
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ var (
|
||||
Namespace: "syncthing",
|
||||
Subsystem: "db",
|
||||
Name: "operations_current",
|
||||
Help: "Number of database operations currently ongoing, per folder and operation",
|
||||
}, []string{"folder", "operation"})
|
||||
metricTotalOperationSeconds = promauto.NewCounterVec(prometheus.CounterOpts{
|
||||
Namespace: "syncthing",
|
||||
@@ -67,11 +68,6 @@ func (m metricsDB) AllLocalFilesWithBlocksHash(folder string, h []byte) (iter.Se
|
||||
return m.DB.AllLocalFilesWithBlocksHash(folder, h)
|
||||
}
|
||||
|
||||
func (m metricsDB) AllLocalFilesWithBlocksHashAnyFolder(hash []byte) (map[string][]FileMetadata, error) {
|
||||
defer m.account("-", "AllLocalFilesWithBlocksHashAnyFolder")()
|
||||
return m.DB.AllLocalFilesWithBlocksHashAnyFolder(hash)
|
||||
}
|
||||
|
||||
func (m metricsDB) AllGlobalFiles(folder string) (iter.Seq[FileMetadata], func() error) {
|
||||
defer m.account(folder, "AllGlobalFiles")()
|
||||
return m.DB.AllGlobalFiles(folder)
|
||||
@@ -107,9 +103,9 @@ func (m metricsDB) GetGlobalAvailability(folder, file string) ([]protocol.Device
|
||||
return m.DB.GetGlobalAvailability(folder, file)
|
||||
}
|
||||
|
||||
func (m metricsDB) AllLocalBlocksWithHash(hash []byte) ([]BlockMapEntry, error) {
|
||||
func (m metricsDB) AllLocalBlocksWithHash(folder string, hash []byte) (iter.Seq[BlockMapEntry], func() error) {
|
||||
defer m.account("-", "AllLocalBlocksWithHash")()
|
||||
return m.DB.AllLocalBlocksWithHash(hash)
|
||||
return m.DB.AllLocalBlocksWithHash(folder, hash)
|
||||
}
|
||||
|
||||
func (m metricsDB) Close() error {
|
||||
|
||||
@@ -8,7 +8,7 @@ package olddb
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"sort"
|
||||
"slices"
|
||||
|
||||
"github.com/syncthing/syncthing/internal/db/olddb/backend"
|
||||
"github.com/syncthing/syncthing/lib/sync"
|
||||
@@ -104,6 +104,6 @@ func (i *smallIndex) Values() []string {
|
||||
}
|
||||
i.mut.Unlock()
|
||||
|
||||
sort.Strings(vals)
|
||||
slices.Sort(vals)
|
||||
return vals
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
)
|
||||
|
||||
const currentSchemaVersion = 1
|
||||
const currentSchemaVersion = 3
|
||||
|
||||
//go:embed sql/**
|
||||
var embedded embed.FS
|
||||
@@ -62,6 +62,17 @@ func openBase(path string, maxConns int, pragmas, schemaScripts, migrationScript
|
||||
baseName: filepath.Base(path),
|
||||
sql: sqlDB,
|
||||
statements: make(map[string]*sqlx.Stmt),
|
||||
tplInput: map[string]any{
|
||||
"FlagLocalUnsupported": protocol.FlagLocalUnsupported,
|
||||
"FlagLocalIgnored": protocol.FlagLocalIgnored,
|
||||
"FlagLocalMustRescan": protocol.FlagLocalMustRescan,
|
||||
"FlagLocalReceiveOnly": protocol.FlagLocalReceiveOnly,
|
||||
"FlagLocalGlobal": protocol.FlagLocalGlobal,
|
||||
"FlagLocalNeeded": protocol.FlagLocalNeeded,
|
||||
"FlagLocalRemoteInvalid": protocol.FlagLocalRemoteInvalid,
|
||||
"LocalInvalidFlags": protocol.LocalInvalidFlags,
|
||||
"SyncthingVersion": build.LongVersion,
|
||||
},
|
||||
}
|
||||
|
||||
for _, script := range schemaScripts {
|
||||
@@ -96,16 +107,6 @@ func openBase(path string, maxConns int, pragmas, schemaScripts, migrationScript
|
||||
return nil, wrap(err)
|
||||
}
|
||||
|
||||
db.tplInput = map[string]any{
|
||||
"FlagLocalUnsupported": protocol.FlagLocalUnsupported,
|
||||
"FlagLocalIgnored": protocol.FlagLocalIgnored,
|
||||
"FlagLocalMustRescan": protocol.FlagLocalMustRescan,
|
||||
"FlagLocalReceiveOnly": protocol.FlagLocalReceiveOnly,
|
||||
"FlagLocalGlobal": protocol.FlagLocalGlobal,
|
||||
"FlagLocalNeeded": protocol.FlagLocalNeeded,
|
||||
"SyncthingVersion": build.LongVersion,
|
||||
}
|
||||
|
||||
return db, nil
|
||||
}
|
||||
|
||||
@@ -151,15 +152,8 @@ func (s *baseDB) stmt(tpl string) stmt {
|
||||
return stmt
|
||||
}
|
||||
|
||||
// Apply template expansions
|
||||
var sb strings.Builder
|
||||
compTpl := template.Must(template.New("tpl").Funcs(tplFuncs).Parse(tpl))
|
||||
if err := compTpl.Execute(&sb, s.tplInput); err != nil {
|
||||
panic("bug: bad template: " + err.Error())
|
||||
}
|
||||
|
||||
// Prepare and cache
|
||||
stmt, err := s.sql.Preparex(sb.String())
|
||||
stmt, err := s.sql.Preparex(s.expandTemplateVars(tpl))
|
||||
if err != nil {
|
||||
return failedStmt{err}
|
||||
}
|
||||
@@ -167,6 +161,17 @@ func (s *baseDB) stmt(tpl string) stmt {
|
||||
return stmt
|
||||
}
|
||||
|
||||
// expandTemplateVars just applies template expansions to the template
|
||||
// string, or dies trying
|
||||
func (s *baseDB) expandTemplateVars(tpl string) string {
|
||||
var sb strings.Builder
|
||||
compTpl := template.Must(template.New("tpl").Funcs(tplFuncs).Parse(tpl))
|
||||
if err := compTpl.Execute(&sb, s.tplInput); err != nil {
|
||||
panic("bug: bad template: " + err.Error())
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
type stmt interface {
|
||||
Exec(args ...any) (sql.Result, error)
|
||||
Get(dest any, args ...any) error
|
||||
@@ -211,7 +216,7 @@ nextScript:
|
||||
// separately. We require it on a separate line because there are
|
||||
// also statement-internal semicolons in the triggers.
|
||||
for _, stmt := range strings.Split(string(bs), "\n;") {
|
||||
if _, err := tx.Exec(stmt); err != nil {
|
||||
if _, err := tx.Exec(s.expandTemplateVars(stmt)); err != nil {
|
||||
return wrap(err, stmt)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/syncthing/syncthing/internal/db"
|
||||
"github.com/syncthing/syncthing/internal/itererr"
|
||||
"github.com/syncthing/syncthing/lib/config"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
"github.com/syncthing/syncthing/lib/rand"
|
||||
@@ -35,13 +34,18 @@ func (s *DB) getFolderDB(folder string, create bool) (*folderDB, error) {
|
||||
|
||||
// Check for an existing database. If we're not supposed to create the
|
||||
// folder, we don't move on if it doesn't already have a database name.
|
||||
var dbName string
|
||||
var dbns sql.NullString
|
||||
if err := s.stmt(`
|
||||
SELECT database_name FROM folders
|
||||
WHERE folder_id = ?
|
||||
`).Get(&dbName, folder); err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
`).Get(&dbns, folder); err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, wrap(err)
|
||||
}
|
||||
|
||||
var dbName string
|
||||
if dbns.Valid {
|
||||
dbName = dbns.String
|
||||
}
|
||||
if dbName == "" && !create {
|
||||
return nil, errNoSuchFolder
|
||||
}
|
||||
@@ -56,6 +60,10 @@ func (s *DB) getFolderDB(folder string, create bool) (*folderDB, error) {
|
||||
if dbName == "" {
|
||||
// First time we want to access this folder, need to create a new
|
||||
// folder ID
|
||||
|
||||
s.updateLock.Lock()
|
||||
defer s.updateLock.Unlock()
|
||||
|
||||
idx, err := s.folderIdxLocked(folder)
|
||||
if err != nil {
|
||||
return nil, wrap(err)
|
||||
@@ -145,24 +153,15 @@ func (s *DB) AllGlobalFilesPrefix(folder string, prefix string) (iter.Seq[db.Fil
|
||||
return fdb.AllGlobalFilesPrefix(prefix)
|
||||
}
|
||||
|
||||
func (s *DB) AllLocalBlocksWithHash(hash []byte) ([]db.BlockMapEntry, error) {
|
||||
var entries []db.BlockMapEntry
|
||||
err := s.forEachFolder(func(fdb *folderDB) error {
|
||||
es, err := itererr.Collect(fdb.AllLocalBlocksWithHash(hash))
|
||||
entries = append(entries, es...)
|
||||
return err
|
||||
})
|
||||
return entries, err
|
||||
}
|
||||
|
||||
func (s *DB) AllLocalFilesWithBlocksHashAnyFolder(hash []byte) (map[string][]db.FileMetadata, error) {
|
||||
res := make(map[string][]db.FileMetadata)
|
||||
err := s.forEachFolder(func(fdb *folderDB) error {
|
||||
files, err := itererr.Collect(fdb.AllLocalFilesWithBlocksHash(hash))
|
||||
res[fdb.folderID] = files
|
||||
return err
|
||||
})
|
||||
return res, err
|
||||
func (s *DB) AllLocalBlocksWithHash(folder string, hash []byte) (iter.Seq[db.BlockMapEntry], func() error) {
|
||||
fdb, err := s.getFolderDB(folder, false)
|
||||
if errors.Is(err, errNoSuchFolder) {
|
||||
return func(yield func(db.BlockMapEntry) bool) {}, func() error { return nil }
|
||||
}
|
||||
if err != nil {
|
||||
return func(yield func(db.BlockMapEntry) bool) {}, func() error { return err }
|
||||
}
|
||||
return fdb.AllLocalBlocksWithHash(hash)
|
||||
}
|
||||
|
||||
func (s *DB) AllLocalFiles(folder string, device protocol.DeviceID) (iter.Seq[protocol.FileInfo], func() error) {
|
||||
@@ -256,7 +255,7 @@ func (s *DB) ListDevicesForFolder(folder string) ([]protocol.DeviceID, error) {
|
||||
func (s *DB) RemoteSequences(folder string) (map[protocol.DeviceID]int64, error) {
|
||||
fdb, err := s.getFolderDB(folder, false)
|
||||
if errors.Is(err, errNoSuchFolder) {
|
||||
return nil, nil
|
||||
return nil, nil //nolint:nilnil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -268,6 +268,57 @@ func TestDontNeedIgnored(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDontNeedRemoteInvalid(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := OpenTemp()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
if err := db.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
// A remote file with the invalid bit set
|
||||
files := []protocol.FileInfo{
|
||||
genFile("test1", 1, 103),
|
||||
}
|
||||
files[0].LocalFlags = protocol.FlagLocalRemoteInvalid
|
||||
err = db.Update(folderID, protocol.DeviceID{42}, files)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// It's not part of the global size
|
||||
s, err := db.CountGlobal(folderID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if s.Bytes != 0 || s.Files != 0 {
|
||||
t.Log(s)
|
||||
t.Error("bad global")
|
||||
}
|
||||
|
||||
// We don't need it
|
||||
s, err = db.CountNeed(folderID, protocol.LocalDeviceID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if s.Bytes != 0 || s.Files != 0 {
|
||||
t.Log(s)
|
||||
t.Error("bad need")
|
||||
}
|
||||
|
||||
// It shouldn't show up in the need list
|
||||
names := mustCollect[protocol.FileInfo](t)(db.AllNeededGlobalFiles(folderID, protocol.LocalDeviceID, config.PullOrderAlphabetic, 0, 0))
|
||||
if len(names) != 0 {
|
||||
t.Log(names)
|
||||
t.Error("need no files")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoteDontNeedLocalIgnored(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ package sqlite
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/syncthing/syncthing/internal/itererr"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
)
|
||||
|
||||
@@ -50,7 +51,7 @@ func TestBlocks(t *testing.T) {
|
||||
|
||||
// Search for blocks
|
||||
|
||||
vals, err := db.AllLocalBlocksWithHash([]byte{1, 2, 3})
|
||||
vals, err := itererr.Collect(db.AllLocalBlocksWithHash(folderID, []byte{1, 2, 3}))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -61,26 +62,13 @@ func TestBlocks(t *testing.T) {
|
||||
t.Log(vals[0])
|
||||
t.Fatal("bad entry")
|
||||
}
|
||||
|
||||
// Get FileInfos for those blocks
|
||||
|
||||
res, err := db.AllLocalFilesWithBlocksHashAnyFolder(vals[0].BlocklistHash)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(res) != 1 {
|
||||
t.Fatal("should return one folder")
|
||||
}
|
||||
if len(res[folderID]) != 1 {
|
||||
t.Fatal("should find one file")
|
||||
}
|
||||
if res[folderID][0].Name != "file1" {
|
||||
if vals[0].FileName != "file1" {
|
||||
t.Fatal("should be file1")
|
||||
}
|
||||
|
||||
// Get the other blocks
|
||||
|
||||
vals, err = db.AllLocalBlocksWithHash([]byte{3, 4, 5})
|
||||
vals, err = itererr.Collect(db.AllLocalBlocksWithHash(folderID, []byte{3, 4, 5}))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -119,7 +107,7 @@ func TestBlocksDeleted(t *testing.T) {
|
||||
|
||||
// We should find one entry for the block hash
|
||||
search := file.Blocks[0].Hash
|
||||
es, err := sdb.AllLocalBlocksWithHash(search)
|
||||
es, err := itererr.Collect(sdb.AllLocalBlocksWithHash(folderID, search))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -134,7 +122,7 @@ func TestBlocksDeleted(t *testing.T) {
|
||||
}
|
||||
|
||||
// Searching for the old hash should yield no hits
|
||||
if hits, err := sdb.AllLocalBlocksWithHash(search); err != nil {
|
||||
if hits, err := itererr.Collect(sdb.AllLocalBlocksWithHash(folderID, search)); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if len(hits) != 0 {
|
||||
t.Log(hits)
|
||||
@@ -142,7 +130,7 @@ func TestBlocksDeleted(t *testing.T) {
|
||||
}
|
||||
|
||||
// Searching for the new hash should yield one hits
|
||||
if hits, err := sdb.AllLocalBlocksWithHash(file.Blocks[0].Hash); err != nil {
|
||||
if hits, err := itererr.Collect(sdb.AllLocalBlocksWithHash(folderID, file.Blocks[0].Hash)); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if len(hits) != 1 {
|
||||
t.Log(hits)
|
||||
|
||||
@@ -50,10 +50,14 @@ func Open(path string, opts ...Option) (*DB, error) {
|
||||
"sql/schema/common/*",
|
||||
"sql/schema/main/*",
|
||||
}
|
||||
migrations := []string{
|
||||
"sql/migrations/common/*",
|
||||
"sql/migrations/main/*",
|
||||
}
|
||||
|
||||
os.MkdirAll(path, 0o700)
|
||||
_ = os.MkdirAll(path, 0o700)
|
||||
mainPath := filepath.Join(path, "main.db")
|
||||
mainBase, err := openBase(mainPath, maxDBConns, pragmas, schemas, nil)
|
||||
mainBase, err := openBase(mainPath, maxDBConns, pragmas, schemas, migrations)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -88,10 +92,14 @@ func OpenForMigration(path string) (*DB, error) {
|
||||
"sql/schema/common/*",
|
||||
"sql/schema/main/*",
|
||||
}
|
||||
migrations := []string{
|
||||
"sql/migrations/common/*",
|
||||
"sql/migrations/main/*",
|
||||
}
|
||||
|
||||
os.MkdirAll(path, 0o700)
|
||||
_ = os.MkdirAll(path, 0o700)
|
||||
mainPath := filepath.Join(path, "main.db")
|
||||
mainBase, err := openBase(mainPath, 1, pragmas, schemas, nil)
|
||||
mainBase, err := openBase(mainPath, 1, pragmas, schemas, migrations)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -80,13 +80,15 @@ func (s *Service) periodic(ctx context.Context) error {
|
||||
t0 := time.Now()
|
||||
l.Debugln("Periodic start")
|
||||
|
||||
s.sdb.updateLock.Lock()
|
||||
defer s.sdb.updateLock.Unlock()
|
||||
|
||||
t1 := time.Now()
|
||||
defer func() { l.Debugln("Periodic done in", time.Since(t1), "+", t1.Sub(t0)) }()
|
||||
|
||||
tidy(ctx, s.sdb.sql)
|
||||
s.sdb.updateLock.Lock()
|
||||
err := tidy(ctx, s.sdb.sql)
|
||||
s.sdb.updateLock.Unlock()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return wrap(s.sdb.forEachFolder(func(fdb *folderDB) error {
|
||||
fdb.updateLock.Lock()
|
||||
@@ -98,8 +100,7 @@ func (s *Service) periodic(ctx context.Context) error {
|
||||
if err := garbageCollectBlocklistsAndBlocksLocked(ctx, fdb); err != nil {
|
||||
return wrap(err)
|
||||
}
|
||||
tidy(ctx, fdb.sql)
|
||||
return nil
|
||||
return tidy(ctx, fdb.sql)
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
// Copyright (C) 2025 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 sqlite
|
||||
|
||||
type DatabaseStatistics struct {
|
||||
Name string `json:"name"`
|
||||
FolderID string `json:"folderID,omitempty"`
|
||||
Tables []TableStatistics `json:"tables"`
|
||||
Total TableStatistics `json:"total"`
|
||||
Children []DatabaseStatistics `json:"children,omitempty"`
|
||||
}
|
||||
|
||||
type TableStatistics struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Size int64 `json:"size"`
|
||||
Unused int64 `json:"unused"`
|
||||
}
|
||||
|
||||
func (s *DB) Statistics() (*DatabaseStatistics, error) {
|
||||
ts, total, err := s.tableStats()
|
||||
if err != nil {
|
||||
return nil, wrap(err)
|
||||
}
|
||||
ds := DatabaseStatistics{
|
||||
Name: s.baseName,
|
||||
Tables: ts,
|
||||
Total: total,
|
||||
}
|
||||
|
||||
err = s.forEachFolder(func(fdb *folderDB) error {
|
||||
tables, total, err := fdb.tableStats()
|
||||
if err != nil {
|
||||
return wrap(err)
|
||||
}
|
||||
ds.Children = append(ds.Children, DatabaseStatistics{
|
||||
Name: fdb.baseName,
|
||||
FolderID: fdb.folderID,
|
||||
Tables: tables,
|
||||
Total: total,
|
||||
})
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, wrap(err)
|
||||
}
|
||||
|
||||
return &ds, nil
|
||||
}
|
||||
|
||||
func (s *baseDB) tableStats() ([]TableStatistics, TableStatistics, error) {
|
||||
var stats []TableStatistics
|
||||
if err := s.stmt(`
|
||||
SELECT name, pgsize AS size, unused FROM dbstat
|
||||
WHERE aggregate=true
|
||||
ORDER BY name
|
||||
`).Select(&stats); err != nil {
|
||||
return nil, TableStatistics{}, wrap(err)
|
||||
}
|
||||
var total TableStatistics
|
||||
for _, s := range stats {
|
||||
total.Size += s.Size
|
||||
total.Unused += s.Unused
|
||||
}
|
||||
return stats, total, nil
|
||||
}
|
||||
@@ -1084,7 +1084,7 @@ func TestInsertLargeFile(t *testing.T) {
|
||||
// Verify all the blocks are here
|
||||
|
||||
for i, block := range files[0].Blocks {
|
||||
bs, err := sdb.AllLocalBlocksWithHash(block.Hash)
|
||||
bs, err := itererr.Collect(sdb.AllLocalBlocksWithHash(folderID, block.Hash))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -39,13 +39,10 @@ func (s *folderDB) CountNeed(device protocol.DeviceID) (db.Counts, error) {
|
||||
}
|
||||
|
||||
func (s *folderDB) CountGlobal() (db.Counts, error) {
|
||||
// Exclude ignored and receive-only changed files from the global count
|
||||
// (legacy expectation? it's a bit weird since those files can in fact
|
||||
// be global and you can get them with GetGlobal etc.)
|
||||
var res []countsRow
|
||||
err := s.stmt(`
|
||||
SELECT s.type, s.count, s.size, s.local_flags, s.deleted FROM counts s
|
||||
WHERE s.local_flags & {{.FlagLocalGlobal}} != 0 AND s.local_flags & {{or .FlagLocalReceiveOnly .FlagLocalIgnored}} = 0
|
||||
WHERE s.local_flags & {{.FlagLocalGlobal}} != 0 AND s.local_flags & {{.LocalInvalidFlags}} = 0
|
||||
`).Select(&res)
|
||||
if err != nil {
|
||||
return db.Counts{}, wrap(err)
|
||||
@@ -84,7 +81,7 @@ func (s *folderDB) needSizeRemote(device protocol.DeviceID) (db.Counts, error) {
|
||||
// See neededGlobalFilesRemote for commentary as that is the same query without summing
|
||||
if err := s.stmt(`
|
||||
SELECT g.type, count(*) as count, sum(g.size) as size, g.local_flags, g.deleted FROM files g
|
||||
WHERE g.local_flags & {{.FlagLocalGlobal}} != 0 AND NOT g.deleted AND NOT g.invalid AND NOT EXISTS (
|
||||
WHERE g.local_flags & {{.FlagLocalGlobal}} != 0 AND NOT g.deleted AND g.local_flags & {{.LocalInvalidFlags}} = 0 AND NOT EXISTS (
|
||||
SELECT 1 FROM FILES f
|
||||
INNER JOIN devices d ON d.idx = f.device_idx
|
||||
WHERE f.name = g.name AND f.version = g.version AND d.device_id = ?
|
||||
@@ -94,10 +91,10 @@ func (s *folderDB) needSizeRemote(device protocol.DeviceID) (db.Counts, error) {
|
||||
UNION ALL
|
||||
|
||||
SELECT g.type, count(*) as count, sum(g.size) as size, g.local_flags, g.deleted FROM files g
|
||||
WHERE g.local_flags & {{.FlagLocalGlobal}} != 0 AND g.deleted AND NOT g.invalid AND EXISTS (
|
||||
WHERE g.local_flags & {{.FlagLocalGlobal}} != 0 AND g.deleted AND g.local_flags & {{.LocalInvalidFlags}} = 0 AND EXISTS (
|
||||
SELECT 1 FROM FILES f
|
||||
INNER JOIN devices d ON d.idx = f.device_idx
|
||||
WHERE f.name = g.name AND d.device_id = ? AND NOT f.deleted AND NOT f.invalid
|
||||
WHERE f.name = g.name AND d.device_id = ? AND NOT f.deleted AND f.local_flags & {{.LocalInvalidFlags}} = 0
|
||||
)
|
||||
GROUP BY g.type, g.local_flags, g.deleted
|
||||
`).Select(&res, device.String(),
|
||||
|
||||
@@ -74,7 +74,7 @@ func (s *folderDB) GetGlobalAvailability(file string) ([]protocol.DeviceID, erro
|
||||
|
||||
func (s *folderDB) AllGlobalFiles() (iter.Seq[db.FileMetadata], func() error) {
|
||||
it, errFn := iterStructs[db.FileMetadata](s.stmt(`
|
||||
SELECT f.sequence, f.name, f.type, f.modified as modnanos, f.size, f.deleted, f.invalid, f.local_flags as localflags FROM files f
|
||||
SELECT f.sequence, f.name, f.type, f.modified as modnanos, f.size, f.deleted, f.local_flags as localflags FROM files f
|
||||
WHERE f.local_flags & {{.FlagLocalGlobal}} != 0
|
||||
ORDER BY f.name
|
||||
`).Queryx())
|
||||
@@ -93,7 +93,7 @@ func (s *folderDB) AllGlobalFilesPrefix(prefix string) (iter.Seq[db.FileMetadata
|
||||
end := prefixEnd(prefix)
|
||||
|
||||
it, errFn := iterStructs[db.FileMetadata](s.stmt(`
|
||||
SELECT f.sequence, f.name, f.type, f.modified as modnanos, f.size, f.deleted, f.invalid, f.local_flags as localflags FROM files f
|
||||
SELECT f.sequence, f.name, f.type, f.modified as modnanos, f.size, f.deleted, f.local_flags as localflags FROM files f
|
||||
WHERE f.name >= ? AND f.name < ? AND f.local_flags & {{.FlagLocalGlobal}} != 0
|
||||
ORDER BY f.name
|
||||
`).Queryx(prefix, end))
|
||||
@@ -158,7 +158,7 @@ func (s *folderDB) neededGlobalFilesRemote(device protocol.DeviceID, selectOpts
|
||||
SELECT fi.fiprotobuf, bl.blprotobuf, g.name, g.size, g.modified FROM fileinfos fi
|
||||
INNER JOIN files g on fi.sequence = g.sequence
|
||||
LEFT JOIN blocklists bl ON bl.blocklist_hash = g.blocklist_hash
|
||||
WHERE g.local_flags & {{.FlagLocalGlobal}} != 0 AND NOT g.deleted AND NOT g.invalid AND NOT EXISTS (
|
||||
WHERE g.local_flags & {{.FlagLocalGlobal}} != 0 AND NOT g.deleted AND g.local_flags & {{.LocalInvalidFlags}} = 0 AND NOT EXISTS (
|
||||
SELECT 1 FROM FILES f
|
||||
INNER JOIN devices d ON d.idx = f.device_idx
|
||||
WHERE f.name = g.name AND f.version = g.version AND d.device_id = ?
|
||||
@@ -169,10 +169,10 @@ func (s *folderDB) neededGlobalFilesRemote(device protocol.DeviceID, selectOpts
|
||||
SELECT fi.fiprotobuf, bl.blprotobuf, g.name, g.size, g.modified FROM fileinfos fi
|
||||
INNER JOIN files g on fi.sequence = g.sequence
|
||||
LEFT JOIN blocklists bl ON bl.blocklist_hash = g.blocklist_hash
|
||||
WHERE g.local_flags & {{.FlagLocalGlobal}} != 0 AND g.deleted AND NOT g.invalid AND EXISTS (
|
||||
WHERE g.local_flags & {{.FlagLocalGlobal}} != 0 AND g.deleted AND g.local_flags & {{.LocalInvalidFlags}} = 0 AND EXISTS (
|
||||
SELECT 1 FROM FILES f
|
||||
INNER JOIN devices d ON d.idx = f.device_idx
|
||||
WHERE f.name = g.name AND d.device_id = ? AND NOT f.deleted AND NOT f.invalid
|
||||
WHERE f.name = g.name AND d.device_id = ? AND NOT f.deleted AND f.local_flags & {{.LocalInvalidFlags}} = 0
|
||||
)
|
||||
`+selectOpts).Queryx(
|
||||
device.String(),
|
||||
|
||||
@@ -89,7 +89,7 @@ func (s *folderDB) AllLocalFilesWithPrefix(device protocol.DeviceID, prefix stri
|
||||
|
||||
func (s *folderDB) AllLocalFilesWithBlocksHash(h []byte) (iter.Seq[db.FileMetadata], func() error) {
|
||||
return iterStructs[db.FileMetadata](s.stmt(`
|
||||
SELECT f.sequence, f.name, f.type, f.modified as modnanos, f.size, f.deleted, f.invalid, f.local_flags as localflags FROM files f
|
||||
SELECT f.sequence, f.name, f.type, f.modified as modnanos, f.size, f.deleted, f.local_flags as localflags FROM files f
|
||||
WHERE f.device_idx = {{.LocalDeviceIdx}} AND f.blocklist_hash = ?
|
||||
`).Queryx(h))
|
||||
}
|
||||
@@ -99,7 +99,7 @@ func (s *folderDB) AllLocalBlocksWithHash(hash []byte) (iter.Seq[db.BlockMapEntr
|
||||
// & blocklists is deferred (garbage collected) while the files list is
|
||||
// not. This filters out blocks that are in fact deleted.
|
||||
return iterStructs[db.BlockMapEntry](s.stmt(`
|
||||
SELECT f.blocklist_hash as blocklisthash, b.idx as blockindex, b.offset, b.size FROM files f
|
||||
SELECT f.blocklist_hash as blocklisthash, b.idx as blockindex, b.offset, b.size, f.name as filename FROM files f
|
||||
LEFT JOIN blocks b ON f.blocklist_hash = b.blocklist_hash
|
||||
WHERE f.device_idx = {{.LocalDeviceIdx}} AND b.hash = ?
|
||||
`).Queryx(hash))
|
||||
|
||||
@@ -32,8 +32,12 @@ func openFolderDB(folder, path string, deleteRetention time.Duration) (*folderDB
|
||||
"sql/schema/common/*",
|
||||
"sql/schema/folder/*",
|
||||
}
|
||||
migrations := []string{
|
||||
"sql/migrations/common/*",
|
||||
"sql/migrations/folder/*",
|
||||
}
|
||||
|
||||
base, err := openBase(path, maxDBConns, pragmas, schemas, nil)
|
||||
base, err := openBase(path, maxDBConns, pragmas, schemas, migrations)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -46,8 +46,8 @@ func (s *folderDB) Update(device protocol.DeviceID, fs []protocol.FileInfo) erro
|
||||
|
||||
//nolint:sqlclosecheck
|
||||
insertFileStmt, err := txp.Preparex(`
|
||||
INSERT OR REPLACE INTO files (device_idx, remote_sequence, name, type, modified, size, version, deleted, invalid, local_flags, blocklist_hash)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
INSERT OR REPLACE INTO files (device_idx, remote_sequence, name, type, modified, size, version, deleted, local_flags, blocklist_hash)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
RETURNING sequence
|
||||
`)
|
||||
if err != nil {
|
||||
@@ -101,7 +101,7 @@ func (s *folderDB) Update(device protocol.DeviceID, fs []protocol.FileInfo) erro
|
||||
remoteSeq = &f.Sequence
|
||||
}
|
||||
var localSeq int64
|
||||
if err := insertFileStmt.Get(&localSeq, deviceIdx, remoteSeq, f.Name, f.Type, f.ModTime().UnixNano(), f.Size, f.Version.String(), f.IsDeleted(), f.IsInvalid(), f.LocalFlags, blockshash); err != nil {
|
||||
if err := insertFileStmt.Get(&localSeq, deviceIdx, remoteSeq, f.Name, f.Type, f.ModTime().UnixNano(), f.Size, f.Version.String(), f.IsDeleted(), f.LocalFlags, blockshash); err != nil {
|
||||
return wrap(err, "insert file")
|
||||
}
|
||||
|
||||
@@ -329,7 +329,7 @@ func (s *folderDB) recalcGlobalForFolder(txp *txPreparedStmts) error {
|
||||
func (s *folderDB) recalcGlobalForFile(txp *txPreparedStmts, file string) error {
|
||||
//nolint:sqlclosecheck
|
||||
selStmt, err := txp.Preparex(`
|
||||
SELECT name, device_idx, sequence, modified, version, deleted, invalid, local_flags FROM files
|
||||
SELECT name, device_idx, sequence, modified, version, deleted, local_flags FROM files
|
||||
WHERE name = ?
|
||||
`)
|
||||
if err != nil {
|
||||
@@ -350,7 +350,7 @@ func (s *folderDB) recalcGlobalForFile(txp *txPreparedStmts, file string) error
|
||||
// The global version is the first one in the list that is not invalid,
|
||||
// or just the first one in the list if all are invalid.
|
||||
var global fileRow
|
||||
globIdx := slices.IndexFunc(es, func(e fileRow) bool { return !e.Invalid })
|
||||
globIdx := slices.IndexFunc(es, func(e fileRow) bool { return !e.IsInvalid() })
|
||||
if globIdx < 0 {
|
||||
globIdx = 0
|
||||
}
|
||||
@@ -368,7 +368,7 @@ func (s *folderDB) recalcGlobalForFile(txp *txPreparedStmts, file string) error
|
||||
// Set the global flag on the global entry. Set the need flag if the
|
||||
// local device needs this file, unless it's invalid.
|
||||
global.LocalFlags |= protocol.FlagLocalGlobal
|
||||
if hasLocal || global.Invalid {
|
||||
if hasLocal || global.IsInvalid() {
|
||||
global.LocalFlags &= ^protocol.FlagLocalNeeded
|
||||
} else {
|
||||
global.LocalFlags |= protocol.FlagLocalNeeded
|
||||
@@ -426,18 +426,17 @@ type fileRow struct {
|
||||
Sequence int64
|
||||
Modified int64
|
||||
Size int64
|
||||
LocalFlags int64 `db:"local_flags"`
|
||||
LocalFlags protocol.FlagLocal `db:"local_flags"`
|
||||
Deleted bool
|
||||
Invalid bool
|
||||
}
|
||||
|
||||
func (e fileRow) Compare(other fileRow) int {
|
||||
// From FileInfo.WinsConflict
|
||||
vc := e.Version.Vector.Compare(other.Version.Vector)
|
||||
vc := e.Version.Compare(other.Version.Vector)
|
||||
switch vc {
|
||||
case protocol.Equal:
|
||||
if e.Invalid != other.Invalid {
|
||||
if e.Invalid {
|
||||
if e.IsInvalid() != other.IsInvalid() {
|
||||
if e.IsInvalid() {
|
||||
return 1
|
||||
}
|
||||
return -1
|
||||
@@ -453,8 +452,8 @@ func (e fileRow) Compare(other fileRow) int {
|
||||
case protocol.Lesser: // we are older
|
||||
return 1
|
||||
case protocol.ConcurrentGreater, protocol.ConcurrentLesser: // there is a conflict
|
||||
if e.Invalid != other.Invalid {
|
||||
if e.Invalid { // we are invalid, we lose
|
||||
if e.IsInvalid() != other.IsInvalid() {
|
||||
if e.IsInvalid() { // we are invalid, we lose
|
||||
return 1
|
||||
}
|
||||
return -1 // they are invalid, we win
|
||||
@@ -477,6 +476,10 @@ func (e fileRow) Compare(other fileRow) int {
|
||||
}
|
||||
}
|
||||
|
||||
func (e fileRow) IsInvalid() bool {
|
||||
return e.LocalFlags.IsInvalid()
|
||||
}
|
||||
|
||||
func (s *folderDB) periodicCheckpointLocked(fs []protocol.FileInfo) {
|
||||
// Induce periodic checkpoints. We add points for each file and block,
|
||||
// and checkpoint when we've written more than a threshold of points.
|
||||
@@ -519,11 +522,12 @@ func (s *folderDB) periodicCheckpointLocked(fs []protocol.FileInfo) {
|
||||
// failed, we'll keep trying it until we succeed. Increase it faster
|
||||
// when we fail to checkpoint, as it's more likely the WAL is
|
||||
// growing and will need truncation when we get out of this state.
|
||||
if res == 1 {
|
||||
switch {
|
||||
case res == 1:
|
||||
s.checkpointsCount += 10
|
||||
} else if res == 0 && checkpointType == "TRUNCATE" {
|
||||
case res == 0 && checkpointType == "TRUNCATE":
|
||||
s.checkpointsCount = 0
|
||||
} else {
|
||||
default:
|
||||
s.checkpointsCount++
|
||||
}
|
||||
s.updatePoints = 0
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
-- Copyright (C) 2025 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/.
|
||||
|
||||
-- The next migration should be number two.
|
||||
@@ -0,0 +1,20 @@
|
||||
-- Copyright (C) 2025 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/.
|
||||
|
||||
-- Remote files with the invalid bit instead gain the RemoteInvalid local
|
||||
-- flag.
|
||||
UPDATE files
|
||||
SET local_flags = local_flags | {{.FlagLocalRemoteInvalid}}
|
||||
FROM (
|
||||
SELECT idx FROM devices
|
||||
WHERE device_id = '7777777-777777N-7777777-777777N-7777777-777777N-7777777-77777Q4'
|
||||
) AS local_device
|
||||
WHERE invalid AND device_idx != local_device.idx
|
||||
;
|
||||
|
||||
-- The invalid column goes away.
|
||||
ALTER TABLE files DROP COLUMN invalid
|
||||
;
|
||||
@@ -0,0 +1,17 @@
|
||||
-- Copyright (C) 2025 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/.
|
||||
|
||||
-- Remove broken file entries in the database.
|
||||
DELETE FROM files
|
||||
WHERE type == 0 -- files
|
||||
AND NOT deleted -- that are not deleted
|
||||
AND blocklist_hash IS null -- with no blocks
|
||||
AND local_flags & {{.LocalInvalidFlags}} == 0 -- and not invalid
|
||||
;
|
||||
|
||||
-- Force a new index transmission.
|
||||
DELETE FROM indexids
|
||||
;
|
||||
@@ -31,7 +31,6 @@ CREATE TABLE IF NOT EXISTS files (
|
||||
size INTEGER NOT NULL,
|
||||
version TEXT NOT NULL COLLATE BINARY,
|
||||
deleted INTEGER NOT NULL, -- boolean
|
||||
invalid INTEGER NOT NULL, -- boolean
|
||||
local_flags INTEGER NOT NULL,
|
||||
blocklist_hash BLOB, -- null when there are no blocks
|
||||
FOREIGN KEY(device_idx) REFERENCES devices(idx) ON DELETE CASCADE
|
||||
|
||||
@@ -12,9 +12,9 @@ CREATE TABLE IF NOT EXISTS counts (
|
||||
device_idx INTEGER NOT NULL,
|
||||
type INTEGER NOT NULL,
|
||||
local_flags INTEGER NOT NULL,
|
||||
deleted INTEGER NOT NULL, -- boolean
|
||||
count INTEGER NOT NULL,
|
||||
size INTEGER NOT NULL,
|
||||
deleted INTEGER NOT NULL, -- boolean
|
||||
PRIMARY KEY(device_idx, type, local_flags, deleted),
|
||||
FOREIGN KEY(device_idx) REFERENCES devices(idx) ON DELETE CASCADE
|
||||
) STRICT, WITHOUT ROWID
|
||||
@@ -24,8 +24,8 @@ CREATE TABLE IF NOT EXISTS counts (
|
||||
|
||||
CREATE TRIGGER IF NOT EXISTS counts_insert AFTER INSERT ON files
|
||||
BEGIN
|
||||
INSERT INTO counts (device_idx, type, local_flags, count, size, deleted)
|
||||
VALUES (NEW.device_idx, NEW.type, NEW.local_flags, 1, NEW.size, NEW.deleted)
|
||||
INSERT INTO counts (device_idx, type, local_flags, deleted, count, size)
|
||||
VALUES (NEW.device_idx, NEW.type, NEW.local_flags, NEW.deleted, 1, NEW.size)
|
||||
ON CONFLICT DO UPDATE SET count = count + 1, size = size + NEW.size;
|
||||
END
|
||||
;
|
||||
@@ -38,8 +38,8 @@ END
|
||||
CREATE TRIGGER IF NOT EXISTS counts_update AFTER UPDATE OF local_flags ON files
|
||||
WHEN NEW.local_flags != OLD.local_flags
|
||||
BEGIN
|
||||
INSERT INTO counts (device_idx, type, local_flags, count, size, deleted)
|
||||
VALUES (NEW.device_idx, NEW.type, NEW.local_flags, 1, NEW.size, NEW.deleted)
|
||||
INSERT INTO counts (device_idx, type, local_flags, deleted, count, size)
|
||||
VALUES (NEW.device_idx, NEW.type, NEW.local_flags, NEW.deleted, 1, NEW.size)
|
||||
ON CONFLICT DO UPDATE SET count = count + 1, size = size + NEW.size;
|
||||
UPDATE counts SET count = count - 1, size = size - OLD.size
|
||||
WHERE device_idx = OLD.device_idx AND type = OLD.type AND local_flags = OLD.local_flags AND deleted = OLD.deleted;
|
||||
|
||||
@@ -37,7 +37,7 @@ func NewTyped(db KV, prefix string) *Typed {
|
||||
// is overwritten.
|
||||
func (n *Typed) PutInt64(key string, val int64) error {
|
||||
var valBs [8]byte
|
||||
binary.BigEndian.PutUint64(valBs[:], uint64(val))
|
||||
binary.BigEndian.PutUint64(valBs[:], uint64(val)) //nolint:gosec
|
||||
return n.db.PutKV(n.prefixedKey(key), valBs[:])
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ func (n *Typed) Int64(key string) (int64, bool, error) {
|
||||
return 0, false, filterNotFound(err)
|
||||
}
|
||||
val := binary.BigEndian.Uint64(valBs)
|
||||
return int64(val), true, nil
|
||||
return int64(val), true, nil //nolint:gosec
|
||||
}
|
||||
|
||||
// PutTime stores a new time.Time. Any existing value (even if of another
|
||||
|
||||
@@ -7,14 +7,16 @@
|
||||
package protoutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"errors"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
var errBufferTooSmall = errors.New("buffer too small")
|
||||
|
||||
func MarshalTo(buf []byte, pb proto.Message) (int, error) {
|
||||
if sz := proto.Size(pb); len(buf) < sz {
|
||||
return 0, fmt.Errorf("buffer too small")
|
||||
return 0, errBufferTooSmall
|
||||
} else if sz == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
+9
-8
@@ -8,6 +8,7 @@ package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"cmp"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
@@ -24,7 +25,7 @@ import (
|
||||
"reflect"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"sort"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -165,7 +166,7 @@ func (s *service) getListener(guiCfg config.GUIConfiguration) (net.Listener, err
|
||||
name = s.tlsDefaultCommonName
|
||||
}
|
||||
|
||||
cert, err = tlsutil.NewCertificate(httpsCertFile, httpsKeyFile, name, httpsCertLifetimeDays)
|
||||
cert, err = tlsutil.NewCertificate(httpsCertFile, httpsKeyFile, name, httpsCertLifetimeDays, true)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -750,7 +751,7 @@ func (*service) getSystemVersion(w http.ResponseWriter, _ *http.Request) {
|
||||
func (*service) getSystemDebug(w http.ResponseWriter, _ *http.Request) {
|
||||
names := l.Facilities()
|
||||
enabled := l.FacilityDebugging()
|
||||
sort.Strings(enabled)
|
||||
slices.Sort(enabled)
|
||||
sendJSON(w, map[string]interface{}{
|
||||
"facilities": names,
|
||||
"enabled": enabled,
|
||||
@@ -1516,8 +1517,8 @@ func (*service) getLang(w http.ResponseWriter, r *http.Request) {
|
||||
langs = append(langs, code)
|
||||
}
|
||||
// Reorder by descending q value
|
||||
sort.SliceStable(langs, func(i, j int) bool {
|
||||
return weights[langs[i]] > weights[langs[j]]
|
||||
slices.SortStableFunc(langs, func(i, j string) int {
|
||||
return cmp.Compare(weights[j], weights[i])
|
||||
})
|
||||
sendJSON(w, langs)
|
||||
}
|
||||
@@ -1803,8 +1804,8 @@ func browseFiles(ffs fs.Filesystem, search string) []string {
|
||||
}
|
||||
|
||||
// sort to return matches in deterministic order (don't depend on file system order)
|
||||
sort.Strings(exactMatches)
|
||||
sort.Strings(caseInsMatches)
|
||||
slices.Sort(exactMatches)
|
||||
slices.Sort(caseInsMatches)
|
||||
return append(exactMatches, caseInsMatches...)
|
||||
}
|
||||
|
||||
@@ -1901,7 +1902,7 @@ func dirNames(dir string) []string {
|
||||
}
|
||||
}
|
||||
|
||||
sort.Strings(dirs)
|
||||
slices.Sort(dirs)
|
||||
return dirs
|
||||
}
|
||||
|
||||
|
||||
+54
-10
@@ -18,6 +18,7 @@ import (
|
||||
ldap "github.com/go-ldap/ldap/v3"
|
||||
"github.com/syncthing/syncthing/lib/config"
|
||||
"github.com/syncthing/syncthing/lib/events"
|
||||
"github.com/syncthing/syncthing/lib/osutil"
|
||||
"github.com/syncthing/syncthing/lib/rand"
|
||||
)
|
||||
|
||||
@@ -27,15 +28,54 @@ const (
|
||||
randomTokenLength = 64
|
||||
)
|
||||
|
||||
func emitLoginAttempt(success bool, username, address string, evLogger events.Logger) {
|
||||
evLogger.Log(events.LoginAttempt, map[string]interface{}{
|
||||
func emitLoginAttempt(success bool, username string, r *http.Request, evLogger events.Logger) {
|
||||
remoteAddress, proxy := remoteAddress(r)
|
||||
evData := map[string]any{
|
||||
"success": success,
|
||||
"username": username,
|
||||
"remoteAddress": address,
|
||||
})
|
||||
if !success {
|
||||
l.Infof("Wrong credentials supplied during API authorization from %s", address)
|
||||
"remoteAddress": remoteAddress,
|
||||
}
|
||||
if proxy != "" {
|
||||
evData["proxy"] = proxy
|
||||
}
|
||||
evLogger.Log(events.LoginAttempt, evData)
|
||||
|
||||
if success {
|
||||
return
|
||||
}
|
||||
if proxy != "" {
|
||||
l.Infof("Wrong credentials supplied during API authorization from %s proxied by %s", remoteAddress, proxy)
|
||||
} else {
|
||||
l.Infof("Wrong credentials supplied during API authorization from %s", remoteAddress)
|
||||
}
|
||||
}
|
||||
|
||||
func remoteAddress(r *http.Request) (remoteAddr, proxy string) {
|
||||
remoteAddr = r.RemoteAddr
|
||||
remoteIP := osutil.IPFromString(r.RemoteAddr)
|
||||
|
||||
// parse X-Forwarded-For only if the proxy connects via unix socket, localhost or a LAN IP
|
||||
var localProxy bool
|
||||
if remoteIP != nil {
|
||||
remoteAddr = remoteIP.String()
|
||||
localProxy = remoteIP.IsLoopback() || remoteIP.IsPrivate() || remoteIP.IsLinkLocalUnicast()
|
||||
} else if remoteAddr == "@" {
|
||||
localProxy = true
|
||||
}
|
||||
|
||||
if !localProxy {
|
||||
return
|
||||
}
|
||||
|
||||
forwardedAddr, _, _ := strings.Cut(r.Header.Get("X-Forwarded-For"), ",")
|
||||
forwardedAddr = strings.TrimSpace(forwardedAddr)
|
||||
forwardedIP := osutil.IPFromString(forwardedAddr)
|
||||
|
||||
if forwardedIP != nil {
|
||||
proxy = remoteAddr
|
||||
remoteAddr = forwardedIP.String()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func antiBruteForceSleep() {
|
||||
@@ -51,7 +91,7 @@ func forbidden(w http.ResponseWriter) {
|
||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||
}
|
||||
|
||||
func isNoAuthPath(path string) bool {
|
||||
func isNoAuthPath(path string, metricsWithoutAuth bool) bool {
|
||||
// Local variable instead of module var to prevent accidental mutation
|
||||
noAuthPaths := []string{
|
||||
"/",
|
||||
@@ -60,6 +100,10 @@ func isNoAuthPath(path string) bool {
|
||||
"/rest/svc/lang", // Required to load language settings on login page
|
||||
}
|
||||
|
||||
if metricsWithoutAuth {
|
||||
noAuthPaths = append(noAuthPaths, "/metrics")
|
||||
}
|
||||
|
||||
// Local variable instead of module var to prevent accidental mutation
|
||||
noAuthPrefixes := []string{
|
||||
// Static assets
|
||||
@@ -115,7 +159,7 @@ func (m *basicAuthAndSessionMiddleware) ServeHTTP(w http.ResponseWriter, r *http
|
||||
}
|
||||
|
||||
// Exception for static assets and REST calls that don't require authentication.
|
||||
if isNoAuthPath(r.URL.Path) {
|
||||
if isNoAuthPath(r.URL.Path, m.guiCfg.MetricsWithoutAuth) {
|
||||
m.next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
@@ -148,7 +192,7 @@ func (m *basicAuthAndSessionMiddleware) passwordAuthHandler(w http.ResponseWrite
|
||||
return
|
||||
}
|
||||
|
||||
emitLoginAttempt(false, req.Username, r.RemoteAddr, m.evLogger)
|
||||
emitLoginAttempt(false, req.Username, r, m.evLogger)
|
||||
antiBruteForceSleep()
|
||||
forbidden(w)
|
||||
}
|
||||
@@ -171,7 +215,7 @@ func attemptBasicAuth(r *http.Request, guiCfg config.GUIConfiguration, ldapCfg c
|
||||
return usernameFromIso, true
|
||||
}
|
||||
|
||||
emitLoginAttempt(false, username, r.RemoteAddr, evLogger)
|
||||
emitLoginAttempt(false, username, r, evLogger)
|
||||
antiBruteForceSleep()
|
||||
return "", false
|
||||
}
|
||||
|
||||
+1
-1
@@ -78,7 +78,7 @@ func (m *csrfManager) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if isNoAuthPath(r.URL.Path) {
|
||||
if isNoAuthPath(r.URL.Path, false) {
|
||||
// REST calls that don't require authentication also do not
|
||||
// need a CSRF token.
|
||||
m.next.ServeHTTP(w, r)
|
||||
|
||||
@@ -117,8 +117,8 @@ func (m *tokenManager) saveLocked() {
|
||||
for token, expiry := range m.tokens.Tokens {
|
||||
tokens = append(tokens, tokenExpiry{token, expiry})
|
||||
}
|
||||
slices.SortFunc(tokens, func(i, j tokenExpiry) int {
|
||||
return int(i.expiry - j.expiry)
|
||||
slices.SortFunc(tokens, func(a, b tokenExpiry) int {
|
||||
return int(a.expiry - b.expiry)
|
||||
})
|
||||
// Remove the oldest tokens.
|
||||
for _, token := range tokens[:len(tokens)-m.maxItems] {
|
||||
@@ -189,7 +189,7 @@ func (m *tokenCookieManager) createSession(username string, persistent bool, w h
|
||||
Path: "/",
|
||||
})
|
||||
|
||||
emitLoginAttempt(true, username, r.RemoteAddr, m.evLogger)
|
||||
emitLoginAttempt(true, username, r, m.evLogger)
|
||||
}
|
||||
|
||||
func (m *tokenCookieManager) hasValidSession(r *http.Request) bool {
|
||||
|
||||
+3
-2
@@ -12,7 +12,7 @@ import (
|
||||
"os"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"sort"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -48,6 +48,7 @@ var (
|
||||
}
|
||||
replaceTags = map[string]string{
|
||||
"sqlite_omit_load_extension": "",
|
||||
"sqlite_dbstat": "",
|
||||
"osusergo": "",
|
||||
"netgo": "",
|
||||
}
|
||||
@@ -126,7 +127,7 @@ func TagsList() []string {
|
||||
}
|
||||
}
|
||||
|
||||
sort.Strings(tags)
|
||||
slices.Sort(tags)
|
||||
|
||||
// Remove any empty tags, which will be at the front of the list now
|
||||
for len(tags) > 0 && tags[0] == "" {
|
||||
|
||||
+10
-6
@@ -17,7 +17,7 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"sort"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -31,7 +31,7 @@ import (
|
||||
|
||||
const (
|
||||
OldestHandledVersion = 10
|
||||
CurrentVersion = 37
|
||||
CurrentVersion = 50
|
||||
MaxRescanIntervalS = 365 * 24 * 60 * 60
|
||||
)
|
||||
|
||||
@@ -235,6 +235,10 @@ func ReadJSON(r io.Reader, myID protocol.DeviceID) (Configuration, error) {
|
||||
func (cfg Configuration) Copy() Configuration {
|
||||
newCfg := cfg
|
||||
|
||||
// Deep copy Defaults
|
||||
newCfg.Defaults.Folder = cfg.Defaults.Folder.Copy()
|
||||
newCfg.Defaults.Device = cfg.Defaults.Device.Copy()
|
||||
|
||||
// Deep copy FolderConfigurations
|
||||
newCfg.Folders = make([]FolderConfiguration, len(cfg.Folders))
|
||||
for i := range newCfg.Folders {
|
||||
@@ -333,8 +337,8 @@ func (cfg *Configuration) prepareDeviceList() map[protocol.DeviceID]*DeviceConfi
|
||||
// - sorted by ID
|
||||
// Happen before preparting folders as that needs a correct device list.
|
||||
cfg.Devices = ensureNoDuplicateOrEmptyIDDevices(cfg.Devices)
|
||||
sort.Slice(cfg.Devices, func(a, b int) bool {
|
||||
return cfg.Devices[a].DeviceID.Compare(cfg.Devices[b].DeviceID) == -1
|
||||
slices.SortFunc(cfg.Devices, func(a, b DeviceConfiguration) int {
|
||||
return a.DeviceID.Compare(b.DeviceID)
|
||||
})
|
||||
|
||||
// Build a list of available devices
|
||||
@@ -376,8 +380,8 @@ func (cfg *Configuration) prepareFolders(myID protocol.DeviceID, existingDevices
|
||||
}
|
||||
}
|
||||
// Ensure that the folder list is sorted by ID
|
||||
sort.Slice(cfg.Folders, func(a, b int) bool {
|
||||
return cfg.Folders[a].ID < cfg.Folders[b].ID
|
||||
slices.SortFunc(cfg.Folders, func(a, b FolderConfiguration) int {
|
||||
return strings.Compare(a.ID, b.ID)
|
||||
})
|
||||
return sharedFolders, nil
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"sort"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -92,6 +92,8 @@ func TestDefaultValues(t *testing.T) {
|
||||
RawStunServers: []string{"default"},
|
||||
AnnounceLANAddresses: true,
|
||||
FeatureFlags: []string{},
|
||||
AuditEnabled: false,
|
||||
AuditFile: "",
|
||||
ConnectionPriorityTCPLAN: 10,
|
||||
ConnectionPriorityQUICLAN: 20,
|
||||
ConnectionPriorityTCPWAN: 30,
|
||||
@@ -295,6 +297,8 @@ func TestOverriddenValues(t *testing.T) {
|
||||
StunKeepaliveMinS: 900,
|
||||
RawStunServers: []string{"foo"},
|
||||
FeatureFlags: []string{"feature"},
|
||||
AuditEnabled: true,
|
||||
AuditFile: "nggyu",
|
||||
ConnectionPriorityTCPLAN: 40,
|
||||
ConnectionPriorityQUICLAN: 45,
|
||||
ConnectionPriorityTCPWAN: 50,
|
||||
@@ -907,7 +911,7 @@ func TestV14ListenAddressesMigration(t *testing.T) {
|
||||
t.Error("Configuration was not converted")
|
||||
}
|
||||
|
||||
sort.Strings(tc[2])
|
||||
slices.Sort(tc[2])
|
||||
if !reflect.DeepEqual(cfg.Options.RawListenAddresses, tc[2]) {
|
||||
t.Errorf("Migration error; actual %#v != expected %#v", cfg.Options.RawListenAddresses, tc[2])
|
||||
}
|
||||
|
||||
@@ -8,12 +8,12 @@ package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"slices"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
)
|
||||
|
||||
const defaultNumConnections = 1 // number of connections to use by default; may change in the future.
|
||||
const defaultNumConnections = 3 // number of connections to use by default
|
||||
|
||||
type DeviceConfiguration struct {
|
||||
DeviceID protocol.DeviceID `json:"deviceID" xml:"id,attr" nodefault:"true"`
|
||||
@@ -100,8 +100,8 @@ func sortedObservedFolderSlice(input map[string]ObservedFolder) []ObservedFolder
|
||||
for _, folder := range input {
|
||||
output = append(output, folder)
|
||||
}
|
||||
sort.Slice(output, func(i, j int) bool {
|
||||
return output[i].Time.Before(output[j].Time)
|
||||
slices.SortFunc(output, func(a, b ObservedFolder) int {
|
||||
return a.Time.Compare(b.Time)
|
||||
})
|
||||
return output
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"fmt"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -288,8 +288,8 @@ func (f *FolderConfiguration) prepare(myID protocol.DeviceID, existingDevices ma
|
||||
f.Devices = ensureDevicePresent(f.Devices, myID)
|
||||
f.Devices = ensureNoUntrustedTrustingSharing(f, f.Devices, existingDevices)
|
||||
|
||||
sort.Slice(f.Devices, func(a, b int) bool {
|
||||
return f.Devices[a].DeviceID.Compare(f.Devices[b].DeviceID) == -1
|
||||
slices.SortFunc(f.Devices, func(a, b FolderDeviceConfiguration) int {
|
||||
return a.DeviceID.Compare(b.DeviceID)
|
||||
})
|
||||
|
||||
if f.RescanIntervalS > MaxRescanIntervalS {
|
||||
|
||||
@@ -25,6 +25,7 @@ type GUIConfiguration struct {
|
||||
User string `json:"user" xml:"user,omitempty"`
|
||||
Password string `json:"password" xml:"password,omitempty"`
|
||||
AuthMode AuthMode `json:"authMode" xml:"authMode,omitempty"`
|
||||
MetricsWithoutAuth bool `json:"metricsWithoutAuth" xml:"metricsWithoutAuth" default:"false"`
|
||||
RawUseTLS bool `json:"useTLS" xml:"tls,attr"`
|
||||
APIKey string `json:"apiKey" xml:"apikey,omitempty"`
|
||||
InsecureAdminAccess bool `json:"insecureAdminAccess" xml:"insecureAdminAccess,omitempty"`
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
// Copyright (C) 2025 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 config
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
// RegisterInfoMetrics registers Prometheus metrics for the given config
|
||||
// wrapper.
|
||||
func RegisterInfoMetrics(cfg Wrapper) {
|
||||
prometheus.DefaultRegisterer.MustRegister(prometheus.CollectorFunc((&folderInfoMetric{cfg}).Collect))
|
||||
prometheus.DefaultRegisterer.MustRegister(prometheus.CollectorFunc((&folderDeviceMetric{cfg}).Collect))
|
||||
}
|
||||
|
||||
type folderInfoMetric struct {
|
||||
cfg Wrapper
|
||||
}
|
||||
|
||||
var folderInfoMetricDesc = prometheus.NewDesc(
|
||||
"syncthing_config_folder_info",
|
||||
"Provides additional information labels on folders",
|
||||
[]string{"folder", "label", "type", "path", "paused"},
|
||||
nil,
|
||||
)
|
||||
|
||||
func (m *folderInfoMetric) Collect(ch chan<- prometheus.Metric) {
|
||||
for _, folder := range m.cfg.FolderList() {
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
folderInfoMetricDesc,
|
||||
prometheus.GaugeValue, 1,
|
||||
folder.ID, folder.Label, folder.Type.String(), folder.Path, strconv.FormatBool(folder.Paused),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
type folderDeviceMetric struct {
|
||||
cfg Wrapper
|
||||
}
|
||||
|
||||
var folderDeviceMetricDesc = prometheus.NewDesc(
|
||||
"syncthing_config_device_info",
|
||||
"Provides additional information labels on devices",
|
||||
[]string{"device", "name", "introducer", "paused", "untrusted"},
|
||||
nil,
|
||||
)
|
||||
|
||||
func (m *folderDeviceMetric) Collect(ch chan<- prometheus.Metric) {
|
||||
for _, device := range m.cfg.DeviceList() {
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
folderDeviceMetricDesc,
|
||||
prometheus.GaugeValue, 1,
|
||||
device.DeviceID.String(), device.Name, strconv.FormatBool(device.Introducer), strconv.FormatBool(device.Paused), strconv.FormatBool(device.Untrusted),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -7,11 +7,12 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
@@ -27,6 +28,7 @@ import (
|
||||
// put the newest on top for readability.
|
||||
var (
|
||||
migrations = migrationSet{
|
||||
{50, migrateToConfigV50},
|
||||
{37, migrateToConfigV37},
|
||||
{36, migrateToConfigV36},
|
||||
{35, migrateToConfigV35},
|
||||
@@ -65,8 +67,8 @@ type migrationSet []migration
|
||||
func (ms migrationSet) apply(cfg *Configuration) {
|
||||
// Make sure we apply the migrations in target version order regardless
|
||||
// of how it was defined.
|
||||
sort.Slice(ms, func(a, b int) bool {
|
||||
return ms[a].targetVersion < ms[b].targetVersion
|
||||
slices.SortFunc(ms, func(a, b migration) int {
|
||||
return cmp.Compare(a.targetVersion, b.targetVersion)
|
||||
})
|
||||
|
||||
// Apply all migrations.
|
||||
@@ -96,6 +98,10 @@ func (m migration) apply(cfg *Configuration) {
|
||||
cfg.Version = m.targetVersion
|
||||
}
|
||||
|
||||
func migrateToConfigV50(cfg *Configuration) {
|
||||
// v50 is Syncthing 2.0
|
||||
}
|
||||
|
||||
func migrateToConfigV37(cfg *Configuration) {
|
||||
// "scan ownership" changed name to "send ownership"
|
||||
for i := range cfg.Folders {
|
||||
@@ -349,7 +355,7 @@ func migrateToConfigV14(cfg *Configuration) {
|
||||
cfg.Options.DeprecatedRelayServers = nil
|
||||
|
||||
// For consistency
|
||||
sort.Strings(cfg.Options.RawListenAddresses)
|
||||
slices.Sort(cfg.Options.RawListenAddresses)
|
||||
|
||||
var newAddrs []string
|
||||
for _, addr := range cfg.Options.RawGlobalAnnServers {
|
||||
|
||||
@@ -67,22 +67,21 @@ type OptionsConfiguration struct {
|
||||
AnnounceLANAddresses bool `json:"announceLANAddresses" xml:"announceLANAddresses" default:"true"`
|
||||
SendFullIndexOnUpgrade bool `json:"sendFullIndexOnUpgrade" xml:"sendFullIndexOnUpgrade"`
|
||||
FeatureFlags []string `json:"featureFlags" xml:"featureFlag"`
|
||||
AuditEnabled bool `json:"auditEnabled" xml:"auditEnabled" default:"false" restart:"true"`
|
||||
AuditFile string `json:"auditFile" xml:"auditFile" restart:"true"`
|
||||
// The number of connections at which we stop trying to connect to more
|
||||
// devices, zero meaning no limit. Does not affect incoming connections.
|
||||
ConnectionLimitEnough int `json:"connectionLimitEnough" xml:"connectionLimitEnough"`
|
||||
// The maximum number of connections which we will allow in total, zero
|
||||
// meaning no limit. Affects incoming connections and prevents
|
||||
// attempting outgoing connections.
|
||||
ConnectionLimitMax int `json:"connectionLimitMax" xml:"connectionLimitMax"`
|
||||
// When set, this allows TLS 1.2 on sync connections, where we otherwise
|
||||
// default to TLS 1.3+ only.
|
||||
InsecureAllowOldTLSVersions bool `json:"insecureAllowOldTLSVersions" xml:"insecureAllowOldTLSVersions"`
|
||||
ConnectionPriorityTCPLAN int `json:"connectionPriorityTcpLan" xml:"connectionPriorityTcpLan" default:"10"`
|
||||
ConnectionPriorityQUICLAN int `json:"connectionPriorityQuicLan" xml:"connectionPriorityQuicLan" default:"20"`
|
||||
ConnectionPriorityTCPWAN int `json:"connectionPriorityTcpWan" xml:"connectionPriorityTcpWan" default:"30"`
|
||||
ConnectionPriorityQUICWAN int `json:"connectionPriorityQuicWan" xml:"connectionPriorityQuicWan" default:"40"`
|
||||
ConnectionPriorityRelay int `json:"connectionPriorityRelay" xml:"connectionPriorityRelay" default:"50"`
|
||||
ConnectionPriorityUpgradeThreshold int `json:"connectionPriorityUpgradeThreshold" xml:"connectionPriorityUpgradeThreshold" default:"0"`
|
||||
ConnectionLimitMax int `json:"connectionLimitMax" xml:"connectionLimitMax"`
|
||||
ConnectionPriorityTCPLAN int `json:"connectionPriorityTcpLan" xml:"connectionPriorityTcpLan" default:"10"`
|
||||
ConnectionPriorityQUICLAN int `json:"connectionPriorityQuicLan" xml:"connectionPriorityQuicLan" default:"20"`
|
||||
ConnectionPriorityTCPWAN int `json:"connectionPriorityTcpWan" xml:"connectionPriorityTcpWan" default:"30"`
|
||||
ConnectionPriorityQUICWAN int `json:"connectionPriorityQuicWan" xml:"connectionPriorityQuicWan" default:"40"`
|
||||
ConnectionPriorityRelay int `json:"connectionPriorityRelay" xml:"connectionPriorityRelay" default:"50"`
|
||||
ConnectionPriorityUpgradeThreshold int `json:"connectionPriorityUpgradeThreshold" xml:"connectionPriorityUpgradeThreshold" default:"0"`
|
||||
// Legacy deprecated
|
||||
DeprecatedUPnPEnabled bool `json:"-" xml:"upnpEnabled,omitempty"` // Deprecated: Do not use.
|
||||
DeprecatedUPnPLeaseM int `json:"-" xml:"upnpLeaseMinutes,omitempty"` // Deprecated: Do not use.
|
||||
@@ -187,7 +186,7 @@ func (opts OptionsConfiguration) StunServers() []string {
|
||||
case "default":
|
||||
_, records, err := net.LookupSRV("stun", "udp", "syncthing.net")
|
||||
if err != nil {
|
||||
l.Warnln("Unable to resolve primary STUN servers via DNS:", err)
|
||||
l.Debugf("Unable to resolve primary STUN servers via DNS:", err)
|
||||
}
|
||||
|
||||
for _, record := range records {
|
||||
|
||||
+2
@@ -45,6 +45,8 @@
|
||||
<unackedNotificationID>asdfasdf</unackedNotificationID>
|
||||
<announceLANAddresses>false</announceLANAddresses>
|
||||
<featureFlag>feature</featureFlag>
|
||||
<auditEnabled>true</auditEnabled>
|
||||
<auditFile>nggyu</auditFile>
|
||||
<connectionPriorityTcpLan>40</connectionPriorityTcpLan>
|
||||
<connectionPriorityQuicLan>45</connectionPriorityQuicLan>
|
||||
<connectionPriorityTcpWan>50</connectionPriorityTcpWan>
|
||||
|
||||
@@ -9,7 +9,8 @@ package config
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"sort"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/structutil"
|
||||
)
|
||||
@@ -84,8 +85,8 @@ func (c *VersioningConfiguration) toInternal() internalVersioningConfiguration {
|
||||
for k, v := range c.Params {
|
||||
tmp.Params = append(tmp.Params, internalParam{k, v})
|
||||
}
|
||||
sort.Slice(tmp.Params, func(a, b int) bool {
|
||||
return tmp.Params[a].Key < tmp.Params[b].Key
|
||||
slices.SortFunc(tmp.Params, func(a, b internalParam) int {
|
||||
return strings.Compare(a.Key, b.Key)
|
||||
})
|
||||
return tmp
|
||||
}
|
||||
|
||||
@@ -49,9 +49,11 @@ type quicListener struct {
|
||||
registry *registry.Registry
|
||||
lanChecker *lanChecker
|
||||
|
||||
address *url.URL
|
||||
laddr net.Addr
|
||||
mut sync.Mutex
|
||||
address *url.URL
|
||||
natService *nat.Service
|
||||
mapping *nat.Mapping
|
||||
laddr net.Addr
|
||||
mut sync.Mutex
|
||||
}
|
||||
|
||||
func (t *quicListener) OnNATTypeChanged(natType stun.NATType) {
|
||||
@@ -126,7 +128,24 @@ func (t *quicListener) serve(ctx context.Context) error {
|
||||
l.Infof("QUIC listener (%v) starting", udpConn.LocalAddr())
|
||||
defer l.Infof("QUIC listener (%v) shutting down", udpConn.LocalAddr())
|
||||
|
||||
var ipVersion nat.IPVersion
|
||||
switch t.uri.Scheme {
|
||||
case "quic4":
|
||||
ipVersion = nat.IPv4Only
|
||||
case "quic6":
|
||||
ipVersion = nat.IPv6Only
|
||||
default:
|
||||
ipVersion = nat.IPvAny
|
||||
}
|
||||
mapping := t.natService.NewMapping(nat.UDP, ipVersion, udpAddr.IP, udpAddr.Port)
|
||||
mapping.OnChanged(func() {
|
||||
t.notifyAddressesChanged(t)
|
||||
})
|
||||
// Should be called after t.mapping is nil'ed out.
|
||||
defer t.natService.RemoveMapping(mapping)
|
||||
|
||||
t.mut.Lock()
|
||||
t.mapping = mapping
|
||||
t.laddr = udpConn.LocalAddr()
|
||||
t.mut.Unlock()
|
||||
defer func() {
|
||||
@@ -196,6 +215,9 @@ func (t *quicListener) WANAddresses() []*url.URL {
|
||||
if t.address != nil {
|
||||
uris = append(uris, t.address)
|
||||
}
|
||||
|
||||
uris = append(uris, portMappingURIs(t.mapping, *t.uri)...)
|
||||
|
||||
t.mut.Unlock()
|
||||
return uris
|
||||
}
|
||||
@@ -232,12 +254,13 @@ func (*quicListenerFactory) Valid(config.Configuration) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *quicListenerFactory) New(uri *url.URL, cfg config.Wrapper, tlsCfg *tls.Config, conns chan internalConn, _ *nat.Service, registry *registry.Registry, lanChecker *lanChecker) genericListener {
|
||||
func (f *quicListenerFactory) New(uri *url.URL, cfg config.Wrapper, tlsCfg *tls.Config, conns chan internalConn, natService *nat.Service, registry *registry.Registry, lanChecker *lanChecker) genericListener {
|
||||
l := &quicListener{
|
||||
uri: fixupPort(uri, config.DefaultQUICPort),
|
||||
cfg: cfg,
|
||||
tlsCfg: tlsCfg,
|
||||
conns: conns,
|
||||
natService: natService,
|
||||
factory: f,
|
||||
registry: registry,
|
||||
lanChecker: lanChecker,
|
||||
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
"net"
|
||||
"net/url"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
stdsync "sync"
|
||||
"time"
|
||||
@@ -445,7 +444,7 @@ func (s *service) handleHellos(ctx context.Context) error {
|
||||
// connections are limited.
|
||||
rd, wr := s.limiter.getLimiters(remoteID, c, c.IsLocal())
|
||||
|
||||
protoConn := protocol.NewConnection(remoteID, rd, wr, c, s.model, c, deviceCfg.Compression.ToProtocol(), s.cfg.FolderPasswords(remoteID), s.keyGen)
|
||||
protoConn := protocol.NewConnection(remoteID, rd, wr, c, s.model, c, deviceCfg.Compression.ToProtocol(), s.keyGen)
|
||||
s.accountAddedConnection(protoConn, hello, s.cfg.Options().ConnectionPriorityUpgradeThreshold)
|
||||
go func() {
|
||||
<-protoConn.Closed()
|
||||
@@ -1151,7 +1150,7 @@ func (s *service) dialParallel(ctx context.Context, deviceID protocol.DeviceID,
|
||||
}
|
||||
|
||||
// Sort the priorities so that we dial lowest first (which means highest...)
|
||||
sort.Ints(priorities)
|
||||
slices.Sort(priorities)
|
||||
|
||||
sema := semaphore.MultiSemaphore{semaphore.New(dialMaxParallelPerDevice), parentSema}
|
||||
for _, prio := range priorities {
|
||||
|
||||
@@ -175,24 +175,9 @@ func (t *tcpListener) WANAddresses() []*url.URL {
|
||||
uris := []*url.URL{
|
||||
maybeReplacePort(t.uri, t.laddr),
|
||||
}
|
||||
if t.mapping != nil {
|
||||
addrs := t.mapping.ExternalAddresses()
|
||||
for _, addr := range addrs {
|
||||
uri := *t.uri
|
||||
// Does net.JoinHostPort internally
|
||||
uri.Host = addr.String()
|
||||
uris = append(uris, &uri)
|
||||
|
||||
// For every address with a specified IP, add one without an IP,
|
||||
// just in case the specified IP is still internal (router behind DMZ).
|
||||
if len(addr.IP) != 0 && !addr.IP.IsUnspecified() {
|
||||
zeroUri := *t.uri
|
||||
addr.IP = nil
|
||||
zeroUri.Host = addr.String()
|
||||
uris = append(uris, &zeroUri)
|
||||
}
|
||||
}
|
||||
}
|
||||
uris = append(uris, portMappingURIs(t.mapping, *t.uri)...)
|
||||
|
||||
t.mut.RUnlock()
|
||||
|
||||
// If we support ReusePort, add an unspecified zero port address, which will be resolved by the discovery server
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/nat"
|
||||
"github.com/syncthing/syncthing/lib/osutil"
|
||||
)
|
||||
|
||||
@@ -130,3 +131,27 @@ func maybeReplacePort(uri *url.URL, laddr net.Addr) *url.URL {
|
||||
uriCopy.Host = net.JoinHostPort(host, lportStr)
|
||||
return &uriCopy
|
||||
}
|
||||
|
||||
func portMappingURIs(mapping *nat.Mapping, listener_uri url.URL) []*url.URL {
|
||||
var uris []*url.URL
|
||||
if mapping != nil {
|
||||
addrs := mapping.ExternalAddresses()
|
||||
for _, addr := range addrs {
|
||||
uri := listener_uri
|
||||
// Does net.JoinHostPort internally
|
||||
uri.Host = addr.String()
|
||||
uris = append(uris, &uri)
|
||||
|
||||
// For every address with a specified IP, add one without an IP,
|
||||
// just in case the specified IP is still internal (router behind DMZ).
|
||||
if len(addr.IP) != 0 && !addr.IP.IsUnspecified() {
|
||||
zeroUri := listener_uri
|
||||
addr.IP = nil
|
||||
zeroUri.Host = addr.String()
|
||||
uris = append(uris, &zeroUri)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return uris
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"sort"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/thejerf/suture/v4"
|
||||
@@ -159,7 +159,7 @@ func (m *manager) Lookup(ctx context.Context, deviceID protocol.DeviceID) (addre
|
||||
m.mut.RUnlock()
|
||||
|
||||
addresses = stringutil.UniqueTrimmedStrings(addresses)
|
||||
sort.Strings(addresses)
|
||||
slices.Sort(addresses)
|
||||
|
||||
l.Debugln("lookup results for", deviceID)
|
||||
l.Debugln(" addresses: ", addresses)
|
||||
|
||||
+4
-4
@@ -379,12 +379,12 @@ func longFilenameSupport(path string) string {
|
||||
return path
|
||||
}
|
||||
|
||||
type ErrWatchEventOutsideRoot struct{ msg string }
|
||||
type WatchEventOutsideRootError struct{ msg string }
|
||||
|
||||
func (e *ErrWatchEventOutsideRoot) Error() string {
|
||||
func (e *WatchEventOutsideRootError) Error() string {
|
||||
return e.msg
|
||||
}
|
||||
|
||||
func (f *BasicFilesystem) newErrWatchEventOutsideRoot(absPath string, roots []string) *ErrWatchEventOutsideRoot {
|
||||
return &ErrWatchEventOutsideRoot{fmt.Sprintf("Watching for changes encountered an event outside of the filesystem root: f.root==%v, roots==%v, path==%v. This should never happen, please report this message to forum.syncthing.net.", f.root, roots, absPath)}
|
||||
func (f *BasicFilesystem) newErrWatchEventOutsideRoot(absPath string, roots []string) *WatchEventOutsideRootError {
|
||||
return &WatchEventOutsideRootError{fmt.Sprintf("Watching for changes encountered an event outside of the filesystem root: f.root==%v, roots==%v, path==%v. This should never happen, please report this message to forum.syncthing.net.", f.root, roots, absPath)}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
)
|
||||
|
||||
func (fi basicFileInfo) InodeChangeTime() time.Time {
|
||||
if sys, ok := fi.FileInfo.Sys().(*syscall.Stat_t); ok {
|
||||
if sys, ok := fi.Sys().(*syscall.Stat_t); ok {
|
||||
return time.Unix(0, sys.Ctimespec.Nano())
|
||||
}
|
||||
return time.Time{}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user