mirror of
https://github.com/syncthing/syncthing.git
synced 2026-01-05 12:29:14 -05:00
Compare commits
93 Commits
v1.29.2
...
v2.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
25e03ef9ab | ||
|
|
e46a0f99c3 | ||
|
|
ed6575411f | ||
|
|
ed97e365b2 | ||
|
|
b4776ea4e0 | ||
|
|
b5ffd0a796 | ||
|
|
c74299b59a | ||
|
|
8b6d837483 | ||
|
|
3e74b3dee2 | ||
|
|
2902da996c | ||
|
|
f6f144bf17 | ||
|
|
ab5c42f4a0 | ||
|
|
780b8fd3bc | ||
|
|
7db3f7eaac | ||
|
|
f0b666269b | ||
|
|
190a59842c | ||
|
|
40888c1a66 | ||
|
|
ddea2e449c | ||
|
|
7cfa871d58 | ||
|
|
95b39a791d | ||
|
|
fa0d933e49 | ||
|
|
e0c1abc5fe | ||
|
|
8372c0288f | ||
|
|
5f5d672a7d | ||
|
|
d23cd197e1 | ||
|
|
d7ca483df1 | ||
|
|
e48be98cd5 | ||
|
|
cbded11c43 | ||
|
|
d5aa991b73 | ||
|
|
05210d0325 | ||
|
|
55da878452 | ||
|
|
e9a2ff3aa6 | ||
|
|
c9650fc7d5 | ||
|
|
cf1cf85ce6 | ||
|
|
2301f72c5b | ||
|
|
7d51b1b620 | ||
|
|
f7c8efd93c | ||
|
|
3e7ccf7c48 | ||
|
|
fa3b9acca3 | ||
|
|
bae976905c | ||
|
|
6bc2784e9a | ||
|
|
1dbdd6b720 | ||
|
|
f15d50c2e8 | ||
|
|
8a2d8ebf81 | ||
|
|
b88aea34b6 | ||
|
|
82a0dd8eaa | ||
|
|
4096a35b86 | ||
|
|
86cbc2486f | ||
|
|
0bcc31d058 | ||
|
|
2c3a890d2f | ||
|
|
f9007ed106 | ||
|
|
2953630bc3 | ||
|
|
a99e670ebb | ||
|
|
05cc6b0f43 | ||
|
|
1efcfeb3ad | ||
|
|
93195911bd | ||
|
|
6085e3a5eb | ||
|
|
e5b72da607 | ||
|
|
0d6117d585 | ||
|
|
f1e136a17b | ||
|
|
025905fcdf | ||
|
|
b1c8f88a44 | ||
|
|
1a25ae32ca | ||
|
|
629971687d | ||
|
|
3c955a9706 | ||
|
|
7f3c8dbff1 | ||
|
|
7762e39fb3 | ||
|
|
4235b2c406 | ||
|
|
3fd090bfa7 | ||
|
|
6dfa54efa6 | ||
|
|
67575e1736 | ||
|
|
65923fc255 | ||
|
|
aea763868f | ||
|
|
26b134ae7b | ||
|
|
893071d2ba | ||
|
|
435f2d2178 | ||
|
|
8461ca539b | ||
|
|
fb977dc61d | ||
|
|
ee7ab4ce25 | ||
|
|
c3ce9713d9 | ||
|
|
6a147091c5 | ||
|
|
6208c36417 | ||
|
|
453fd20eeb | ||
|
|
28f0cffdb6 | ||
|
|
87c16c6cf5 | ||
|
|
5495c98e63 | ||
|
|
da7d5ce608 | ||
|
|
b300c297c6 | ||
|
|
124673f7a8 | ||
|
|
0395cf2bc0 | ||
|
|
36cd70040a | ||
|
|
1fbd396ffa | ||
|
|
2834bad85e |
4
.github/workflows/build-infra-dockers.yaml
vendored
4
.github/workflows/build-infra-dockers.yaml
vendored
@@ -7,7 +7,7 @@ on:
|
||||
- infra-*
|
||||
|
||||
env:
|
||||
GO_VERSION: "~1.23.0"
|
||||
GO_VERSION: "~1.24.0"
|
||||
CGO_ENABLED: "0"
|
||||
BUILD_USER: docker
|
||||
BUILD_HOST: github.syncthing.net
|
||||
@@ -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:
|
||||
|
||||
18
.github/workflows/build-nightly.yaml
vendored
Normal file
18
.github/workflows/build-nightly.yaml
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
name: Build Syncthing (Nightly)
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Run nightly build at 05:00 UTC
|
||||
- cron: '00 05 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
build-syncthing:
|
||||
uses: ./.github/workflows/build-syncthing.yaml
|
||||
# if we only want nightlies to run for specific users:
|
||||
# if: contains(fromJSON('["syncthing", "calmh"]'), github.repository_owner)
|
||||
secrets: inherit
|
||||
312
.github/workflows/build-syncthing.yaml
vendored
312
.github/workflows/build-syncthing.yaml
vendored
@@ -3,20 +3,16 @@ name: Build Syncthing
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
schedule:
|
||||
# Run nightly build at 05:00 UTC
|
||||
- cron: '00 05 * * *'
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
# The go version to use for builds. We set check-latest to true when
|
||||
# installing, so we get the latest patch version that matches the
|
||||
# expression.
|
||||
GO_VERSION: "~1.23.0"
|
||||
GO_VERSION: "~1.24.0"
|
||||
|
||||
# Optimize compatibility on the slow archictures.
|
||||
GO386: softfloat
|
||||
GOARM: "5"
|
||||
GOMIPS: softfloat
|
||||
|
||||
# Avoid hilarious amounts of obscuring log output when running tests.
|
||||
@@ -26,6 +22,8 @@ env:
|
||||
BUILD_USER: builder
|
||||
BUILD_HOST: github.syncthing.net
|
||||
|
||||
TAGS: "netgo osusergo sqlite_omit_load_extension"
|
||||
|
||||
# A note on actions and third party code... The actions under actions/ (like
|
||||
# `uses: actions/checkout`) are maintained by GitHub, and we need to trust
|
||||
# GitHub to maintain their code and infrastructure or we're in deep shit in
|
||||
@@ -48,7 +46,7 @@ jobs:
|
||||
runner: ["windows-latest", "ubuntu-latest", "macos-latest"]
|
||||
# The oldest version in this list should match what we have in our go.mod.
|
||||
# Variables don't seem to be supported here, or we could have done something nice.
|
||||
go: ["~1.22.6", "~1.23.0"]
|
||||
go: ["~1.23.0", "~1.24.0"]
|
||||
runs-on: ${{ matrix.runner }}
|
||||
steps:
|
||||
- name: Set git to use LF
|
||||
@@ -87,6 +85,7 @@ jobs:
|
||||
LOKI_USER: ${{ secrets.LOKI_USER }}
|
||||
LOKI_PASSWORD: ${{ secrets.LOKI_PASSWORD }}
|
||||
LOKI_LABELS: "go=${{ matrix.go }},runner=${{ matrix.runner }},repo=${{ github.repository }},ref=${{ github.ref }}"
|
||||
CGO_ENABLED: "1"
|
||||
|
||||
#
|
||||
# Meta checks for formatting, copyright, etc
|
||||
@@ -127,6 +126,7 @@ jobs:
|
||||
- package-cross
|
||||
- package-source
|
||||
- package-debian
|
||||
- package-windows
|
||||
- govulncheck
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -137,19 +137,8 @@ jobs:
|
||||
|
||||
package-windows:
|
||||
name: Package for Windows
|
||||
if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/heads/release-') || startsWith(github.ref, 'refs/tags/v'))
|
||||
environment: release
|
||||
runs-on: windows-latest
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set git to use LF
|
||||
# Without this, the checkout will happen with CRLF line endings,
|
||||
# which is fine for the source code but messes up tests that depend
|
||||
# on data on disk being as expected. Ideally, those tests should be
|
||||
# fixed, but not today.
|
||||
run: |
|
||||
git config --global core.autocrlf false
|
||||
git config --global core.eol lf
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
@@ -161,17 +150,14 @@ jobs:
|
||||
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: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~\AppData\Local\go-build
|
||||
~\go\pkg\mod
|
||||
key: ${{ runner.os }}-go-${{ env.GO_VERSION }}-package-${{ hashFiles('**/go.sum') }}
|
||||
~/.cache/go-build
|
||||
~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ env.GO_VERSION }}-package-windows-${{ hashFiles('**/go.sum') }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
@@ -179,25 +165,73 @@ jobs:
|
||||
|
||||
- name: Create packages
|
||||
run: |
|
||||
$targets = 'syncthing', 'stdiscosrv', 'strelaysrv'
|
||||
$archs = 'amd64', 'arm', 'arm64', '386'
|
||||
foreach ($arch in $archs) {
|
||||
foreach ($tgt in $targets) {
|
||||
go run build.go -goarch $arch zip $tgt
|
||||
}
|
||||
}
|
||||
for tgt in syncthing stdiscosrv strelaysrv ; do
|
||||
go run build.go -tags "${{env.TAGS}}" -goos windows -goarch amd64 -cc "zig cc -target x86_64-windows" zip $tgt
|
||||
go run build.go -tags "${{env.TAGS}}" -goos windows -goarch 386 -cc "zig cc -target x86-windows" zip $tgt
|
||||
go run build.go -tags "${{env.TAGS}}" -goos windows -goarch arm64 -cc "zig cc -target aarch64-windows" zip $tgt
|
||||
# go run build.go -tags "${{env.TAGS}}" -goos windows -goarch arm -cc "zig cc -target thumb-windows" zip $tgt # failes with linker errors
|
||||
done
|
||||
env:
|
||||
CGO_ENABLED: "0"
|
||||
CODESIGN_SIGNTOOL: ${{ secrets.CODESIGN_SIGNTOOL }}
|
||||
CODESIGN_CERTIFICATE_BASE64: ${{ secrets.CODESIGN_CERTIFICATE_BASE64 }}
|
||||
CODESIGN_CERTIFICATE_PASSWORD: ${{ secrets.CODESIGN_CERTIFICATE_PASSWORD }}
|
||||
CODESIGN_TIMESTAMP_SERVER: ${{ secrets.CODESIGN_TIMESTAMP_SERVER }}
|
||||
CGO_ENABLED: "1"
|
||||
|
||||
- name: Archive artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: unsigned-packages-windows
|
||||
path: "*.zip"
|
||||
|
||||
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'))
|
||||
environment: release
|
||||
runs-on: windows-latest
|
||||
needs:
|
||||
- package-windows
|
||||
steps:
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: unsigned-packages-windows
|
||||
path: packages
|
||||
|
||||
- name: Extract packages
|
||||
working-directory: packages
|
||||
run: |
|
||||
$files = Get-ChildItem "." -Filter *.zip
|
||||
foreach ($file in $files) {
|
||||
7z x $file.Name
|
||||
}
|
||||
|
||||
- name: Sign files with Trusted Signing
|
||||
uses: azure/trusted-signing-action@v0.5.1
|
||||
with:
|
||||
azure-tenant-id: ${{ secrets.AZURE_TRUSTED_SIGNING_TENANT_ID }}
|
||||
azure-client-id: ${{ secrets.AZURE_TRUSTED_SIGNING_CLIENT_ID }}
|
||||
azure-client-secret: ${{ secrets.AZURE_TRUSTED_SIGNING_CLIENT_SECRET }}
|
||||
endpoint: ${{ secrets.AZURE_TRUSTED_SIGNING_ENDPOINT }}
|
||||
trusted-signing-account-name: ${{ secrets.AZURE_TRUSTED_SIGNING_ACCOUNT }}
|
||||
certificate-profile-name: ${{ secrets.AZURE_TRUSTED_SIGNING_PROFILE }}
|
||||
files-folder: ${{ github.workspace }}\packages
|
||||
files-folder-filter: exe
|
||||
files-folder-recurse: true
|
||||
file-digest: SHA256
|
||||
timestamp-rfc3161: http://timestamp.acs.microsoft.com
|
||||
timestamp-digest: SHA256
|
||||
|
||||
- name: Repackage packages
|
||||
working-directory: packages
|
||||
run: |
|
||||
$files = Get-ChildItem "." -Filter *.zip
|
||||
foreach ($file in $files) {
|
||||
Remove-Item $file.Name
|
||||
7z a -tzip $file.Name $file.BaseName
|
||||
}
|
||||
|
||||
- name: Archive artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: packages-windows
|
||||
path: "*.zip"
|
||||
path: "packages/*.zip"
|
||||
|
||||
#
|
||||
# Linux
|
||||
@@ -223,6 +257,8 @@ jobs:
|
||||
go version
|
||||
echo "GO_VERSION=$(go version | sed 's#^.*go##;s# .*##')" >> $GITHUB_ENV
|
||||
|
||||
- uses: mlugg/setup-zig@v1
|
||||
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
@@ -232,14 +268,25 @@ jobs:
|
||||
|
||||
- name: Create packages
|
||||
run: |
|
||||
archs=$(go tool dist list | grep linux | sed 's#linux/##')
|
||||
for goarch in $archs ; do
|
||||
for tgt in syncthing stdiscosrv strelaysrv ; do
|
||||
go run build.go -goarch "$goarch" tar "$tgt"
|
||||
done
|
||||
sudo apt-get install -y gcc-mips64-linux-gnuabi64 gcc-mips64el-linux-gnuabi64
|
||||
for tgt in syncthing stdiscosrv strelaysrv ; do
|
||||
go run build.go -tags "${{env.TAGS}}" -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 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"
|
||||
go run build.go -tags "${{env.TAGS}}" -goos linux -goarch mips64 -cc mips64-linux-gnuabi64-gcc tar "$tgt"
|
||||
go run build.go -tags "${{env.TAGS}}" -goos linux -goarch mips64le -cc mips64el-linux-gnuabi64-gcc tar "$tgt"
|
||||
go run build.go -tags "${{env.TAGS}}" -goos linux -goarch riscv64 -cc "zig cc -target riscv64-linux-musl" tar "$tgt"
|
||||
go run build.go -tags "${{env.TAGS}}" -goos linux -goarch s390x -cc "zig cc -target s390x-linux-musl" tar "$tgt"
|
||||
go run build.go -tags "${{env.TAGS}}" -goos linux -goarch loong64 -cc "zig cc -target loongarch64-linux-musl" tar "$tgt"
|
||||
# go run build.go -tags "${{env.TAGS}}" -goos linux -goarch ppc64 -cc "zig cc -target powerpc64-linux-musl" tar "$tgt" # fails with linkmode not supported
|
||||
go run build.go -tags "${{env.TAGS}}" -goos linux -goarch ppc64le -cc "zig cc -target powerpc64le-linux-musl" tar "$tgt"
|
||||
done
|
||||
env:
|
||||
CGO_ENABLED: "0"
|
||||
CGO_ENABLED: "1"
|
||||
EXTRA_LDFLAGS: "-linkmode=external -extldflags=-static"
|
||||
|
||||
- name: Archive artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
@@ -257,6 +304,8 @@ jobs:
|
||||
name: Package for macOS
|
||||
if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/heads/release-') || startsWith(github.ref, 'refs/tags/v'))
|
||||
environment: release
|
||||
env:
|
||||
CODESIGN_IDENTITY: ${{ secrets.CODESIGN_IDENTITY }}
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -283,6 +332,7 @@ jobs:
|
||||
key: ${{ runner.os }}-go-${{ env.GO_VERSION }}-package-${{ hashFiles('**/go.sum') }}
|
||||
|
||||
- name: Import signing certificate
|
||||
if: env.CODESIGN_IDENTITY != ''
|
||||
run: |
|
||||
# Set up a run-specific keychain, making it available for the
|
||||
# `codesign` tool.
|
||||
@@ -310,7 +360,7 @@ jobs:
|
||||
- name: Create package (amd64)
|
||||
run: |
|
||||
for tgt in syncthing stdiscosrv strelaysrv ; do
|
||||
go run build.go -goarch amd64 zip "$tgt"
|
||||
go run build.go -tags "${{env.TAGS}}" -goarch amd64 zip "$tgt"
|
||||
done
|
||||
env:
|
||||
CGO_ENABLED: "1"
|
||||
@@ -326,7 +376,7 @@ jobs:
|
||||
EOT
|
||||
chmod 755 xgo.sh
|
||||
for tgt in syncthing stdiscosrv strelaysrv ; do
|
||||
go run build.go -gocmd ./xgo.sh -goarch arm64 zip "$tgt"
|
||||
go run build.go -tags "${{env.TAGS}}" -gocmd ./xgo.sh -goarch arm64 zip "$tgt"
|
||||
done
|
||||
env:
|
||||
CGO_ENABLED: "1"
|
||||
@@ -355,7 +405,7 @@ jobs:
|
||||
|
||||
notarize-macos:
|
||||
name: Notarize for macOS
|
||||
if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/heads/release-') || startsWith(github.ref, 'refs/tags/v'))
|
||||
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'))
|
||||
environment: release
|
||||
needs:
|
||||
- package-macos
|
||||
@@ -437,7 +487,7 @@ jobs:
|
||||
goarch="${plat#*/}"
|
||||
echo "::group ::$plat"
|
||||
for tgt in syncthing stdiscosrv strelaysrv ; do
|
||||
if ! go run build.go -goos "$goos" -goarch "$goarch" tar "$tgt" 2>/dev/null; then
|
||||
if ! go run build.go -goos "$goos" -goarch "$goarch" tar "$tgt" ; then
|
||||
echo "::warning ::Failed to build $tgt for $plat"
|
||||
fi
|
||||
done
|
||||
@@ -499,10 +549,10 @@ jobs:
|
||||
|
||||
sign-for-upgrade:
|
||||
name: Sign for upgrade
|
||||
if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/heads/release-') || startsWith(github.ref, 'refs/tags/v'))
|
||||
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'))
|
||||
environment: release
|
||||
needs:
|
||||
- package-windows
|
||||
- codesign-windows
|
||||
- package-linux
|
||||
- package-macos
|
||||
- package-cross
|
||||
@@ -617,6 +667,8 @@ jobs:
|
||||
run: |
|
||||
gem install fpm
|
||||
|
||||
- uses: mlugg/setup-zig@v1
|
||||
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
@@ -624,15 +676,17 @@ jobs:
|
||||
~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ env.GO_VERSION }}-debian-${{ hashFiles('**/go.sum') }}
|
||||
|
||||
- name: Package for Debian
|
||||
- name: Package for Debian (CGO)
|
||||
run: |
|
||||
for arch in amd64 i386 armhf armel arm64 ; do
|
||||
for tgt in syncthing stdiscosrv strelaysrv ; do
|
||||
go run build.go -no-upgrade -installsuffix=no-upgrade -goarch "$arch" deb "$tgt"
|
||||
done
|
||||
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 arm64 -cc "zig cc -target aarch64-linux-musl" deb "$tgt"
|
||||
done
|
||||
env:
|
||||
BUILD_USER: debian
|
||||
CGO_ENABLED: "1"
|
||||
EXTRA_LDFLAGS: "-linkmode=external -extldflags=-static"
|
||||
|
||||
- name: Archive artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
@@ -646,7 +700,7 @@ jobs:
|
||||
|
||||
publish-nightly:
|
||||
name: Publish nightly build
|
||||
if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && startsWith(github.ref, 'refs/heads/release-nightly')
|
||||
if: github.repository_owner == 'syncthing' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && startsWith(github.ref, 'refs/heads/release-nightly')
|
||||
environment: release
|
||||
needs:
|
||||
- sign-for-upgrade
|
||||
@@ -680,15 +734,12 @@ jobs:
|
||||
- name: Push artifacts
|
||||
uses: docker://docker.io/rclone/rclone:latest
|
||||
env:
|
||||
RCLONE_CONFIG_OBJSTORE_TYPE: s3
|
||||
RCLONE_CONFIG_OBJSTORE_PROVIDER: ${{ secrets.S3_PROVIDER }}
|
||||
RCLONE_CONFIG_OBJSTORE_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY_ID }}
|
||||
RCLONE_CONFIG_OBJSTORE_SECRET_ACCESS_KEY: ${{ secrets.S3_SECRET_ACCESS_KEY }}
|
||||
RCLONE_CONFIG_OBJSTORE_ENDPOINT: ${{ secrets.S3_ENDPOINT }}
|
||||
RCLONE_CONFIG_OBJSTORE_REGION: ${{ secrets.S3_REGION }}
|
||||
RCLONE_CONFIG_OBJSTORE_ACL: public-read
|
||||
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
|
||||
with:
|
||||
args: sync packages objstore:${{ secrets.S3_BUCKET }}/nightly
|
||||
args: sync -v packages objstore:nightly
|
||||
|
||||
#
|
||||
# Push release artifacts to Spaces
|
||||
@@ -696,7 +747,7 @@ jobs:
|
||||
|
||||
publish-release-files:
|
||||
name: Publish release files
|
||||
if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/tags/v'))
|
||||
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
|
||||
needs:
|
||||
- sign-for-upgrade
|
||||
@@ -734,28 +785,22 @@ jobs:
|
||||
- name: Push to object store (${{ env.VERSION }})
|
||||
uses: docker://docker.io/rclone/rclone:latest
|
||||
env:
|
||||
RCLONE_CONFIG_OBJSTORE_TYPE: s3
|
||||
RCLONE_CONFIG_OBJSTORE_PROVIDER: ${{ secrets.S3_PROVIDER }}
|
||||
RCLONE_CONFIG_OBJSTORE_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY_ID }}
|
||||
RCLONE_CONFIG_OBJSTORE_SECRET_ACCESS_KEY: ${{ secrets.S3_SECRET_ACCESS_KEY }}
|
||||
RCLONE_CONFIG_OBJSTORE_ENDPOINT: ${{ secrets.S3_ENDPOINT }}
|
||||
RCLONE_CONFIG_OBJSTORE_REGION: ${{ secrets.S3_REGION }}
|
||||
RCLONE_CONFIG_OBJSTORE_ACL: public-read
|
||||
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
|
||||
with:
|
||||
args: sync packages objstore:${{ secrets.S3_BUCKET }}/release/${{ env.VERSION }}
|
||||
args: sync -v packages objstore:release/${{ env.VERSION }}
|
||||
|
||||
- name: Push to object store (latest)
|
||||
uses: docker://docker.io/rclone/rclone:latest
|
||||
env:
|
||||
RCLONE_CONFIG_OBJSTORE_TYPE: s3
|
||||
RCLONE_CONFIG_OBJSTORE_PROVIDER: ${{ secrets.S3_PROVIDER }}
|
||||
RCLONE_CONFIG_OBJSTORE_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY_ID }}
|
||||
RCLONE_CONFIG_OBJSTORE_SECRET_ACCESS_KEY: ${{ secrets.S3_SECRET_ACCESS_KEY }}
|
||||
RCLONE_CONFIG_OBJSTORE_ENDPOINT: ${{ secrets.S3_ENDPOINT }}
|
||||
RCLONE_CONFIG_OBJSTORE_REGION: ${{ secrets.S3_REGION }}
|
||||
RCLONE_CONFIG_OBJSTORE_ACL: public-read
|
||||
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
|
||||
with:
|
||||
args: sync objstore:${{ secrets.S3_BUCKET }}/release/${{ env.VERSION }} objstore:${{ secrets.S3_BUCKET }}/release/latest
|
||||
args: sync -v objstore:release/${{ env.VERSION }} objstore:release/latest
|
||||
|
||||
#
|
||||
# Push Debian/APT archive
|
||||
@@ -763,7 +808,7 @@ jobs:
|
||||
|
||||
publish-apt:
|
||||
name: Publish APT
|
||||
if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/heads/release-') || startsWith(github.ref, 'refs/tags/v'))
|
||||
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'))
|
||||
environment: release
|
||||
needs:
|
||||
- package-debian
|
||||
@@ -790,7 +835,9 @@ jobs:
|
||||
- name: Prepare packages
|
||||
run: |
|
||||
kind=stable
|
||||
if [[ $VERSION == *-rc.[0-9] ]] ; then
|
||||
if [[ $VERSION == v2* ]] ; then
|
||||
kind=v2
|
||||
elif [[ $VERSION == *-rc.[0-9] ]] ; then
|
||||
kind=candidate
|
||||
elif [[ $VERSION == *-* ]] ; then
|
||||
kind=nightly
|
||||
@@ -802,15 +849,11 @@ jobs:
|
||||
- name: Pull archive
|
||||
uses: docker://docker.io/rclone/rclone:latest
|
||||
env:
|
||||
RCLONE_CONFIG_OBJSTORE_TYPE: s3
|
||||
RCLONE_CONFIG_OBJSTORE_PROVIDER: ${{ secrets.S3_PROVIDER }}
|
||||
RCLONE_CONFIG_OBJSTORE_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY_ID }}
|
||||
RCLONE_CONFIG_OBJSTORE_SECRET_ACCESS_KEY: ${{ secrets.S3_SECRET_ACCESS_KEY }}
|
||||
RCLONE_CONFIG_OBJSTORE_ENDPOINT: ${{ secrets.S3_ENDPOINT }}
|
||||
RCLONE_CONFIG_OBJSTORE_REGION: ${{ secrets.S3_REGION }}
|
||||
RCLONE_CONFIG_OBJSTORE_ACL: public-read
|
||||
RCLONE_CONFIG_OBJSTORE_TYPE: ${{ secrets.AZUREBLOB_TYPE }}
|
||||
RCLONE_CONFIG_OBJSTORE_ACCOUNT: ${{ secrets.AZUREBLOB_ACCOUNT }}
|
||||
RCLONE_CONFIG_OBJSTORE_KEY: ${{ secrets.AZUREBLOB_KEY }}
|
||||
with:
|
||||
args: sync objstore:syncthing-apt/dists dists
|
||||
args: sync objstore:apt/dists dists
|
||||
|
||||
- name: Update archive
|
||||
uses: docker://ghcr.io/kastelo/ezapt:latest
|
||||
@@ -825,15 +868,12 @@ jobs:
|
||||
- name: Push archive
|
||||
uses: docker://docker.io/rclone/rclone:latest
|
||||
env:
|
||||
RCLONE_CONFIG_OBJSTORE_TYPE: s3
|
||||
RCLONE_CONFIG_OBJSTORE_PROVIDER: ${{ secrets.S3_PROVIDER }}
|
||||
RCLONE_CONFIG_OBJSTORE_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY_ID }}
|
||||
RCLONE_CONFIG_OBJSTORE_SECRET_ACCESS_KEY: ${{ secrets.S3_SECRET_ACCESS_KEY }}
|
||||
RCLONE_CONFIG_OBJSTORE_ENDPOINT: ${{ secrets.S3_ENDPOINT }}
|
||||
RCLONE_CONFIG_OBJSTORE_REGION: ${{ secrets.S3_REGION }}
|
||||
RCLONE_CONFIG_OBJSTORE_ACL: public-read
|
||||
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
|
||||
with:
|
||||
args: sync dists -v objstore:syncthing-apt/dists
|
||||
args: sync -v dists objstore:apt/dists
|
||||
|
||||
#
|
||||
# Build and push to Docker Hub
|
||||
@@ -842,8 +882,10 @@ jobs:
|
||||
docker-syncthing:
|
||||
name: Build and push Docker images
|
||||
runs-on: ubuntu-latest
|
||||
if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/release' || github.ref == 'refs/heads/infrastructure' || startsWith(github.ref, 'refs/heads/release-') || startsWith(github.ref, 'refs/tags/v'))
|
||||
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
|
||||
environment: docker
|
||||
env:
|
||||
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
@@ -856,13 +898,13 @@ jobs:
|
||||
include:
|
||||
- pkg: syncthing
|
||||
dockerfile: Dockerfile
|
||||
image: syncthing/syncthing
|
||||
image: syncthing
|
||||
- pkg: strelaysrv
|
||||
dockerfile: Dockerfile.strelaysrv
|
||||
image: syncthing/relaysrv
|
||||
image: relaysrv
|
||||
- pkg: stdiscosrv
|
||||
dockerfile: Dockerfile.stdiscosrv
|
||||
image: syncthing/discosrv
|
||||
image: discosrv
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -880,6 +922,8 @@ jobs:
|
||||
go version
|
||||
echo "GO_VERSION=$(go version | sed 's#^.*go##;s# .*##')" >> $GITHUB_ENV
|
||||
|
||||
- uses: mlugg/setup-zig@v1
|
||||
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
@@ -887,33 +931,34 @@ jobs:
|
||||
~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ env.GO_VERSION }}-docker-${{ matrix.pkg }}-${{ hashFiles('**/go.sum') }}
|
||||
|
||||
- name: Build binaries
|
||||
- name: Build binaries (CGO)
|
||||
run: |
|
||||
for arch in amd64 arm64 arm; do
|
||||
go run build.go -goos linux -goarch "$arch" -no-upgrade build ${{ matrix.pkg }}
|
||||
mv ${{ matrix.pkg }} ${{ matrix.pkg }}-linux-"$arch"
|
||||
done
|
||||
env:
|
||||
CGO_ENABLED: "0"
|
||||
BUILD_USER: docker
|
||||
# amd64
|
||||
go run build.go -goos linux -goarch amd64 -tags "${{env.TAGS}}" -cc "zig cc -target x86_64-linux-musl" -no-upgrade build ${{ matrix.pkg }}
|
||||
mv ${{ matrix.pkg }} ${{ matrix.pkg }}-linux-amd64
|
||||
|
||||
- name: Check if we will be able to push images
|
||||
run: |
|
||||
if [[ "${{ secrets.DOCKERHUB_TOKEN }}" != "" ]]; then
|
||||
echo "DOCKER_PUSH=true" >> $GITHUB_ENV;
|
||||
fi
|
||||
# arm64
|
||||
go run build.go -goos linux -goarch arm64 -tags "${{env.TAGS}}" -cc "zig cc -target aarch64-linux-musl" -no-upgrade build ${{ matrix.pkg }}
|
||||
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 }}
|
||||
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.DOCKER_PUSH == 'true'
|
||||
if: env.DOCKERHUB_USERNAME != ''
|
||||
with:
|
||||
registry: docker.io
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
username: ${{ env.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@v3
|
||||
if: env.DOCKER_PUSH == 'true'
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
@@ -926,18 +971,31 @@ jobs:
|
||||
run: |
|
||||
version=$(go run build.go version)
|
||||
version=${version#v}
|
||||
repo=ghcr.io/${{ github.repository_owner }}/${{ matrix.image }}
|
||||
ref="${{github.ref_name}}"
|
||||
ref=${ref//\//-} # slashes to dashes
|
||||
|
||||
# List of tags for ghcr.io
|
||||
if [[ $version == @([0-9]|[0-9][0-9]).@([0-9]|[0-9][0-9]).@([0-9]|[0-9][0-9]) ]] ; then
|
||||
echo Release version, pushing to :latest and version tags
|
||||
major=${version%.*.*}
|
||||
minor=${version%.*}
|
||||
tags=docker.io/${{ matrix.image }}:$version,ghcr.io/${{ matrix.image }}:$version,docker.io/${{ matrix.image }}:$major,ghcr.io/${{ matrix.image }}:$major,docker.io/${{ matrix.image }}:$minor,ghcr.io/${{ matrix.image }}:$minor,docker.io/${{ matrix.image }}:latest,ghcr.io/${{ matrix.image }}:latest
|
||||
tags=$repo:$version,$repo:$major,$repo:$minor,$repo:latest
|
||||
elif [[ $version == *-rc.@([0-9]|[0-9][0-9]) ]] ; then
|
||||
echo Release candidate, pushing to :rc and version tags
|
||||
tags=docker.io/${{ matrix.image }}:$version,ghcr.io/${{ matrix.image }}:$version,docker.io/${{ matrix.image }}:rc,ghcr.io/${{ matrix.image }}:rc
|
||||
tags=$repo:$version,$repo:rc
|
||||
elif [[ $ref == "main" ]] ; then
|
||||
tags=$repo:edge
|
||||
else
|
||||
echo Development version, pushing to :edge
|
||||
tags=docker.io/${{ matrix.image }}:edge,ghcr.io/${{ matrix.image }}:edge
|
||||
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
|
||||
|
||||
@@ -947,8 +1005,8 @@ jobs:
|
||||
context: .
|
||||
file: ${{ matrix.dockerfile }}
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/7
|
||||
push: ${{ env.DOCKER_PUSH == 'true' }}
|
||||
tags: ${{ env.DOCKER_TAGS }}
|
||||
push: true
|
||||
labels: |
|
||||
org.opencontainers.image.version=${{ env.VERSION }}
|
||||
org.opencontainers.image.revision=${{ github.sha }}
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -17,5 +17,4 @@ deb
|
||||
*.bz2
|
||||
/repos
|
||||
/proto/scripts/protoc-gen-gosyncthing
|
||||
/gui/next-gen-gui
|
||||
/compat.json
|
||||
|
||||
@@ -3,6 +3,7 @@ linters:
|
||||
disable:
|
||||
- cyclop
|
||||
- depguard
|
||||
- err113
|
||||
- exhaustive
|
||||
- exhaustruct
|
||||
- funlen
|
||||
@@ -12,6 +13,7 @@ linters:
|
||||
- gocognit
|
||||
- goconst
|
||||
- gocyclo
|
||||
- godot
|
||||
- godox
|
||||
- gofmt
|
||||
- goimports
|
||||
@@ -21,15 +23,19 @@ linters:
|
||||
- ireturn
|
||||
- lll
|
||||
- maintidx
|
||||
- musttag
|
||||
- nestif
|
||||
- nlreturn
|
||||
- nonamedreturns
|
||||
- paralleltest
|
||||
- prealloc
|
||||
- protogetter
|
||||
- scopelint
|
||||
- tagalign
|
||||
- tagliatelle
|
||||
- testpackage
|
||||
- varnamelen
|
||||
- wrapcheck
|
||||
- wsl
|
||||
|
||||
issues:
|
||||
|
||||
@@ -42,7 +42,6 @@ approval_rules:
|
||||
paths:
|
||||
- ^[^/]+\.md
|
||||
- ^\.policy\.yml
|
||||
- ^\.github/
|
||||
- ^LICENSE
|
||||
requires:
|
||||
count: 1
|
||||
|
||||
5
AUTHORS
5
AUTHORS
@@ -97,6 +97,7 @@ Daniel Harte (norgeous) <daniel@harte.me> <daniel@danielharte.co.uk> <norgeous@u
|
||||
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>
|
||||
@@ -222,6 +223,7 @@ Marc Laporte (marclaporte) <marc@marclaporte.com> <marc@laporte.name>
|
||||
Marc Pujol (kilburn) <kilburn@la3.org>
|
||||
Marcin Dziadus (marcindziadus) <dziadus.marcin@gmail.com>
|
||||
marco-m <marco.molteni@laposte.net>
|
||||
Marcus B Spencer <marcus@marcusspencer.xyz>
|
||||
Marcus Legendre <marcus.legendre@gmail.com>
|
||||
Mario Majila <mariustshipichik@gmail.com>
|
||||
Mark Pulford (mpx) <mark@kyne.com.au>
|
||||
@@ -229,6 +231,7 @@ 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>
|
||||
Matic Potočnik <hairyfotr@gmail.com>
|
||||
Matt Burke (burkemw3) <mburke@amplify.com> <burkemw3@gmail.com>
|
||||
Matt Robenolt <matt@ydekproductions.com>
|
||||
@@ -287,6 +290,7 @@ 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>
|
||||
Quentin Hibon <qh.public@yahoo.com>
|
||||
Rahmi Pruitt <rjpruitt16@gmail.com>
|
||||
@@ -320,6 +324,7 @@ 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>
|
||||
Thomas <9749173+uhthomas@users.noreply.github.com>
|
||||
|
||||
179
build.go
179
build.go
@@ -15,7 +15,6 @@ import (
|
||||
"bytes"
|
||||
"compress/flate"
|
||||
"compress/gzip"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
@@ -39,27 +38,26 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
goarch string
|
||||
goos string
|
||||
noupgrade bool
|
||||
version string
|
||||
goCmd string
|
||||
race bool
|
||||
debug = os.Getenv("BUILDDEBUG") != ""
|
||||
extraTags string
|
||||
installSuffix string
|
||||
pkgdir string
|
||||
cc string
|
||||
run string
|
||||
benchRun string
|
||||
buildOut string
|
||||
debugBinary bool
|
||||
coverage bool
|
||||
long bool
|
||||
timeout = "120s"
|
||||
longTimeout = "600s"
|
||||
numVersions = 5
|
||||
withNextGenGUI = os.Getenv("BUILD_NEXT_GEN_GUI") != ""
|
||||
goarch string
|
||||
goos string
|
||||
noupgrade bool
|
||||
version string
|
||||
goCmd string
|
||||
race bool
|
||||
debug = os.Getenv("BUILDDEBUG") != ""
|
||||
extraTags string
|
||||
installSuffix string
|
||||
pkgdir string
|
||||
cc string
|
||||
run string
|
||||
benchRun string
|
||||
buildOut string
|
||||
debugBinary bool
|
||||
coverage bool
|
||||
long bool
|
||||
timeout = "120s"
|
||||
longTimeout = "600s"
|
||||
numVersions = 5
|
||||
)
|
||||
|
||||
type target struct {
|
||||
@@ -290,10 +288,10 @@ func runCommand(cmd string, target target) {
|
||||
build(target, tags)
|
||||
|
||||
case "test":
|
||||
test(strings.Fields(extraTags), "github.com/syncthing/syncthing/lib/...", "github.com/syncthing/syncthing/cmd/...")
|
||||
test(strings.Fields(extraTags), "github.com/syncthing/syncthing/internal/...", "github.com/syncthing/syncthing/lib/...", "github.com/syncthing/syncthing/cmd/...")
|
||||
|
||||
case "bench":
|
||||
bench(strings.Fields(extraTags), "github.com/syncthing/syncthing/lib/...", "github.com/syncthing/syncthing/cmd/...")
|
||||
bench(strings.Fields(extraTags), "github.com/syncthing/syncthing/internal/...", "github.com/syncthing/syncthing/lib/...", "github.com/syncthing/syncthing/cmd/...")
|
||||
|
||||
case "integration":
|
||||
integration(false)
|
||||
@@ -381,7 +379,6 @@ func parseFlags() {
|
||||
flag.IntVar(&numVersions, "num-versions", numVersions, "Number of versions for changelog command")
|
||||
flag.StringVar(&run, "run", "", "Specify which tests to run")
|
||||
flag.StringVar(&benchRun, "bench", "", "Specify which benchmarks to run")
|
||||
flag.BoolVar(&withNextGenGUI, "with-next-gen-gui", withNextGenGUI, "Also build 'newgui'")
|
||||
flag.StringVar(&buildOut, "build-out", "", "Set the '-o' value for 'go build'")
|
||||
flag.Parse()
|
||||
}
|
||||
@@ -454,10 +451,6 @@ func benchArgs() []string {
|
||||
}
|
||||
|
||||
func install(target target, tags []string) {
|
||||
if (target.name == "syncthing" || target.name == "") && !withNextGenGUI {
|
||||
log.Println("Notice: Next generation GUI will not be built; see --with-next-gen-gui.")
|
||||
}
|
||||
|
||||
lazyRebuildAssets()
|
||||
|
||||
tags = append(target.tags, tags...)
|
||||
@@ -481,16 +474,12 @@ func install(target target, tags []string) {
|
||||
defer shouldCleanupSyso(sysoPath)
|
||||
}
|
||||
|
||||
args := []string{"install", "-v"}
|
||||
args := []string{"install"}
|
||||
args = appendParameters(args, tags, target.buildPkgs...)
|
||||
runPrint(goCmd, args...)
|
||||
}
|
||||
|
||||
func build(target target, tags []string) {
|
||||
if (target.name == "syncthing" || target.name == "") && !withNextGenGUI {
|
||||
log.Println("Notice: Next generation GUI will not be built; see --with-next-gen-gui.")
|
||||
}
|
||||
|
||||
lazyRebuildAssets()
|
||||
tags = append(target.tags, tags...)
|
||||
|
||||
@@ -513,7 +502,7 @@ func build(target target, tags []string) {
|
||||
defer shouldCleanupSyso(sysoPath)
|
||||
}
|
||||
|
||||
args := []string{"build", "-v"}
|
||||
args := []string{"build"}
|
||||
if buildOut != "" {
|
||||
args = append(args, "-o", buildOut)
|
||||
}
|
||||
@@ -525,13 +514,6 @@ func setBuildEnvVars() {
|
||||
os.Setenv("GOOS", goos)
|
||||
os.Setenv("GOARCH", goarch)
|
||||
os.Setenv("CC", cc)
|
||||
if os.Getenv("CGO_ENABLED") == "" {
|
||||
switch goos {
|
||||
case "darwin", "solaris":
|
||||
default:
|
||||
os.Setenv("CGO_ENABLED", "0")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func appendParameters(args []string, tags []string, pkgs ...string) []string {
|
||||
@@ -646,6 +628,9 @@ func buildDeb(target target) {
|
||||
// than just 0.14.26. This rectifies that.
|
||||
debver = strings.Replace(debver, "-", "~", -1)
|
||||
}
|
||||
if strings.Contains(debver, "_") {
|
||||
debver = strings.Replace(debver, "_", "~", -1)
|
||||
}
|
||||
args := []string{
|
||||
"-t", "deb",
|
||||
"-s", "dir",
|
||||
@@ -747,12 +732,9 @@ func shouldBuildSyso(dir string) (string, error) {
|
||||
sysoPath := filepath.Join(dir, "cmd", "syncthing", "resource.syso")
|
||||
|
||||
// See https://github.com/josephspurrier/goversioninfo#command-line-flags
|
||||
armOption := ""
|
||||
if strings.Contains(goarch, "arm") {
|
||||
armOption = "-arm=true"
|
||||
}
|
||||
|
||||
if _, err := runError("goversioninfo", "-o", sysoPath, armOption); err != nil {
|
||||
arm := strings.HasPrefix(goarch, "arm")
|
||||
a64 := strings.Contains(goarch, "64")
|
||||
if _, err := runError("goversioninfo", "-o", sysoPath, fmt.Sprintf("-arm=%v", arm), fmt.Sprintf("-64=%v", a64)); err != nil {
|
||||
return "", errors.New("failed to create " + sysoPath + ": " + err.Error())
|
||||
}
|
||||
|
||||
@@ -824,43 +806,11 @@ func lazyRebuildAssets() {
|
||||
shouldRebuild := shouldRebuildAssets("lib/api/auto/gui.files.go", "gui") ||
|
||||
shouldRebuildAssets("cmd/infra/strelaypoolsrv/auto/gui.files.go", "cmd/infra/strelaypoolsrv/gui")
|
||||
|
||||
if withNextGenGUI {
|
||||
shouldRebuild = buildNextGenGUI() || shouldRebuild
|
||||
}
|
||||
|
||||
if shouldRebuild {
|
||||
rebuildAssets()
|
||||
}
|
||||
}
|
||||
|
||||
func buildNextGenGUI() bool {
|
||||
// Check if we need to run the npm process, and if so also set the flag
|
||||
// to rebuild Go assets afterwards. The index.html is regenerated every
|
||||
// time by the build process. This assumes the new GUI ends up in
|
||||
// next-gen-gui/dist/next-gen-gui.
|
||||
|
||||
if !shouldRebuildAssets("gui/next-gen-gui/index.html", "next-gen-gui") {
|
||||
// The GUI is up to date.
|
||||
return false
|
||||
}
|
||||
|
||||
runPrintInDir("next-gen-gui", "npm", "install")
|
||||
runPrintInDir("next-gen-gui", "npm", "run", "build", "--", "--prod", "--subresource-integrity")
|
||||
|
||||
rmr("gui/tech-ui")
|
||||
|
||||
for _, src := range listFiles("next-gen-gui/dist") {
|
||||
rel, _ := filepath.Rel("next-gen-gui/dist", src)
|
||||
dst := filepath.Join("gui", rel)
|
||||
if err := copyFile(src, dst, 0o644); err != nil {
|
||||
fmt.Println("copy:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func shouldRebuildAssets(target, srcdir string) bool {
|
||||
info, err := os.Stat(target)
|
||||
if err != nil {
|
||||
@@ -1345,10 +1295,7 @@ func zipFile(out string, files []archiveFile) {
|
||||
}
|
||||
|
||||
func codesign(target target) {
|
||||
switch goos {
|
||||
case "windows":
|
||||
windowsCodesign(target.BinaryName())
|
||||
case "darwin":
|
||||
if goos == "darwin" {
|
||||
macosCodesign(target.BinaryName())
|
||||
}
|
||||
}
|
||||
@@ -1372,70 +1319,6 @@ func macosCodesign(file string) {
|
||||
}
|
||||
}
|
||||
|
||||
func windowsCodesign(file string) {
|
||||
st := "signtool.exe"
|
||||
|
||||
if path := os.Getenv("CODESIGN_SIGNTOOL"); path != "" {
|
||||
st = path
|
||||
}
|
||||
|
||||
for i, algo := range []string{"sha1", "sha256"} {
|
||||
args := []string{"sign", "/fd", algo}
|
||||
if f := os.Getenv("CODESIGN_CERTIFICATE_FILE"); f != "" {
|
||||
args = append(args, "/f", f)
|
||||
} else if b := os.Getenv("CODESIGN_CERTIFICATE_BASE64"); b != "" {
|
||||
// Decode the PFX certificate from base64.
|
||||
bs, err := base64.RawStdEncoding.DecodeString(b)
|
||||
if err != nil {
|
||||
log.Println("Codesign: signing failed: decoding base64:", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Write it to a temporary file
|
||||
f, err := os.CreateTemp("", "codesign-*.pfx")
|
||||
if err != nil {
|
||||
log.Println("Codesign: signing failed: creating temp file:", err)
|
||||
return
|
||||
}
|
||||
_ = f.Chmod(0o600) // best effort remove other users' access
|
||||
defer os.Remove(f.Name())
|
||||
if _, err := f.Write(bs); err != nil {
|
||||
log.Println("Codesign: signing failed: writing temp file:", err)
|
||||
return
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
log.Println("Codesign: signing failed: closing temp file:", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Use that when signing
|
||||
args = append(args, "/f", f.Name())
|
||||
}
|
||||
if p := os.Getenv("CODESIGN_CERTIFICATE_PASSWORD"); p != "" {
|
||||
args = append(args, "/p", p)
|
||||
}
|
||||
if tr := os.Getenv("CODESIGN_TIMESTAMP_SERVER"); tr != "" {
|
||||
switch algo {
|
||||
case "sha256":
|
||||
args = append(args, "/tr", tr, "/td", algo)
|
||||
default:
|
||||
args = append(args, "/t", tr)
|
||||
}
|
||||
}
|
||||
if i > 0 {
|
||||
args = append(args, "/as")
|
||||
}
|
||||
args = append(args, file)
|
||||
|
||||
bs, err := runError(st, args...)
|
||||
if err != nil {
|
||||
log.Printf("Codesign: signing failed: %v: %s", err, string(bs))
|
||||
return
|
||||
}
|
||||
log.Println("Codesign: successfully signed", file, "using", algo)
|
||||
}
|
||||
}
|
||||
|
||||
func metalint() {
|
||||
lazyRebuildAssets()
|
||||
runPrint(goCmd, "test", "-run", "Metalint", "./meta")
|
||||
|
||||
@@ -72,7 +72,7 @@ func main() {
|
||||
if *standardBlocks || blockSize < protocol.MinBlockSize {
|
||||
blockSize = protocol.BlockSize(fi.Size())
|
||||
}
|
||||
bs, err := scanner.Blocks(context.TODO(), fd, blockSize, fi.Size(), nil, true)
|
||||
bs, err := scanner.Blocks(context.TODO(), fd, blockSize, fi.Size(), nil)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ to NAT or firewall issues.
|
||||
|
||||
There is very little reason why you'd want to run this yourself, as
|
||||
`relaypoolsrv` is just used for announcement and lookup of public relay
|
||||
servers. If you are looking to setup a private or a public relay, please
|
||||
servers. If you are looking to set up a private or a public relay, please
|
||||
check the documentation for
|
||||
[relaysrv](https://github.com/syncthing/relaysrv), which also explains how
|
||||
to join the default public pool.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -258,9 +258,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 +275,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,7 +111,7 @@ 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"
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
@@ -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.
|
||||
|
||||
@@ -41,5 +41,4 @@ func (p *profileCommand) Run(ctx Context) error {
|
||||
type debugCommand struct {
|
||||
File fileCommand `cmd:"" help:"Show information about a file (or directory/symlink)"`
|
||||
Profile profileCommand `cmd:"" help:"Save a profile to help figuring out what Syncthing does"`
|
||||
Index indexCommand `cmd:"" help:"Show information about the index (database)"`
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
// Copyright (C) 2014 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 cli
|
||||
|
||||
import (
|
||||
"github.com/alecthomas/kong"
|
||||
)
|
||||
|
||||
type indexCommand struct {
|
||||
Dump struct{} `cmd:"" help:"Print the entire db"`
|
||||
DumpSize struct{} `cmd:"" help:"Print the db size of different categories of information"`
|
||||
Check struct{} `cmd:"" help:"Check the database for inconsistencies"`
|
||||
Account struct{} `cmd:"" help:"Print key and value size statistics per key type"`
|
||||
}
|
||||
|
||||
func (*indexCommand) Run(kongCtx *kong.Context) error {
|
||||
switch kongCtx.Selected().Name {
|
||||
case "dump":
|
||||
return indexDump()
|
||||
case "dump-size":
|
||||
return indexDumpSize()
|
||||
case "check":
|
||||
return indexCheck()
|
||||
case "account":
|
||||
return indexAccount()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
// Copyright (C) 2020 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 cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"text/tabwriter"
|
||||
)
|
||||
|
||||
// indexAccount prints key and data size statistics per class
|
||||
func indexAccount() error {
|
||||
ldb, err := getDB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
it, err := ldb.NewPrefixIterator(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var ksizes [256]int
|
||||
var dsizes [256]int
|
||||
var counts [256]int
|
||||
var max [256]int
|
||||
|
||||
for it.Next() {
|
||||
key := it.Key()
|
||||
t := key[0]
|
||||
ds := len(it.Value())
|
||||
ks := len(key)
|
||||
s := ks + ds
|
||||
|
||||
counts[t]++
|
||||
ksizes[t] += ks
|
||||
dsizes[t] += ds
|
||||
if s > max[t] {
|
||||
max[t] = s
|
||||
}
|
||||
}
|
||||
|
||||
tw := tabwriter.NewWriter(os.Stdout, 1, 1, 1, ' ', tabwriter.AlignRight)
|
||||
toti, totds, totks := 0, 0, 0
|
||||
for t := range ksizes {
|
||||
if ksizes[t] > 0 {
|
||||
// yes metric kilobytes 🤘
|
||||
fmt.Fprintf(tw, "0x%02x:\t%d items,\t%d KB keys +\t%d KB data,\t%d B +\t%d B avg,\t%d B max\t\n", t, counts[t], ksizes[t]/1000, dsizes[t]/1000, ksizes[t]/counts[t], dsizes[t]/counts[t], max[t])
|
||||
toti += counts[t]
|
||||
totds += dsizes[t]
|
||||
totks += ksizes[t]
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(tw, "Total\t%d items,\t%d KB keys +\t%d KB data.\t\n", toti, totks/1000, totds/1000)
|
||||
tw.Flush()
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,162 +0,0 @@
|
||||
// Copyright (C) 2015 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 cli
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/syncthing/syncthing/internal/gen/bep"
|
||||
"github.com/syncthing/syncthing/internal/gen/dbproto"
|
||||
"github.com/syncthing/syncthing/lib/db"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
)
|
||||
|
||||
func indexDump() error {
|
||||
ldb, err := getDB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
it, err := ldb.NewPrefixIterator(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for it.Next() {
|
||||
key := it.Key()
|
||||
switch key[0] {
|
||||
case db.KeyTypeDevice:
|
||||
folder := binary.BigEndian.Uint32(key[1:])
|
||||
device := binary.BigEndian.Uint32(key[1+4:])
|
||||
name := nulString(key[1+4+4:])
|
||||
fmt.Printf("[device] F:%d D:%d N:%q", folder, device, name)
|
||||
|
||||
var f bep.FileInfo
|
||||
err := proto.Unmarshal(it.Value(), &f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf(" V:%v\n", &f)
|
||||
|
||||
case db.KeyTypeGlobal:
|
||||
folder := binary.BigEndian.Uint32(key[1:])
|
||||
name := nulString(key[1+4:])
|
||||
var flv dbproto.VersionList
|
||||
proto.Unmarshal(it.Value(), &flv)
|
||||
fmt.Printf("[global] F:%d N:%q V:%s\n", folder, name, &flv)
|
||||
|
||||
case db.KeyTypeBlock:
|
||||
folder := binary.BigEndian.Uint32(key[1:])
|
||||
hash := key[1+4 : 1+4+32]
|
||||
name := nulString(key[1+4+32:])
|
||||
fmt.Printf("[block] F:%d H:%x N:%q I:%d\n", folder, hash, name, binary.BigEndian.Uint32(it.Value()))
|
||||
|
||||
case db.KeyTypeDeviceStatistic:
|
||||
fmt.Printf("[dstat] K:%x V:%x\n", key, it.Value())
|
||||
|
||||
case db.KeyTypeFolderStatistic:
|
||||
fmt.Printf("[fstat] K:%x V:%x\n", key, it.Value())
|
||||
|
||||
case db.KeyTypeVirtualMtime:
|
||||
folder := binary.BigEndian.Uint32(key[1:])
|
||||
name := nulString(key[1+4:])
|
||||
val := it.Value()
|
||||
var realTime, virtualTime time.Time
|
||||
realTime.UnmarshalBinary(val[:len(val)/2])
|
||||
virtualTime.UnmarshalBinary(val[len(val)/2:])
|
||||
fmt.Printf("[mtime] F:%d N:%q R:%v V:%v\n", folder, name, realTime, virtualTime)
|
||||
|
||||
case db.KeyTypeFolderIdx:
|
||||
key := binary.BigEndian.Uint32(key[1:])
|
||||
fmt.Printf("[folderidx] K:%d V:%q\n", key, it.Value())
|
||||
|
||||
case db.KeyTypeDeviceIdx:
|
||||
key := binary.BigEndian.Uint32(key[1:])
|
||||
val := it.Value()
|
||||
device := "<nil>"
|
||||
if len(val) > 0 {
|
||||
dev, err := protocol.DeviceIDFromBytes(val)
|
||||
if err != nil {
|
||||
device = fmt.Sprintf("<invalid %d bytes>", len(val))
|
||||
} else {
|
||||
device = dev.String()
|
||||
}
|
||||
}
|
||||
fmt.Printf("[deviceidx] K:%d V:%s\n", key, device)
|
||||
|
||||
case db.KeyTypeIndexID:
|
||||
device := binary.BigEndian.Uint32(key[1:])
|
||||
folder := binary.BigEndian.Uint32(key[5:])
|
||||
fmt.Printf("[indexid] D:%d F:%d I:%x\n", device, folder, it.Value())
|
||||
|
||||
case db.KeyTypeFolderMeta:
|
||||
folder := binary.BigEndian.Uint32(key[1:])
|
||||
fmt.Printf("[foldermeta] F:%d", folder)
|
||||
var cs dbproto.CountsSet
|
||||
if err := proto.Unmarshal(it.Value(), &cs); err != nil {
|
||||
fmt.Printf(" (invalid)\n")
|
||||
} else {
|
||||
fmt.Printf(" V:%v\n", &cs)
|
||||
}
|
||||
|
||||
case db.KeyTypeMiscData:
|
||||
fmt.Printf("[miscdata] K:%q V:%q\n", key[1:], it.Value())
|
||||
|
||||
case db.KeyTypeSequence:
|
||||
folder := binary.BigEndian.Uint32(key[1:])
|
||||
seq := binary.BigEndian.Uint64(key[5:])
|
||||
fmt.Printf("[sequence] F:%d S:%d V:%q\n", folder, seq, it.Value())
|
||||
|
||||
case db.KeyTypeNeed:
|
||||
folder := binary.BigEndian.Uint32(key[1:])
|
||||
file := string(key[5:])
|
||||
fmt.Printf("[need] F:%d V:%q\n", folder, file)
|
||||
|
||||
case db.KeyTypeBlockList:
|
||||
fmt.Printf("[blocklist] H:%x\n", key[1:])
|
||||
|
||||
case db.KeyTypeBlockListMap:
|
||||
folder := binary.BigEndian.Uint32(key[1:])
|
||||
hash := key[5:37]
|
||||
fileName := string(key[37:])
|
||||
fmt.Printf("[blocklistmap] F:%d H:%x N:%s\n", folder, hash, fileName)
|
||||
|
||||
case db.KeyTypeVersion:
|
||||
fmt.Printf("[version] H:%x", key[1:])
|
||||
var v bep.Vector
|
||||
err := proto.Unmarshal(it.Value(), &v)
|
||||
if err != nil {
|
||||
fmt.Printf(" (invalid)\n")
|
||||
} else {
|
||||
fmt.Printf(" V:%v\n", &v)
|
||||
}
|
||||
|
||||
case db.KeyTypePendingFolder:
|
||||
device := binary.BigEndian.Uint32(key[1:])
|
||||
folder := string(key[5:])
|
||||
var of dbproto.ObservedFolder
|
||||
proto.Unmarshal(it.Value(), &of)
|
||||
fmt.Printf("[pendingFolder] D:%d F:%s V:%v\n", device, folder, &of)
|
||||
|
||||
case db.KeyTypePendingDevice:
|
||||
device := "<invalid>"
|
||||
dev, err := protocol.DeviceIDFromBytes(key[1:])
|
||||
if err == nil {
|
||||
device = dev.String()
|
||||
}
|
||||
var od dbproto.ObservedDevice
|
||||
proto.Unmarshal(it.Value(), &od)
|
||||
fmt.Printf("[pendingDevice] D:%v V:%v\n", device, &od)
|
||||
|
||||
default:
|
||||
fmt.Printf("[??? %d]\n %x\n %x\n", key[0], key, it.Value())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
// Copyright (C) 2015 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 cli
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/db"
|
||||
)
|
||||
|
||||
func indexDumpSize() error {
|
||||
type sizedElement struct {
|
||||
key string
|
||||
size int
|
||||
}
|
||||
|
||||
ldb, err := getDB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
it, err := ldb.NewPrefixIterator(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var elems []sizedElement
|
||||
for it.Next() {
|
||||
var ele sizedElement
|
||||
|
||||
key := it.Key()
|
||||
switch key[0] {
|
||||
case db.KeyTypeDevice:
|
||||
folder := binary.BigEndian.Uint32(key[1:])
|
||||
device := binary.BigEndian.Uint32(key[1+4:])
|
||||
name := nulString(key[1+4+4:])
|
||||
ele.key = fmt.Sprintf("DEVICE:%d:%d:%s", folder, device, name)
|
||||
|
||||
case db.KeyTypeGlobal:
|
||||
folder := binary.BigEndian.Uint32(key[1:])
|
||||
name := nulString(key[1+4:])
|
||||
ele.key = fmt.Sprintf("GLOBAL:%d:%s", folder, name)
|
||||
|
||||
case db.KeyTypeBlock:
|
||||
folder := binary.BigEndian.Uint32(key[1:])
|
||||
hash := key[1+4 : 1+4+32]
|
||||
name := nulString(key[1+4+32:])
|
||||
ele.key = fmt.Sprintf("BLOCK:%d:%x:%s", folder, hash, name)
|
||||
|
||||
case db.KeyTypeDeviceStatistic:
|
||||
ele.key = fmt.Sprintf("DEVICESTATS:%s", key[1:])
|
||||
|
||||
case db.KeyTypeFolderStatistic:
|
||||
ele.key = fmt.Sprintf("FOLDERSTATS:%s", key[1:])
|
||||
|
||||
case db.KeyTypeVirtualMtime:
|
||||
ele.key = fmt.Sprintf("MTIME:%s", key[1:])
|
||||
|
||||
case db.KeyTypeFolderIdx:
|
||||
id := binary.BigEndian.Uint32(key[1:])
|
||||
ele.key = fmt.Sprintf("FOLDERIDX:%d", id)
|
||||
|
||||
case db.KeyTypeDeviceIdx:
|
||||
id := binary.BigEndian.Uint32(key[1:])
|
||||
ele.key = fmt.Sprintf("DEVICEIDX:%d", id)
|
||||
|
||||
default:
|
||||
ele.key = fmt.Sprintf("UNKNOWN:%x", key)
|
||||
}
|
||||
ele.size = len(it.Value())
|
||||
elems = append(elems, ele)
|
||||
}
|
||||
|
||||
sort.Slice(elems, func(i, j int) bool {
|
||||
return elems[i].size > elems[j].size
|
||||
})
|
||||
for _, ele := range elems {
|
||||
fmt.Println(ele.key, ele.size)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,434 +0,0 @@
|
||||
// Copyright (C) 2018 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 cli
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/syncthing/syncthing/internal/gen/bep"
|
||||
"github.com/syncthing/syncthing/internal/gen/dbproto"
|
||||
"github.com/syncthing/syncthing/lib/db"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
)
|
||||
|
||||
type fileInfoKey struct {
|
||||
folder uint32
|
||||
device uint32
|
||||
name string
|
||||
}
|
||||
|
||||
type globalKey struct {
|
||||
folder uint32
|
||||
name string
|
||||
}
|
||||
|
||||
type sequenceKey struct {
|
||||
folder uint32
|
||||
sequence uint64
|
||||
}
|
||||
|
||||
func indexCheck() (err error) {
|
||||
ldb, err := getDB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
folders := make(map[uint32]string)
|
||||
devices := make(map[uint32]string)
|
||||
deviceToIDs := make(map[string]uint32)
|
||||
fileInfos := make(map[fileInfoKey]*bep.FileInfo)
|
||||
globals := make(map[globalKey]*dbproto.VersionList)
|
||||
sequences := make(map[sequenceKey]string)
|
||||
needs := make(map[globalKey]struct{})
|
||||
blocklists := make(map[string]struct{})
|
||||
versions := make(map[string]*bep.Vector)
|
||||
usedBlocklists := make(map[string]struct{})
|
||||
usedVersions := make(map[string]struct{})
|
||||
var localDeviceKey uint32
|
||||
success := true
|
||||
defer func() {
|
||||
if err == nil {
|
||||
if success {
|
||||
fmt.Println("Index check completed successfully.")
|
||||
} else {
|
||||
err = errors.New("Inconsistencies found in the index")
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
it, err := ldb.NewPrefixIterator(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for it.Next() {
|
||||
key := it.Key()
|
||||
switch key[0] {
|
||||
case db.KeyTypeDevice:
|
||||
folder := binary.BigEndian.Uint32(key[1:])
|
||||
device := binary.BigEndian.Uint32(key[1+4:])
|
||||
name := nulString(key[1+4+4:])
|
||||
|
||||
var f bep.FileInfo
|
||||
err := proto.Unmarshal(it.Value(), &f)
|
||||
if err != nil {
|
||||
fmt.Println("Unable to unmarshal FileInfo:", err)
|
||||
success = false
|
||||
continue
|
||||
}
|
||||
|
||||
fileInfos[fileInfoKey{folder, device, name}] = &f
|
||||
|
||||
case db.KeyTypeGlobal:
|
||||
folder := binary.BigEndian.Uint32(key[1:])
|
||||
name := nulString(key[1+4:])
|
||||
var flv dbproto.VersionList
|
||||
if err := proto.Unmarshal(it.Value(), &flv); err != nil {
|
||||
fmt.Println("Unable to unmarshal VersionList:", err)
|
||||
success = false
|
||||
continue
|
||||
}
|
||||
globals[globalKey{folder, name}] = &flv
|
||||
|
||||
case db.KeyTypeFolderIdx:
|
||||
key := binary.BigEndian.Uint32(it.Key()[1:])
|
||||
folders[key] = string(it.Value())
|
||||
|
||||
case db.KeyTypeDeviceIdx:
|
||||
key := binary.BigEndian.Uint32(it.Key()[1:])
|
||||
devices[key] = string(it.Value())
|
||||
deviceToIDs[string(it.Value())] = key
|
||||
if bytes.Equal(it.Value(), protocol.LocalDeviceID[:]) {
|
||||
localDeviceKey = key
|
||||
}
|
||||
|
||||
case db.KeyTypeSequence:
|
||||
folder := binary.BigEndian.Uint32(key[1:])
|
||||
seq := binary.BigEndian.Uint64(key[5:])
|
||||
val := it.Value()
|
||||
sequences[sequenceKey{folder, seq}] = string(val[9:])
|
||||
|
||||
case db.KeyTypeNeed:
|
||||
folder := binary.BigEndian.Uint32(key[1:])
|
||||
name := nulString(key[1+4:])
|
||||
needs[globalKey{folder, name}] = struct{}{}
|
||||
|
||||
case db.KeyTypeBlockList:
|
||||
hash := string(key[1:])
|
||||
blocklists[hash] = struct{}{}
|
||||
|
||||
case db.KeyTypeVersion:
|
||||
hash := string(key[1:])
|
||||
var v bep.Vector
|
||||
if err := proto.Unmarshal(it.Value(), &v); err != nil {
|
||||
fmt.Println("Unable to unmarshal Vector:", err)
|
||||
success = false
|
||||
continue
|
||||
}
|
||||
versions[hash] = &v
|
||||
}
|
||||
}
|
||||
|
||||
if localDeviceKey == 0 {
|
||||
fmt.Println("Missing key for local device in device index (bailing out)")
|
||||
success = false
|
||||
return
|
||||
}
|
||||
|
||||
var missingSeq []sequenceKey
|
||||
for fk, fi := range fileInfos {
|
||||
if fk.name != fi.Name {
|
||||
fmt.Printf("Mismatching FileInfo name, %q (key) != %q (actual)\n", fk.name, fi.Name)
|
||||
success = false
|
||||
}
|
||||
|
||||
folder := folders[fk.folder]
|
||||
if folder == "" {
|
||||
fmt.Printf("Unknown folder ID %d for FileInfo %q\n", fk.folder, fk.name)
|
||||
success = false
|
||||
continue
|
||||
}
|
||||
if devices[fk.device] == "" {
|
||||
fmt.Printf("Unknown device ID %d for FileInfo %q, folder %q\n", fk.folder, fk.name, folder)
|
||||
success = false
|
||||
}
|
||||
|
||||
if fk.device == localDeviceKey {
|
||||
sk := sequenceKey{fk.folder, uint64(fi.Sequence)}
|
||||
name, ok := sequences[sk]
|
||||
if !ok {
|
||||
fmt.Printf("Sequence entry missing for FileInfo %q, folder %q, seq %d\n", fi.Name, folder, fi.Sequence)
|
||||
missingSeq = append(missingSeq, sk)
|
||||
success = false
|
||||
continue
|
||||
}
|
||||
if name != fi.Name {
|
||||
fmt.Printf("Sequence entry refers to wrong name, %q (seq) != %q (FileInfo), folder %q, seq %d\n", name, fi.Name, folder, fi.Sequence)
|
||||
success = false
|
||||
}
|
||||
}
|
||||
|
||||
if len(fi.Blocks) == 0 && len(fi.BlocksHash) != 0 {
|
||||
key := string(fi.BlocksHash)
|
||||
if _, ok := blocklists[key]; !ok {
|
||||
fmt.Printf("Missing block list for file %q, block list hash %x\n", fi.Name, fi.BlocksHash)
|
||||
success = false
|
||||
} else {
|
||||
usedBlocklists[key] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
if fi.VersionHash != nil {
|
||||
key := string(fi.VersionHash)
|
||||
if _, ok := versions[key]; !ok {
|
||||
fmt.Printf("Missing version vector for file %q, version hash %x\n", fi.Name, fi.VersionHash)
|
||||
success = false
|
||||
} else {
|
||||
usedVersions[key] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
_, ok := globals[globalKey{fk.folder, fk.name}]
|
||||
if !ok {
|
||||
fmt.Printf("Missing global for file %q\n", fi.Name)
|
||||
success = false
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Aggregate the ranges of missing sequence entries, print them
|
||||
|
||||
sort.Slice(missingSeq, func(a, b int) bool {
|
||||
if missingSeq[a].folder != missingSeq[b].folder {
|
||||
return missingSeq[a].folder < missingSeq[b].folder
|
||||
}
|
||||
return missingSeq[a].sequence < missingSeq[b].sequence
|
||||
})
|
||||
|
||||
var folder uint32
|
||||
var startSeq, prevSeq uint64
|
||||
for _, sk := range missingSeq {
|
||||
if folder != sk.folder || sk.sequence != prevSeq+1 {
|
||||
if folder != 0 {
|
||||
fmt.Printf("Folder %d missing %d sequence entries: #%d - #%d\n", folder, prevSeq-startSeq+1, startSeq, prevSeq)
|
||||
}
|
||||
startSeq = sk.sequence
|
||||
folder = sk.folder
|
||||
}
|
||||
prevSeq = sk.sequence
|
||||
}
|
||||
if folder != 0 {
|
||||
fmt.Printf("Folder %d missing %d sequence entries: #%d - #%d\n", folder, prevSeq-startSeq+1, startSeq, prevSeq)
|
||||
}
|
||||
|
||||
for gk, vl := range globals {
|
||||
folder := folders[gk.folder]
|
||||
if folder == "" {
|
||||
fmt.Printf("Unknown folder ID %d for VersionList %q\n", gk.folder, gk.name)
|
||||
success = false
|
||||
}
|
||||
checkGlobal := func(i int, device []byte, version protocol.Vector, invalid, deleted bool) {
|
||||
dev, ok := deviceToIDs[string(device)]
|
||||
if !ok {
|
||||
fmt.Printf("VersionList %q, folder %q refers to unknown device %q\n", gk.name, folder, device)
|
||||
success = false
|
||||
}
|
||||
fi, ok := fileInfos[fileInfoKey{gk.folder, dev, gk.name}]
|
||||
if !ok {
|
||||
fmt.Printf("VersionList %q, folder %q, entry %d refers to unknown FileInfo\n", gk.name, folder, i)
|
||||
success = false
|
||||
}
|
||||
|
||||
fiv := fi.Version
|
||||
if fi.VersionHash != nil {
|
||||
fiv = versions[string(fi.VersionHash)]
|
||||
}
|
||||
if !protocol.VectorFromWire(fiv).Equal(version) {
|
||||
fmt.Printf("VersionList %q, folder %q, entry %d, FileInfo version mismatch, %v (VersionList) != %v (FileInfo)\n", gk.name, folder, i, version, fi.Version)
|
||||
success = false
|
||||
}
|
||||
ffi := protocol.FileInfoFromDB(fi)
|
||||
if ffi.IsInvalid() != invalid {
|
||||
fmt.Printf("VersionList %q, folder %q, entry %d, FileInfo invalid mismatch, %v (VersionList) != %v (FileInfo)\n", gk.name, folder, i, invalid, ffi.IsInvalid())
|
||||
success = false
|
||||
}
|
||||
if ffi.IsDeleted() != deleted {
|
||||
fmt.Printf("VersionList %q, folder %q, entry %d, FileInfo deleted mismatch, %v (VersionList) != %v (FileInfo)\n", gk.name, folder, i, deleted, ffi.IsDeleted())
|
||||
success = false
|
||||
}
|
||||
}
|
||||
for i, fv := range vl.Versions {
|
||||
ver := protocol.VectorFromWire(fv.Version)
|
||||
for _, device := range fv.Devices {
|
||||
checkGlobal(i, device, ver, false, fv.Deleted)
|
||||
}
|
||||
for _, device := range fv.InvalidDevices {
|
||||
checkGlobal(i, device, ver, true, fv.Deleted)
|
||||
}
|
||||
}
|
||||
|
||||
// If we need this file we should have a need entry for it. False
|
||||
// positives from needsLocally for deleted files, where we might
|
||||
// legitimately lack an entry if we never had it, and ignored files.
|
||||
if needsLocally(vl) {
|
||||
_, ok := needs[gk]
|
||||
if !ok {
|
||||
fv, _ := vlGetGlobal(vl)
|
||||
devB, _ := fvFirstDevice(fv)
|
||||
dev := deviceToIDs[string(devB)]
|
||||
fi := protocol.FileInfoFromDB(fileInfos[fileInfoKey{gk.folder, dev, gk.name}])
|
||||
if !fi.IsDeleted() && !fi.IsIgnored() {
|
||||
fmt.Printf("Missing need entry for needed file %q, folder %q\n", gk.name, folder)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
seenSeq := make(map[fileInfoKey]uint64)
|
||||
for sk, name := range sequences {
|
||||
folder := folders[sk.folder]
|
||||
if folder == "" {
|
||||
fmt.Printf("Unknown folder ID %d for sequence entry %d, %q\n", sk.folder, sk.sequence, name)
|
||||
success = false
|
||||
continue
|
||||
}
|
||||
|
||||
if prev, ok := seenSeq[fileInfoKey{folder: sk.folder, name: name}]; ok {
|
||||
fmt.Printf("Duplicate sequence entry for %q, folder %q, seq %d (prev %d)\n", name, folder, sk.sequence, prev)
|
||||
success = false
|
||||
}
|
||||
seenSeq[fileInfoKey{folder: sk.folder, name: name}] = sk.sequence
|
||||
|
||||
fi, ok := fileInfos[fileInfoKey{sk.folder, localDeviceKey, name}]
|
||||
if !ok {
|
||||
fmt.Printf("Missing FileInfo for sequence entry %d, folder %q, %q\n", sk.sequence, folder, name)
|
||||
success = false
|
||||
continue
|
||||
}
|
||||
if fi.Sequence != int64(sk.sequence) {
|
||||
fmt.Printf("Sequence mismatch for %q, folder %q, %d (key) != %d (FileInfo)\n", name, folder, sk.sequence, fi.Sequence)
|
||||
success = false
|
||||
}
|
||||
}
|
||||
|
||||
for nk := range needs {
|
||||
folder := folders[nk.folder]
|
||||
if folder == "" {
|
||||
fmt.Printf("Unknown folder ID %d for need entry %q\n", nk.folder, nk.name)
|
||||
success = false
|
||||
continue
|
||||
}
|
||||
|
||||
vl, ok := globals[nk]
|
||||
if !ok {
|
||||
fmt.Printf("Missing global for need entry %q, folder %q\n", nk.name, folder)
|
||||
success = false
|
||||
continue
|
||||
}
|
||||
|
||||
if !needsLocally(vl) {
|
||||
fmt.Printf("Need entry for file we don't need, %q, folder %q\n", nk.name, folder)
|
||||
success = false
|
||||
}
|
||||
}
|
||||
|
||||
if d := len(blocklists) - len(usedBlocklists); d > 0 {
|
||||
fmt.Printf("%d block list entries out of %d needs GC\n", d, len(blocklists))
|
||||
}
|
||||
if d := len(versions) - len(usedVersions); d > 0 {
|
||||
fmt.Printf("%d version entries out of %d needs GC\n", d, len(versions))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func needsLocally(vl *dbproto.VersionList) bool {
|
||||
gfv, gok := vlGetGlobal(vl)
|
||||
if !gok { // That's weird, but we hardly need something non-existent
|
||||
return false
|
||||
}
|
||||
fv, ok := vlGet(vl, protocol.LocalDeviceID[:])
|
||||
return db.Need(gfv, ok, protocol.VectorFromWire(fv.Version))
|
||||
}
|
||||
|
||||
// Get returns a FileVersion that contains the given device and whether it has
|
||||
// been found at all.
|
||||
func vlGet(vl *dbproto.VersionList, device []byte) (*dbproto.FileVersion, bool) {
|
||||
_, i, _, ok := vlFindDevice(vl, device)
|
||||
if !ok {
|
||||
return &dbproto.FileVersion{}, false
|
||||
}
|
||||
return vl.Versions[i], true
|
||||
}
|
||||
|
||||
// GetGlobal returns the current global FileVersion. The returned FileVersion
|
||||
// may be invalid, if all FileVersions are invalid. Returns false only if
|
||||
// VersionList is empty.
|
||||
func vlGetGlobal(vl *dbproto.VersionList) (*dbproto.FileVersion, bool) {
|
||||
i := vlFindGlobal(vl)
|
||||
if i == -1 {
|
||||
return nil, false
|
||||
}
|
||||
return vl.Versions[i], true
|
||||
}
|
||||
|
||||
// findGlobal returns the first version that isn't invalid, or if all versions are
|
||||
// invalid just the first version (i.e. 0) or -1, if there's no versions at all.
|
||||
func vlFindGlobal(vl *dbproto.VersionList) int {
|
||||
for i := range vl.Versions {
|
||||
if !fvIsInvalid(vl.Versions[i]) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
if len(vl.Versions) == 0 {
|
||||
return -1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// findDevice returns whether the device is in InvalidVersions or Versions and
|
||||
// in InvalidDevices or Devices (true for invalid), the positions in the version
|
||||
// and device slices and whether it has been found at all.
|
||||
func vlFindDevice(vl *dbproto.VersionList, device []byte) (bool, int, int, bool) {
|
||||
for i, v := range vl.Versions {
|
||||
if j := deviceIndex(v.Devices, device); j != -1 {
|
||||
return false, i, j, true
|
||||
}
|
||||
if j := deviceIndex(v.InvalidDevices, device); j != -1 {
|
||||
return true, i, j, true
|
||||
}
|
||||
}
|
||||
return false, -1, -1, false
|
||||
}
|
||||
|
||||
func deviceIndex(devices [][]byte, device []byte) int {
|
||||
for i, dev := range devices {
|
||||
if bytes.Equal(device, dev) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func fvFirstDevice(fv *dbproto.FileVersion) ([]byte, bool) {
|
||||
if len(fv.Devices) != 0 {
|
||||
return fv.Devices[0], true
|
||||
}
|
||||
if len(fv.InvalidDevices) != 0 {
|
||||
return fv.InvalidDevices[0], true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func fvIsInvalid(fv *dbproto.FileVersion) bool {
|
||||
return fv == nil || len(fv.Devices) == 0
|
||||
}
|
||||
@@ -14,15 +14,12 @@ import (
|
||||
"github.com/alecthomas/kong"
|
||||
"github.com/kballard/go-shellquote"
|
||||
|
||||
"github.com/syncthing/syncthing/cmd/syncthing/cmdutil"
|
||||
"github.com/syncthing/syncthing/lib/config"
|
||||
)
|
||||
|
||||
type CLI struct {
|
||||
cmdutil.CommonOptions
|
||||
DataDir string `name:"data" placeholder:"PATH" env:"STDATADIR" help:"Set data directory (database and logs)"`
|
||||
GUIAddress string `name:"gui-address"`
|
||||
GUIAPIKey string `name:"gui-apikey"`
|
||||
GUIAddress string `name:"gui-address" env:"STGUIADDRESS"`
|
||||
GUIAPIKey string `name:"gui-apikey" env:"STGUIAPIKEY"`
|
||||
|
||||
Show showCommand `cmd:"" help:"Show command group"`
|
||||
Debug debugCommand `cmd:"" help:"Debug command group"`
|
||||
@@ -37,11 +34,6 @@ type Context struct {
|
||||
}
|
||||
|
||||
func (cli CLI) AfterApply(kongCtx *kong.Context) error {
|
||||
err := cmdutil.SetConfigDataLocationsFromFlags(cli.HomeDir, cli.ConfDir, cli.DataDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("command line options: %w", err)
|
||||
}
|
||||
|
||||
clientFactory := &apiClientFactory{
|
||||
cfg: config.GUIConfiguration{
|
||||
RawAddress: cli.GUIAddress,
|
||||
|
||||
@@ -17,8 +17,6 @@ import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/config"
|
||||
"github.com/syncthing/syncthing/lib/db/backend"
|
||||
"github.com/syncthing/syncthing/lib/locations"
|
||||
)
|
||||
|
||||
func responseToBArray(response *http.Response) ([]byte, error) {
|
||||
@@ -133,10 +131,6 @@ func prettyPrintResponse(response *http.Response) error {
|
||||
return prettyPrintJSON(data)
|
||||
}
|
||||
|
||||
func getDB() (backend.Backend, error) {
|
||||
return backend.OpenLevelDBRO(locations.Get(locations.Database))
|
||||
}
|
||||
|
||||
func nulString(bs []byte) string {
|
||||
for i := range bs {
|
||||
if bs[i] == 0 {
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
// Copyright (C) 2021 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 cmdutil
|
||||
|
||||
// CommonOptions are reused among several subcommands
|
||||
type CommonOptions struct {
|
||||
buildCommonOptions
|
||||
ConfDir string `name:"config" placeholder:"PATH" env:"STCONFDIR" help:"Set configuration directory (config and keys)"`
|
||||
HomeDir string `name:"home" placeholder:"PATH" env:"STHOMEDIR" help:"Set configuration and data directory"`
|
||||
NoDefaultFolder bool `env:"STNODEFAULTFOLDER" help:"Don't create the \"default\" folder on first startup"`
|
||||
SkipPortProbing bool `help:"Don't try to find free ports for GUI and listen addresses on first startup"`
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
// Copyright (C) 2014 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 cmdutil
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/locations"
|
||||
)
|
||||
|
||||
func SetConfigDataLocationsFromFlags(homeDir, confDir, dataDir string) error {
|
||||
homeSet := homeDir != ""
|
||||
confSet := confDir != ""
|
||||
dataSet := dataDir != ""
|
||||
switch {
|
||||
case dataSet != confSet:
|
||||
return errors.New("either both or none of --config and --data must be given, use --home to set both at once")
|
||||
case homeSet && dataSet:
|
||||
return errors.New("--home must not be used together with --config and --data")
|
||||
case homeSet:
|
||||
confDir = homeDir
|
||||
dataDir = homeDir
|
||||
fallthrough
|
||||
case dataSet:
|
||||
if err := locations.SetBaseDir(locations.ConfigBaseDir, confDir); err != nil {
|
||||
return err
|
||||
}
|
||||
return locations.SetBaseDir(locations.DataBaseDir, dataDir)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -238,7 +238,7 @@ func (c *CLI) decryptFile(encFi *protocol.FileInfo, plainFi *protocol.FileInfo,
|
||||
}
|
||||
|
||||
// Verify the hash against the plaintext block info
|
||||
if !scanner.Validate(dec, plainBlock.Hash, 0) {
|
||||
if !scanner.Validate(dec, plainBlock.Hash) {
|
||||
// The block decrypted correctly but fails the hash check. This
|
||||
// is odd and unexpected, but it it's still a valid block from
|
||||
// the source. The file might have changed while we pulled it?
|
||||
|
||||
@@ -11,42 +11,26 @@ import (
|
||||
"bufio"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/syncthing/syncthing/cmd/syncthing/cmdutil"
|
||||
"github.com/syncthing/syncthing/lib/config"
|
||||
"github.com/syncthing/syncthing/lib/events"
|
||||
"github.com/syncthing/syncthing/lib/fs"
|
||||
"github.com/syncthing/syncthing/lib/locations"
|
||||
"github.com/syncthing/syncthing/lib/logger"
|
||||
"github.com/syncthing/syncthing/lib/osutil"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
"github.com/syncthing/syncthing/lib/syncthing"
|
||||
)
|
||||
|
||||
type CLI struct {
|
||||
cmdutil.CommonOptions
|
||||
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)"`
|
||||
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"`
|
||||
}
|
||||
|
||||
func (c *CLI) Run(l logger.Logger) error {
|
||||
if c.HideConsole {
|
||||
osutil.HideConsole()
|
||||
}
|
||||
|
||||
if c.HomeDir != "" {
|
||||
if c.ConfDir != "" {
|
||||
return errors.New("--home must not be used together with --config")
|
||||
}
|
||||
c.ConfDir = c.HomeDir
|
||||
}
|
||||
if c.ConfDir == "" {
|
||||
c.ConfDir = locations.GetBaseDir(locations.ConfigBaseDir)
|
||||
}
|
||||
|
||||
// Support reading the password from a pipe or similar
|
||||
if c.GUIPassword == "-" {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
@@ -57,7 +41,7 @@ func (c *CLI) Run(l logger.Logger) error {
|
||||
c.GUIPassword = string(password)
|
||||
}
|
||||
|
||||
if err := Generate(l, c.ConfDir, c.GUIUser, c.GUIPassword, c.NoDefaultFolder, c.SkipPortProbing); err != nil {
|
||||
if err := Generate(l, locations.GetBaseDir(locations.ConfigBaseDir), c.GUIUser, c.GUIPassword, c.NoDefaultFolder, c.NoPortProbing); err != nil {
|
||||
return fmt.Errorf("failed to generate config and keys: %w", err)
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package cmdutil
|
||||
package main
|
||||
|
||||
type buildCommonOptions struct {
|
||||
type buildSpecificOptions struct {
|
||||
HideConsole bool `hidden:""`
|
||||
}
|
||||
@@ -4,8 +4,8 @@
|
||||
// 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 cmdutil
|
||||
package main
|
||||
|
||||
type buildCommonOptions struct {
|
||||
HideConsole bool `name:"no-console" help:"Hide console window"`
|
||||
type buildSpecificOptions struct {
|
||||
HideConsole bool `name:"no-console" help:"Hide console window" env:"STHIDECONSOLE"`
|
||||
}
|
||||
@@ -25,23 +25,21 @@ import (
|
||||
"runtime/pprof"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/alecthomas/kong"
|
||||
"github.com/gofrs/flock"
|
||||
"github.com/thejerf/suture/v4"
|
||||
"github.com/willabides/kongplete"
|
||||
|
||||
"github.com/syncthing/syncthing/cmd/syncthing/cli"
|
||||
"github.com/syncthing/syncthing/cmd/syncthing/cmdutil"
|
||||
"github.com/syncthing/syncthing/cmd/syncthing/decrypt"
|
||||
"github.com/syncthing/syncthing/cmd/syncthing/generate"
|
||||
"github.com/syncthing/syncthing/internal/db"
|
||||
_ "github.com/syncthing/syncthing/lib/automaxprocs"
|
||||
"github.com/syncthing/syncthing/lib/build"
|
||||
"github.com/syncthing/syncthing/lib/config"
|
||||
"github.com/syncthing/syncthing/lib/db"
|
||||
"github.com/syncthing/syncthing/lib/db/backend"
|
||||
"github.com/syncthing/syncthing/lib/dialer"
|
||||
"github.com/syncthing/syncthing/lib/events"
|
||||
"github.com/syncthing/syncthing/lib/fs"
|
||||
@@ -128,53 +126,68 @@ var (
|
||||
// The entrypoint struct is the main entry point for the command line parser. The
|
||||
// commands and options here are top level commands to syncthing.
|
||||
// Cli is just a placeholder for the help text (see main).
|
||||
var entrypoint struct {
|
||||
Serve serveOptions `cmd:"" help:"Run Syncthing"`
|
||||
Generate generate.CLI `cmd:"" help:"Generate key and config, then exit"`
|
||||
Decrypt decrypt.CLI `cmd:"" help:"Decrypt or verify an encrypted folder"`
|
||||
Cli cli.CLI `cmd:"" help:"Command line interface for Syncthing"`
|
||||
type CLI struct {
|
||||
// The directory options are defined at top level and available for all
|
||||
// subcommands. Their settings take effect on the `locations` package by
|
||||
// way of the command line parser, so anything using `locations.Get` etc
|
||||
// will be doing the right thing.
|
||||
ConfDir string `name:"config" short:"C" placeholder:"PATH" env:"STCONFDIR" help:"Set configuration directory (config and keys)"`
|
||||
DataDir string `name:"data" short:"D" placeholder:"PATH" env:"STDATADIR" help:"Set data directory (database and logs)"`
|
||||
HomeDir string `name:"home" short:"H" placeholder:"PATH" env:"STHOMEDIR" help:"Set configuration and data directory"`
|
||||
|
||||
Serve serveCmd `cmd:"" help:"Run Syncthing (default)" default:"withargs"`
|
||||
CLI cli.CLI `cmd:"" help:"Command line interface for Syncthing"`
|
||||
|
||||
Browser browserCmd `cmd:"" help:"Open GUI in browser, then exit"`
|
||||
Decrypt decrypt.CLI `cmd:"" help:"Decrypt or verify an encrypted folder"`
|
||||
DeviceID deviceIDCmd `cmd:"" help:"Show device ID, then exit"`
|
||||
Generate generate.CLI `cmd:"" help:"Generate key and config, then exit"`
|
||||
Paths pathsCmd `cmd:"" help:"Show configuration paths, then exit"`
|
||||
Upgrade upgradeCmd `cmd:"" help:"Perform or check for upgrade, then exit"`
|
||||
Version versionCmd `cmd:"" help:"Show current version, then exit"`
|
||||
Debug debugCmd `cmd:"" help:"Various debugging commands"`
|
||||
|
||||
InstallCompletions kongplete.InstallCompletions `cmd:"" help:"Print commands to install shell completions"`
|
||||
}
|
||||
|
||||
// serveOptions are the options for the `syncthing serve` command.
|
||||
type serveOptions struct {
|
||||
cmdutil.CommonOptions
|
||||
AllowNewerConfig bool `help:"Allow loading newer than current config version"`
|
||||
Audit bool `help:"Write events to audit file"`
|
||||
AuditFile string `name:"auditfile" placeholder:"PATH" help:"Specify audit file (use \"-\" for stdout, \"--\" for stderr)"`
|
||||
BrowserOnly bool `help:"Open GUI in browser"`
|
||||
DataDir string `name:"data" placeholder:"PATH" env:"STDATADIR" help:"Set data directory (database and logs)"`
|
||||
DeviceID bool `help:"Show the device ID"`
|
||||
GenerateDir string `name:"generate" placeholder:"PATH" help:"Generate key and config in specified dir, then exit"` // DEPRECATED: replaced by subcommand!
|
||||
GUIAddress string `name:"gui-address" placeholder:"URL" help:"Override GUI address (e.g. \"http://192.0.2.42:8443\")"`
|
||||
GUIAPIKey string `name:"gui-apikey" placeholder:"API-KEY" help:"Override GUI API key"`
|
||||
LogFile string `name:"logfile" default:"${logFile}" placeholder:"PATH" help:"Log file name (see below)"`
|
||||
LogFlags int `name:"logflags" default:"${logFlags}" placeholder:"BITS" help:"Select information in log line prefix (see below)"`
|
||||
LogMaxFiles int `placeholder:"N" default:"${logMaxFiles}" name:"log-max-old-files" help:"Number of old files to keep (zero to keep only current)"`
|
||||
LogMaxSize int `placeholder:"BYTES" default:"${logMaxSize}" help:"Maximum size of any file (zero to disable log rotation)"`
|
||||
NoBrowser bool `help:"Do not start browser"`
|
||||
NoRestart bool `env:"STNORESTART" help:"Do not restart Syncthing when exiting due to API/GUI command, upgrade, or crash"`
|
||||
NoUpgrade bool `env:"STNOUPGRADE" help:"Disable automatic upgrades"`
|
||||
Paths bool `help:"Show configuration paths"`
|
||||
Paused bool `help:"Start with all devices and folders paused"`
|
||||
Unpaused bool `help:"Start with all devices and folders unpaused"`
|
||||
Upgrade bool `help:"Perform upgrade"`
|
||||
UpgradeCheck bool `help:"Check for available upgrade"`
|
||||
UpgradeTo string `placeholder:"URL" help:"Force upgrade directly from specified URL"`
|
||||
Verbose bool `help:"Print verbose log output"`
|
||||
Version bool `help:"Show version"`
|
||||
func (c *CLI) AfterApply() error {
|
||||
// Executed after parsing command line options but before running actual
|
||||
// subcommands
|
||||
return setConfigDataLocationsFromFlags(c.HomeDir, c.ConfDir, c.DataDir)
|
||||
}
|
||||
|
||||
// serveCmd are the options for the `syncthing serve` command.
|
||||
type serveCmd struct {
|
||||
buildSpecificOptions
|
||||
|
||||
AllowNewerConfig bool `help:"Allow loading newer than current config version" env:"STALLOWNEWERCONFIG"`
|
||||
Audit bool `help:"Write events to audit file" env:"STAUDIT"`
|
||||
AuditFile string `name:"auditfile" help:"Specify audit file (use \"-\" for stdout, \"--\" for stderr)" placeholder:"PATH" env:"STAUDITFILE"`
|
||||
DBMaintenanceInterval time.Duration `help:"Database maintenance interval" default:"8h" env:"STDBMAINTENANCEINTERVAL"`
|
||||
DBDeleteRetentionInterval time.Duration `help:"Database deleted item retention interval" default:"4320h" env:"STDBDELETERETENTIONINTERVAL"`
|
||||
GUIAddress string `name:"gui-address" help:"Override GUI address (e.g. \"http://192.0.2.42:8443\")" placeholder:"URL" env:"STGUIADDRESS"`
|
||||
GUIAPIKey string `name:"gui-apikey" help:"Override GUI API key" placeholder:"API-KEY" env:"STGUIAPIKEY"`
|
||||
LogFile string `name:"logfile" help:"Log file name (see below)" default:"${logFile}" placeholder:"PATH" env:"STLOGFILE"`
|
||||
LogFlags int `name:"logflags" help:"Select information in log line prefix (see below)" default:"${logFlags}" placeholder:"BITS" env:"STLOGFLAGS"`
|
||||
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"`
|
||||
Paused bool `help:"Start with all devices and folders paused" env:"STPAUSED"`
|
||||
Unpaused bool `help:"Start with all devices and folders unpaused" env:"STUNPAUSED"`
|
||||
Verbose bool `help:"Print verbose log output" env:"STVERBOSE"`
|
||||
|
||||
// Debug options below
|
||||
DebugDBIndirectGCInterval time.Duration `env:"STGCINDIRECTEVERY" help:"Database indirection GC interval"`
|
||||
DebugDBRecheckInterval time.Duration `env:"STRECHECKDBEVERY" help:"Database metadata recalculation interval"`
|
||||
DebugGUIAssetsDir string `placeholder:"PATH" help:"Directory to load GUI assets from" env:"STGUIASSETS"`
|
||||
DebugPerfStats bool `env:"STPERFSTATS" help:"Write running performance statistics to perf-$pid.csv (Unix only)"`
|
||||
DebugProfileBlock bool `env:"STBLOCKPROFILE" help:"Write block profiles to block-$pid-$timestamp.pprof every 20 seconds"`
|
||||
DebugProfileCPU bool `help:"Write a CPU profile to cpu-$pid.pprof on exit" env:"STCPUPROFILE"`
|
||||
DebugProfileHeap bool `env:"STHEAPPROFILE" help:"Write heap profiles to heap-$pid-$timestamp.pprof each time heap usage increases"`
|
||||
DebugProfilerListen string `placeholder:"ADDR" env:"STPROFILER" help:"Network profiler listen address"`
|
||||
DebugResetDatabase bool `name:"reset-database" help:"Reset the database, forcing a full rescan and resync"`
|
||||
DebugResetDeltaIdxs bool `name:"reset-deltas" help:"Reset delta index IDs, forcing a full index exchange"`
|
||||
DebugGUIAssetsDir string `help:"Directory to load GUI assets from" placeholder:"PATH" env:"STGUIASSETS"`
|
||||
DebugPerfStats bool `help:"Write running performance statistics to perf-$pid.csv (Unix only)" env:"STPERFSTATS"`
|
||||
DebugProfileBlock bool `help:"Write block profiles to block-$pid-$timestamp.pprof every 20 seconds" env:"STBLOCKPROFILE"`
|
||||
DebugProfileCPU bool `help:"Write a CPU profile to cpu-$pid.pprof on exit" env:"STCPUPROFILE"`
|
||||
DebugProfileHeap bool `help:"Write heap profiles to heap-$pid-$timestamp.pprof each time heap usage increases" env:"STHEAPPROFILE"`
|
||||
DebugProfilerListen string `help:"Network profiler listen address" placeholder:"ADDR" env:"STPROFILER" `
|
||||
DebugResetDeltaIdxs bool `help:"Reset delta index IDs, forcing a full index exchange"`
|
||||
|
||||
// Internal options, not shown to users
|
||||
InternalRestarting bool `env:"STRESTART" hidden:"1"`
|
||||
@@ -206,31 +219,9 @@ func defaultVars() kong.Vars {
|
||||
}
|
||||
|
||||
func main() {
|
||||
// First some massaging of the raw command line to fit the new model.
|
||||
// Basically this means adding the default command at the front, and
|
||||
// converting -options to --options.
|
||||
|
||||
args := os.Args[1:]
|
||||
switch {
|
||||
case len(args) == 0:
|
||||
// Empty command line is equivalent to just calling serve
|
||||
args = []string{"serve"}
|
||||
case args[0] == "-help":
|
||||
// For consistency, we consider this equivalent with --help even
|
||||
// though kong would otherwise consider it a bad flag.
|
||||
args[0] = "--help"
|
||||
case args[0] == "-h", args[0] == "--help":
|
||||
// Top level request for help, let it pass as-is to be handled by
|
||||
// kong to list commands.
|
||||
case strings.HasPrefix(args[0], "-"):
|
||||
// There are flags not preceded by a command, so we tack on the
|
||||
// "serve" command and convert the old style arguments (single dash)
|
||||
// to new style (double dash).
|
||||
args = append([]string{"serve"}, convertLegacyArgs(args)...)
|
||||
}
|
||||
|
||||
// Create a parser with an overridden help function to print our extra
|
||||
// help info.
|
||||
var entrypoint CLI
|
||||
parser, err := kong.New(
|
||||
&entrypoint,
|
||||
kong.ConfigureHelp(kong.HelpOptions{
|
||||
@@ -245,7 +236,7 @@ func main() {
|
||||
}
|
||||
|
||||
kongplete.Complete(parser)
|
||||
ctx, err := parser.Parse(args)
|
||||
ctx, err := parser.Parse(os.Args[1:])
|
||||
parser.FatalIfErrorf(err)
|
||||
ctx.BindTo(l, (*logger.Logger)(nil)) // main logger available to subcommands
|
||||
err = ctx.Run()
|
||||
@@ -264,149 +255,54 @@ func helpHandler(options kong.HelpOptions, ctx *kong.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// serveOptions.Run() is the entrypoint for `syncthing serve`
|
||||
func (options serveOptions) Run() error {
|
||||
l.SetFlags(options.LogFlags)
|
||||
// serveCmd.Run() is the entrypoint for `syncthing serve`
|
||||
func (c *serveCmd) Run() error {
|
||||
l.SetFlags(c.LogFlags)
|
||||
|
||||
if options.GUIAddress != "" {
|
||||
if c.GUIAddress != "" {
|
||||
// The config picks this up from the environment.
|
||||
os.Setenv("STGUIADDRESS", options.GUIAddress)
|
||||
os.Setenv("STGUIADDRESS", c.GUIAddress)
|
||||
}
|
||||
if options.GUIAPIKey != "" {
|
||||
if c.GUIAPIKey != "" {
|
||||
// The config picks this up from the environment.
|
||||
os.Setenv("STGUIAPIKEY", options.GUIAPIKey)
|
||||
os.Setenv("STGUIAPIKEY", c.GUIAPIKey)
|
||||
}
|
||||
|
||||
if options.HideConsole {
|
||||
if c.HideConsole {
|
||||
osutil.HideConsole()
|
||||
}
|
||||
|
||||
// Not set as default above because the strings can be really long.
|
||||
err := cmdutil.SetConfigDataLocationsFromFlags(options.HomeDir, options.ConfDir, options.DataDir)
|
||||
if err != nil {
|
||||
l.Warnln("Command line options:", err)
|
||||
os.Exit(svcutil.ExitError.AsInt())
|
||||
}
|
||||
|
||||
// Treat an explicitly empty log file name as no log file
|
||||
if options.LogFile == "" {
|
||||
options.LogFile = "-"
|
||||
if c.LogFile == "" {
|
||||
c.LogFile = "-"
|
||||
}
|
||||
if options.LogFile != "default" {
|
||||
if c.LogFile != "default" {
|
||||
// We must set this *after* expandLocations above.
|
||||
if err := locations.Set(locations.LogFile, options.LogFile); err != nil {
|
||||
if err := locations.Set(locations.LogFile, c.LogFile); err != nil {
|
||||
l.Warnln("Setting log file path:", err)
|
||||
os.Exit(svcutil.ExitError.AsInt())
|
||||
}
|
||||
}
|
||||
|
||||
if options.DebugGUIAssetsDir != "" {
|
||||
if c.DebugGUIAssetsDir != "" {
|
||||
// The asset dir is blank if STGUIASSETS wasn't set, in which case we
|
||||
// should look for extra assets in the default place.
|
||||
if err := locations.Set(locations.GUIAssets, options.DebugGUIAssetsDir); err != nil {
|
||||
if err := locations.Set(locations.GUIAssets, c.DebugGUIAssetsDir); err != nil {
|
||||
l.Warnln("Setting GUI assets path:", err)
|
||||
os.Exit(svcutil.ExitError.AsInt())
|
||||
}
|
||||
}
|
||||
|
||||
if options.Version {
|
||||
fmt.Println(build.LongVersion)
|
||||
return nil
|
||||
}
|
||||
|
||||
if options.Paths {
|
||||
fmt.Print(locations.PrettyPaths())
|
||||
return nil
|
||||
}
|
||||
|
||||
if options.DeviceID {
|
||||
cert, err := tls.LoadX509KeyPair(
|
||||
locations.Get(locations.CertFile),
|
||||
locations.Get(locations.KeyFile),
|
||||
)
|
||||
if err != nil {
|
||||
l.Warnln("Error reading device ID:", err)
|
||||
os.Exit(svcutil.ExitError.AsInt())
|
||||
}
|
||||
|
||||
fmt.Println(protocol.NewDeviceID(cert.Certificate[0]))
|
||||
return nil
|
||||
}
|
||||
|
||||
if options.BrowserOnly {
|
||||
if err := openGUI(); err != nil {
|
||||
l.Warnln("Failed to open web UI:", err)
|
||||
os.Exit(svcutil.ExitError.AsInt())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if options.GenerateDir != "" {
|
||||
if err := generate.Generate(l, options.GenerateDir, "", "", options.NoDefaultFolder, options.SkipPortProbing); err != nil {
|
||||
l.Warnln("Failed to generate config and keys:", err)
|
||||
os.Exit(svcutil.ExitError.AsInt())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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())
|
||||
}
|
||||
|
||||
if options.UpgradeTo != "" {
|
||||
err := upgrade.ToURL(options.UpgradeTo)
|
||||
if err != nil {
|
||||
l.Warnln("Error while Upgrading:", err)
|
||||
os.Exit(svcutil.ExitError.AsInt())
|
||||
}
|
||||
l.Infoln("Upgraded from", options.UpgradeTo)
|
||||
return nil
|
||||
}
|
||||
|
||||
if options.UpgradeCheck {
|
||||
if _, err := checkUpgrade(); err != nil {
|
||||
l.Warnln("Checking for upgrade:", err)
|
||||
os.Exit(exitCodeForUpgrade(err))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if options.Upgrade {
|
||||
release, err := checkUpgrade()
|
||||
if err == nil {
|
||||
// Use leveldb database locks to protect against concurrent upgrades
|
||||
var ldb backend.Backend
|
||||
ldb, err = syncthing.OpenDBBackend(locations.Get(locations.Database), config.TuningAuto)
|
||||
if err != nil {
|
||||
err = upgradeViaRest()
|
||||
} else {
|
||||
_ = ldb.Close()
|
||||
err = upgrade.To(release)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
l.Warnln("Upgrade:", err)
|
||||
os.Exit(exitCodeForUpgrade(err))
|
||||
}
|
||||
l.Infof("Upgraded to %q", release.Tag)
|
||||
os.Exit(svcutil.ExitUpgrade.AsInt())
|
||||
}
|
||||
|
||||
if options.DebugResetDatabase {
|
||||
if err := resetDB(); err != nil {
|
||||
l.Warnln("Resetting database:", err)
|
||||
os.Exit(svcutil.ExitError.AsInt())
|
||||
}
|
||||
l.Infoln("Successfully reset database - it will be rebuilt after next start.")
|
||||
return nil
|
||||
}
|
||||
|
||||
if options.InternalInnerProcess {
|
||||
syncthingMain(options)
|
||||
if c.InternalInnerProcess {
|
||||
c.syncthingMain()
|
||||
} else {
|
||||
monitorMain(options)
|
||||
c.monitorMain()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -515,14 +411,14 @@ func upgradeViaRest() error {
|
||||
return err
|
||||
}
|
||||
|
||||
func syncthingMain(options serveOptions) {
|
||||
if options.DebugProfileBlock {
|
||||
func (c *serveCmd) syncthingMain() {
|
||||
if c.DebugProfileBlock {
|
||||
startBlockProfiler()
|
||||
}
|
||||
if options.DebugProfileHeap {
|
||||
if c.DebugProfileHeap {
|
||||
startHeapProfiler()
|
||||
}
|
||||
if options.DebugPerfStats {
|
||||
if c.DebugPerfStats {
|
||||
startPerfStats()
|
||||
}
|
||||
|
||||
@@ -544,6 +440,17 @@ func syncthingMain(options serveOptions) {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Ensure we are the only running instance
|
||||
lf := flock.New(locations.Get(locations.LockFile))
|
||||
locked, err := lf.TryLock()
|
||||
if err != nil {
|
||||
l.Warnln("Failed to acquire lock:", err)
|
||||
os.Exit(1)
|
||||
} else if !locked {
|
||||
l.Warnln("Failed to acquire lock: is another Syncthing instance already running?")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
@@ -556,7 +463,7 @@ func syncthingMain(options serveOptions) {
|
||||
evLogger := events.NewLogger()
|
||||
earlyService.Add(evLogger)
|
||||
|
||||
cfgWrapper, err := syncthing.LoadConfigAtStartup(locations.Get(locations.ConfigFile), cert, evLogger, options.AllowNewerConfig, options.NoDefaultFolder, options.SkipPortProbing)
|
||||
cfgWrapper, err := syncthing.LoadConfigAtStartup(locations.Get(locations.ConfigFile), cert, evLogger, c.AllowNewerConfig, c.NoDefaultFolder, c.NoPortProbing)
|
||||
if err != nil {
|
||||
l.Warnln("Failed to initialize config:", err)
|
||||
os.Exit(svcutil.ExitError.AsInt())
|
||||
@@ -567,7 +474,7 @@ func syncthingMain(options serveOptions) {
|
||||
// unless we are in a build where it's disabled or the STNOUPGRADE
|
||||
// environment variable is set.
|
||||
|
||||
if build.IsCandidate && !upgrade.DisabledByCompilation && !options.NoUpgrade {
|
||||
if build.IsCandidate && !upgrade.DisabledByCompilation && !c.NoUpgrade {
|
||||
cfgWrapper.Modify(func(cfg *config.Configuration) {
|
||||
l.Infoln("Automatic upgrade is always enabled for candidate releases.")
|
||||
if cfg.Options.AutoUpgradeIntervalH == 0 || cfg.Options.AutoUpgradeIntervalH > 24 {
|
||||
@@ -580,8 +487,12 @@ func syncthingMain(options serveOptions) {
|
||||
})
|
||||
}
|
||||
|
||||
dbFile := locations.Get(locations.Database)
|
||||
ldb, err := syncthing.OpenDBBackend(dbFile, cfgWrapper.Options().DatabaseTuning)
|
||||
if err := syncthing.TryMigrateDatabase(c.DBDeleteRetentionInterval); err != nil {
|
||||
l.Warnln("Failed to migrate old-style database:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
sdb, err := syncthing.OpenDatabase(locations.Get(locations.Database), c.DBDeleteRetentionInterval)
|
||||
if err != nil {
|
||||
l.Warnln("Error opening database:", err)
|
||||
os.Exit(1)
|
||||
@@ -590,11 +501,11 @@ func syncthingMain(options serveOptions) {
|
||||
// Check if auto-upgrades is possible, and if yes, and it's enabled do an initial
|
||||
// upgrade immediately. The auto-upgrade routine can only be started
|
||||
// later after App is initialised.
|
||||
|
||||
autoUpgradePossible := autoUpgradePossible(options)
|
||||
autoUpgradePossible := c.autoUpgradePossible()
|
||||
if autoUpgradePossible && cfgWrapper.Options().AutoUpgradeEnabled() {
|
||||
// try to do upgrade directly and log the error if relevant.
|
||||
release, err := initialAutoUpgradeCheck(db.NewMiscDataNamespace(ldb))
|
||||
miscDB := db.NewMiscDB(sdb)
|
||||
release, err := initialAutoUpgradeCheck(miscDB)
|
||||
if err == nil {
|
||||
err = upgrade.To(release)
|
||||
}
|
||||
@@ -605,36 +516,29 @@ func syncthingMain(options serveOptions) {
|
||||
l.Infoln("Initial automatic upgrade:", err)
|
||||
}
|
||||
} else {
|
||||
l.Infof("Upgraded to %q, exiting now.", release.Tag)
|
||||
l.Infof("Upgraded to %q, should exit now.", release.Tag)
|
||||
os.Exit(svcutil.ExitUpgrade.AsInt())
|
||||
}
|
||||
}
|
||||
|
||||
if options.Unpaused {
|
||||
if c.Unpaused {
|
||||
setPauseState(cfgWrapper, false)
|
||||
} else if options.Paused {
|
||||
} else if c.Paused {
|
||||
setPauseState(cfgWrapper, true)
|
||||
}
|
||||
|
||||
appOpts := syncthing.Options{
|
||||
NoUpgrade: options.NoUpgrade,
|
||||
ProfilerAddr: options.DebugProfilerListen,
|
||||
ResetDeltaIdxs: options.DebugResetDeltaIdxs,
|
||||
Verbose: options.Verbose,
|
||||
DBRecheckInterval: options.DebugDBRecheckInterval,
|
||||
DBIndirectGCInterval: options.DebugDBIndirectGCInterval,
|
||||
NoUpgrade: c.NoUpgrade,
|
||||
ProfilerAddr: c.DebugProfilerListen,
|
||||
ResetDeltaIdxs: c.DebugResetDeltaIdxs,
|
||||
Verbose: c.Verbose,
|
||||
DBMaintenanceInterval: c.DBMaintenanceInterval,
|
||||
}
|
||||
if options.Audit {
|
||||
appOpts.AuditWriter = auditWriter(options.AuditFile)
|
||||
}
|
||||
if dur, err := time.ParseDuration(os.Getenv("STRECHECKDBEVERY")); err == nil {
|
||||
appOpts.DBRecheckInterval = dur
|
||||
}
|
||||
if dur, err := time.ParseDuration(os.Getenv("STGCINDIRECTEVERY")); err == nil {
|
||||
appOpts.DBIndirectGCInterval = dur
|
||||
if c.Audit {
|
||||
appOpts.AuditWriter = auditWriter(c.AuditFile)
|
||||
}
|
||||
|
||||
app, err := syncthing.New(cfgWrapper, ldb, evLogger, cert, appOpts)
|
||||
app, err := syncthing.New(cfgWrapper, sdb, evLogger, cert, appOpts)
|
||||
if err != nil {
|
||||
l.Warnln("Failed to start Syncthing:", err)
|
||||
os.Exit(svcutil.ExitError.AsInt())
|
||||
@@ -646,7 +550,7 @@ func syncthingMain(options serveOptions) {
|
||||
|
||||
setupSignalHandling(app)
|
||||
|
||||
if options.DebugProfileCPU {
|
||||
if c.DebugProfileCPU {
|
||||
f, err := os.Create(fmt.Sprintf("cpu-%d.pprof", os.Getpid()))
|
||||
if err != nil {
|
||||
l.Warnln("Creating profile:", err)
|
||||
@@ -664,7 +568,7 @@ func syncthingMain(options serveOptions) {
|
||||
|
||||
cleanConfigDirectory()
|
||||
|
||||
if cfgWrapper.Options().StartBrowser && !options.NoBrowser && !options.InternalRestarting {
|
||||
if cfgWrapper.Options().StartBrowser && !c.NoBrowser && !c.InternalRestarting {
|
||||
// Can potentially block if the utility we are invoking doesn't
|
||||
// fork, and just execs, hence keep it in its own routine.
|
||||
go func() { _ = openURL(cfgWrapper.GUI().URL()) }()
|
||||
@@ -676,10 +580,14 @@ func syncthingMain(options serveOptions) {
|
||||
l.Warnln("Syncthing stopped with error:", app.Error())
|
||||
}
|
||||
|
||||
if options.DebugProfileCPU {
|
||||
if c.DebugProfileCPU {
|
||||
pprof.StopCPUProfile()
|
||||
}
|
||||
|
||||
// Best effort remove lockfile, doesn't matter if it succeeds
|
||||
_ = lf.Unlock()
|
||||
_ = os.Remove(locations.Get(locations.LockFile))
|
||||
|
||||
os.Exit(int(status))
|
||||
}
|
||||
|
||||
@@ -749,15 +657,11 @@ func auditWriter(auditFile string) io.Writer {
|
||||
return fd
|
||||
}
|
||||
|
||||
func resetDB() error {
|
||||
return os.RemoveAll(locations.Get(locations.Database))
|
||||
}
|
||||
|
||||
func autoUpgradePossible(options serveOptions) bool {
|
||||
func (c *serveCmd) autoUpgradePossible() bool {
|
||||
if upgrade.DisabledByCompilation {
|
||||
return false
|
||||
}
|
||||
if options.NoUpgrade {
|
||||
if c.NoUpgrade {
|
||||
l.Infof("No automatic upgrades; STNOUPGRADE environment variable defined.")
|
||||
return false
|
||||
}
|
||||
@@ -821,7 +725,7 @@ func autoUpgrade(cfg config.Wrapper, app *syncthing.App, evLogger events.Logger)
|
||||
}
|
||||
}
|
||||
|
||||
func initialAutoUpgradeCheck(misc *db.NamespacedKV) (upgrade.Release, error) {
|
||||
func initialAutoUpgradeCheck(misc *db.Typed) (upgrade.Release, error) {
|
||||
if last, ok, err := misc.Time(upgradeCheckKey); err == nil && ok && time.Since(last) < upgradeCheckInterval {
|
||||
return upgrade.Release{}, errTooEarlyUpgradeCheck
|
||||
}
|
||||
@@ -830,6 +734,10 @@ func initialAutoUpgradeCheck(misc *db.NamespacedKV) (upgrade.Release, error) {
|
||||
if err != nil {
|
||||
return upgrade.Release{}, err
|
||||
}
|
||||
if upgrade.CompareVersions(release.Tag, build.Version) == upgrade.MajorNewer {
|
||||
return upgrade.Release{}, errors.New("higher major version")
|
||||
}
|
||||
|
||||
if lastVersion, ok, err := misc.String(upgradeVersionKey); err == nil && ok && lastVersion == release.Tag {
|
||||
// Only check time if we try to upgrade to the same release.
|
||||
if lastTime, ok, err := misc.Time(upgradeTimeKey); err == nil && ok && time.Since(lastTime) < upgradeRetryInterval {
|
||||
@@ -924,3 +832,127 @@ func convertLegacyArgs(args []string) []string {
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
type versionCmd struct{}
|
||||
|
||||
func (versionCmd) Run() error {
|
||||
fmt.Println(build.LongVersion)
|
||||
return nil
|
||||
}
|
||||
|
||||
type deviceIDCmd struct{}
|
||||
|
||||
func (deviceIDCmd) Run() error {
|
||||
cert, err := tls.LoadX509KeyPair(
|
||||
locations.Get(locations.CertFile),
|
||||
locations.Get(locations.KeyFile),
|
||||
)
|
||||
if err != nil {
|
||||
l.Warnln("Error reading device ID:", err)
|
||||
os.Exit(svcutil.ExitError.AsInt())
|
||||
}
|
||||
|
||||
fmt.Println(protocol.NewDeviceID(cert.Certificate[0]))
|
||||
return nil
|
||||
}
|
||||
|
||||
type pathsCmd struct{}
|
||||
|
||||
func (pathsCmd) Run() error {
|
||||
fmt.Print(locations.PrettyPaths())
|
||||
return nil
|
||||
}
|
||||
|
||||
type upgradeCmd struct {
|
||||
CheckOnly bool `short:"c" help:"Check for available upgrade, then exit"`
|
||||
From string `short:"u" placeholder:"URL" help:"Force upgrade directly from specified URL"`
|
||||
}
|
||||
|
||||
func (u upgradeCmd) Run() error {
|
||||
if u.CheckOnly {
|
||||
if _, err := checkUpgrade(); err != nil {
|
||||
l.Warnln("Checking for upgrade:", err)
|
||||
os.Exit(exitCodeForUpgrade(err))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if u.From != "" {
|
||||
err := upgrade.ToURL(u.From)
|
||||
if err != nil {
|
||||
l.Warnln("Error while Upgrading:", err)
|
||||
os.Exit(svcutil.ExitError.AsInt())
|
||||
}
|
||||
l.Infoln("Upgraded from", u.From)
|
||||
return nil
|
||||
}
|
||||
|
||||
release, err := checkUpgrade()
|
||||
if err == nil {
|
||||
lf := flock.New(locations.Get(locations.LockFile))
|
||||
locked, err := lf.TryLock()
|
||||
if err != nil {
|
||||
l.Warnln("Upgrade:", err)
|
||||
os.Exit(1)
|
||||
} else if locked {
|
||||
err = upgradeViaRest()
|
||||
} else {
|
||||
err = upgrade.To(release)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
l.Warnln("Upgrade:", err)
|
||||
os.Exit(exitCodeForUpgrade(err))
|
||||
}
|
||||
l.Infof("Upgraded to %q", release.Tag)
|
||||
os.Exit(svcutil.ExitUpgrade.AsInt())
|
||||
return nil
|
||||
}
|
||||
|
||||
type browserCmd struct{}
|
||||
|
||||
func (browserCmd) Run() error {
|
||||
if err := openGUI(); err != nil {
|
||||
l.Warnln("Failed to open web UI:", err)
|
||||
os.Exit(svcutil.ExitError.AsInt())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type debugCmd struct {
|
||||
ResetDatabase resetDatabaseCmd `cmd:"" help:"Reset the database, forcing a full rescan and resync"`
|
||||
}
|
||||
|
||||
type resetDatabaseCmd struct{}
|
||||
|
||||
func (resetDatabaseCmd) Run() error {
|
||||
l.Infoln("Removing database in", locations.Get(locations.Database))
|
||||
if err := os.RemoveAll(locations.Get(locations.Database)); err != nil {
|
||||
l.Warnln("Resetting database:", err)
|
||||
os.Exit(svcutil.ExitError.AsInt())
|
||||
}
|
||||
l.Infoln("Successfully reset database - it will be rebuilt after next start.")
|
||||
return nil
|
||||
}
|
||||
|
||||
func setConfigDataLocationsFromFlags(homeDir, confDir, dataDir string) error {
|
||||
homeSet := homeDir != ""
|
||||
confSet := confDir != ""
|
||||
dataSet := dataDir != ""
|
||||
switch {
|
||||
case dataSet != confSet:
|
||||
return errors.New("either both or none of --config and --data must be given, use --home to set both at once")
|
||||
case homeSet && dataSet:
|
||||
return errors.New("--home must not be used together with --config and --data")
|
||||
case homeSet:
|
||||
confDir = homeDir
|
||||
dataDir = homeDir
|
||||
fallthrough
|
||||
case dataSet:
|
||||
if err := locations.SetBaseDir(locations.ConfigBaseDir, confDir); err != nil {
|
||||
return err
|
||||
}
|
||||
return locations.SetBaseDir(locations.DataBaseDir, dataDir)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ const (
|
||||
panicUploadNoticeWait = 10 * time.Second
|
||||
)
|
||||
|
||||
func monitorMain(options serveOptions) {
|
||||
func (c *serveCmd) monitorMain() {
|
||||
l.SetPrefix("[monitor] ")
|
||||
|
||||
var dst io.Writer = os.Stdout
|
||||
@@ -58,13 +58,13 @@ func monitorMain(options serveOptions) {
|
||||
open := func(name string) (io.WriteCloser, error) {
|
||||
return newAutoclosedFile(name, logFileAutoCloseDelay, logFileMaxOpenTime)
|
||||
}
|
||||
if options.LogMaxSize > 0 {
|
||||
fileDst, err = newRotatedFile(logFile, open, int64(options.LogMaxSize), options.LogMaxFiles)
|
||||
if c.LogMaxSize > 0 {
|
||||
fileDst, err = newRotatedFile(logFile, open, int64(c.LogMaxSize), c.LogMaxFiles)
|
||||
} else {
|
||||
fileDst, err = open(logFile)
|
||||
}
|
||||
if err != nil {
|
||||
l.Warnln("Failed to setup logging to file, proceeding with logging to stdout only:", err)
|
||||
l.Warnln("Failed to set up logging to file, proceeding with logging to stdout only:", err)
|
||||
} else {
|
||||
if build.IsWindows {
|
||||
// Translate line breaks to Windows standard
|
||||
@@ -178,7 +178,7 @@ func monitorMain(options serveOptions) {
|
||||
|
||||
if exiterr, ok := err.(*exec.ExitError); ok {
|
||||
exitCode := exiterr.ExitCode()
|
||||
if stopped || options.NoRestart {
|
||||
if stopped || c.NoRestart {
|
||||
os.Exit(exitCode)
|
||||
}
|
||||
if exitCode == svcutil.ExitUpgrade.AsInt() {
|
||||
@@ -192,7 +192,7 @@ func monitorMain(options serveOptions) {
|
||||
}
|
||||
}
|
||||
|
||||
if options.NoRestart {
|
||||
if c.NoRestart {
|
||||
os.Exit(svcutil.ExitError.AsInt())
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,9 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/locations"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
"golang.org/x/exp/constraints"
|
||||
)
|
||||
|
||||
func startPerfStats() {
|
||||
@@ -29,37 +31,68 @@ func savePerfStats(file string) {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var prevUsage int64
|
||||
var prevTime int64
|
||||
var rusage syscall.Rusage
|
||||
var memstats runtime.MemStats
|
||||
var prevTime time.Time
|
||||
var curRus, prevRus syscall.Rusage
|
||||
var curMem, prevMem runtime.MemStats
|
||||
var prevIn, prevOut int64
|
||||
|
||||
t0 := time.Now()
|
||||
syscall.Getrusage(syscall.RUSAGE_SELF, &prevRus)
|
||||
runtime.ReadMemStats(&prevMem)
|
||||
|
||||
fmt.Fprintf(fd, "TIME_S\tCPU_S\tHEAP_KIB\tRSS_KIB\tNETIN_KBPS\tNETOUT_KBPS\tDBSIZE_KIB\n")
|
||||
|
||||
for t := range time.NewTicker(250 * time.Millisecond).C {
|
||||
if err := syscall.Getrusage(syscall.RUSAGE_SELF, &rusage); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
curTime := time.Now().UnixNano()
|
||||
timeDiff := curTime - prevTime
|
||||
curUsage := rusage.Utime.Nano() + rusage.Stime.Nano()
|
||||
usageDiff := curUsage - prevUsage
|
||||
cpuUsagePercent := 100 * float64(usageDiff) / float64(timeDiff)
|
||||
prevTime = curTime
|
||||
prevUsage = curUsage
|
||||
syscall.Getrusage(syscall.RUSAGE_SELF, &curRus)
|
||||
runtime.ReadMemStats(&curMem)
|
||||
in, out := protocol.TotalInOut()
|
||||
var inRate, outRate float64
|
||||
if timeDiff > 0 {
|
||||
inRate = float64(in-prevIn) / (float64(timeDiff) / 1e9) // bytes per second
|
||||
outRate = float64(out-prevOut) / (float64(timeDiff) / 1e9) // bytes per second
|
||||
}
|
||||
timeDiff := t.Sub(prevTime)
|
||||
|
||||
fmt.Fprintf(fd, "%.03f\t%f\t%d\t%d\t%.0f\t%.0f\t%d\n",
|
||||
t.Sub(t0).Seconds(),
|
||||
rate(cpusec(&prevRus), cpusec(&curRus), timeDiff, 1),
|
||||
(curMem.Sys-curMem.HeapReleased)/1024,
|
||||
curRus.Maxrss/1024,
|
||||
rate(prevIn, in, timeDiff, 1e3),
|
||||
rate(prevOut, out, timeDiff, 1e3),
|
||||
dirsize(locations.Get(locations.Database))/1024,
|
||||
)
|
||||
|
||||
prevTime = t
|
||||
prevRus = curRus
|
||||
prevMem = curMem
|
||||
prevIn, prevOut = in, out
|
||||
|
||||
runtime.ReadMemStats(&memstats)
|
||||
|
||||
startms := int(t.Sub(t0).Seconds() * 1000)
|
||||
|
||||
fmt.Fprintf(fd, "%d\t%f\t%d\t%d\t%.0f\t%.0f\n", startms, cpuUsagePercent, memstats.Alloc, memstats.Sys-memstats.HeapReleased, inRate, outRate)
|
||||
}
|
||||
}
|
||||
|
||||
func cpusec(r *syscall.Rusage) float64 {
|
||||
return float64(r.Utime.Nano()+r.Stime.Nano()) / float64(time.Second)
|
||||
}
|
||||
|
||||
type number interface {
|
||||
constraints.Float | constraints.Integer
|
||||
}
|
||||
|
||||
func rate[T number](prev, cur T, d time.Duration, div float64) float64 {
|
||||
diff := cur - prev
|
||||
rate := float64(diff) / d.Seconds() / div
|
||||
return rate
|
||||
}
|
||||
|
||||
func dirsize(location string) int64 {
|
||||
entries, err := os.ReadDir(location)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
var size int64
|
||||
for _, entry := range entries {
|
||||
fi, err := entry.Info()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
size += fi.Size()
|
||||
}
|
||||
|
||||
return size
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Name=Start Syncthing
|
||||
GenericName=File synchronization
|
||||
Comment=Starts the main syncthing process in the background.
|
||||
Exec=/usr/bin/syncthing serve --no-browser --logfile=default
|
||||
Exec=syncthing serve --no-browser --logfile=default
|
||||
Icon=syncthing
|
||||
Terminal=false
|
||||
Type=Application
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Name=Syncthing Web UI
|
||||
GenericName=File synchronization UI
|
||||
Comment=Opens Syncthing's Web UI in the default browser (Syncthing must already be started).
|
||||
Exec=/usr/bin/syncthing --browser-only
|
||||
Exec=syncthing --browser-only
|
||||
Icon=syncthing
|
||||
Terminal=false
|
||||
Type=Application
|
||||
|
||||
67
go.mod
67
go.mod
@@ -1,55 +1,60 @@
|
||||
module github.com/syncthing/syncthing
|
||||
|
||||
go 1.22.0
|
||||
go 1.23.0
|
||||
|
||||
require (
|
||||
github.com/AudriusButkevicius/recli v0.0.7-0.20220911121932-d000ce8fbf0f
|
||||
github.com/alecthomas/kong v1.6.0
|
||||
github.com/aws/aws-sdk-go v1.55.5
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.0
|
||||
github.com/alecthomas/kong v1.10.0
|
||||
github.com/aws/aws-sdk-go v1.55.6
|
||||
github.com/calmh/incontainer v1.0.0
|
||||
github.com/calmh/xdr v1.2.0
|
||||
github.com/ccding/go-stun v0.1.5
|
||||
github.com/chmduquesne/rollinghash v4.0.0+incompatible
|
||||
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/gobwas/glob v0.2.3
|
||||
github.com/greatroar/blobloom v0.8.0
|
||||
github.com/gofrs/flock v0.12.1
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7
|
||||
github.com/jackpal/gateway v1.0.16
|
||||
github.com/jackpal/go-nat-pmp v1.0.2
|
||||
github.com/jmoiron/sqlx v1.4.0
|
||||
github.com/julienschmidt/httprouter v1.3.0
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||
github.com/maruel/panicparse/v2 v2.4.0
|
||||
github.com/maxbrunsfeld/counterfeiter/v6 v6.8.1
|
||||
github.com/maruel/panicparse/v2 v2.5.0
|
||||
github.com/mattn/go-sqlite3 v1.14.27
|
||||
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.20.5
|
||||
github.com/puzpuzpuz/xsync/v3 v3.4.0
|
||||
github.com/quic-go/quic-go v0.48.2
|
||||
github.com/prometheus/client_golang v1.21.1
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1
|
||||
github.com/quic-go/quic-go v0.50.1
|
||||
github.com/rabbitmq/amqp091-go v1.10.0
|
||||
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475
|
||||
github.com/shirou/gopsutil/v4 v4.24.12
|
||||
github.com/syncthing/notify v0.0.0-20210616190510-c6b7342338d2
|
||||
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/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.31.0
|
||||
golang.org/x/net v0.33.0
|
||||
golang.org/x/sys v0.28.0
|
||||
golang.org/x/text v0.21.0
|
||||
golang.org/x/time v0.8.0
|
||||
golang.org/x/tools v0.28.0
|
||||
google.golang.org/protobuf v1.36.1
|
||||
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/time v0.11.0
|
||||
golang.org/x/tools v0.31.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.17.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // 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
|
||||
@@ -57,21 +62,23 @@ require (
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/ebitengine/purego v0.8.1 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/ebitengine/purego v0.8.2 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.7 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||
github.com/gofrs/flock v0.12.1 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/pprof v0.0.0-20241009165004-a3522334989c // indirect
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e // 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/oschwald/maxminddb-golang v1.13.1 // indirect
|
||||
@@ -80,8 +87,9 @@ require (
|
||||
github.com/posener/complete v1.2.3 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.60.0 // indirect
|
||||
github.com/prometheus/common v0.62.0 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/riywo/loginshell v0.0.0-20200815045211-7d26008be1ab // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
@@ -89,11 +97,14 @@ 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.4.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect
|
||||
golang.org/x/mod v0.22.0 // indirect
|
||||
golang.org/x/sync v0.10.0 // indirect
|
||||
go.uber.org/mock v0.5.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
|
||||
golang.org/x/mod v0.24.0 // indirect
|
||||
golang.org/x/sync v0.12.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
modernc.org/libc v1.62.1 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.9.1 // indirect
|
||||
)
|
||||
|
||||
// https://github.com/gobwas/glob/pull/55
|
||||
|
||||
166
go.sum
166
go.sum
@@ -1,18 +1,32 @@
|
||||
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.17.0 h1:g0EZJwz7xkXQiZAI5xi9f3WWFYBlX1CPTrR+NDToRkQ=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0/go.mod h1:XCW7KnZet0Opnr7HccfUw1PLc4CjHqpcaxW8DHklNkQ=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 h1:B/dfvscEQtew9dVuoxqxrUKKv8Ih2f55PydknDamU+g=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0/go.mod h1:fiPSssYvltE08HJchL04dOy+RD4hgrjph0cwGGMntdI=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0 h1:PiSrjRPpkQNjrM8H0WwKMnZUdu1RGMtd/LdGKUrOo+c=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0/go.mod h1:oDrbWx4ewMylP7xHivfgixbfGBT6APAwsSoHRKotnIc=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.0 h1:UXT0o77lXQrikd1kgwIPQOUect7EoR/+sbP4wQKdzxM=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.0/go.mod h1:cTvi54pg19DoT07ekoeMgE/taAwNtCShVeZqA+Iv2xI=
|
||||
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.3.2 h1:kYRSnvJju5gYVyhkij+RTJ/VR6QIUaCfWeaFm2ycsjQ=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.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.6.0 h1:mwOzbdMR7uv2vul9J0FU3GYxE7ls/iX1ieMg5WIM6gE=
|
||||
github.com/alecthomas/kong v1.6.0/go.mod h1:p2vqieVMeTAnaC83txKtXe8FLke2X07aruPWXyMPQrU=
|
||||
github.com/alecthomas/kong v1.10.0 h1:8K4rGDpT7Iu+jEXCIJUeKqvpwZHbsFRoebLbnzlmrpw=
|
||||
github.com/alecthomas/kong v1.10.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.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU=
|
||||
github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
|
||||
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/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=
|
||||
@@ -29,8 +43,6 @@ github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d h1:S2NE3iHSwP0XV
|
||||
github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chmduquesne/rollinghash v4.0.0+incompatible h1:hnREQO+DXjqIw3rUTzWN7/+Dpw+N5Um8zpKV0JOEgbo=
|
||||
github.com/chmduquesne/rollinghash v4.0.0+incompatible/go.mod h1:Uc2I36RRfTAf7Dge82bi3RU0OQUmXT9iweIcPqvr8A0=
|
||||
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=
|
||||
@@ -41,8 +53,10 @@ github.com/d4l3k/messagediff v1.2.1/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkE
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
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/ebitengine/purego v0.8.1 h1:sdRKd6plj7KYW33EH5As6YKfe8m9zbN9JMrOjNVF/BE=
|
||||
github.com/ebitengine/purego v0.8.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
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/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=
|
||||
@@ -60,11 +74,15 @@ github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ4
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
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.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1/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=
|
||||
@@ -81,17 +99,16 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
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-20241009165004-a3522334989c h1:NDovD0SMpBYXlE1zJmS1q55vWB/fUQBcPAqAboZSccA=
|
||||
github.com/google/pprof v0.0.0-20241009165004-a3522334989c/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||
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/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/greatroar/blobloom v0.8.0 h1:I9RlEkfqK9/6f1v9mFmDYegDQ/x0mISCpiNpAm23Pt4=
|
||||
github.com/greatroar/blobloom v0.8.0/go.mod h1:mjMJ1hh1wjGVfr93QIHJ6FfDNVrA0IELv8OvMHJxHKs=
|
||||
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=
|
||||
@@ -127,6 +144,8 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
|
||||
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
|
||||
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
|
||||
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=
|
||||
@@ -139,18 +158,27 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 h1:7UMa6KCCMjZEMDtTVdcGu0B1GmmC7QJKiCCjyTAWQy0=
|
||||
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
|
||||
github.com/maruel/panicparse/v2 v2.4.0 h1:yQKMIbQ0DKfinzVkTkcUzQyQ60UCiNnYfR7PWwTs2VI=
|
||||
github.com/maruel/panicparse/v2 v2.4.0/go.mod h1:nOY2OKe8csO3F3SA5+hsxot05JLgukrF54B9x88fVp4=
|
||||
github.com/maxbrunsfeld/counterfeiter/v6 v6.8.1 h1:NicmruxkeqHjDv03SfSxqmaLuisddudfP3h5wdXFbhM=
|
||||
github.com/maxbrunsfeld/counterfeiter/v6 v6.8.1/go.mod h1:eyp4DdUJAKkr9tvxR3jWhw2mDK7CWABMG5r9uyaKC7I=
|
||||
github.com/maruel/panicparse/v2 v2.5.0 h1:yCtuS0FWjfd0RTYMXGpDvWcb0kINm8xJGu18/xMUh00=
|
||||
github.com/maruel/panicparse/v2 v2.5.0/go.mod h1:DA2fDiBk63bKfBf4CVZP9gb4fuvzdPbLDsSI873hweQ=
|
||||
github.com/mattn/go-isatty v0.0.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=
|
||||
github.com/maxmind/geoipupdate/v6 v6.1.0/go.mod h1:cZYCDzfMzTY4v6dKRdV7KTB6SStxtn3yFkiJ1btTGGc=
|
||||
github.com/miscreant/miscreant.go v0.0.0-20200214223636-26d376326b75 h1:cUVxyR+UfmdEAZGJ8IiKld1O0dbGotEnkMolG5hfMSY=
|
||||
github.com/miscreant/miscreant.go v0.0.0-20200214223636-26d376326b75/go.mod h1:pBbZyGwC5i16IBkjVKoy/sznA8jPD/K9iedwe1ESE6w=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY=
|
||||
@@ -167,14 +195,16 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J
|
||||
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.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
|
||||
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
|
||||
github.com/onsi/gomega v1.36.1 h1:bJDPBO7ibjxcbHMgSCoo4Yj18UWbKDlLwX1x9sybDcw=
|
||||
github.com/onsi/gomega v1.36.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
|
||||
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=
|
||||
@@ -186,32 +216,34 @@ 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.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
|
||||
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
||||
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_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.60.0 h1:+V9PAREWNvJMAuJ1x1BaWl9dewMW4YrHZQbx0sJNllA=
|
||||
github.com/prometheus/common v0.60.0/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw=
|
||||
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
|
||||
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
|
||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||
github.com/quic-go/quic-go v0.48.2 h1:wsKXZPeGWpMpCGSWqOcqpW2wZYic/8T3aqiOID0/KWE=
|
||||
github.com/quic-go/quic-go v0.48.2/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs=
|
||||
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/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-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/riywo/loginshell v0.0.0-20200815045211-7d26008be1ab h1:ZjX6I48eZSFetPb41dHudEyVr5v953N15TsNZXlkcWY=
|
||||
github.com/riywo/loginshell v0.0.0-20200815045211-7d26008be1ab/go.mod h1:/PfPXh0EntGc3QAAyUaviy4S9tzy4Zp0e2ilq4voC6E=
|
||||
github.com/rogpeppe/go-internal v1.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.24.12 h1:qvePBOk20e0IKA1QXrIIU+jmk+zEiYVVx06WjBRlZo4=
|
||||
github.com/shirou/gopsutil/v4 v4.24.12/go.mod h1:DCtMPAad2XceTeIAbGyVfycbYQNBGk2P8cvDi7/VN9o=
|
||||
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/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=
|
||||
@@ -227,8 +259,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl
|
||||
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-20210616190510-c6b7342338d2 h1:F4snRP//nIuTTW9LYEzVH4HVwDG9T3M4t8y/2nqMbiY=
|
||||
github.com/syncthing/notify v0.0.0-20210616190510-c6b7342338d2/go.mod h1:J0q59IWjLtpRIJulohwqEZvjzwOfTEPp8SVhDJl+y0Y=
|
||||
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/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=
|
||||
@@ -252,8 +284,8 @@ 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.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
|
||||
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
||||
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
||||
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
||||
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=
|
||||
@@ -262,18 +294,19 @@ 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 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY=
|
||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8=
|
||||
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/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.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
|
||||
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
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=
|
||||
@@ -291,8 +324,9 @@ 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 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
||||
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/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=
|
||||
@@ -301,8 +335,9 @@ 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 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
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/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=
|
||||
@@ -326,12 +361,14 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
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 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||
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/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=
|
||||
@@ -350,10 +387,11 @@ 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 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
|
||||
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
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/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=
|
||||
@@ -361,8 +399,8 @@ 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.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8=
|
||||
golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
|
||||
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/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=
|
||||
@@ -376,8 +414,8 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
|
||||
google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
@@ -393,5 +431,29 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
modernc.org/cc/v4 v4.25.2 h1:T2oH7sZdGvTaie0BRNFbIYsabzCxUQg8nLqCdQ2i0ic=
|
||||
modernc.org/cc/v4 v4.25.2/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||
modernc.org/ccgo/v4 v4.25.1 h1:TFSzPrAGmDsdnhT9X2UrcPMI3N/mJ9/X9ykKXwLhDsU=
|
||||
modernc.org/ccgo/v4 v4.25.1/go.mod h1:njjuAYiPflywOOrm3B7kCB444ONP5pAVr8PIEoE0uDw=
|
||||
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
|
||||
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
|
||||
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||
modernc.org/libc v1.62.1 h1:s0+fv5E3FymN8eJVmnk0llBe6rOxCu/DEU+XygRbS8s=
|
||||
modernc.org/libc v1.62.1/go.mod h1:iXhATfJQLjG3NWy56a6WVU73lWOcdYVxsvwCgoPljuo=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.9.1 h1:V/Z1solwAVmMW1yttq3nDdZPJqV1rM05Ccq6KMSZ34g=
|
||||
modernc.org/memory v1.9.1/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
||||
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||
modernc.org/sqlite v1.37.0 h1:s1TMe7T3Q3ovQiK2Ouz4Jwh7dw4ZDqbebSDTlSJdfjI=
|
||||
modernc.org/sqlite v1.37.0/go.mod h1:5YiWv+YviqGMuGw4V+PNplcyaJ5v+vQd7TQOgkACoJM=
|
||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
|
||||
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
|
||||
|
||||
@@ -154,7 +154,7 @@
|
||||
"Failed Items": "العناصر الفاشلة",
|
||||
"Failed to load file versions.": "لم يُتَوَصَّل لنسخة الملف.",
|
||||
"Failed to load ignore patterns.": "فشل التَّوَصُّل إلى مُرَشِّحات التجاهل.",
|
||||
"Failed to setup, retrying": "فشل الإعداد، تجري المحاولة مرة أخرى",
|
||||
"Failed to set up, retrying": "فشل الإعداد، تجري المحاولة مرة أخرى",
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "يُتوقع فشل الاتصال بخوادم IPv6، إذا لم يكن IPv6 متاحا.",
|
||||
"File Pull Order": "ترتيب استيراد الملفات",
|
||||
"File Versioning": "إصدارات الملف",
|
||||
|
||||
@@ -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": "Почистване на версии",
|
||||
@@ -154,7 +156,7 @@
|
||||
"Failed Items": "Елементи с грешка",
|
||||
"Failed to load file versions.": "Грешка при зареждане на версии.",
|
||||
"Failed to load ignore patterns.": "Грешка при зареждане на шаблони за пренебрегване.",
|
||||
"Failed to setup, retrying": "Грешка при настройване, извършва се повторен опит",
|
||||
"Failed to set up, retrying": "Грешка при настройване, извършва се повторен опит",
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "Неуспешна връзка към сървъри по IPv6 може да се очаква ако няма свързаност по IPv6.",
|
||||
"File Pull Order": "Ред на изтегляне",
|
||||
"File Versioning": "Версии на файловете",
|
||||
|
||||
@@ -154,7 +154,7 @@
|
||||
"Failed Items": "Elements fallats",
|
||||
"Failed to load file versions.": "No s'han pogut carregar les versions dels fitxers.",
|
||||
"Failed to load ignore patterns.": "No s'han pogut carregar els patrons ignorats.",
|
||||
"Failed to setup, retrying": "No s'ha pogut configurar, s'està tornant a provar",
|
||||
"Failed to set up, retrying": "No s'ha pogut configurar, s'està tornant a provar",
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "S'espera que no es pugui connectar als servidors IPv6 si no hi ha connectivitat IPv6.",
|
||||
"File Pull Order": "Ordre d'agafar fitxers",
|
||||
"File Versioning": "Versionat de Fitxers",
|
||||
|
||||
@@ -150,7 +150,7 @@
|
||||
"Failed Items": "Objectes fallits",
|
||||
"Failed to load file versions.": "No s'han pogut carregar les versions dels fitxers.",
|
||||
"Failed to load ignore patterns.": "No s'han pogut carregar els patrons ignorats.",
|
||||
"Failed to setup, retrying": "Errada en la configuració, reintentant",
|
||||
"Failed to set up, retrying": "Errada en la configuració, reintentant",
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "És possible que es produïsca una fallada al connectar als servidors IPv6 si no hi ha connectivitat IPv6.",
|
||||
"File Pull Order": "Ordre de fitxers del pull",
|
||||
"File Versioning": "Versionat de fitxer",
|
||||
|
||||
@@ -154,7 +154,7 @@
|
||||
"Failed Items": "Nezdařené položky",
|
||||
"Failed to load file versions.": "Nepodařilo se nahrát verze souboru.",
|
||||
"Failed to load ignore patterns.": "Načtení vzorů ignorovaného se nezdařilo.",
|
||||
"Failed to setup, retrying": "Nastavování se nezdařilo, zkouší se znovu",
|
||||
"Failed to set up, retrying": "Nastavování se nezdařilo, zkouší se znovu",
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "Je v pořádku, když se připojení k IPv6 serverům nezdaří, pokud není k dispozici IPv6 konektivita.",
|
||||
"File Pull Order": "Pořadí stahování souborů",
|
||||
"File Versioning": "Správa verzí souborů",
|
||||
|
||||
@@ -154,7 +154,7 @@
|
||||
"Failed Items": "Mislykkede filer",
|
||||
"Failed to load file versions.": "Fil versioner kunne ikke indlæses.",
|
||||
"Failed to load ignore patterns.": "Ignorerings-mønstre kunne ikke indlæses.",
|
||||
"Failed to setup, retrying": "Opsætning mislykkedes; prøver igen",
|
||||
"Failed to set up, retrying": "Opsætning mislykkedes; prøver igen",
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "Fejl i forbindelse med opkobling til IPv6-servere skal forventes, hvis der ikke er IPv6-forbindelse.",
|
||||
"File Pull Order": "Hentningsrækkefølge for filer",
|
||||
"File Versioning": "Filversionering",
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"Allowed Networks": "Erlaubte Netzwerke",
|
||||
"Alphabetic": "Alphabetisch",
|
||||
"Altered by ignoring deletes.": "Weicht ab, weil Löschungen ignoriert werden.",
|
||||
"Always turned on when the folder type is \"{%foldertype%}\".": "Immer eingeschaltet, wenn der Ordnertyp „{{foldertype}}“ ist.",
|
||||
"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.": "Die Versionierung erfolgt über einen externen Befehl. Er muss die Datei aus dem geteilten Ordner entfernen. Wenn der Pfad zur Anwendung Leerzeichen enthält, sollte er in Anführungszeichen gesetzt werden.",
|
||||
"Anonymous Usage Reporting": "Anonymer Nutzungsbericht",
|
||||
"Anonymous usage report format has changed. Would you like to move to the new format?": "Das Format des anonymen Nutzungsberichts hat sich geändert. Möchten Sie auf das neue Format umsteigen?",
|
||||
@@ -52,6 +53,7 @@
|
||||
"Body:": "Nachrichtentext:",
|
||||
"Bugs": "Fehler",
|
||||
"Cancel": "Abbrechen",
|
||||
"Cannot be enabled when the folder type is \"{%foldertype%}\".": "Kann nicht aktiviert werden, wenn der Ordnertyp „{{foldertype}}“ ist.",
|
||||
"Changelog": "Änderungsprotokoll",
|
||||
"Clean out after": "Löschen nach",
|
||||
"Cleaning Versions": "Versionen bereinigen",
|
||||
@@ -154,7 +156,7 @@
|
||||
"Failed Items": "Fehlgeschlagene Elemente",
|
||||
"Failed to load file versions.": "Fehler beim Laden der Dateiversionen.",
|
||||
"Failed to load ignore patterns.": "Fehler beim Laden der Ignoriermuster.",
|
||||
"Failed to setup, retrying": "Fehler beim Einrichten, erneuter Versuch",
|
||||
"Failed to set up, retrying": "Fehler beim Einrichten, erneuter Versuch",
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "Ein Verbindungsfehler zu IPv6-Servern ist zu erwarten, wenn es keine IPv6-Konnektivität gibt.",
|
||||
"File Pull Order": "Dateiübertragungsreihenfolge",
|
||||
"File Versioning": "Dateiversionierung",
|
||||
|
||||
@@ -154,7 +154,7 @@
|
||||
"Failed Items": "Αρχεία που απέτυχαν",
|
||||
"Failed to load file versions.": "Η φόρτωση των εκδόσεων αρχείων απέτυχε.",
|
||||
"Failed to load ignore patterns.": "Αποτυχία φόρτωσης μοτίβων παράβλεψης.",
|
||||
"Failed to setup, retrying": "Αποτυχία ενεργοποίησης, γίνεται νέα προσπάθεια",
|
||||
"Failed to set up, retrying": "Αποτυχία ενεργοποίησης, γίνεται νέα προσπάθεια",
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "Είναι φυσιολογική η αποτυχία σύνδεσης σε εξυπηρετητές IPv6 όταν δεν υπάρχει συνδεσιμότητα IPv6.",
|
||||
"File Pull Order": "Σειρά με την οποία θα κατεβαίνουν τα αρχεία",
|
||||
"File Versioning": "Τήρηση εκδόσεων αρχείων",
|
||||
|
||||
@@ -150,7 +150,7 @@
|
||||
"Failed Items": "Failed Items",
|
||||
"Failed to load file versions.": "Failed to load file versions.",
|
||||
"Failed to load ignore patterns.": "Failed to load ignore patterns.",
|
||||
"Failed to setup, retrying": "Failed to setup, retrying",
|
||||
"Failed to set up, retrying": "Failed to set up, retrying",
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.",
|
||||
"File Pull Order": "File Pull Order",
|
||||
"File Versioning": "File Versioning",
|
||||
|
||||
@@ -154,7 +154,7 @@
|
||||
"Failed Items": "Failed Items",
|
||||
"Failed to load file versions.": "Failed to load file versions.",
|
||||
"Failed to load ignore patterns.": "Failed to load ignore patterns.",
|
||||
"Failed to setup, retrying": "Failed to setup, retrying",
|
||||
"Failed to set up, retrying": "Failed to set up, retrying",
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.",
|
||||
"File Pull Order": "File Pull Order",
|
||||
"File Versioning": "File Versioning",
|
||||
|
||||
@@ -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",
|
||||
@@ -154,7 +156,7 @@
|
||||
"Failed Items": "Failed Items",
|
||||
"Failed to load file versions.": "Failed to load file versions.",
|
||||
"Failed to load ignore patterns.": "Failed to load ignore patterns.",
|
||||
"Failed to setup, retrying": "Failed to setup, retrying",
|
||||
"Failed to set up, retrying": "Failed to set up, retrying",
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.",
|
||||
"File Pull Order": "File Pull Order",
|
||||
"File Versioning": "File Versioning",
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
"Error": "Eraro",
|
||||
"External File Versioning": "Ekstera Versionado de Dosiero",
|
||||
"Failed Items": "Malsukcesaj Eroj",
|
||||
"Failed to setup, retrying": "Malsukcesis agordi, provante denove",
|
||||
"Failed to set up, retrying": "Malsukcesis agordi, provante denove",
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "Malsukceso por konekti al IPv6 serviloj atendante se ekzistas neniu IPv6 konektebleco.",
|
||||
"File Pull Order": "Ordo por Tiri Dosieron",
|
||||
"File Versioning": "Versionado de Dosieroj",
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"Add Device": "Agregar el dispositivo",
|
||||
"Add Folder": "Agregar Carpeta",
|
||||
"Add Remote Device": "Añadir un dispositivo remoto",
|
||||
"Add devices from the introducer to our device list, for mutually shared folders.": "Añadir dispositivos desde el introductor a nuestra lista de dispositivos, para las carpetas compartidas mutuamente.",
|
||||
"Add devices from the introducer to our device list, for mutually shared folders.": "Añadir dispositivos del presentador a nuestra lista de dispositivos para las carpetas compartidas mutuamente.",
|
||||
"Add filter entry": "Añadir una entrada al filtro",
|
||||
"Add ignore patterns": "Agregar patrones a ignorar",
|
||||
"Add new folder?": "¿Agregar una carpeta nueva?",
|
||||
@@ -29,7 +29,7 @@
|
||||
"Altered by ignoring deletes.": "Alterado ignorando eliminaciones.",
|
||||
"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.": "Un comando externo maneja las versiones. Tienes que eliminar el archivo de la carpeta compartida. Si la ruta a la aplicación contiene espacios, ésta debe estar entre comillas.",
|
||||
"Anonymous Usage Reporting": "Informe anónimo de uso",
|
||||
"Anonymous usage report format has changed. Would you like to move to the new format?": "El formato del informe de uso anónimo a cambiado. ¿Desearía usar el nuevo formato?",
|
||||
"Anonymous usage report format has changed. Would you like to move to the new format?": "El formato del informe de uso anónimo a cambiado. ¿Le gustaría pasar al nuevo formato?",
|
||||
"Applied to LAN": "Aplicado a la LAN",
|
||||
"Apply": "Solicitar",
|
||||
"Are you sure you want to override all remote changes?": "¿Está seguro(a) de que desea sobreescribir todos los cambios remotos?",
|
||||
@@ -131,7 +131,7 @@
|
||||
"Edit Device": "Editar Dispositivo",
|
||||
"Edit Device Defaults": "Editar Valores Predeterminados del Dispositivo",
|
||||
"Edit Folder": "Editar Carpeta",
|
||||
"Edit Folder Defaults": "Editar Valores Predeterminados de la Carpeta",
|
||||
"Edit Folder Defaults": "Editar Valores Predeterminados de las Carpeta",
|
||||
"Editing {%path%}.": "Editando {{path}}.",
|
||||
"Enable Crash Reporting": "Activar Informes de Fallos",
|
||||
"Enable NAT traversal": "Permitir NAT transversal",
|
||||
@@ -154,7 +154,7 @@
|
||||
"Failed Items": "Elementos fallidos",
|
||||
"Failed to load file versions.": "Error al cargar las versiones de los archivos.",
|
||||
"Failed to load ignore patterns.": "No se pudieron cargar los patrones de ignorar.",
|
||||
"Failed to setup, retrying": "Fallo en la configuración, reintentando",
|
||||
"Failed to set up, retrying": "Fallo en la configuración, reintentando",
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "Se espera un fallo al conectar a los servidores IPv6 si no hay conectividad IPv6.",
|
||||
"File Pull Order": "Orden de Obtención de los Archivos",
|
||||
"File Versioning": "Versionado de ficheros",
|
||||
@@ -269,7 +269,7 @@
|
||||
"No upgrades": "Sin actualizaciones",
|
||||
"Not shared": "No Compartido(a)",
|
||||
"Notice": "Aviso",
|
||||
"Number of Connections": "Número de las conexiones",
|
||||
"Number of Connections": "Número de conexiones",
|
||||
"OK": "De acuerdo",
|
||||
"Off": "Desactivado",
|
||||
"Oldest First": "El más antiguo primero",
|
||||
@@ -532,7 +532,7 @@
|
||||
"deleted": "eliminado",
|
||||
"deny": "denegar",
|
||||
"directories": "directorios",
|
||||
"file": "expediente",
|
||||
"file": "fichero",
|
||||
"files": "archivos",
|
||||
"folder": "carpeta",
|
||||
"full documentation": "Documentación completa",
|
||||
|
||||
@@ -130,7 +130,7 @@
|
||||
"External File Versioning": "Fitxategi bertsioen kanpoko kudeaketa",
|
||||
"Failed Items": "Huts egin duten fitxategiak",
|
||||
"Failed to load ignore patterns.": "Huts egin du baztertze ereduak kargatzean.",
|
||||
"Failed to setup, retrying": "Konfigurazioan huts egitea, berriro saiatuz",
|
||||
"Failed to set up, retrying": "Konfigurazioan huts egitea, berriro saiatuz",
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "IPv6 zerbitzariei buruzko konexioak huts eginen du, IPv6 konektibitaterik ez bada",
|
||||
"File Pull Order": "Fitxategiak berreskuratzeko ordena",
|
||||
"File Versioning": "Fitxategiak zaintzeko metodoa",
|
||||
|
||||
@@ -122,7 +122,7 @@
|
||||
"External": "Ulkoinen",
|
||||
"External File Versioning": "Ulkoinen tiedostoversionti",
|
||||
"Failed Items": "Epäonnistuneet kohteet",
|
||||
"Failed to setup, retrying": "Käyttöönotto epäonnistui, Yritetään uudelleen",
|
||||
"Failed to set up, retrying": "Käyttöönotto epäonnistui, Yritetään uudelleen",
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "Yhteys IPv6-palvelimiin todennäköisesti epäonnistuu, koska IPv6-yhteyksiä ei ole.",
|
||||
"File Pull Order": "Tiedostojen noutojärjestys",
|
||||
"File Versioning": "Tiedostoversiointi",
|
||||
|
||||
@@ -154,7 +154,7 @@
|
||||
"Failed Items": "Mga Nabigong Item",
|
||||
"Failed to load file versions.": "Nabigong i-load ang mga bersyon ng file.",
|
||||
"Failed to load ignore patterns.": "Nabigong i-load ang mga ignore pattern.",
|
||||
"Failed to setup, retrying": "Nabigong i-set up, sinusubukan muli",
|
||||
"Failed to set up, retrying": "Nabigong i-set up, sinusubukan muli",
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "Inaasahan ang pagbigo sa pagkonekta sa mga IPv6 na server kapag walang konektibidad sa IPv6.",
|
||||
"File Pull Order": "Order ng Pagkuha ng File",
|
||||
"File Versioning": "File Versioning",
|
||||
@@ -403,7 +403,7 @@
|
||||
"Syncthing is Free and Open Source Software licensed as MPL v2.0.": "Ang Syncthing ay Libre at Open Source na Software na nakalisensya sa MPL v2.0.",
|
||||
"Syncthing is a continuous file synchronization program. It synchronizes files between two or more computers in real time, safely protected from prying eyes. Your data is your data alone and you deserve to choose where it is stored, whether it is shared with some third party, and how it's transmitted over the internet.": "Ang Syncthing ay isang continuous na file synchronization na program. Sini-synchronize nito ang mga file sa pagitan ng dalawa o higit pang mga computer sa totoong oras, ligtas na protektado mula sa prying na mata. Ang iyong data ay iyong data at nararapat kang pumili kung saan sila ilalagay, kung binabahagi ito sa third party, at kung paano ito pinapadala sa Internet.",
|
||||
"Syncthing is listening on the following network addresses for connection attempts from other devices:": "Nakikinig ang Syncthing sa mga sumusunod na network address para sa mga tangka sa koneksyon mula sa ibang device:",
|
||||
"Syncthing is not listening for connection attempts from other devices on any address. Only outgoing connections from this device may work.": "Hindi nakikinig ang Syncthing sa mga tangka sa koneksyon mula sa ibang mga device sa anumang address. Ang mga palabas na koneksyon lamang ay maaring gumana.",
|
||||
"Syncthing is not listening for connection attempts from other devices on any address. Only outgoing connections from this device may work.": "Hindi nakikinig ang Syncthing sa mga tangka sa koneksyon mula sa ibang mga device sa anumang address. Ang mga palabas na koneksyon lamang ay maaaring gumana.",
|
||||
"Syncthing is restarting.": "Nagre-restart ang Syncthing.",
|
||||
"Syncthing is saving changes.": "Nagse-save ng mga pagbabago ang Syncthing.",
|
||||
"Syncthing is upgrading.": "Naga-upgrade ang Syncthing.",
|
||||
@@ -417,17 +417,17 @@
|
||||
"The Syncthing Authors": "Ang Mga Awtor ng Syncthing",
|
||||
"The Syncthing admin interface is configured to allow remote access without a password.": "Naka-configure ang Syncthing admin interface na payagan ang remote access nang walang password.",
|
||||
"The aggregated statistics are publicly available at the URL below.": "Available nang publiko ang pinagsama-samang istatistika sa URL sa ibaba.",
|
||||
"The cleanup interval cannot be blank.": "Hindi maaring walang laman ang pagitan ng paglinis.",
|
||||
"The cleanup interval cannot be blank.": "Hindi maaaring walang laman ang pagitan ng paglinis.",
|
||||
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Na-save na ang configuration ngunit hindi naka-activate. Kailangang mag-restart ang Syncthing para i-activate ang bagong configuration.",
|
||||
"The device ID cannot be blank.": "Hindi maaring walang laman ang Device ID.",
|
||||
"The device ID cannot be blank.": "Hindi maaaring walang laman ang Device ID.",
|
||||
"The device ID to enter here can be found in the \"Actions > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "Mahahanap ang device ID na ilalagay dito sa \"Mga Aksyon > Ipakita ang ID\" na dialog sa isa pang device. Opsyonal ang mga puwang at gitling (hindi pinapansin).",
|
||||
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes, and app versions. If the reported data set is changed you will be prompted with this dialog again.": "Araw-araw na pinapadala ang naka-encrypt na ulat sa paggamit. Ginagamit ito sa pag-track ng mga karaniwang platform, laki ng folder, at bersyon ng app. Kapag nabago ang tinakdang data ng ulat ipo-prompt kang muli ng dialog na ito.",
|
||||
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "Mukhang hindi angkop ang inilagay na device ID. Dapat itong 52 o 56 na character na string na binubuo ng mga titik at numero, na may mga puwang at gitling bilang opsyonal.",
|
||||
"The folder ID cannot be blank.": "Hindi maaring walang laman ang folder ID.",
|
||||
"The folder ID cannot be blank.": "Hindi maaaring walang laman ang folder ID.",
|
||||
"The folder ID must be unique.": "Kailangang kakaiba ang folder ID.",
|
||||
"The folder content on other devices will be overwritten to become identical with this device. Files not present here will be deleted on other devices.": "Io-overwrite ang nilalaman ng folder sa mga ibang device para maging magkapareho sa device na ito. Ang mga file na hindi nandito ay buburahin sa mga ibang device.",
|
||||
"The folder content on this device will be overwritten to become identical with other devices. Files newly added here will be deleted.": "Io-overwrite ang nilalaman ng folder sa mga ibang device para maging magkapareho sa device na ito. Ang mga file na kamakilang dinagdag dito ay buburahin sa mga ibang device.",
|
||||
"The folder path cannot be blank.": "Hindi maaring walang laman ang path ng folder.",
|
||||
"The folder path cannot be blank.": "Hindi maaaring walang laman ang path ng folder.",
|
||||
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "Ang mga sumusunod na pagitan ay ginagamit: sa unang oras ang isang bersyon ay pinapanatili bawat 30 segundo, sa unang araw ang isang bersyon ay pinapanatili bawat oras, sa unang 30 araw ang isang bersyon ay pinapanatili bawat araw, hanggang sa pinakamataas na edad ang isang bersyon ay pinapanatili bawat linggo.",
|
||||
"The following items could not be synchronized.": "Hindi ma-synchronize ang mga sumusunod na item.",
|
||||
"The following items were changed locally.": "Binago ng lokal ang mga sumusunod na item.",
|
||||
@@ -436,14 +436,14 @@
|
||||
"The following unexpected items were found.": "Nahanap ang mga sumusunod na hindi inaasahang item.",
|
||||
"The interval must be a positive number of seconds.": "Dapat positibong numero ng segundo ang pagitan.",
|
||||
"The interval, in seconds, for running cleanup in the versions directory. Zero to disable periodic cleaning.": "Ang pagitan, bilang segundo, para sa pagtakbo ng paglinis sa versions na direktoryo. Sero para i-disable ang periodical na paglinis.",
|
||||
"The maximum age must be a number and cannot be blank.": "Dapat numero ang pinakamataas na edad at hindi maaring walang laman.",
|
||||
"The maximum age must be a number and cannot be blank.": "Dapat numero ang pinakamataas na edad at hindi maaaring walang laman.",
|
||||
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Ang pinakamataas na oras para panatilihin ang bersyon (bilang araw, itakda sa 0 para panatilihin ang mga bersyon magpakailanman).",
|
||||
"The number of connections must be a non-negative number.": "Dapat hindi negatibong numero ang bilang ng mga koneksyon.",
|
||||
"The number of days must be a number and cannot be blank.": "Dapat numero ang bilang ng araw at hindi maaring walang laman.",
|
||||
"The number of days must be a number and cannot be blank.": "Dapat numero ang bilang ng araw at hindi maaaring walang laman.",
|
||||
"The number of days to keep files in the trash can. Zero means forever.": "Ang bilang ng araw para panatilihin ang mga file sa basurahan. Ang sero ay ibig sabihin ay magpakailanman.",
|
||||
"The number of old versions to keep, per file.": "Ang bilang ng mga lumang bersyon na dapat panatilihin, bawat file.",
|
||||
"The number of versions must be a number and cannot be blank.": "Dapat numero ang bilang ng mga bersyon at hindi maaring walang laman.",
|
||||
"The path cannot be blank.": "Hindi maaring walang laman ang path.",
|
||||
"The number of versions must be a number and cannot be blank.": "Dapat numero ang bilang ng mga bersyon at hindi maaaring walang laman.",
|
||||
"The path cannot be blank.": "Hindi maaaring walang laman ang path.",
|
||||
"The rate limit is applied to the accumulated traffic of all connections to this device.": "Ina-apply ang rate limit sa naipon na traffic ng lahat ng mga koneksyon sa device na ito.",
|
||||
"The rate limit must be a non-negative number (0: no limit)": "Dapat hindi negatibong numero ang rate limit (0: walang limitasyon)",
|
||||
"The remote device has not accepted sharing this folder.": "Hindi tinanggap ng remote device ang pagbahagi ng folder na ito.",
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"Allowed Networks": "Réseaux autorisés",
|
||||
"Alphabetic": "Alphabétique",
|
||||
"Altered by ignoring deletes.": "Protégé par \"Ignore Delete\".",
|
||||
"Always turned on when the folder type is \"{%foldertype%}\".": "Toujours activé pour le type de partage \"{{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.": "Une commande externe gère les versions de fichiers. Il lui incombe de supprimer les fichiers du répertoire partagé. Si le chemin contient des espaces, il doit être spécifié entre guillemets.",
|
||||
"Anonymous Usage Reporting": "Rapport anonyme de statistiques d'utilisation",
|
||||
"Anonymous usage report format has changed. Would you like to move to the new format?": "Le format du rapport anonyme d'utilisation a changé. Voulez-vous passer au nouveau format ?",
|
||||
@@ -52,6 +53,7 @@
|
||||
"Body:": "Corps du message :",
|
||||
"Bugs": "Bogues",
|
||||
"Cancel": "Annuler",
|
||||
"Cannot be enabled when the folder type is \"{%foldertype%}\".": "Ne peut être activé pour le type de partage \"{{foldertype}}\".",
|
||||
"Changelog": "Historique des versions",
|
||||
"Clean out after": "Conserver pendant",
|
||||
"Cleaning Versions": "Purge des versions",
|
||||
@@ -154,7 +156,7 @@
|
||||
"Failed Items": "Éléments en échec",
|
||||
"Failed to load file versions.": "Échec de chargement des versions de fichiers.",
|
||||
"Failed to load ignore patterns.": "Échec du chargement des masques d'exclusions.",
|
||||
"Failed to setup, retrying": "Échec, nouvel essai",
|
||||
"Failed to set up, retrying": "Échec, nouvel essai",
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "La connexion aux serveurs en IPv6 va échouer s'il n'y a pas de connectivité IPv6.",
|
||||
"File Pull Order": "Ordre de récupération des fichiers",
|
||||
"File Versioning": "Préservation des fichiers",
|
||||
|
||||
@@ -146,7 +146,7 @@
|
||||
"Error": "Flater",
|
||||
"External File Versioning": "Ekstern ferzjebehear foar triemen",
|
||||
"Failed Items": "Mislearre items",
|
||||
"Failed to setup, retrying": "Ynskeakeljen mislearre, wurd no opnij besocht",
|
||||
"Failed to set up, retrying": "Ynskeakeljen mislearre, wurd no opnij besocht",
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "Mislearjen fan it ferbinen mei IPv6-tsjinners wurd ferwachte as der gjin stipe foar IPv6-ferbinings is.",
|
||||
"File Pull Order": "Triemlûkfolchoarder",
|
||||
"File Versioning": "Triemferzjebehear",
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"Allowed Networks": "Líonraí Ceadaithe",
|
||||
"Alphabetic": "Aibítreach",
|
||||
"Altered by ignoring deletes.": "Athraithe trí neamhaird a dhéanamh ar scriosadh.",
|
||||
"Always turned on when the folder type is \"{%foldertype%}\".": "Cuirtear ar siúl i gcónaí é nuair is é \"{{foldertype}}\" an cineál fillteáin.",
|
||||
"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.": "Láimhseálann ordú seachtrach an leagan. Caithfidh sé an comhad a bhaint den fhillteán comhroinnte. Má tá spásanna sa chosán chuig an bhfeidhmchlár, ba chóir é a lua.",
|
||||
"Anonymous Usage Reporting": "Tuairisciú Úsáide Gan Ainm",
|
||||
"Anonymous usage report format has changed. Would you like to move to the new format?": "Tá athrú tagtha ar fhormáid na tuarascála úsáide gan ainm. Ar mhaith leat bogadh go dtí an fhormáid nua?",
|
||||
@@ -52,6 +53,7 @@
|
||||
"Body:": "Comhlacht:",
|
||||
"Bugs": "Fabhtanna",
|
||||
"Cancel": "Cuir ar ceal",
|
||||
"Cannot be enabled when the folder type is \"{%foldertype%}\".": "Ní féidir é a chumasú nuair is é \"{{foldertype}}\" an cineál fillteáin.",
|
||||
"Changelog": "ChangelogName",
|
||||
"Clean out after": "Glan amach tar éis",
|
||||
"Cleaning Versions": "Leaganacha Glantacháin",
|
||||
@@ -154,7 +156,7 @@
|
||||
"Failed Items": "Míreanna Teipthe",
|
||||
"Failed to load file versions.": "Theip ar luchtú leaganacha comhaid.",
|
||||
"Failed to load ignore patterns.": "Theip ar phatrúin neamhairde a luchtú.",
|
||||
"Failed to setup, retrying": "Theip ar thus, ag triail arís",
|
||||
"Failed to set up, retrying": "Theip ar thus, ag triail arís",
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "Táthar ag súil le mainneachtain ceangal le freastalaithe IPv6 mura bhfuil nascacht IPv6 ann.",
|
||||
"File Pull Order": "Ordú Tarraingthe Comhad",
|
||||
"File Versioning": "Leagan Comhaid",
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"About": "Sobre",
|
||||
"Action": "Acción",
|
||||
"Actions": "Accións",
|
||||
"Active filter rules": "Regras de filtrado activas",
|
||||
"Add": "Engadir",
|
||||
"Add Device": "Engadir dispositivo",
|
||||
"Add Folder": "Engadir cartafol",
|
||||
@@ -25,6 +26,7 @@
|
||||
"Allow Anonymous Usage Reporting?": "Permitir o informe de uso anónimo?",
|
||||
"Allowed Networks": "Redes permitidas",
|
||||
"Alphabetic": "Alfabética",
|
||||
"Altered by ignoring deletes.": "Cambiado por ignorar o borrado.",
|
||||
"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.": "Un comando externo xestiona as versións. Ten que eliminar o ficheiro do cartafol compartido. Si a ruta ao aplicativo contén espazos, deberían ir acotados.",
|
||||
"Anonymous Usage Reporting": "Informe anónimo de uso",
|
||||
"Anonymous usage report format has changed. Would you like to move to the new format?": "O formato do informe de uso anónimo cambiou. Quere usar o novo formato?",
|
||||
@@ -37,21 +39,26 @@
|
||||
"Are you sure you want to restore {%count%} files?": "Está seguro de que desexa restaurar {{count}} ficheiros?",
|
||||
"Are you sure you want to revert all local changes?": "Está seguro de que quere reverter todos os cambios locais?",
|
||||
"Are you sure you want to upgrade?": "Está seguro de que desexa actualizar?",
|
||||
"Authentication Required": "Autenticación Necesaria",
|
||||
"Authors": "Autores",
|
||||
"Auto Accept": "Aceptar automaticamente",
|
||||
"Automatic Crash Reporting": "Informe Automático de Erros",
|
||||
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Agora a actualización automática permite escoller entre versións estables e versións candidatas.",
|
||||
"Automatic upgrades": "Actualizacións automáticas",
|
||||
"Automatic upgrades are always enabled for candidate releases.": "As actualizacións automáticas sempre están activadas para versións candidatas.",
|
||||
"Automatically create or share folders that this device advertises at the default path.": "Crear ou compartir automaticamente os cartafoles na ruta predeterminada que este dispositivo anuncia.",
|
||||
"Available debug logging facilities:": "Ferramentas de depuración dispoñibles:",
|
||||
"Be careful!": "Teña coidado!",
|
||||
"Body:": "Corpo:",
|
||||
"Bugs": "Erros",
|
||||
"Cancel": "Cancelar",
|
||||
"Changelog": "Rexistro de cambios",
|
||||
"Clean out after": "Limpar despois",
|
||||
"Cleaning Versions": "Limpando Versións",
|
||||
"Cleanup Interval": "Intervalo de Limpeza",
|
||||
"Click to see full identification string and QR code.": "Faga clic para ver a cadea de identificación completa e o código QR.",
|
||||
"Close": "Pechar",
|
||||
"Command": "Orde",
|
||||
"Comment, when used at the start of a line": "Comentar, cando se usa ao inicio dunha liña",
|
||||
"Compression": "Compresión",
|
||||
"Configuration Directory": "Directorio de Configuración",
|
||||
@@ -69,13 +76,17 @@
|
||||
"Copied!": "Copiado!",
|
||||
"Copy": "Copiar",
|
||||
"Copy failed! Try to select and copy manually.": "Fallou a copia! Probe a seleccionar e copiar manualmente.",
|
||||
"Currently Shared With Devices": "Compartido actualmente cos dispositivos",
|
||||
"Custom Range": "Rango personalizado",
|
||||
"Danger!": "Perigo!",
|
||||
"Database Location": "Localización da Base de Datos",
|
||||
"Debugging Facilities": "Ferramentas de depuración",
|
||||
"Default": "Predeterminado",
|
||||
"Default Configuration": "Configuración Predeterminada",
|
||||
"Default Device": "Dispositivo Predeterminado",
|
||||
"Default Folder": "Cartafol Predeterminado",
|
||||
"Default Ignore Patterns": "Patróns de Ignorado Predeterminados",
|
||||
"Defaults": "Predeterminados",
|
||||
"Delete": "Eliminar",
|
||||
"Delete Unexpected Items": "Eliminar os Ítems Inesperados",
|
||||
"Deleted {%file%}": "Eliminado {{file}}",
|
||||
@@ -89,10 +100,16 @@
|
||||
"Device Identification": "Identificación do dispositivo",
|
||||
"Device Name": "Nome do dispositivo",
|
||||
"Device Status": "Estado do dispositivo",
|
||||
"Device is untrusted, enter encryption password": "Sen confianza no dispositivo, escribir contrasinal para cifrar",
|
||||
"Device rate limits": "Límites do dispositivo",
|
||||
"Device that last modified the item": "Dispositivo que modificou o elemento por última vez",
|
||||
"Devices": "Dispositivos",
|
||||
"Disable Crash Reporting": "Desactivar o Informe de Erros",
|
||||
"Disabled": "Deshabilitado",
|
||||
"Disabled periodic scanning and disabled watching for changes": "Desactivaronse o escaneo periódico e o control de cambios",
|
||||
"Disabled periodic scanning and enabled watching for changes": "Desactivouse o escaneo periódico e activouse o control de cambios",
|
||||
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Desactivouse o escaneo periódico e fallou establecer a vixilancia de cambios, reintento cada minuto:",
|
||||
"Disables comparing and syncing file permissions. Useful on systems with nonexistent or custom permissions (e.g. FAT, exFAT, Synology, Android).": "Desactiva a comparación e sincronización dos permisos dos ficheiros. Útil en sistemas que non teñen ou usan permisos personalizados (ex. FAT, exFAT, Synology, Android).",
|
||||
"Discard": "Descartar",
|
||||
"Disconnected": "Desconectado",
|
||||
"Disconnected (Inactive)": "Desconectado (Inactivo)",
|
||||
@@ -102,7 +119,9 @@
|
||||
"Discovery Failures": "Erros de Descubrimento",
|
||||
"Discovery Status": "Estado do Descubrimento",
|
||||
"Dismiss": "Descartar",
|
||||
"Do not add it to the ignore list, so this notification may recur.": "Non engadilo á lista de ignorados, para que esta notificación poida repetirse.",
|
||||
"Do not restore": "Non restaurar",
|
||||
"Do not restore all": "Non restablecer todo",
|
||||
"Do you want to enable watching for changes for all your folders?": "Quere habilitar o control de cambios para todos os seus cartafois?",
|
||||
"Documentation": "Documentación",
|
||||
"Download Rate": "Velocidade de Descarga",
|
||||
@@ -110,11 +129,17 @@
|
||||
"Downloading": "Descargando",
|
||||
"Edit": "Editar",
|
||||
"Edit Device": "Editar o Dispositivo",
|
||||
"Edit Device Defaults": "Editar predeterminados do dispositivo",
|
||||
"Edit Folder": "Editar o Cartafol",
|
||||
"Edit Folder Defaults": "Editar predeterminados dos cartafoles",
|
||||
"Editing {%path%}.": "Editando {{path}}.",
|
||||
"Enable Crash Reporting": "Activar informar dos fallos",
|
||||
"Enable NAT traversal": "Habilitar o NAT traversal",
|
||||
"Enable Relaying": "Habilitar Relevos",
|
||||
"Enabled": "Habilitado",
|
||||
"Enables sending extended attributes to other devices, and applying incoming extended attributes. May require running with elevated privileges.": "Activa o envío de atributos extendidos a outros dispositivos, e aplicar os atributos extendidos recibidos. Podería requerir a execución con privilexios elevados.",
|
||||
"Enables sending extended attributes to other devices, but not applying incoming extended attributes. This can have a significant performance impact. Always enabled when \"Sync Extended Attributes\" is enabled.": "Activa o envío de atributos extendidos a outros dispositivos, pero non aplica atributos extendidos que se reciben. Isto podería afectar significativamente ao rendemento. Sempre está activado cando «Sincr Atributos Extendidos\" está activado.",
|
||||
"Enables sending ownership information to other devices, and applying incoming ownership information. Typically requires running with elevated privileges.": "Activa o envío de información sobre a propiedade a outros dispositivos, e aplica a información sobre a propiedade cando se recibe. Normalmente require a execución con privilexios elevados.",
|
||||
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Introduza un número non negativo (por exemplo, \"2.35\") e seleccione unha unidade. As porcentaxes son como partes totais do tamaño do disco.",
|
||||
"Enter a non-privileged port number (1024 - 65535).": "Introduza un número de porto non privilexiado (1024-65535).",
|
||||
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Introduza direccións separadas por comas (\"tcp://ip:porto\", \"tcp://host:porto\") ou \"dynamic\" para realizar o descubrimento automático da dirección.",
|
||||
@@ -125,8 +150,14 @@
|
||||
"Extended Attributes Filter": "Filtro de Atributos Estendidos",
|
||||
"External": "Externo",
|
||||
"External File Versioning": "Versionado de Fichiro Externo",
|
||||
"Failed Items": "Elmentos fallados",
|
||||
"Failed to load file versions.": "Fallou a carga das versións dos ficheiros.",
|
||||
"Failed to load ignore patterns.": "Fallou a carga de patróns ignorados.",
|
||||
"Failed to set up, retrying": "Fallou a configuración, reintentando",
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "É de agardar o fallo ao conectar con servidores IPv6 se non hai conexión por IPv6.",
|
||||
"File Pull Order": "Orde de Obtención de Arquivos",
|
||||
"File Versioning": "Versionado de Ficheiros",
|
||||
"Files are moved to .stversions directory when replaced or deleted by Syncthing.": "Os ficheiros móvense ao directorio .stversions cando se substitúen ou eleminan con Syncthing.",
|
||||
"Filter by date": "FIltrar por data",
|
||||
"Filter by name": "Filtrar por nome",
|
||||
"Folder": "Cartafol",
|
||||
|
||||
@@ -1,114 +1,555 @@
|
||||
{
|
||||
"A device with that ID is already added.": "כבר נוסף התקן עם המזהה הזה.",
|
||||
"A negative number of days doesn't make sense.": "מספר שלילי של ימים אינו הגיוני.",
|
||||
"A new major version may not be compatible with previous versions.": "ייתכן שגרסה עיקרית חדשה לא תהיה תואמת לגרסאות קודמות.",
|
||||
"API Key": "מפתח API",
|
||||
"About": "על אודות",
|
||||
"About": "אודות",
|
||||
"Action": "פעולה",
|
||||
"Actions": "פעולות",
|
||||
"Add": "הוספה",
|
||||
"Add Device": "הוספת התקן",
|
||||
"Add Folder": "הוספת תיקיה",
|
||||
"Add Remote Device": "הוספת התקן מרוחק",
|
||||
"Add new folder?": "להוסיף תיקיה חדשה?",
|
||||
"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, כלומר ברירת מחדל חדשה של שעה). אתה יכול גם להגדיר אותו ידנית עבור כל תיקייה מאוחר יותר לאחר שבחרת 'לא'.",
|
||||
"Address": "כתובת",
|
||||
"Addresses": "כתובות",
|
||||
"Advanced": "מתקדם",
|
||||
"Advanced Configuration": "הגדרת תצורה מתקדמת",
|
||||
"Advanced Configuration": "תצורה מתקדמת",
|
||||
"All Data": "כל הנתונים",
|
||||
"Allow Anonymous Usage Reporting?": "לאפשר דיווח שימוש בעילום שם?",
|
||||
"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": "אלפאבתי",
|
||||
"Anonymous Usage Reporting": "דיווח שימוש בעילום שם",
|
||||
"Are you sure you want to restore {%count%} files?": "האם אכן ברצונך לשחזר {{count}} קבצים?",
|
||||
"Are you sure you want to upgrade?": "האם אכן ברצונך לשדרג?",
|
||||
"Alphabetic": "אלפביתי",
|
||||
"Altered by ignoring deletes.": "השתנה על ידי התעלמות ממחיקות.",
|
||||
"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": "הוחל על LAN",
|
||||
"Apply": "החל",
|
||||
"Are you sure you want to override all remote changes?": "האם אתה בטוח שברצונך לדרוס את כל השינויים המרוחקים?",
|
||||
"Are you sure you want to permanently delete all these files?": "האם אתה בטוח שברצונך למחוק לצמיתות את כל הקבצים האלה?",
|
||||
"Are you sure you want to remove device {%name%}?": "האם אתה בטוח שברצונך להסיר את ההתקן {{name}}?",
|
||||
"Are you sure you want to remove folder {%label%}?": "האם אתה בטוח שברצונך להסיר את התיקייה {{label}}?",
|
||||
"Are you sure you want to restore {%count%} files?": "האם אתה בטוח שברצונך לשחזר {{count}} קבצים?",
|
||||
"Are you sure you want to revert all local changes?": "האם אתה בטוח שברצונך לבטל את כל השינויים המקומיים?",
|
||||
"Are you sure you want to upgrade?": "האם אתה בטוח שברצונך לשדרג?",
|
||||
"Authentication Required": "נדרש אימות",
|
||||
"Authors": "מחברים",
|
||||
"Auto Accept": "הסכמה אוטומטית",
|
||||
"Automatic upgrades": "שדרוג אוטומטי",
|
||||
"Automatic Crash Reporting": "דיווח קריסה אוטומטי",
|
||||
"Automatic upgrade now offers the choice between stable releases and release candidates.": "שדרוג אוטומטי עכשיו מציע את הבחירה בין שחרורים יציבים ומועמדים לשחרור.",
|
||||
"Automatic upgrades": "שדרוגים אוטומטיים",
|
||||
"Automatic upgrades are always enabled for candidate releases.": "שדרוגים אוטומטיים תמיד מופעלים עבור מועמדים לשחרור.",
|
||||
"Automatically create or share folders that this device advertises at the default path.": "צור או שתף באופן אוטומטי תיקיות שההתקן הזה מפרסם בנתיב ברירת המחדל.",
|
||||
"Available debug logging facilities:": "מתקני רישום דיבג זמינים:",
|
||||
"Be careful!": "זהירות!",
|
||||
"Bugs": "תקלות",
|
||||
"Changelog": "רשימת שינויים",
|
||||
"Close": "סגירה",
|
||||
"Body:": "גוף:",
|
||||
"Bugs": "באגים",
|
||||
"Cancel": "ביטול",
|
||||
"Changelog": "יומן שינויים",
|
||||
"Clean out after": "נקה לאחר",
|
||||
"Cleaning Versions": "מנקה גרסאות",
|
||||
"Cleanup Interval": "פרק זמן לניקוי",
|
||||
"Click to see full identification string and QR code.": "לחץ כדי לראות מחרוזת זיהוי מלאה וקוד QR.",
|
||||
"Close": "סגור",
|
||||
"Command": "פקודה",
|
||||
"Comment, when used at the start of a line": "הערה, כאשר משתמשים בה בתחילת שורה",
|
||||
"Compression": "דחיסה",
|
||||
"Connection Error": "תקלת חיבור",
|
||||
"Configuration Directory": "תיקיית תצורה",
|
||||
"Configuration File": "קובץ תצורה",
|
||||
"Configured": "מוגדר",
|
||||
"Connected (Unused)": "מחובר (לא בשימוש)",
|
||||
"Connection Error": "שגיאת חיבור",
|
||||
"Connection Management": "ניהול חיבורים",
|
||||
"Connection Type": "סוג חיבור",
|
||||
"Connections": "חיבורים",
|
||||
"Connections via relays might be rate limited by the relay": "חיבורים דרך ממסרים עשויים להיות מוגבלים בקצב על ידי הממסר",
|
||||
"Continuously watching for changes is now available within Syncthing. This will detect changes on disk and issue a scan on only the modified paths. The benefits are that changes are propagated quicker and that less full scans are required.": "מעקב רציף אחר שינויים זמין כעת בתוך Syncthing. זה יגלה שינויים בדיסק ויוציא סריקה רק על הנתיבים שהשתנו. היתרונות הן ששינויים מופצים מהר יותר ושנדרשות פחות סריקות מלאות.",
|
||||
"Copied from elsewhere": "הועתק ממקום אחר",
|
||||
"Copied from original": "הועתק מהמקור",
|
||||
"Copied!": "הועתק!",
|
||||
"Copy": "העתק",
|
||||
"Copy failed! Try to select and copy manually.": "העתקה נכשלה! נסה לבחור ולהעתיק ידנית.",
|
||||
"Currently Shared With Devices": "כרגע משותף עם התקנים",
|
||||
"Custom Range": "טווח מותאם אישית",
|
||||
"Danger!": "סכנה!",
|
||||
"Database Location": "מיקום מסד נתונים",
|
||||
"Debugging Facilities": "מתקני דיבוג",
|
||||
"Default": "ברירת מחדל",
|
||||
"Default Configuration": "תצורת ברירת מחדל",
|
||||
"Default Device": "התקן ברירת מחדל",
|
||||
"Default Folder": "תיקייה ברירת מחדל",
|
||||
"Default Ignore Patterns": "דפוסי התעלמות ברירת מחדל",
|
||||
"Defaults": "ברירות מחדל",
|
||||
"Delete": "מחק",
|
||||
"Delete Unexpected Items": "מחק פריטים בלתי צפויים",
|
||||
"Deleted {%file%}": "{{file}} נמחק",
|
||||
"Deselect All": "בטל את הבחירה בהכל",
|
||||
"Deselect devices to stop sharing this folder with.": "בטל בחירת התקנים כדי להפסיק שיתוף תיקייה זו.",
|
||||
"Deselect folders to stop sharing with this device.": "בטל בחירת תיקיות להפסקת השיתוף עם התקן זה.",
|
||||
"Device": "התקן",
|
||||
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "התקן \"{{name}}\" ({{device}} ב־{{address}}) רוצה להתחבר. להוסיף התקן חדש?",
|
||||
"Device Certificate": "תעודת התקן",
|
||||
"Device ID": "מזהה התקן",
|
||||
"Device Identification": "מזהה התקן",
|
||||
"Device Identification": "זיהוי התקן",
|
||||
"Device Name": "שם התקן",
|
||||
"Device Status": "מצב התקן",
|
||||
"Device is untrusted, enter encryption password": "ההתקן אינו מהימן, הזן סיסמת הצפנה",
|
||||
"Device rate limits": "מגבלות קצב התקן",
|
||||
"Device that last modified the item": "ההתקן ששינה את הפריט בפעם האחרונה",
|
||||
"Devices": "התקנים",
|
||||
"Disable Crash Reporting": "השבת דיווח קריסה",
|
||||
"Disabled": "מושבת",
|
||||
"Disabled periodic scanning and disabled watching for changes": "סריקה תקופתית הושבתה ומעקב אחר שינויים הושבת",
|
||||
"Disabled periodic scanning and enabled watching for changes": "סריקה תקופתית הושבתה ומעקב אחר שינויים הופעל",
|
||||
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "סריקה תקופתית הושבתה והגדרת מעקב אחר שינויים נכשלה, מנסה שוב כל דקה:",
|
||||
"Disables comparing and syncing file permissions. Useful on systems with nonexistent or custom permissions (e.g. FAT, exFAT, Synology, Android).": "משבית השוואה וסנכרון של הרשאות קובץ. שימושי במערכות עם הרשאות שאינן קיימות או מותאמות אישית (למשל Android, Synology, exFAT, FAT).",
|
||||
"Discard": "השלך",
|
||||
"Disconnected": "מנותק",
|
||||
"Disconnected (Inactive)": "מנותק (לא פעיל)",
|
||||
"Disconnected (Unused)": "מנותק (לא בשימוש)",
|
||||
"Discovered": "התגלה",
|
||||
"Discovery": "גילוי",
|
||||
"Discovery Failures": "כשלי גילוי",
|
||||
"Discovery Status": "מצב גילוי",
|
||||
"Dismiss": "דחה",
|
||||
"Do not add it to the ignore list, so this notification may recur.": "אל תוסיף אותו לרשימת ההתעלמות, כך שהתראה זו עשויה לחזור על עצמה.",
|
||||
"Do not restore": "אל תשחזר",
|
||||
"Do not restore all": "אל תשחזר הכל",
|
||||
"Do you want to enable watching for changes for all your folders?": "האם ברצונך להפעיל מעקב אחר שינויים עבור כל התיקיות שלך?",
|
||||
"Documentation": "תיעוד",
|
||||
"Download Rate": "קצב הורדה",
|
||||
"Edit": "עריכה",
|
||||
"Edit Device": "עריכת התקן",
|
||||
"Edit Folder": "עריכה תיקיה",
|
||||
"Downloaded": "הורד",
|
||||
"Downloading": "מוריד",
|
||||
"Edit": "ערוך",
|
||||
"Edit Device": "ערוך התקן",
|
||||
"Edit Device Defaults": "ערוך ברירות מחדל של התקן",
|
||||
"Edit Folder": "ערוך תיקייה",
|
||||
"Edit Folder Defaults": "ערוך ברירות מחדל של תיקייה",
|
||||
"Editing {%path%}.": "עורך {{path}}.",
|
||||
"Enable Crash Reporting": "הפעל דיווח קריסה",
|
||||
"Enable NAT traversal": "הפעל חציית NAT",
|
||||
"Enable Relaying": "הפעל ממסור",
|
||||
"Enabled": "מופעל",
|
||||
"Enables sending extended attributes to other devices, and applying incoming extended attributes. May require running with elevated privileges.": "מאפשר שליחת תכונות מורחבות להתקנים אחרים, ומחיל תכונות מורחבות נכנסות. עשוי לדרוש הרצה עם הרשאות גבוהות.",
|
||||
"Enables sending extended attributes to other devices, but not applying incoming extended attributes. This can have a significant performance impact. Always enabled when \"Sync Extended Attributes\" is enabled.": "מאפשר שליחת תכונות מורחבות להתקנים אחרים, אבל לא מחיל תכונות מורחבות נכנסות. זה יכול להשפיע משמעותית על הביצועים. תמיד מופעל כאשר \"סנכרון תכונות מורחבות\" מופעל.",
|
||||
"Enables sending ownership information to other devices, and applying incoming ownership information. Typically requires running with elevated privileges.": "מאפשר שליחת מידע על בעלות להתקנים אחרים, ומחיל מידע על בעלות נכנס. בדרך כלל דורש הרצה עם הרשאות גבוהות.",
|
||||
"Enables sending ownership information to other devices, but not applying incoming ownership information. This can have a significant performance impact. Always enabled when \"Sync Ownership\" is enabled.": "מאפשר שליחת מידע על בעלות להתקנים אחרים, אבל לא מחיל מידע על בעלות נכנס. זה יכול להשפיע משמעותית על הביצועים. תמיד מופעל כאשר \"סנכרון בעלות\" מופעל.",
|
||||
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "הזן מספר לא שלילי (למשל, \"2.35\") ובחר יחידה. אחוזים הם כחלק מגודל הדיסק הכולל.",
|
||||
"Enter a non-privileged port number (1024 - 65535).": "הזן מספר יציאה לא מיוחס (1024-65535).",
|
||||
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "הזן כתובות מופרדות בפסיק (\"tcp://ip:port\", \"tcp://host:port\") או \"dynamic\" כדי לבצע גילוי אוטומטי של הכתובת.",
|
||||
"Enter ignore patterns, one per line.": "הזן דפוסי התעלמות, אחד בכל שורה.",
|
||||
"Enter up to three octal digits.": "הזן עד שלושה ספרות אוקטליות.",
|
||||
"Error": "שגיאה",
|
||||
"Folder": "תיקיה",
|
||||
"Folder ID": "מזהה תיקיה",
|
||||
"Folder Label": "כותרת תיקיה",
|
||||
"Folder Path": "נתיב תיקיה",
|
||||
"Folder Type": "סוג תיקיה",
|
||||
"Extended Attributes": "תכונות מורחבות",
|
||||
"Extended Attributes Filter": "מסנן תכונות מורחבות",
|
||||
"External": "חיצוני",
|
||||
"External File Versioning": "ניהול גרסאות קבצים חיצוני",
|
||||
"Failed Items": "פריטים שנכשלו",
|
||||
"Failed to load file versions.": "טעינת גרסאות קבצים נכשלה.",
|
||||
"Failed to load ignore patterns.": "טעינת דפוסי התעלמות נכשלה.",
|
||||
"Failed to set up, retrying": "ההגדרה נכשלה, מנסה שוב",
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "צפוי כשל בהתחברות לשרתי IPv6 אם אין קישוריות IPv6.",
|
||||
"File Pull Order": "סדר משיכת קבצים",
|
||||
"File Versioning": "ניהול גרסאות קבצים",
|
||||
"Files are moved to .stversions directory when replaced or deleted by Syncthing.": "הקבצים מועברים לתיקיית stversions. כאשר הם מוחלפים או נמחקים על ידי Syncthing.",
|
||||
"Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.": "הקבצים מועברים לגרסאות עם חותמת תאריך בתיקיית stversions. כאשר הם מוחלפים או נמחקים על ידי Syncthing.",
|
||||
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "הקבצים מוגנים מפני שינויים שנעשו בהתקנים אחרים, אבל שינויים שנעשו בהתקן זה יישלחו לשאר האשכול.",
|
||||
"Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.": "הקבצים מסונכרנים מהאשכול, אבל כל שינוי שנעשה מקומית לא יישלח להתקנים אחרים.",
|
||||
"Filesystem Watcher Errors": "שגיאות משגיח מערכת קבצים",
|
||||
"Filter by date": "סנן לפי תאריך",
|
||||
"Filter by name": "סנן לפי שם",
|
||||
"Folder": "תיקייה",
|
||||
"Folder ID": "מזהה תיקייה",
|
||||
"Folder Label": "תווית תיקייה",
|
||||
"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}}\" לאחר הוספת התיקייה. אתה צריך להסיר את התיקייה, למחוק או לפענח את הנתונים בדיסק, ולהוסיף את התיקייה שוב.",
|
||||
"Folders": "תיקיות",
|
||||
"GUI": "ממשק משתמש",
|
||||
"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": "לנצח",
|
||||
"Full Rescan Interval (s)": "פרק זמן לסריקה מלאה מחדש (שנ')",
|
||||
"GUI": "ממשק משתמש גרפי",
|
||||
"GUI / API HTTPS Certificate": "תעודת HTTPS עבור ממשק גרפי / תכנותי",
|
||||
"GUI Authentication Password": "סיסמת אימות ממשק גרפי",
|
||||
"GUI Authentication User": "משתמש אימות ממשק גרפי",
|
||||
"GUI Authentication: Set User and Password": "אימות ממשק גרפי: הגדר משתמש וסיסמה",
|
||||
"GUI Listen Address": "כתובת האזנה של ממשק גרפי",
|
||||
"GUI Override Directory": "תיקיית מעקף ממשק גרפי",
|
||||
"GUI Theme": "ערכת נושא של ממשק גרפי",
|
||||
"General": "כללי",
|
||||
"Generate": "צור",
|
||||
"Global Discovery": "גילוי גלובלי",
|
||||
"Global Discovery Servers": "שרתי גילוי גלובלי",
|
||||
"Global State": "מצב גלובלי",
|
||||
"Help": "עזרה",
|
||||
"Hint: only deny-rules detected while the default is deny. Consider adding \"permit any\" as last rule.": "רמז: רק כללי שלילה מזוהים כאשר ברירת המחדל היא לשלול. שקול להוסיף \"התר כל\" ככלל אחרון.",
|
||||
"Home page": "דף הבית",
|
||||
"However, your current settings indicate you might not want it enabled. We have disabled automatic crash reporting for you.": "עם זאת, ההגדרות הנוכחיות שלך מצביעות על כך שאתה אולי לא רוצה את זה מופעל. השבתנו דיווח קריסה אוטומטי עבורך.",
|
||||
"Identification": "זיהוי",
|
||||
"If untrusted, enter encryption password": "אם לא מהימן, הזן סיסמת הצפנה",
|
||||
"If you want to prevent other users on this computer from accessing Syncthing and through it your files, consider setting up authentication.": "אם אתה רוצה למנוע ממשתמשים אחרים במחשב זה לגשת אל Syncthing ודרך כך לקבצים שלך, שקול להגדיר אימות.",
|
||||
"Ignore": "התעלמות",
|
||||
"Ignore Permissions": "התעלמות מהרשאות",
|
||||
"Keep Versions": "שמירת גרסאות",
|
||||
"Ignore Patterns": "דפוסי התעלמות",
|
||||
"Ignore Permissions": "התעלם מהרשאות",
|
||||
"Ignore patterns can only be added after the folder is created. If checked, an input field to enter ignore patterns will be presented after saving.": "ניתן להוסיף דפוסי התעלמות רק לאחר יצירת התיקייה. אם מסומן, שדה קלט להזנת דפוסי התעלמות יוצג לאחר השמירה.",
|
||||
"Ignored Devices": "התקנים בהתעלמות",
|
||||
"Ignored Folders": "תיקיות בהתעלמות",
|
||||
"Ignored at": "בהתעלמות אצל",
|
||||
"Included Software": "תוכנות כלולות",
|
||||
"Incoming Rate Limit (KiB/s)": "מגבלת קצב נכנס (KiB/s)",
|
||||
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "תצורה שגויה עלולה לגרום נזק לתכולת התיקייה שלך ולהפוך את Syncthing לבלתי ניתן להפעלה.",
|
||||
"Incorrect user name or password.": "שם משתמש או סיסמה שגויים.",
|
||||
"Internally used paths:": "נתיבים בשימוש פנימי:",
|
||||
"Introduced By": "הוצג על ידי",
|
||||
"Introducer": "מציג",
|
||||
"Introduction": "מבוא",
|
||||
"Inversion of the given condition (i.e. do not exclude)": "היפוך של התנאי הנתון (כלומר אל תחריג)",
|
||||
"Keep Versions": "שמור גרסאות",
|
||||
"LDAP": "LDAP",
|
||||
"Learn more": "למידע נוסף",
|
||||
"Loading...": "כעת בטעינה...",
|
||||
"Largest First": "הגדול ביותר תחילה",
|
||||
"Last 30 Days": "30 הימים האחרונים",
|
||||
"Last 7 Days": "7 הימים האחרונים",
|
||||
"Last Month": "החודש האחרון",
|
||||
"Last Scan": "הסריקה האחרונה",
|
||||
"Last seen": "נראה לאחרונה",
|
||||
"Latest Change": "השינוי העדכני ביותר",
|
||||
"Learn more": "למד עוד",
|
||||
"Learn more at {%url%}": "למד עוד באתר {{url}}",
|
||||
"Limit": "מגבלה",
|
||||
"Listener Failures": "כשלי מאזין",
|
||||
"Listener Status": "מצב מאזין",
|
||||
"Listeners": "מאזינים",
|
||||
"Loading data...": "טוען נתונים...",
|
||||
"Loading...": "טוען...",
|
||||
"Local Additions": "הוספות מקומיות",
|
||||
"Local Discovery": "גילוי מקומי",
|
||||
"Local State": "מצב מקומי",
|
||||
"Local State (Total)": "מצב מקומי (סה\"כ)",
|
||||
"Locally Changed Items": "פריטים שהשתנו מקומית",
|
||||
"Log": "יומן",
|
||||
"Move to top of queue": "העברה לראש הרשימה",
|
||||
"Log File": "קובץ יומן",
|
||||
"Log In": "היכנס",
|
||||
"Log Out": "צא",
|
||||
"Log in to see paths information.": "היכנס כדי לראות מידע על נתיבים.",
|
||||
"Log in to see version information.": "היכנס כדי לראות מידע על הגרסה.",
|
||||
"Log tailing paused. Scroll to the bottom to continue.": "מעקב יומן הושהה. גלול לתחתית כדי להמשיך.",
|
||||
"Login failed, see Syncthing logs for details.": "הכניסה נכשלה, ראה יומני Syncthing לפרטים.",
|
||||
"Logs": "יומנים",
|
||||
"Major Upgrade": "שדרוג עיקרי",
|
||||
"Mass actions": "פעולות המוניות",
|
||||
"Maximum Age": "גיל מרבי",
|
||||
"Maximum single entry size": "גודל ערך יחיד מרבי",
|
||||
"Maximum total size": "גודל כולל מרבי",
|
||||
"Metadata Only": "מטא-נתונים בלבד",
|
||||
"Minimum Free Disk Space": "שטח דיסק פנוי מזערי",
|
||||
"Mod. Device": "שנה התקן",
|
||||
"Mod. Time": "זמן שינוי",
|
||||
"More than a month ago": "לפני יותר מחודש",
|
||||
"More than a week ago": "לפני יותר משבוע",
|
||||
"More than a year ago": "לפני יותר משנה",
|
||||
"Move to top of queue": "העבר לראש התור",
|
||||
"Multi level wildcard (matches multiple directory levels)": "תו כללי רב רמות (תואם רמות תיקייה מרובות)",
|
||||
"Never": "לעולם לא",
|
||||
"New Device": "התקן חדש",
|
||||
"New Folder": "תיקיה חדשה",
|
||||
"New Folder": "תיקייה חדשה",
|
||||
"Newest First": "החדש ביותר תחילה",
|
||||
"No": "לא",
|
||||
"No File Versioning": "אין ניהול גרסאות קבצים",
|
||||
"No files will be deleted as a result of this operation.": "לא יימחקו קבצים כתוצאה מפעולה זו.",
|
||||
"No rules set": "לא נקבעו כללים",
|
||||
"No upgrades": "אין שדרוגים",
|
||||
"Not shared": "לא בשיתוף",
|
||||
"Notice": "הודעה",
|
||||
"Number of Connections": "מספר חיבורים",
|
||||
"OK": "אישור",
|
||||
"Off": "כבוי",
|
||||
"Oldest First": "הישן ביותר תחילה",
|
||||
"Optional descriptive label for the folder. Can be different on each device.": "תווית תיאורית אופציונלית עבור התיקייה. יכולה להיות שונה בכל התקן.",
|
||||
"Options": "אפשרויות",
|
||||
"Out of Sync": "לא מסונכרן",
|
||||
"Out of Sync Items": "פריטים לא מסונכרנים",
|
||||
"Outgoing Rate Limit (KiB/s)": "מגבלת קצב יוצא (KiB/s)",
|
||||
"Override": "דריסה",
|
||||
"Override Changes": "דרוס שינויים",
|
||||
"Ownership": "בעלות",
|
||||
"Password": "סיסמה",
|
||||
"Path": "נתיב",
|
||||
"Pause": "השהיה",
|
||||
"Pause All": "להשהות הכול",
|
||||
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "נתיב לתיקייה במחשב המקומי. תיווצר אם אינה קיימת. תו הטילדה (~) יכול לשמש כקיצור דרך עבור",
|
||||
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "הנתיב שבו יש לאחסן גרסאות (השאר ריק עבור תיקיית stversion. ברירת המחדל בתיקייה המשותפת).",
|
||||
"Paths": "נתיבים",
|
||||
"Pause": "השהה",
|
||||
"Pause All": "השהה הכל",
|
||||
"Paused": "מושהה",
|
||||
"Paused (Unused)": "מושהה (לא בשימוש)",
|
||||
"Pending changes": "שינויים ממתינים",
|
||||
"Periodic scanning at given interval and disabled watching for changes": "סריקה תקופתית בפרק זמן שניתן ומעקב אחר שינויים הושבת",
|
||||
"Periodic scanning at given interval and enabled watching for changes": "סריקה תקופתית בפרק זמן שניתן ומעקב אחר שינויים הופעל",
|
||||
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "סריקה תקופתית בפרק זמן שניתן והגדרת מעקב אחר שינויים נכשלה, מנסה שוב כל דקה:",
|
||||
"Permanently add it to the ignore list, suppressing further notifications.": "הוסף אותו לצמיתות לרשימת ההתעלמות, תוך העלמת התראות נוספות.",
|
||||
"Please consult the release notes before performing a major upgrade.": "נא לבדוק את הערות השחרור לפני ביצוע שדרוג עיקרי.",
|
||||
"Please set a GUI Authentication User and Password in the Settings dialog.": "נא להגדיר משתמש וסיסמה לאימות ממשק גרפי בשיח ההגדרות.",
|
||||
"Please wait": "נא להמתין",
|
||||
"Prefix indicating that the file can be deleted if preventing directory removal": "קידומת המציינת כי הקובץ ניתן למחיקה אם מונעים הסרת תיקייה",
|
||||
"Prefix indicating that the pattern should be matched without case sensitivity": "קידומת המציינת כי יש להתאים את הדפוס ללא רגישות לרישיות",
|
||||
"Preparing to Sync": "מתכונן לסנכרון",
|
||||
"Preview": "תצוגה מקדימה",
|
||||
"Preview Usage Report": "תצוגה מקדימה של דוח שימוש",
|
||||
"QR code": "קוד QR",
|
||||
"QUIC LAN": "QUIC LAN",
|
||||
"QUIC WAN": "QUIC WAN",
|
||||
"Quick guide to supported patterns": "מדריך מהיר לדפוסים נתמכים",
|
||||
"Random": "אקראי",
|
||||
"Receive Encrypted": "קבלה מוצפנת",
|
||||
"Receive Only": "קבלה בלבד",
|
||||
"Received data is already encrypted": "הנתונים שהתקבלו כבר מוצפנים",
|
||||
"Recent Changes": "שינויים אחרונים",
|
||||
"Reduced by ignore patterns": "הופחת על ידי דפוסי התעלמות",
|
||||
"Relay LAN": "ממסר LAN",
|
||||
"Relay WAN": "ממסר WAN",
|
||||
"Release Notes": "הערות שחרור",
|
||||
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "מועמדים לשחרור מכילים את התכונות והתיקונים העדכניים ביותר. הם דומים לשחרורים הדו־שבועיים המסורתיים של Syncthing.",
|
||||
"Remote Devices": "התקנים מרוחקים",
|
||||
"Remove": "הסרה",
|
||||
"Remote GUI": "ממשק גרפי מרוחק",
|
||||
"Remove": "הסר",
|
||||
"Remove Device": "הסר התקן",
|
||||
"Remove Folder": "הסר תיקייה",
|
||||
"Required identifier for the folder. Must be the same on all cluster devices.": "נדרש מזהה עבור התיקייה. חייב להיות אותו הדבר בכל התקני האשכול.",
|
||||
"Rescan": "סריקה מחדש",
|
||||
"Rescan All": "לסרוק הכול",
|
||||
"Save": "שמירה",
|
||||
"Scanning": "כעת בסריקה",
|
||||
"Send & Receive": "שליחה וקבלה",
|
||||
"Rescan All": "סרוק הכל מחדש",
|
||||
"Rescans": "סריקות מחדש",
|
||||
"Restart": "הפעלה מחדש",
|
||||
"Restart Needed": "נדרשת הפעלה מחדש",
|
||||
"Restarting": "מפעיל מחדש",
|
||||
"Restore": "שחזר",
|
||||
"Restore Versions": "שחזר גרסאות",
|
||||
"Resume": "המשך",
|
||||
"Resume All": "המשך הכל",
|
||||
"Reused": "בשימוש חוזר",
|
||||
"Revert": "בטל",
|
||||
"Revert Local Changes": "בטל שינויים מקומיים",
|
||||
"Save": "שמור",
|
||||
"Saving changes": "שומר שינויים",
|
||||
"Scan Time Remaining": "זמן הסריקה שנותר",
|
||||
"Scanning": "סורק",
|
||||
"See external versioning help for supported templated command line parameters.": "ראה עזרה בנושא ניהול גרסאות חיצוני עבור פרמטרים תבניתיים הנתמכים בשורת הפקודה.",
|
||||
"Select All": "בחר הכל",
|
||||
"Select a version": "בחר גרסה",
|
||||
"Select additional devices to share this folder with.": "בחר התקנים נוספים לשיתוף תיקייה זו.",
|
||||
"Select additional folders to share with this device.": "בחר תיקיות נוספות לשיתוף עם התקן זה.",
|
||||
"Select latest version": "בחר את הגרסה העדכנית ביותר",
|
||||
"Select oldest version": "בחר את הגרסה הישנה ביותר",
|
||||
"Send & Receive": "שליחה & קבלה",
|
||||
"Send Extended Attributes": "שלח תכונות מורחבות",
|
||||
"Send Only": "שליחה בלבד",
|
||||
"Share": "שיתוף",
|
||||
"Share Folder": "שיתוף תיקיה",
|
||||
"Share this folder?": "לשתף תיקיה זו?",
|
||||
"Send Ownership": "שלח בעלות",
|
||||
"Set Ignores on Added Folder": "הגדר התעלמויות על התיקייה שנוספה",
|
||||
"Settings": "הגדרות",
|
||||
"Share": "שתף",
|
||||
"Share Folder": "שתף תיקייה",
|
||||
"Share by Email": "שתף באמצעות דוא\"ל",
|
||||
"Share by SMS": "שתף באמצעות מסרון",
|
||||
"Share this folder?": "לשתף תיקייה זו?",
|
||||
"Shared Folders": "תיקיות משותפות",
|
||||
"Shared With": "משותף עם",
|
||||
"Show ID": "הצגת מזהה",
|
||||
"Sharing": "שיתוף",
|
||||
"Show ID": "הצג מזהה",
|
||||
"Show QR": "הצג QR",
|
||||
"Show detailed discovery status": "הצג מצב גילוי מפורט",
|
||||
"Show detailed listener status": "הצג מצב מאזין מפורט",
|
||||
"Show diff with previous version": "הצג הבדל עם הגרסה הקודמת",
|
||||
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "מוצג במקום מזהה ההתקן במצב האשכול. יפורסם להתקנים אחרים כשם ברירת מחדל אופציונלי.",
|
||||
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "מוצג במקום מזהה ההתקן במצב האשכול. יעודכן לשם שההתקן מפרסם אם נותר ריק.",
|
||||
"Shutdown": "כיבוי",
|
||||
"Shutdown Complete": "הכיבוי הושלם",
|
||||
"Simple": "פשוט",
|
||||
"Simple File Versioning": "ניהול גרסאות קבצים פשוט",
|
||||
"Single level wildcard (matches within a directory only)": "תו כללי ברמה אחת (תואם בתוך תיקייה בלבד)",
|
||||
"Size": "גודל",
|
||||
"Smallest First": "הקטן ביותר תחילה",
|
||||
"Some discovery methods could not be established for finding other devices or announcing this device:": "לא היה ניתן להקים כמה שיטות גילוי למציאת התקנים אחרים או הכרזת התקן זה:",
|
||||
"Some items could not be restored:": "לא היה ניתן לשחזר כמה פריטים:",
|
||||
"Some listening addresses could not be enabled to accept connections:": "לא היה ניתן להפעיל כמה כתובות האזנה לקבלת חיבורים:",
|
||||
"Source Code": "קוד מקור",
|
||||
"Stable releases and release candidates": "שחרורים יציבים ומועמדים לשחרור",
|
||||
"Stable releases are delayed by about two weeks. During this time they go through testing as release candidates.": "שחרורים יציבים מתעכבים בערך בשבועיים. במהלך הזמן הזה הם עוברים בדיקות בתור מועמדים לשחרור.",
|
||||
"Stable releases only": "שחרורים יציבים בלבד",
|
||||
"Staggered": "מדורג",
|
||||
"Staggered File Versioning": "ניהול גרסאות קבצים מדורג",
|
||||
"Start Browser": "התחל דפדפן",
|
||||
"Statistics": "סטטיסטיקה",
|
||||
"Stay logged in": "הישאר מחובר",
|
||||
"Stopped": "נעצר",
|
||||
"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.": "מאחסן ומסנכרן נתונים מוצפנים בלבד. תיקיות בכל ההתקנים המחוברים צריכות להיות מוגדרות עם אותה הסיסמה או להיות גם מסוג \"{{receiveEncrypted}}\".",
|
||||
"Subject:": "נושא:",
|
||||
"Support": "תמיכה",
|
||||
"Syncing": "כעת בסנכרון",
|
||||
"Support Bundle": "חבילת תמיכה",
|
||||
"Sync Extended Attributes": "סנכרון תכונות מורחבות",
|
||||
"Sync Ownership": "סנכרון בעלות",
|
||||
"Sync Protocol Listen Addresses": "כתובות האזנה של פרוטוקול הסנכרון",
|
||||
"Sync Status": "מצב סנכרון",
|
||||
"Syncing": "מסנכרן",
|
||||
"Syncthing device ID for \"{%devicename%}\"": "מזהה התקן Syncthing עבור \"{{devicename}}\"",
|
||||
"Syncthing has been shut down.": "Syncthing כבה.",
|
||||
"Syncthing includes the following software or portions thereof:": "Syncthing כולל את התוכנות הבאות או חלקים מהן:",
|
||||
"Syncthing is Free and Open Source Software licensed as MPL v2.0.": "Syncthing היא תוכנה חופשית וקוד פתוח המורשית כ־MPL v2.0.",
|
||||
"Syncthing is a continuous file synchronization program. It synchronizes files between two or more computers in real time, safely protected from prying eyes. Your data is your data alone and you deserve to choose where it is stored, whether it is shared with some third party, and how it's transmitted over the internet.": "Syncthing היא תוכנת סנכרון רציף של קבצים. היא מסנכרנת קבצים בין שני מחשבים או יותר בזמן אמת, מוגנים בבטחה מפני עיניים חטטניות. הנתונים שלך הם הנתונים שלך בלבד ומגיע לך לבחור היכן הם מאוחסנים, אם הם משותפים עם צד שלישי כלשהו, ואיך הם משודרים על גבי האינטרנט.",
|
||||
"Syncthing is listening on the following network addresses for connection attempts from other devices:": "Syncthing מאזין בכתובות הרשת הבאות לניסיונות חיבור מהתקנים אחרים:",
|
||||
"Syncthing is not listening for connection attempts from other devices on any address. Only outgoing connections from this device may work.": "Syncthing אינו מאזין לניסיונות חיבור מהתקנים אחרים בכתובת כלשהי. רק חיבורים יוצאים מהתקן זה עשויים לעבוד.",
|
||||
"Syncthing is restarting.": "Syncthing מופעל מחדש.",
|
||||
"Syncthing is saving changes.": "Syncthing שומר שינויים.",
|
||||
"Syncthing is upgrading.": "Syncthing משתדרג.",
|
||||
"Syncthing now supports automatically reporting crashes to the developers. This feature is enabled by default.": "Syncthing עכשיו תומך בדיווח קריסות באופן אוטומטי למפתחים. תכונה זו מופעלת כברירת מחדל.",
|
||||
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "נראה כי Syncthing נפל, או שיש בעיה עם חיבור האינטרנט שלך. מנסה שוב…",
|
||||
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "נראה כי Syncthing חווה בעיה בעיבוד הבקשה שלך. נא לרענן את הדף או להפעיל מחדש את Syncthing אם הבעיה נמשכת.",
|
||||
"TCP LAN": "TCP LAN",
|
||||
"TCP WAN": "TCP WAN",
|
||||
"Take me back": "קח אותי בחזרה",
|
||||
"The GUI address is overridden by startup options. Changes here will not take effect while the override is in place.": "הכתובת של הממשק הגרפי נעקפת על ידי אפשרויות הפעלה. שינויים כאן לא ייכנסו לתוקף כל עוד המעקף נמצא במקום.",
|
||||
"The Syncthing Authors": "מחברי Syncthing",
|
||||
"The Syncthing admin interface is configured to allow remote access without a password.": "ממשק המנהל של Syncthing מוגדר לאפשר גישה מרוחקת ללא סיסמה.",
|
||||
"The aggregated statistics are publicly available at the URL below.": "הסטטיסטיקות המצטברות זמינות לציבור בכתובת ה־URL למטה.",
|
||||
"The cleanup interval cannot be blank.": "פרק זמן לניקוי אינו יכול להיות ריק.",
|
||||
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "התצורה נשמרה אבל לא הופעלה. יש לאתחל את Syncthing כדי להפעיל את התצורה החדשה.",
|
||||
"The device ID cannot be blank.": "מזהה ההתקן אינו יכול להיות ריק.",
|
||||
"The device ID to enter here can be found in the \"Actions > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "ניתן למצוא את מזהה ההתקן אותו יש להזין כאן בדו־השיח \"פעולות > הצג מזהה\" בהתקנים האחרים. רווחים ומקפים הם אופציונליים (מוזנחים).",
|
||||
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes, and app versions. If the reported data set is changed you will be prompted with this dialog again.": "דוח השימוש המוצפן נשלח מדי יום. הוא משמש למעקב אחר פלטפורמות שכיחות, גדלי תיקיות, וגרסאות יישום. אם מערך הנתונים המדווח ישתנה, תתבקש עם דו-שיח זה שוב.",
|
||||
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "מזהה ההתקן שהוזן לא נראה חוקי. הוא צריך להיות מחרוזת באורך 52 או 56 תווים המורכבת מאותיות ומספרים, כאשר רווחים ומקפים הם אופציונליים.",
|
||||
"The folder ID cannot be blank.": "מזהה התיקייה לא יכול להיות ריק.",
|
||||
"The folder ID must be unique.": "מזהה התיקייה חייב להיות ייחודי.",
|
||||
"The folder content on other devices will be overwritten to become identical with this device. Files not present here will be deleted on other devices.": "תוכן התיקייה בהתקנים אחרים יידרס כדי להפוך לזהה עם התקן זה. קבצים שלא נוכחים כאן יימחקו בהתקנים אחרים.",
|
||||
"The folder content on this device will be overwritten to become identical with other devices. Files newly added here will be deleted.": "תוכן התיקייה בהתקן זה יידרס כדי להפוך לזהה עם התקנים אחרים. קבצים חדשים שנוספו כאן יימחקו.",
|
||||
"The folder path cannot be blank.": "נתיב התיקייה אינו יכול להיות ריק.",
|
||||
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "נעשה שימוש בפרקי הזמן הבאים: במשך השעה הראשונה נשמרת גרסה כל 30 שניות, במשך היום הראשון נשמרת גרסה כל שעה, במשך 30 הימים הראשונים נשמרת גרסה כל יום, עד הגיל המרבי נשמרת גרסה כל שבוע.",
|
||||
"The following items could not be synchronized.": "לא היה ניתן לסנכרן את הפריטים הבאים.",
|
||||
"The following items were changed locally.": "הפריטים הבאים שונו מקומית.",
|
||||
"The following methods are used to discover other devices on the network and announce this device to be found by others:": "נעשה שימוש בשיטות הבאות כדי לגלות התקנים אחרים ברשת ולהכריז על התקן זה כדי שימצא על ידי אחרים:",
|
||||
"The following text will automatically be inserted into a new message.": "הטקסט הבא יוכנס באופן אוטומטי לתוך הודעה חדשה.",
|
||||
"The following unexpected items were found.": "הפריטים הבלתי צפויים הבאים נמצאו.",
|
||||
"The interval must be a positive number of seconds.": "פרק הזמן חייב להיות מספר חיובי של שניות.",
|
||||
"The interval, in seconds, for running cleanup in the versions directory. Zero to disable periodic cleaning.": "פרק הזמן, בשניות, להרצת ניקוי בתיקיית הגרסאות. אפס כדי להשבית ניקוי תקופתי.",
|
||||
"The maximum age must be a number and cannot be blank.": "הגיל המרבי חייב להיות מספר ואינו יכול להיות ריק.",
|
||||
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "הזמן המרבי לשמירת גרסה (בימים, הגדר ל־0 כדי להשאיר גרסאות לנצח).",
|
||||
"The number of connections must be a non-negative number.": "מספר החיבורים חייב להיות מספר לא שלילי.",
|
||||
"The number of days must be a number and cannot be blank.": "מספר הימים חייב להיות מספר ואינו יכול להיות ריק.",
|
||||
"The number of days to keep files in the trash can. Zero means forever.": "מספר הימים לשמירת קבצים בפח האשפה. אפס פירושו לנצח.",
|
||||
"The number of old versions to keep, per file.": "המספר של גרסאות ישנות שיש לשמור, לכל קובץ.",
|
||||
"The number of versions must be a number and cannot be blank.": "מספר הגרסאות חייב להיות מספר ואינו יכול להיות ריק.",
|
||||
"The path cannot be blank.": "הנתיב אינו יכול להיות ריק.",
|
||||
"The rate limit is applied to the accumulated traffic of all connections to this device.": "מגבלת הקצב מוחלת על התעבורה המצטברת של כל החיבורים להתקן זה.",
|
||||
"The rate limit must be a non-negative number (0: no limit)": "מגבלת הקצב חייבת להיות מספר לא שלילי (0: ללא הגבלה)",
|
||||
"The remote device has not accepted sharing this folder.": "ההתקן המרוחק לא קיבל את שיתוף תיקייה זו.",
|
||||
"The remote device has paused this folder.": "ההתקן המרוחק השהה את התיקייה הזו.",
|
||||
"The rescan interval must be a non-negative number of seconds.": "פרק הזמן לסריקה מחדש חייב להיות מספר לא שלילי של שניות.",
|
||||
"There are no devices to share this folder with.": "אין התקנים לשיתוף תיקייה זו.",
|
||||
"There are no file versions to restore.": "אין גרסאות קובץ לשחזור.",
|
||||
"There are no folders to share with this device.": "אין תיקיות לשיתוף עם התקן זה.",
|
||||
"They are retried automatically and will be synced when the error is resolved.": "הם מנוסים מחדש באופן אוטומטי ויסונכרנו כאשר השגיאה תיפתר.",
|
||||
"This Device": "התקן זה",
|
||||
"This Month": "החודש הזה",
|
||||
"This can easily give hackers access to read and change any files on your computer.": "זה יכול לתת בקלות להאקרים גישה לקרוא ולשנות את כל הקבצים במחשב שלך.",
|
||||
"This device cannot automatically discover other devices or announce its own address to be found by others. Only devices with statically configured addresses can connect.": "התקן זה לא יכול לגלות באופן אוטומטי התקנים אחרים ולהכריז על כתובת משלו כדי שימצא על ידי אחרים. רק התקנים עם כתובות מוגדרות סטטית יכולים להתחבר.",
|
||||
"This is a major version upgrade.": "זהו שדרוג גרסה עיקרית.",
|
||||
"This setting controls the free space required on the home (i.e., index database) disk.": "הגדרה זו שולטת במקום הפנוי הנדרש בדיסק הבית (כלומר, אינדקס של מסד נתונים).",
|
||||
"Time": "זמן",
|
||||
"Time the item was last modified": "זמן השינוי האחרון של הפריט",
|
||||
"To connect with the Syncthing device named \"{%devicename%}\", add a new remote device on your end with this ID:": "כדי להתחבר עם התקן Syncthing בשם \"{{devicename}}\", הוסף התקן מרוחק חדש בקצה שלך עם מזהה זה:",
|
||||
"To permit a rule, have the checkbox checked. To deny a rule, leave it unchecked.": "כדי להתיר כלל, סמן את תיבת הסימון. כדי לשלול כלל, השאר אותה לא מסומנת.",
|
||||
"Today": "היום",
|
||||
"Trash Can": "פח אשפה",
|
||||
"Trash Can File Versioning": "ניהול גרסאות קבצים עם פח אשפה",
|
||||
"Type": "סוג",
|
||||
"UNIX Permissions": "הרשאות UNIX",
|
||||
"Unavailable": "לא זמין",
|
||||
"Unavailable/Disabled by administrator or maintainer": "לא זמין/מושבת על ידי מנהל או מתחזק",
|
||||
"Undecided (will prompt)": "לא הוחלט (יתבקש)",
|
||||
"Unexpected Items": "פריטים בלתי צפויים",
|
||||
"Unexpected items have been found in this folder.": "פריטים בלתי צפויים נמצאו בתיקייה זו.",
|
||||
"Unignore": "בטל התעלמות",
|
||||
"Unknown": "לא ידוע",
|
||||
"Unshared": "לא משותף",
|
||||
"Unshared Devices": "התקנים לא משותפים",
|
||||
"Unshared Folders": "תיקיות לא משותפות",
|
||||
"Untrusted": "לא מהימן",
|
||||
"Up to Date": "מעודכן",
|
||||
"Updated {%file%}": "{{file}} עודכן",
|
||||
"Upgrade": "שדרוג",
|
||||
"Upgrade To {%version%}": "שדרוג לגרסה {{version}}",
|
||||
"Upgrading": "כעת בשדרוג",
|
||||
"Upgrade To {%version%}": "שדרג ל־{{version}}",
|
||||
"Upgrading": "משדרג",
|
||||
"Upload Rate": "קצב העלאה",
|
||||
"Uptime": "זמן פעולה",
|
||||
"Use HTTPS for GUI": "שימוש ב־HTTP לממשק המשתמש",
|
||||
"Usage reporting is always enabled for candidate releases.": "דיווח שימוש תמיד מופעל עבור מועמדים לשחרור.",
|
||||
"Use HTTPS for GUI": "השתמש ב־HTTPS עבור ממשק גרפי",
|
||||
"Use notifications from the filesystem to detect changed items.": "השתמש בהתראות ממערכת הקבצים כדי לגלות פריטים שהשתנו.",
|
||||
"User": "משתמש",
|
||||
"User Home": "תיקיית הבית של המשתמש",
|
||||
"Username/Password has not been set for the GUI authentication. Please consider setting it up.": "שם משתמש/סיסמה לא הוגדרו עבור אימות הממשק הגרפי. נא לשקול להגדיר אותם.",
|
||||
"Using a QUIC connection over LAN": "משתמש בחיבור QUIC על גבי LAN",
|
||||
"Using a QUIC connection over WAN": "משתמש בחיבור QUIC על גבי WAN",
|
||||
"Using a direct TCP connection over LAN": "משתמש בחיבור TCP ישיר על גבי LAN",
|
||||
"Using a direct TCP connection over WAN": "משתמש בחיבור TCP ישיר על גבי WAN",
|
||||
"Version": "גרסה",
|
||||
"Versions": "גרסאות",
|
||||
"Versions Path": "נתיב גרסאות",
|
||||
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "גרסאות נמחקות באופן אוטומטי אם הן ישנות יותר מהגיל המרבי או חורגות ממספר הקבצים המותרים בפרק זמן.",
|
||||
"Waiting to Clean": "ממתין לנקות",
|
||||
"Waiting to Scan": "ממתין לסריקה",
|
||||
"Waiting to Sync": "ממתין לסנכרון",
|
||||
"Warning": "אזהרה",
|
||||
"Warning, this path is a parent directory of an existing folder \"{%otherFolder%}\".": "אזהרה, הנתיב הזה הוא תיקיית אב של תיקייה קיימת \"{{otherFolder}}\".",
|
||||
"Warning, this path is a parent directory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "אזהרה, הנתיב הזה הוא תיקיית אב של תיקייה קיימת \"{{otherFolderLabel}}\" ({{otherFolder}}).",
|
||||
"Warning, this path is a subdirectory of an existing folder \"{%otherFolder%}\".": "אזהרה, הנתיב הזה הוא תיקיית משנה של תיקייה קיימת \"{{otherFolder}}\".",
|
||||
"Warning, this path is a subdirectory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "אזהרה, הנתיב הזה הוא תיקיית משנה של תיקייה קיימת \"{{otherFolderLabel}}\" ({{otherFolder}}).",
|
||||
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "אזהרה: אם אתה משתמש במשגיח חיצוני כמו {{syncthingInotify}}, עליך לוודא שהוא מושבת.",
|
||||
"Watch for Changes": "עקוב אחר שינויים",
|
||||
"Watching for Changes": "מעקב אחר שינויים",
|
||||
"Watching for changes discovers most changes without periodic scanning.": "מעקב אחר שינויים מגלה את רוב השינויים ללא סריקה תקופתית.",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "בעת הוספת התקן חדש, זכור כי יש להוסיף את ההתקן הזה גם בצד השני.",
|
||||
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "בעת הוספת תיקייה חדשה, זכור כי מזהה התיקייה משמש לקשירת תיקיות יחד בין התקנים. הם רגישים לרישיות וצריכים להתאים בדיוק בין כל ההתקנים.",
|
||||
"When set to more than one on both devices, Syncthing will attempt to establish multiple concurrent connections. If the values differ, the highest will be used. Set to zero to let Syncthing decide.": "כאשר מוגדר ליותר מאחד בשני ההתקנים, Syncthing ינסה להקים חיבורים בו-זמניים מרובים. אם הערכים שונים, ייעשה שימוש בערך הגבוה ביותר. הגדר לאפס כדי לתת ל־Syncthing להחליט.",
|
||||
"Yes": "כן",
|
||||
"You must keep at least one version.": "חובה לשמור גרסה אחת לפחות.",
|
||||
"Yesterday": "אתמול",
|
||||
"You can also copy and paste the text into a new message manually.": "אתה יכול גם להעתיק ולהדביק את הטקסט לתוך הודעה חדשה באופן ידני.",
|
||||
"You can also select one of these nearby devices:": "אתה יכול גם לבחור אחד מההתקנים הקרובים הבאים:",
|
||||
"You can change your choice at any time in the Settings dialog.": "אתה יכול לשנות את הבחירה שלך בכל עת בדו־שיח ההגדרות.",
|
||||
"You can read more about the two release channels at the link below.": "אתה יכול לקרוא עוד על שני ערוצי השחרור בקישור למטה.",
|
||||
"You have no ignored devices.": "אין לך התקנים בהתעלמות.",
|
||||
"You have no ignored folders.": "אין לך תיקיות בהתעלמות.",
|
||||
"You have unsaved changes. Do you really want to discard them?": "יש לך שינויים שלא נשמרו. האם אתה באמת רוצה להשליך אותם?",
|
||||
"You must keep at least one version.": "אתה חייב להשאיר לפחות גרסה אחת.",
|
||||
"You should never add or change anything locally in a \"{%receiveEncrypted%}\" folder.": "לעולם אל תוסיף או תשנה כלום באופן מקומי בתיקיית \"{{receiveEncrypted}}\".",
|
||||
"Your SMS app should open to let you choose the recipient and send it from your own number.": "יישום המסרונים שלך צריך להיפתח כדי לתת לך לבחור את הנמען ולשלוח את המסרון מהמספר שלך.",
|
||||
"Your email app should open to let you choose the recipient and send it from your own address.": "יישום הדוא\"ל שלך צריך להיפתח כדי לתת לך לבחור את הנמען ולשלוח את ההודעה מהכתובת שלך.",
|
||||
"days": "ימים",
|
||||
"deleted": "נמחק",
|
||||
"deny": "לשלול",
|
||||
"directories": "תיקיות",
|
||||
"file": "קובץ",
|
||||
"files": "קבצים",
|
||||
"folder": "תיקייה",
|
||||
"full documentation": "תיעוד מלא",
|
||||
"items": "פריטים"
|
||||
"items": "פריטים",
|
||||
"modified": "שונה",
|
||||
"permit": "היתר",
|
||||
"seconds": "שניות",
|
||||
"theme": {
|
||||
"name": {
|
||||
"black": "שחור",
|
||||
"dark": "כהה",
|
||||
"default": "ברירת מחדל",
|
||||
"light": "בהיר"
|
||||
}
|
||||
},
|
||||
"unknown device": "התקן לא ידוע",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} רוצה לשתף את התיקייה \"{{folder}}\".",
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} רוצה לשתף את התיקייה \"{{folderlabel}}\" ({{folder}}).",
|
||||
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} עשוי להציג מחדש את ההתקן הזה."
|
||||
}
|
||||
|
||||
@@ -154,7 +154,7 @@
|
||||
"Failed Items": "विफल वस्तुएं",
|
||||
"Failed to load file versions.": "फाइल संस्करण लोड करने में विफल।",
|
||||
"Failed to load ignore patterns.": "नजरअंदाज प्रतिमान लोड करने में विफल।",
|
||||
"Failed to setup, retrying": "स्थापना करने में विफल, पुनः प्रयास किया जा रहा है",
|
||||
"Failed to set up, retrying": "स्थापना करने में विफल, पुनः प्रयास किया जा रहा है",
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "यदि IPv6 संयोजकता नहीं है तो IPv6 सर्वर से जुड़ने में विफलता अपेक्षित है।",
|
||||
"File Pull Order": "फाइल खींचने का क्रम",
|
||||
"File Versioning": "फाइल संस्करणीकरण",
|
||||
|
||||
@@ -146,7 +146,7 @@
|
||||
"Failed Items": "Hibás elemek",
|
||||
"Failed to load file versions.": "Nem sikerült betölteni a fájlverziókat.",
|
||||
"Failed to load ignore patterns.": "Nem sikerült betölteni a mellőzési mintákat.",
|
||||
"Failed to setup, retrying": "Telepítés nem sikerült, újrapróbálkozás",
|
||||
"Failed to set up, retrying": "Telepítés nem sikerült, újrapróbálkozás",
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "Mivel nincs IPv6 kapcsolat, ezért várhatóan nem fog sikerülni IPv6-os szerverekhez csatlakozni.",
|
||||
"File Pull Order": "Fájlküldési sorrend",
|
||||
"File Versioning": "Fájlverzió-követés",
|
||||
|
||||
@@ -146,7 +146,7 @@
|
||||
"Failed Items": "Berkas yang gagal",
|
||||
"Failed to load file versions.": "Gagal memuat versi berkas.",
|
||||
"Failed to load ignore patterns.": "Gagal memuat pola pengabaian.",
|
||||
"Failed to setup, retrying": "Gagal menyiapkan, mengulang",
|
||||
"Failed to set up, retrying": "Gagal menyiapkan, mengulang",
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "Gagal untuk menyambung ke server IPv6 itu disangka apabila tidak ada konektivitas IPv6.",
|
||||
"File Pull Order": "Urutan Penarikan Berkas",
|
||||
"File Versioning": "Pemversian Berkas",
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"Allowed Networks": "Reti Consentite",
|
||||
"Alphabetic": "Alfabetico",
|
||||
"Altered by ignoring deletes.": "Modificato ignorando le eliminazioni.",
|
||||
"Always turned on when the folder type is \"{%foldertype%}\".": "Sempre attivato quando il tipo di cartella è \"{{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.": "Il controllo versione è gestito da un comando esterno. Quest'ultimo deve rimuovere il file dalla cartella condivisa. Se il percorso dell'applicazione contiene spazi, deve essere indicato tra virgolette.",
|
||||
"Anonymous Usage Reporting": "Statistiche Anonime di Utilizzo",
|
||||
"Anonymous usage report format has changed. Would you like to move to the new format?": "Il formato delle statistiche anonime di utilizzo è cambiato. Vuoi passare al nuovo formato?",
|
||||
@@ -52,6 +53,7 @@
|
||||
"Body:": "Corpo:",
|
||||
"Bugs": "Bug",
|
||||
"Cancel": "Annulla",
|
||||
"Cannot be enabled when the folder type is \"{%foldertype%}\".": "Non può essere abilitato se il tipo di cartella è \"{{foldertype}}\".",
|
||||
"Changelog": "Registro modifiche",
|
||||
"Clean out after": "Svuota dopo",
|
||||
"Cleaning Versions": "Pulizia Versioni",
|
||||
@@ -154,7 +156,7 @@
|
||||
"Failed Items": "Elementi Errati",
|
||||
"Failed to load file versions.": "Impossibile caricare le versioni dei file.",
|
||||
"Failed to load ignore patterns.": "Impossibile caricare gli schemi di esclusione.",
|
||||
"Failed to setup, retrying": "Configurazione fallita, riprovo",
|
||||
"Failed to set up, retrying": "Configurazione fallita, riprovo",
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "La connessione a server IPv6 fallisce se non c'è connettività IPv6.",
|
||||
"File Pull Order": "Ordine Prelievo File",
|
||||
"File Versioning": "Controllo Versione File",
|
||||
|
||||
@@ -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": "버전 정리",
|
||||
@@ -154,7 +156,7 @@
|
||||
"Failed Items": "실패 항목",
|
||||
"Failed to load file versions.": "파일 버전을 불러오기에 실패했습니다.",
|
||||
"Failed to load ignore patterns.": "무시 양식을 불러오기에 실패했습니다.",
|
||||
"Failed to setup, retrying": "설정 적용 실패; 재시도 중",
|
||||
"Failed to set up, retrying": "설정 적용 실패; 재시도 중",
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "IPv6에 연결되어 있지 않을 때는 IPv6 서버에 접속하지 못하는 것이 정상입니다.",
|
||||
"File Pull Order": "파일 수신 순서",
|
||||
"File Versioning": "파일 버전 관리",
|
||||
|
||||
@@ -6,11 +6,14 @@
|
||||
"About": "Apie programą",
|
||||
"Action": "Veiksmas",
|
||||
"Actions": "Veiksmai",
|
||||
"Active filter rules": "Aktyvios filtro taisyklės",
|
||||
"Add": "Pridėti",
|
||||
"Add Device": "Pridėti įrenginį",
|
||||
"Add Folder": "Pridėti aplanką",
|
||||
"Add Remote Device": "Pridėti nuotolinį įrenginį",
|
||||
"Add devices from the introducer to our device list, for mutually shared folders.": "Pridėti įrenginius iš supažindintojo į mūsų įrenginių sąrašą, siekiant abipusiškai bendrinti aplankus.",
|
||||
"Add filter entry": "Pridėti filtro įrašą",
|
||||
"Add ignore patterns": "Pridėti ignoravimo šablonų",
|
||||
"Add new folder?": "Pridėti naują aplanką?",
|
||||
"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.": "Pilnas nuskaitymo iš naujo intervalas bus papildomai padidintas (60 kartų, t.y. nauja numatytoji 1 val. reikšmė). Taip pat vėliau, pasirinkę Ne, galite jį konfigūruoti rankiniu būdu kiekvienam atskiram aplankui.",
|
||||
"Address": "Adresas",
|
||||
@@ -45,6 +48,7 @@
|
||||
"Automatically create or share folders that this device advertises at the default path.": "Automatiškai sukurti ar bendrinti aplankus, kuriuos šis įrenginys skelbia numatytajame kelyje.",
|
||||
"Available debug logging facilities:": "Prieinamos derinimo registravimo priemonės:",
|
||||
"Be careful!": "Būkite atsargūs!",
|
||||
"Body:": "Turinys:",
|
||||
"Bugs": "Klaidos",
|
||||
"Cancel": "Atsisakyti",
|
||||
"Changelog": "Keitinių žurnalas",
|
||||
@@ -63,6 +67,7 @@
|
||||
"Connection Management": "Ryšių valdymas",
|
||||
"Connection Type": "Ryšio tipas",
|
||||
"Connections": "Ryšiai",
|
||||
"Connections via relays might be rate limited by the relay": "Ryšiai per tarpinius mazgus gali būti ribojami pagal užklausų skaičių per laiko vienetą",
|
||||
"Continuously watching for changes is now available within Syncthing. This will detect changes on disk and issue a scan on only the modified paths. The benefits are that changes are propagated quicker and that less full scans are required.": "Pastoviai stebėti pakeitimus dabar galima Syncthing viduje. Tai aptiks pakeitimus jūsų diske ir paleis nuskaitymą tik modifikuotuose keliuose. Pranašumas yra tas, kad pakeitimai sklis greičiau ir reikės mažiau pilnų nuskaitymų.",
|
||||
"Copied from elsewhere": "Nukopijuota iš kitur",
|
||||
"Copied from original": "Nukopijuota iš originalo",
|
||||
@@ -139,7 +144,7 @@
|
||||
"Failed Items": "Nepavykę siuntimai",
|
||||
"Failed to load file versions.": "Nepavyko įkelti failo versijų.",
|
||||
"Failed to load ignore patterns.": "Nepavyko įkelti nepaisymo šablonų.",
|
||||
"Failed to setup, retrying": "Nepavyko nustatyti, bandoma iš naujo",
|
||||
"Failed to set up, retrying": "Nepavyko nustatyti, bandoma iš naujo",
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "Nesėkmė prisijungti prie IPv6 serverių yra tikėtina, jei nėra IPv6 ryšio.",
|
||||
"File Pull Order": "Failų siuntimo tvarka",
|
||||
"File Versioning": "Versijų valdymas",
|
||||
|
||||
@@ -154,7 +154,7 @@
|
||||
"Failed Items": "Elementsynkronisering som har mislyktes",
|
||||
"Failed to load file versions.": "Lasting av fil-versjoner feilet.",
|
||||
"Failed to load ignore patterns.": "Lasting av ignorer mønstre feilet.",
|
||||
"Failed to setup, retrying": "Klarte ikke å utføre oppsett, prøver igjen",
|
||||
"Failed to set up, retrying": "Klarte ikke å utføre oppsett, prøver igjen",
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "Å ikke klare å koble til IPv6-tjenere er forventet hvis det ikke er noen IPv6-tilknytning.",
|
||||
"File Pull Order": "Filenes henterekkefølge",
|
||||
"File Versioning": "Versjonskontroll",
|
||||
|
||||
@@ -154,7 +154,7 @@
|
||||
"Failed Items": "Mislukte items",
|
||||
"Failed to load file versions.": "Laden van bestandsversies mislukt.",
|
||||
"Failed to load ignore patterns.": "Laden van negeerpatronen mislukt.",
|
||||
"Failed to setup, retrying": "Instellen mislukt, opnieuw proberen",
|
||||
"Failed to set up, retrying": "Instellen mislukt, opnieuw proberen",
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "Als er geen IPv6-connectiviteit is worden problemen bij verbinden met IPv6-servers verwacht.",
|
||||
"File Pull Order": "Volgorde voor binnenhalen van bestanden",
|
||||
"File Versioning": "Versiebeheer",
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"Allowed Networks": "Dozwolone sieci",
|
||||
"Alphabetic": "Alfabetycznie",
|
||||
"Altered by ignoring deletes.": "Zmieniono przez ignorowanie usuniętych",
|
||||
"Always turned on when the folder type is \"{%foldertype%}\".": "Zawsze włączone, gdy typ folderu to \"{{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.": "Zewnętrzne polecenie odpowiedzialne jest za wersjonowanie. Musi ono usunąć plik ze współdzielonego folderu. Jeżeli ścieżka do aplikacji zawiera spacje, to powinna ona być zamknięta w cudzysłowie.",
|
||||
"Anonymous Usage Reporting": "Anonimowe statystyki użycia",
|
||||
"Anonymous usage report format has changed. Would you like to move to the new format?": "Format anonimowych statystyk użycia uległ zmianie. Czy chcesz przejść na nowy format?",
|
||||
@@ -52,6 +53,7 @@
|
||||
"Body:": "Treść:",
|
||||
"Bugs": "Błędy",
|
||||
"Cancel": "Anuluj",
|
||||
"Cannot be enabled when the folder type is \"{%foldertype%}\".": "Nie można włączyć, jeśli typem folderu jest \"{{foldertype}}\".",
|
||||
"Changelog": "Historia zmian",
|
||||
"Clean out after": "Opróżnij po",
|
||||
"Cleaning Versions": "Czyszczenie wersji",
|
||||
@@ -154,7 +156,7 @@
|
||||
"Failed Items": "Elementy zakończone niepowodzeniem",
|
||||
"Failed to load file versions.": "Nie udało się załadować wersji plików.",
|
||||
"Failed to load ignore patterns.": "Nie udało się załadować wzorców ignorowania.",
|
||||
"Failed to setup, retrying": "Nie udało się ustawić; ponawiam próbę",
|
||||
"Failed to set up, retrying": "Nie udało się ustawić; ponawiam próbę",
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "Błąd połączenia do serwerów IPv6 może wystąpić, gdy w ogóle nie ma połączenia po IPv6.",
|
||||
"File Pull Order": "Kolejność pobierania plików",
|
||||
"File Versioning": "Wersjonowanie plików",
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"Allowed Networks": "Redes permitidas",
|
||||
"Alphabetic": "Alfabética",
|
||||
"Altered by ignoring deletes.": "Alterado ignorando exclusões.",
|
||||
"Always turned on when the folder type is \"{%foldertype%}\".": "Sempre ativado quando o tipo de pasta for \"{{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.": "Um comando externo controla o controle de versão. Tem que remover o arquivo da pasta compartilhada. Se o caminho para o aplicativo contiver espaços, ele deve ser colocado entre aspas.",
|
||||
"Anonymous Usage Reporting": "Relatórios anônimos de uso",
|
||||
"Anonymous usage report format has changed. Would you like to move to the new format?": "O formato do relatório anônimo de uso mudou. Gostaria de usar o formato novo?",
|
||||
@@ -52,6 +53,7 @@
|
||||
"Body:": "Corpo:",
|
||||
"Bugs": "Erros",
|
||||
"Cancel": "Cancelar",
|
||||
"Cannot be enabled when the folder type is \"{%foldertype%}\".": "Não pode ser ativado se o tipo de pasta for \"{{foldertype}}\".",
|
||||
"Changelog": "Registro de alterações",
|
||||
"Clean out after": "Limpar depois de",
|
||||
"Cleaning Versions": "Limpando Versões",
|
||||
@@ -154,7 +156,7 @@
|
||||
"Failed Items": "Itens com falha",
|
||||
"Failed to load file versions.": "Falha ao carregar versões do arquivo.",
|
||||
"Failed to load ignore patterns.": "Falha ao carregar os padrões para ignorar.",
|
||||
"Failed to setup, retrying": "Não foi possível configurar, tentando novamente",
|
||||
"Failed to set up, retrying": "Não foi possível configurar, tentando novamente",
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "Falhas na conexão a servidores IPv6 são esperadas caso não haja conectividade IPv6.",
|
||||
"File Pull Order": "Ordem de retirada do arquivo",
|
||||
"File Versioning": "Versionamento de arquivos",
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"Allowed Networks": "Redes permitidas",
|
||||
"Alphabetic": "Alfabética",
|
||||
"Altered by ignoring deletes.": "Alterada por terem sido ignoradas as eliminações.",
|
||||
"Always turned on when the folder type is \"{%foldertype%}\".": "Sempre ligado quando o tipo de pasta é \"{{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.": "Um comando externo gere as versões. Esse comando tem que remover o ficheiro da pasta partilhada. Se o caminho para a aplicação contiver espaços, então terá de o escrever entre aspas.",
|
||||
"Anonymous Usage Reporting": "Enviar relatórios anónimos de utilização",
|
||||
"Anonymous usage report format has changed. Would you like to move to the new format?": "O formato do relatório anónimo de utilização foi alterado. Gostaria de mudar para o novo formato?",
|
||||
@@ -52,6 +53,7 @@
|
||||
"Body:": "Corpo:",
|
||||
"Bugs": "Erros",
|
||||
"Cancel": "Cancelar",
|
||||
"Cannot be enabled when the folder type is \"{%foldertype%}\".": "Não pode ser habilitado quando o tipo de pasta é \"{{foldertype}}\".",
|
||||
"Changelog": "Registo de alterações",
|
||||
"Clean out after": "Esvaziar ao fim de",
|
||||
"Cleaning Versions": "Limpando versões",
|
||||
@@ -154,7 +156,7 @@
|
||||
"Failed Items": "Itens que falharam",
|
||||
"Failed to load file versions.": "Falha ao carregar as versões do ficheiro.",
|
||||
"Failed to load ignore patterns.": "Falha ao carregar os padrões de exclusão.",
|
||||
"Failed to setup, retrying": "A preparação falhou, a tentar novamente",
|
||||
"Failed to set up, retrying": "A preparação falhou, a tentar novamente",
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "São esperadas falhas na ligação a servidores IPv6 se não existir conectividade IPv6.",
|
||||
"File Pull Order": "Ordem de obtenção de ficheiros",
|
||||
"File Versioning": "Gestão de versões de ficheiros",
|
||||
|
||||
@@ -143,7 +143,6 @@
|
||||
"Error": "Eroare",
|
||||
"External File Versioning": "Administrare externă a versiunilor documentului",
|
||||
"Failed Items": "Failed Items",
|
||||
"Failed to setup, retrying": "Failed to setup, retrying",
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.",
|
||||
"File Pull Order": "File Pull Order",
|
||||
"File Versioning": "Versiune Fișier",
|
||||
|
||||
@@ -154,7 +154,7 @@
|
||||
"Failed Items": "Сбойные объекты",
|
||||
"Failed to load file versions.": "Не удалось загрузить версии файлов.",
|
||||
"Failed to load ignore patterns.": "Не удалось загрузить шаблоны игнорирования.",
|
||||
"Failed to setup, retrying": "Не удалось настроить, пробуем ещё",
|
||||
"Failed to set up, retrying": "Не удалось настроить, пробуем ещё",
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "Если нет IPv6-соединений, при подключении к IPv6-серверам произойдёт ошибка.",
|
||||
"File Pull Order": "Порядок получения файлов",
|
||||
"File Versioning": "Управление версиями",
|
||||
|
||||
@@ -143,7 +143,7 @@
|
||||
"Failed Items": "අසාර්ථක අයිතම",
|
||||
"Failed to load file versions.": "ගොනු අනුවාද පූරණය කිරීමට අසමත් විය.",
|
||||
"Failed to load ignore patterns.": "නොසලකා හැරීමේ රටා පූරණය කිරීමට අසමත් විය.",
|
||||
"Failed to setup, retrying": "පිහිටුවීමට අසමත් විය, උත්සාහ කරමින්",
|
||||
"Failed to set up, retrying": "පිහිටුවීමට අසමත් විය, උත්සාහ කරමින්",
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "IPv6 සම්බන්ධතාවක් නොමැති නම් IPv6 සේවාදායක වෙත සම්බන්ධ වීමට අසමත් වීම අපේක්ෂා කෙරේ.",
|
||||
"File Pull Order": "ගොනු ඇදීමේ නියෝගය",
|
||||
"File Versioning": "ගොනු අනුවාදය",
|
||||
|
||||
@@ -153,7 +153,7 @@
|
||||
"Failed Items": "Zlyhané položky",
|
||||
"Failed to load file versions.": "Nepodarilo sa načítať verzie súborov.",
|
||||
"Failed to load ignore patterns.": "Nepodarilo sa načítať ignorované vzory.",
|
||||
"Failed to setup, retrying": "Nepodarilo sa nastaviť, opakujem",
|
||||
"Failed to set up, retrying": "Nepodarilo sa nastaviť, opakujem",
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "Zlyhanie pripojenia k IPv6 serverom je očakávané ak neexistujú žiadne IPv6 pripojenia.",
|
||||
"File Pull Order": "Poradie sťahovania súborov",
|
||||
"File Versioning": "Verzie súborov",
|
||||
|
||||
@@ -131,7 +131,7 @@
|
||||
"External File Versioning": "Zunanje beleženje različic datotek",
|
||||
"Failed Items": "Neuspeli predmeti",
|
||||
"Failed to load ignore patterns.": "Prezrih vzorcev ni bilo mogoče naložiti.",
|
||||
"Failed to setup, retrying": "Nastavitev ni uspela, ponovni poskus",
|
||||
"Failed to set up, retrying": "Nastavitev ni uspela, ponovni poskus",
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "Neuspeh povezav z IPv6 strežniki je pričakovan, če ni IPv6 povezljivost.",
|
||||
"File Pull Order": "Vrstni red prenosa datotek",
|
||||
"File Versioning": "Beleženje različic datotek",
|
||||
|
||||
@@ -154,7 +154,7 @@
|
||||
"Failed Items": "Misslyckade objekt",
|
||||
"Failed to load file versions.": "Det gick inte att läsa in filversioner.",
|
||||
"Failed to load ignore patterns.": "Det gick inte att läsa in ignoreringsmönster.",
|
||||
"Failed to setup, retrying": "Det gick inte att ställa in, försöker igen",
|
||||
"Failed to set up, retrying": "Det gick inte att ställa in, försöker igen",
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "Det går inte att ansluta till IPv6-servrar om det inte finns någon IPv6-anslutning.",
|
||||
"File Pull Order": "Filhämtningsprioritering",
|
||||
"File Versioning": "Filversionshantering",
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
{
|
||||
"A device with that ID is already added.": "อุปกรณ์ที่เป็น ID นั้นๆ ได้ถูกเพิ่มเข้าไปแล้ว"
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"Allowed Networks": "İzin Verilen Ağlar",
|
||||
"Alphabetic": "Alfabetik",
|
||||
"Altered by ignoring deletes.": "Silmeler yoksayılarak değiştirildi.",
|
||||
"Always turned on when the folder type is \"{%foldertype%}\".": "Klasör türü \"{{foldertype}}\" olduğunda her zaman açıktır.",
|
||||
"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.": "Harici bir komut sürümlendirmeyi gerçekleştirir. Dosyayı paylaşılan klasörden kaldırmak zorundadır. Eğer uygulama yolu boşluklar içeriyorsa, tırnak içine alınmalıdır.",
|
||||
"Anonymous Usage Reporting": "İsimsiz Kullanım Bildirme",
|
||||
"Anonymous usage report format has changed. Would you like to move to the new format?": "İsimsiz kullanım raporu biçimi değişti. Yeni biçime geçmek ister misiniz?",
|
||||
@@ -52,6 +53,7 @@
|
||||
"Body:": "Gövde:",
|
||||
"Bugs": "Hatalar",
|
||||
"Cancel": "İptal",
|
||||
"Cannot be enabled when the folder type is \"{%foldertype%}\".": "Klasör türü \"{{foldertype}}\" olduğunda etkinleştirilemez.",
|
||||
"Changelog": "Değişiklik Günlüğü",
|
||||
"Clean out after": "Şundan sonra temizle",
|
||||
"Cleaning Versions": "Sürümleri Temizleme",
|
||||
@@ -154,7 +156,7 @@
|
||||
"Failed Items": "Başarısız Olan Öğeler",
|
||||
"Failed to load file versions.": "Dosya sürümlerini yükleme başarısız.",
|
||||
"Failed to load ignore patterns.": "Yoksayma şekillerini yükleme başarısız.",
|
||||
"Failed to setup, retrying": "Ayarlama başarısız, yeniden deneniyor",
|
||||
"Failed to set up, retrying": "Ayarlama başarısız, yeniden deneniyor",
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "IPv6 bağlanabilirliği yoksa IPv6 sunucularına bağlanma hatası beklenmekte.",
|
||||
"File Pull Order": "Dosya Çekme Sırası",
|
||||
"File Versioning": "Dosya Sürümlendirme",
|
||||
|
||||
@@ -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": "Очищення версій",
|
||||
@@ -154,7 +156,7 @@
|
||||
"Failed Items": "Невдалі",
|
||||
"Failed to load file versions.": "Не вдалося завантажити версії файлів.",
|
||||
"Failed to load ignore patterns.": "Не вдалося завантажити шаблони ігнорування.",
|
||||
"Failed to setup, retrying": "Не вдалося налаштувати, повторна спроба",
|
||||
"Failed to set up, retrying": "Не вдалося налаштувати, повторна спроба",
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "За відсутності з'єднання IPv6 очікується неможливість під'єднання до серверів IPv6.",
|
||||
"File Pull Order": "Порядок витягнення файлів",
|
||||
"File Versioning": "Версіонування файлів",
|
||||
@@ -550,6 +552,6 @@
|
||||
},
|
||||
"unknown device": "невідомий пристрій",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} хоче поділитися папкою \"{{folder}}\".",
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} хоче поділитися папкою \"{{folderLabel}}\" ({{folder}}).",
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} хоче поділитися текою \"{{folderlabel}}\" ({{folder}}).",
|
||||
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} може повторно порекомендувати цей пристрій."
|
||||
}
|
||||
|
||||
@@ -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": "清理版本中",
|
||||
@@ -154,7 +156,7 @@
|
||||
"Failed Items": "失败的项目",
|
||||
"Failed to load file versions.": "加载文件版本失败。",
|
||||
"Failed to load ignore patterns.": "加载忽略模式失败。",
|
||||
"Failed to setup, retrying": "设置失败,正在重试",
|
||||
"Failed to set up, retrying": "设置失败,正在重试",
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "如果本机没有配置 IPv6,则无法连接 IPv6 服务器是正常的。",
|
||||
"File Pull Order": "文件拉取顺序",
|
||||
"File Versioning": "文件版本控制",
|
||||
@@ -228,12 +230,12 @@
|
||||
"Listener Failures": "监听程序失败",
|
||||
"Listener Status": "监听程序状态",
|
||||
"Listeners": "监听程序",
|
||||
"Loading data...": "正在载入数据…",
|
||||
"Loading...": "正在载入…",
|
||||
"Loading data...": "正在加载数据…",
|
||||
"Loading...": "正在加载…",
|
||||
"Local Additions": "本地添加",
|
||||
"Local Discovery": "本地发现",
|
||||
"Local State": "本地状态",
|
||||
"Local State (Total)": "本地状态汇总",
|
||||
"Local State (Total)": "本地状态(总计)",
|
||||
"Locally Changed Items": "本地更改的项目",
|
||||
"Log": "日志",
|
||||
"Log File": "日志文件",
|
||||
@@ -481,11 +483,11 @@
|
||||
"Untrusted": "不受信任",
|
||||
"Up to Date": "最新",
|
||||
"Updated {%file%}": "已更新 {{file}}",
|
||||
"Upgrade": "更新",
|
||||
"Upgrade To {%version%}": "升级至版本 {{version}}",
|
||||
"Upgrade": "升级",
|
||||
"Upgrade To {%version%}": "升级到 {{version}}",
|
||||
"Upgrading": "升级中",
|
||||
"Upload Rate": "上传速率",
|
||||
"Uptime": "启动时间",
|
||||
"Uptime": "运行时间",
|
||||
"Usage reporting is always enabled for candidate releases.": "发布候选版始终启用使用报告。",
|
||||
"Use HTTPS for GUI": "使用 HTTPS 连接到 GUI",
|
||||
"Use notifications from the filesystem to detect changed items.": "使用来自文件系统的通知来检测更改的项目。",
|
||||
|
||||
@@ -145,7 +145,7 @@
|
||||
"Failed Items": "失敗的項目",
|
||||
"Failed to load file versions.": "無法加載文件版本。",
|
||||
"Failed to load ignore patterns.": "無法加載忽略模式。",
|
||||
"Failed to setup, retrying": "設置失敗,正在重試。",
|
||||
"Failed to set up, retrying": "設置失敗,正在重試。",
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "如果本機沒有配置IPv6,則無法連接IPv6服務器是正常的。",
|
||||
"File Pull Order": "文件拉取順序",
|
||||
"File Versioning": "版本控制",
|
||||
|
||||
@@ -154,7 +154,7 @@
|
||||
"Failed Items": "失敗的項目",
|
||||
"Failed to load file versions.": "無法載入檔案版本。",
|
||||
"Failed to load ignore patterns.": "無法載入忽略模式。",
|
||||
"Failed to setup, retrying": "無法設定,正在重試",
|
||||
"Failed to set up, retrying": "無法設定,正在重試",
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "若沒有 IPv6 連線能力,則無法連接 IPv6 伺服器為正常現象。",
|
||||
"File Pull Order": "提取檔案的順序",
|
||||
"File Versioning": "檔案版本控制",
|
||||
|
||||
@@ -1 +1 @@
|
||||
var langPrettyprint = {"ar":"Arabic","bg":"Bulgarian","ca":"Catalan","ca@valencia":"Valencian","cs":"Czech","da":"Danish","de":"German","el":"Greek","en":"English","en-GB":"English (United Kingdom)","es":"Spanish","eu":"Basque","fil":"Filipino","fr":"French","fy":"Frisian","ga":"Irish","hi":"Hindi","hu":"Hungarian","id":"Indonesian","it":"Italian","ja":"Japanese","ko-KR":"Korean","lt":"Lithuanian","nl":"Dutch","pl":"Polish","pt-BR":"Portuguese (Brazil)","pt-PT":"Portuguese (Portugal)","ro-RO":"Romanian","ru":"Russian","sk":"Slovak","sl":"Slovenian","sv":"Swedish","tr":"Turkish","uk":"Ukrainian","zh-CN":"Chinese (Simplified Han script)","zh-HK":"Chinese (Traditional Han script, Hong Kong)","zh-TW":"Chinese (Traditional Han script)"}
|
||||
var langPrettyprint = {"ar":"Arabic","bg":"Bulgarian","ca":"Catalan","ca@valencia":"Valencian","cs":"Czech","da":"Danish","de":"German","el":"Greek","en":"English","en-GB":"English (United Kingdom)","es":"Spanish","eu":"Basque","fil":"Filipino","fr":"French","fy":"Frisian","ga":"Irish","he-IL":"Hebrew (Israel)","hi":"Hindi","hu":"Hungarian","id":"Indonesian","it":"Italian","ja":"Japanese","ko-KR":"Korean","lt":"Lithuanian","nl":"Dutch","pl":"Polish","pt-BR":"Portuguese (Brazil)","pt-PT":"Portuguese (Portugal)","ro-RO":"Romanian","ru":"Russian","sk":"Slovak","sl":"Slovenian","sv":"Swedish","tr":"Turkish","uk":"Ukrainian","zh-CN":"Chinese (Simplified Han script)","zh-HK":"Chinese (Traditional Han script, Hong Kong)","zh-TW":"Chinese (Traditional Han script)"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
var validLangs = ["ar","bg","ca","ca@valencia","cs","da","de","el","en","en-GB","es","eu","fil","fr","fy","ga","hi","hu","id","it","ja","ko-KR","lt","nl","pl","pt-BR","pt-PT","ro-RO","ru","sk","sl","sv","tr","uk","zh-CN","zh-HK","zh-TW"]
|
||||
var validLangs = ["ar","bg","ca","ca@valencia","cs","da","de","el","en","en-GB","es","eu","fil","fr","fy","ga","he-IL","hi","hu","id","it","ja","ko-KR","lt","nl","pl","pt-BR","pt-PT","ro-RO","ru","sk","sl","sv","tr","uk","zh-CN","zh-HK","zh-TW"]
|
||||
|
||||
@@ -521,7 +521,7 @@
|
||||
</span>
|
||||
<span ng-if="folder.fsWatcherEnabled && !folder.paused && folderStatus(folder) !== 'stopped' && model[folder.id].watchError" tooltip data-original-title="{{'Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:' | translate}}<br/>{{model[folder.id].watchError}}">
|
||||
<span class="far fa-clock"></span> {{folder.rescanIntervalS | duration}} 
|
||||
<span class="fas fa-eye-slash"></span> <span translate>Failed to setup, retrying</span>
|
||||
<span class="fas fa-eye-slash"></span> <span translate>Failed to set up, retrying</span>
|
||||
</span>
|
||||
</div>
|
||||
<div ng-if="folder.rescanIntervalS <= 0">
|
||||
@@ -535,7 +535,7 @@
|
||||
</span>
|
||||
<span ng-if="folder.fsWatcherEnabled && !folder.paused && folderStatus(folder) !== 'stopped' && model[folder.id].watchError" tooltip data-original-title="{{'Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:' | translate}}<br/>{{model[folder.id].watchError}}">
|
||||
<span class="far fa-clock"></span> <span translate>Disabled</span> 
|
||||
<span class="fas fa-eye-slash"></span> <span translate>Failed to setup, retrying</span>
|
||||
<span class="fas fa-eye-slash"></span> <span translate>Failed to set up, retrying</span>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
@@ -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, 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 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, Ross Smith II, 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, 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, derekriemer, desbma, diemade, digital, entity0xfe, georgespatton, ghjklw, guangwu, gudvinr, ignacy123, janost, jaseg, jelle van der Waa, jtagcat, klemens, kylosus, luchenhan, luzpaz, marco-m, maxice8, mclang, mv1005, nf, orangekame3, otbutz, overkill, perewa, red_led, rubenbe, sec65, vapatel2, villekalliomaki, wangguoliang, wouter bolsterlee, xarx00, xjtdy888, 佛跳墙, 落心
|
||||
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, 佛跳墙, 落心
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -59,9 +59,12 @@ Jakob Borg, Audrius Butkevicius, Jesse Lucas, Simon Frei, Tomasz Wilczyński, Al
|
||||
<li><a href="https://github.com/golang/protobuf">golang/protobuf</a>, Copyright © 2010 The Go Authors.</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/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/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>
|
||||
|
||||
@@ -1165,7 +1165,7 @@ angular.module('syncthing.core')
|
||||
}
|
||||
|
||||
// Disconnected
|
||||
if (!unused && $scope.deviceStats[deviceCfg.deviceID] && $scope.deviceStats[deviceCfg.deviceID].lastSeenDays && $scope.deviceStats[deviceCfg.deviceID].lastSeenDays >= 7) {
|
||||
if (!unused && $scope.deviceStats[deviceCfg.deviceID] && (!$scope.deviceStats[deviceCfg.deviceID].lastSeenDays || $scope.deviceStats[deviceCfg.deviceID].lastSeenDays >= 7)) {
|
||||
return status + 'disconnected-inactive';
|
||||
} else {
|
||||
return status + 'disconnected';
|
||||
|
||||
@@ -5,18 +5,12 @@ angular.module('syncthing.core')
|
||||
link: function (scope, elm, attrs, ctrl) {
|
||||
ctrl.$parsers.unshift(function (viewValue) {
|
||||
$http.get(urlbase + '/svc/deviceid?id=' + viewValue).success(function (resp) {
|
||||
if (resp.error) {
|
||||
ctrl.$setValidity('validDeviceid', false);
|
||||
} else {
|
||||
ctrl.$setValidity('validDeviceid', true);
|
||||
}
|
||||
let isValid = !resp.error;
|
||||
let isUnique = !isValid || !scope.devices.hasOwnProperty(resp.id);
|
||||
|
||||
ctrl.$setValidity('validDeviceid', isValid);
|
||||
ctrl.$setValidity('unique', isUnique);
|
||||
});
|
||||
//Prevents user from adding a duplicate ID
|
||||
if (scope.devices.hasOwnProperty(viewValue)) {
|
||||
ctrl.$setValidity('unique', false);
|
||||
} else {
|
||||
ctrl.$setValidity('unique', true);
|
||||
}
|
||||
return viewValue;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -251,6 +251,9 @@
|
||||
<select class="form-control" ng-if="currentFolder.type == 'sendonly'" disabled>
|
||||
<option value="disabled" translate>Disabled</option>
|
||||
</select>
|
||||
<p class="help-block" ng-if="currentFolder.type === 'sendonly'" translate translate-value-foldertype="{{'Send Only' | translate}}">
|
||||
Cannot be enabled when the folder type is "{%foldertype%}".
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -282,6 +285,9 @@
|
||||
<p translate class="help-block">
|
||||
Disables comparing and syncing file permissions. Useful on systems with nonexistent or custom permissions (e.g. FAT, exFAT, Synology, Android).
|
||||
</p>
|
||||
<p class="help-block" ng-if="currentFolder._recvEnc" translate translate-value-foldertype="{{'Receive Encrypted' | translate}}">
|
||||
Always turned on when the folder type is "{%foldertype%}".
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -297,12 +303,18 @@
|
||||
<p translate class="help-block">
|
||||
Enables sending ownership information to other devices, and applying incoming ownership information. Typically requires running with elevated privileges.
|
||||
</p>
|
||||
<p class="help-block" ng-if="has(['sendonly', 'receiveencrypted'], currentFolder.type)" translate translate-value-foldertype="{{currentFolder.type === 'sendonly' ? ('Send Only' | translate) : currentFolder.type === 'receiveencrypted' ? ('Receive Encrypted' | translate) : ''}}">
|
||||
Cannot be enabled when the folder type is "{%foldertype%}".
|
||||
</p>
|
||||
<label>
|
||||
<input type="checkbox" ng-disabled="currentFolder.type == 'receiveonly' || currentFolder.type == 'receiveencrypted' || currentFolder.syncOwnership" ng-checked="currentFolder.sendOwnership || currentFolder.syncOwnership" ng-model="currentFolder.sendOwnership" /> <span translate>Send Ownership</span>
|
||||
</label>
|
||||
<p translate class="help-block">
|
||||
Enables sending ownership information to other devices, but not applying incoming ownership information. This can have a significant performance impact. Always enabled when "Sync Ownership" is enabled.
|
||||
</p>
|
||||
<p class="help-block" ng-if="has(['receiveonly', 'receiveencrypted'], currentFolder.type)" translate translate-value-foldertype="{{currentFolder.type === 'receiveonly' ? ('Receive Only' | translate) : currentFolder.type === 'receiveencrypted' ? ('Receive Encrypted' | translate) : ''}}">
|
||||
Cannot be enabled when the folder type is "{%foldertype%}".
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-md-6 form-group">
|
||||
<p>
|
||||
@@ -315,12 +327,18 @@
|
||||
<p translate class="help-block">
|
||||
Enables sending extended attributes to other devices, and applying incoming extended attributes. May require running with elevated privileges.
|
||||
</p>
|
||||
<p class="help-block" ng-if="has(['sendonly', 'receiveencrypted'], currentFolder.type)" translate translate-value-foldertype="{{currentFolder.type === 'sendonly' ? ('Send Only' | translate) : currentFolder.type === 'receiveencrypted' ? ('Receive Encrypted' | translate) : ''}}">
|
||||
Cannot be enabled when the folder type is "{%foldertype%}".
|
||||
</p>
|
||||
<label>
|
||||
<input type="checkbox" ng-disabled="currentFolder.type == 'receiveonly' || currentFolder.type == 'receiveencrypted' || currentFolder.syncXattrs" ng-checked="currentFolder.sendXattrs || currentFolder.syncXattrs" ng-model="currentFolder.sendXattrs" /> <span translate>Send Extended Attributes</span>
|
||||
</label>
|
||||
<p translate class="help-block">
|
||||
Enables sending extended attributes to other devices, but not applying incoming extended attributes. This can have a significant performance impact. Always enabled when "Sync Extended Attributes" is enabled.
|
||||
</p>
|
||||
<p class="help-block" ng-if="has(['receiveonly', 'receiveencrypted'], currentFolder.type)" translate translate-value-foldertype="{{currentFolder.type === 'receiveonly' ? ('Receive Only' | translate) : currentFolder.type === 'receiveencrypted' ? ('Receive Encrypted' | translate) : ''}}">
|
||||
Cannot be enabled when the folder type is "{%foldertype%}".
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
80
internal/blob/azureblob/azureblob.go
Normal file
80
internal/blob/azureblob/azureblob.go
Normal file
@@ -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
|
||||
}
|
||||
23
internal/blob/interface.go
Normal file
23
internal/blob/interface.go
Normal file
@@ -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
|
||||
}
|
||||
73
internal/db/counts.go
Normal file
73
internal/db/counts.go
Normal file
@@ -0,0 +1,73 @@
|
||||
// Copyright (C) 2014 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 db
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
)
|
||||
|
||||
type Counts struct {
|
||||
Files int
|
||||
Directories int
|
||||
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
|
||||
}
|
||||
|
||||
func (c Counts) Add(other Counts) Counts {
|
||||
return Counts{
|
||||
Files: c.Files + other.Files,
|
||||
Directories: c.Directories + other.Directories,
|
||||
Symlinks: c.Symlinks + other.Symlinks,
|
||||
Deleted: c.Deleted + other.Deleted,
|
||||
Bytes: c.Bytes + other.Bytes,
|
||||
Sequence: c.Sequence + other.Sequence,
|
||||
DeviceID: protocol.EmptyDeviceID,
|
||||
LocalFlags: c.LocalFlags | other.LocalFlags,
|
||||
}
|
||||
}
|
||||
|
||||
func (c Counts) TotalItems() int {
|
||||
return c.Files + c.Directories + c.Symlinks + c.Deleted
|
||||
}
|
||||
|
||||
func (c Counts) String() string {
|
||||
var flags strings.Builder
|
||||
if c.LocalFlags&protocol.FlagLocalNeeded != 0 {
|
||||
flags.WriteString("Need")
|
||||
}
|
||||
if c.LocalFlags&protocol.FlagLocalIgnored != 0 {
|
||||
flags.WriteString("Ignored")
|
||||
}
|
||||
if c.LocalFlags&protocol.FlagLocalMustRescan != 0 {
|
||||
flags.WriteString("Rescan")
|
||||
}
|
||||
if c.LocalFlags&protocol.FlagLocalReceiveOnly != 0 {
|
||||
flags.WriteString("Recvonly")
|
||||
}
|
||||
if c.LocalFlags&protocol.FlagLocalUnsupported != 0 {
|
||||
flags.WriteString("Unsupported")
|
||||
}
|
||||
if c.LocalFlags != 0 {
|
||||
flags.WriteString(fmt.Sprintf("(%x)", c.LocalFlags))
|
||||
}
|
||||
if flags.Len() == 0 {
|
||||
flags.WriteString("---")
|
||||
}
|
||||
return fmt.Sprintf("{Device:%v, Files:%d, Dirs:%d, Symlinks:%d, Del:%d, Bytes:%d, Seq:%d, Flags:%s}", c.DeviceID, c.Files, c.Directories, c.Symlinks, c.Deleted, c.Bytes, c.Sequence, flags.String())
|
||||
}
|
||||
|
||||
// Equal compares the numbers only, not sequence/dev/flags.
|
||||
func (c Counts) Equal(o Counts) bool {
|
||||
return c.Files == o.Files && c.Directories == o.Directories && c.Symlinks == o.Symlinks && c.Deleted == o.Deleted && c.Bytes == o.Bytes
|
||||
}
|
||||
123
internal/db/interface.go
Normal file
123
internal/db/interface.go
Normal file
@@ -0,0 +1,123 @@
|
||||
// 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 db // import "github.com/syncthing/syncthing/internal/db/sqlite"
|
||||
|
||||
import (
|
||||
"iter"
|
||||
"time"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/config"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
"github.com/thejerf/suture/v4"
|
||||
)
|
||||
|
||||
type DB interface {
|
||||
Service(maintenanceInterval time.Duration) suture.Service
|
||||
|
||||
// Basics
|
||||
Update(folder string, device protocol.DeviceID, fs []protocol.FileInfo) error
|
||||
Close() error
|
||||
|
||||
// Single files
|
||||
GetDeviceFile(folder string, device protocol.DeviceID, file string) (protocol.FileInfo, bool, error)
|
||||
GetGlobalAvailability(folder, file string) ([]protocol.DeviceID, error)
|
||||
GetGlobalFile(folder string, file string) (protocol.FileInfo, bool, error)
|
||||
|
||||
// File iterators
|
||||
//
|
||||
// n.b. there is a slight inconsistency in the return types where some
|
||||
// return a FileInfo iterator and some a FileMetadata iterator. The
|
||||
// latter is more lightweight, and the discrepancy depends on how the
|
||||
// functions tend to be used. We can introduce more variations as
|
||||
// required.
|
||||
AllGlobalFiles(folder string) (iter.Seq[FileMetadata], func() error)
|
||||
AllGlobalFilesPrefix(folder string, prefix string) (iter.Seq[FileMetadata], func() error)
|
||||
AllLocalFiles(folder string, device protocol.DeviceID) (iter.Seq[protocol.FileInfo], func() error)
|
||||
AllLocalFilesBySequence(folder string, device protocol.DeviceID, startSeq int64, limit int) (iter.Seq[protocol.FileInfo], func() error)
|
||||
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)
|
||||
|
||||
// Cleanup
|
||||
DropAllFiles(folder string, device protocol.DeviceID) error
|
||||
DropDevice(device protocol.DeviceID) error
|
||||
DropFilesNamed(folder string, device protocol.DeviceID, names []string) error
|
||||
DropFolder(folder string) error
|
||||
|
||||
// Various metadata
|
||||
GetDeviceSequence(folder string, device protocol.DeviceID) (int64, error)
|
||||
ListFolders() ([]string, error)
|
||||
ListDevicesForFolder(folder string) ([]protocol.DeviceID, error)
|
||||
RemoteSequences(folder string) (map[protocol.DeviceID]int64, error)
|
||||
|
||||
// Counts
|
||||
CountGlobal(folder string) (Counts, error)
|
||||
CountLocal(folder string, device protocol.DeviceID) (Counts, error)
|
||||
CountNeed(folder string, device protocol.DeviceID) (Counts, error)
|
||||
CountReceiveOnlyChanged(folder string) (Counts, error)
|
||||
|
||||
// Index IDs
|
||||
DropAllIndexIDs() error
|
||||
GetIndexID(folder string, device protocol.DeviceID) (protocol.IndexID, error)
|
||||
SetIndexID(folder string, device protocol.DeviceID, id protocol.IndexID) error
|
||||
|
||||
// MtimeFS
|
||||
DeleteMtime(folder, name string) error
|
||||
GetMtime(folder, name string) (ondisk, virtual time.Time)
|
||||
PutMtime(folder, name string, ondisk, virtual time.Time) error
|
||||
|
||||
KV
|
||||
}
|
||||
|
||||
// Generic KV store
|
||||
type KV interface {
|
||||
GetKV(key string) ([]byte, error)
|
||||
PutKV(key string, val []byte) error
|
||||
DeleteKV(key string) error
|
||||
PrefixKV(prefix string) (iter.Seq[KeyValue], func() error)
|
||||
}
|
||||
|
||||
type BlockMapEntry struct {
|
||||
BlocklistHash []byte
|
||||
Offset int64
|
||||
BlockIndex int
|
||||
Size int
|
||||
}
|
||||
|
||||
type KeyValue struct {
|
||||
Key string
|
||||
Value []byte
|
||||
}
|
||||
|
||||
type FileMetadata struct {
|
||||
Name string
|
||||
Sequence int64
|
||||
ModNanos int64
|
||||
Size int64
|
||||
LocalFlags int64
|
||||
Type protocol.FileInfoType
|
||||
Deleted bool
|
||||
Invalid bool
|
||||
}
|
||||
|
||||
func (f *FileMetadata) ModTime() time.Time {
|
||||
return time.Unix(0, f.ModNanos)
|
||||
}
|
||||
|
||||
func (f *FileMetadata) IsReceiveOnlyChanged() bool {
|
||||
return f.LocalFlags&protocol.FlagLocalReceiveOnly != 0
|
||||
}
|
||||
|
||||
func (f *FileMetadata) IsDirectory() bool {
|
||||
return f.Type == protocol.FileInfoTypeDirectory
|
||||
}
|
||||
|
||||
func (f *FileMetadata) ShouldConflict() bool {
|
||||
return f.LocalFlags&protocol.LocalConflictFlags != 0
|
||||
}
|
||||
229
internal/db/metrics.go
Normal file
229
internal/db/metrics.go
Normal file
@@ -0,0 +1,229 @@
|
||||
// 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 db
|
||||
|
||||
import (
|
||||
"iter"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
"github.com/syncthing/syncthing/lib/config"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
)
|
||||
|
||||
var (
|
||||
metricCurrentOperations = promauto.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Namespace: "syncthing",
|
||||
Subsystem: "db",
|
||||
Name: "operations_current",
|
||||
}, []string{"folder", "operation"})
|
||||
metricTotalOperationSeconds = promauto.NewCounterVec(prometheus.CounterOpts{
|
||||
Namespace: "syncthing",
|
||||
Subsystem: "db",
|
||||
Name: "operation_seconds_total",
|
||||
Help: "Total time spent in database operations, per folder and operation",
|
||||
}, []string{"folder", "operation"})
|
||||
metricTotalOperationsCount = promauto.NewCounterVec(prometheus.CounterOpts{
|
||||
Namespace: "syncthing",
|
||||
Subsystem: "db",
|
||||
Name: "operations_total",
|
||||
Help: "Total number of database operations, per folder and operation",
|
||||
}, []string{"folder", "operation"})
|
||||
metricTotalFilesUpdatedCount = promauto.NewCounterVec(prometheus.CounterOpts{
|
||||
Namespace: "syncthing",
|
||||
Subsystem: "db",
|
||||
Name: "files_updated_total",
|
||||
Help: "Total number of files updated",
|
||||
}, []string{"folder"})
|
||||
)
|
||||
|
||||
func MetricsWrap(db DB) DB {
|
||||
return metricsDB{db}
|
||||
}
|
||||
|
||||
type metricsDB struct {
|
||||
DB
|
||||
}
|
||||
|
||||
func (m metricsDB) account(folder, op string) func() {
|
||||
t0 := time.Now()
|
||||
metricCurrentOperations.WithLabelValues(folder, op).Inc()
|
||||
return func() {
|
||||
if dur := time.Since(t0).Seconds(); dur > 0 {
|
||||
metricTotalOperationSeconds.WithLabelValues(folder, op).Add(dur)
|
||||
}
|
||||
metricTotalOperationsCount.WithLabelValues(folder, op).Inc()
|
||||
metricCurrentOperations.WithLabelValues(folder, op).Dec()
|
||||
}
|
||||
}
|
||||
|
||||
func (m metricsDB) AllLocalFilesWithBlocksHash(folder string, h []byte) (iter.Seq[FileMetadata], func() error) {
|
||||
defer m.account(folder, "AllLocalFilesWithBlocksHash")()
|
||||
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)
|
||||
}
|
||||
|
||||
func (m metricsDB) AllGlobalFilesPrefix(folder string, prefix string) (iter.Seq[FileMetadata], func() error) {
|
||||
defer m.account(folder, "AllGlobalFilesPrefix")()
|
||||
return m.DB.AllGlobalFilesPrefix(folder, prefix)
|
||||
}
|
||||
|
||||
func (m metricsDB) AllLocalFiles(folder string, device protocol.DeviceID) (iter.Seq[protocol.FileInfo], func() error) {
|
||||
defer m.account(folder, "AllLocalFiles")()
|
||||
return m.DB.AllLocalFiles(folder, device)
|
||||
}
|
||||
|
||||
func (m metricsDB) AllLocalFilesWithPrefix(folder string, device protocol.DeviceID, prefix string) (iter.Seq[protocol.FileInfo], func() error) {
|
||||
defer m.account(folder, "AllLocalFilesPrefix")()
|
||||
return m.DB.AllLocalFilesWithPrefix(folder, device, prefix)
|
||||
}
|
||||
|
||||
func (m metricsDB) AllLocalFilesBySequence(folder string, device protocol.DeviceID, startSeq int64, limit int) (iter.Seq[protocol.FileInfo], func() error) {
|
||||
defer m.account(folder, "AllLocalFilesBySequence")()
|
||||
return m.DB.AllLocalFilesBySequence(folder, device, startSeq, limit)
|
||||
}
|
||||
|
||||
func (m metricsDB) AllNeededGlobalFiles(folder string, device protocol.DeviceID, order config.PullOrder, limit, offset int) (iter.Seq[protocol.FileInfo], func() error) {
|
||||
defer m.account(folder, "AllNeededGlobalFiles")()
|
||||
return m.DB.AllNeededGlobalFiles(folder, device, order, limit, offset)
|
||||
}
|
||||
|
||||
func (m metricsDB) GetGlobalAvailability(folder, file string) ([]protocol.DeviceID, error) {
|
||||
defer m.account(folder, "GetGlobalAvailability")()
|
||||
return m.DB.GetGlobalAvailability(folder, file)
|
||||
}
|
||||
|
||||
func (m metricsDB) AllLocalBlocksWithHash(hash []byte) ([]BlockMapEntry, error) {
|
||||
defer m.account("-", "AllLocalBlocksWithHash")()
|
||||
return m.DB.AllLocalBlocksWithHash(hash)
|
||||
}
|
||||
|
||||
func (m metricsDB) Close() error {
|
||||
defer m.account("-", "Close")()
|
||||
return m.DB.Close()
|
||||
}
|
||||
|
||||
func (m metricsDB) ListDevicesForFolder(folder string) ([]protocol.DeviceID, error) {
|
||||
defer m.account(folder, "ListDevicesForFolder")()
|
||||
return m.DB.ListDevicesForFolder(folder)
|
||||
}
|
||||
|
||||
func (m metricsDB) RemoteSequences(folder string) (map[protocol.DeviceID]int64, error) {
|
||||
defer m.account(folder, "RemoteSequences")()
|
||||
return m.DB.RemoteSequences(folder)
|
||||
}
|
||||
|
||||
func (m metricsDB) DropAllFiles(folder string, device protocol.DeviceID) error {
|
||||
defer m.account(folder, "DropAllFiles")()
|
||||
return m.DB.DropAllFiles(folder, device)
|
||||
}
|
||||
|
||||
func (m metricsDB) DropDevice(device protocol.DeviceID) error {
|
||||
defer m.account("-", "DropDevice")()
|
||||
return m.DB.DropDevice(device)
|
||||
}
|
||||
|
||||
func (m metricsDB) DropFilesNamed(folder string, device protocol.DeviceID, names []string) error {
|
||||
defer m.account(folder, "DropFilesNamed")()
|
||||
return m.DB.DropFilesNamed(folder, device, names)
|
||||
}
|
||||
|
||||
func (m metricsDB) DropFolder(folder string) error {
|
||||
defer m.account(folder, "DropFolder")()
|
||||
return m.DB.DropFolder(folder)
|
||||
}
|
||||
|
||||
func (m metricsDB) DropAllIndexIDs() error {
|
||||
defer m.account("-", "IndexIDDropAll")()
|
||||
return m.DB.DropAllIndexIDs()
|
||||
}
|
||||
|
||||
func (m metricsDB) ListFolders() ([]string, error) {
|
||||
defer m.account("-", "ListFolders")()
|
||||
return m.DB.ListFolders()
|
||||
}
|
||||
|
||||
func (m metricsDB) GetGlobalFile(folder string, file string) (protocol.FileInfo, bool, error) {
|
||||
defer m.account(folder, "GetGlobalFile")()
|
||||
return m.DB.GetGlobalFile(folder, file)
|
||||
}
|
||||
|
||||
func (m metricsDB) CountGlobal(folder string) (Counts, error) {
|
||||
defer m.account(folder, "CountGlobal")()
|
||||
return m.DB.CountGlobal(folder)
|
||||
}
|
||||
|
||||
func (m metricsDB) GetIndexID(folder string, device protocol.DeviceID) (protocol.IndexID, error) {
|
||||
defer m.account(folder, "IndexIDGet")()
|
||||
return m.DB.GetIndexID(folder, device)
|
||||
}
|
||||
|
||||
func (m metricsDB) GetDeviceFile(folder string, device protocol.DeviceID, file string) (protocol.FileInfo, bool, error) {
|
||||
defer m.account(folder, "GetDeviceFile")()
|
||||
return m.DB.GetDeviceFile(folder, device, file)
|
||||
}
|
||||
|
||||
func (m metricsDB) CountLocal(folder string, device protocol.DeviceID) (Counts, error) {
|
||||
defer m.account(folder, "CountLocal")()
|
||||
return m.DB.CountLocal(folder, device)
|
||||
}
|
||||
|
||||
func (m metricsDB) CountNeed(folder string, device protocol.DeviceID) (Counts, error) {
|
||||
defer m.account(folder, "CountNeed")()
|
||||
return m.DB.CountNeed(folder, device)
|
||||
}
|
||||
|
||||
func (m metricsDB) CountReceiveOnlyChanged(folder string) (Counts, error) {
|
||||
defer m.account(folder, "CountReceiveOnlyChanged")()
|
||||
return m.DB.CountReceiveOnlyChanged(folder)
|
||||
}
|
||||
|
||||
func (m metricsDB) GetDeviceSequence(folder string, device protocol.DeviceID) (int64, error) {
|
||||
defer m.account(folder, "GetDeviceSequence")()
|
||||
return m.DB.GetDeviceSequence(folder, device)
|
||||
}
|
||||
|
||||
func (m metricsDB) SetIndexID(folder string, device protocol.DeviceID, id protocol.IndexID) error {
|
||||
defer m.account(folder, "IndexIDSet")()
|
||||
return m.DB.SetIndexID(folder, device, id)
|
||||
}
|
||||
|
||||
func (m metricsDB) Update(folder string, device protocol.DeviceID, fs []protocol.FileInfo) error {
|
||||
defer m.account(folder, "Update")()
|
||||
defer metricTotalFilesUpdatedCount.WithLabelValues(folder).Add(float64(len(fs)))
|
||||
return m.DB.Update(folder, device, fs)
|
||||
}
|
||||
|
||||
func (m metricsDB) GetKV(key string) ([]byte, error) {
|
||||
defer m.account("-", "GetKV")()
|
||||
return m.DB.GetKV(key)
|
||||
}
|
||||
|
||||
func (m metricsDB) PutKV(key string, val []byte) error {
|
||||
defer m.account("-", "PutKV")()
|
||||
return m.DB.PutKV(key, val)
|
||||
}
|
||||
|
||||
func (m metricsDB) DeleteKV(key string) error {
|
||||
defer m.account("-", "DeleteKV")()
|
||||
return m.DB.DeleteKV(key)
|
||||
}
|
||||
|
||||
func (m metricsDB) PrefixKV(prefix string) (iter.Seq[KeyValue], func() error) {
|
||||
defer m.account("-", "PrefixKV")()
|
||||
return m.DB.PrefixKV(prefix)
|
||||
}
|
||||
@@ -8,6 +8,7 @@ package db
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
@@ -17,6 +18,14 @@ import (
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
)
|
||||
|
||||
type ObservedDB struct {
|
||||
kv KV
|
||||
}
|
||||
|
||||
func NewObservedDB(kv KV) *ObservedDB {
|
||||
return &ObservedDB{kv: kv}
|
||||
}
|
||||
|
||||
type ObservedFolder struct {
|
||||
Time time.Time `json:"time"`
|
||||
Label string `json:"label"`
|
||||
@@ -52,39 +61,42 @@ func (o *ObservedDevice) fromWire(w *dbproto.ObservedDevice) {
|
||||
o.Address = w.GetAddress()
|
||||
}
|
||||
|
||||
func (db *Lowlevel) AddOrUpdatePendingDevice(device protocol.DeviceID, name, address string) error {
|
||||
key := db.keyer.GeneratePendingDeviceKey(nil, device[:])
|
||||
func (db *ObservedDB) AddOrUpdatePendingDevice(device protocol.DeviceID, name, address string) error {
|
||||
key := "device/" + device.String()
|
||||
od := &dbproto.ObservedDevice{
|
||||
Time: timestamppb.New(time.Now().Truncate(time.Second)),
|
||||
Name: name,
|
||||
Address: address,
|
||||
}
|
||||
return db.Put(key, mustMarshal(od))
|
||||
return db.kv.PutKV(key, mustMarshal(od))
|
||||
}
|
||||
|
||||
func (db *Lowlevel) RemovePendingDevice(device protocol.DeviceID) error {
|
||||
key := db.keyer.GeneratePendingDeviceKey(nil, device[:])
|
||||
return db.Delete(key)
|
||||
func (db *ObservedDB) RemovePendingDevice(device protocol.DeviceID) error {
|
||||
key := "device/" + device.String()
|
||||
return db.kv.DeleteKV(key)
|
||||
}
|
||||
|
||||
// PendingDevices enumerates all entries. Invalid ones are dropped from the database
|
||||
// after a warning log message, as a side-effect.
|
||||
func (db *Lowlevel) PendingDevices() (map[protocol.DeviceID]ObservedDevice, error) {
|
||||
iter, err := db.NewPrefixIterator([]byte{KeyTypePendingDevice})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer iter.Release()
|
||||
func (db *ObservedDB) PendingDevices() (map[protocol.DeviceID]ObservedDevice, error) {
|
||||
res := make(map[protocol.DeviceID]ObservedDevice)
|
||||
for iter.Next() {
|
||||
keyDev := db.keyer.DeviceFromPendingDeviceKey(iter.Key())
|
||||
deviceID, err := protocol.DeviceIDFromBytes(keyDev)
|
||||
it, errFn := db.kv.PrefixKV("device/")
|
||||
for kv := range it {
|
||||
_, keyDev, ok := strings.Cut(kv.Key, "/")
|
||||
if !ok {
|
||||
if err := db.kv.DeleteKV(kv.Key); err != nil {
|
||||
return nil, fmt.Errorf("delete invalid pending device: %w", err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
deviceID, err := protocol.DeviceIDFromString(keyDev)
|
||||
var protoD dbproto.ObservedDevice
|
||||
var od ObservedDevice
|
||||
if err != nil {
|
||||
goto deleteKey
|
||||
}
|
||||
if err = proto.Unmarshal(iter.Value(), &protoD); err != nil {
|
||||
if err = proto.Unmarshal(kv.Value, &protoD); err != nil {
|
||||
goto deleteKey
|
||||
}
|
||||
od.fromWire(&protoD)
|
||||
@@ -94,52 +106,37 @@ func (db *Lowlevel) PendingDevices() (map[protocol.DeviceID]ObservedDevice, erro
|
||||
// Deleting invalid entries is the only possible "repair" measure and
|
||||
// appropriate for the importance of pending entries. They will come back
|
||||
// soon if still relevant.
|
||||
l.Infof("Invalid pending device entry, deleting from database: %x", iter.Key())
|
||||
if err := db.Delete(iter.Key()); err != nil {
|
||||
return nil, err
|
||||
if err := db.kv.DeleteKV(kv.Key); err != nil {
|
||||
return nil, fmt.Errorf("delete invalid pending device: %w", err)
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
return res, errFn()
|
||||
}
|
||||
|
||||
func (db *Lowlevel) AddOrUpdatePendingFolder(id string, of ObservedFolder, device protocol.DeviceID) error {
|
||||
key, err := db.keyer.GeneratePendingFolderKey(nil, device[:], []byte(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return db.Put(key, mustMarshal(of.toWire()))
|
||||
func (db *ObservedDB) AddOrUpdatePendingFolder(id string, of ObservedFolder, device protocol.DeviceID) error {
|
||||
key := "folder/" + device.String() + "/" + id
|
||||
return db.kv.PutKV(key, mustMarshal(of.toWire()))
|
||||
}
|
||||
|
||||
// RemovePendingFolderForDevice removes entries for specific folder / device combinations.
|
||||
func (db *Lowlevel) RemovePendingFolderForDevice(id string, device protocol.DeviceID) error {
|
||||
key, err := db.keyer.GeneratePendingFolderKey(nil, device[:], []byte(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return db.Delete(key)
|
||||
func (db *ObservedDB) RemovePendingFolderForDevice(id string, device protocol.DeviceID) error {
|
||||
key := "folder/" + device.String() + "/" + id
|
||||
return db.kv.DeleteKV(key)
|
||||
}
|
||||
|
||||
// RemovePendingFolder removes all entries matching a specific folder ID.
|
||||
func (db *Lowlevel) RemovePendingFolder(id string) error {
|
||||
iter, err := db.NewPrefixIterator([]byte{KeyTypePendingFolder})
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating iterator: %w", err)
|
||||
}
|
||||
defer iter.Release()
|
||||
var iterErr error
|
||||
for iter.Next() {
|
||||
if id != string(db.keyer.FolderFromPendingFolderKey(iter.Key())) {
|
||||
func (db *ObservedDB) RemovePendingFolder(id string) error {
|
||||
it, errFn := db.kv.PrefixKV("folder/")
|
||||
for kv := range it {
|
||||
parts := strings.Split(kv.Key, "/")
|
||||
if len(parts) != 3 || parts[2] != id {
|
||||
continue
|
||||
}
|
||||
if err = db.Delete(iter.Key()); err != nil {
|
||||
if iterErr != nil {
|
||||
l.Debugf("Repeat error removing pending folder: %v", err)
|
||||
} else {
|
||||
iterErr = err
|
||||
}
|
||||
if err := db.kv.DeleteKV(kv.Key); err != nil {
|
||||
return fmt.Errorf("delete pending folder: %w", err)
|
||||
}
|
||||
}
|
||||
return iterErr
|
||||
return errFn()
|
||||
}
|
||||
|
||||
// Consolidated information about a pending folder
|
||||
@@ -147,41 +144,37 @@ type PendingFolder struct {
|
||||
OfferedBy map[protocol.DeviceID]ObservedFolder `json:"offeredBy"`
|
||||
}
|
||||
|
||||
func (db *Lowlevel) PendingFolders() (map[string]PendingFolder, error) {
|
||||
func (db *ObservedDB) PendingFolders() (map[string]PendingFolder, error) {
|
||||
return db.PendingFoldersForDevice(protocol.EmptyDeviceID)
|
||||
}
|
||||
|
||||
// PendingFoldersForDevice enumerates only entries matching the given device ID, unless it
|
||||
// is EmptyDeviceID. Invalid ones are dropped from the database after a info log
|
||||
// message, as a side-effect.
|
||||
func (db *Lowlevel) PendingFoldersForDevice(device protocol.DeviceID) (map[string]PendingFolder, error) {
|
||||
var err error
|
||||
prefixKey := []byte{KeyTypePendingFolder}
|
||||
func (db *ObservedDB) PendingFoldersForDevice(device protocol.DeviceID) (map[string]PendingFolder, error) {
|
||||
prefix := "folder/"
|
||||
if device != protocol.EmptyDeviceID {
|
||||
prefixKey, err = db.keyer.GeneratePendingFolderKey(nil, device[:], nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
prefix += device.String() + "/"
|
||||
}
|
||||
iter, err := db.NewPrefixIterator(prefixKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer iter.Release()
|
||||
res := make(map[string]PendingFolder)
|
||||
for iter.Next() {
|
||||
keyDev, ok := db.keyer.DeviceFromPendingFolderKey(iter.Key())
|
||||
deviceID, err := protocol.DeviceIDFromBytes(keyDev)
|
||||
it, errFn := db.kv.PrefixKV(prefix)
|
||||
for kv := range it {
|
||||
parts := strings.Split(kv.Key, "/")
|
||||
if len(parts) != 3 {
|
||||
continue
|
||||
}
|
||||
keyDev := parts[1]
|
||||
deviceID, err := protocol.DeviceIDFromString(keyDev)
|
||||
var protoF dbproto.ObservedFolder
|
||||
var of ObservedFolder
|
||||
var folderID string
|
||||
if !ok || err != nil {
|
||||
if err != nil {
|
||||
goto deleteKey
|
||||
}
|
||||
if folderID = string(db.keyer.FolderFromPendingFolderKey(iter.Key())); len(folderID) < 1 {
|
||||
if folderID = parts[2]; len(folderID) < 1 {
|
||||
goto deleteKey
|
||||
}
|
||||
if err = proto.Unmarshal(iter.Value(), &protoF); err != nil {
|
||||
if err = proto.Unmarshal(kv.Value, &protoF); err != nil {
|
||||
goto deleteKey
|
||||
}
|
||||
if _, ok := res[folderID]; !ok {
|
||||
@@ -196,10 +189,17 @@ func (db *Lowlevel) PendingFoldersForDevice(device protocol.DeviceID) (map[strin
|
||||
// Deleting invalid entries is the only possible "repair" measure and
|
||||
// appropriate for the importance of pending entries. They will come back
|
||||
// soon if still relevant.
|
||||
l.Infof("Invalid pending folder entry, deleting from database: %x", iter.Key())
|
||||
if err := db.Delete(iter.Key()); err != nil {
|
||||
return nil, err
|
||||
if err := db.kv.DeleteKV(kv.Key); err != nil {
|
||||
return nil, fmt.Errorf("delete invalid pending folder: %w", err)
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
return res, errFn()
|
||||
}
|
||||
|
||||
func mustMarshal(m proto.Message) []byte {
|
||||
bs, err := proto.Marshal(m)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return bs
|
||||
}
|
||||
@@ -108,29 +108,8 @@ type Iterator interface {
|
||||
// is empty for a db in memory.
|
||||
type Backend interface {
|
||||
Reader
|
||||
Writer
|
||||
NewReadTransaction() (ReadTransaction, error)
|
||||
NewWriteTransaction(hooks ...CommitHook) (WriteTransaction, error)
|
||||
Close() error
|
||||
Compact() error
|
||||
Location() string
|
||||
}
|
||||
|
||||
type Tuning int
|
||||
|
||||
const (
|
||||
// N.b. these constants must match those in lib/config.Tuning!
|
||||
TuningAuto Tuning = iota
|
||||
TuningSmall
|
||||
TuningLarge
|
||||
)
|
||||
|
||||
func Open(path string, tuning Tuning) (Backend, error) {
|
||||
return OpenLevelDB(path, tuning)
|
||||
}
|
||||
|
||||
func OpenMemory() Backend {
|
||||
return OpenLevelDBMemory()
|
||||
}
|
||||
|
||||
var (
|
||||
113
internal/db/olddb/backend/leveldb_backend.go
Normal file
113
internal/db/olddb/backend/leveldb_backend.go
Normal file
@@ -0,0 +1,113 @@
|
||||
// Copyright (C) 2018 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 backend
|
||||
|
||||
import (
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
"github.com/syndtr/goleveldb/leveldb/iterator"
|
||||
"github.com/syndtr/goleveldb/leveldb/util"
|
||||
)
|
||||
|
||||
// leveldbBackend implements Backend on top of a leveldb
|
||||
type leveldbBackend struct {
|
||||
ldb *leveldb.DB
|
||||
closeWG *closeWaitGroup
|
||||
location string
|
||||
}
|
||||
|
||||
func newLeveldbBackend(ldb *leveldb.DB, location string) *leveldbBackend {
|
||||
return &leveldbBackend{
|
||||
ldb: ldb,
|
||||
closeWG: &closeWaitGroup{},
|
||||
location: location,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *leveldbBackend) NewReadTransaction() (ReadTransaction, error) {
|
||||
return b.newSnapshot()
|
||||
}
|
||||
|
||||
func (b *leveldbBackend) newSnapshot() (leveldbSnapshot, error) {
|
||||
rel, err := newReleaser(b.closeWG)
|
||||
if err != nil {
|
||||
return leveldbSnapshot{}, err
|
||||
}
|
||||
snap, err := b.ldb.GetSnapshot()
|
||||
if err != nil {
|
||||
rel.Release()
|
||||
return leveldbSnapshot{}, wrapLeveldbErr(err)
|
||||
}
|
||||
return leveldbSnapshot{
|
||||
snap: snap,
|
||||
rel: rel,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (b *leveldbBackend) Close() error {
|
||||
b.closeWG.CloseWait()
|
||||
return wrapLeveldbErr(b.ldb.Close())
|
||||
}
|
||||
|
||||
func (b *leveldbBackend) Get(key []byte) ([]byte, error) {
|
||||
val, err := b.ldb.Get(key, nil)
|
||||
return val, wrapLeveldbErr(err)
|
||||
}
|
||||
|
||||
func (b *leveldbBackend) NewPrefixIterator(prefix []byte) (Iterator, error) {
|
||||
return &leveldbIterator{b.ldb.NewIterator(util.BytesPrefix(prefix), nil)}, nil
|
||||
}
|
||||
|
||||
func (b *leveldbBackend) NewRangeIterator(first, last []byte) (Iterator, error) {
|
||||
return &leveldbIterator{b.ldb.NewIterator(&util.Range{Start: first, Limit: last}, nil)}, nil
|
||||
}
|
||||
|
||||
func (b *leveldbBackend) Location() string {
|
||||
return b.location
|
||||
}
|
||||
|
||||
// leveldbSnapshot implements backend.ReadTransaction
|
||||
type leveldbSnapshot struct {
|
||||
snap *leveldb.Snapshot
|
||||
rel *releaser
|
||||
}
|
||||
|
||||
func (l leveldbSnapshot) Get(key []byte) ([]byte, error) {
|
||||
val, err := l.snap.Get(key, nil)
|
||||
return val, wrapLeveldbErr(err)
|
||||
}
|
||||
|
||||
func (l leveldbSnapshot) NewPrefixIterator(prefix []byte) (Iterator, error) {
|
||||
return l.snap.NewIterator(util.BytesPrefix(prefix), nil), nil
|
||||
}
|
||||
|
||||
func (l leveldbSnapshot) NewRangeIterator(first, last []byte) (Iterator, error) {
|
||||
return l.snap.NewIterator(&util.Range{Start: first, Limit: last}, nil), nil
|
||||
}
|
||||
|
||||
func (l leveldbSnapshot) Release() {
|
||||
l.snap.Release()
|
||||
l.rel.Release()
|
||||
}
|
||||
|
||||
type leveldbIterator struct {
|
||||
iterator.Iterator
|
||||
}
|
||||
|
||||
func (it *leveldbIterator) Error() error {
|
||||
return wrapLeveldbErr(it.Iterator.Error())
|
||||
}
|
||||
|
||||
// wrapLeveldbErr wraps errors so that the backend package can recognize them
|
||||
func wrapLeveldbErr(err error) error {
|
||||
switch err {
|
||||
case leveldb.ErrClosed:
|
||||
return errClosed
|
||||
case leveldb.ErrNotFound:
|
||||
return errNotFound
|
||||
}
|
||||
return err
|
||||
}
|
||||
32
internal/db/olddb/backend/leveldb_open.go
Normal file
32
internal/db/olddb/backend/leveldb_open.go
Normal file
@@ -0,0 +1,32 @@
|
||||
// Copyright (C) 2018 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 backend
|
||||
|
||||
import (
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
"github.com/syndtr/goleveldb/leveldb/opt"
|
||||
)
|
||||
|
||||
const dbMaxOpenFiles = 100
|
||||
|
||||
// OpenLevelDBRO attempts to open the database at the given location, read
|
||||
// only.
|
||||
func OpenLevelDBRO(location string) (Backend, error) {
|
||||
opts := &opt.Options{
|
||||
OpenFilesCacheCapacity: dbMaxOpenFiles,
|
||||
ReadOnly: true,
|
||||
}
|
||||
ldb, err := open(location, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newLeveldbBackend(ldb, location), nil
|
||||
}
|
||||
|
||||
func open(location string, opts *opt.Options) (*leveldb.DB, error) {
|
||||
return leveldb.OpenFile(location, opts)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user