Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0945304a79 | ||
|
|
9703dd9f57 | ||
|
|
259e9ef08e | ||
|
|
6a0c6128d8 |
+2
-17
@@ -1,28 +1,13 @@
|
|||||||
version: 2
|
version: 2
|
||||||
updates:
|
updates:
|
||||||
|
|
||||||
- package-ecosystem: "github-actions"
|
- package-ecosystem: "github-actions"
|
||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: monthly
|
interval: monthly
|
||||||
cooldown:
|
open-pull-requests-limit: 10
|
||||||
default-days: 14
|
|
||||||
groups:
|
|
||||||
actions:
|
|
||||||
applies-to: version-updates
|
|
||||||
patterns:
|
|
||||||
- "*"
|
|
||||||
|
|
||||||
- package-ecosystem: "gomod"
|
- package-ecosystem: "gomod"
|
||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: monthly
|
interval: monthly
|
||||||
allow:
|
open-pull-requests-limit: 10
|
||||||
- dependency-type: direct
|
|
||||||
cooldown:
|
|
||||||
default-days: 14
|
|
||||||
groups:
|
|
||||||
dependencies:
|
|
||||||
applies-to: version-updates
|
|
||||||
patterns:
|
|
||||||
- "*"
|
|
||||||
|
|||||||
@@ -1,52 +0,0 @@
|
|||||||
version: 1
|
|
||||||
creds:
|
|
||||||
- registry: docker.io
|
|
||||||
user: "{{env \"DOCKERHUB_USERNAME\"}}"
|
|
||||||
pass: "{{env \"DOCKERHUB_TOKEN\"}}"
|
|
||||||
|
|
||||||
defaults:
|
|
||||||
ratelimit:
|
|
||||||
min: 100
|
|
||||||
retry: 1m
|
|
||||||
parallel: 4
|
|
||||||
|
|
||||||
sync:
|
|
||||||
|
|
||||||
- source: ghcr.io/syncthing/syncthing
|
|
||||||
target: docker.io/syncthing/syncthing
|
|
||||||
type: repository
|
|
||||||
tags:
|
|
||||||
allow:
|
|
||||||
- latest
|
|
||||||
- rc
|
|
||||||
- edge
|
|
||||||
- \d+
|
|
||||||
- \d+\.\d+
|
|
||||||
- \d+\.\d+\.\d+
|
|
||||||
- \d+\.\d+\.\d+-rc\.\d+
|
|
||||||
|
|
||||||
- source: ghcr.io/syncthing/relaysrv
|
|
||||||
target: docker.io/syncthing/relaysrv
|
|
||||||
type: repository
|
|
||||||
tags:
|
|
||||||
allow:
|
|
||||||
- latest
|
|
||||||
- rc
|
|
||||||
- edge
|
|
||||||
- \d+
|
|
||||||
- \d+\.\d+
|
|
||||||
- \d+\.\d+\.\d+
|
|
||||||
- \d+\.\d+\.\d+-rc\.\d+
|
|
||||||
|
|
||||||
- source: ghcr.io/syncthing/discosrv
|
|
||||||
target: docker.io/syncthing/discosrv
|
|
||||||
type: repository
|
|
||||||
tags:
|
|
||||||
allow:
|
|
||||||
- latest
|
|
||||||
- rc
|
|
||||||
- edge
|
|
||||||
- \d+
|
|
||||||
- \d+\.\d+
|
|
||||||
- \d+\.\d+\.\d+
|
|
||||||
- \d+\.\d+\.\d+-rc\.\d+
|
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
name: Build Infrastructure Images
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- infrastructure
|
||||||
|
- infra-*
|
||||||
|
|
||||||
|
env:
|
||||||
|
GO_VERSION: "~1.24.0"
|
||||||
|
CGO_ENABLED: "0"
|
||||||
|
BUILD_USER: docker
|
||||||
|
BUILD_HOST: github.syncthing.net
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
docker-syncthing:
|
||||||
|
name: Build and push Docker images
|
||||||
|
if: github.repository == 'syncthing/syncthing'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
environment: docker
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
pkg:
|
||||||
|
- stcrashreceiver
|
||||||
|
- strelaypoolsrv
|
||||||
|
- stupgrades
|
||||||
|
- ursrv
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: ${{ env.GO_VERSION }}
|
||||||
|
check-latest: true
|
||||||
|
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Login to GHCR
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build binaries
|
||||||
|
run: |
|
||||||
|
for arch in arm64 amd64; do
|
||||||
|
go run build.go -goos linux -goarch "$arch" build ${{ matrix.pkg }}
|
||||||
|
mv ${{ matrix.pkg }} ${{ matrix.pkg }}-linux-"$arch"
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Set Docker tags (all branches)
|
||||||
|
run: |
|
||||||
|
tags=docker.io/syncthing/${{ matrix.pkg }}:${{ github.sha }},ghcr.io/syncthing/infra/${{ matrix.pkg }}:${{ github.sha }}
|
||||||
|
echo "TAGS=$tags" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Set Docker tags (latest)
|
||||||
|
if: github.ref == 'refs/heads/infrastructure'
|
||||||
|
run: |
|
||||||
|
tags=docker.io/syncthing/${{ matrix.pkg }}:latest,ghcr.io/syncthing/infra/${{ matrix.pkg }}:latest,${{ env.TAGS }}
|
||||||
|
echo "TAGS=$tags" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Build and push
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./Dockerfile.${{ matrix.pkg }}
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
push: true
|
||||||
|
tags: ${{ env.TAGS }}
|
||||||
|
labels: |
|
||||||
|
org.opencontainers.image.revision=${{ github.sha }}
|
||||||
@@ -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
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,49 @@
|
|||||||
|
name: Run PR linters
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
|
||||||
|
#
|
||||||
|
# golangci-lint runs a suite of static analysis checks on the code
|
||||||
|
#
|
||||||
|
|
||||||
|
golangci:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: Golangci-lint
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: 'stable'
|
||||||
|
|
||||||
|
- name: ensure asset generation
|
||||||
|
run: go run build.go assets
|
||||||
|
|
||||||
|
- name: golangci-lint
|
||||||
|
uses: golangci/golangci-lint-action@v8
|
||||||
|
with:
|
||||||
|
only-new-issues: true
|
||||||
|
|
||||||
|
#
|
||||||
|
# Meta checks for formatting, copyright, etc
|
||||||
|
#
|
||||||
|
|
||||||
|
meta:
|
||||||
|
name: Meta checks
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: 'stable'
|
||||||
|
|
||||||
|
- run: |
|
||||||
|
go run build.go assets
|
||||||
|
go test -v ./meta
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
name: PR metadata
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request_target:
|
||||||
|
types:
|
||||||
|
- opened
|
||||||
|
- reopened
|
||||||
|
- edited
|
||||||
|
- synchronize
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
|
||||||
|
#
|
||||||
|
# Set labels on PRs, which are then used to categorise release notes
|
||||||
|
#
|
||||||
|
|
||||||
|
labels:
|
||||||
|
name: Set labels
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: srvaroa/labeler@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
name: Release Syncthing
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- release
|
||||||
|
- release-rc*
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
create-release-tag:
|
||||||
|
name: Create release tag
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
environment: release
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
ref: ${{ github.ref }} # https://github.com/actions/checkout/issues/882
|
||||||
|
token: ${{ secrets.ACTIONS_GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: stable
|
||||||
|
|
||||||
|
- name: Determine version to release
|
||||||
|
run: |
|
||||||
|
if [[ "$GITHUB_REF_NAME" == "release" ]] ; then
|
||||||
|
next=$(go run ./script/next-version.go)
|
||||||
|
else
|
||||||
|
next=$(go run ./script/next-version.go --pre)
|
||||||
|
fi
|
||||||
|
echo "NEXT=$next" >> $GITHUB_ENV
|
||||||
|
echo "Next version is $next"
|
||||||
|
|
||||||
|
prev=$(git describe --exclude "*-*" --abbrev=0)
|
||||||
|
echo "PREV=$prev" >> $GITHUB_ENV
|
||||||
|
echo "Previous version is $prev"
|
||||||
|
|
||||||
|
- name: Determine release notes
|
||||||
|
run: |
|
||||||
|
go run ./script/relnotes.go --new-ver "$NEXT" --branch "$GITHUB_REF_NAME" --prev-ver "$PREV" > notes.md
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.ACTIONS_GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Create and push tag
|
||||||
|
run: |
|
||||||
|
git config --global user.name 'Syncthing Release Automation'
|
||||||
|
git config --global user.email 'release@syncthing.net'
|
||||||
|
git tag -a -F notes.md --cleanup=whitespace "$NEXT"
|
||||||
|
git push origin "$NEXT"
|
||||||
|
|
||||||
|
- name: Trigger the build
|
||||||
|
uses: benc-uk/workflow-dispatch@v1
|
||||||
|
with:
|
||||||
|
workflow: build-syncthing.yaml
|
||||||
|
ref: refs/tags/${{ env.NEXT }}
|
||||||
|
token: ${{ secrets.ACTIONS_GITHUB_TOKEN }}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
name: Trigger nightly build & release
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
schedule:
|
||||||
|
# Run nightly build at 01:00 UTC
|
||||||
|
- cron: '00 01 * * *'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
|
||||||
|
trigger-nightly:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: Push to release-nightly to trigger build
|
||||||
|
steps:
|
||||||
|
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.ACTIONS_GITHUB_TOKEN }}
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- run: |
|
||||||
|
git push origin main:release-nightly
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
name: Update translations and documentation
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
schedule:
|
||||||
|
- cron: '42 3 * * 1'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
|
||||||
|
update_transifex_docs:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: Update translations and documentation
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
token: ${{ secrets.ACTIONS_GITHUB_TOKEN }}
|
||||||
|
- uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: stable
|
||||||
|
- run: |
|
||||||
|
set -euo pipefail
|
||||||
|
git config --global user.name 'Syncthing Release Automation'
|
||||||
|
git config --global user.email 'release@syncthing.net'
|
||||||
|
bash build.sh translate
|
||||||
|
bash build.sh prerelease
|
||||||
|
git push
|
||||||
|
env:
|
||||||
|
WEBLATE_TOKEN: ${{ secrets.WEBLATE_TOKEN }}
|
||||||
@@ -17,4 +17,5 @@ deb
|
|||||||
*.bz2
|
*.bz2
|
||||||
/repos
|
/repos
|
||||||
/proto/scripts/protoc-gen-gosyncthing
|
/proto/scripts/protoc-gen-gosyncthing
|
||||||
|
/gui/next-gen-gui
|
||||||
/compat.json
|
/compat.json
|
||||||
|
|||||||
@@ -4,18 +4,15 @@ linters:
|
|||||||
disable:
|
disable:
|
||||||
- cyclop
|
- cyclop
|
||||||
- depguard
|
- depguard
|
||||||
- err113
|
|
||||||
- exhaustive
|
- exhaustive
|
||||||
- exhaustruct
|
- exhaustruct
|
||||||
- forbidigo
|
- forbidigo
|
||||||
- funcorder
|
|
||||||
- funlen
|
- funlen
|
||||||
- gochecknoglobals
|
- gochecknoglobals
|
||||||
- gochecknoinits
|
- gochecknoinits
|
||||||
- gocognit
|
- gocognit
|
||||||
- goconst
|
- goconst
|
||||||
- gocyclo
|
- gocyclo
|
||||||
- godot
|
|
||||||
- godox
|
- godox
|
||||||
- gomoddirectives
|
- gomoddirectives
|
||||||
- inamedparam
|
- inamedparam
|
||||||
@@ -27,7 +24,6 @@ linters:
|
|||||||
- musttag
|
- musttag
|
||||||
- nestif
|
- nestif
|
||||||
- nlreturn
|
- nlreturn
|
||||||
- noinlineerr
|
|
||||||
- nonamedreturns
|
- nonamedreturns
|
||||||
- paralleltest
|
- paralleltest
|
||||||
- prealloc
|
- prealloc
|
||||||
@@ -43,7 +39,6 @@ linters:
|
|||||||
- whitespace
|
- whitespace
|
||||||
- wrapcheck
|
- wrapcheck
|
||||||
- wsl
|
- wsl
|
||||||
- wsl_v5
|
|
||||||
exclusions:
|
exclusions:
|
||||||
generated: lax
|
generated: lax
|
||||||
presets:
|
presets:
|
||||||
@@ -53,38 +48,11 @@ linters:
|
|||||||
- std-error-handling
|
- std-error-handling
|
||||||
paths:
|
paths:
|
||||||
- internal/gen
|
- internal/gen
|
||||||
- internal/db/olddb
|
|
||||||
- cmd/dev
|
- cmd/dev
|
||||||
- repos
|
- repos
|
||||||
- third_party$
|
- third_party$
|
||||||
- builtin$
|
- builtin$
|
||||||
- examples$
|
- examples$
|
||||||
- _test\.go$
|
|
||||||
rules:
|
|
||||||
# relax the slog rules for debug lines, for now
|
|
||||||
- linters: [sloglint]
|
|
||||||
source: Debug
|
|
||||||
# contexts are irrelevant for SQLite
|
|
||||||
- linters: [noctx]
|
|
||||||
text: database/sql
|
|
||||||
# Rollback errors can be ignored
|
|
||||||
- linters: [errcheck]
|
|
||||||
source: Rollback
|
|
||||||
# Embedded fields named in selectors may add clarity
|
|
||||||
- linters: [staticcheck]
|
|
||||||
text: QF1008
|
|
||||||
# Don't necessarily rewrite !(foo || bar) to !foo && !bar
|
|
||||||
- linters: [staticcheck]
|
|
||||||
text: QF1001
|
|
||||||
# Don't necessarily pass the context just for the sake of the logger
|
|
||||||
- linters: [contextcheck]
|
|
||||||
text: "->log`"
|
|
||||||
settings:
|
|
||||||
sloglint:
|
|
||||||
context: "scope"
|
|
||||||
static-msg: true
|
|
||||||
msg-style: capitalized
|
|
||||||
key-naming-case: camel
|
|
||||||
formatters:
|
formatters:
|
||||||
enable:
|
enable:
|
||||||
- gofumpt
|
- gofumpt
|
||||||
|
|||||||
+4
-19
@@ -8,20 +8,17 @@
|
|||||||
policy:
|
policy:
|
||||||
approval:
|
approval:
|
||||||
- subject is conventional commit
|
- subject is conventional commit
|
||||||
- or:
|
|
||||||
- project metadata requires maintainer approval
|
- project metadata requires maintainer approval
|
||||||
- a maintainer claims responsibility
|
|
||||||
- or:
|
- or:
|
||||||
- is approved by a syncthing contributor
|
- is approved by a syncthing contributor
|
||||||
- is a translation or dependency update by a contributor
|
- is a translation or dependency update by a contributor
|
||||||
- is a trivial change by a contributor
|
- is a trivial change by a contributor
|
||||||
- a maintainer claims responsibility
|
|
||||||
|
|
||||||
# Additionally, maintainers can disapprove of a PR
|
# Additionally, contributors can disapprove of a PR
|
||||||
disapproval:
|
disapproval:
|
||||||
requires:
|
requires:
|
||||||
teams:
|
teams:
|
||||||
- syncthing/maintainers
|
- syncthing/contributors
|
||||||
|
|
||||||
# The rules for the policy are described below.
|
# The rules for the policy are described below.
|
||||||
|
|
||||||
@@ -52,8 +49,7 @@ approval_rules:
|
|||||||
- syncthing/maintainers
|
- syncthing/maintainers
|
||||||
options:
|
options:
|
||||||
ignore_update_merges: true
|
ignore_update_merges: true
|
||||||
allow_non_author_contributor: true
|
allow_contributor: true
|
||||||
invalidate_on_push: true
|
|
||||||
|
|
||||||
# Regular pull requests require approval by an active contributor
|
# Regular pull requests require approval by an active contributor
|
||||||
- name: is approved by a syncthing contributor
|
- name: is approved by a syncthing contributor
|
||||||
@@ -63,8 +59,7 @@ approval_rules:
|
|||||||
- syncthing/contributors
|
- syncthing/contributors
|
||||||
options:
|
options:
|
||||||
ignore_update_merges: true
|
ignore_update_merges: true
|
||||||
allow_non_author_contributor: true
|
allow_contributor: true
|
||||||
invalidate_on_push: true
|
|
||||||
|
|
||||||
# Changes to some files (translations, dependencies, compatibility) do not
|
# Changes to some files (translations, dependencies, compatibility) do not
|
||||||
# require approval if they were proposed by a contributor and have a
|
# require approval if they were proposed by a contributor and have a
|
||||||
@@ -101,13 +96,3 @@ approval_rules:
|
|||||||
has_author_in:
|
has_author_in:
|
||||||
teams:
|
teams:
|
||||||
- syncthing/contributors
|
- syncthing/contributors
|
||||||
|
|
||||||
# A member of the maintainers group can take responsibility by adding the
|
|
||||||
# appropriate label.
|
|
||||||
- name: a maintainer claims responsibility
|
|
||||||
if:
|
|
||||||
has_labels:
|
|
||||||
- maintainer-responsibility
|
|
||||||
has_author_in:
|
|
||||||
teams:
|
|
||||||
- syncthing/maintainers
|
|
||||||
|
|||||||
@@ -13,103 +13,104 @@
|
|||||||
# contents of this file.
|
# contents of this file.
|
||||||
#
|
#
|
||||||
|
|
||||||
Jakob Borg (calmh) <jakob@nym.se> <jakob@kastelo.net> <jborg@coreweave.com>
|
Aaron Bieber (qbit) <qbit@deftly.net>
|
||||||
Audrius Butkevicius (AudriusButkevicius) <audrius.butkevicius@gmail.com> <github@audrius.rocks>
|
|
||||||
Simon Frei (imsodin) <freisim93@gmail.com>
|
|
||||||
Tomasz Wilczyński <5626656+tomasz1986@users.noreply.github.com> <twilczynski@naver.com>
|
|
||||||
Alexander Graf (alex2108) <register-github@alex-graf.de>
|
|
||||||
Alexandre Viau (aviau) <alexandre@alexandreviau.net> <aviau@debian.org>
|
|
||||||
Anderson Mesquita (andersonvom) <andersonvom@gmail.com>
|
|
||||||
André Colomb (acolomb) <src@andre.colomb.de> <github.com@andre.colomb.de>
|
|
||||||
Antony Male (canton7) <antony.male@gmail.com>
|
|
||||||
Ben Schulz (uok) <ueomkail@gmail.com> <uok@users.noreply.github.com>
|
|
||||||
bt90 <btom1990@googlemail.com>
|
|
||||||
Caleb Callaway (cqcallaw) <enlightened.despot@gmail.com>
|
|
||||||
Daniel Harte (norgeous) <daniel@harte.me> <daniel@danielharte.co.uk> <norgeous@users.noreply.github.com>
|
|
||||||
Emil Lundberg <emil@emlun.se>
|
|
||||||
Eric P <eric@kastelo.net>
|
|
||||||
Evgeny Kuznetsov <evgeny@kuznetsov.md>
|
|
||||||
greatroar <61184462+greatroar@users.noreply.github.com>
|
|
||||||
Lars K.W. Gohlke (lkwg82) <lkwg82@gmx.de>
|
|
||||||
Lode Hoste (Zillode) <zillode@zillode.be>
|
|
||||||
Marcus B Spencer <marcus@marcusspencer.xyz> <marcus@marcusspencer.us>
|
|
||||||
Michael Ploujnikov (plouj) <ploujj@gmail.com>
|
|
||||||
Ross Smith II (rasa) <ross@smithii.com>
|
|
||||||
Stefan Tatschner (rumpelsepp) <stefan@sevenbyte.org> <rumpelsepp@sevenbyte.org> <stefan@rumpelsepp.org>
|
|
||||||
Tommy van der Vorst <tommy-github@pixelspark.nl> <tommy@pixelspark.nl>
|
|
||||||
Wulf Weich (wweich) <wweich@users.noreply.github.com> <wweich@gmx.de> <wulf@weich-kr.de>
|
|
||||||
Adam Piggott (ProactiveServices) <aD@simplypeachy.co.uk> <simplypeachy@users.noreply.github.com> <ProactiveServices@users.noreply.github.com> <adam@proactiveservices.co.uk>
|
Adam Piggott (ProactiveServices) <aD@simplypeachy.co.uk> <simplypeachy@users.noreply.github.com> <ProactiveServices@users.noreply.github.com> <adam@proactiveservices.co.uk>
|
||||||
Adel Qalieh (adelq) <aqalieh95@gmail.com> <adelq@users.noreply.github.com>
|
Adel Qalieh (adelq) <aqalieh95@gmail.com> <adelq@users.noreply.github.com>
|
||||||
|
Alan Pope <alan@popey.com>
|
||||||
|
Alberto Donato <albertodonato@users.noreply.github.com>
|
||||||
Aleksey Vasenev <margtu-fivt@ya.ru>
|
Aleksey Vasenev <margtu-fivt@ya.ru>
|
||||||
Alessandro G. (alessandro.g89) <alessandro.g89@gmail.com>
|
Alessandro G. (alessandro.g89) <alessandro.g89@gmail.com>
|
||||||
Alex Ionescu <github@ionescu.sh>
|
Alex Ionescu <github@ionescu.sh>
|
||||||
Alex Lindeman <139387+aelindeman@users.noreply.github.com>
|
Alex Lindeman <139387+aelindeman@users.noreply.github.com>
|
||||||
Alex Xu <alex.hello71@gmail.com>
|
Alex Xu <alex.hello71@gmail.com>
|
||||||
|
Alexander Graf (alex2108) <register-github@alex-graf.de>
|
||||||
Alexander Seiler <seileralex@gmail.com>
|
Alexander Seiler <seileralex@gmail.com>
|
||||||
Alexandre Alves <alexandrealvesdb.contact@gmail.com>
|
Alexandre Alves <alexandrealvesdb.contact@gmail.com>
|
||||||
|
Alexandre Viau (aviau) <alexandre@alexandreviau.net> <aviau@debian.org>
|
||||||
Aman Gupta <aman@tmm1.net>
|
Aman Gupta <aman@tmm1.net>
|
||||||
|
Anatoli Babenia <anatoli@rainforce.org>
|
||||||
|
Anderson Mesquita (andersonvom) <andersonvom@gmail.com>
|
||||||
Andreas Sommer <andreas.sommer87@googlemail.com>
|
Andreas Sommer <andreas.sommer87@googlemail.com>
|
||||||
andresvia <andres.via@gmail.com>
|
andresvia <andres.via@gmail.com>
|
||||||
|
Andrew Dunham (andrew-d) <andrew@du.nham.ca>
|
||||||
|
Andrew Meyer <andrewm.bpi@gmail.com>
|
||||||
Andrew Rabert (nvllsvm) <ar@nullsum.net> <6550543+nvllsvm@users.noreply.github.com>
|
Andrew Rabert (nvllsvm) <ar@nullsum.net> <6550543+nvllsvm@users.noreply.github.com>
|
||||||
Andrey D (scienmind) <scintertech@cryptolab.net> <scienmind@users.noreply.github.com>
|
Andrey D (scienmind) <scintertech@cryptolab.net> <scienmind@users.noreply.github.com>
|
||||||
|
André Colomb (acolomb) <src@andre.colomb.de> <github.com@andre.colomb.de>
|
||||||
andyleap <andyleap@gmail.com>
|
andyleap <andyleap@gmail.com>
|
||||||
Anjan Momi <anjan@momi.ca>
|
Anjan Momi <anjan@momi.ca>
|
||||||
Anthony Goeckner <agoeckner@users.noreply.github.com>
|
Anthony Goeckner <agoeckner@users.noreply.github.com>
|
||||||
Antoine Lamielle (0x010C) <antoine.lamielle@0x010c.fr> <gh@0x010c.fr>
|
Antoine Lamielle (0x010C) <antoine.lamielle@0x010c.fr> <gh@0x010c.fr>
|
||||||
|
Antony Male (canton7) <antony.male@gmail.com>
|
||||||
Anur <anurnomeru@163.com>
|
Anur <anurnomeru@163.com>
|
||||||
Aranjedeath <Aranjedeath@users.noreply.github.com>
|
Aranjedeath <Aranjedeath@users.noreply.github.com>
|
||||||
ardevd <ardevd@users.noreply.github.com>
|
|
||||||
Arkadiusz Tymiński <gevleeog@gmail.com>
|
Arkadiusz Tymiński <gevleeog@gmail.com>
|
||||||
Aroun <login@b-vo.fr>
|
Aroun <login@b-vo.fr>
|
||||||
Arthur Axel fREW Schmidt (frioux) <frew@afoolishmanifesto.com> <frioux@gmail.com>
|
Arthur Axel fREW Schmidt (frioux) <frew@afoolishmanifesto.com> <frioux@gmail.com>
|
||||||
Artur Zubilewicz <AkaZecik@users.noreply.github.com>
|
Artur Zubilewicz <AkaZecik@users.noreply.github.com>
|
||||||
Ashish Bhate <bhate.ashish@gmail.com>
|
Ashish Bhate <bhate.ashish@gmail.com>
|
||||||
|
Audrius Butkevicius (AudriusButkevicius) <audrius.butkevicius@gmail.com> <github@audrius.rocks>
|
||||||
Aurélien Rainone <476650+arl@users.noreply.github.com>
|
Aurélien Rainone <476650+arl@users.noreply.github.com>
|
||||||
BAHADIR YILMAZ <bahadiryilmaz32@gmail.com>
|
BAHADIR YILMAZ <bahadiryilmaz32@gmail.com>
|
||||||
Bart De Vries (mogwa1) <devriesb@gmail.com>
|
Bart De Vries (mogwa1) <devriesb@gmail.com>
|
||||||
Beat Reichenbach <44111292+beatreichenbach@users.noreply.github.com>
|
Beat Reichenbach <44111292+beatreichenbach@users.noreply.github.com>
|
||||||
Ben Norcombe <bennorcombe@pm.me>
|
Ben Curthoys (bencurthoys) <ben@bencurthoys.com>
|
||||||
|
Ben Schulz (uok) <ueomkail@gmail.com> <uok@users.noreply.github.com>
|
||||||
Ben Shepherd (benshep) <bjashepherd@gmail.com>
|
Ben Shepherd (benshep) <bjashepherd@gmail.com>
|
||||||
Ben Sidhom (bsidhom) <bsidhom@gmail.com>
|
Ben Sidhom (bsidhom) <bsidhom@gmail.com>
|
||||||
Benedikt Heine (bebehei) <bebe@bebehei.de>
|
Benedikt Heine (bebehei) <bebe@bebehei.de>
|
||||||
|
Benedikt Morbach <benedikt.morbach@googlemail.com>
|
||||||
|
Benjamin Nater <17193640+bn4t@users.noreply.github.com>
|
||||||
Benno Fünfstück <benno.fuenfstueck@gmail.com>
|
Benno Fünfstück <benno.fuenfstueck@gmail.com>
|
||||||
Benny Ng (tpng) <benny.tpng@gmail.com>
|
Benny Ng (tpng) <benny.tpng@gmail.com>
|
||||||
boomsquared <54829195+boomsquared@users.noreply.github.com>
|
boomsquared <54829195+boomsquared@users.noreply.github.com>
|
||||||
Boqin Qin <bobbqqin@bupt.edu.cn>
|
Boqin Qin <bobbqqin@bupt.edu.cn>
|
||||||
Boris Rybalkin <ribalkin@gmail.com>
|
Boris Rybalkin <ribalkin@gmail.com>
|
||||||
|
Brandon Philips (philips) <brandon@ifup.org>
|
||||||
Brendan Long (brendanlong) <self@brendanlong.com>
|
Brendan Long (brendanlong) <self@brendanlong.com>
|
||||||
|
Brian R. Becker (brbecker) <brbecker@gmail.com>
|
||||||
|
bt90 <btom1990@googlemail.com>
|
||||||
|
Caleb Callaway (cqcallaw) <enlightened.despot@gmail.com>
|
||||||
|
Carsten Hagemann (carstenhag) <moter8@gmail.com> <carsten@chagemann.de>
|
||||||
Catfriend1 <16361913+Catfriend1@users.noreply.github.com>
|
Catfriend1 <16361913+Catfriend1@users.noreply.github.com>
|
||||||
Cathryne Linenweaver (Cathryne) <cathryne.linenweaver@gmail.com> <Cathryne@users.noreply.github.com> <katrinleinweber@MAC.local>
|
Cathryne Linenweaver (Cathryne) <cathryne.linenweaver@gmail.com> <Cathryne@users.noreply.github.com> <katrinleinweber@MAC.local>
|
||||||
Cedric Staniewski (xduugu) <cedric@gmx.ca>
|
Cedric Staniewski (xduugu) <cedric@gmx.ca>
|
||||||
|
chenrui <rui@meetup.com>
|
||||||
Chih-Hsuan Yen <yan12125@gmail.com> <1937689+yan12125@users.noreply.github.com>
|
Chih-Hsuan Yen <yan12125@gmail.com> <1937689+yan12125@users.noreply.github.com>
|
||||||
Choongkyu <choongkyu.kim+gh@gmail.com> <vapidlyrapid+gh@gmail.com>
|
Choongkyu <choongkyu.kim+gh@gmail.com> <vapidlyrapid+gh@gmail.com>
|
||||||
Chris Howie (cdhowie) <me@chrishowie.com>
|
Chris Howie (cdhowie) <me@chrishowie.com>
|
||||||
Chris Joel (cdata) <chris@scriptolo.gy>
|
Chris Joel (cdata) <chris@scriptolo.gy>
|
||||||
|
Chris Tonkinson <chris@masterbran.ch>
|
||||||
Christian Kujau <ckujau@users.noreply.github.com>
|
Christian Kujau <ckujau@users.noreply.github.com>
|
||||||
Christian Prescott <me@christianprescott.com>
|
Christian Prescott <me@christianprescott.com>
|
||||||
chucic <chucic@seznam.cz>
|
chucic <chucic@seznam.cz>
|
||||||
cjc7373 <niuchangcun@gmail.com>
|
cjc7373 <niuchangcun@gmail.com>
|
||||||
Colin Kennedy (moshen) <moshen.colin@gmail.com>
|
Colin Kennedy (moshen) <moshen.colin@gmail.com>
|
||||||
Cromefire_ <tim.l@nghorst.net> <26320625+cromefire@users.noreply.github.com>
|
Cromefire_ <tim.l@nghorst.net> <26320625+cromefire@users.noreply.github.com>
|
||||||
cui <cuiweixie@gmail.com>
|
cui fliter <imcusg@gmail.com>
|
||||||
Cyprien Devillez <cypx@users.noreply.github.com>
|
Cyprien Devillez <cypx@users.noreply.github.com>
|
||||||
d-volution <49024624+d-volution@users.noreply.github.com>
|
d-volution <49024624+d-volution@users.noreply.github.com>
|
||||||
|
Dale Visser <dale.visser@live.com>
|
||||||
Dan <benda.daniel@gmail.com>
|
Dan <benda.daniel@gmail.com>
|
||||||
Daniel Barczyk <46358936+DanielBarczyk@users.noreply.github.com>
|
Daniel Barczyk <46358936+DanielBarczyk@users.noreply.github.com>
|
||||||
Daniel Bergmann (brgmnn) <dan.arne.bergmann@gmail.com> <brgmnn@users.noreply.github.com>
|
Daniel Bergmann (brgmnn) <dan.arne.bergmann@gmail.com> <brgmnn@users.noreply.github.com>
|
||||||
|
Daniel Harte (norgeous) <daniel@harte.me> <daniel@danielharte.co.uk> <norgeous@users.noreply.github.com>
|
||||||
Daniel Martí (mvdan) <mvdan@mvdan.cc>
|
Daniel Martí (mvdan) <mvdan@mvdan.cc>
|
||||||
Daniel Padrta <64928366+danpadcz@users.noreply.github.com>
|
Daniel Padrta <64928366+danpadcz@users.noreply.github.com>
|
||||||
Daniil Gentili <daniil@daniil.it>
|
|
||||||
Darshil Chanpura (dtchanpura) <dtchanpura@gmail.com> <dcprime314@gmail.com>
|
Darshil Chanpura (dtchanpura) <dtchanpura@gmail.com> <dcprime314@gmail.com>
|
||||||
dashangcun <907225865@qq.com>
|
dashangcun <907225865@qq.com>
|
||||||
David Rimmer (dinosore) <dinosore@dbrsoftware.co.uk>
|
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>
|
DeflateAwning <11021263+DeflateAwning@users.noreply.github.com>
|
||||||
Denis A. (dva) <denisva@gmail.com>
|
Denis A. (dva) <denisva@gmail.com>
|
||||||
Dennis Wilson (snnd) <dw@risu.io>
|
Dennis Wilson (snnd) <dw@risu.io>
|
||||||
|
dependabot-preview[bot] <dependabot-preview[bot]@users.noreply.github.com> <27856297+dependabot-preview[bot]@users.noreply.github.com>
|
||||||
|
dependabot[bot] <dependabot[bot]@users.noreply.github.com> <49699333+dependabot[bot]@users.noreply.github.com>
|
||||||
derekriemer <derek.riemer@colorado.edu>
|
derekriemer <derek.riemer@colorado.edu>
|
||||||
DerRockWolf <50499906+DerRockWolf@users.noreply.github.com>
|
DerRockWolf <50499906+DerRockWolf@users.noreply.github.com>
|
||||||
desbma <desbma@users.noreply.github.com>
|
desbma <desbma@users.noreply.github.com>
|
||||||
Devon G. Redekopp <devon@redekopp.com>
|
Devon G. Redekopp <devon@redekopp.com>
|
||||||
|
diemade <spamkill@posteo.ch>
|
||||||
digital <didev@dinid.net>
|
digital <didev@dinid.net>
|
||||||
Dimitri Papadopoulos Orfanos <3234522+DimitriPapadopoulos@users.noreply.github.com>
|
Dimitri Papadopoulos Orfanos <3234522+DimitriPapadopoulos@users.noreply.github.com>
|
||||||
Dmitry Saveliev (dsaveliev) <d.e.saveliev@gmail.com>
|
Dmitry Saveliev (dsaveliev) <d.e.saveliev@gmail.com>
|
||||||
@@ -119,13 +120,14 @@ Dominik Heidler (asdil12) <dominik@heidler.eu>
|
|||||||
Elias Jarlebring (jarlebring) <jarlebring@gmail.com>
|
Elias Jarlebring (jarlebring) <jarlebring@gmail.com>
|
||||||
Elliot Huffman <thelich2@gmail.com>
|
Elliot Huffman <thelich2@gmail.com>
|
||||||
Emil Hessman (ceh) <emil@hessman.se>
|
Emil Hessman (ceh) <emil@hessman.se>
|
||||||
|
Emil Lundberg <emil@emlun.se>
|
||||||
Eng Zer Jun <engzerjun@gmail.com>
|
Eng Zer Jun <engzerjun@gmail.com>
|
||||||
entity0xfe <109791748+entity0xfe@users.noreply.github.com> <entity0xfe@my.domain>
|
entity0xfe <109791748+entity0xfe@users.noreply.github.com> <entity0xfe@my.domain>
|
||||||
Epifeny <batterystarter@gmail.com>
|
|
||||||
epifeny <epifeny@users.noreply.github.com>
|
|
||||||
Eric Lesiuta <elesiuta@gmail.com>
|
Eric Lesiuta <elesiuta@gmail.com>
|
||||||
|
Eric P <eric@kastelo.net>
|
||||||
Erik Meitner (WSGCSysadmin) <e.meitner@willystreet.coop>
|
Erik Meitner (WSGCSysadmin) <e.meitner@willystreet.coop>
|
||||||
Evan Spensley <94762716+0evan@users.noreply.github.com>
|
Evan Spensley <94762716+0evan@users.noreply.github.com>
|
||||||
|
Evgeny Kuznetsov <evgeny@kuznetsov.md>
|
||||||
Federico Castagnini (facastagnini) <federico.castagnini@gmail.com>
|
Federico Castagnini (facastagnini) <federico.castagnini@gmail.com>
|
||||||
Felix <53702818+f-eliks@users.noreply.github.com>
|
Felix <53702818+f-eliks@users.noreply.github.com>
|
||||||
Felix Ableitner (Nutomic) <me@nutomic.com>
|
Felix Ableitner (Nutomic) <me@nutomic.com>
|
||||||
@@ -139,6 +141,7 @@ ghjklw <malo@jaffre.info>
|
|||||||
Gilli Sigurdsson (gillisig) <gilli@vx.is>
|
Gilli Sigurdsson (gillisig) <gilli@vx.is>
|
||||||
Gleb Sinyavskiy <zhulik.gleb@gmail.com>
|
Gleb Sinyavskiy <zhulik.gleb@gmail.com>
|
||||||
Graham Miln (grahammiln) <graham.miln@dssw.co.uk> <graham.miln@miln.eu>
|
Graham Miln (grahammiln) <graham.miln@dssw.co.uk> <graham.miln@miln.eu>
|
||||||
|
greatroar <61184462+greatroar@users.noreply.github.com>
|
||||||
Greg <gco@jazzhaiku.com>
|
Greg <gco@jazzhaiku.com>
|
||||||
guangwu <guoguangwu@magic-shield.com>
|
guangwu <guoguangwu@magic-shield.com>
|
||||||
gudvinr <gudvinr@gmail.com>
|
gudvinr <gudvinr@gmail.com>
|
||||||
@@ -153,36 +156,50 @@ Hugo Locurcio <hugo.locurcio@hugo.pro>
|
|||||||
Iain Barnett <iainspeed@gmail.com>
|
Iain Barnett <iainspeed@gmail.com>
|
||||||
Ian Johnson (anonymouse64) <ian.johnson@canonical.com> <person.uwsome@gmail.com>
|
Ian Johnson (anonymouse64) <ian.johnson@canonical.com> <person.uwsome@gmail.com>
|
||||||
ignacy123 <ignacy.buczek@onet.pl>
|
ignacy123 <ignacy.buczek@onet.pl>
|
||||||
|
Ikko Ashimine <eltociear@gmail.com>
|
||||||
|
Ilya Brin <464157+ilyabrin@users.noreply.github.com>
|
||||||
Iskander Sharipov (Alex) <quasilyte@gmail.com>
|
Iskander Sharipov (Alex) <quasilyte@gmail.com>
|
||||||
Jaakko Hannikainen (jgke) <jgke@jgke.fi>
|
Jaakko Hannikainen (jgke) <jgke@jgke.fi>
|
||||||
|
Jacek Szafarkiewicz (hadogenes) <szafar@linux.pl>
|
||||||
Jack Croft <jccroft1@users.noreply.github.com>
|
Jack Croft <jccroft1@users.noreply.github.com>
|
||||||
Jacob <jyundt@gmail.com>
|
Jacob <jyundt@gmail.com>
|
||||||
Jake Peterson (acogdev) <jake@acogdev.com>
|
Jake Peterson (acogdev) <jake@acogdev.com>
|
||||||
|
Jakob Borg (calmh) <jakob@nym.se> <jakob@kastelo.net> <jborg@coreweave.com>
|
||||||
James O'Beirne <wild-github@au92.org>
|
James O'Beirne <wild-github@au92.org>
|
||||||
James Patterson (jpjp) <jamespatterson@operamail.com> <jpjp@users.noreply.github.com>
|
James Patterson (jpjp) <jamespatterson@operamail.com> <jpjp@users.noreply.github.com>
|
||||||
|
janost <janost@tuta.io>
|
||||||
Jaroslav Lichtblau <svetlemodry@users.noreply.github.com>
|
Jaroslav Lichtblau <svetlemodry@users.noreply.github.com>
|
||||||
Jaroslav Malec (dzarda) <dzardacz@gmail.com>
|
Jaroslav Malec (dzarda) <dzardacz@gmail.com>
|
||||||
|
jaseg <githubaccount@jaseg.net>
|
||||||
Jaspitta <ste.scarpitta@gmail.com>
|
Jaspitta <ste.scarpitta@gmail.com>
|
||||||
|
Jauder Ho <jauderho@users.noreply.github.com>
|
||||||
Jaya Chithra (jayachithra) <s.k.jayachithra@gmail.com>
|
Jaya Chithra (jayachithra) <s.k.jayachithra@gmail.com>
|
||||||
Jaya Kumar <jaya.kumar@ict.nl>
|
Jaya Kumar <jaya.kumar@ict.nl>
|
||||||
Jeffery To <jeffery.to@gmail.com>
|
Jeffery To <jeffery.to@gmail.com>
|
||||||
jelle van der Waa <jelle@vdwaa.nl>
|
jelle van der Waa <jelle@vdwaa.nl>
|
||||||
Jens Diemer (jedie) <github.com@jensdiemer.de> <git@jensdiemer.de>
|
Jens Diemer (jedie) <github.com@jensdiemer.de> <git@jensdiemer.de>
|
||||||
|
Jerry Jacobs (xor-gate) <jerry.jacobs@xor-gate.org> <xor-gate@users.noreply.github.com>
|
||||||
|
Jesse Lucas <jesse@jesselucas.com>
|
||||||
Jochen Voss (seehuhn) <voss@seehuhn.de>
|
Jochen Voss (seehuhn) <voss@seehuhn.de>
|
||||||
|
Johan Andersson <j@i19.se>
|
||||||
Johan Vromans (sciurius) <jvromans@squirrel.nl>
|
Johan Vromans (sciurius) <jvromans@squirrel.nl>
|
||||||
John Rinehart (fuzzybear3965) <johnrichardrinehart@gmail.com>
|
John Rinehart (fuzzybear3965) <johnrichardrinehart@gmail.com>
|
||||||
Jonas Thelemann <e-mail@jonas-thelemann.de>
|
Jonas Thelemann <e-mail@jonas-thelemann.de>
|
||||||
Jonathan <artback@protonmail.com> <jonagn@gmail.com>
|
Jonathan <artback@protonmail.com> <jonagn@gmail.com>
|
||||||
|
Jonathan Cross <jcross@gmail.com>
|
||||||
|
Jonta <359397+Jonta@users.noreply.github.com>
|
||||||
Jose Manuel Delicado (jmdaweb) <jmdaweb@hotmail.com> <jmdaweb@users.noreply.github.com>
|
Jose Manuel Delicado (jmdaweb) <jmdaweb@hotmail.com> <jmdaweb@users.noreply.github.com>
|
||||||
JRNitre <nichinichisou67@outlook.com>
|
|
||||||
jtagcat <git-514635f7@jtag.cat> <git-12dbd862@jtag.cat>
|
jtagcat <git-514635f7@jtag.cat> <git-12dbd862@jtag.cat>
|
||||||
Julian Lehrhuber <jul13579@users.noreply.github.com>
|
Julian Lehrhuber <jul13579@users.noreply.github.com>
|
||||||
Jörg Thalheim <Mic92@users.noreply.github.com>
|
Jörg Thalheim <Mic92@users.noreply.github.com>
|
||||||
Jędrzej Kula <kula.jedrek@gmail.com>
|
Jędrzej Kula <kula.jedrek@gmail.com>
|
||||||
|
K.B.Dharun Krishna <kbdharunkrishna@gmail.com>
|
||||||
|
Kalle Laine <pahakalle@protonmail.com>
|
||||||
Kapil Sareen <kapilsareen584@gmail.com>
|
Kapil Sareen <kapilsareen584@gmail.com>
|
||||||
Karol Różycki (krozycki) <rozycki.karol@gmail.com>
|
Karol Różycki (krozycki) <rozycki.karol@gmail.com>
|
||||||
Kebin Liu <lkebin@gmail.com>
|
Kebin Liu <lkebin@gmail.com>
|
||||||
Keith Harrison <keithh@protonmail.com>
|
Keith Harrison <keithh@protonmail.com>
|
||||||
|
Keith Turner <kturner@apache.org>
|
||||||
Kelong Cong (kc1212) <kc04bc@gmx.com> <kc1212@users.noreply.github.com>
|
Kelong Cong (kc1212) <kc04bc@gmx.com> <kc1212@users.noreply.github.com>
|
||||||
Ken'ichi Kamada (kamadak) <kamada@nanohz.org>
|
Ken'ichi Kamada (kamadak) <kamada@nanohz.org>
|
||||||
Kevin Allen (ironmig) <kma1660@gmail.com>
|
Kevin Allen (ironmig) <kma1660@gmail.com>
|
||||||
@@ -191,24 +208,31 @@ Kevin White, Jr. (kwhite17) <kevinwhite1710@gmail.com>
|
|||||||
klemens <ka7@github.com>
|
klemens <ka7@github.com>
|
||||||
Kurt Fitzner (Kudalufi) <kurt@va1der.ca> <kurt.fitzner@gmail.com>
|
Kurt Fitzner (Kudalufi) <kurt@va1der.ca> <kurt.fitzner@gmail.com>
|
||||||
kylosus <33132401+kylosus@users.noreply.github.com>
|
kylosus <33132401+kylosus@users.noreply.github.com>
|
||||||
|
Lars K.W. Gohlke (lkwg82) <lkwg82@gmx.de>
|
||||||
Lars Lehtonen <lars.lehtonen@gmail.com>
|
Lars Lehtonen <lars.lehtonen@gmail.com>
|
||||||
|
Laurent Arnoud <laurent@spkdev.net>
|
||||||
Laurent Etiemble (letiemble) <laurent.etiemble@gmail.com> <laurent.etiemble@monobjc.net>
|
Laurent Etiemble (letiemble) <laurent.etiemble@gmail.com> <laurent.etiemble@monobjc.net>
|
||||||
Leo Arias (elopio) <yo@elopio.net>
|
Leo Arias (elopio) <yo@elopio.net>
|
||||||
Liu Siyuan (liusy182) <liusy182@gmail.com> <liusy182@hotmail.com>
|
Liu Siyuan (liusy182) <liusy182@gmail.com> <liusy182@hotmail.com>
|
||||||
|
Lode Hoste (Zillode) <zillode@zillode.be>
|
||||||
Lord Landon Agahnim (LordLandon) <lordlandon@gmail.com>
|
Lord Landon Agahnim (LordLandon) <lordlandon@gmail.com>
|
||||||
LSmithx2 <42276854+lsmithx2@users.noreply.github.com>
|
LSmithx2 <42276854+lsmithx2@users.noreply.github.com>
|
||||||
Luiz Angelo Daros de Luca <luizluca@gmail.com>
|
luchenhan <168071714+luchenhan@users.noreply.github.com>
|
||||||
Lukas Lihotzki <lukas@lihotzki.de>
|
Lukas Lihotzki <lukas@lihotzki.de>
|
||||||
Luke Hamburg <1992842+luckman212@users.noreply.github.com>
|
Luke Hamburg <1992842+luckman212@users.noreply.github.com>
|
||||||
luzpaz <luzpaz@users.noreply.github.com>
|
luzpaz <luzpaz@users.noreply.github.com>
|
||||||
Majed Abdulaziz (majedev) <majed.alhajry@gmail.com>
|
Majed Abdulaziz (majedev) <majed.alhajry@gmail.com>
|
||||||
Marc Laporte (marclaporte) <marc@marclaporte.com> <marc@laporte.name>
|
Marc Laporte (marclaporte) <marc@marclaporte.com> <marc@laporte.name>
|
||||||
|
Marc Pujol (kilburn) <kilburn@la3.org>
|
||||||
Marcel Meyer <mm.marcelmeyer@gmail.com>
|
Marcel Meyer <mm.marcelmeyer@gmail.com>
|
||||||
Marcin Dziadus (marcindziadus) <dziadus.marcin@gmail.com>
|
Marcin Dziadus (marcindziadus) <dziadus.marcin@gmail.com>
|
||||||
|
marco-m <marco.molteni@laposte.net>
|
||||||
|
Marcus B Spencer <marcus@marcusspencer.xyz> <marcus@marcusspencer.us>
|
||||||
Marcus Legendre <marcus.legendre@gmail.com>
|
Marcus Legendre <marcus.legendre@gmail.com>
|
||||||
Mario Majila <mariustshipichik@gmail.com>
|
Mario Majila <mariustshipichik@gmail.com>
|
||||||
Mark Pulford (mpx) <mark@kyne.com.au>
|
Mark Pulford (mpx) <mark@kyne.com.au>
|
||||||
Martchus <martchus@gmx.net>
|
Martchus <martchus@gmx.net>
|
||||||
|
Martin Polehla <p0l0us@users.noreply.github.com>
|
||||||
Mateusz Naściszewski (mateon1) <matin1111@wp.pl>
|
Mateusz Naściszewski (mateon1) <matin1111@wp.pl>
|
||||||
Mateusz Ż <thedead4fun@live.com>
|
Mateusz Ż <thedead4fun@live.com>
|
||||||
mathias4833 <67101597+mathias4833@users.noreply.github.com>
|
mathias4833 <67101597+mathias4833@users.noreply.github.com>
|
||||||
@@ -219,12 +243,15 @@ Matteo Ruina <matteo.ruina@gmail.com>
|
|||||||
Maurizio Tomasi <ziotom78@gmail.com>
|
Maurizio Tomasi <ziotom78@gmail.com>
|
||||||
Max <github@germancoding.com>
|
Max <github@germancoding.com>
|
||||||
Max Schulze (kralo) <max.schulze@online.de> <kralo@users.noreply.github.com>
|
Max Schulze (kralo) <max.schulze@online.de> <kralo@users.noreply.github.com>
|
||||||
|
maxice8 <30738253+maxice8@users.noreply.github.com>
|
||||||
MaximAL <almaximal@ya.ru>
|
MaximAL <almaximal@ya.ru>
|
||||||
|
Maxime Thirouin <m@moox.io>
|
||||||
Maximilian <maxi.rostock@outlook.de> <public@complexvector.space>
|
Maximilian <maxi.rostock@outlook.de> <public@complexvector.space>
|
||||||
Maxwell G <maxwell@gtmx.me>
|
mclang <1721600+mclang@users.noreply.github.com>
|
||||||
Michael Jephcote (Rewt0r) <rewt0r@gmx.com> <Rewt0r@users.noreply.github.com>
|
Michael Jephcote (Rewt0r) <rewt0r@gmx.com> <Rewt0r@users.noreply.github.com>
|
||||||
|
Michael Ploujnikov (plouj) <ploujj@gmail.com>
|
||||||
Michael Rienstra <mrienstra@gmail.com>
|
Michael Rienstra <mrienstra@gmail.com>
|
||||||
Michael Wang 汪東陽 <michael19920327@gmail.com>
|
Michael Tilli (pyfisch) <pyfisch@gmail.com>
|
||||||
MichaIng <micha@dietpi.com>
|
MichaIng <micha@dietpi.com>
|
||||||
Migelo <miha@filetki.si>
|
Migelo <miha@filetki.si>
|
||||||
Mike Boone <mike@boonedocks.net>
|
Mike Boone <mike@boonedocks.net>
|
||||||
@@ -233,6 +260,7 @@ MikolajTwarog <43782609+MikolajTwarog@users.noreply.github.com>
|
|||||||
Mingxuan Lin <gdlmx@users.noreply.github.com>
|
Mingxuan Lin <gdlmx@users.noreply.github.com>
|
||||||
mv1005 <49659413+mv1005@users.noreply.github.com>
|
mv1005 <49659413+mv1005@users.noreply.github.com>
|
||||||
Nate Morrison (nrm21) <natemorrison@gmail.com>
|
Nate Morrison (nrm21) <natemorrison@gmail.com>
|
||||||
|
Naveen <172697+naveensrinivasan@users.noreply.github.com>
|
||||||
nf <nf@wh3rd.net>
|
nf <nf@wh3rd.net>
|
||||||
Nicholas Rishel (PrototypeNM1) <rishel.nick@gmail.com> <PrototypeNM1@users.noreply.github.com>
|
Nicholas Rishel (PrototypeNM1) <rishel.nick@gmail.com> <PrototypeNM1@users.noreply.github.com>
|
||||||
Nick Busey <NickBusey@users.noreply.github.com>
|
Nick Busey <NickBusey@users.noreply.github.com>
|
||||||
@@ -247,6 +275,7 @@ NoLooseEnds <jon.koslung@gmail.com>
|
|||||||
Oliver Freyermuth <o.freyermuth@googlemail.com>
|
Oliver Freyermuth <o.freyermuth@googlemail.com>
|
||||||
orangekame3 <miya.org.0309@gmail.com>
|
orangekame3 <miya.org.0309@gmail.com>
|
||||||
otbutz <tbutz@optitool.de>
|
otbutz <tbutz@optitool.de>
|
||||||
|
Otiel <Otiel@users.noreply.github.com>
|
||||||
overkill <22098433+0verk1ll@users.noreply.github.com>
|
overkill <22098433+0verk1ll@users.noreply.github.com>
|
||||||
Oyebanji Jacob Mayowa <oyebanji05@gmail.com>
|
Oyebanji Jacob Mayowa <oyebanji05@gmail.com>
|
||||||
Pablo <pbaeyens31+github@gmail.com>
|
Pablo <pbaeyens31+github@gmail.com>
|
||||||
@@ -254,6 +283,7 @@ Pascal Jungblut (pascalj) <github@pascalj.com> <mail@pascal-jungblut.com>
|
|||||||
Paul Brit <paulbrit44@gmail.com>
|
Paul Brit <paulbrit44@gmail.com>
|
||||||
Paul Donald <newtwen+github@gmail.com>
|
Paul Donald <newtwen+github@gmail.com>
|
||||||
Pawel Palenica (qepasa) <pawelpalenica11@gmail.com>
|
Pawel Palenica (qepasa) <pawelpalenica11@gmail.com>
|
||||||
|
Paweł Rozlach <vespian@users.noreply.github.com>
|
||||||
perewa <cavalcante.ten@gmail.com>
|
perewa <cavalcante.ten@gmail.com>
|
||||||
Peter Badida <KeyWeeUsr@users.noreply.github.com>
|
Peter Badida <KeyWeeUsr@users.noreply.github.com>
|
||||||
Peter Dave Hello <hsu@peterdavehello.org>
|
Peter Dave Hello <hsu@peterdavehello.org>
|
||||||
@@ -263,18 +293,20 @@ Phani Rithvij <phanirithvij2000@gmail.com>
|
|||||||
Phil Davis <phil.davis@inf.org>
|
Phil Davis <phil.davis@inf.org>
|
||||||
Philippe Schommers (filoozoom) <philippe@schommers.be>
|
Philippe Schommers (filoozoom) <philippe@schommers.be>
|
||||||
Phill Luby (pluby) <phill.luby@newredo.com>
|
Phill Luby (pluby) <phill.luby@newredo.com>
|
||||||
|
Pier Paolo Ramon <ramonpierre@gmail.com>
|
||||||
Piotr Bejda (piobpl) <piotrb10@gmail.com>
|
Piotr Bejda (piobpl) <piotrb10@gmail.com>
|
||||||
polyfloyd <polyfloyd@users.noreply.github.com>
|
polyfloyd <polyfloyd@users.noreply.github.com>
|
||||||
Prathik P Kulkarni <83969842+prathik8794@users.noreply.github.com>
|
Pramodh KP (pramodhkp) <pramodh.p@directi.com> <1507241+pramodhkp@users.noreply.github.com>
|
||||||
pullmerge <166967364+pullmerge@users.noreply.github.com>
|
pullmerge <166967364+pullmerge@users.noreply.github.com>
|
||||||
Quentin Hibon <qh.public@yahoo.com>
|
Quentin Hibon <qh.public@yahoo.com>
|
||||||
Rahmi Pruitt <rjpruitt16@gmail.com>
|
Rahmi Pruitt <rjpruitt16@gmail.com>
|
||||||
RealCharlesChia <161665317+RealCharlesChia@users.noreply.github.com>
|
|
||||||
red_led <red-led@users.noreply.github.com>
|
red_led <red-led@users.noreply.github.com>
|
||||||
|
Richard Hartmann <RichiH@users.noreply.github.com>
|
||||||
Robert Carosi (nov1n) <robert@carosi.nl>
|
Robert Carosi (nov1n) <robert@carosi.nl>
|
||||||
Roberto Santalla <roobre@users.noreply.github.com>
|
Roberto Santalla <roobre@users.noreply.github.com>
|
||||||
Robin Schoonover <robin@cornhooves.org>
|
Robin Schoonover <robin@cornhooves.org>
|
||||||
Roman Zaynetdinov (zaynetro) <romanznet@gmail.com>
|
Roman Zaynetdinov (zaynetro) <romanznet@gmail.com>
|
||||||
|
Ross Smith II (rasa) <ross@smithii.com>
|
||||||
rubenbe <github-com-00ff86@vandamme.email>
|
rubenbe <github-com-00ff86@vandamme.email>
|
||||||
Ruslan Yevdokymov <38809160+ruslanye@users.noreply.github.com>
|
Ruslan Yevdokymov <38809160+ruslanye@users.noreply.github.com>
|
||||||
Ryan Qian <i@bitbili.net>
|
Ryan Qian <i@bitbili.net>
|
||||||
@@ -286,17 +318,19 @@ Sergey Mishin (ralder) <ralder@yandex.ru>
|
|||||||
Sertonix <83883937+Sertonix@users.noreply.github.com>
|
Sertonix <83883937+Sertonix@users.noreply.github.com>
|
||||||
Severin von Wnuck-Lipinski <ss7@live.de>
|
Severin von Wnuck-Lipinski <ss7@live.de>
|
||||||
Shaarad Dalvi <60266155+shaaraddalvi@users.noreply.github.com> <shdalv@microsoft.com>
|
Shaarad Dalvi <60266155+shaaraddalvi@users.noreply.github.com> <shdalv@microsoft.com>
|
||||||
Shivam Kumar <155747305+maishivamhoo123@users.noreply.github.com>
|
Simon Frei (imsodin) <freisim93@gmail.com>
|
||||||
Simon Mwepu <simonmwepu@gmail.com>
|
Simon Mwepu <simonmwepu@gmail.com>
|
||||||
Simon Pickup <simon@pickupinfinity.com>
|
Simon Pickup <simon@pickupinfinity.com>
|
||||||
Sly_tom_cat <slytomcat@mail.ru>
|
Sly_tom_cat <slytomcat@mail.ru>
|
||||||
Sonu Kumar Saw <31889738+dev-saw99@users.noreply.github.com>
|
Sonu Kumar Saw <31889738+dev-saw99@users.noreply.github.com>
|
||||||
Stefan Kuntz (Stefan-Code) <stefan.github@gmail.com> <Stefan.github@gmail.com>
|
Stefan Kuntz (Stefan-Code) <stefan.github@gmail.com> <Stefan.github@gmail.com>
|
||||||
|
Stefan Tatschner (rumpelsepp) <stefan@sevenbyte.org> <rumpelsepp@sevenbyte.org> <stefan@rumpelsepp.org>
|
||||||
Steven Eckhoff <steven.eckhoff.opensource@gmail.com>
|
Steven Eckhoff <steven.eckhoff.opensource@gmail.com>
|
||||||
Suhas Gundimeda (snugghash) <suhas.gundimeda@gmail.com> <snugghash@gmail.com>
|
Suhas Gundimeda (snugghash) <suhas.gundimeda@gmail.com> <snugghash@gmail.com>
|
||||||
Sven Bachmann <dev@mcbachmann.de>
|
Sven Bachmann <dev@mcbachmann.de>
|
||||||
|
Syncthing Automation <automation@syncthing.net>
|
||||||
|
Syncthing Release Automation <release@syncthing.net>
|
||||||
Sébastien WENSKE <sebastien@wenske.fr>
|
Sébastien WENSKE <sebastien@wenske.fr>
|
||||||
Tao <mail@steadytao.com>
|
|
||||||
Taylor Khan (nelsonkhan) <nelsonkhan@gmail.com>
|
Taylor Khan (nelsonkhan) <nelsonkhan@gmail.com>
|
||||||
Terrance <git@terrance.allofti.me>
|
Terrance <git@terrance.allofti.me>
|
||||||
TheCreeper <TheCreeper@users.noreply.github.com>
|
TheCreeper <TheCreeper@users.noreply.github.com>
|
||||||
@@ -304,17 +338,19 @@ Thomas <9749173+uhthomas@users.noreply.github.com>
|
|||||||
Thomas Hipp <thomashipp@gmail.com>
|
Thomas Hipp <thomashipp@gmail.com>
|
||||||
Tim Abell (timabell) <tim@timwise.co.uk>
|
Tim Abell (timabell) <tim@timwise.co.uk>
|
||||||
Tim Howes (timhowes) <timhowes@berkeley.edu>
|
Tim Howes (timhowes) <timhowes@berkeley.edu>
|
||||||
|
Tim Nordenfur <tim@gurka.se>
|
||||||
Tobias Frölich <40638719+tobifroe@users.noreply.github.com>
|
Tobias Frölich <40638719+tobifroe@users.noreply.github.com>
|
||||||
Tobias Klauser <tobias.klauser@gmail.com>
|
Tobias Klauser <tobias.klauser@gmail.com>
|
||||||
Tobias Nygren (tnn2) <tnn@nygren.pp.se>
|
Tobias Nygren (tnn2) <tnn@nygren.pp.se>
|
||||||
Tobias Tom (tobiastom) <t.tom@succont.de>
|
Tobias Tom (tobiastom) <t.tom@succont.de>
|
||||||
Tom Jakubowski <tom@crystae.net>
|
Tom Jakubowski <tom@crystae.net>
|
||||||
|
Tomasz Wilczyński <5626656+tomasz1986@users.noreply.github.com> <twilczynski@naver.com>
|
||||||
|
Tommy Thorn <tommy-github-email@thorn.ws>
|
||||||
|
Tommy van der Vorst <tommy-github@pixelspark.nl> <tommy@pixelspark.nl>
|
||||||
Tully Robinson (tojrobinson) <tully@tojr.org>
|
Tully Robinson (tojrobinson) <tully@tojr.org>
|
||||||
Tyler Brazier (tylerbrazier) <tyler@tylerbrazier.com>
|
Tyler Brazier (tylerbrazier) <tyler@tylerbrazier.com>
|
||||||
Tyler Kropp <kropptyler@gmail.com>
|
Tyler Kropp <kropptyler@gmail.com>
|
||||||
Umer-Azaz <umer_azaz@yahoo.com>
|
|
||||||
Unrud (Unrud) <unrud@openaliasbox.org> <Unrud@users.noreply.github.com>
|
Unrud (Unrud) <unrud@openaliasbox.org> <Unrud@users.noreply.github.com>
|
||||||
Val Markovic <val@markovic.io>
|
|
||||||
vapatel2 <149737089+vapatel2@users.noreply.github.com>
|
vapatel2 <149737089+vapatel2@users.noreply.github.com>
|
||||||
Veeti Paananen (veeti) <veeti.paananen@rojekti.fi>
|
Veeti Paananen (veeti) <veeti.paananen@rojekti.fi>
|
||||||
Victor Buinsky (buinsky) <vix_booja@tut.by>
|
Victor Buinsky (buinsky) <vix_booja@tut.by>
|
||||||
@@ -322,16 +358,15 @@ Vik <63919734+ViktorOn@users.noreply.github.com>
|
|||||||
Vil Brekin (Vilbrekin) <vilbrekin@gmail.com>
|
Vil Brekin (Vilbrekin) <vilbrekin@gmail.com>
|
||||||
villekalliomaki <53118179+villekalliomaki@users.noreply.github.com>
|
villekalliomaki <53118179+villekalliomaki@users.noreply.github.com>
|
||||||
Vladimir Rusinov <vrusinov@google.com> <vladimir.rusinov@gmail.com>
|
Vladimir Rusinov <vrusinov@google.com> <vladimir.rusinov@gmail.com>
|
||||||
vvaswani <2571660+vvaswani@users.noreply.github.com>
|
|
||||||
wangguoliang <liangcszzu@163.com>
|
wangguoliang <liangcszzu@163.com>
|
||||||
WangXi <xib1102@icloud.com>
|
WangXi <xib1102@icloud.com>
|
||||||
Will Rouesnel <wrouesnel@wrouesnel.com>
|
Will Rouesnel <wrouesnel@wrouesnel.com>
|
||||||
William A. Kennington III (wkennington) <william@wkennington.com>
|
William A. Kennington III (wkennington) <william@wkennington.com>
|
||||||
wouter bolsterlee <wouter@bolsterl.ee>
|
wouter bolsterlee <wouter@bolsterl.ee>
|
||||||
|
Wulf Weich (wweich) <wweich@users.noreply.github.com> <wweich@gmx.de> <wulf@weich-kr.de>
|
||||||
xarx00 <xarx00@users.noreply.github.com>
|
xarx00 <xarx00@users.noreply.github.com>
|
||||||
Xavier O. (damajor) <damajor@gmail.com>
|
Xavier O. (damajor) <damajor@gmail.com>
|
||||||
xjtdy888 (xjtdy888) <xjtdy888@163.com> <xjtdy888@gmail.com>
|
xjtdy888 (xjtdy888) <xjtdy888@163.com>
|
||||||
Yannic A. (eipiminus1) <eipiminusone+github@gmail.com> <eipiminus1@users.noreply.github.com>
|
Yannic A. (eipiminus1) <eipiminusone+github@gmail.com> <eipiminus1@users.noreply.github.com>
|
||||||
yparitcher <y@paritcher.com>
|
|
||||||
佛跳墙 <daoquan@qq.com>
|
佛跳墙 <daoquan@qq.com>
|
||||||
落心 <luoxin.ttt@gmail.com>
|
落心 <luoxin.ttt@gmail.com>
|
||||||
|
|||||||
+11
-151
@@ -34,163 +34,19 @@ Note that the previously used service at
|
|||||||
retired and we kindly ask you to sign up on Weblate for continued
|
retired and we kindly ask you to sign up on Weblate for continued
|
||||||
involvement.
|
involvement.
|
||||||
|
|
||||||
|
## Contributing Code
|
||||||
|
|
||||||
|
Every contribution is welcome. If you want to contribute but are unsure
|
||||||
|
where to start, any open issues are fair game! See the [Contribution
|
||||||
|
Guidelines](https://docs.syncthing.net/dev/contributing.html) for the full
|
||||||
|
story on committing code.
|
||||||
|
|
||||||
## Contributing Documentation
|
## Contributing Documentation
|
||||||
|
|
||||||
Updates to the [documentation site](https://docs.syncthing.net/) can be
|
Updates to the [documentation site](https://docs.syncthing.net/) can be
|
||||||
made as pull requests on the [documentation
|
made as pull requests on the [documentation
|
||||||
repository](https://github.com/syncthing/docs).
|
repository](https://github.com/syncthing/docs).
|
||||||
|
|
||||||
## Contributing Code
|
|
||||||
|
|
||||||
Every contribution is welcome. If you want to contribute but are unsure
|
|
||||||
where to start, any open issues are fair game! Here's a short rundown of
|
|
||||||
what you need to keep in mind:
|
|
||||||
|
|
||||||
- Don't worry. You are not expected to get everything right on the first
|
|
||||||
attempt, we'll guide you through it.
|
|
||||||
|
|
||||||
- Make sure there is an
|
|
||||||
[issue](https://github.com/syncthing/syncthing/issues) that describes the
|
|
||||||
change you want to do. If the thing you want to do does not have an issue
|
|
||||||
yet, please file one before starting work on it.
|
|
||||||
|
|
||||||
- Fork the repository and make your changes in a new branch. Once it's ready
|
|
||||||
for review, create a pull request.
|
|
||||||
|
|
||||||
### Authorship
|
|
||||||
|
|
||||||
All code authors are listed in the AUTHORS file. When your first pull
|
|
||||||
request is accepted your details are added to the AUTHORS file and the list
|
|
||||||
of authors in the GUI. Commits must be made with the same name and email as
|
|
||||||
listed in the AUTHORS file. To accomplish this, ensure that your git
|
|
||||||
configuration is set correctly prior to making your first commit:
|
|
||||||
|
|
||||||
$ git config --global user.name "Jane Doe"
|
|
||||||
$ git config --global user.email janedoe@example.com
|
|
||||||
|
|
||||||
You must be reachable on the given email address. If you do not wish to use
|
|
||||||
your real name for whatever reason, using a nickname or pseudonym is
|
|
||||||
perfectly acceptable.
|
|
||||||
|
|
||||||
### The Developer Certificate of Origin (DCO)
|
|
||||||
|
|
||||||
The Syncthing project requires the Developer Certificate of Origin (DCO)
|
|
||||||
sign-off on pull requests (PRs). This means that all commit messages must
|
|
||||||
contain a signature line to indicate that the developer accepts the DCO.
|
|
||||||
|
|
||||||
The DCO is a lightweight way for contributors to certify that they wrote (or
|
|
||||||
otherwise have the right to submit) the code and changes they are
|
|
||||||
contributing to the project. Here is the full [text of the
|
|
||||||
DCO](https://developercertificate.org):
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
By making a contribution to this project, I certify that:
|
|
||||||
|
|
||||||
1. The contribution was created in whole or in part by me and I have the
|
|
||||||
right to submit it under the open source license indicated in the file;
|
|
||||||
or
|
|
||||||
|
|
||||||
2. The contribution is based upon previous work that, to the best of my
|
|
||||||
knowledge, is covered under an appropriate open source license and I have
|
|
||||||
the right under that license to submit that work with modifications,
|
|
||||||
whether created in whole or in part by me, under the same open source
|
|
||||||
license (unless I am permitted to submit under a different license), as
|
|
||||||
indicated in the file; or
|
|
||||||
|
|
||||||
3. The contribution was provided directly to me by some other person who
|
|
||||||
certified (1), (2) or (3) and I have not modified it.
|
|
||||||
|
|
||||||
4. I understand and agree that this project and the contribution are public
|
|
||||||
and that a record of the contribution (including all personal information
|
|
||||||
I submit with it, including my sign-off) is maintained indefinitely and
|
|
||||||
may be redistributed consistent with this project or the open source
|
|
||||||
license(s) involved.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Contributors indicate that they adhere to these requirements by adding
|
|
||||||
a `Signed-off-by` line to their commit messages. For example:
|
|
||||||
|
|
||||||
This is my commit message
|
|
||||||
|
|
||||||
Signed-off-by: Random J Developer <random@developer.example.org>
|
|
||||||
|
|
||||||
The name and email address in this line must match those of the committing
|
|
||||||
author, and be the same as what you want in the AUTHORS file as per above.
|
|
||||||
|
|
||||||
### Coding Style
|
|
||||||
|
|
||||||
#### General
|
|
||||||
|
|
||||||
- All text files use Unix line endings. The git settings already present in
|
|
||||||
the repository attempt to enforce this.
|
|
||||||
|
|
||||||
- When making changes, follow the brace and parenthesis style of the
|
|
||||||
surrounding code.
|
|
||||||
|
|
||||||
#### Go Specific
|
|
||||||
|
|
||||||
- Follow the conventions laid out in [Effective
|
|
||||||
Go](https://go.dev/doc/effective_go) as much as makes sense. The review
|
|
||||||
guidelines in [Go Code Review
|
|
||||||
Comments](https://github.com/golang/go/wiki/CodeReviewComments) should
|
|
||||||
generally be followed.
|
|
||||||
|
|
||||||
- Each commit should be `go fmt` clean.
|
|
||||||
|
|
||||||
- Imports are grouped per `goimports` standard; that is, standard
|
|
||||||
library first, then third party libraries after a blank line.
|
|
||||||
|
|
||||||
### Commits
|
|
||||||
|
|
||||||
- Commit messages (and pull request titles) should follow the [conventional
|
|
||||||
commits](https://www.conventionalcommits.org/en/v1.0.0/) specification and
|
|
||||||
be in lower case.
|
|
||||||
|
|
||||||
- We use a scope description in the commit message subject. This is the
|
|
||||||
component of Syncthing that the commit affects. For example, `gui`,
|
|
||||||
`protocol`, `scanner`, `upnp`, etc -- typically, the part after
|
|
||||||
`internal/`, `lib/` or `cmd/` in the package path. If the commit doesn't
|
|
||||||
affect a specific component, such as for changes to the build system or
|
|
||||||
documentation, the scope should be omitted. The same goes for changes that
|
|
||||||
affect many components which would be cumbersome to list.
|
|
||||||
|
|
||||||
- Commits that resolve an existing issue must include the issue number
|
|
||||||
as `(fixes #123)` at the end of the commit message subject. A correctly
|
|
||||||
formatted commit message subject looks like this:
|
|
||||||
|
|
||||||
feat(dialer): add env var to disable proxy fallback (fixes #3006)
|
|
||||||
|
|
||||||
- If the commit message subject doesn't say it all, one or more paragraphs of
|
|
||||||
describing text should be added to the commit message. This should explain
|
|
||||||
why the change is made and what it accomplishes.
|
|
||||||
|
|
||||||
- When drafting a pull request, please feel free to add commits with
|
|
||||||
corrections and merge from `main` when necessary. This provides a clear time
|
|
||||||
line with changes and simplifies review. Do not, in general, rebase your
|
|
||||||
commits, as this makes review harder.
|
|
||||||
|
|
||||||
- Pull requests are merged to `main` using squash merge. The "stream of
|
|
||||||
consciousness" set of commits described in the previous point will be reduced
|
|
||||||
to a single commit at merge time. The pull request title and description will
|
|
||||||
be used as the commit message.
|
|
||||||
|
|
||||||
### Tests
|
|
||||||
|
|
||||||
Yes please, do add tests when adding features or fixing bugs. Also, when a
|
|
||||||
pull request is filed a number of automatic tests are run on the code. This
|
|
||||||
includes:
|
|
||||||
|
|
||||||
- That the code actually builds and the test suite passes.
|
|
||||||
|
|
||||||
- That the code is correctly formatted (`go fmt`).
|
|
||||||
|
|
||||||
- That the commits are based on a reasonably recent `main`.
|
|
||||||
|
|
||||||
- That the output from `go lint` and `go vet` is clean. (This checks for a
|
|
||||||
number of potential problems the compiler doesn't catch.)
|
|
||||||
|
|
||||||
## Licensing
|
## Licensing
|
||||||
|
|
||||||
All contributions are made available under the same license as the already
|
All contributions are made available under the same license as the already
|
||||||
@@ -203,6 +59,10 @@ otherwise stated this means MPLv2, but there are exceptions:
|
|||||||
- The documentation (man/...) is licensed under the Creative Commons
|
- The documentation (man/...) is licensed under the Creative Commons
|
||||||
Attribution 4.0 International License.
|
Attribution 4.0 International License.
|
||||||
|
|
||||||
|
- Projects under vendor/... are copyright by and licensed from their
|
||||||
|
respective original authors. Contributions should be made to the original
|
||||||
|
project, not here.
|
||||||
|
|
||||||
Regardless of the license in effect, you retain the copyright to your
|
Regardless of the license in effect, you retain the copyright to your
|
||||||
contribution.
|
contribution.
|
||||||
|
|
||||||
|
|||||||
+64
-20
@@ -23,7 +23,52 @@ example `UMASK=002`.
|
|||||||
**Docker cli**
|
**Docker cli**
|
||||||
```
|
```
|
||||||
$ docker pull syncthing/syncthing
|
$ docker pull syncthing/syncthing
|
||||||
$ docker run --network=host -e STGUIADDRESS= \
|
$ docker run -p 8384:8384 -p 22000:22000/tcp -p 22000:22000/udp -p 21027:21027/udp \
|
||||||
|
-v /wherever/st-sync:/var/syncthing \
|
||||||
|
--hostname=my-syncthing \
|
||||||
|
syncthing/syncthing:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
**Docker compose**
|
||||||
|
```yml
|
||||||
|
---
|
||||||
|
version: "3"
|
||||||
|
services:
|
||||||
|
syncthing:
|
||||||
|
image: syncthing/syncthing
|
||||||
|
container_name: syncthing
|
||||||
|
hostname: my-syncthing
|
||||||
|
environment:
|
||||||
|
- PUID=1000
|
||||||
|
- PGID=1000
|
||||||
|
volumes:
|
||||||
|
- /wherever/st-sync:/var/syncthing
|
||||||
|
ports:
|
||||||
|
- 8384:8384 # Web UI
|
||||||
|
- 22000:22000/tcp # TCP file transfers
|
||||||
|
- 22000:22000/udp # QUIC file transfers
|
||||||
|
- 21027:21027/udp # Receive local discovery broadcasts
|
||||||
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: curl -fkLsS -m 2 127.0.0.1:8384/rest/noauth/health | grep -o --color=never OK || exit 1
|
||||||
|
interval: 1m
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
```
|
||||||
|
|
||||||
|
## Discovery
|
||||||
|
|
||||||
|
Note that Docker's default network mode prevents local IP addresses from
|
||||||
|
being discovered, as Syncthing is only able to see the internal IP of the
|
||||||
|
container on the `172.17.0.0/16` subnet. This will result in poor transfer rates
|
||||||
|
if local device addresses are not manually configured.
|
||||||
|
|
||||||
|
It is therefore advisable to use the [host network mode](https://docs.docker.com/network/host/) instead:
|
||||||
|
|
||||||
|
**Docker cli**
|
||||||
|
```
|
||||||
|
$ docker pull syncthing/syncthing
|
||||||
|
$ docker run --network=host \
|
||||||
-v /wherever/st-sync:/var/syncthing \
|
-v /wherever/st-sync:/var/syncthing \
|
||||||
syncthing/syncthing:latest
|
syncthing/syncthing:latest
|
||||||
```
|
```
|
||||||
@@ -40,7 +85,6 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
- PUID=1000
|
- PUID=1000
|
||||||
- PGID=1000
|
- PGID=1000
|
||||||
- STGUIADDRESS=
|
|
||||||
volumes:
|
volumes:
|
||||||
- /wherever/st-sync:/var/syncthing
|
- /wherever/st-sync:/var/syncthing
|
||||||
network_mode: host
|
network_mode: host
|
||||||
@@ -52,27 +96,27 @@ services:
|
|||||||
retries: 3
|
retries: 3
|
||||||
```
|
```
|
||||||
|
|
||||||
## Discovery
|
|
||||||
|
|
||||||
Please note that Docker's default network mode prevents local IP addresses
|
|
||||||
from being discovered, as Syncthing can only see the internal IP address of
|
|
||||||
the container on the `172.17.0.0/16` subnet. This would likely break the ability
|
|
||||||
for nodes to establish LAN connections properly, resulting in poor transfer
|
|
||||||
rates unless local device addresses are configured manually.
|
|
||||||
|
|
||||||
It is therefore strongly recommended to stick to the [host network mode](https://docs.docker.com/network/host/),
|
|
||||||
as shown above.
|
|
||||||
|
|
||||||
Be aware that syncthing alone is now in control of what interfaces and ports it
|
Be aware that syncthing alone is now in control of what interfaces and ports it
|
||||||
listens on. You can edit the syncthing configuration to change the defaults if
|
listens on. You can edit the syncthing configuration to change the defaults if
|
||||||
there are conflicts.
|
there are conflicts.
|
||||||
|
|
||||||
## GUI Security
|
## GUI Security
|
||||||
|
|
||||||
By default Syncthing inside the Docker image listens on `0.0.0.0:8384`. This
|
By default Syncthing inside the Docker image listens on 0.0.0.0:8384 to
|
||||||
allows GUI connections when running without host network mode. The example
|
allow GUI connections via the Docker proxy. This is set by the
|
||||||
above unsets the `STGUIADDRESS` environment variable to have Syncthing fall
|
`STGUIADDRESS` environment variable in the Dockerfile, as it differs from
|
||||||
back to listening on what has been configured in the configuration file or the
|
what Syncthing would otherwise use by default. This means you should set up
|
||||||
GUI settings dialog. By default this is the localhost IP address `127.0.0.1`.
|
authentication in the GUI, like for any other externally reachable Syncthing
|
||||||
If you configure your GUI to be externally reachable, make sure you set up
|
instance. If you do not require the GUI, or you use host networking, you can
|
||||||
authentication and enable TLS.
|
unset the `STGUIADDRESS` variable to have Syncthing fall back to listening
|
||||||
|
on 127.0.0.1:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ docker pull syncthing/syncthing
|
||||||
|
$ docker run -e STGUIADDRESS= \
|
||||||
|
-v /wherever/st-sync:/var/syncthing \
|
||||||
|
syncthing/syncthing:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
With the environment variable unset Syncthing will follow what is set in the
|
||||||
|
configuration file / GUI settings dialog.
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ var (
|
|||||||
timeout = "120s"
|
timeout = "120s"
|
||||||
longTimeout = "600s"
|
longTimeout = "600s"
|
||||||
numVersions = 5
|
numVersions = 5
|
||||||
|
withNextGenGUI = os.Getenv("BUILD_NEXT_GEN_GUI") != ""
|
||||||
)
|
)
|
||||||
|
|
||||||
type target struct {
|
type target struct {
|
||||||
@@ -288,10 +289,10 @@ func runCommand(cmd string, target target) {
|
|||||||
build(target, tags)
|
build(target, tags)
|
||||||
|
|
||||||
case "test":
|
case "test":
|
||||||
test(strings.Fields(extraTags), "github.com/syncthing/syncthing/internal/...", "github.com/syncthing/syncthing/lib/...", "github.com/syncthing/syncthing/cmd/...")
|
test(strings.Fields(extraTags), "github.com/syncthing/syncthing/lib/...", "github.com/syncthing/syncthing/cmd/...")
|
||||||
|
|
||||||
case "bench":
|
case "bench":
|
||||||
bench(strings.Fields(extraTags), "github.com/syncthing/syncthing/internal/...", "github.com/syncthing/syncthing/lib/...", "github.com/syncthing/syncthing/cmd/...")
|
bench(strings.Fields(extraTags), "github.com/syncthing/syncthing/lib/...", "github.com/syncthing/syncthing/cmd/...")
|
||||||
|
|
||||||
case "integration":
|
case "integration":
|
||||||
integration(false)
|
integration(false)
|
||||||
@@ -379,6 +380,7 @@ func parseFlags() {
|
|||||||
flag.IntVar(&numVersions, "num-versions", numVersions, "Number of versions for changelog command")
|
flag.IntVar(&numVersions, "num-versions", numVersions, "Number of versions for changelog command")
|
||||||
flag.StringVar(&run, "run", "", "Specify which tests to run")
|
flag.StringVar(&run, "run", "", "Specify which tests to run")
|
||||||
flag.StringVar(&benchRun, "bench", "", "Specify which benchmarks 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.StringVar(&buildOut, "build-out", "", "Set the '-o' value for 'go build'")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
}
|
}
|
||||||
@@ -451,6 +453,10 @@ func benchArgs() []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func install(target target, tags []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()
|
lazyRebuildAssets()
|
||||||
|
|
||||||
tags = append(target.tags, tags...)
|
tags = append(target.tags, tags...)
|
||||||
@@ -474,12 +480,16 @@ func install(target target, tags []string) {
|
|||||||
defer shouldCleanupSyso(sysoPath)
|
defer shouldCleanupSyso(sysoPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
args := []string{"install"}
|
args := []string{"install", "-v"}
|
||||||
args = appendParameters(args, tags, target.buildPkgs...)
|
args = appendParameters(args, tags, target.buildPkgs...)
|
||||||
runPrint(goCmd, args...)
|
runPrint(goCmd, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func build(target target, tags []string) {
|
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()
|
lazyRebuildAssets()
|
||||||
tags = append(target.tags, tags...)
|
tags = append(target.tags, tags...)
|
||||||
|
|
||||||
@@ -502,7 +512,7 @@ func build(target target, tags []string) {
|
|||||||
defer shouldCleanupSyso(sysoPath)
|
defer shouldCleanupSyso(sysoPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
args := []string{"build"}
|
args := []string{"build", "-v"}
|
||||||
if buildOut != "" {
|
if buildOut != "" {
|
||||||
args = append(args, "-o", buildOut)
|
args = append(args, "-o", buildOut)
|
||||||
}
|
}
|
||||||
@@ -514,6 +524,13 @@ func setBuildEnvVars() {
|
|||||||
os.Setenv("GOOS", goos)
|
os.Setenv("GOOS", goos)
|
||||||
os.Setenv("GOARCH", goarch)
|
os.Setenv("GOARCH", goarch)
|
||||||
os.Setenv("CC", cc)
|
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 {
|
func appendParameters(args []string, tags []string, pkgs ...string) []string {
|
||||||
@@ -732,9 +749,12 @@ func shouldBuildSyso(dir string) (string, error) {
|
|||||||
sysoPath := filepath.Join(dir, "cmd", "syncthing", "resource.syso")
|
sysoPath := filepath.Join(dir, "cmd", "syncthing", "resource.syso")
|
||||||
|
|
||||||
// See https://github.com/josephspurrier/goversioninfo#command-line-flags
|
// See https://github.com/josephspurrier/goversioninfo#command-line-flags
|
||||||
arm := strings.HasPrefix(goarch, "arm")
|
armOption := ""
|
||||||
a64 := strings.Contains(goarch, "64")
|
if strings.Contains(goarch, "arm") {
|
||||||
if _, err := runError("goversioninfo", "-o", sysoPath, fmt.Sprintf("-arm=%v", arm), fmt.Sprintf("-64=%v", a64)); err != nil {
|
armOption = "-arm=true"
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := runError("goversioninfo", "-o", sysoPath, armOption); err != nil {
|
||||||
return "", errors.New("failed to create " + sysoPath + ": " + err.Error())
|
return "", errors.New("failed to create " + sysoPath + ": " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -806,11 +826,43 @@ func lazyRebuildAssets() {
|
|||||||
shouldRebuild := shouldRebuildAssets("lib/api/auto/gui.files.go", "gui") ||
|
shouldRebuild := shouldRebuildAssets("lib/api/auto/gui.files.go", "gui") ||
|
||||||
shouldRebuildAssets("cmd/infra/strelaypoolsrv/auto/gui.files.go", "cmd/infra/strelaypoolsrv/gui")
|
shouldRebuildAssets("cmd/infra/strelaypoolsrv/auto/gui.files.go", "cmd/infra/strelaypoolsrv/gui")
|
||||||
|
|
||||||
|
if withNextGenGUI {
|
||||||
|
shouldRebuild = buildNextGenGUI() || shouldRebuild
|
||||||
|
}
|
||||||
|
|
||||||
if shouldRebuild {
|
if shouldRebuild {
|
||||||
rebuildAssets()
|
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 {
|
func shouldRebuildAssets(target, srcdir string) bool {
|
||||||
info, err := os.Stat(target)
|
info, err := os.Stat(target)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -870,6 +922,7 @@ func testmocks() {
|
|||||||
"github.com/syncthing/syncthing/lib/connections",
|
"github.com/syncthing/syncthing/lib/connections",
|
||||||
"github.com/syncthing/syncthing/lib/discover",
|
"github.com/syncthing/syncthing/lib/discover",
|
||||||
"github.com/syncthing/syncthing/lib/events",
|
"github.com/syncthing/syncthing/lib/events",
|
||||||
|
"github.com/syncthing/syncthing/lib/logger",
|
||||||
"github.com/syncthing/syncthing/lib/model",
|
"github.com/syncthing/syncthing/lib/model",
|
||||||
"github.com/syncthing/syncthing/lib/protocol",
|
"github.com/syncthing/syncthing/lib/protocol",
|
||||||
}
|
}
|
||||||
@@ -900,7 +953,6 @@ func weblate() {
|
|||||||
func ldflags(tags []string) string {
|
func ldflags(tags []string) string {
|
||||||
b := new(strings.Builder)
|
b := new(strings.Builder)
|
||||||
b.WriteString("-w")
|
b.WriteString("-w")
|
||||||
b.WriteString(" -buildid=")
|
|
||||||
fmt.Fprintf(b, " -X github.com/syncthing/syncthing/lib/build.Version=%s", version)
|
fmt.Fprintf(b, " -X github.com/syncthing/syncthing/lib/build.Version=%s", version)
|
||||||
fmt.Fprintf(b, " -X github.com/syncthing/syncthing/lib/build.Stamp=%d", buildStamp())
|
fmt.Fprintf(b, " -X github.com/syncthing/syncthing/lib/build.Stamp=%d", buildStamp())
|
||||||
fmt.Fprintf(b, " -X github.com/syncthing/syncthing/lib/build.User=%s", buildUser())
|
fmt.Fprintf(b, " -X github.com/syncthing/syncthing/lib/build.User=%s", buildUser())
|
||||||
@@ -922,9 +974,6 @@ func rmr(paths ...string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getReleaseVersion() (string, error) {
|
func getReleaseVersion() (string, error) {
|
||||||
if ver := os.Getenv("VERSION"); ver != "" {
|
|
||||||
return strings.TrimSpace(ver), nil
|
|
||||||
}
|
|
||||||
bs, err := os.ReadFile("RELEASE")
|
bs, err := os.ReadFile("RELEASE")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -969,7 +1018,7 @@ func getGitVersion() (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getVersion() string {
|
func getVersion() string {
|
||||||
// First try for a RELEASE file or $VERSION env var,
|
// First try for a RELEASE file,
|
||||||
if ver, err := getReleaseVersion(); err == nil {
|
if ver, err := getReleaseVersion(); err == nil {
|
||||||
return ver
|
return ver
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
_ "github.com/syncthing/syncthing/lib/automaxprocs"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import (
|
|||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
|
|
||||||
"github.com/syncthing/syncthing/internal/gen/discoproto"
|
"github.com/syncthing/syncthing/internal/gen/discoproto"
|
||||||
|
_ "github.com/syncthing/syncthing/lib/automaxprocs"
|
||||||
"github.com/syncthing/syncthing/lib/beacon"
|
"github.com/syncthing/syncthing/lib/beacon"
|
||||||
"github.com/syncthing/syncthing/lib/discover"
|
"github.com/syncthing/syncthing/lib/discover"
|
||||||
"github.com/syncthing/syncthing/lib/protocol"
|
"github.com/syncthing/syncthing/lib/protocol"
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
_ "github.com/syncthing/syncthing/lib/automaxprocs"
|
||||||
)
|
)
|
||||||
|
|
||||||
type event struct {
|
type event struct {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
_ "github.com/syncthing/syncthing/lib/automaxprocs"
|
||||||
"github.com/syncthing/syncthing/lib/protocol"
|
"github.com/syncthing/syncthing/lib/protocol"
|
||||||
"github.com/syncthing/syncthing/lib/scanner"
|
"github.com/syncthing/syncthing/lib/scanner"
|
||||||
)
|
)
|
||||||
@@ -71,7 +72,7 @@ func main() {
|
|||||||
if *standardBlocks || blockSize < protocol.MinBlockSize {
|
if *standardBlocks || blockSize < protocol.MinBlockSize {
|
||||||
blockSize = protocol.BlockSize(fi.Size())
|
blockSize = protocol.BlockSize(fi.Size())
|
||||||
}
|
}
|
||||||
bs, err := scanner.Blocks(context.TODO(), fd, blockSize, fi.Size(), nil)
|
bs, err := scanner.Blocks(context.TODO(), fd, blockSize, fi.Size(), nil, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
_ "github.com/syncthing/syncthing/lib/automaxprocs"
|
||||||
"github.com/syncthing/syncthing/lib/config"
|
"github.com/syncthing/syncthing/lib/config"
|
||||||
"github.com/syncthing/syncthing/lib/discover"
|
"github.com/syncthing/syncthing/lib/discover"
|
||||||
"github.com/syncthing/syncthing/lib/events"
|
"github.com/syncthing/syncthing/lib/events"
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
_ "github.com/syncthing/syncthing/lib/automaxprocs"
|
||||||
"github.com/syncthing/syncthing/lib/fs"
|
"github.com/syncthing/syncthing/lib/fs"
|
||||||
"github.com/syncthing/syncthing/lib/ignore"
|
"github.com/syncthing/syncthing/lib/ignore"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
_ "github.com/syncthing/syncthing/lib/automaxprocs"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
_ "github.com/syncthing/syncthing/lib/automaxprocs"
|
||||||
"github.com/syncthing/syncthing/lib/signature"
|
"github.com/syncthing/syncthing/lib/signature"
|
||||||
"github.com/syncthing/syncthing/lib/upgrade"
|
"github.com/syncthing/syncthing/lib/upgrade"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import (
|
|||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
_ "github.com/syncthing/syncthing/lib/automaxprocs"
|
||||||
"github.com/syncthing/syncthing/lib/protocol"
|
"github.com/syncthing/syncthing/lib/protocol"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -52,9 +53,11 @@ func main() {
|
|||||||
// Run one certificate generator per CPU core.
|
// Run one certificate generator per CPU core.
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
for i := 0; i < runtime.GOMAXPROCS(-1); i++ {
|
for i := 0; i < runtime.GOMAXPROCS(-1); i++ {
|
||||||
wg.Go(func() {
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
generatePrefixed(prefix, &count, found, stop)
|
generatePrefixed(prefix, &count, found, stop)
|
||||||
})
|
wg.Done()
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save the result, when one has been found.
|
// Save the result, when one has been found.
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
_ "github.com/syncthing/syncthing/lib/automaxprocs"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import (
|
|||||||
"github.com/alecthomas/kong"
|
"github.com/alecthomas/kong"
|
||||||
raven "github.com/getsentry/raven-go"
|
raven "github.com/getsentry/raven-go"
|
||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
|
_ "github.com/syncthing/syncthing/lib/automaxprocs"
|
||||||
"github.com/syncthing/syncthing/lib/build"
|
"github.com/syncthing/syncthing/lib/build"
|
||||||
"github.com/syncthing/syncthing/lib/ur"
|
"github.com/syncthing/syncthing/lib/ur"
|
||||||
)
|
)
|
||||||
@@ -43,7 +44,7 @@ type cli struct {
|
|||||||
SentryQueue int `help:"Maximum number of reports to queue for sending to Sentry" default:"64" env:"SENTRY_QUEUE"`
|
SentryQueue int `help:"Maximum number of reports to queue for sending to Sentry" default:"64" env:"SENTRY_QUEUE"`
|
||||||
DiskQueue int `help:"Maximum number of reports to queue for writing to disk" default:"64" env:"DISK_QUEUE"`
|
DiskQueue int `help:"Maximum number of reports to queue for writing to disk" default:"64" env:"DISK_QUEUE"`
|
||||||
MetricsListen string `help:"HTTP listen address for metrics" default:":8081" env:"METRICS_LISTEN_ADDRESS"`
|
MetricsListen string `help:"HTTP listen address for metrics" default:":8081" env:"METRICS_LISTEN_ADDRESS"`
|
||||||
IgnorePatterns string `help:"File containing ignore patterns (regexp)" env:"IGNORE_PATTERNS" type:"existingfile"`
|
IngorePatterns string `help:"File containing ignore patterns (regexp)" env:"IGNORE_PATTERNS" type:"existingfile"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -67,9 +68,9 @@ func main() {
|
|||||||
go ss.Serve(context.Background())
|
go ss.Serve(context.Background())
|
||||||
|
|
||||||
var ip *ignorePatterns
|
var ip *ignorePatterns
|
||||||
if params.IgnorePatterns != "" {
|
if params.IngorePatterns != "" {
|
||||||
var err error
|
var err error
|
||||||
ip, err = loadIgnorePatterns(params.IgnorePatterns)
|
ip, err = loadIgnorePatterns(params.IngorePatterns)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to load ignore patterns: %v", err)
|
log.Fatalf("Failed to load ignore patterns: %v", err)
|
||||||
}
|
}
|
||||||
@@ -101,8 +102,6 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.SetOutput(os.Stdout)
|
log.SetOutput(os.Stdout)
|
||||||
log.Println(build.LongVersionFor("stcrashreceiver"))
|
|
||||||
|
|
||||||
if err := http.ListenAndServe(params.Listen, mux); err != nil {
|
if err := http.ListenAndServe(params.Listen, mux); err != nil {
|
||||||
log.Fatalln("HTTP serve:", err)
|
log.Fatalln("HTTP serve:", err)
|
||||||
}
|
}
|
||||||
@@ -119,7 +118,7 @@ func handleFailureFn(dsn, failureDir string, ignore *ignorePatterns) func(w http
|
|||||||
bs, err := io.ReadAll(lr)
|
bs, err := io.ReadAll(lr)
|
||||||
req.Body.Close()
|
req.Body.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), 500)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,7 +130,7 @@ func handleFailureFn(dsn, failureDir string, ignore *ignorePatterns) func(w http
|
|||||||
var reports []ur.FailureReport
|
var reports []ur.FailureReport
|
||||||
err = json.Unmarshal(bs, &reports)
|
err = json.Unmarshal(bs, &reports)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
http.Error(w, err.Error(), 400)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(reports) == 0 {
|
if len(reports) == 0 {
|
||||||
@@ -142,7 +141,7 @@ func handleFailureFn(dsn, failureDir string, ignore *ignorePatterns) func(w http
|
|||||||
|
|
||||||
version, err := build.ParseVersion(reports[0].Version)
|
version, err := build.ParseVersion(reports[0].Version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
http.Error(w, err.Error(), 400)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, r := range reports {
|
for _, r := range reports {
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ func sendReport(dsn string, pkt *raven.Packet, userID string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The client sets release and such on the packet before sending, in the
|
// The client sets release and such on the packet before sending, in the
|
||||||
// misguided idea that it knows this better than the packet we give
|
// misguided idea that it knows this better than than the packet we give
|
||||||
// it. So we copy the values from the packet to the client first...
|
// it. So we copy the values from the packet to the client first...
|
||||||
cli.SetRelease(pkt.Release)
|
cli.SetRelease(pkt.Release)
|
||||||
cli.SetEnvironment(pkt.Environment)
|
cli.SetEnvironment(pkt.Environment)
|
||||||
@@ -136,7 +136,7 @@ func parseCrashReport(path string, report []byte) (*raven.Packet, error) {
|
|||||||
|
|
||||||
r := bytes.NewReader(report)
|
r := bytes.NewReader(report)
|
||||||
ctx, _, err := stack.ScanSnapshot(r, io.Discard, stack.DefaultOpts())
|
ctx, _, err := stack.ScanSnapshot(r, io.Discard, stack.DefaultOpts())
|
||||||
if err != nil && !errors.Is(err, io.EOF) {
|
if err != nil && err != io.EOF {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if ctx == nil || len(ctx.Goroutines) == 0 {
|
if ctx == nil || len(ctx.Goroutines) == 0 {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@@ -23,7 +23,7 @@ import (
|
|||||||
// remote IP, and the current month.
|
// remote IP, and the current month.
|
||||||
func userIDFor(req *http.Request) string {
|
func userIDFor(req *http.Request) string {
|
||||||
addr := req.RemoteAddr
|
addr := req.RemoteAddr
|
||||||
if fwd := req.Header.Get("X-Forwarded-For"); fwd != "" {
|
if fwd := req.Header.Get("x-forwarded-for"); fwd != "" {
|
||||||
addr = fwd
|
addr = fwd
|
||||||
}
|
}
|
||||||
if host, _, err := net.SplitHostPort(addr); err == nil {
|
if host, _, err := net.SplitHostPort(addr); err == nil {
|
||||||
@@ -32,7 +32,7 @@ func userIDFor(req *http.Request) string {
|
|||||||
now := time.Now().Format("200601")
|
now := time.Now().Format("200601")
|
||||||
salt := "stcrashreporter"
|
salt := "stcrashreporter"
|
||||||
hash := sha256.Sum256([]byte(salt + addr + now))
|
hash := sha256.Sum256([]byte(salt + addr + now))
|
||||||
return hex.EncodeToString(hash[:8])
|
return fmt.Sprintf("%x", hash[:8])
|
||||||
}
|
}
|
||||||
|
|
||||||
// 01234567890abcdef... => 01/23
|
// 01234567890abcdef... => 01/23
|
||||||
|
|||||||
@@ -193,7 +193,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<script type="text/javascript" src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
<script type="text/javascript" src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
|
||||||
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.8/angular.min.js"></script>
|
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.8/angular.min.js"></script>
|
||||||
<script type="text/javascript" src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
|
<script type="text/javascript" src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -27,11 +26,12 @@ import (
|
|||||||
|
|
||||||
"github.com/syncthing/syncthing/cmd/infra/strelaypoolsrv/auto"
|
"github.com/syncthing/syncthing/cmd/infra/strelaypoolsrv/auto"
|
||||||
"github.com/syncthing/syncthing/lib/assets"
|
"github.com/syncthing/syncthing/lib/assets"
|
||||||
"github.com/syncthing/syncthing/lib/build"
|
_ "github.com/syncthing/syncthing/lib/automaxprocs"
|
||||||
"github.com/syncthing/syncthing/lib/geoip"
|
"github.com/syncthing/syncthing/lib/geoip"
|
||||||
"github.com/syncthing/syncthing/lib/protocol"
|
"github.com/syncthing/syncthing/lib/protocol"
|
||||||
"github.com/syncthing/syncthing/lib/rand"
|
"github.com/syncthing/syncthing/lib/rand"
|
||||||
"github.com/syncthing/syncthing/lib/relay/client"
|
"github.com/syncthing/syncthing/lib/relay/client"
|
||||||
|
"github.com/syncthing/syncthing/lib/sync"
|
||||||
"github.com/syncthing/syncthing/lib/tlsutil"
|
"github.com/syncthing/syncthing/lib/tlsutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -115,7 +115,7 @@ var (
|
|||||||
|
|
||||||
requests chan request
|
requests chan request
|
||||||
|
|
||||||
mut sync.RWMutex
|
mut = sync.NewRWMutex()
|
||||||
knownRelays = make([]*relay, 0)
|
knownRelays = make([]*relay, 0)
|
||||||
permanentRelays = make([]*relay, 0)
|
permanentRelays = make([]*relay, 0)
|
||||||
evictionTimers = make(map[string]*time.Timer)
|
evictionTimers = make(map[string]*time.Timer)
|
||||||
@@ -147,8 +147,6 @@ func main() {
|
|||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
log.Println(build.LongVersionFor("strelaypoolsrv"))
|
|
||||||
|
|
||||||
requests = make(chan request, requestQueueLen)
|
requests = make(chan request, requestQueueLen)
|
||||||
geoip, err := geoip.NewGeoLite2CityProvider(context.Background(), geoipAccountID, geoipLicenseKey, os.TempDir())
|
geoip, err := geoip.NewGeoLite2CityProvider(context.Background(), geoipAccountID, geoipLicenseKey, os.TempDir())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -164,7 +162,7 @@ func main() {
|
|||||||
|
|
||||||
testCert = createTestCertificate()
|
testCert = createTestCertificate()
|
||||||
|
|
||||||
for range requestProcessors {
|
for i := 0; i < requestProcessors; i++ {
|
||||||
go requestProcessor(geoip)
|
go requestProcessor(geoip)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,7 +180,7 @@ func main() {
|
|||||||
relayTestsTotal.WithLabelValues("success").Inc()
|
relayTestsTotal.WithLabelValues("success").Inc()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Run the stats refresher once the relays are loaded.
|
// Run the the stats refresher once the relays are loaded.
|
||||||
statsRefresher(statsRefresh)
|
statsRefresher(statsRefresh)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -428,7 +426,7 @@ func handleRegister(w http.ResponseWriter, r *http.Request) {
|
|||||||
newRelay.URL = uri.String()
|
newRelay.URL = uri.String()
|
||||||
} else if host != rhost && relayCert == nil {
|
} else if host != rhost && relayCert == nil {
|
||||||
if debug {
|
if debug {
|
||||||
log.Println("IP address advertised does not match client IP address", rhost, uri)
|
log.Println("IP address advertised does not match client IP address", r.RemoteAddr, uri)
|
||||||
}
|
}
|
||||||
http.Error(w, fmt.Sprintf("IP advertised %s does not match client IP %s", host, rhost), http.StatusUnauthorized)
|
http.Error(w, fmt.Sprintf("IP advertised %s does not match client IP %s", host, rhost), http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
@@ -452,13 +450,13 @@ func handleRegister(w http.ResponseWriter, r *http.Request) {
|
|||||||
case requests <- request{&newRelay, reschan, prometheus.NewTimer(relayTestActionsSeconds.WithLabelValues("queue"))}:
|
case requests <- request{&newRelay, reschan, prometheus.NewTimer(relayTestActionsSeconds.WithLabelValues("queue"))}:
|
||||||
result := <-reschan
|
result := <-reschan
|
||||||
if result.err != nil {
|
if result.err != nil {
|
||||||
log.Println("Join from", rhost, "failed:", result.err)
|
log.Println("Join from", r.RemoteAddr, "failed:", result.err)
|
||||||
globalBlocklist.AddError(rhost)
|
globalBlocklist.AddError(rhost)
|
||||||
relayTestsTotal.WithLabelValues("failed").Inc()
|
relayTestsTotal.WithLabelValues("failed").Inc()
|
||||||
http.Error(w, result.err.Error(), http.StatusBadRequest)
|
http.Error(w, result.err.Error(), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Println("Join from", rhost, "succeeded")
|
log.Println("Join from", r.RemoteAddr, "succeeded")
|
||||||
globalBlocklist.ClearErrors(rhost)
|
globalBlocklist.ClearErrors(rhost)
|
||||||
relayTestsTotal.WithLabelValues("success").Inc()
|
relayTestsTotal.WithLabelValues("success").Inc()
|
||||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
@@ -622,7 +620,7 @@ func createTestCertificate() tls.Certificate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
certFile, keyFile := filepath.Join(tmpDir, "cert.pem"), filepath.Join(tmpDir, "key.pem")
|
certFile, keyFile := filepath.Join(tmpDir, "cert.pem"), filepath.Join(tmpDir, "key.pem")
|
||||||
cert, err := tlsutil.NewCertificate(certFile, keyFile, "relaypoolsrv", 20*365, false)
|
cert, err := tlsutil.NewCertificate(certFile, keyFile, "relaypoolsrv", 20*365)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln("Failed to create test X509 key pair:", err)
|
log.Fatalln("Failed to create test X509 key pair:", err)
|
||||||
}
|
}
|
||||||
@@ -655,7 +653,6 @@ func getLocation(host string, geoip *geoip.Provider) location {
|
|||||||
|
|
||||||
type loggingResponseWriter struct {
|
type loggingResponseWriter struct {
|
||||||
http.ResponseWriter
|
http.ResponseWriter
|
||||||
|
|
||||||
statusCode int
|
statusCode int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -27,6 +28,8 @@ func init() {
|
|||||||
{URL: "known2"},
|
{URL: "known2"},
|
||||||
{URL: "known3"},
|
{URL: "known3"},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mut = new(sync.RWMutex)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Regression test: handleGetRequest should not modify permanentRelays.
|
// Regression test: handleGetRequest should not modify permanentRelays.
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/syncthing/syncthing/lib/sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -104,11 +104,12 @@ func refreshStats() {
|
|||||||
mut.RUnlock()
|
mut.RUnlock()
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
var wg sync.WaitGroup
|
wg := sync.NewWaitGroup()
|
||||||
|
|
||||||
results := make(chan statsFetchResult, len(relays))
|
results := make(chan statsFetchResult, len(relays))
|
||||||
for _, rel := range relays {
|
for _, rel := range relays {
|
||||||
wg.Go(func() {
|
wg.Add(1)
|
||||||
|
go func(rel *relay) {
|
||||||
t0 := time.Now()
|
t0 := time.Now()
|
||||||
stats := fetchStats(rel)
|
stats := fetchStats(rel)
|
||||||
duration := time.Since(t0).Seconds()
|
duration := time.Since(t0).Seconds()
|
||||||
@@ -122,7 +123,8 @@ func refreshStats() {
|
|||||||
relay: rel,
|
relay: rel,
|
||||||
stats: fetchStats(rel),
|
stats: fetchStats(rel),
|
||||||
}
|
}
|
||||||
})
|
wg.Done()
|
||||||
|
}(rel)
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
@@ -171,7 +173,7 @@ func fetchStats(relay *relay) *stats {
|
|||||||
|
|
||||||
var stats stats
|
var stats stats
|
||||||
|
|
||||||
if err := json.NewDecoder(response.Body).Decode(&stats); err != nil {
|
if json.NewDecoder(response.Body).Decode(&stats); err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return &stats
|
return &stats
|
||||||
|
|||||||
@@ -24,8 +24,7 @@ import (
|
|||||||
|
|
||||||
"github.com/alecthomas/kong"
|
"github.com/alecthomas/kong"
|
||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
"github.com/syncthing/syncthing/internal/slogutil"
|
_ "github.com/syncthing/syncthing/lib/automaxprocs"
|
||||||
"github.com/syncthing/syncthing/lib/build"
|
|
||||||
"github.com/syncthing/syncthing/lib/httpcache"
|
"github.com/syncthing/syncthing/lib/httpcache"
|
||||||
"github.com/syncthing/syncthing/lib/upgrade"
|
"github.com/syncthing/syncthing/lib/upgrade"
|
||||||
)
|
)
|
||||||
@@ -45,8 +44,6 @@ func main() {
|
|||||||
Level: slog.LevelInfo,
|
Level: slog.LevelInfo,
|
||||||
})))
|
})))
|
||||||
|
|
||||||
slog.Info(build.LongVersionFor("stupgrades"))
|
|
||||||
|
|
||||||
if err := server(¶ms); err != nil {
|
if err := server(¶ms); err != nil {
|
||||||
fmt.Printf("Error: %v\n", err)
|
fmt.Printf("Error: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -61,10 +58,10 @@ func server(params *cli) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("metrics: %w", err)
|
return fmt.Errorf("metrics: %w", err)
|
||||||
}
|
}
|
||||||
slog.Info("Metrics listener started", slogutil.Address(params.MetricsListen))
|
slog.Info("Metrics listener started", "addr", params.MetricsListen)
|
||||||
go func() {
|
go func() {
|
||||||
if err := http.Serve(metricsListen, mux); err != nil {
|
if err := http.Serve(metricsListen, mux); err != nil {
|
||||||
slog.Warn("Metrics server returned", slogutil.Error(err))
|
slog.Warn("Metrics server returned", "error", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
@@ -78,9 +75,9 @@ func server(params *cli) error {
|
|||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for range time.NewTicker(params.CacheTime).C {
|
for range time.NewTicker(params.CacheTime).C {
|
||||||
slog.Info("Refreshing cached releases", slogutil.URI(params.URL))
|
slog.Info("Refreshing cached releases", "url", params.URL)
|
||||||
if err := cache.Update(context.Background()); err != nil {
|
if err := cache.Update(context.Background()); err != nil {
|
||||||
slog.Error("Failed to refresh cached releases", slogutil.URI(params.URL), slogutil.Error(err))
|
slog.Error("Failed to refresh cached releases", "url", params.URL, "error", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@@ -112,7 +109,7 @@ func server(params *cli) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("listen: %w", err)
|
return fmt.Errorf("listen: %w", err)
|
||||||
}
|
}
|
||||||
slog.Info("Main listener started", slogutil.Address(params.Listen))
|
slog.Info("Main listener started", "addr", params.Listen)
|
||||||
|
|
||||||
return srv.Serve(srvListener)
|
return srv.Serve(srvListener)
|
||||||
}
|
}
|
||||||
@@ -140,7 +137,7 @@ func (p *githubReleases) serveReleases(w http.ResponseWriter, req *http.Request)
|
|||||||
osv := req.Header.Get("Syncthing-Os-Version")
|
osv := req.Header.Get("Syncthing-Os-Version")
|
||||||
if ua != "" && osv != "" {
|
if ua != "" && osv != "" {
|
||||||
// We should determine the compatibility of the releases.
|
// We should determine the compatibility of the releases.
|
||||||
rels = filterForCompatibility(rels, ua, osv)
|
rels = filterForCompabitility(rels, ua, osv)
|
||||||
} else {
|
} else {
|
||||||
metricFilterCalls.WithLabelValues("no-ua-or-osversion").Inc()
|
metricFilterCalls.WithLabelValues("no-ua-or-osversion").Inc()
|
||||||
}
|
}
|
||||||
@@ -226,7 +223,7 @@ func filterForLatest(rels []upgrade.Release) []upgrade.Release {
|
|||||||
|
|
||||||
var userAgentOSArchExp = regexp.MustCompile(`^syncthing.*\(.+ (\w+)-(\w+)\)$`)
|
var userAgentOSArchExp = regexp.MustCompile(`^syncthing.*\(.+ (\w+)-(\w+)\)$`)
|
||||||
|
|
||||||
func filterForCompatibility(rels []upgrade.Release, ua, osv string) []upgrade.Release {
|
func filterForCompabitility(rels []upgrade.Release, ua, osv string) []upgrade.Release {
|
||||||
osArch := userAgentOSArchExp.FindStringSubmatch(ua)
|
osArch := userAgentOSArchExp.FindStringSubmatch(ua)
|
||||||
if len(osArch) != 3 {
|
if len(osArch) != 3 {
|
||||||
metricFilterCalls.WithLabelValues("bad-os-arch").Inc()
|
metricFilterCalls.WithLabelValues("bad-os-arch").Inc()
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
|
|
||||||
"github.com/alecthomas/kong"
|
"github.com/alecthomas/kong"
|
||||||
"github.com/syncthing/syncthing/cmd/infra/ursrv/serve"
|
"github.com/syncthing/syncthing/cmd/infra/ursrv/serve"
|
||||||
"github.com/syncthing/syncthing/lib/build"
|
_ "github.com/syncthing/syncthing/lib/automaxprocs"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CLI struct {
|
type CLI struct {
|
||||||
@@ -25,8 +25,6 @@ func main() {
|
|||||||
Level: slog.LevelInfo,
|
Level: slog.LevelInfo,
|
||||||
})))
|
})))
|
||||||
|
|
||||||
slog.Info(build.LongVersionFor("ursrv"))
|
|
||||||
|
|
||||||
var cli CLI
|
var cli CLI
|
||||||
ctx := kong.Parse(&cli)
|
ctx := kong.Parse(&cli)
|
||||||
if err := ctx.Run(); err != nil {
|
if err := ctx.Run(); err != nil {
|
||||||
|
|||||||
@@ -32,21 +32,6 @@ var (
|
|||||||
Subsystem: "ursrv_v2",
|
Subsystem: "ursrv_v2",
|
||||||
Name: "collect_seconds_last",
|
Name: "collect_seconds_last",
|
||||||
})
|
})
|
||||||
metricsRecalcsTotal = promauto.NewCounter(prometheus.CounterOpts{
|
|
||||||
Namespace: "syncthing",
|
|
||||||
Subsystem: "ursrv_v2",
|
|
||||||
Name: "recalcs_total",
|
|
||||||
})
|
|
||||||
metricsRecalcSecondsTotal = promauto.NewCounter(prometheus.CounterOpts{
|
|
||||||
Namespace: "syncthing",
|
|
||||||
Subsystem: "ursrv_v2",
|
|
||||||
Name: "recalc_seconds_total",
|
|
||||||
})
|
|
||||||
metricsRecalcSecondsLast = promauto.NewGauge(prometheus.GaugeOpts{
|
|
||||||
Namespace: "syncthing",
|
|
||||||
Subsystem: "ursrv_v2",
|
|
||||||
Name: "recalc_seconds_last",
|
|
||||||
})
|
|
||||||
metricsWriteSecondsLast = promauto.NewGauge(prometheus.GaugeOpts{
|
metricsWriteSecondsLast = promauto.NewGauge(prometheus.GaugeOpts{
|
||||||
Namespace: "syncthing",
|
Namespace: "syncthing",
|
||||||
Subsystem: "ursrv_v2",
|
Subsystem: "ursrv_v2",
|
||||||
|
|||||||
@@ -7,9 +7,6 @@
|
|||||||
package serve
|
package serve
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log/slog"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -31,7 +28,7 @@ type metricsSet struct {
|
|||||||
gaugeVecLabels map[string][]string
|
gaugeVecLabels map[string][]string
|
||||||
summaries map[string]*metricSummary
|
summaries map[string]*metricSummary
|
||||||
|
|
||||||
collectMut sync.RWMutex
|
collectMut sync.Mutex
|
||||||
collectCutoff time.Duration
|
collectCutoff time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,7 +44,7 @@ func newMetricsSet(srv *server) *metricsSet {
|
|||||||
|
|
||||||
var initForType func(reflect.Type)
|
var initForType func(reflect.Type)
|
||||||
initForType = func(t reflect.Type) {
|
initForType = func(t reflect.Type) {
|
||||||
for i := range t.NumField() {
|
for i := 0; i < t.NumField(); i++ {
|
||||||
field := t.Field(i)
|
field := t.Field(i)
|
||||||
if field.Type.Kind() == reflect.Struct {
|
if field.Type.Kind() == reflect.Struct {
|
||||||
initForType(field.Type)
|
initForType(field.Type)
|
||||||
@@ -111,60 +108,6 @@ func nameConstLabels(name string) (string, prometheus.Labels) {
|
|||||||
return name, m
|
return name, m
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *metricsSet) Serve(ctx context.Context) error {
|
|
||||||
s.recalc()
|
|
||||||
|
|
||||||
const recalcInterval = 5 * time.Minute
|
|
||||||
next := time.Until(time.Now().Truncate(recalcInterval).Add(recalcInterval))
|
|
||||||
recalcTimer := time.NewTimer(next)
|
|
||||||
defer recalcTimer.Stop()
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-recalcTimer.C:
|
|
||||||
s.recalc()
|
|
||||||
next := time.Until(time.Now().Truncate(recalcInterval).Add(recalcInterval))
|
|
||||||
recalcTimer.Reset(next)
|
|
||||||
case <-ctx.Done():
|
|
||||||
return ctx.Err()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *metricsSet) recalc() {
|
|
||||||
s.collectMut.Lock()
|
|
||||||
defer s.collectMut.Unlock()
|
|
||||||
|
|
||||||
t0 := time.Now()
|
|
||||||
defer func() {
|
|
||||||
dur := time.Since(t0)
|
|
||||||
slog.Info("Metrics recalculated", "d", dur.String())
|
|
||||||
metricsRecalcSecondsLast.Set(dur.Seconds())
|
|
||||||
metricsRecalcSecondsTotal.Add(dur.Seconds())
|
|
||||||
metricsRecalcsTotal.Inc()
|
|
||||||
}()
|
|
||||||
|
|
||||||
for _, g := range s.gauges {
|
|
||||||
g.Set(0)
|
|
||||||
}
|
|
||||||
for _, g := range s.gaugeVecs {
|
|
||||||
g.Reset()
|
|
||||||
}
|
|
||||||
for _, g := range s.summaries {
|
|
||||||
g.Reset()
|
|
||||||
}
|
|
||||||
|
|
||||||
cutoff := time.Now().Add(s.collectCutoff)
|
|
||||||
s.srv.reports.Range(func(key string, r *contract.Report) bool {
|
|
||||||
if s.collectCutoff < 0 && r.Received.Before(cutoff) {
|
|
||||||
s.srv.reports.Delete(key)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
s.addReport(r)
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *metricsSet) addReport(r *contract.Report) {
|
func (s *metricsSet) addReport(r *contract.Report) {
|
||||||
gaugeVecs := make(map[string][]string)
|
gaugeVecs := make(map[string][]string)
|
||||||
s.addReportStruct(reflect.ValueOf(r).Elem(), gaugeVecs)
|
s.addReportStruct(reflect.ValueOf(r).Elem(), gaugeVecs)
|
||||||
@@ -175,7 +118,7 @@ func (s *metricsSet) addReport(r *contract.Report) {
|
|||||||
|
|
||||||
func (s *metricsSet) addReportStruct(v reflect.Value, gaugeVecs map[string][]string) {
|
func (s *metricsSet) addReportStruct(v reflect.Value, gaugeVecs map[string][]string) {
|
||||||
t := v.Type()
|
t := v.Type()
|
||||||
for i := range v.NumField() {
|
for i := 0; i < v.NumField(); i++ {
|
||||||
field := v.Field(i)
|
field := v.Field(i)
|
||||||
if field.Kind() == reflect.Struct {
|
if field.Kind() == reflect.Struct {
|
||||||
s.addReportStruct(field, gaugeVecs)
|
s.addReportStruct(field, gaugeVecs)
|
||||||
@@ -255,8 +198,8 @@ func (s *metricsSet) Describe(c chan<- *prometheus.Desc) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *metricsSet) Collect(c chan<- prometheus.Metric) {
|
func (s *metricsSet) Collect(c chan<- prometheus.Metric) {
|
||||||
s.collectMut.RLock()
|
s.collectMut.Lock()
|
||||||
defer s.collectMut.RUnlock()
|
defer s.collectMut.Unlock()
|
||||||
|
|
||||||
t0 := time.Now()
|
t0 := time.Now()
|
||||||
defer func() {
|
defer func() {
|
||||||
@@ -266,6 +209,26 @@ func (s *metricsSet) Collect(c chan<- prometheus.Metric) {
|
|||||||
metricsCollectsTotal.Inc()
|
metricsCollectsTotal.Inc()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
for _, g := range s.gauges {
|
||||||
|
g.Set(0)
|
||||||
|
}
|
||||||
|
for _, g := range s.gaugeVecs {
|
||||||
|
g.Reset()
|
||||||
|
}
|
||||||
|
for _, g := range s.summaries {
|
||||||
|
g.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
cutoff := time.Now().Add(s.collectCutoff)
|
||||||
|
s.srv.reports.Range(func(key string, r *contract.Report) bool {
|
||||||
|
if s.collectCutoff < 0 && r.Received.Before(cutoff) {
|
||||||
|
s.srv.reports.Delete(key)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
s.addReport(r)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
for _, g := range s.gauges {
|
for _, g := range s.gauges {
|
||||||
c <- g
|
c <- g
|
||||||
}
|
}
|
||||||
@@ -336,12 +299,12 @@ func (q *metricSummary) Collect(c chan<- prometheus.Metric) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
slices.Sort(vs)
|
slices.Sort(vs)
|
||||||
|
c <- prometheus.MustNewConstMetric(q.qDesc, prometheus.GaugeValue, vs[0], append(labelVals, "0")...)
|
||||||
pctiles := []float64{0, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 0.75, 0.9, 0.95, 0.975, 0.99, 1}
|
c <- prometheus.MustNewConstMetric(q.qDesc, prometheus.GaugeValue, vs[len(vs)*5/100], append(labelVals, "0.05")...)
|
||||||
for _, pct := range pctiles {
|
c <- prometheus.MustNewConstMetric(q.qDesc, prometheus.GaugeValue, vs[len(vs)/2], append(labelVals, "0.5")...)
|
||||||
idx := int(float64(len(vs)-1) * pct)
|
c <- prometheus.MustNewConstMetric(q.qDesc, prometheus.GaugeValue, vs[len(vs)*9/10], append(labelVals, "0.9")...)
|
||||||
c <- prometheus.MustNewConstMetric(q.qDesc, prometheus.GaugeValue, vs[idx], append(labelVals, fmt.Sprint(pct))...)
|
c <- prometheus.MustNewConstMetric(q.qDesc, prometheus.GaugeValue, vs[len(vs)*95/100], append(labelVals, "0.95")...)
|
||||||
}
|
c <- prometheus.MustNewConstMetric(q.qDesc, prometheus.GaugeValue, vs[len(vs)-1], append(labelVals, "1")...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,12 +27,11 @@ import (
|
|||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
"github.com/puzpuzpuz/xsync/v3"
|
"github.com/puzpuzpuz/xsync/v3"
|
||||||
"github.com/syncthing/syncthing/internal/blob"
|
"github.com/syncthing/syncthing/internal/blob"
|
||||||
|
"github.com/syncthing/syncthing/internal/blob/azureblob"
|
||||||
"github.com/syncthing/syncthing/internal/blob/s3"
|
"github.com/syncthing/syncthing/internal/blob/s3"
|
||||||
"github.com/syncthing/syncthing/internal/slogutil"
|
|
||||||
"github.com/syncthing/syncthing/lib/build"
|
"github.com/syncthing/syncthing/lib/build"
|
||||||
"github.com/syncthing/syncthing/lib/geoip"
|
"github.com/syncthing/syncthing/lib/geoip"
|
||||||
"github.com/syncthing/syncthing/lib/ur/contract"
|
"github.com/syncthing/syncthing/lib/ur/contract"
|
||||||
"github.com/thejerf/suture/v4"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type CLI struct {
|
type CLI struct {
|
||||||
@@ -48,6 +47,10 @@ type CLI struct {
|
|||||||
S3Bucket string `name:"s3-bucket" env:"UR_S3_BUCKET"`
|
S3Bucket string `name:"s3-bucket" env:"UR_S3_BUCKET"`
|
||||||
S3AccessKeyID string `name:"s3-access-key-id" env:"UR_S3_ACCESS_KEY_ID"`
|
S3AccessKeyID string `name:"s3-access-key-id" env:"UR_S3_ACCESS_KEY_ID"`
|
||||||
S3SecretKey string `name:"s3-secret-key" env:"UR_S3_SECRET_KEY"`
|
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 (
|
var (
|
||||||
@@ -74,8 +77,7 @@ var (
|
|||||||
{regexp.MustCompile(`\svagrant@bullseye`), "F-Droid"},
|
{regexp.MustCompile(`\svagrant@bullseye`), "F-Droid"},
|
||||||
{regexp.MustCompile(`\svagrant@bookworm`), "F-Droid"},
|
{regexp.MustCompile(`\svagrant@bookworm`), "F-Droid"},
|
||||||
|
|
||||||
{regexp.MustCompile(`\sreproducible-build@Catfriend1-syncthing-android`), "Syncthing-Fork Catfriend1 (3rd party)"},
|
{regexp.MustCompile(`Anwender@NET2017`), "Syncthing-Fork (3rd party)"},
|
||||||
{regexp.MustCompile(`\sreproducible-build@nel0x-syncthing-android-gplay`), "Syncthing-Fork nel0x (3rd party)"},
|
|
||||||
|
|
||||||
{regexp.MustCompile(`\sbuilduser@(archlinux|svetlemodry)`), "Arch (3rd party)"},
|
{regexp.MustCompile(`\sbuilduser@(archlinux|svetlemodry)`), "Arch (3rd party)"},
|
||||||
{regexp.MustCompile(`\ssyncthing@archlinux`), "Arch (3rd party)"},
|
{regexp.MustCompile(`\ssyncthing@archlinux`), "Arch (3rd party)"},
|
||||||
@@ -102,23 +104,23 @@ func (cli *CLI) Run() error {
|
|||||||
|
|
||||||
urListener, err := net.Listen("tcp", cli.Listen)
|
urListener, err := net.Listen("tcp", cli.Listen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to listen (usage reports)", slogutil.Error(err))
|
slog.Error("Failed to listen (usage reports)", "error", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
slog.Info("Listening (usage reports)", slogutil.Address(urListener.Addr()))
|
slog.Info("Listening (usage reports)", "address", urListener.Addr())
|
||||||
|
|
||||||
internalListener, err := net.Listen("tcp", cli.ListenInternal)
|
internalListener, err := net.Listen("tcp", cli.ListenInternal)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to listen (internal)", slogutil.Error(err))
|
slog.Error("Failed to listen (internal)", "error", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
slog.Info("Listening (internal)", slogutil.Address(internalListener.Addr()))
|
slog.Info("Listening (internal)", "address", internalListener.Addr())
|
||||||
|
|
||||||
var geo *geoip.Provider
|
var geo *geoip.Provider
|
||||||
if cli.GeoIPAccountID != 0 && cli.GeoIPLicenseKey != "" {
|
if cli.GeoIPAccountID != 0 && cli.GeoIPLicenseKey != "" {
|
||||||
geo, err = geoip.NewGeoLite2CityProvider(context.Background(), cli.GeoIPAccountID, cli.GeoIPLicenseKey, os.TempDir())
|
geo, err = geoip.NewGeoLite2CityProvider(context.Background(), cli.GeoIPAccountID, cli.GeoIPLicenseKey, os.TempDir())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to load GeoIP", slogutil.Error(err))
|
slog.Error("Failed to load GeoIP", "error", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
go geo.Serve(context.TODO())
|
go geo.Serve(context.TODO())
|
||||||
@@ -130,14 +132,20 @@ func (cli *CLI) Run() error {
|
|||||||
if cli.S3Endpoint != "" {
|
if cli.S3Endpoint != "" {
|
||||||
blobs, 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 {
|
if err != nil {
|
||||||
slog.Error("Failed to create S3 session", slogutil.Error(err))
|
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
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := os.Stat(cli.DumpFile); err != nil && blobs != nil {
|
if _, err := os.Stat(cli.DumpFile); err != nil && blobs != nil {
|
||||||
if err := cli.downloadDumpFile(blobs); err != nil {
|
if err := cli.downloadDumpFile(blobs); err != nil {
|
||||||
slog.Error("Failed to download dump file", slogutil.Error(err))
|
slog.Error("Failed to download dump file", "error", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,7 +167,7 @@ func (cli *CLI) Run() error {
|
|||||||
go func() {
|
go func() {
|
||||||
for range time.Tick(cli.DumpInterval) {
|
for range time.Tick(cli.DumpInterval) {
|
||||||
if err := cli.saveDumpFile(srv, blobs); err != nil {
|
if err := cli.saveDumpFile(srv, blobs); err != nil {
|
||||||
slog.Error("Failed to write dump file", slogutil.Error(err))
|
slog.Error("Failed to write dump file", "error", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@@ -178,12 +186,7 @@ func (cli *CLI) Run() error {
|
|||||||
// New external metrics endpoint accepts reports from clients and serves
|
// New external metrics endpoint accepts reports from clients and serves
|
||||||
// aggregated usage reporting metrics.
|
// aggregated usage reporting metrics.
|
||||||
|
|
||||||
main := suture.NewSimple("main")
|
|
||||||
main.ServeBackground(context.Background())
|
|
||||||
|
|
||||||
ms := newMetricsSet(srv)
|
ms := newMetricsSet(srv)
|
||||||
main.Add(ms)
|
|
||||||
|
|
||||||
reg := prometheus.NewRegistry()
|
reg := prometheus.NewRegistry()
|
||||||
reg.MustRegister(ms)
|
reg.MustRegister(ms)
|
||||||
|
|
||||||
@@ -194,7 +197,7 @@ func (cli *CLI) Run() error {
|
|||||||
|
|
||||||
metricsSrv := http.Server{
|
metricsSrv := http.Server{
|
||||||
ReadTimeout: 5 * time.Second,
|
ReadTimeout: 5 * time.Second,
|
||||||
WriteTimeout: 60 * time.Second,
|
WriteTimeout: 15 * time.Second,
|
||||||
Handler: mux,
|
Handler: mux,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,11 +226,6 @@ func (cli *CLI) downloadDumpFile(blobs blob.Store) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (cli *CLI) saveDumpFile(srv *server, blobs blob.Store) error {
|
func (cli *CLI) saveDumpFile(srv *server, blobs blob.Store) error {
|
||||||
t0 := time.Now()
|
|
||||||
defer func() {
|
|
||||||
metricsWriteSecondsLast.Set(float64(time.Since(t0)))
|
|
||||||
}()
|
|
||||||
|
|
||||||
fd, err := os.Create(cli.DumpFile + ".tmp")
|
fd, err := os.Create(cli.DumpFile + ".tmp")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("creating dump file: %w", err)
|
return fmt.Errorf("creating dump file: %w", err)
|
||||||
@@ -246,10 +244,9 @@ func (cli *CLI) saveDumpFile(srv *server, blobs blob.Store) error {
|
|||||||
if err := os.Rename(cli.DumpFile+".tmp", cli.DumpFile); err != nil {
|
if err := os.Rename(cli.DumpFile+".tmp", cli.DumpFile); err != nil {
|
||||||
return fmt.Errorf("renaming dump file: %w", err)
|
return fmt.Errorf("renaming dump file: %w", err)
|
||||||
}
|
}
|
||||||
slog.Info("Dump file saved", "d", time.Since(t0).String())
|
slog.Info("Dump file saved")
|
||||||
|
|
||||||
if blobs != nil {
|
if blobs != nil {
|
||||||
t1 := time.Now()
|
|
||||||
key := fmt.Sprintf("reports-%s.jsons.gz", time.Now().UTC().Format("2006-01-02"))
|
key := fmt.Sprintf("reports-%s.jsons.gz", time.Now().UTC().Format("2006-01-02"))
|
||||||
fd, err := os.Open(cli.DumpFile)
|
fd, err := os.Open(cli.DumpFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -259,7 +256,7 @@ func (cli *CLI) saveDumpFile(srv *server, blobs blob.Store) error {
|
|||||||
return fmt.Errorf("uploading dump file: %w", err)
|
return fmt.Errorf("uploading dump file: %w", err)
|
||||||
}
|
}
|
||||||
_ = fd.Close()
|
_ = fd.Close()
|
||||||
slog.Info("Dump file uploaded", "d", time.Since(t1).String())
|
slog.Info("Dump file uploaded")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -310,7 +307,7 @@ func (s *server) handleNewData(w http.ResponseWriter, r *http.Request) {
|
|||||||
lr := &io.LimitedReader{R: r.Body, N: 40 * 1024}
|
lr := &io.LimitedReader{R: r.Body, N: 40 * 1024}
|
||||||
bs, _ := io.ReadAll(lr)
|
bs, _ := io.ReadAll(lr)
|
||||||
if err := json.Unmarshal(bs, &rep); err != nil {
|
if err := json.Unmarshal(bs, &rep); err != nil {
|
||||||
log.Error("Failed to decode JSON", slogutil.Error(err))
|
log.Error("Failed to decode JSON", "error", err)
|
||||||
http.Error(w, "JSON Decode Error", http.StatusInternalServerError)
|
http.Error(w, "JSON Decode Error", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -320,7 +317,7 @@ func (s *server) handleNewData(w http.ResponseWriter, r *http.Request) {
|
|||||||
rep.Address = addr
|
rep.Address = addr
|
||||||
|
|
||||||
if err := rep.Validate(); err != nil {
|
if err := rep.Validate(); err != nil {
|
||||||
log.Error("Failed to validate report", slogutil.Error(err))
|
log.Error("Failed to validate report", "error", err)
|
||||||
http.Error(w, "Validation Error", http.StatusInternalServerError)
|
http.Error(w, "Validation Error", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -371,13 +368,6 @@ func (s *server) addReport(rep *contract.Report) bool {
|
|||||||
rep.DistOS = rep.OS
|
rep.DistOS = rep.OS
|
||||||
rep.DistArch = rep.Arch
|
rep.DistArch = rep.Arch
|
||||||
|
|
||||||
if strings.HasPrefix(rep.Version, "v2.") {
|
|
||||||
rep.Database.ModernCSQLite = strings.Contains(rep.LongVersion, "modernc-sqlite")
|
|
||||||
rep.Database.MattnSQLite = !rep.Database.ModernCSQLite
|
|
||||||
} else {
|
|
||||||
rep.Database.LevelDB = true
|
|
||||||
}
|
|
||||||
|
|
||||||
_, loaded := s.reports.LoadAndStore(rep.UniqueID, rep)
|
_, loaded := s.reports.LoadAndStore(rep.UniqueID, rep)
|
||||||
return loaded
|
return loaded
|
||||||
}
|
}
|
||||||
@@ -397,7 +387,6 @@ func (s *server) save(w io.Writer) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) load(r io.Reader) {
|
func (s *server) load(r io.Reader) {
|
||||||
t0 := time.Now()
|
|
||||||
dec := json.NewDecoder(r)
|
dec := json.NewDecoder(r)
|
||||||
s.reports.Clear()
|
s.reports.Clear()
|
||||||
for {
|
for {
|
||||||
@@ -405,12 +394,12 @@ func (s *server) load(r io.Reader) {
|
|||||||
if err := dec.Decode(&rep); errors.Is(err, io.EOF) {
|
if err := dec.Decode(&rep); errors.Is(err, io.EOF) {
|
||||||
break
|
break
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
slog.Error("Failed to load record", slogutil.Error(err))
|
slog.Error("Failed to load record", "error", err)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
s.addReport(&rep)
|
s.addReport(&rep)
|
||||||
}
|
}
|
||||||
slog.Info("Loaded reports", "count", s.reports.Size(), "d", time.Since(t0).String())
|
slog.Info("Loaded reports", "count", s.reports.Size())
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log/slog"
|
"log"
|
||||||
|
|
||||||
amqp "github.com/rabbitmq/amqp091-go"
|
amqp "github.com/rabbitmq/amqp091-go"
|
||||||
"github.com/thejerf/suture/v4"
|
"github.com/thejerf/suture/v4"
|
||||||
@@ -23,7 +23,6 @@ import (
|
|||||||
|
|
||||||
type amqpReplicator struct {
|
type amqpReplicator struct {
|
||||||
suture.Service
|
suture.Service
|
||||||
|
|
||||||
broker string
|
broker string
|
||||||
sender *amqpSender
|
sender *amqpSender
|
||||||
receiver *amqpReceiver
|
receiver *amqpReceiver
|
||||||
@@ -173,7 +172,7 @@ func (s *amqpReceiver) Serve(ctx context.Context) error {
|
|||||||
id, err = protocol.DeviceIDFromString(string(rec.Key))
|
id, err = protocol.DeviceIDFromString(string(rec.Key))
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Warn("Failed to parse replication device ID", "error", err)
|
log.Println("Replication device ID:", err)
|
||||||
replicationRecvsTotal.WithLabelValues("error").Inc()
|
replicationRecvsTotal.WithLabelValues("error").Inc()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
+32
-35
@@ -18,7 +18,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
io "io"
|
io "io"
|
||||||
"log"
|
"log"
|
||||||
"log/slog"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -67,7 +66,7 @@ type contextKey int
|
|||||||
|
|
||||||
const idKey contextKey = iota
|
const idKey contextKey = iota
|
||||||
|
|
||||||
func newAPISrv(addr string, cert tls.Certificate, db database, repl replicator, useHTTP, compression bool, desiredUnseenNotFoundRate, desiredSeenNotFoundRate float64) *apiSrv {
|
func newAPISrv(addr string, cert tls.Certificate, db database, repl replicator, useHTTP, compression bool, desiredNotFoundRate float64) *apiSrv {
|
||||||
return &apiSrv{
|
return &apiSrv{
|
||||||
addr: addr,
|
addr: addr,
|
||||||
cert: cert,
|
cert: cert,
|
||||||
@@ -78,13 +77,13 @@ func newAPISrv(addr string, cert tls.Certificate, db database, repl replicator,
|
|||||||
seenTracker: &retryAfterTracker{
|
seenTracker: &retryAfterTracker{
|
||||||
name: "seenTracker",
|
name: "seenTracker",
|
||||||
bucketStarts: time.Now(),
|
bucketStarts: time.Now(),
|
||||||
desiredRate: desiredSeenNotFoundRate,
|
desiredRate: desiredNotFoundRate / 2,
|
||||||
currentDelay: notFoundRetryUnknownMinSeconds,
|
currentDelay: notFoundRetryUnknownMinSeconds,
|
||||||
},
|
},
|
||||||
notSeenTracker: &retryAfterTracker{
|
notSeenTracker: &retryAfterTracker{
|
||||||
name: "notSeenTracker",
|
name: "notSeenTracker",
|
||||||
bucketStarts: time.Now(),
|
bucketStarts: time.Now(),
|
||||||
desiredRate: desiredUnseenNotFoundRate,
|
desiredRate: desiredNotFoundRate / 2,
|
||||||
currentDelay: notFoundRetryUnknownMaxSeconds / 2,
|
currentDelay: notFoundRetryUnknownMaxSeconds / 2,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -94,7 +93,7 @@ func (s *apiSrv) Serve(ctx context.Context) error {
|
|||||||
if s.useHTTP {
|
if s.useHTTP {
|
||||||
listener, err := net.Listen("tcp", s.addr)
|
listener, err := net.Listen("tcp", s.addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.ErrorContext(ctx, "Failed to listen", "error", err)
|
log.Println("Listen:", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
s.listener = listener
|
s.listener = listener
|
||||||
@@ -108,7 +107,7 @@ func (s *apiSrv) Serve(ctx context.Context) error {
|
|||||||
|
|
||||||
tlsListener, err := tls.Listen("tcp", s.addr, tlsCfg)
|
tlsListener, err := tls.Listen("tcp", s.addr, tlsCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.ErrorContext(ctx, "Failed to listen", "error", err)
|
log.Println("Listen:", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
s.listener = tlsListener
|
s.listener = tlsListener
|
||||||
@@ -133,7 +132,7 @@ func (s *apiSrv) Serve(ctx context.Context) error {
|
|||||||
|
|
||||||
err := srv.Serve(s.listener)
|
err := srv.Serve(s.listener)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.ErrorContext(ctx, "Failed to serve", "error", err)
|
log.Println("Serve:", err)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -152,7 +151,9 @@ func (s *apiSrv) handler(w http.ResponseWriter, req *http.Request) {
|
|||||||
reqID := requestID(rand.Int63())
|
reqID := requestID(rand.Int63())
|
||||||
req = req.WithContext(context.WithValue(req.Context(), idKey, reqID))
|
req = req.WithContext(context.WithValue(req.Context(), idKey, reqID))
|
||||||
|
|
||||||
slog.Debug("Handling request", "id", reqID, "method", req.Method, "url", req.URL, "proto", req.Proto)
|
if debug {
|
||||||
|
log.Println(reqID, req.Method, req.URL, req.Proto)
|
||||||
|
}
|
||||||
|
|
||||||
remoteAddr := &net.TCPAddr{
|
remoteAddr := &net.TCPAddr{
|
||||||
IP: nil,
|
IP: nil,
|
||||||
@@ -173,7 +174,7 @@ func (s *apiSrv) handler(w http.ResponseWriter, req *http.Request) {
|
|||||||
var err error
|
var err error
|
||||||
remoteAddr, err = net.ResolveTCPAddr("tcp", req.RemoteAddr)
|
remoteAddr, err = net.ResolveTCPAddr("tcp", req.RemoteAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Warn("Failed to resolve remote address", "address", req.RemoteAddr, "error", err)
|
log.Println("remoteAddr:", err)
|
||||||
lw.Header().Set("Retry-After", errorRetryAfterString())
|
lw.Header().Set("Retry-After", errorRetryAfterString())
|
||||||
http.Error(lw, "Internal Server Error", http.StatusInternalServerError)
|
http.Error(lw, "Internal Server Error", http.StatusInternalServerError)
|
||||||
apiRequestsTotal.WithLabelValues("no_remote_addr").Inc()
|
apiRequestsTotal.WithLabelValues("no_remote_addr").Inc()
|
||||||
@@ -196,7 +197,9 @@ func (s *apiSrv) handleGET(w http.ResponseWriter, req *http.Request) {
|
|||||||
|
|
||||||
deviceID, err := protocol.DeviceIDFromString(req.URL.Query().Get("device"))
|
deviceID, err := protocol.DeviceIDFromString(req.URL.Query().Get("device"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Debug("Request with bad device param", "id", reqID, "error", err)
|
if debug {
|
||||||
|
log.Println(reqID, "bad device param:", err)
|
||||||
|
}
|
||||||
lookupRequestsTotal.WithLabelValues("bad_request").Inc()
|
lookupRequestsTotal.WithLabelValues("bad_request").Inc()
|
||||||
w.Header().Set("Retry-After", errorRetryAfterString())
|
w.Header().Set("Retry-After", errorRetryAfterString())
|
||||||
http.Error(w, "Bad Request", http.StatusBadRequest)
|
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||||
@@ -254,9 +257,11 @@ func (s *apiSrv) handleGET(w http.ResponseWriter, req *http.Request) {
|
|||||||
func (s *apiSrv) handlePOST(remoteAddr *net.TCPAddr, w http.ResponseWriter, req *http.Request) {
|
func (s *apiSrv) handlePOST(remoteAddr *net.TCPAddr, w http.ResponseWriter, req *http.Request) {
|
||||||
reqID := req.Context().Value(idKey).(requestID)
|
reqID := req.Context().Value(idKey).(requestID)
|
||||||
|
|
||||||
rawCert, err := s.certificateBytes(req)
|
rawCert, err := certificateBytes(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Debug("Request without certificates", "id", reqID, "error", err)
|
if debug {
|
||||||
|
log.Println(reqID, "no certificates:", err)
|
||||||
|
}
|
||||||
announceRequestsTotal.WithLabelValues("no_certificate").Inc()
|
announceRequestsTotal.WithLabelValues("no_certificate").Inc()
|
||||||
w.Header().Set("Retry-After", errorRetryAfterString())
|
w.Header().Set("Retry-After", errorRetryAfterString())
|
||||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||||
@@ -265,7 +270,9 @@ func (s *apiSrv) handlePOST(remoteAddr *net.TCPAddr, w http.ResponseWriter, req
|
|||||||
|
|
||||||
var ann announcement
|
var ann announcement
|
||||||
if err := json.NewDecoder(req.Body).Decode(&ann); err != nil {
|
if err := json.NewDecoder(req.Body).Decode(&ann); err != nil {
|
||||||
slog.Debug("Failed to decode request", "id", reqID, "error", err)
|
if debug {
|
||||||
|
log.Println(reqID, "decode:", err)
|
||||||
|
}
|
||||||
announceRequestsTotal.WithLabelValues("bad_request").Inc()
|
announceRequestsTotal.WithLabelValues("bad_request").Inc()
|
||||||
w.Header().Set("Retry-After", errorRetryAfterString())
|
w.Header().Set("Retry-After", errorRetryAfterString())
|
||||||
http.Error(w, "Bad Request", http.StatusBadRequest)
|
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||||
@@ -276,7 +283,9 @@ func (s *apiSrv) handlePOST(remoteAddr *net.TCPAddr, w http.ResponseWriter, req
|
|||||||
|
|
||||||
addresses := fixupAddresses(remoteAddr, ann.Addresses)
|
addresses := fixupAddresses(remoteAddr, ann.Addresses)
|
||||||
if len(addresses) == 0 {
|
if len(addresses) == 0 {
|
||||||
slog.Debug("Request without addresses", "id", reqID, "error", err)
|
if debug {
|
||||||
|
log.Println(reqID, "no addresses")
|
||||||
|
}
|
||||||
announceRequestsTotal.WithLabelValues("bad_request").Inc()
|
announceRequestsTotal.WithLabelValues("bad_request").Inc()
|
||||||
w.Header().Set("Retry-After", errorRetryAfterString())
|
w.Header().Set("Retry-After", errorRetryAfterString())
|
||||||
http.Error(w, "Bad Request", http.StatusBadRequest)
|
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||||
@@ -284,7 +293,9 @@ func (s *apiSrv) handlePOST(remoteAddr *net.TCPAddr, w http.ResponseWriter, req
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := s.handleAnnounce(deviceID, addresses); err != nil {
|
if err := s.handleAnnounce(deviceID, addresses); err != nil {
|
||||||
slog.Debug("Failed to handle request", "id", reqID, "error", err)
|
if debug {
|
||||||
|
log.Println(reqID, "handle:", err)
|
||||||
|
}
|
||||||
announceRequestsTotal.WithLabelValues("internal_error").Inc()
|
announceRequestsTotal.WithLabelValues("internal_error").Inc()
|
||||||
w.Header().Set("Retry-After", errorRetryAfterString())
|
w.Header().Set("Retry-After", errorRetryAfterString())
|
||||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||||
@@ -295,7 +306,9 @@ func (s *apiSrv) handlePOST(remoteAddr *net.TCPAddr, w http.ResponseWriter, req
|
|||||||
|
|
||||||
w.Header().Set("Reannounce-After", reannounceAfterString())
|
w.Header().Set("Reannounce-After", reannounceAfterString())
|
||||||
w.WriteHeader(http.StatusNoContent)
|
w.WriteHeader(http.StatusNoContent)
|
||||||
slog.Debug("Device announced", "id", reqID, "device", deviceID, "addresses", addresses)
|
if debug {
|
||||||
|
log.Println(reqID, "announced", deviceID, addresses)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *apiSrv) Stop() {
|
func (s *apiSrv) Stop() {
|
||||||
@@ -330,17 +343,14 @@ func handlePing(w http.ResponseWriter, _ *http.Request) {
|
|||||||
w.WriteHeader(http.StatusNoContent)
|
w.WriteHeader(http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *apiSrv) certificateBytes(req *http.Request) ([]byte, error) {
|
func certificateBytes(req *http.Request) ([]byte, error) {
|
||||||
if req.TLS != nil && len(req.TLS.PeerCertificates) > 0 {
|
if req.TLS != nil && len(req.TLS.PeerCertificates) > 0 {
|
||||||
return req.TLS.PeerCertificates[0].Raw, nil
|
return req.TLS.PeerCertificates[0].Raw, nil
|
||||||
}
|
}
|
||||||
if !s.useHTTP {
|
|
||||||
return nil, errors.New("no certificate presented")
|
|
||||||
}
|
|
||||||
|
|
||||||
var bs []byte
|
var bs []byte
|
||||||
|
|
||||||
if hdr := req.Header.Get("X-Ssl-Cert"); hdr != "" {
|
if hdr := req.Header.Get("X-SSL-Cert"); hdr != "" {
|
||||||
if strings.Contains(hdr, "%") {
|
if strings.Contains(hdr, "%") {
|
||||||
// Nginx using $ssl_client_escaped_cert
|
// Nginx using $ssl_client_escaped_cert
|
||||||
// The certificate is in PEM format with url encoding.
|
// The certificate is in PEM format with url encoding.
|
||||||
@@ -503,7 +513,6 @@ func fixupAddresses(remote *net.TCPAddr, addresses []string) []string {
|
|||||||
|
|
||||||
type loggingResponseWriter struct {
|
type loggingResponseWriter struct {
|
||||||
http.ResponseWriter
|
http.ResponseWriter
|
||||||
|
|
||||||
statusCode int
|
statusCode int
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -567,17 +576,5 @@ func (t *retryAfterTracker) retryAfterS() int {
|
|||||||
}
|
}
|
||||||
t.curCount++
|
t.curCount++
|
||||||
t.mut.Unlock()
|
t.mut.Unlock()
|
||||||
|
return t.currentDelay + rand.Intn(t.currentDelay/4)
|
||||||
// Skewed normal distribution with the mean at currentDelay and the
|
|
||||||
// limits (50% and 150%) at 3 standard deviations
|
|
||||||
nf := rand.NormFloat64()
|
|
||||||
minD := max(notFoundRetryUnknownMinSeconds, t.currentDelay/2)
|
|
||||||
maxD := min(notFoundRetryUnknownMaxSeconds, t.currentDelay*3/2)
|
|
||||||
intv := float64(maxD - t.currentDelay)
|
|
||||||
if nf < 0 {
|
|
||||||
intv = float64(t.currentDelay - minD)
|
|
||||||
}
|
|
||||||
nf = min(max(nf*intv/3+float64(t.currentDelay), notFoundRetryUnknownMinSeconds), notFoundRetryUnknownMaxSeconds)
|
|
||||||
|
|
||||||
return int(nf)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/syncthing/syncthing/lib/protocol"
|
"github.com/syncthing/syncthing/lib/protocol"
|
||||||
"github.com/syncthing/syncthing/lib/tlsutil"
|
"github.com/syncthing/syncthing/lib/tlsutil"
|
||||||
@@ -107,63 +106,16 @@ func addr(host string, port int) *net.TCPAddr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRetryAfterSHistogram(t *testing.T) {
|
|
||||||
tracker := &retryAfterTracker{
|
|
||||||
name: "test",
|
|
||||||
bucketStarts: time.Now(),
|
|
||||||
desiredRate: 100,
|
|
||||||
currentDelay: 1800,
|
|
||||||
}
|
|
||||||
|
|
||||||
const n = 1000
|
|
||||||
bucketSize := 60 // seconds per histogram bucket
|
|
||||||
numBuckets := (notFoundRetryUnknownMaxSeconds + bucketSize - 1) / bucketSize
|
|
||||||
buckets := make([]int, numBuckets)
|
|
||||||
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
v := tracker.retryAfterS()
|
|
||||||
if v < notFoundRetryUnknownMinSeconds || v > notFoundRetryUnknownMaxSeconds {
|
|
||||||
t.Fatalf("retryAfterS() = %d, out of range [%d, %d]", v, notFoundRetryUnknownMinSeconds, notFoundRetryUnknownMaxSeconds)
|
|
||||||
}
|
|
||||||
b := (v - 1) / bucketSize
|
|
||||||
if b >= numBuckets {
|
|
||||||
b = numBuckets - 1
|
|
||||||
}
|
|
||||||
buckets[b]++
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print a horizontal histogram
|
|
||||||
maxCount := 0
|
|
||||||
for _, c := range buckets {
|
|
||||||
if c > maxCount {
|
|
||||||
maxCount = c
|
|
||||||
}
|
|
||||||
}
|
|
||||||
barWidth := 60
|
|
||||||
for i, c := range buckets {
|
|
||||||
lo := i*bucketSize + 1
|
|
||||||
hi := (i + 1) * bucketSize
|
|
||||||
if hi > notFoundRetryUnknownMaxSeconds {
|
|
||||||
hi = notFoundRetryUnknownMaxSeconds
|
|
||||||
}
|
|
||||||
bar := ""
|
|
||||||
if maxCount > 0 {
|
|
||||||
bar = strings.Repeat("#", c*barWidth/maxCount)
|
|
||||||
}
|
|
||||||
t.Logf("%4d-%4ds | %-*s %d", lo, hi, barWidth, bar, c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkAPIRequests(b *testing.B) {
|
func BenchmarkAPIRequests(b *testing.B) {
|
||||||
db := newInMemoryStore(b.TempDir(), 0, nil)
|
db := newInMemoryStore(b.TempDir(), 0, nil)
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
go db.Serve(ctx)
|
go db.Serve(ctx)
|
||||||
api := newAPISrv("127.0.0.1:0", tls.Certificate{}, db, nil, true, true, 1000, 1000)
|
api := newAPISrv("127.0.0.1:0", tls.Certificate{}, db, nil, true, true, 1000)
|
||||||
srv := httptest.NewServer(http.HandlerFunc(api.handler))
|
srv := httptest.NewServer(http.HandlerFunc(api.handler))
|
||||||
|
|
||||||
kf := b.TempDir() + "/cert"
|
kf := b.TempDir() + "/cert"
|
||||||
crt, err := tlsutil.NewCertificate(kf+".crt", kf+".key", "localhost", 7, true)
|
crt, err := tlsutil.NewCertificate(kf+".crt", kf+".key", "localhost", 7)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
+42
-62
@@ -13,7 +13,7 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"log/slog"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"runtime"
|
"runtime"
|
||||||
@@ -74,24 +74,24 @@ func newInMemoryStore(dir string, flushInterval time.Duration, blobs blob.Store)
|
|||||||
// Try to read from blob storage
|
// Try to read from blob storage
|
||||||
latestKey, cerr := blobs.LatestKey(context.Background())
|
latestKey, cerr := blobs.LatestKey(context.Background())
|
||||||
if cerr != nil {
|
if cerr != nil {
|
||||||
slog.Error("Failed to find database in blob storage", "error", cerr)
|
log.Println("Error finding database from blob storage:", cerr)
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
fd, cerr := os.Create(path.Join(s.dir, "records.db"))
|
fd, cerr := os.Create(path.Join(s.dir, "records.db"))
|
||||||
if cerr != nil {
|
if cerr != nil {
|
||||||
slog.Error("Failed to create database file", "error", cerr)
|
log.Println("Error creating database file:", cerr)
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
if cerr := blobs.Download(context.Background(), latestKey, fd); cerr != nil {
|
if cerr := blobs.Download(context.Background(), latestKey, fd); cerr != nil {
|
||||||
slog.Error("Failed to download database from blob storage", "error", cerr)
|
log.Printf("Error downloading database from blob storage: %v", cerr)
|
||||||
}
|
}
|
||||||
_ = fd.Close()
|
_ = fd.Close()
|
||||||
nr, err = s.read()
|
nr, err = s.read()
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to read database", "error", err)
|
log.Println("Error reading database:", err)
|
||||||
}
|
}
|
||||||
slog.Info("Loaded database", "records", nr)
|
log.Printf("Read %d records from database", nr)
|
||||||
s.expireAndCalculateStatistics()
|
s.expireAndCalculateStatistics()
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
@@ -113,7 +113,7 @@ func (s *inMemoryStore) merge(key *protocol.DeviceID, addrs []*discosrv.Database
|
|||||||
}
|
}
|
||||||
|
|
||||||
if oldRec, ok := s.m.Load(*key); ok {
|
if oldRec, ok := s.m.Load(*key); ok {
|
||||||
newRec = merge(newRec, oldRec)
|
newRec = merge(oldRec, newRec)
|
||||||
}
|
}
|
||||||
s.m.Store(*key, newRec)
|
s.m.Store(*key, newRec)
|
||||||
|
|
||||||
@@ -135,13 +135,7 @@ func (s *inMemoryStore) get(key *protocol.DeviceID) (*discosrv.DatabaseRecord, e
|
|||||||
return &discosrv.DatabaseRecord{}, nil
|
return &discosrv.DatabaseRecord{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
naddresses, changed := expire(rec.Addresses, s.clock.Now())
|
rec.Addresses = expire(rec.Addresses, s.clock.Now())
|
||||||
if changed {
|
|
||||||
rec = &discosrv.DatabaseRecord{
|
|
||||||
Addresses: naddresses,
|
|
||||||
Seen: rec.Seen,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
databaseOperations.WithLabelValues(dbOpGet, dbResSuccess).Inc()
|
databaseOperations.WithLabelValues(dbOpGet, dbResSuccess).Inc()
|
||||||
return rec, nil
|
return rec, nil
|
||||||
}
|
}
|
||||||
@@ -159,13 +153,13 @@ loop:
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-t.C:
|
case <-t.C:
|
||||||
slog.InfoContext(ctx, "Calculating statistics")
|
log.Println("Calculating statistics")
|
||||||
s.expireAndCalculateStatistics()
|
s.expireAndCalculateStatistics()
|
||||||
slog.InfoContext(ctx, "Flushing database")
|
log.Println("Flushing database")
|
||||||
if err := s.write(); err != nil {
|
if err := s.write(); err != nil {
|
||||||
slog.ErrorContext(ctx, "Failed to write database", "error", err)
|
log.Println("Error writing database:", err)
|
||||||
}
|
}
|
||||||
slog.InfoContext(ctx, "Finished flushing database")
|
log.Println("Finished flushing database")
|
||||||
t.Reset(s.flushInterval)
|
t.Reset(s.flushInterval)
|
||||||
|
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
@@ -190,12 +184,12 @@ func (s *inMemoryStore) expireAndCalculateStatistics() {
|
|||||||
}
|
}
|
||||||
n++
|
n++
|
||||||
|
|
||||||
addresses, changed := expire(rec.Addresses, now)
|
addresses := expire(rec.Addresses, now)
|
||||||
if changed {
|
if len(addresses) == 0 {
|
||||||
rec = &discosrv.DatabaseRecord{
|
rec.Addresses = nil
|
||||||
Addresses: addresses,
|
s.m.Store(key, rec)
|
||||||
Seen: rec.Seen,
|
} else if len(addresses) != len(rec.Addresses) {
|
||||||
}
|
rec.Addresses = addresses
|
||||||
s.m.Store(key, rec)
|
s.m.Store(key, rec)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,7 +256,7 @@ func (s *inMemoryStore) write() (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
bw := bufio.NewWriterSize(fd, 1<<20)
|
bw := bufio.NewWriter(fd)
|
||||||
|
|
||||||
var buf []byte
|
var buf []byte
|
||||||
var rangeErr error
|
var rangeErr error
|
||||||
@@ -306,7 +300,7 @@ func (s *inMemoryStore) write() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := bw.Flush(); err != nil {
|
if err := bw.Flush(); err != nil {
|
||||||
_ = fd.Close()
|
_ = fd.Close
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := fd.Close(); err != nil {
|
if err := fd.Close(); err != nil {
|
||||||
@@ -316,24 +310,18 @@ func (s *inMemoryStore) write() (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if info, err := os.Lstat(dbf); err == nil {
|
|
||||||
slog.Info("Saved database", "name", dbf, "size", info.Size(), "modtime", info.ModTime())
|
|
||||||
} else {
|
|
||||||
slog.Warn("Failed to stat database after save", "error", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Upload to blob storage
|
// Upload to blob storage
|
||||||
if s.blobs != nil {
|
if s.blobs != nil {
|
||||||
fd, err = os.Open(dbf)
|
fd, err = os.Open(dbf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to upload database to blob storage", "error", err)
|
log.Printf("Error uploading database to blob storage: %v", err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
defer fd.Close()
|
defer fd.Close()
|
||||||
if err := s.blobs.Upload(context.Background(), s.objKey, fd); err != nil {
|
if err := s.blobs.Upload(context.Background(), s.objKey, fd); err != nil {
|
||||||
slog.Error("Failed to upload database to blob storage", "error", err)
|
log.Printf("Error uploading database to blob storage: %v", err)
|
||||||
}
|
}
|
||||||
slog.Info("Finished uploading database")
|
log.Println("Finished uploading database")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -372,14 +360,14 @@ func (s *inMemoryStore) read() (int, error) {
|
|||||||
key, err = protocol.DeviceIDFromString(string(rec.Key))
|
key, err = protocol.DeviceIDFromString(string(rec.Key))
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Got bad device ID while reading database", "error", err)
|
log.Println("Bad device ID:", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
slices.SortFunc(rec.Addresses, Cmp)
|
slices.SortFunc(rec.Addresses, Cmp)
|
||||||
rec.Addresses, _ = expire(slices.CompactFunc(rec.Addresses, Equal), s.clock.Now())
|
rec.Addresses = slices.CompactFunc(rec.Addresses, Equal)
|
||||||
s.m.Store(key, &discosrv.DatabaseRecord{
|
s.m.Store(key, &discosrv.DatabaseRecord{
|
||||||
Addresses: rec.Addresses,
|
Addresses: expire(rec.Addresses, s.clock.Now()),
|
||||||
Seen: rec.Seen,
|
Seen: rec.Seen,
|
||||||
})
|
})
|
||||||
nr++
|
nr++
|
||||||
@@ -390,7 +378,7 @@ func (s *inMemoryStore) read() (int, error) {
|
|||||||
// merge returns the merged result of the two database records a and b. The
|
// merge returns the merged result of the two database records a and b. The
|
||||||
// result is the union of the two address sets, with the newer expiry time
|
// result is the union of the two address sets, with the newer expiry time
|
||||||
// chosen for any duplicates. The address list in a is overwritten and
|
// chosen for any duplicates. The address list in a is overwritten and
|
||||||
// reused for the result; b is not modified.
|
// reused for the result.
|
||||||
func merge(a, b *discosrv.DatabaseRecord) *discosrv.DatabaseRecord {
|
func merge(a, b *discosrv.DatabaseRecord) *discosrv.DatabaseRecord {
|
||||||
// Both lists must be sorted for this to work.
|
// Both lists must be sorted for this to work.
|
||||||
|
|
||||||
@@ -421,33 +409,25 @@ func merge(a, b *discosrv.DatabaseRecord) *discosrv.DatabaseRecord {
|
|||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
// expire returns the list of addresses after removing expired entries. A
|
// expire returns the list of addresses after removing expired entries.
|
||||||
// new slice is allocated if any changes are required, and the changed
|
// Expiration happen in place, so the slice given as the parameter is
|
||||||
// boolean indicates whether that happened or not.
|
// destroyed. Internal order is preserved.
|
||||||
func expire(addrs []*discosrv.DatabaseAddress, now time.Time) (result []*discosrv.DatabaseAddress, changed bool) {
|
func expire(addrs []*discosrv.DatabaseAddress, now time.Time) []*discosrv.DatabaseAddress {
|
||||||
cutoff := now.UnixNano()
|
cutoff := now.UnixNano()
|
||||||
remains := 0
|
naddrs := addrs[:0]
|
||||||
for _, a := range addrs {
|
for i := range addrs {
|
||||||
if a.Expires < cutoff {
|
if i > 0 && addrs[i].Address == addrs[i-1].Address {
|
||||||
changed = true
|
// Skip duplicates
|
||||||
} else {
|
continue
|
||||||
remains++
|
}
|
||||||
|
if addrs[i].Expires >= cutoff {
|
||||||
|
naddrs = append(naddrs, addrs[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !changed {
|
if len(naddrs) == 0 {
|
||||||
return addrs, false
|
return nil
|
||||||
}
|
}
|
||||||
if remains == 0 {
|
return naddrs
|
||||||
return nil, true
|
|
||||||
}
|
|
||||||
|
|
||||||
naddrs := make([]*discosrv.DatabaseAddress, 0, remains)
|
|
||||||
for _, a := range addrs {
|
|
||||||
if a.Expires >= cutoff {
|
|
||||||
naddrs = append(naddrs, a)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return naddrs, true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Cmp(d, other *discosrv.DatabaseAddress) (n int) {
|
func Cmp(d, other *discosrv.DatabaseAddress) (n int) {
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ func TestFilter(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
res, _ := expire(tc.a, time.Unix(0, 10))
|
res := expire(tc.a, time.Unix(0, 10))
|
||||||
if fmt.Sprint(res) != fmt.Sprint(tc.b) {
|
if fmt.Sprint(res) != fmt.Sprint(tc.b) {
|
||||||
t.Errorf("Incorrect result %v, expected %v", res, tc.b)
|
t.Errorf("Incorrect result %v, expected %v", res, tc.b)
|
||||||
}
|
}
|
||||||
|
|||||||
+28
-31
@@ -9,12 +9,12 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"log"
|
||||||
"log/slog"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
_ "net/http/pprof"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"runtime"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/alecthomas/kong"
|
"github.com/alecthomas/kong"
|
||||||
@@ -22,8 +22,9 @@ import (
|
|||||||
"github.com/thejerf/suture/v4"
|
"github.com/thejerf/suture/v4"
|
||||||
|
|
||||||
"github.com/syncthing/syncthing/internal/blob"
|
"github.com/syncthing/syncthing/internal/blob"
|
||||||
|
"github.com/syncthing/syncthing/internal/blob/azureblob"
|
||||||
"github.com/syncthing/syncthing/internal/blob/s3"
|
"github.com/syncthing/syncthing/internal/blob/s3"
|
||||||
"github.com/syncthing/syncthing/internal/slogutil"
|
_ "github.com/syncthing/syncthing/lib/automaxprocs"
|
||||||
"github.com/syncthing/syncthing/lib/build"
|
"github.com/syncthing/syncthing/lib/build"
|
||||||
"github.com/syncthing/syncthing/lib/protocol"
|
"github.com/syncthing/syncthing/lib/protocol"
|
||||||
"github.com/syncthing/syncthing/lib/rand"
|
"github.com/syncthing/syncthing/lib/rand"
|
||||||
@@ -32,6 +33,7 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
addressExpiryTime = 2 * time.Hour
|
addressExpiryTime = 2 * time.Hour
|
||||||
|
databaseStatisticsInterval = 5 * time.Minute
|
||||||
|
|
||||||
// Reannounce-After is set to reannounceAfterSeconds +
|
// Reannounce-After is set to reannounceAfterSeconds +
|
||||||
// random(reannounzeFuzzSeconds), similar for Retry-After
|
// random(reannounzeFuzzSeconds), similar for Retry-After
|
||||||
@@ -64,10 +66,7 @@ type CLI struct {
|
|||||||
Compression bool `group:"Listen" help:"Enable GZIP compression of responses" env:"DISCOVERY_COMPRESSION"`
|
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"`
|
Listen string `group:"Listen" help:"Listen address" default:":8443" env:"DISCOVERY_LISTEN"`
|
||||||
MetricsListen string `group:"Listen" help:"Metrics listen address" env:"DISCOVERY_METRICS_LISTEN"`
|
MetricsListen string `group:"Listen" help:"Metrics listen address" env:"DISCOVERY_METRICS_LISTEN"`
|
||||||
DesiredUnseenNotFoundRate float64 `group:"Listen" help:"Desired maximum rate of not-found replies for never seen devices (/s)" default:"1000" env:"DISCOVERY_UNSEEN_RATE"`
|
DesiredNotFoundRate float64 `group:"Listen" help:"Desired maximum rate of not-found replies (/s)" default:"1000"`
|
||||||
DesiredSeenNotFoundRate float64 `group:"Listen" help:"Desired maximum rate of not-found replies for previously seen devices (/s)" default:"1000" env:"DISCOVERY_SEEN_RATE"`
|
|
||||||
|
|
||||||
ShutdownDelay float64 `help:"Time to wait before shutdown after receiving a shutdown signal (s)" env:"DISCOVERY_SHUTDOWN_DELAY"`
|
|
||||||
|
|
||||||
DBDir string `group:"Database" help:"Database directory" default:"." env:"DISCOVERY_DB_DIR"`
|
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"`
|
DBFlushInterval time.Duration `group:"Database" help:"Interval between database flushes" default:"5m" env:"DISCOVERY_DB_FLUSH_INTERVAL"`
|
||||||
@@ -78,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"`
|
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"`
|
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"`
|
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"`
|
Debug bool `short:"d" help:"Print debug output" env:"DISCOVERY_DEBUG"`
|
||||||
@@ -85,37 +88,34 @@ type CLI struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
log.SetOutput(os.Stdout)
|
||||||
|
|
||||||
var cli CLI
|
var cli CLI
|
||||||
kong.Parse(&cli)
|
kong.Parse(&cli)
|
||||||
|
debug = cli.Debug
|
||||||
|
|
||||||
level := slog.LevelInfo
|
log.Println(build.LongVersionFor("stdiscosrv"))
|
||||||
if cli.Debug {
|
|
||||||
level = slog.LevelDebug
|
|
||||||
}
|
|
||||||
slogutil.SetDefaultLevel(level)
|
|
||||||
if cli.Version {
|
if cli.Version {
|
||||||
fmt.Println(build.LongVersionFor("stdiscosrv"))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
slog.Info(build.LongVersionFor("stdiscosrv"))
|
|
||||||
|
buildInfo.WithLabelValues(build.Version, runtime.Version(), build.User, build.Date.UTC().Format("2006-01-02T15:04:05Z")).Set(1)
|
||||||
|
|
||||||
var cert tls.Certificate
|
var cert tls.Certificate
|
||||||
if !cli.HTTP {
|
if !cli.HTTP {
|
||||||
var err error
|
var err error
|
||||||
cert, err = tls.LoadX509KeyPair(cli.Cert, cli.Key)
|
cert, err = tls.LoadX509KeyPair(cli.Cert, cli.Key)
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
slog.Info("Failed to load keypair. Generating one, this might take a while...")
|
log.Println("Failed to load keypair. Generating one, this might take a while...")
|
||||||
cert, err = tlsutil.NewCertificate(cli.Cert, cli.Key, "stdiscosrv", 20*365, false)
|
cert, err = tlsutil.NewCertificate(cli.Cert, cli.Key, "stdiscosrv", 20*365)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to generate X509 key pair", "error", err)
|
log.Fatalln("Failed to generate X509 key pair:", err)
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
slog.Error("Failed to load keypair", "error", err)
|
log.Fatalln("Failed to load keypair:", err)
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
devID := protocol.NewDeviceID(cert.Certificate[0])
|
devID := protocol.NewDeviceID(cert.Certificate[0])
|
||||||
slog.Info("Loaded certificate keypair", "deviceId", devID.String())
|
log.Println("Server device ID is", devID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Root of the service tree.
|
// Root of the service tree.
|
||||||
@@ -129,10 +129,11 @@ func main() {
|
|||||||
var err error
|
var err error
|
||||||
if cli.DBS3Endpoint != "" {
|
if cli.DBS3Endpoint != "" {
|
||||||
blobs, err = s3.NewSession(cli.DBS3Endpoint, cli.DBS3Region, cli.DBS3Bucket, cli.DBS3AccessKeyID, cli.DBS3SecretKey)
|
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 {
|
if err != nil {
|
||||||
slog.Error("Failed to create blob store", "error", err)
|
log.Fatalf("Failed to create blob store: %v", err)
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start the database.
|
// Start the database.
|
||||||
@@ -149,7 +150,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Start the main API server.
|
// Start the main API server.
|
||||||
qs := newAPISrv(cli.Listen, cert, db, repl, cli.HTTP, cli.Compression, cli.DesiredUnseenNotFoundRate, cli.DesiredSeenNotFoundRate)
|
qs := newAPISrv(cli.Listen, cert, db, repl, cli.HTTP, cli.Compression, cli.DesiredNotFoundRate)
|
||||||
main.Add(qs)
|
main.Add(qs)
|
||||||
|
|
||||||
// If we have a metrics port configured, start a metrics handler.
|
// If we have a metrics port configured, start a metrics handler.
|
||||||
@@ -157,9 +158,7 @@ func main() {
|
|||||||
go func() {
|
go func() {
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
mux.Handle("/metrics", promhttp.Handler())
|
mux.Handle("/metrics", promhttp.Handler())
|
||||||
err := http.ListenAndServe(cli.MetricsListen, mux)
|
log.Fatal(http.ListenAndServe(cli.MetricsListen, mux))
|
||||||
slog.Error("Failed to serve", "error", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,11 +168,9 @@ func main() {
|
|||||||
// Cancel on signal
|
// Cancel on signal
|
||||||
signalChan := make(chan os.Signal, 1)
|
signalChan := make(chan os.Signal, 1)
|
||||||
signal.Notify(signalChan, os.Interrupt)
|
signal.Notify(signalChan, os.Interrupt)
|
||||||
signal.Notify(signalChan, syscall.SIGTERM)
|
|
||||||
go func() {
|
go func() {
|
||||||
sig := <-signalChan
|
sig := <-signalChan
|
||||||
slog.Info("Received signal; shutting down", "signal", sig, "delay", cli.ShutdownDelay)
|
log.Printf("Received signal %s; shutting down", sig)
|
||||||
time.Sleep(time.Duration(float64(time.Second) * cli.ShutdownDelay))
|
|
||||||
cancel()
|
cancel()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,14 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
buildInfo = prometheus.NewGaugeVec(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Namespace: "syncthing",
|
||||||
|
Subsystem: "discovery",
|
||||||
|
Name: "build_info",
|
||||||
|
Help: "A metric with a constant '1' value labeled by version, goversion, builduser and builddate from which stdiscosrv was built.",
|
||||||
|
}, []string{"version", "goversion", "builduser", "builddate"})
|
||||||
|
|
||||||
apiRequestsTotal = prometheus.NewCounterVec(
|
apiRequestsTotal = prometheus.NewCounterVec(
|
||||||
prometheus.CounterOpts{
|
prometheus.CounterOpts{
|
||||||
Namespace: "syncthing",
|
Namespace: "syncthing",
|
||||||
@@ -124,7 +132,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
prometheus.MustRegister(
|
prometheus.MustRegister(buildInfo,
|
||||||
apiRequestsTotal, apiRequestsSeconds,
|
apiRequestsTotal, apiRequestsSeconds,
|
||||||
lookupRequestsTotal, announceRequestsTotal,
|
lookupRequestsTotal, announceRequestsTotal,
|
||||||
replicationSendsTotal, replicationRecvsTotal,
|
replicationSendsTotal, replicationRecvsTotal,
|
||||||
|
|||||||
+9
-16
@@ -14,7 +14,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"syscall"
|
"syscall"
|
||||||
@@ -22,6 +21,7 @@ import (
|
|||||||
|
|
||||||
"golang.org/x/time/rate"
|
"golang.org/x/time/rate"
|
||||||
|
|
||||||
|
_ "github.com/syncthing/syncthing/lib/automaxprocs"
|
||||||
"github.com/syncthing/syncthing/lib/build"
|
"github.com/syncthing/syncthing/lib/build"
|
||||||
"github.com/syncthing/syncthing/lib/config"
|
"github.com/syncthing/syncthing/lib/config"
|
||||||
"github.com/syncthing/syncthing/lib/events"
|
"github.com/syncthing/syncthing/lib/events"
|
||||||
@@ -71,13 +71,9 @@ var (
|
|||||||
|
|
||||||
// httpClient is the HTTP client we use for outbound requests. It has a
|
// httpClient is the HTTP client we use for outbound requests. It has a
|
||||||
// timeout and may get further options set during initialization.
|
// timeout and may get further options set during initialization.
|
||||||
var (
|
var httpClient = &http.Client{
|
||||||
httpTransport = &http.Transport{}
|
|
||||||
httpClient = &http.Client{
|
|
||||||
Timeout: 30 * time.Second,
|
Timeout: 30 * time.Second,
|
||||||
Transport: httpTransport,
|
|
||||||
}
|
}
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
log.SetFlags(log.Lshortfile | log.LstdFlags)
|
log.SetFlags(log.Lshortfile | log.LstdFlags)
|
||||||
@@ -136,7 +132,9 @@ func main() {
|
|||||||
// also come from that address.
|
// also come from that address.
|
||||||
laddr.Port = 0
|
laddr.Port = 0
|
||||||
boundDialer := &net.Dialer{LocalAddr: laddr}
|
boundDialer := &net.Dialer{LocalAddr: laddr}
|
||||||
httpTransport.DialContext = boundDialer.DialContext
|
httpClient.Transport = &http.Transport{
|
||||||
|
DialContext: boundDialer.DialContext,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println(longVer)
|
log.Println(longVer)
|
||||||
@@ -159,17 +157,12 @@ func main() {
|
|||||||
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Failed to load keypair. Generating one, this might take a while...")
|
log.Println("Failed to load keypair. Generating one, this might take a while...")
|
||||||
cert, err = tlsutil.NewCertificate(certFile, keyFile, "strelaysrv", 20*365, false)
|
cert, err = tlsutil.NewCertificate(certFile, keyFile, "strelaysrv", 20*365)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln("Failed to generate X509 key pair:", err)
|
log.Fatalln("Failed to generate X509 key pair:", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Outgoing HTTPS requests may use our certificate for authentication
|
|
||||||
httpTransport.TLSClientConfig = &tls.Config{
|
|
||||||
Certificates: []tls.Certificate{cert},
|
|
||||||
}
|
|
||||||
|
|
||||||
tlsCfg := &tls.Config{
|
tlsCfg := &tls.Config{
|
||||||
Certificates: []tls.Certificate{cert},
|
Certificates: []tls.Certificate{cert},
|
||||||
NextProtos: []string{protocol.ProtocolName},
|
NextProtos: []string{protocol.ProtocolName},
|
||||||
@@ -254,10 +247,10 @@ func main() {
|
|||||||
query.Set("pingInterval", pingInterval.String())
|
query.Set("pingInterval", pingInterval.String())
|
||||||
query.Set("networkTimeout", networkTimeout.String())
|
query.Set("networkTimeout", networkTimeout.String())
|
||||||
if sessionLimitBps > 0 {
|
if sessionLimitBps > 0 {
|
||||||
query.Set("sessionLimitBps", strconv.Itoa(sessionLimitBps))
|
query.Set("sessionLimitBps", fmt.Sprint(sessionLimitBps))
|
||||||
}
|
}
|
||||||
if globalLimitBps > 0 {
|
if globalLimitBps > 0 {
|
||||||
query.Set("globalLimitBps", strconv.Itoa(globalLimitBps))
|
query.Set("globalLimitBps", fmt.Sprint(globalLimitBps))
|
||||||
}
|
}
|
||||||
if statusAddr != "" {
|
if statusAddr != "" {
|
||||||
query.Set("statusAddr", statusAddr)
|
query.Set("statusAddr", statusAddr)
|
||||||
@@ -284,7 +277,7 @@ func main() {
|
|||||||
for _, pool := range pools {
|
for _, pool := range pools {
|
||||||
pool = strings.TrimSpace(pool)
|
pool = strings.TrimSpace(pool)
|
||||||
if len(pool) > 0 {
|
if len(pool) > 0 {
|
||||||
go poolHandler(pool, uri, mapping)
|
go poolHandler(pool, uri, mapping, cert)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+20
-2
@@ -4,6 +4,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
@@ -16,7 +17,7 @@ const (
|
|||||||
httpStatusEnhanceYourCalm = 429
|
httpStatusEnhanceYourCalm = 429
|
||||||
)
|
)
|
||||||
|
|
||||||
func poolHandler(pool string, uri *url.URL, mapping mapping) {
|
func poolHandler(pool string, uri *url.URL, mapping mapping, ownCert tls.Certificate) {
|
||||||
if debug {
|
if debug {
|
||||||
log.Println("Joining", pool)
|
log.Println("Joining", pool)
|
||||||
}
|
}
|
||||||
@@ -31,7 +32,24 @@ func poolHandler(pool string, uri *url.URL, mapping mapping) {
|
|||||||
uriCopy.String(),
|
uriCopy.String(),
|
||||||
})
|
})
|
||||||
|
|
||||||
resp, err := httpClient.Post(pool, "application/json", &b) //nolint:noctx
|
poolUrl, err := url.Parse(pool)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Could not parse pool url '%s': %v", pool, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client := http.DefaultClient
|
||||||
|
if poolUrl.Scheme == "https" {
|
||||||
|
// Sent our certificate in join request
|
||||||
|
client = &http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{
|
||||||
|
Certificates: []tls.Certificate{ownCert},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := client.Post(pool, "application/json", &b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error joining pool %v: HTTP request: %v", pool, err)
|
log.Printf("Error joining pool %v: HTTP request: %v", pool, err)
|
||||||
time.Sleep(time.Minute)
|
time.Sleep(time.Minute)
|
||||||
|
|||||||
@@ -158,12 +158,19 @@ func (s *session) Serve() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
wg := sync.WaitGroup{}
|
wg := sync.WaitGroup{}
|
||||||
|
wg.Add(2)
|
||||||
|
|
||||||
var err0 error
|
var err0 error
|
||||||
wg.Go(func() { err0 = s.proxy(s.conns[0], s.conns[1]) })
|
go func() {
|
||||||
|
err0 = s.proxy(s.conns[0], s.conns[1])
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
|
||||||
var err1 error
|
var err1 error
|
||||||
wg.Go(func() { err1 = s.proxy(s.conns[1], s.conns[0]) })
|
go func() {
|
||||||
|
err1 = s.proxy(s.conns[1], s.conns[0])
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
|
||||||
sessionMut.Lock()
|
sessionMut.Lock()
|
||||||
activeSessions = append(activeSessions, s)
|
activeSessions = append(activeSessions, s)
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ func (r *rateCalculator) updateRates(interval time.Duration) {
|
|||||||
|
|
||||||
func (r *rateCalculator) rate(periods int) int64 {
|
func (r *rateCalculator) rate(periods int) int64 {
|
||||||
var tot int64
|
var tot int64
|
||||||
for i := range periods {
|
for i := 0; i < periods; i++ {
|
||||||
tot += r.rates[i]
|
tot += r.rates[i]
|
||||||
}
|
}
|
||||||
return tot / int64(periods)
|
return tot / int64(periods)
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"errors"
|
|
||||||
"flag"
|
"flag"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
@@ -15,6 +14,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
_ "github.com/syncthing/syncthing/lib/automaxprocs"
|
||||||
syncthingprotocol "github.com/syncthing/syncthing/lib/protocol"
|
syncthingprotocol "github.com/syncthing/syncthing/lib/protocol"
|
||||||
"github.com/syncthing/syncthing/lib/relay/client"
|
"github.com/syncthing/syncthing/lib/relay/client"
|
||||||
"github.com/syncthing/syncthing/lib/relay/protocol"
|
"github.com/syncthing/syncthing/lib/relay/protocol"
|
||||||
@@ -133,8 +133,7 @@ func connectToStdio(stdin <-chan string, conn net.Conn) {
|
|||||||
conn.SetReadDeadline(time.Now().Add(time.Millisecond))
|
conn.SetReadDeadline(time.Now().Add(time.Millisecond))
|
||||||
n, err := conn.Read(buf[0:])
|
n, err := conn.Read(buf[0:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var nerr net.Error
|
nerr, ok := err.(net.Error)
|
||||||
ok := errors.As(err, &nerr)
|
|
||||||
if !ok || !nerr.Timeout() {
|
if !ok || !nerr.Timeout() {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
func setTCPOptions(conn net.Conn) error {
|
func setTCPOptions(conn net.Conn) error {
|
||||||
tcpConn, ok := conn.(*net.TCPConn)
|
tcpConn, ok := conn.(*net.TCPConn)
|
||||||
if !ok {
|
if !ok {
|
||||||
return errors.New("not a TCP connection")
|
return errors.New("Not a TCP connection")
|
||||||
}
|
}
|
||||||
if err := tcpConn.SetLinger(0); err != nil {
|
if err := tcpConn.SetLinger(0); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -8,14 +8,11 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"runtime/pprof"
|
"runtime/pprof"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/syncthing/syncthing/internal/slogutil"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func startBlockProfiler() {
|
func startBlockProfiler() {
|
||||||
@@ -23,10 +20,10 @@ func startBlockProfiler() {
|
|||||||
if profiler == nil {
|
if profiler == nil {
|
||||||
panic("Couldn't find block profiler")
|
panic("Couldn't find block profiler")
|
||||||
}
|
}
|
||||||
slog.Debug("Starting block profiling")
|
l.Debugln("Starting block profiling")
|
||||||
go func() {
|
go func() {
|
||||||
err := saveBlockingProfiles(profiler) // Only returns on error
|
err := saveBlockingProfiles(profiler) // Only returns on error
|
||||||
slog.Error("Block profiler failed", slogutil.Error(err))
|
l.Warnln("Block profiler failed:", err)
|
||||||
panic("Block profiler failed")
|
panic("Block profiler failed")
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ type APIClient interface {
|
|||||||
|
|
||||||
type apiClient struct {
|
type apiClient struct {
|
||||||
http.Client
|
http.Client
|
||||||
|
|
||||||
cfg config.GUIConfiguration
|
cfg config.GUIConfiguration
|
||||||
apikey string
|
apikey string
|
||||||
}
|
}
|
||||||
@@ -92,11 +91,11 @@ func loadGUIConfig() (config.GUIConfiguration, error) {
|
|||||||
guiCfg := cfg.GUI()
|
guiCfg := cfg.GUI()
|
||||||
|
|
||||||
if guiCfg.Address() == "" {
|
if guiCfg.Address() == "" {
|
||||||
return config.GUIConfiguration{}, errors.New("could not find GUI Address")
|
return config.GUIConfiguration{}, errors.New("Could not find GUI Address")
|
||||||
}
|
}
|
||||||
|
|
||||||
if guiCfg.APIKey == "" {
|
if guiCfg.APIKey == "" {
|
||||||
return config.GUIConfiguration{}, errors.New("could not find GUI API key")
|
return config.GUIConfiguration{}, errors.New("Could not find GUI API key")
|
||||||
}
|
}
|
||||||
|
|
||||||
return guiCfg, nil
|
return guiCfg, nil
|
||||||
@@ -114,7 +113,7 @@ func (c *apiClient) Endpoint() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *apiClient) Do(req *http.Request) (*http.Response, error) {
|
func (c *apiClient) Do(req *http.Request) (*http.Response, error) {
|
||||||
req.Header.Set("X-Api-Key", c.apikey)
|
req.Header.Set("X-API-Key", c.apikey)
|
||||||
resp, err := c.Client.Do(req)
|
resp, err := c.Client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"github.com/AudriusButkevicius/recli"
|
"github.com/AudriusButkevicius/recli"
|
||||||
@@ -19,53 +18,6 @@ import (
|
|||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Try to mimic the kong output format through custom help templates
|
|
||||||
var customAppHelpTemplate = `Usage: {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}}{{if .Commands}} <command> [flags]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}
|
|
||||||
|
|
||||||
{{.Description}}{{if .VisibleFlags}}
|
|
||||||
|
|
||||||
Flags:
|
|
||||||
{{range $index, $option := .VisibleFlags}}{{if $index}}
|
|
||||||
{{end}}{{$option}}{{end}}{{end}}{{if .VisibleCommands}}
|
|
||||||
|
|
||||||
Commands:{{range .VisibleCategories}}{{if .Name}}
|
|
||||||
|
|
||||||
{{.Name}}:{{range .VisibleCommands}}
|
|
||||||
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}}
|
|
||||||
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{end}}
|
|
||||||
`
|
|
||||||
|
|
||||||
var customCommandHelpTemplate = `Usage: {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [flags]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}
|
|
||||||
|
|
||||||
{{.Usage}}{{if .VisibleFlags}}
|
|
||||||
|
|
||||||
Flags:
|
|
||||||
{{range $index, $option := .VisibleFlags}}{{if $index}}
|
|
||||||
{{end}}{{$option}}{{end}}{{end}}{{if .Category}}
|
|
||||||
|
|
||||||
Category:
|
|
||||||
{{.Category}}{{end}}{{if .Description}}
|
|
||||||
|
|
||||||
{{.Description}}{{end}}
|
|
||||||
`
|
|
||||||
|
|
||||||
var customSubcommandHelpTemplate = `Usage: {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} <command>{{if .VisibleFlags}} [flags]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Description}}
|
|
||||||
|
|
||||||
{{.Description}}{{else}}{{if .Usage}}
|
|
||||||
|
|
||||||
{{.Usage}}{{end}}{{end}}{{if .VisibleFlags}}
|
|
||||||
|
|
||||||
Flags:
|
|
||||||
{{range $index, $option := .VisibleFlags}}{{if $index}}
|
|
||||||
{{end}}{{$option}}{{end}}{{end}}{{if .VisibleCommands}}
|
|
||||||
|
|
||||||
Commands:{{range .VisibleCategories}}{{if .Name}}
|
|
||||||
|
|
||||||
{{.Name}}:{{range .VisibleCommands}}
|
|
||||||
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}}
|
|
||||||
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{end}}
|
|
||||||
`
|
|
||||||
|
|
||||||
type configHandler struct {
|
type configHandler struct {
|
||||||
original, cfg config.Configuration
|
original, cfg config.Configuration
|
||||||
client APIClient
|
client APIClient
|
||||||
@@ -76,18 +28,13 @@ type configCommand struct {
|
|||||||
Args []string `arg:"" default:"-h"`
|
Args []string `arg:"" default:"-h"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *configCommand) Run(ctx Context, outerCtx *kong.Context) error {
|
func (c *configCommand) Run(ctx Context, _ *kong.Context) error {
|
||||||
app := cli.NewApp()
|
app := cli.NewApp()
|
||||||
app.Name = "syncthing cli config"
|
app.Name = "syncthing"
|
||||||
app.HelpName = "syncthing cli config"
|
app.Author = "The Syncthing Authors"
|
||||||
app.Description = outerCtx.Selected().Help
|
|
||||||
app.Metadata = map[string]interface{}{
|
app.Metadata = map[string]interface{}{
|
||||||
"clientFactory": ctx.clientFactory,
|
"clientFactory": ctx.clientFactory,
|
||||||
}
|
}
|
||||||
app.CustomAppHelpTemplate = customAppHelpTemplate
|
|
||||||
// Override global templates, as this is out only usage of the package
|
|
||||||
cli.CommandHelpTemplate = customCommandHelpTemplate
|
|
||||||
cli.SubcommandHelpTemplate = customSubcommandHelpTemplate
|
|
||||||
|
|
||||||
h := new(configHandler)
|
h := new(configHandler)
|
||||||
h.client, h.err = ctx.clientFactory.getClient()
|
h.client, h.err = ctx.clientFactory.getClient()
|
||||||
@@ -108,8 +55,6 @@ func (c *configCommand) Run(ctx Context, outerCtx *kong.Context) error {
|
|||||||
|
|
||||||
app.Commands = commands
|
app.Commands = commands
|
||||||
app.HideHelp = true
|
app.HideHelp = true
|
||||||
// Explicitly re-add help only as flags, not as commands
|
|
||||||
app.Flags = []cli.Flag{cli.HelpFlag}
|
|
||||||
app.Before = h.configBefore
|
app.Before = h.configBefore
|
||||||
app.After = h.configAfter
|
app.After = h.configAfter
|
||||||
|
|
||||||
@@ -141,7 +86,7 @@ func (h *configHandler) configAfter(_ *cli.Context) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != 200 {
|
||||||
body, err := responseToBArray(resp)
|
body, err := responseToBArray(resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -41,4 +41,5 @@ func (p *profileCommand) Run(ctx Context) error {
|
|||||||
type debugCommand struct {
|
type debugCommand struct {
|
||||||
File fileCommand `cmd:"" help:"Show information about a file (or directory/symlink)"`
|
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"`
|
Profile profileCommand `cmd:"" help:"Save a profile to help figuring out what Syncthing does"`
|
||||||
|
Index indexCommand `cmd:"" help:"Show information about the index (database)"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ package cli
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/alecthomas/kong"
|
"github.com/alecthomas/kong"
|
||||||
@@ -35,7 +34,7 @@ func (e *errorsPushCommand) Run(ctx Context) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if response.StatusCode != http.StatusOK {
|
if response.StatusCode != 200 {
|
||||||
errStr = fmt.Sprint("Failed to push error\nStatus code: ", response.StatusCode)
|
errStr = fmt.Sprint("Failed to push error\nStatus code: ", response.StatusCode)
|
||||||
bytes, err := responseToBArray(response)
|
bytes, err := responseToBArray(response)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
// 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
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
// 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
|
||||||
|
}
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
// 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
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
// 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 (
|
||||||
|
"cmp"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"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)
|
||||||
|
}
|
||||||
|
|
||||||
|
slices.SortFunc(elems, func(a, b sizedElement) int {
|
||||||
|
return cmp.Compare(b.size, a.size)
|
||||||
|
})
|
||||||
|
for _, ele := range elems {
|
||||||
|
fmt.Println(ele.key, ele.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,435 @@
|
|||||||
|
// 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"
|
||||||
|
"cmp"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"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
|
||||||
|
|
||||||
|
slices.SortFunc(missingSeq, func(a, b sequenceKey) int {
|
||||||
|
if a.folder != b.folder {
|
||||||
|
return cmp.Compare(a.folder, b.folder)
|
||||||
|
}
|
||||||
|
return cmp.Compare(a.sequence, 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,12 +14,15 @@ import (
|
|||||||
"github.com/alecthomas/kong"
|
"github.com/alecthomas/kong"
|
||||||
"github.com/kballard/go-shellquote"
|
"github.com/kballard/go-shellquote"
|
||||||
|
|
||||||
|
"github.com/syncthing/syncthing/cmd/syncthing/cmdutil"
|
||||||
"github.com/syncthing/syncthing/lib/config"
|
"github.com/syncthing/syncthing/lib/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CLI struct {
|
type CLI struct {
|
||||||
GUIAddress string `name:"gui-address" env:"STGUIADDRESS"`
|
cmdutil.CommonOptions
|
||||||
GUIAPIKey string `name:"gui-apikey" env:"STGUIAPIKEY"`
|
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"`
|
||||||
|
|
||||||
Show showCommand `cmd:"" help:"Show command group"`
|
Show showCommand `cmd:"" help:"Show command group"`
|
||||||
Debug debugCommand `cmd:"" help:"Debug command group"`
|
Debug debugCommand `cmd:"" help:"Debug command group"`
|
||||||
@@ -34,6 +37,11 @@ type Context struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (cli CLI) AfterApply(kongCtx *kong.Context) error {
|
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{
|
clientFactory := &apiClientFactory{
|
||||||
cfg: config.GUIConfiguration{
|
cfg: config.GUIConfiguration{
|
||||||
RawAddress: cli.GUIAddress,
|
RawAddress: cli.GUIAddress,
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/alecthomas/kong"
|
"github.com/alecthomas/kong"
|
||||||
@@ -64,7 +63,7 @@ func (f *folderOverrideCommand) Run(ctx Context) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if response.StatusCode != http.StatusOK {
|
if response.StatusCode != 200 {
|
||||||
errStr := fmt.Sprint("Failed to override changes\nStatus code: ", response.StatusCode)
|
errStr := fmt.Sprint("Failed to override changes\nStatus code: ", response.StatusCode)
|
||||||
bytes, err := responseToBArray(response)
|
bytes, err := responseToBArray(response)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/syncthing/syncthing/lib/config"
|
"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) {
|
func responseToBArray(response *http.Response) ([]byte, error) {
|
||||||
@@ -131,6 +133,19 @@ func prettyPrintResponse(response *http.Response) error {
|
|||||||
return prettyPrintJSON(data)
|
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 {
|
||||||
|
return string(bs[:i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return string(bs)
|
||||||
|
}
|
||||||
|
|
||||||
func normalizePath(path string) string {
|
func normalizePath(path string) string {
|
||||||
return filepath.ToSlash(filepath.Clean(path))
|
return filepath.ToSlash(filepath.Clean(path))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
// 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"`
|
||||||
|
}
|
||||||
@@ -7,8 +7,8 @@
|
|||||||
//go:build !windows
|
//go:build !windows
|
||||||
// +build !windows
|
// +build !windows
|
||||||
|
|
||||||
package main
|
package cmdutil
|
||||||
|
|
||||||
type buildSpecificOptions struct {
|
type buildCommonOptions struct {
|
||||||
HideConsole bool `hidden:""`
|
HideConsole bool `hidden:""`
|
||||||
}
|
}
|
||||||
@@ -4,8 +4,8 @@
|
|||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
// 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/.
|
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
package main
|
package cmdutil
|
||||||
|
|
||||||
type buildSpecificOptions struct {
|
type buildCommonOptions struct {
|
||||||
HideConsole bool `name:"no-console" help:"Hide console window" env:"STHIDECONSOLE"`
|
HideConsole bool `name:"no-console" help:"Hide console window"`
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
// 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
|
||||||
|
}
|
||||||
@@ -11,15 +11,12 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/syncthing/syncthing/internal/slogutil"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -31,12 +28,12 @@ const (
|
|||||||
// directory to the crash reporting server as urlBase. Uploads are attempted
|
// directory to the crash reporting server as urlBase. Uploads are attempted
|
||||||
// with the newest log first.
|
// with the newest log first.
|
||||||
//
|
//
|
||||||
// This can block for a long time. The context can set a final deadline
|
// This can can block for a long time. The context can set a final deadline
|
||||||
// for this.
|
// for this.
|
||||||
func uploadPanicLogs(ctx context.Context, urlBase, dir string) {
|
func uploadPanicLogs(ctx context.Context, urlBase, dir string) {
|
||||||
files, err := filepath.Glob(filepath.Join(dir, "panic-*.log"))
|
files, err := filepath.Glob(filepath.Join(dir, "panic-*.log"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.ErrorContext(ctx, "Failed to list panic logs", slogutil.Error(err))
|
l.Warnln("Failed to list panic logs:", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,7 +48,7 @@ func uploadPanicLogs(ctx context.Context, urlBase, dir string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := uploadPanicLog(ctx, urlBase, file); err != nil {
|
if err := uploadPanicLog(ctx, urlBase, file); err != nil {
|
||||||
slog.ErrorContext(ctx, "Reporting crash", slogutil.Error(err))
|
l.Warnln("Reporting crash:", err)
|
||||||
} else {
|
} else {
|
||||||
// Rename the log so we don't have to try to report it again. This
|
// Rename the log so we don't have to try to report it again. This
|
||||||
// succeeds, or it does not. There is no point complaining about it.
|
// succeeds, or it does not. There is no point complaining about it.
|
||||||
@@ -74,7 +71,7 @@ func uploadPanicLog(ctx context.Context, urlBase, file string) error {
|
|||||||
data = filterLogLines(data)
|
data = filterLogLines(data)
|
||||||
|
|
||||||
hash := fmt.Sprintf("%x", sha256.Sum256(data))
|
hash := fmt.Sprintf("%x", sha256.Sum256(data))
|
||||||
slog.InfoContext(ctx, "Reporting crash", slogutil.FilePath(filepath.Base(file)), slog.String("id", hash[:8]))
|
l.Infof("Reporting crash found in %s (report ID %s) ...\n", filepath.Base(file), hash[:8])
|
||||||
|
|
||||||
url := fmt.Sprintf("%s/%s", urlBase, hash)
|
url := fmt.Sprintf("%s/%s", urlBase, hash)
|
||||||
headReq, err := http.NewRequest(http.MethodHead, url, nil)
|
headReq, err := http.NewRequest(http.MethodHead, url, nil)
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import "github.com/syncthing/syncthing/internal/slogutil"
|
import (
|
||||||
|
"github.com/syncthing/syncthing/lib/logger"
|
||||||
|
)
|
||||||
|
|
||||||
func init() { slogutil.RegisterPackage("Main package") }
|
var l = logger.DefaultLogger.NewFacility("main", "Main package")
|
||||||
|
|||||||
@@ -238,7 +238,7 @@ func (c *CLI) decryptFile(encFi *protocol.FileInfo, plainFi *protocol.FileInfo,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Verify the hash against the plaintext block info
|
// Verify the hash against the plaintext block info
|
||||||
if !scanner.Validate(dec, plainBlock.Hash) {
|
if !scanner.Validate(dec, plainBlock.Hash, 0) {
|
||||||
// The block decrypted correctly but fails the hash check. This
|
// The block decrypted correctly but fails the hash check. This
|
||||||
// is odd and unexpected, but it it's still a valid block from
|
// is odd and unexpected, but it it's still a valid block from
|
||||||
// the source. The file might have changed while we pulled it?
|
// the source. The file might have changed while we pulled it?
|
||||||
|
|||||||
@@ -11,25 +11,42 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/syncthing/syncthing/cmd/syncthing/cmdutil"
|
||||||
"github.com/syncthing/syncthing/lib/config"
|
"github.com/syncthing/syncthing/lib/config"
|
||||||
"github.com/syncthing/syncthing/lib/events"
|
"github.com/syncthing/syncthing/lib/events"
|
||||||
"github.com/syncthing/syncthing/lib/fs"
|
"github.com/syncthing/syncthing/lib/fs"
|
||||||
"github.com/syncthing/syncthing/lib/locations"
|
"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/protocol"
|
||||||
"github.com/syncthing/syncthing/lib/syncthing"
|
"github.com/syncthing/syncthing/lib/syncthing"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CLI struct {
|
type CLI struct {
|
||||||
|
cmdutil.CommonOptions
|
||||||
GUIUser string `placeholder:"STRING" help:"Specify new GUI authentication user name"`
|
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)"`
|
GUIPassword string `placeholder:"STRING" help:"Specify new GUI authentication password (use - to read from standard input)"`
|
||||||
NoPortProbing bool `help:"Don't try to find free ports for GUI and listen addresses on first startup" env:"STNOPORTPROBING"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CLI) Run() error {
|
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
|
// Support reading the password from a pipe or similar
|
||||||
if c.GUIPassword == "-" {
|
if c.GUIPassword == "-" {
|
||||||
reader := bufio.NewReader(os.Stdin)
|
reader := bufio.NewReader(os.Stdin)
|
||||||
@@ -40,13 +57,13 @@ func (c *CLI) Run() error {
|
|||||||
c.GUIPassword = string(password)
|
c.GUIPassword = string(password)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := Generate(locations.GetBaseDir(locations.ConfigBaseDir), c.GUIUser, c.GUIPassword, c.NoPortProbing); err != nil {
|
if err := Generate(l, c.ConfDir, c.GUIUser, c.GUIPassword, c.NoDefaultFolder, c.SkipPortProbing); err != nil {
|
||||||
return fmt.Errorf("failed to generate config and keys: %w", err)
|
return fmt.Errorf("failed to generate config and keys: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func Generate(confDir, guiUser, guiPassword string, skipPortProbing bool) error {
|
func Generate(l logger.Logger, confDir, guiUser, guiPassword string, noDefaultFolder, skipPortProbing bool) error {
|
||||||
dir, err := fs.ExpandTilde(confDir)
|
dir, err := fs.ExpandTilde(confDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -61,21 +78,20 @@ func Generate(confDir, guiUser, guiPassword string, skipPortProbing bool) error
|
|||||||
certFile, keyFile := locations.Get(locations.CertFile), locations.Get(locations.KeyFile)
|
certFile, keyFile := locations.Get(locations.CertFile), locations.Get(locations.KeyFile)
|
||||||
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
slog.Warn("Key exists; will not overwrite")
|
l.Warnln("Key exists; will not overwrite.")
|
||||||
} else {
|
} else {
|
||||||
cert, err = syncthing.GenerateCertificate(certFile, keyFile)
|
cert, err = syncthing.GenerateCertificate(certFile, keyFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("create certificate: %w", err)
|
return fmt.Errorf("create certificate: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
myID = protocol.NewDeviceID(cert.Certificate[0])
|
myID = protocol.NewDeviceID(cert.Certificate[0])
|
||||||
slog.Info("Calculated device ID", slog.String("device", myID.String()))
|
l.Infoln("Device ID:", myID)
|
||||||
|
|
||||||
cfgFile := locations.Get(locations.ConfigFile)
|
cfgFile := locations.Get(locations.ConfigFile)
|
||||||
cfg, _, err := config.Load(cfgFile, myID, events.NoopLogger)
|
cfg, _, err := config.Load(cfgFile, myID, events.NoopLogger)
|
||||||
if fs.IsNotExist(err) {
|
if fs.IsNotExist(err) {
|
||||||
if cfg, err = syncthing.DefaultConfig(cfgFile, myID, events.NoopLogger, skipPortProbing); err != nil {
|
if cfg, err = syncthing.DefaultConfig(cfgFile, myID, events.NoopLogger, noDefaultFolder, skipPortProbing); err != nil {
|
||||||
return fmt.Errorf("create config: %w", err)
|
return fmt.Errorf("create config: %w", err)
|
||||||
}
|
}
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
@@ -88,7 +104,7 @@ func Generate(confDir, guiUser, guiPassword string, skipPortProbing bool) error
|
|||||||
|
|
||||||
var updateErr error
|
var updateErr error
|
||||||
waiter, err := cfg.Modify(func(cfg *config.Configuration) {
|
waiter, err := cfg.Modify(func(cfg *config.Configuration) {
|
||||||
updateErr = updateGUIAuthentication(&cfg.GUI, guiUser, guiPassword)
|
updateErr = updateGUIAuthentication(l, &cfg.GUI, guiUser, guiPassword)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("modify config: %w", err)
|
return fmt.Errorf("modify config: %w", err)
|
||||||
@@ -104,17 +120,17 @@ func Generate(confDir, guiUser, guiPassword string, skipPortProbing bool) error
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateGUIAuthentication(guiCfg *config.GUIConfiguration, guiUser, guiPassword string) error {
|
func updateGUIAuthentication(l logger.Logger, guiCfg *config.GUIConfiguration, guiUser, guiPassword string) error {
|
||||||
if guiUser != "" && guiCfg.User != guiUser {
|
if guiUser != "" && guiCfg.User != guiUser {
|
||||||
guiCfg.User = guiUser
|
guiCfg.User = guiUser
|
||||||
slog.Info("Updated GUI authentication user", "name", guiUser)
|
l.Infoln("Updated GUI authentication user name:", guiUser)
|
||||||
}
|
}
|
||||||
|
|
||||||
if guiPassword != "" && guiCfg.Password != guiPassword {
|
if guiPassword != "" && guiCfg.Password != guiPassword {
|
||||||
if err := guiCfg.SetPassword(guiPassword); err != nil {
|
if err := guiCfg.SetPassword(guiPassword); err != nil {
|
||||||
return fmt.Errorf("failed to set GUI authentication password: %w", err)
|
return fmt.Errorf("failed to set GUI authentication password: %w", err)
|
||||||
}
|
}
|
||||||
slog.Info("Updated GUI authentication password")
|
l.Infoln("Updated GUI authentication password.")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,21 +8,18 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"runtime/pprof"
|
"runtime/pprof"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/syncthing/syncthing/internal/slogutil"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func startHeapProfiler() {
|
func startHeapProfiler() {
|
||||||
slog.Debug("Starting heap profiling")
|
l.Debugln("Starting heap profiling")
|
||||||
go func() {
|
go func() {
|
||||||
err := saveHeapProfiles(1) // Only returns on error
|
err := saveHeapProfiles(1) // Only returns on error
|
||||||
slog.Error("Heap profiler failed", slogutil.Error(err))
|
l.Warnln("Heap profiler failed:", err)
|
||||||
panic("Heap profiler failed")
|
panic("Heap profiler failed")
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|||||||
+341
-453
File diff suppressed because it is too large
Load Diff
+56
-32
@@ -9,31 +9,28 @@ package main
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log/slog"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/syncthing/syncthing/internal/slogutil"
|
|
||||||
"github.com/syncthing/syncthing/lib/build"
|
"github.com/syncthing/syncthing/lib/build"
|
||||||
"github.com/syncthing/syncthing/lib/fs"
|
"github.com/syncthing/syncthing/lib/fs"
|
||||||
"github.com/syncthing/syncthing/lib/locations"
|
"github.com/syncthing/syncthing/lib/locations"
|
||||||
"github.com/syncthing/syncthing/lib/osutil"
|
"github.com/syncthing/syncthing/lib/osutil"
|
||||||
"github.com/syncthing/syncthing/lib/svcutil"
|
"github.com/syncthing/syncthing/lib/svcutil"
|
||||||
|
"github.com/syncthing/syncthing/lib/sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
stdoutFirstLines []string // The first 10 lines of stdout
|
stdoutFirstLines []string // The first 10 lines of stdout
|
||||||
stdoutLastLines []string // The last 50 lines of stdout
|
stdoutLastLines []string // The last 50 lines of stdout
|
||||||
stdoutMut sync.Mutex
|
stdoutMut = sync.NewMutex()
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -46,7 +43,9 @@ const (
|
|||||||
panicUploadNoticeWait = 10 * time.Second
|
panicUploadNoticeWait = 10 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *serveCmd) monitorMain() {
|
func monitorMain(options serveOptions) {
|
||||||
|
l.SetPrefix("[monitor] ")
|
||||||
|
|
||||||
var dst io.Writer = os.Stdout
|
var dst io.Writer = os.Stdout
|
||||||
|
|
||||||
logFile := locations.Get(locations.LogFile)
|
logFile := locations.Get(locations.LogFile)
|
||||||
@@ -59,13 +58,13 @@ func (c *serveCmd) monitorMain() {
|
|||||||
open := func(name string) (io.WriteCloser, error) {
|
open := func(name string) (io.WriteCloser, error) {
|
||||||
return newAutoclosedFile(name, logFileAutoCloseDelay, logFileMaxOpenTime)
|
return newAutoclosedFile(name, logFileAutoCloseDelay, logFileMaxOpenTime)
|
||||||
}
|
}
|
||||||
if c.LogMaxSize > 0 {
|
if options.LogMaxSize > 0 {
|
||||||
fileDst, err = newRotatedFile(logFile, open, int64(c.LogMaxSize), c.LogMaxFiles)
|
fileDst, err = newRotatedFile(logFile, open, int64(options.LogMaxSize), options.LogMaxFiles)
|
||||||
} else {
|
} else {
|
||||||
fileDst, err = open(logFile)
|
fileDst, err = open(logFile)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to set up logging to file, proceeding with logging to stdout only", slogutil.Error(err))
|
l.Warnln("Failed to set up logging to file, proceeding with logging to stdout only:", err)
|
||||||
} else {
|
} else {
|
||||||
if build.IsWindows {
|
if build.IsWindows {
|
||||||
// Translate line breaks to Windows standard
|
// Translate line breaks to Windows standard
|
||||||
@@ -79,14 +78,14 @@ func (c *serveCmd) monitorMain() {
|
|||||||
// Log to both stdout and file.
|
// Log to both stdout and file.
|
||||||
dst = io.MultiWriter(dst, fileDst)
|
dst = io.MultiWriter(dst, fileDst)
|
||||||
|
|
||||||
slog.Info("Saved log output", slogutil.FilePath(logFile))
|
l.Infof(`Log output saved to file "%s"`, logFile)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
args := os.Args
|
args := os.Args
|
||||||
binary, err := getBinary(args[0])
|
binary, err := getBinary(args[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to start the main Syncthing process", slogutil.Error(err))
|
l.Warnln("Error starting the main Syncthing process:", err)
|
||||||
panic("Error starting the main Syncthing process")
|
panic("Error starting the main Syncthing process")
|
||||||
}
|
}
|
||||||
var restarts [restartCounts]time.Time
|
var restarts [restartCounts]time.Time
|
||||||
@@ -103,7 +102,7 @@ func (c *serveCmd) monitorMain() {
|
|||||||
maybeReportPanics()
|
maybeReportPanics()
|
||||||
|
|
||||||
if t := time.Since(restarts[0]); t < restartLoopThreshold {
|
if t := time.Since(restarts[0]); t < restartLoopThreshold {
|
||||||
slog.Error("Too many restarts; not retrying further", slog.Int("count", restartCounts), slog.Any("interval", t))
|
l.Warnf("%d restarts in %v; not retrying further", restartCounts, t)
|
||||||
os.Exit(svcutil.ExitError.AsInt())
|
os.Exit(svcutil.ExitError.AsInt())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,10 +122,10 @@ func (c *serveCmd) monitorMain() {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
slog.Debug("Starting syncthing")
|
l.Debugln("Starting syncthing")
|
||||||
err = cmd.Start()
|
err = cmd.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to start the main Syncthing process", slogutil.Error(err))
|
l.Warnln("Error starting the main Syncthing process:", err)
|
||||||
panic("Error starting the main Syncthing process")
|
panic("Error starting the main Syncthing process")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,10 +134,19 @@ func (c *serveCmd) monitorMain() {
|
|||||||
stdoutLastLines = make([]string, 0, 50)
|
stdoutLastLines = make([]string, 0, 50)
|
||||||
stdoutMut.Unlock()
|
stdoutMut.Unlock()
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
wg := sync.NewWaitGroup()
|
||||||
|
|
||||||
wg.Go(func() { copyStderr(stderr, dst) })
|
wg.Add(1)
|
||||||
wg.Go(func() { copyStdout(stdout, dst) })
|
go func() {
|
||||||
|
copyStderr(stderr, dst)
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
copyStdout(stdout, dst)
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
|
||||||
exit := make(chan error)
|
exit := make(chan error)
|
||||||
|
|
||||||
@@ -150,13 +158,13 @@ func (c *serveCmd) monitorMain() {
|
|||||||
stopped := false
|
stopped := false
|
||||||
select {
|
select {
|
||||||
case s := <-stopSign:
|
case s := <-stopSign:
|
||||||
slog.Info("Received signal; exiting", "signal", s)
|
l.Infof("Signal %d received; exiting", s)
|
||||||
cmd.Process.Signal(sigTerm)
|
cmd.Process.Signal(sigTerm)
|
||||||
err = <-exit
|
err = <-exit
|
||||||
stopped = true
|
stopped = true
|
||||||
|
|
||||||
case s := <-restartSign:
|
case s := <-restartSign:
|
||||||
slog.Info("Received signal; restarting", "signal", s)
|
l.Infof("Signal %d received; restarting", s)
|
||||||
cmd.Process.Signal(sigHup)
|
cmd.Process.Signal(sigHup)
|
||||||
err = <-exit
|
err = <-exit
|
||||||
|
|
||||||
@@ -168,28 +176,27 @@ func (c *serveCmd) monitorMain() {
|
|||||||
os.Exit(svcutil.ExitSuccess.AsInt())
|
os.Exit(svcutil.ExitSuccess.AsInt())
|
||||||
}
|
}
|
||||||
|
|
||||||
exiterr := &exec.ExitError{}
|
if exiterr, ok := err.(*exec.ExitError); ok {
|
||||||
if errors.As(err, &exiterr) {
|
|
||||||
exitCode := exiterr.ExitCode()
|
exitCode := exiterr.ExitCode()
|
||||||
if stopped || c.NoRestart {
|
if stopped || options.NoRestart {
|
||||||
os.Exit(exitCode)
|
os.Exit(exitCode)
|
||||||
}
|
}
|
||||||
if exitCode == svcutil.ExitUpgrade.AsInt() {
|
if exitCode == svcutil.ExitUpgrade.AsInt() {
|
||||||
// Restart the monitor process to release the .old
|
// Restart the monitor process to release the .old
|
||||||
// binary as part of the upgrade process.
|
// binary as part of the upgrade process.
|
||||||
slog.Info("Restarting monitor...")
|
l.Infoln("Restarting monitor...")
|
||||||
if err = restartMonitor(binary, args); err != nil {
|
if err = restartMonitor(binary, args); err != nil {
|
||||||
slog.Error("Failed to restart monitor", slogutil.Error(err))
|
l.Warnln("Restart:", err)
|
||||||
}
|
}
|
||||||
os.Exit(exitCode)
|
os.Exit(exitCode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.NoRestart {
|
if options.NoRestart {
|
||||||
os.Exit(svcutil.ExitError.AsInt())
|
os.Exit(svcutil.ExitError.AsInt())
|
||||||
}
|
}
|
||||||
|
|
||||||
slog.Info("Syncthing exited", slogutil.Error(err))
|
l.Infoln("Syncthing exited:", err)
|
||||||
time.Sleep(restartPause)
|
time.Sleep(restartPause)
|
||||||
|
|
||||||
if first {
|
if first {
|
||||||
@@ -236,13 +243,29 @@ func copyStderr(stderr io.Reader, dst io.Writer) {
|
|||||||
if panicFd == nil && (strings.HasPrefix(line, "panic:") || strings.HasPrefix(line, "fatal error:")) {
|
if panicFd == nil && (strings.HasPrefix(line, "panic:") || strings.HasPrefix(line, "fatal error:")) {
|
||||||
panicFd, err = os.Create(locations.GetTimestamped(locations.PanicLog))
|
panicFd, err = os.Create(locations.GetTimestamped(locations.PanicLog))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to create panic log", slogutil.Error(err))
|
l.Warnln("Create panic log:", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
slog.Error("Panic detected, writing to file", slogutil.FilePath(panicFd.Name()))
|
l.Warnf("Panic detected, writing to \"%s\"", panicFd.Name())
|
||||||
slog.Info("Please check for existing issues with similar panic message at https://github.com/syncthing/syncthing/issues/")
|
if strings.Contains(line, "leveldb") && strings.Contains(line, "corrupt") {
|
||||||
slog.Info("If no issue with similar panic message exists, please create a new issue with the panic log attached")
|
l.Warnln(`
|
||||||
|
*********************************************************************************
|
||||||
|
* Crash due to corrupt database. *
|
||||||
|
* *
|
||||||
|
* This crash usually occurs due to one of the following reasons: *
|
||||||
|
* - Syncthing being stopped abruptly (killed/loss of power) *
|
||||||
|
* - Bad hardware (memory/disk issues) *
|
||||||
|
* - Software that affects disk writes (SSD caching software and similar) *
|
||||||
|
* *
|
||||||
|
* Please see the following URL for instructions on how to recover: *
|
||||||
|
* https://docs.syncthing.net/users/faq.html#my-syncthing-database-is-corrupt *
|
||||||
|
*********************************************************************************
|
||||||
|
`)
|
||||||
|
} else {
|
||||||
|
l.Warnln("Please check for existing issues with similar panic message at https://github.com/syncthing/syncthing/issues/")
|
||||||
|
l.Warnln("If no issue with similar panic message exists, please create a new issue with the panic log attached")
|
||||||
|
}
|
||||||
|
|
||||||
stdoutMut.Lock()
|
stdoutMut.Lock()
|
||||||
for _, line := range stdoutFirstLines {
|
for _, line := range stdoutFirstLines {
|
||||||
@@ -423,6 +446,7 @@ func newAutoclosedFile(name string, closeDelay, maxOpenTime time.Duration) (*aut
|
|||||||
name: name,
|
name: name,
|
||||||
closeDelay: closeDelay,
|
closeDelay: closeDelay,
|
||||||
maxOpenTime: maxOpenTime,
|
maxOpenTime: maxOpenTime,
|
||||||
|
mut: sync.NewMutex(),
|
||||||
closed: make(chan struct{}),
|
closed: make(chan struct{}),
|
||||||
closeTimer: time.NewTimer(time.Minute),
|
closeTimer: time.NewTimer(time.Minute),
|
||||||
}
|
}
|
||||||
@@ -530,7 +554,7 @@ func maybeReportPanics() {
|
|||||||
// Try to get a config to see if/where panics should be reported.
|
// Try to get a config to see if/where panics should be reported.
|
||||||
cfg, err := loadOrDefaultConfig()
|
cfg, err := loadOrDefaultConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Couldn't load config; not reporting crash")
|
l.Warnln("Couldn't load config; not reporting crash")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -550,7 +574,7 @@ func maybeReportPanics() {
|
|||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
case <-time.After(panicUploadNoticeWait):
|
case <-time.After(panicUploadNoticeWait):
|
||||||
slog.Warn("Uploading crash reports is taking a while, please wait")
|
l.Warnln("Uploading crash reports is taking a while, please wait...")
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|||||||
@@ -16,11 +16,7 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/syncthing/syncthing/lib/build"
|
|
||||||
"github.com/syncthing/syncthing/lib/locations"
|
|
||||||
"github.com/syncthing/syncthing/lib/osutil"
|
|
||||||
"github.com/syncthing/syncthing/lib/protocol"
|
"github.com/syncthing/syncthing/lib/protocol"
|
||||||
"golang.org/x/exp/constraints"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func startPerfStats() {
|
func startPerfStats() {
|
||||||
@@ -33,55 +29,37 @@ func savePerfStats(file string) {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var prevTime time.Time
|
var prevUsage int64
|
||||||
var curRus, prevRus syscall.Rusage
|
var prevTime int64
|
||||||
var curMem, prevMem runtime.MemStats
|
var rusage syscall.Rusage
|
||||||
|
var memstats runtime.MemStats
|
||||||
var prevIn, prevOut int64
|
var prevIn, prevOut int64
|
||||||
|
|
||||||
t0 := time.Now()
|
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 {
|
for t := range time.NewTicker(250 * time.Millisecond).C {
|
||||||
syscall.Getrusage(syscall.RUSAGE_SELF, &curRus)
|
if err := syscall.Getrusage(syscall.RUSAGE_SELF, &rusage); err != nil {
|
||||||
runtime.ReadMemStats(&curMem)
|
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
|
||||||
in, out := protocol.TotalInOut()
|
in, out := protocol.TotalInOut()
|
||||||
timeDiff := t.Sub(prevTime)
|
var inRate, outRate float64
|
||||||
|
if timeDiff > 0 {
|
||||||
rss := curRus.Maxrss
|
inRate = float64(in-prevIn) / (float64(timeDiff) / 1e9) // bytes per second
|
||||||
if build.IsDarwin {
|
outRate = float64(out-prevOut) / (float64(timeDiff) / 1e9) // bytes per second
|
||||||
rss /= 1024
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
|
||||||
rss,
|
|
||||||
rate(prevIn, in, timeDiff, 1e3),
|
|
||||||
rate(prevOut, out, timeDiff, 1e3),
|
|
||||||
osutil.DirSize(locations.Get(locations.Database))/1024,
|
|
||||||
)
|
|
||||||
|
|
||||||
prevTime = t
|
|
||||||
prevRus = curRus
|
|
||||||
prevMem = curMem
|
|
||||||
prevIn, prevOut = in, out
|
prevIn, prevOut = in, out
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func cpusec(r *syscall.Rusage) float64 {
|
runtime.ReadMemStats(&memstats)
|
||||||
return float64(r.Utime.Nano()+r.Stime.Nano()) / float64(time.Second)
|
|
||||||
}
|
|
||||||
|
|
||||||
type number interface {
|
startms := int(t.Sub(t0).Seconds() * 1000)
|
||||||
constraints.Float | constraints.Integer
|
|
||||||
}
|
|
||||||
|
|
||||||
func rate[T number](prev, cur T, d time.Duration, div float64) float64 {
|
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)
|
||||||
diff := cur - prev
|
}
|
||||||
rate := float64(diff) / d.Seconds() / div
|
|
||||||
return rate
|
|
||||||
}
|
}
|
||||||
|
|||||||
-14
@@ -32,17 +32,3 @@
|
|||||||
darwin: "20"
|
darwin: "20"
|
||||||
linux: "3.2"
|
linux: "3.2"
|
||||||
windows: "10.0"
|
windows: "10.0"
|
||||||
|
|
||||||
- runtime: go1.25
|
|
||||||
requirements:
|
|
||||||
# macOS 12 (Monterey) per https://tip.golang.org/doc/go1.25#darwin
|
|
||||||
darwin: "21"
|
|
||||||
linux: "3.2"
|
|
||||||
windows: "10.0"
|
|
||||||
|
|
||||||
- runtime: go1.26 # no changes from 1.25
|
|
||||||
requirements:
|
|
||||||
darwin: "21"
|
|
||||||
linux: "3.2"
|
|
||||||
windows: "10.0"
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
Name=Syncthing Web UI
|
Name=Syncthing Web UI
|
||||||
GenericName=File synchronization UI
|
GenericName=File synchronization UI
|
||||||
Comment=Opens Syncthing's Web UI in the default browser (Syncthing must already be started).
|
Comment=Opens Syncthing's Web UI in the default browser (Syncthing must already be started).
|
||||||
Exec=syncthing browser
|
Exec=syncthing --browser-only
|
||||||
Icon=syncthing
|
Icon=syncthing
|
||||||
Terminal=false
|
Terminal=false
|
||||||
Type=Application
|
Type=Application
|
||||||
|
|||||||
@@ -7,215 +7,22 @@ StartLimitBurst=4
|
|||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
User=%i
|
User=%i
|
||||||
Environment="STLOGFORMATTIMESTAMP="
|
ExecStart=/usr/bin/syncthing serve --no-browser --no-restart --logflags=0
|
||||||
Environment="STLOGFORMATLEVELSTRING=false"
|
|
||||||
Environment="STLOGFORMATLEVELSYSLOG=true"
|
|
||||||
ExecStart=/usr/bin/syncthing serve --no-browser --no-restart
|
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
RestartSec=1
|
RestartSec=1
|
||||||
SuccessExitStatus=3 4
|
SuccessExitStatus=3 4
|
||||||
RestartForceExitStatus=3 4
|
RestartForceExitStatus=3 4
|
||||||
|
|
||||||
#############
|
# Hardening
|
||||||
# SANDBOXING
|
|
||||||
#############
|
|
||||||
#
|
|
||||||
# This section contains best-effort sandboxing of syncthing. Such sandboxing is
|
|
||||||
# useful to reduce the blast damage of a syncthing exploit.
|
|
||||||
#
|
|
||||||
# The sandboxing is "best-effort" only because some of these options are ignored
|
|
||||||
# if your systemd or kernel are too old or configured in unusual ways. Systemd
|
|
||||||
# should (but may not) tell you in the journal logs if that's the case. See the
|
|
||||||
# logs (after starting the service) with:
|
|
||||||
#
|
|
||||||
# journalctl --boot --pager-end --unit syncthing@<user-you-used>.service
|
|
||||||
#
|
|
||||||
# See systemd's analysis of syncthing's sandbox with:
|
|
||||||
#
|
|
||||||
# systemd-analyze security syncthing@<user-you-used>.service
|
|
||||||
#
|
|
||||||
# Most of these sandboxing options are documented in `man systemd.exec`.
|
|
||||||
#
|
|
||||||
# NOTE: Some of these options _appear_ redundant with each other... but
|
|
||||||
# depending on the version and configs of systemd and the kernel, some of the
|
|
||||||
# "redundant" options may be non-functional while others still work.
|
|
||||||
# We recommend leaving the "redundant" options in place.
|
|
||||||
|
|
||||||
# Makes /usr, /boot, /efi and /etc read-only.
|
|
||||||
ProtectSystem=full
|
ProtectSystem=full
|
||||||
# Protect several system areas syncthing should not be touching.
|
PrivateTmp=true
|
||||||
ProtectKernelTunables=true
|
|
||||||
ProtectKernelModules=true
|
|
||||||
ProtectKernelLogs=true
|
|
||||||
ProtectControlGroups=true
|
|
||||||
ProtectHostname=true
|
|
||||||
ProtectClock=true
|
|
||||||
# No new privileges through SUID/SGID binaries
|
|
||||||
NoNewPrivileges=true
|
|
||||||
# Prevents *setting* SUID/SGID bits on files/dirs
|
|
||||||
RestrictSUIDSGID=true
|
|
||||||
# Prevent memory pages that are both writable and executable. This kills JIT
|
|
||||||
# compilers, but syncthing is precompiled.
|
|
||||||
MemoryDenyWriteExecute=true
|
|
||||||
# Prevents creation of unprivileged user namespaces which are a significant
|
|
||||||
# source of privilege escalation exploits.
|
|
||||||
#
|
|
||||||
# (In 2023, Google saw 44% of kernel exploits using unpriv. user namespaces.
|
|
||||||
# Source: https://ubuntu.com/blog/ubuntu-23-10-restricted-unprivileged-user-namespaces)
|
|
||||||
#
|
|
||||||
# The service can still be placed *inside* such user namespaces (and is, through
|
|
||||||
# other sandboxing options), it just can't create any itself.
|
|
||||||
RestrictNamespaces=true
|
|
||||||
# RT task scheduling can be abused for denial-of-service
|
|
||||||
RestrictRealtime=true
|
|
||||||
# NOTE: This option is poorly named. It doesn't _restrict_ the listed families,
|
|
||||||
# it _allows_ the listed families. Unlisted ones are restricted.
|
|
||||||
#
|
|
||||||
# Specifically, notice the absence of AF_PACKET (raw packets).
|
|
||||||
# AF_UNIX is needed to support binding to UNIX sockets.
|
|
||||||
# AF_NETLINK is needed to support hotplugging of network devices and because
|
|
||||||
# otherwise we see the following (non-fatal) error on startup:
|
|
||||||
#
|
|
||||||
# Failed to list network interfaces (error="route ip+net: netlinkrib:
|
|
||||||
# address family not supported by protocol" log.pkg=upnp)
|
|
||||||
#
|
|
||||||
# This option does NOT affect systemd socket passing using .socket units.
|
|
||||||
RestrictAddressFamilies=AF_INET AF_INET6 AF_NETLINK AF_UNIX
|
|
||||||
# The lifetime limit of (superuser) capabilities that syncthing can acquire.
|
|
||||||
# This option _restricts_ capabilities.
|
|
||||||
#
|
|
||||||
# NOTE: This is set to `CAP_CHOWN CAP_FOWNER` to avoid breaking users that have
|
|
||||||
# set `AmbientCapabilities=CAP_CHOWN CAP_FOWNER` to enable the `syncOwnership`
|
|
||||||
# option as described in:
|
|
||||||
# https://docs.syncthing.net/users/autostart.html#permissions
|
|
||||||
#
|
|
||||||
# If you do not use the `syncOwnership` option, you can set this to:
|
|
||||||
# CapabilityBoundingSet=
|
|
||||||
CapabilityBoundingSet=CAP_CHOWN CAP_FOWNER
|
|
||||||
# Start with empty (superuser) capabilities.
|
|
||||||
# This option _expands_ capabilities.
|
|
||||||
# AmbientCapabilities should equal CapabilityBoundingSet.
|
|
||||||
#
|
|
||||||
# NOTE: IFF you wish to use the `syncOwnership` option, you must set this to:
|
|
||||||
# AmbientCapabilities=CAP_CHOWN CAP_FOWNER
|
|
||||||
# in a systemd drop-in file. Be aware that this gives syncthing the ability to
|
|
||||||
# change or ignore file ownership across the entire operating system.
|
|
||||||
AmbientCapabilities=
|
|
||||||
# Disables `personality` system call; it can be used for privilege escalation.
|
|
||||||
LockPersonality=true
|
|
||||||
# Prevents circumvention of restrictions through the use of x86 syscalls on
|
|
||||||
# x86-64 systems.
|
|
||||||
SystemCallArchitectures=native
|
SystemCallArchitectures=native
|
||||||
# Clean up IPC objects after service stops.
|
MemoryDenyWriteExecute=true
|
||||||
RemoveIPC=true
|
NoNewPrivileges=true
|
||||||
# Create private namespace for System V IPC.
|
|
||||||
# NOTE: This does not apply to AF_UNIX sockets which are more commonly used.
|
|
||||||
PrivateIPC=true
|
|
||||||
# Completely isolated /tmp and /var/tmp
|
|
||||||
PrivateTmp=disconnected
|
|
||||||
# New /dev with safe virtual devices like /dev/null
|
|
||||||
PrivateDevices=true
|
|
||||||
# Allow access to devices explicitly listed with DeviceAllow and pseudo devices
|
|
||||||
# like /dev/null.
|
|
||||||
DevicePolicy=closed
|
|
||||||
# Creates a new PID namespace. /proc now contains only entries for processes
|
|
||||||
# in this PID namespace.
|
|
||||||
PrivatePIDs=true
|
|
||||||
# Make processes owned by other users hidden in /proc/
|
|
||||||
ProtectProc=invisible
|
|
||||||
# Prevent access to non-pid interfaces in /proc.
|
|
||||||
ProcSubset=pid
|
|
||||||
# System call allow-list. `@system-service` is a systemd-provided category that
|
|
||||||
# allows common syscalls needed for system services.
|
|
||||||
SystemCallFilter=@system-service
|
|
||||||
# Return EPERM when a disallowed syscall is made instead of killing the process.
|
|
||||||
SystemCallErrorNumber=EPERM
|
|
||||||
# Digits from left to right; disallow creation of files with:
|
|
||||||
# - special security-related bits like setuid/setgid
|
|
||||||
# - (no restrictions on file owner permissions)
|
|
||||||
# - group-writable access
|
|
||||||
# - world-readable access
|
|
||||||
# NOTE: The default value is 0022. We are only restricting special security bits
|
|
||||||
# and world-readable access.
|
|
||||||
# NOTE: Syncthing can still _explicitly_ change file permissions using `chmod`.
|
|
||||||
UMask=7027
|
|
||||||
# The default HOME folder for system users on Debian-like systems is
|
|
||||||
# /nonexistent, which should never exist.
|
|
||||||
# We prevent syncthing from accessing that folder it if was previously created
|
|
||||||
# through misconfiguration, or from creating it if it's (correctly) missing.
|
|
||||||
InaccessiblePaths=-/nonexistent
|
|
||||||
|
|
||||||
##################
|
# Elevated permissions to sync ownership (disabled by default),
|
||||||
# OPTIONAL CONFIG
|
# see https://docs.syncthing.net/advanced/folder-sync-ownership
|
||||||
##################
|
|
||||||
#
|
|
||||||
# Users that want to tweak this service file should add a systemd drop-in
|
|
||||||
# file to avoid changing the original file.
|
|
||||||
#
|
|
||||||
# Documentation describing drop-in files:
|
|
||||||
# https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html
|
|
||||||
#
|
|
||||||
# Example drop-in file location (assuming user "syncthing"):
|
|
||||||
# /etc/systemd/system/syncthing@syncthing.service.d/override.conf
|
|
||||||
#
|
|
||||||
## Elevated permissions to sync ownership (disabled by default),
|
|
||||||
## see https://docs.syncthing.net/advanced/folder-sync-ownership
|
|
||||||
##
|
|
||||||
## NOTE:
|
|
||||||
## - Use the same value for *both* of these options.
|
|
||||||
## - PrivateUsers=false must be set (false is the default, but you might have
|
|
||||||
## changed it to true in the "extra credit" section below).
|
|
||||||
#AmbientCapabilities=CAP_CHOWN CAP_FOWNER
|
#AmbientCapabilities=CAP_CHOWN CAP_FOWNER
|
||||||
#CapabilityBoundingSet=CAP_CHOWN CAP_FOWNER
|
|
||||||
|
|
||||||
#########################
|
|
||||||
# EXTRA CREDIT FOR USERS
|
|
||||||
#########################
|
|
||||||
#
|
|
||||||
# Users that want to harden their systems further should set the following
|
|
||||||
# properties. (Also through a systemd drop-in file; see comments above.)
|
|
||||||
#
|
|
||||||
## Makes all of / read-only *except*:
|
|
||||||
## - /dev/, /proc/ and /sys/ (see other Protect* options)
|
|
||||||
## - ReadWritePaths=
|
|
||||||
## - StateDirectory=, LogsDirectory= and similar
|
|
||||||
##
|
|
||||||
## This cannot be enabled by default because we don't know which folders you wish to
|
|
||||||
## share. If enabling this option, enable it along with ReadWritePaths=, e.g.:
|
|
||||||
## ReadWritePaths=/my/shared/dir1 /my/shared/dir2
|
|
||||||
#ProtectSystem=strict
|
|
||||||
#
|
|
||||||
## When enabled, sets up a new user namespace. Maps the "root" user and group as
|
|
||||||
## well as the unit's own user and group to themselves and everything else to
|
|
||||||
## the "nobody" user and group.
|
|
||||||
## This is useful to securely detach the user and group databases used by the
|
|
||||||
## unit from the rest of the system, and thus to create an effective sandbox
|
|
||||||
## environment.
|
|
||||||
#PrivateUsers=true
|
|
||||||
#
|
|
||||||
## Makes /home, /root and /run/user *invisible* while allowing BindPaths= and
|
|
||||||
## BindReadOnlyPaths= to "carve out" access to parts of those dirs.
|
|
||||||
## (Use 'true' instead of 'tmpfs' if you don't need to carve out anything.)
|
|
||||||
##
|
|
||||||
## "Invisible" is superior to read-only provided by ProtectSystem=strict because
|
|
||||||
## it prevents information disclosure of private user data in case of service
|
|
||||||
## compromise.
|
|
||||||
#ProtectHome=tmpfs
|
|
||||||
#
|
|
||||||
## Disallow execution of all binaries. ExecPaths= below carves out exceptions.
|
|
||||||
## Can't be enabled by default due to the External File Versioning feature:
|
|
||||||
## https://docs.syncthing.net/users/versioning.html#external-file-versioning
|
|
||||||
##
|
|
||||||
## If you do not use that feature, you can enable both NoExecPaths and
|
|
||||||
## ExecPaths.
|
|
||||||
## If you do use that featuer, you can still use these options; just add
|
|
||||||
## the paths to the binaries you invoke to ExecPaths so they can be executed.
|
|
||||||
#NoExecPaths=/
|
|
||||||
## Allow execution of syncthing and system shared libraries.
|
|
||||||
## NOTE: If you are seeing an error like
|
|
||||||
## "Failed to execute /some/path/to/syncthing: Permission denied", this is the
|
|
||||||
## option you need to update to use your non-standard install location.
|
|
||||||
#ExecPaths=/usr/bin/syncthing /usr/lib
|
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
|||||||
@@ -5,10 +5,7 @@ StartLimitIntervalSec=60
|
|||||||
StartLimitBurst=4
|
StartLimitBurst=4
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Environment="STLOGFORMATTIMESTAMP="
|
ExecStart=/usr/bin/syncthing serve --no-browser --no-restart --logflags=0
|
||||||
Environment="STLOGFORMATLEVELSTRING=false"
|
|
||||||
Environment="STLOGFORMATLEVELSYSLOG=true"
|
|
||||||
ExecStart=/usr/bin/syncthing serve --no-browser --no-restart
|
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
RestartSec=1
|
RestartSec=1
|
||||||
SuccessExitStatus=3 4
|
SuccessExitStatus=3 4
|
||||||
|
|||||||
@@ -18,4 +18,4 @@ env STNORESTART=yes
|
|||||||
respawn
|
respawn
|
||||||
|
|
||||||
# the syncthing command Upstart is to execute when it is started up
|
# the syncthing command Upstart is to execute when it is started up
|
||||||
exec $SYNCTHING_EXE --no-browser
|
exec $SYNCTHING_EXE -no-browser
|
||||||
|
|||||||
@@ -1,69 +1,72 @@
|
|||||||
module github.com/syncthing/syncthing
|
module github.com/syncthing/syncthing
|
||||||
|
|
||||||
go 1.25.0
|
go 1.23.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/AudriusButkevicius/recli v0.0.7
|
github.com/AudriusButkevicius/recli v0.0.7-0.20220911121932-d000ce8fbf0f
|
||||||
github.com/alecthomas/kong v1.15.0
|
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.1
|
||||||
github.com/aws/aws-sdk-go v1.55.8
|
github.com/alecthomas/kong v1.11.0
|
||||||
|
github.com/aws/aws-sdk-go v1.55.7
|
||||||
github.com/calmh/incontainer v1.0.0
|
github.com/calmh/incontainer v1.0.0
|
||||||
github.com/calmh/xdr v1.2.0
|
github.com/calmh/xdr v1.2.0
|
||||||
github.com/ccding/go-stun v0.1.5
|
github.com/ccding/go-stun v0.1.5
|
||||||
github.com/coreos/go-semver v0.3.1
|
github.com/chmduquesne/rollinghash v4.0.0+incompatible
|
||||||
github.com/d4l3k/messagediff v1.2.1
|
github.com/d4l3k/messagediff v1.2.1
|
||||||
github.com/getsentry/raven-go v0.2.0
|
github.com/getsentry/raven-go v0.2.0
|
||||||
github.com/go-ldap/ldap/v3 v3.4.13
|
github.com/go-ldap/ldap/v3 v3.4.11
|
||||||
github.com/gobwas/glob v0.2.3
|
github.com/gobwas/glob v0.2.3
|
||||||
github.com/gofrs/flock v0.13.0
|
github.com/gofrs/flock v0.12.1
|
||||||
|
github.com/greatroar/blobloom v0.8.0
|
||||||
github.com/hashicorp/golang-lru/v2 v2.0.7
|
github.com/hashicorp/golang-lru/v2 v2.0.7
|
||||||
github.com/jackpal/gateway v1.1.1
|
github.com/jackpal/gateway v1.0.16
|
||||||
github.com/jackpal/go-nat-pmp v1.0.2
|
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/julienschmidt/httprouter v1.3.0
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||||
github.com/maruel/panicparse/v2 v2.5.0
|
github.com/maruel/panicparse/v2 v2.5.0
|
||||||
github.com/mattn/go-sqlite3 v1.14.44
|
github.com/maxbrunsfeld/counterfeiter/v6 v6.11.2
|
||||||
github.com/maxmind/geoipupdate/v6 v6.1.0
|
github.com/maxmind/geoipupdate/v6 v6.1.0
|
||||||
github.com/miscreant/miscreant.go v0.0.0-20200214223636-26d376326b75
|
github.com/miscreant/miscreant.go v0.0.0-20200214223636-26d376326b75
|
||||||
github.com/oschwald/geoip2-golang v1.13.0
|
github.com/oschwald/geoip2-golang v1.11.0
|
||||||
github.com/pierrec/lz4/v4 v4.1.26
|
github.com/pierrec/lz4/v4 v4.1.22
|
||||||
github.com/prometheus/client_golang v1.23.2
|
github.com/prometheus/client_golang v1.22.0
|
||||||
github.com/puzpuzpuz/xsync/v3 v3.5.1
|
github.com/puzpuzpuz/xsync/v3 v3.5.1
|
||||||
github.com/quic-go/quic-go v0.59.0
|
github.com/quic-go/quic-go v0.52.0
|
||||||
github.com/rabbitmq/amqp091-go v1.11.0
|
github.com/rabbitmq/amqp091-go v1.10.0
|
||||||
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9
|
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9
|
||||||
github.com/shirou/gopsutil/v4 v4.26.4
|
github.com/shirou/gopsutil/v4 v4.25.4
|
||||||
github.com/syncthing/notify v0.0.0-20250528144937-c7027d4f7465
|
github.com/syncthing/notify v0.0.0-20250528144937-c7027d4f7465
|
||||||
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d
|
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d
|
||||||
github.com/thejerf/suture/v4 v4.0.6
|
github.com/thejerf/suture/v4 v4.0.6
|
||||||
github.com/urfave/cli v1.22.17
|
github.com/urfave/cli v1.22.16
|
||||||
github.com/vitrun/qart v0.0.0-20160531060029-bf64b92db6b0
|
github.com/vitrun/qart v0.0.0-20160531060029-bf64b92db6b0
|
||||||
github.com/willabides/kongplete v0.4.0
|
github.com/willabides/kongplete v0.4.0
|
||||||
github.com/wlynxg/anet v0.0.5
|
go.uber.org/automaxprocs v1.6.0
|
||||||
golang.org/x/crypto v0.51.0
|
golang.org/x/crypto v0.38.0
|
||||||
golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a
|
golang.org/x/net v0.40.0
|
||||||
golang.org/x/net v0.54.0
|
golang.org/x/sys v0.33.0
|
||||||
golang.org/x/sys v0.44.0
|
golang.org/x/text v0.25.0
|
||||||
golang.org/x/text v0.37.0
|
golang.org/x/time v0.11.0
|
||||||
golang.org/x/time v0.15.0
|
golang.org/x/tools v0.33.0
|
||||||
google.golang.org/protobuf v1.36.11
|
google.golang.org/protobuf v1.36.6
|
||||||
modernc.org/sqlite v1.50.0
|
sigs.k8s.io/yaml v1.4.0
|
||||||
sigs.k8s.io/yaml v1.6.0
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Azure/go-ntlmssp v0.1.0 // indirect
|
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 // indirect
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect
|
||||||
|
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||||
github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d // indirect
|
github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
|
github.com/coreos/go-semver v0.3.1 // indirect
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/ebitengine/purego v0.8.3 // indirect
|
||||||
github.com/ebitengine/purego v0.10.0 // indirect
|
|
||||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||||
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
|
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
|
||||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||||
|
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||||
github.com/golang/snappy v0.0.4 // indirect
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
github.com/google/pprof v0.0.0-20250423184734-337e5dd93bb4 // indirect
|
github.com/google/pprof v0.0.0-20250423184734-337e5dd93bb4 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
@@ -71,50 +74,29 @@ require (
|
|||||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||||
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect
|
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
|
||||||
github.com/maxbrunsfeld/counterfeiter/v6 v6.12.0 // indirect
|
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
github.com/ncruces/go-strftime v1.0.0 // indirect
|
|
||||||
github.com/nxadm/tail v1.4.11 // indirect
|
github.com/nxadm/tail v1.4.11 // indirect
|
||||||
|
github.com/onsi/ginkgo/v2 v2.23.4 // indirect
|
||||||
github.com/oschwald/maxminddb-golang v1.13.1 // indirect
|
github.com/oschwald/maxminddb-golang v1.13.1 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/posener/complete v1.2.3 // indirect
|
github.com/posener/complete v1.2.3 // indirect
|
||||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||||
github.com/prometheus/client_model v0.6.2 // indirect
|
github.com/prometheus/client_model v0.6.1 // indirect
|
||||||
github.com/prometheus/common v0.66.1 // indirect
|
github.com/prometheus/common v0.62.0 // indirect
|
||||||
github.com/prometheus/procfs v0.16.1 // 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/riywo/loginshell v0.0.0-20200815045211-7d26008be1ab // indirect
|
||||||
github.com/rogpeppe/go-internal v1.12.0 // indirect
|
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
github.com/stretchr/objx v0.5.2 // indirect
|
github.com/stretchr/objx v0.5.2 // indirect
|
||||||
github.com/stretchr/testify v1.11.1 // indirect
|
github.com/stretchr/testify v1.10.0 // indirect
|
||||||
github.com/tklauser/go-sysconf v0.3.16 // indirect
|
github.com/tklauser/go-sysconf v0.3.14 // indirect
|
||||||
github.com/tklauser/numcpus v0.11.0 // indirect
|
github.com/tklauser/numcpus v0.9.0 // indirect
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||||
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
go.uber.org/mock v0.5.2 // indirect
|
||||||
golang.org/x/mod v0.36.0 // indirect
|
golang.org/x/mod v0.24.0 // indirect
|
||||||
golang.org/x/sync v0.20.0 // indirect
|
golang.org/x/sync v0.14.0 // indirect
|
||||||
golang.org/x/telemetry v0.0.0-20260508192327-42602be52be6 // indirect
|
|
||||||
golang.org/x/tools v0.45.0 // indirect
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
modernc.org/libc v1.72.0 // indirect
|
|
||||||
modernc.org/mathutil v1.7.1 // indirect
|
|
||||||
modernc.org/memory v1.11.0 // indirect
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// https://github.com/gobwas/glob/pull/55
|
// https://github.com/gobwas/glob/pull/55
|
||||||
replace github.com/gobwas/glob v0.2.3 => github.com/calmh/glob v0.0.0-20220615080505-1d823af5017b
|
replace github.com/gobwas/glob v0.2.3 => github.com/calmh/glob v0.0.0-20220615080505-1d823af5017b
|
||||||
|
|
||||||
// https://github.com/jackpal/gateway/pull/49
|
|
||||||
replace github.com/jackpal/gateway v1.1.1 => github.com/marbens-arch/gateway v1.1.2-0.20260308173556-c567cc04e7d4
|
|
||||||
|
|
||||||
// https://github.com/mattn/go-sqlite3/pull/1338
|
|
||||||
replace github.com/mattn/go-sqlite3 v1.14.44 => github.com/calmh/go-sqlite3 v1.14.35-0.20260509063420-822b4765116d
|
|
||||||
|
|
||||||
tool (
|
|
||||||
github.com/calmh/xdr/cmd/genxdr
|
|
||||||
github.com/maxbrunsfeld/counterfeiter/v6
|
|
||||||
golang.org/x/tools/cmd/goimports
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -1,26 +1,34 @@
|
|||||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
github.com/AudriusButkevicius/recli v0.0.7-0.20220911121932-d000ce8fbf0f h1:GmH5lT+moM7PbAJFBq57nH9WJ+wRnBXr/tyaYWbSAx8=
|
||||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
github.com/AudriusButkevicius/recli v0.0.7-0.20220911121932-d000ce8fbf0f/go.mod h1:Nhfib1j/VFnLrXL9cHgA+/n2O6P5THuWelOnbfPNd78=
|
||||||
github.com/AudriusButkevicius/recli v0.0.7 h1:9zjbYlTupi+W5SJXm2cR2sV2mJAIg1sIfDcsW7hrkPM=
|
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 h1:Gt0j3wceWMwPmiazCa8MzMA0MfhmPIz0Qp0FJ6qcM0U=
|
||||||
github.com/AudriusButkevicius/recli v0.0.7/go.mod h1:Nhfib1j/VFnLrXL9cHgA+/n2O6P5THuWelOnbfPNd78=
|
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM=
|
||||||
github.com/Azure/go-ntlmssp v0.1.0 h1:DjFo6YtWzNqNvQdrwEyr/e4nhU3vRiwenz5QX7sFz+A=
|
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.9.0 h1:OVoM452qUFBrX+URdH3VpR299ma4kfom0yB0URYky9g=
|
||||||
github.com/Azure/go-ntlmssp v0.1.0/go.mod h1:NYqdhxd/8aAct/s4qSYZEerdPuH1liG2/X9DiVTbhpk=
|
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.9.0/go.mod h1:kUjrAo8bgEwLeZ/CmHqNl3Z/kPm7y6FKfxxK0izYUg4=
|
||||||
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 h1:FPKJS1T+clwv+OLGt13a8UjqeRuh0O4SJ3lUriThc+4=
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA=
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.8.0 h1:LR0kAX9ykz8G4YgLCaRDVJ3+n43R8MneB5dTy2konZo=
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.8.0/go.mod h1:DWAciXemNf++PQJLeXUB4HHH5OpsAh12HZnu2wXE1jA=
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.1 h1:lhZdRq7TIx0GJQvSyX2Si406vrYsov2FXGp/RnSEtcs=
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.1/go.mod h1:8cl44BDmi+effbARHMQjgOKA2AYvcohNm7KEt42mSV8=
|
||||||
|
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8=
|
||||||
|
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
|
||||||
|
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs=
|
||||||
|
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
|
||||||
|
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||||
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
|
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
|
||||||
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
|
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
|
||||||
github.com/alecthomas/kong v1.15.0 h1:BVJstKbpO73zKpmIu+m/aLRrNmWwxXPIGTNin9VmLVI=
|
github.com/alecthomas/kong v1.11.0 h1:y++1gI7jf8O7G7l4LZo5ASFhrhJvzc+WgF/arranEmM=
|
||||||
github.com/alecthomas/kong v1.15.0/go.mod h1:wrlbXem1CWqUV5Vbmss5ISYhsVPkBb1Yo7YKJghju2I=
|
github.com/alecthomas/kong v1.11.0/go.mod h1:p2vqieVMeTAnaC83txKtXe8FLke2X07aruPWXyMPQrU=
|
||||||
github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs=
|
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
|
||||||
github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||||
github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e h1:4dAU9FXIyQktpoUAgOJK3OTFc/xug0PCXYCqU0FgDKI=
|
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI=
|
||||||
github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
|
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
|
||||||
github.com/aws/aws-sdk-go v1.55.8 h1:JRmEUbU52aJQZ2AjX4q4Wu7t4uZjOu71uyNmaWlUkJQ=
|
github.com/aws/aws-sdk-go v1.55.7 h1:UJrkFq7es5CShfBwlWAC8DA077vp8PyVbQd3lqLiztE=
|
||||||
github.com/aws/aws-sdk-go v1.55.8/go.mod h1:ZkViS9AqA6otK+JBBNH2++sx1sgxrPKcSzPPvQkUtXk=
|
github.com/aws/aws-sdk-go v1.55.7/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
|
||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
github.com/calmh/glob v0.0.0-20220615080505-1d823af5017b h1:Fjm4GuJ+TGMgqfGHN42IQArJb77CfD/mAwLbDUoJe6g=
|
github.com/calmh/glob v0.0.0-20220615080505-1d823af5017b h1:Fjm4GuJ+TGMgqfGHN42IQArJb77CfD/mAwLbDUoJe6g=
|
||||||
github.com/calmh/glob v0.0.0-20220615080505-1d823af5017b/go.mod h1:91K7jfEsgJSyfSrX+gmrRfZMtntx6JsHolWubGXDopg=
|
github.com/calmh/glob v0.0.0-20220615080505-1d823af5017b/go.mod h1:91K7jfEsgJSyfSrX+gmrRfZMtntx6JsHolWubGXDopg=
|
||||||
github.com/calmh/go-sqlite3 v1.14.35-0.20260509063420-822b4765116d h1:liVtRMlDBqDyR4qKkMRIFTdUK6+aoG6Oh97DKnhrCHc=
|
|
||||||
github.com/calmh/go-sqlite3 v1.14.35-0.20260509063420-822b4765116d/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ=
|
|
||||||
github.com/calmh/incontainer v1.0.0 h1:g2cTUtZuFGmMGX8GoykPkN1Judj2uw8/3/aEtq4Z/rg=
|
github.com/calmh/incontainer v1.0.0 h1:g2cTUtZuFGmMGX8GoykPkN1Judj2uw8/3/aEtq4Z/rg=
|
||||||
github.com/calmh/incontainer v1.0.0/go.mod h1:eOhqnw15c9X+4RNBe0W3HlUZFfX16O0EDsCOInTndHY=
|
github.com/calmh/incontainer v1.0.0/go.mod h1:eOhqnw15c9X+4RNBe0W3HlUZFfX16O0EDsCOInTndHY=
|
||||||
github.com/calmh/xdr v1.2.0 h1:GaGSNH4ZDw9kNdYqle6+RcAENiaQ8/611Ok+jQbBEeU=
|
github.com/calmh/xdr v1.2.0 h1:GaGSNH4ZDw9kNdYqle6+RcAENiaQ8/611Ok+jQbBEeU=
|
||||||
@@ -33,22 +41,22 @@ 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/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 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
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/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/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
|
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
|
||||||
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
|
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
|
github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
github.com/d4l3k/messagediff v1.2.1 h1:ZcAIMYsUg0EAp9X+tt8/enBE/Q8Yd5kzPynLyKptt9U=
|
github.com/d4l3k/messagediff v1.2.1 h1:ZcAIMYsUg0EAp9X+tt8/enBE/Q8Yd5kzPynLyKptt9U=
|
||||||
github.com/d4l3k/messagediff v1.2.1/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo=
|
github.com/d4l3k/messagediff v1.2.1/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
github.com/ebitengine/purego v0.8.3 h1:K+0AjQp63JEZTEMZiwsI9g0+hAMNohwUOtY0RPGexmc=
|
||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
github.com/ebitengine/purego v0.8.3/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||||
github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU=
|
|
||||||
github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.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.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||||
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
|
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
|
||||||
@@ -59,16 +67,20 @@ github.com/getsentry/raven-go v0.2.0 h1:no+xWJRb5ZI7eE8TWgIq1jLulQiIoLG0IfYxv5JY
|
|||||||
github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
|
github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
|
||||||
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=
|
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=
|
||||||
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||||
github.com/go-ldap/ldap/v3 v3.4.13 h1:+x1nG9h+MZN7h/lUi5Q3UZ0fJ1GyDQYbPvbuH38baDQ=
|
github.com/go-ldap/ldap/v3 v3.4.11 h1:4k0Yxweg+a3OyBLjdYn5OKglv18JNvfDykSoI8bW0gU=
|
||||||
github.com/go-ldap/ldap/v3 v3.4.13/go.mod h1:LxsGZV6vbaK0sIvYfsv47rfh4ca0JXokCoKjZxsszv0=
|
github.com/go-ldap/ldap/v3 v3.4.11/go.mod h1:bY7t0FLK8OAVpp/vV6sSlpz3EQDGcQwc8pF0ujLgKvM=
|
||||||
|
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||||
|
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
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 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
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 v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||||
github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw=
|
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||||
github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0=
|
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||||
|
github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E=
|
||||||
|
github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0=
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.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/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||||
@@ -84,6 +96,7 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
|
|||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
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.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/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
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 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
@@ -92,6 +105,8 @@ github.com/google/pprof v0.0.0-20250423184734-337e5dd93bb4 h1:gD0vax+4I+mAj+jECh
|
|||||||
github.com/google/pprof v0.0.0-20250423184734-337e5dd93bb4/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA=
|
github.com/google/pprof v0.0.0-20250423184734-337e5dd93bb4/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
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.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 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
@@ -106,6 +121,8 @@ github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUq
|
|||||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
|
github.com/jackpal/gateway v1.0.16 h1:mTBRuHSW8qviVqX7kXnxKevqlfS/OA01ys6k6fxSX7w=
|
||||||
|
github.com/jackpal/gateway v1.0.16/go.mod h1:IOn1OUbso/cGYmnCBZbCEqhNCLSz0xxdtIpUpri5/nA=
|
||||||
github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus=
|
github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus=
|
||||||
github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
|
github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
|
||||||
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
|
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
|
||||||
@@ -124,8 +141,6 @@ 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 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 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
||||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
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 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
|
||||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||||
@@ -138,27 +153,18 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
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 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
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 h1:7UMa6KCCMjZEMDtTVdcGu0B1GmmC7QJKiCCjyTAWQy0=
|
||||||
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
|
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
|
||||||
github.com/marbens-arch/gateway v1.1.2-0.20260308173556-c567cc04e7d4 h1:8TAc8a26Pp0+8I6rj43BqhMi84D1zvtY0sPzO3IKnBw=
|
|
||||||
github.com/marbens-arch/gateway v1.1.2-0.20260308173556-c567cc04e7d4/go.mod h1:Tl1vZVtUaXx5j6P5HFmv45alhEi4yHHLfT4PRbB7eyw=
|
|
||||||
github.com/maruel/panicparse/v2 v2.5.0 h1:yCtuS0FWjfd0RTYMXGpDvWcb0kINm8xJGu18/xMUh00=
|
github.com/maruel/panicparse/v2 v2.5.0 h1:yCtuS0FWjfd0RTYMXGpDvWcb0kINm8xJGu18/xMUh00=
|
||||||
github.com/maruel/panicparse/v2 v2.5.0/go.mod h1:DA2fDiBk63bKfBf4CVZP9gb4fuvzdPbLDsSI873hweQ=
|
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/maxbrunsfeld/counterfeiter/v6 v6.11.2 h1:yVCLo4+ACVroOEr4iFU1iH46Ldlzz2rTuu18Ra7M8sU=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/maxbrunsfeld/counterfeiter/v6 v6.11.2/go.mod h1:VzB2VoMh1Y32/QqDfg9ZJYHj99oM4LiGtqPZydTiQSQ=
|
||||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
|
||||||
github.com/maxbrunsfeld/counterfeiter/v6 v6.12.0 h1:aOeI7xAOVdK+R6xbVsZuU9HmCZYmQVmZgPf9xJUd2Sg=
|
|
||||||
github.com/maxbrunsfeld/counterfeiter/v6 v6.12.0/go.mod h1:0hZWbtfeCYUQeAQdPLUzETiBhUSns7O6LDj9vH88xKA=
|
|
||||||
github.com/maxmind/geoipupdate/v6 v6.1.0 h1:sdtTHzzQNJlXF5+fd/EoPTucRHyMonYt/Cok8xzzfqA=
|
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/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 h1:cUVxyR+UfmdEAZGJ8IiKld1O0dbGotEnkMolG5hfMSY=
|
||||||
github.com/miscreant/miscreant.go v0.0.0-20200214223636-26d376326b75/go.mod h1:pBbZyGwC5i16IBkjVKoy/sznA8jPD/K9iedwe1ESE6w=
|
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 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
|
||||||
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
|
||||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
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.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||||
github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY=
|
github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY=
|
||||||
@@ -169,18 +175,22 @@ github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vv
|
|||||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||||
github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
|
github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
|
||||||
|
github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus=
|
||||||
|
github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8=
|
||||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
github.com/onsi/gomega v1.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.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||||
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
|
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
|
||||||
github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
|
github.com/onsi/gomega v1.36.3 h1:hID7cr8t3Wp26+cYnfcjR6HpJ00fdogN6dqZ1t6IylU=
|
||||||
github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
|
github.com/onsi/gomega v1.36.3/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
|
||||||
github.com/oschwald/geoip2-golang v1.13.0 h1:Q44/Ldc703pasJeP5V9+aFSZFmBN7DKHbNsSFzQATJI=
|
github.com/oschwald/geoip2-golang v1.11.0 h1:hNENhCn1Uyzhf9PTmquXENiWS6AlxAEnBII6r8krA3w=
|
||||||
github.com/oschwald/geoip2-golang v1.13.0/go.mod h1:P9zG+54KPEFOliZ29i7SeYZ/GM6tfEL+rgSn03hYuUo=
|
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 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE=
|
||||||
github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8=
|
github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8=
|
||||||
github.com/pierrec/lz4/v4 v4.1.26 h1:GrpZw1gZttORinvzBdXPUXATeqlJjqUG/D87TKMnhjY=
|
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
|
||||||
github.com/pierrec/lz4/v4 v4.1.26/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
|
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.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
@@ -190,24 +200,24 @@ github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXq
|
|||||||
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
|
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
|
||||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
|
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
|
||||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||||
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
|
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||||
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
|
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
||||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
|
||||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
|
||||||
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
|
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||||
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
|
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||||
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
|
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
|
||||||
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
|
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.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg=
|
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/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||||
github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=
|
github.com/quic-go/quic-go v0.52.0 h1:/SlHrCRElyaU6MaEPKqKr9z83sBg2v4FLLvWM+Z47pA=
|
||||||
github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
|
github.com/quic-go/quic-go v0.52.0/go.mod h1:MFlGGpcpJqRAfmYi6NC2cptDPSxRWTOGNuP4wqrWmzQ=
|
||||||
github.com/rabbitmq/amqp091-go v1.11.0 h1:HxIctVm9Gid/Vtn706necmZ7Wj6pgGI2eqplRbEY8O8=
|
github.com/rabbitmq/amqp091-go v1.10.0 h1:STpn5XsHlHGcecLmMFCtg7mqq0RnD+zFr4uzukfVhBw=
|
||||||
github.com/rabbitmq/amqp091-go v1.11.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o=
|
github.com/rabbitmq/amqp091-go v1.10.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o=
|
||||||
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg=
|
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/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 h1:ZjX6I48eZSFetPb41dHudEyVr5v953N15TsNZXlkcWY=
|
||||||
github.com/riywo/loginshell v0.0.0-20200815045211-7d26008be1ab/go.mod h1:/PfPXh0EntGc3QAAyUaviy4S9tzy4Zp0e2ilq4voC6E=
|
github.com/riywo/loginshell v0.0.0-20200815045211-7d26008be1ab/go.mod h1:/PfPXh0EntGc3QAAyUaviy4S9tzy4Zp0e2ilq4voC6E=
|
||||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||||
@@ -216,8 +226,8 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf
|
|||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
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 h1:z/Q9idDcay5m5irkZ28M7PtQM4aOISzOpj4bUPkDee8=
|
||||||
github.com/sclevine/spec v1.4.0/go.mod h1:LvpgJaFyvQzRvc1kaDs0bulYwzC70PbiYjC4QnFHkOM=
|
github.com/sclevine/spec v1.4.0/go.mod h1:LvpgJaFyvQzRvc1kaDs0bulYwzC70PbiYjC4QnFHkOM=
|
||||||
github.com/shirou/gopsutil/v4 v4.26.4 h1:B4SXVbcwTyrocPHEmWBC4uCYr4Xcu3MK1TXqbprAOWY=
|
github.com/shirou/gopsutil/v4 v4.25.4 h1:cdtFO363VEOOFrUCjZRh4XVJkb548lyF0q0uTeMqYPw=
|
||||||
github.com/shirou/gopsutil/v4 v4.26.4/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ=
|
github.com/shirou/gopsutil/v4 v4.25.4/go.mod h1:xbuxyoZj+UsgnZrENu3lQivsngRR5BdjbJwf2fv4szA=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
@@ -229,49 +239,43 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
|||||||
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
|
||||||
github.com/syncthing/notify v0.0.0-20250528144937-c7027d4f7465 h1:yhxdTGmFkAM2TFA65c3NgGwpnIkUM8oVqPX2e9S7IVg=
|
github.com/syncthing/notify v0.0.0-20250528144937-c7027d4f7465 h1:yhxdTGmFkAM2TFA65c3NgGwpnIkUM8oVqPX2e9S7IVg=
|
||||||
github.com/syncthing/notify v0.0.0-20250528144937-c7027d4f7465/go.mod h1:J0q59IWjLtpRIJulohwqEZvjzwOfTEPp8SVhDJl+y0Y=
|
github.com/syncthing/notify v0.0.0-20250528144937-c7027d4f7465/go.mod h1:J0q59IWjLtpRIJulohwqEZvjzwOfTEPp8SVhDJl+y0Y=
|
||||||
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDdvS342BElfbETmL1Aiz3i2t0zfRj16Hs=
|
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDdvS342BElfbETmL1Aiz3i2t0zfRj16Hs=
|
||||||
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48=
|
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=
|
github.com/thejerf/suture/v4 v4.0.6 h1:QsuCEsCqb03xF9tPAsWAj8QOAJBgQI1c0VqJNaingg8=
|
||||||
github.com/thejerf/suture/v4 v4.0.6/go.mod h1:gu9Y4dXNUWFrByqRt30Rm9/UZ0wzRSt9AJS6xu/ZGxU=
|
github.com/thejerf/suture/v4 v4.0.6/go.mod h1:gu9Y4dXNUWFrByqRt30Rm9/UZ0wzRSt9AJS6xu/ZGxU=
|
||||||
github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA=
|
github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
|
||||||
github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI=
|
github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
|
||||||
github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw=
|
github.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo=
|
||||||
github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=
|
github.com/tklauser/numcpus v0.9.0/go.mod h1:SN6Nq1O3VychhC1npsWostA+oW+VOQTxZrS604NSRyI=
|
||||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||||
github.com/urfave/cli v1.22.17 h1:SYzXoiPfQjHBbkYxbew5prZHS1TOLT3ierW8SYLqtVQ=
|
github.com/urfave/cli v1.22.16 h1:MH0k6uJxdwdeWQTwhSO42Pwr4YLrNLwBtg1MRgTqPdQ=
|
||||||
github.com/urfave/cli v1.22.17/go.mod h1:b0ht0aqgH/6pBYzzxURyrM4xXNgsoT/n2ZzwQiEhNVo=
|
github.com/urfave/cli v1.22.16/go.mod h1:EeJR6BKodywf4zciqrdw6hpCPk68JO9z5LazXZMn5Po=
|
||||||
github.com/vitrun/qart v0.0.0-20160531060029-bf64b92db6b0 h1:okhMind4q9H1OxF44gNegWkiP4H/gsTFLalHFa4OOUI=
|
github.com/vitrun/qart v0.0.0-20160531060029-bf64b92db6b0 h1:okhMind4q9H1OxF44gNegWkiP4H/gsTFLalHFa4OOUI=
|
||||||
github.com/vitrun/qart v0.0.0-20160531060029-bf64b92db6b0/go.mod h1:TTbGUfE+cXXceWtbTHq6lqcTvYPBKLNejBEbnUsQJtU=
|
github.com/vitrun/qart v0.0.0-20160531060029-bf64b92db6b0/go.mod h1:TTbGUfE+cXXceWtbTHq6lqcTvYPBKLNejBEbnUsQJtU=
|
||||||
github.com/willabides/kongplete v0.4.0 h1:eivXxkp5ud5+4+NVN9e4goxC5mSh3n1RHov+gsblM2g=
|
github.com/willabides/kongplete v0.4.0 h1:eivXxkp5ud5+4+NVN9e4goxC5mSh3n1RHov+gsblM2g=
|
||||||
github.com/willabides/kongplete v0.4.0/go.mod h1:0P0jtWD9aTsqPSUAl4de35DLghrr57XcayPyvqSi2X8=
|
github.com/willabides/kongplete v0.4.0/go.mod h1:0P0jtWD9aTsqPSUAl4de35DLghrr57XcayPyvqSi2X8=
|
||||||
github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
|
|
||||||
github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
|
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||||
|
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
|
||||||
|
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
|
||||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
|
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
|
||||||
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
|
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
|
||||||
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
|
|
||||||
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
|
|
||||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
|
||||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
|
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||||
golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
|
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
||||||
golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a h1:+3jdDGGB8NGb1Zktc737jlt3/A5f6UlwSzmvqUuufxw=
|
|
||||||
golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a/go.mod h1:d2fgXJLVs4dYDHUk5lwMIfzRzSrWCfGZb0ZqeLa/Vcw=
|
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=
|
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
|
||||||
golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=
|
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-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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
@@ -280,13 +284,13 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
|
|||||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w=
|
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
|
||||||
golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ=
|
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-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-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
|
||||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-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-20180926160741-c2ed4eda69e7/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
@@ -308,26 +312,23 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.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.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||||
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
|
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
|
||||||
golang.org/x/telemetry v0.0.0-20260508192327-42602be52be6 h1:HjU6IWBiAgRIdAJ9/y1rwCn+UELEmwV+VsTLzj/W4sE=
|
|
||||||
golang.org/x/telemetry v0.0.0-20260508192327-42602be52be6/go.mod h1:Eqhaxk/wZsWEH8CRxLwj6xzEJbz7k1EFGqx7nyCoabE=
|
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||||
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
||||||
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
|
||||||
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
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-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-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8=
|
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
|
||||||
golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0=
|
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-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-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
@@ -341,8 +342,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.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-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.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
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 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 h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
@@ -358,33 +359,5 @@ 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.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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
modernc.org/cc/v4 v4.27.3 h1:uNCgn37E5U09mTv1XgskEVUJ8ADKpmFMPxzGJ0TSo+U=
|
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
|
||||||
modernc.org/cc/v4 v4.27.3/go.mod h1:3YjcbCqhoTTHPycJDRl2WZKKFj0nwcOIPBfEZK0Hdk8=
|
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
|
||||||
modernc.org/ccgo/v4 v4.32.4 h1:L5OB8rpEX4ZsXEQwGozRfJyJSFHbbNVOoQ59DU9/KuU=
|
|
||||||
modernc.org/ccgo/v4 v4.32.4/go.mod h1:lY7f+fiTDHfcv6YlRgSkxYfhs+UvOEEzj49jAn2TOx0=
|
|
||||||
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
|
|
||||||
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
|
|
||||||
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
|
||||||
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
|
||||||
modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo=
|
|
||||||
modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
|
||||||
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
|
||||||
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
|
||||||
modernc.org/libc v1.72.0 h1:IEu559v9a0XWjw0DPoVKtXpO2qt5NVLAnFaBbjq+n8c=
|
|
||||||
modernc.org/libc v1.72.0/go.mod h1:tTU8DL8A+XLVkEY3x5E/tO7s2Q/q42EtnNWda/L5QhQ=
|
|
||||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
|
||||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
|
||||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
|
||||||
modernc.org/memory v1.11.0/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.50.0 h1:eMowQSWLK0MeiQTdmz3lqoF5dqclujdlIKeJA11+7oM=
|
|
||||||
modernc.org/sqlite v1.50.0/go.mod h1:m0w8xhwYUVY3H6pSDwc3gkJ/irZT/0YEXwBlhaxQEew=
|
|
||||||
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.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
|
|
||||||
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
|
|
||||||
|
|||||||
@@ -221,8 +221,8 @@ li[language-select] > .dropdown-menu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
li[language-select] > .dropdown-menu > li > a {
|
li[language-select] > .dropdown-menu > li > a {
|
||||||
white-space: normal;
|
overflow: hidden;
|
||||||
overflow-wrap: break-word;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav>li{
|
.nav>li{
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"A device with that ID is already added.": "أُضيف هذا الجهاز بالفعل.",
|
"A device with that ID is already added.": "أضيف هذا الجهاز بالفعل.",
|
||||||
"A negative number of days doesn't make sense.": "لا يمكن استخدام قيمة سالبة لعدد الأيام.",
|
"A negative number of days doesn't make sense.": "لا يمكن استخدام قيمة سالبة لعدد الأيام.",
|
||||||
"A new major version may not be compatible with previous versions.": "الإصدار الجديد قد لا يتوافق مع الإصدارات السابقة.",
|
"A new major version may not be compatible with previous versions.": "الإصدار الجديد قد لا يتوافق مع الإصدارات السابقة.",
|
||||||
"API Key": "مفتاح واجهة برمجة التطبيقات \"API\"",
|
"API Key": "مفتاح واجهة برمجة التطبيقات \"API\"",
|
||||||
@@ -7,12 +7,12 @@
|
|||||||
"Action": "إجراء",
|
"Action": "إجراء",
|
||||||
"Actions": "الإجراءات",
|
"Actions": "الإجراءات",
|
||||||
"Active filter rules": "قواعد التصفية النشطة",
|
"Active filter rules": "قواعد التصفية النشطة",
|
||||||
"Add": "أضف",
|
"Add": "إضافة",
|
||||||
"Add Device": "أضف جهاز",
|
"Add Device": "إضافة جهاز",
|
||||||
"Add Folder": "أضف مجلد",
|
"Add Folder": "إضافة مجلد",
|
||||||
"Add Remote Device": "أضف جهاز بعيد",
|
"Add Remote Device": "إضافة جهاز بعيد",
|
||||||
"Add devices from the introducer to our device list, for mutually shared folders.": "أضف أجهزة من الوسيط إلى قائمة الأجهزة الخاصة بنا، للمجلدات المشتركة بشكل متبادل.",
|
"Add devices from the introducer to our device list, for mutually shared folders.": "أضف أجهزة من الوسيط إلى قائمة الأجهزة الخاصة بنا، للمجلدات المشتركة بشكل متبادل.",
|
||||||
"Add filter entry": "أضف عامل التصفية",
|
"Add filter entry": "إضافة عامل التصفية",
|
||||||
"Add ignore patterns": "أضف أنماط التجاهل",
|
"Add ignore patterns": "أضف أنماط التجاهل",
|
||||||
"Add new folder?": "إضافة مجلد جديد؟",
|
"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 مرة، وهو الافتراضي الجديد من 1H). يمكنك أيضًا التحكم بالإعدادات وتعديلها يدويًا لكل مجلد لاحقًا بعد اختيار \"لا\".",
|
"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 مرة، وهو الافتراضي الجديد من 1H). يمكنك أيضًا التحكم بالإعدادات وتعديلها يدويًا لكل مجلد لاحقًا بعد اختيار \"لا\".",
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
"Are you sure you want to override all remote changes?": "هل أنت متأكد أنك تريد تجاوز كافة التغييرات عن بُعد؟",
|
"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 permanently delete all these files?": "هل أنت متأكد أنك تريد حذف كل هذه الملفات بشكل دائم؟",
|
||||||
"Are you sure you want to remove device {%name%}?": "هل أنت متيقِّن من حذف هذا الجهاز {{name}}؟",
|
"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 remove folder {%label%}?": "هل انت متاكد من حذف المجلد {{label}}؟",
|
||||||
"Are you sure you want to restore {%count%} files?": "هل انت متاكد من استعادة {{count}} ملف؟",
|
"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 revert all local changes?": "هل أنت متأكد أنك تريد التراجع عن كافة التغييرات المحلية؟",
|
||||||
"Are you sure you want to upgrade?": "هل أنت متأكد أنك تريد الترقية؟",
|
"Are you sure you want to upgrade?": "هل أنت متأكد أنك تريد الترقية؟",
|
||||||
@@ -50,12 +50,11 @@
|
|||||||
"Automatically create or share folders that this device advertises at the default path.": "أنشئ وشارك المجلدات الموجودة في المسار الافتراضي تلقائيا.",
|
"Automatically create or share folders that this device advertises at the default path.": "أنشئ وشارك المجلدات الموجودة في المسار الافتراضي تلقائيا.",
|
||||||
"Available debug logging facilities:": "خدمات سجلات تدقيق البرمجيات المتوفرة:",
|
"Available debug logging facilities:": "خدمات سجلات تدقيق البرمجيات المتوفرة:",
|
||||||
"Be careful!": "احذر!",
|
"Be careful!": "احذر!",
|
||||||
"Block Indexing": "فهرسة الكُتل",
|
|
||||||
"Body:": "جسم:",
|
"Body:": "جسم:",
|
||||||
"Bugs": "أخطاء برمجية",
|
"Bugs": "أخطاء برمجية",
|
||||||
"Cancel": "إلغاء",
|
"Cancel": "إلغاء",
|
||||||
"Cannot be enabled when the folder type is \"{%foldertype%}\".": "لا يمكن تفعيله عندما يكون نوع المجلد هو \"{{foldertype}}\".",
|
"Cannot be enabled when the folder type is \"{%foldertype%}\".": "لا يمكن تفعيله عندما يكون نوع المجلد هو \"{{foldertype}}\".",
|
||||||
"Changelog": "سجل التغييرات",
|
"Changelog": "سجل التغيير",
|
||||||
"Clean out after": "نظف بعد",
|
"Clean out after": "نظف بعد",
|
||||||
"Cleaning Versions": "إصدارات نظيفة",
|
"Cleaning Versions": "إصدارات نظيفة",
|
||||||
"Cleanup Interval": "الفاصل الزمني للتنظيف",
|
"Cleanup Interval": "الفاصل الزمني للتنظيف",
|
||||||
@@ -77,13 +76,12 @@
|
|||||||
"Copied from elsewhere": "منسوخ من مكان أخر",
|
"Copied from elsewhere": "منسوخ من مكان أخر",
|
||||||
"Copied from original": "منسوخ من الأصل",
|
"Copied from original": "منسوخ من الأصل",
|
||||||
"Copied!": "نُسِخَ!",
|
"Copied!": "نُسِخَ!",
|
||||||
"Copy": "انسخ",
|
"Copy": "نسخ",
|
||||||
"Copy failed! Try to select and copy manually.": "فشل النسخ! حاول التحديد والنسخ يدويًا.",
|
"Copy failed! Try to select and copy manually.": "فشل النسخ! حاول التحديد والنسخ يدويًا.",
|
||||||
"Currently Shared With Devices": "مُشارَك مع الأجهزة حاليا",
|
"Currently Shared With Devices": "مُشارَك مع الأجهزة حاليا",
|
||||||
"Custom Range": "نطاق مخصص",
|
"Custom Range": "نطاق مخصص",
|
||||||
"Danger!": "خطر!",
|
"Danger!": "خطر!",
|
||||||
"Database Location": "موقع قاعدة البيانات",
|
"Database Location": "موقع قاعدة البيانات",
|
||||||
"Debug": "تصحيح العلل",
|
|
||||||
"Debugging Facilities": "خدمات تدقيق البرمجيات",
|
"Debugging Facilities": "خدمات تدقيق البرمجيات",
|
||||||
"Default": "أفتراضي",
|
"Default": "أفتراضي",
|
||||||
"Default Configuration": "اعدادات افتراضية",
|
"Default Configuration": "اعدادات افتراضية",
|
||||||
@@ -91,16 +89,15 @@
|
|||||||
"Default Folder": "المجلد الافتراضي",
|
"Default Folder": "المجلد الافتراضي",
|
||||||
"Default Ignore Patterns": "أنماط التجاهل الافتراضية",
|
"Default Ignore Patterns": "أنماط التجاهل الافتراضية",
|
||||||
"Defaults": "الافتراضات",
|
"Defaults": "الافتراضات",
|
||||||
"Delete": "احذف",
|
"Delete": "حذف",
|
||||||
"Delete Unexpected Items": "احذف العناصر غير المتوقعة",
|
"Delete Unexpected Items": "حذف العناصر غير المتوقعة",
|
||||||
"Deleted {%file%}": "حُذِف {{file}}",
|
"Deleted {%file%}": "حُذِفت {{file}}",
|
||||||
"Deselect All": "الغاء تحديد الكل",
|
"Deselect All": "الغاء تحديد الكل",
|
||||||
"Deselect devices to stop sharing this folder with.": "قم بإلغاء تحديد الأجهزة لإيقاف مشاركة هذا المجلد معها.",
|
"Deselect devices to stop sharing this folder with.": "قم بإلغاء تحديد الأجهزة لإيقاف مشاركة هذا المجلد معها.",
|
||||||
"Deselect folders to stop sharing with this device.": "قم بإلغاء تحديد المجلدات لإيقاف المشاركة مع هذا الجهاز.",
|
"Deselect folders to stop sharing with this device.": "قم بإلغاء تحديد المجلدات لإيقاف المشاركة مع هذا الجهاز.",
|
||||||
"Device": "جهاز",
|
"Device": "جهاز",
|
||||||
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "الجهاز \"{{name}}\" ({{device}} في {{address}}) يرغب في الاتصال، إضافة جهاز جديد؟",
|
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "الجهاز \"{{name}}\" ({{device}} في {{address}}) يرغب في الاتصال، إضافة جهاز جديد؟",
|
||||||
"Device Certificate": "شهادة الجهاز",
|
"Device Certificate": "شهادة الجهاز",
|
||||||
"Device Group": "مجموعة الجهاز",
|
|
||||||
"Device ID": "معرف الجهاز",
|
"Device ID": "معرف الجهاز",
|
||||||
"Device Identification": "معرف الجهاز",
|
"Device Identification": "معرف الجهاز",
|
||||||
"Device Name": "اسم الجهاز",
|
"Device Name": "اسم الجهاز",
|
||||||
@@ -115,7 +112,7 @@
|
|||||||
"Disabled periodic scanning and enabled watching for changes": "تعطيل المسح الدوري وتفعيل مشاهدة التغييرات",
|
"Disabled periodic scanning and enabled watching for changes": "تعطيل المسح الدوري وتفعيل مشاهدة التغييرات",
|
||||||
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "تعطيل المسح الدوري وفشل إعداد مشاهدة التغييرات، إعادة المحاولة كل 1 دقيقة:",
|
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "تعطيل المسح الدوري وفشل إعداد مشاهدة التغييرات، إعادة المحاولة كل 1 دقيقة:",
|
||||||
"Disables comparing and syncing file permissions. Useful on systems with nonexistent or custom permissions (e.g. FAT, exFAT, Synology, Android).": "تعطيل مقارنة أذونات الملفات ومزامنتها. مفيد على الأنظمة ذات الأذونات غير الموجودة أو المخصصة (مثل FAT و exFAT و Synology و Android).",
|
"Disables comparing and syncing file permissions. Useful on systems with nonexistent or custom permissions (e.g. FAT, exFAT, Synology, Android).": "تعطيل مقارنة أذونات الملفات ومزامنتها. مفيد على الأنظمة ذات الأذونات غير الموجودة أو المخصصة (مثل FAT و exFAT و Synology و Android).",
|
||||||
"Discard": "أهمِل",
|
"Discard": "تجاهل",
|
||||||
"Disconnected": "غير متصل",
|
"Disconnected": "غير متصل",
|
||||||
"Disconnected (Inactive)": "غير متصل (غير نشط)",
|
"Disconnected (Inactive)": "غير متصل (غير نشط)",
|
||||||
"Disconnected (Unused)": "غير متصل (غير مستخدم)",
|
"Disconnected (Unused)": "غير متصل (غير مستخدم)",
|
||||||
@@ -138,10 +135,10 @@
|
|||||||
"Edit Folder": "تعديل المجلد",
|
"Edit Folder": "تعديل المجلد",
|
||||||
"Edit Folder Defaults": "تعديل الإعدادت الافتراضية للمجلد",
|
"Edit Folder Defaults": "تعديل الإعدادت الافتراضية للمجلد",
|
||||||
"Editing {%path%}.": "تعديل {{path}}.",
|
"Editing {%path%}.": "تعديل {{path}}.",
|
||||||
"Enable Crash Reporting": "فعِّل التبليغ عن الإنهيار",
|
"Enable Crash Reporting": "تفعيل التبليغ عن الاخطاء",
|
||||||
"Enable NAT traversal": "فعِّل اجتياز NAT",
|
"Enable NAT traversal": "تفعيل اجتياز النات",
|
||||||
"Enable Relaying": "فعِّل الترحيل",
|
"Enable Relaying": "تفعيل الترحيل",
|
||||||
"Enabled": "مُفعّل",
|
"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, 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 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, and applying incoming ownership information. Typically requires running with elevated privileges.": "يفعل إرسال معلومات الملكية للأجهزة الأخرى، ويفعل معلومات الملكية المستوردة. غالبا ما يطلب صلاحيات أكثر.",
|
||||||
@@ -171,7 +168,6 @@
|
|||||||
"Filter by date": "فلترة بالتاريخ",
|
"Filter by date": "فلترة بالتاريخ",
|
||||||
"Filter by name": "فلتر باستخدام الاسم",
|
"Filter by name": "فلتر باستخدام الاسم",
|
||||||
"Folder": "مجلد",
|
"Folder": "مجلد",
|
||||||
"Folder Group": "مجموعة المجلد",
|
|
||||||
"Folder ID": "مُعرِّف المجلد",
|
"Folder ID": "مُعرِّف المجلد",
|
||||||
"Folder Label": "تسمية المجلد",
|
"Folder Label": "تسمية المجلد",
|
||||||
"Folder Path": "مسار المجلد",
|
"Folder Path": "مسار المجلد",
|
||||||
@@ -199,7 +195,7 @@
|
|||||||
"Help": "مساعدة",
|
"Help": "مساعدة",
|
||||||
"Hint: only deny-rules detected while the default is deny. Consider adding \"permit any\" as last rule.": "ملحوظة: إذا كان الإعداد الافتراضي هو الرفض، وحدها قواعد الرفض تُرصد. جرب إضافة \"السماح للكل\" كخيار أخير.",
|
"Hint: only deny-rules detected while the default is deny. Consider adding \"permit any\" as last rule.": "ملحوظة: إذا كان الإعداد الافتراضي هو الرفض، وحدها قواعد الرفض تُرصد. جرب إضافة \"السماح للكل\" كخيار أخير.",
|
||||||
"Home page": "الصفحة الرئيسية",
|
"Home page": "الصفحة الرئيسية",
|
||||||
"However, your current settings indicate you might not want it enabled. We have disabled automatic crash reporting for you.": "ومع ذلك، تشير إعداداتك الحالية إلى أنك قد لا ترغب في تفعيله. لذلك توقف الإبلاغ التلقائي عن الأعطال.",
|
"However, your current settings indicate you might not want it enabled. We have disabled automatic crash reporting for you.": "ومع ذلك، تشير إعداداتك الحالية إلى أنك قد لا ترغب في تمكينه. لذلك توقف الإبلاغ التلقائي عن الأعطال.",
|
||||||
"Identification": "المُعرِّف",
|
"Identification": "المُعرِّف",
|
||||||
"If untrusted, enter encryption password": "في حالة الرِّيبة، أدخل كلمة سر التشفير",
|
"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، يُنصَح بإعداد وثائق الملكية.",
|
"If you want to prevent other users on this computer from accessing Syncthing and through it your files, consider setting up authentication.": "إذا أردت منع المستخدمين الآخرين على هذا الحاسب من الوصول لملفاتك من خلال Syncthing، يُنصَح بإعداد وثائق الملكية.",
|
||||||
@@ -214,7 +210,6 @@
|
|||||||
"Incoming Rate Limit (KiB/s)": "الحد الأقصى البيانات الواردة (KiB/s)",
|
"Incoming Rate Limit (KiB/s)": "الحد الأقصى البيانات الواردة (KiB/s)",
|
||||||
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "الإعدادات الغير صحيحة قد تدمر بيانات المجلد وتُفْشِلُ المزامنة.",
|
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "الإعدادات الغير صحيحة قد تدمر بيانات المجلد وتُفْشِلُ المزامنة.",
|
||||||
"Incorrect user name or password.": "رُصِدَ خطأ في اسم المستخدم أو كلمة المرور.",
|
"Incorrect user name or password.": "رُصِدَ خطأ في اسم المستخدم أو كلمة المرور.",
|
||||||
"Info": "معلومات",
|
|
||||||
"Internally used paths:": "المسار المستخدم محليّا:",
|
"Internally used paths:": "المسار المستخدم محليّا:",
|
||||||
"Introduced By": "عرف بواسطة",
|
"Introduced By": "عرف بواسطة",
|
||||||
"Introducer": "الوسيط",
|
"Introducer": "الوسيط",
|
||||||
@@ -232,7 +227,6 @@
|
|||||||
"Learn more": "اعرف أكثر",
|
"Learn more": "اعرف أكثر",
|
||||||
"Learn more at {%url%}": "اطلع على المزيد في {{url}}",
|
"Learn more at {%url%}": "اطلع على المزيد في {{url}}",
|
||||||
"Limit": "الحد",
|
"Limit": "الحد",
|
||||||
"Limit Bandwidth in LAN": "تحديد عرض النطاق الترددي في الشبكة المحلية",
|
|
||||||
"Listener Failures": "أعطال المنصت",
|
"Listener Failures": "أعطال المنصت",
|
||||||
"Listener Status": "حالة المنصت",
|
"Listener Status": "حالة المنصت",
|
||||||
"Listeners": "المنصتين",
|
"Listeners": "المنصتين",
|
||||||
@@ -252,7 +246,6 @@
|
|||||||
"Log tailing paused. Scroll to the bottom to continue.": "تتبع السجل متوقف، مَرِّر للأسفل للاستئناف.",
|
"Log tailing paused. Scroll to the bottom to continue.": "تتبع السجل متوقف، مَرِّر للأسفل للاستئناف.",
|
||||||
"Login failed, see Syncthing logs for details.": "فشل تسجيل الدخول، اطَّلِع على سِجِلِّ Syncthing للتفاصيل.",
|
"Login failed, see Syncthing logs for details.": "فشل تسجيل الدخول، اطَّلِع على سِجِلِّ Syncthing للتفاصيل.",
|
||||||
"Logs": "سجلات",
|
"Logs": "سجلات",
|
||||||
"Maintain an index of all blocks in the folder, enabling reuse of blocks from other files when syncing changes. Disable to reduce database size at the cost of not being able to reuse blocks across files.": "الاحتفاظ بفهرس لجميع الكُتل الموجودة في المجلد، مما يتيح إعادة استخدام الكتل من ملفات أخرى عند مزامنة التغييرات. عطّل هذا الخيار لتقليل حجم قاعدة البيانات، ولكن على حساب عدم القدرة على إعادة استخدام الكتل عبر الملفات المختلفة.",
|
|
||||||
"Major Upgrade": "ترقية أساسية",
|
"Major Upgrade": "ترقية أساسية",
|
||||||
"Mass actions": "التأثيرات العامة",
|
"Mass actions": "التأثيرات العامة",
|
||||||
"Maximum Age": "أقصى مدة",
|
"Maximum Age": "أقصى مدة",
|
||||||
@@ -283,8 +276,6 @@
|
|||||||
"Off": "اطفئ",
|
"Off": "اطفئ",
|
||||||
"Oldest First": "الأقدم أولا",
|
"Oldest First": "الأقدم أولا",
|
||||||
"Optional descriptive label for the folder. Can be different on each device.": "تسمية وصفية اختيارية للمجلد. يمكن أن تكون مختلفة على كل جهاز.",
|
"Optional descriptive label for the folder. Can be different on each device.": "تسمية وصفية اختيارية للمجلد. يمكن أن تكون مختلفة على كل جهاز.",
|
||||||
"Optional group for the device. Can be different on each device.": "مجموعة اختيارية للجهاز. يمكن أن تختلف من جهاز لآخر.",
|
|
||||||
"Optional group for the folder. Can be different on each device.": "مجموعة اختيارية للمجلد. يمكن أن تختلف من جهاز لآخر.",
|
|
||||||
"Options": "خيارات",
|
"Options": "خيارات",
|
||||||
"Out of Sync": "خارج التزامن",
|
"Out of Sync": "خارج التزامن",
|
||||||
"Out of Sync Items": "عناصر خارج التزامن",
|
"Out of Sync Items": "عناصر خارج التزامن",
|
||||||
@@ -330,9 +321,9 @@
|
|||||||
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "مُمَهِّدات الإصدار تحتوي على آخر الخصائص والإصلاحات. وهي مماثلة للإصدارات النصف شهرية التقليدية لـ Syncthing .",
|
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "مُمَهِّدات الإصدار تحتوي على آخر الخصائص والإصلاحات. وهي مماثلة للإصدارات النصف شهرية التقليدية لـ Syncthing .",
|
||||||
"Remote Devices": "جهاز بعيد",
|
"Remote Devices": "جهاز بعيد",
|
||||||
"Remote GUI": "الواجهة النائية",
|
"Remote GUI": "الواجهة النائية",
|
||||||
"Remove": "أزِل",
|
"Remove": "إزالة",
|
||||||
"Remove Device": "أزِل الجهاز",
|
"Remove Device": "حذف جهاز",
|
||||||
"Remove Folder": "أزِل المجلد",
|
"Remove Folder": "حذف مجلد",
|
||||||
"Required identifier for the folder. Must be the same on all cluster devices.": "يتطلب معرفًا للمجلد. يجب أن يستخدم نفس المعرف لبقية الأجهزة.",
|
"Required identifier for the folder. Must be the same on all cluster devices.": "يتطلب معرفًا للمجلد. يجب أن يستخدم نفس المعرف لبقية الأجهزة.",
|
||||||
"Rescan": "إعادة فحص",
|
"Rescan": "إعادة فحص",
|
||||||
"Rescan All": "إعادة فحص الكل",
|
"Rescan All": "إعادة فحص الكل",
|
||||||
@@ -379,7 +370,7 @@
|
|||||||
"Show diff with previous version": "أظهر الفرق مقارنةً بالنسخة السابقة",
|
"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 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.": "يُعرَض بدلا من المُعرِّف ضمن العناقيد. إذا تُرك فارغا، سيُحدَّث إلى الاسم المختار من قِبَل الجهاز.",
|
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "يُعرَض بدلا من المُعرِّف ضمن العناقيد. إذا تُرك فارغا، سيُحدَّث إلى الاسم المختار من قِبَل الجهاز.",
|
||||||
"Shut Down": "إغلاق",
|
"Shutdown": "إغلاق",
|
||||||
"Shutdown Complete": "أُغلِق",
|
"Shutdown Complete": "أُغلِق",
|
||||||
"Simple": "بسيط",
|
"Simple": "بسيط",
|
||||||
"Simple File Versioning": "التقسيم البسيط لإصدارات الملفات",
|
"Simple File Versioning": "التقسيم البسيط لإصدارات الملفات",
|
||||||
@@ -396,7 +387,6 @@
|
|||||||
"Staggered": "مترنِّح",
|
"Staggered": "مترنِّح",
|
||||||
"Staggered File Versioning": "تقسمات إصدارات الملف مهترئة",
|
"Staggered File Versioning": "تقسمات إصدارات الملف مهترئة",
|
||||||
"Start Browser": "تشغيل المتصفح",
|
"Start Browser": "تشغيل المتصفح",
|
||||||
"Starting": "يبدأ",
|
|
||||||
"Statistics": "إحصائيات",
|
"Statistics": "إحصائيات",
|
||||||
"Stay logged in": "ابقِ مُسجل الدخول",
|
"Stay logged in": "ابقِ مُسجل الدخول",
|
||||||
"Stopped": "متوقف",
|
"Stopped": "متوقف",
|
||||||
@@ -522,14 +512,14 @@
|
|||||||
"Warning, this path is a subdirectory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "تحذير، هذا المجلد هو أحد محتويات مجلد مضاف مسبقا \"{{otherFolderLabel}}\" ({{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}}، تأكد من تعطيله.",
|
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "تحذير: إذا كنت مستخدما لمراقب خارجي كـ {{syncthingInotify}}، تأكد من تعطيله.",
|
||||||
"Watch for Changes": "راقب التغييرات",
|
"Watch for Changes": "راقب التغييرات",
|
||||||
"Watching for Changes": "يراقب التغيرات",
|
"Watching for Changes": "جاري مراقبة التغيرات",
|
||||||
"Watching for changes discovers most changes without periodic scanning.": "مراقبة التغييرات تكشف معظم التغييرات دون إجراء المسح الدوري.",
|
"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 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 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.",
|
"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": "نعم",
|
"Yes": "نعم",
|
||||||
"Yesterday": "أمس",
|
"Yesterday": "أمس",
|
||||||
"You can also copy and paste the text into a new message manually.": "يمكنك نسخ النص ولصقه في رسالة جديدة يدويًا.",
|
"You can also copy and paste the text into a new message manually.": "يكنك نسخ النص لتدرجه في رسالة جديدة بنفسك.",
|
||||||
"You can also select one of these nearby devices:": "يمكنك أيضا اختيار واحدة من الأجهزة القريبة:",
|
"You can also select one of these nearby devices:": "يمكنك أيضا اختيار واحدة من الأجهزة القريبة:",
|
||||||
"You can change your choice at any time in the Settings dialog.": "يمكنك تغيير اختيارك في أي وقت بواسطة الاعدادات.",
|
"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 can read more about the two release channels at the link below.": "يمكنك قراءة المزيد عن إصداريّ القناتين عبر الرابط بالأسفل.",
|
||||||
@@ -541,7 +531,7 @@
|
|||||||
"Your SMS app should open to let you choose the recipient and send it from your own number.": "ينبغي أن يسمح تطبيق SMS لديك بأن تختار مستلما ويرسلها من رقمك.",
|
"Your SMS app should open to let you choose the recipient and send it from your own number.": "ينبغي أن يسمح تطبيق SMS لديك بأن تختار مستلما ويرسلها من رقمك.",
|
||||||
"Your email app should open to let you choose the recipient and send it from your own address.": "ينبغي أن يسمح تطبيق البريد الإلكتروني الخاص بك باختيار مستلم و أن يرسلها من عنوانك.",
|
"Your email app should open to let you choose the recipient and send it from your own address.": "ينبغي أن يسمح تطبيق البريد الإلكتروني الخاص بك باختيار مستلم و أن يرسلها من عنوانك.",
|
||||||
"days": "أيام",
|
"days": "أيام",
|
||||||
"deleted": "حُذِف",
|
"deleted": "مُسِحَ",
|
||||||
"deny": "امنع",
|
"deny": "امنع",
|
||||||
"directories": "مجلدات",
|
"directories": "مجلدات",
|
||||||
"file": "ملف",
|
"file": "ملف",
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
{
|
|
||||||
}
|
|
||||||
@@ -27,7 +27,6 @@
|
|||||||
"Allowed Networks": "Дазволеныя Сеткі",
|
"Allowed Networks": "Дазволеныя Сеткі",
|
||||||
"Alphabetic": "Па Алфавіту",
|
"Alphabetic": "Па Алфавіту",
|
||||||
"Altered by ignoring deletes.": "Зменена з-за ігнаравання выдаленняў.",
|
"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.": "Знешняя каманда утрымлівае версіянаванне. Яно патрабуе выдалення файла з агульнай дырэкторыі. Калі шлях да прыкладання ўтрымлівае прабелы, трэба іх выдаліць.",
|
"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?": "Фармат ананімнай спрадвыздачы аб выкарыстанні быў зменены. Ці жадаеце вы выкарыстоўваць новы фармат?",
|
||||||
@@ -153,7 +152,7 @@
|
|||||||
"Show ID": "Паказаць ID",
|
"Show ID": "Паказаць ID",
|
||||||
"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 advertised to other devices as an optional default name.",
|
"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 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.": "Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.",
|
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.",
|
||||||
"Shut Down": "Выключыць",
|
"Shutdown": "Выключыць",
|
||||||
"Shutdown Complete": "Выключэньне завершанае",
|
"Shutdown Complete": "Выключэньне завершанае",
|
||||||
"Simple File Versioning": "Простае захоўваньне вэрсій",
|
"Simple File Versioning": "Простае захоўваньне вэрсій",
|
||||||
"Single level wildcard (matches within a directory only)": "Single level wildcard (matches within a directory only)",
|
"Single level wildcard (matches within a directory only)": "Single level wildcard (matches within a directory only)",
|
||||||
|
|||||||
@@ -50,7 +50,6 @@
|
|||||||
"Automatically create or share folders that this device advertises at the default path.": "Автоматично създава в подразбираната папка или споделя папките, които устройството предлага.",
|
"Automatically create or share folders that this device advertises at the default path.": "Автоматично създава в подразбираната папка или споделя папките, които устройството предлага.",
|
||||||
"Available debug logging facilities:": "Достъпни улеснения при отстраняване на дефекти:",
|
"Available debug logging facilities:": "Достъпни улеснения при отстраняване на дефекти:",
|
||||||
"Be careful!": "Внимание!",
|
"Be careful!": "Внимание!",
|
||||||
"Block Indexing": "Индексиране на блокове",
|
|
||||||
"Body:": "Съдържание:",
|
"Body:": "Съдържание:",
|
||||||
"Bugs": "Дефекти",
|
"Bugs": "Дефекти",
|
||||||
"Cancel": "Отказ",
|
"Cancel": "Отказ",
|
||||||
@@ -83,7 +82,6 @@
|
|||||||
"Custom Range": "В периода",
|
"Custom Range": "В периода",
|
||||||
"Danger!": "Опасност!",
|
"Danger!": "Опасност!",
|
||||||
"Database Location": "Местоположение на хранилището",
|
"Database Location": "Местоположение на хранилището",
|
||||||
"Debug": "Отстраняване на дефекти",
|
|
||||||
"Debugging Facilities": "Отстраняване на дефекти",
|
"Debugging Facilities": "Отстраняване на дефекти",
|
||||||
"Default": "По подразбиране",
|
"Default": "По подразбиране",
|
||||||
"Default Configuration": "Настройки по подразбиране",
|
"Default Configuration": "Настройки по подразбиране",
|
||||||
@@ -100,7 +98,6 @@
|
|||||||
"Device": "Устройство",
|
"Device": "Устройство",
|
||||||
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Устройство \"{{name}}\" ({{device}}) с адрес {{address}} желае да се свърже. Да бъде ли добавено?",
|
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Устройство \"{{name}}\" ({{device}}) с адрес {{address}} желае да се свърже. Да бъде ли добавено?",
|
||||||
"Device Certificate": "Сертификат на устройството",
|
"Device Certificate": "Сертификат на устройството",
|
||||||
"Device Group": "Група устройства",
|
|
||||||
"Device ID": "Идентификатор на устройство",
|
"Device ID": "Идентификатор на устройство",
|
||||||
"Device Identification": "Идентификатор на устройство",
|
"Device Identification": "Идентификатор на устройство",
|
||||||
"Device Name": "Име на устройството",
|
"Device Name": "Име на устройството",
|
||||||
@@ -171,7 +168,6 @@
|
|||||||
"Filter by date": "Филтриране по дата",
|
"Filter by date": "Филтриране по дата",
|
||||||
"Filter by name": "Филтриране по име",
|
"Filter by name": "Филтриране по име",
|
||||||
"Folder": "Папка",
|
"Folder": "Папка",
|
||||||
"Folder Group": "Група папки",
|
|
||||||
"Folder ID": "Идентификатор на папката",
|
"Folder ID": "Идентификатор на папката",
|
||||||
"Folder Label": "Име на папката",
|
"Folder Label": "Име на папката",
|
||||||
"Folder Path": "Път до папката",
|
"Folder Path": "Път до папката",
|
||||||
@@ -180,7 +176,7 @@
|
|||||||
"Folder type \"{%receiveEncrypted%}\" can only be set when adding a new folder.": "Вида „{{receiveEncrypted}}“ може да бъде избран само при добавяне на папка.",
|
"Folder type \"{%receiveEncrypted%}\" can only be set when adding a new folder.": "Вида „{{receiveEncrypted}}“ може да бъде избран само при добавяне на папка.",
|
||||||
"Folder type \"{%receiveEncrypted%}\" cannot be changed after adding the folder. You need to remove the folder, delete or decrypt the data on disk, and add the folder again.": "Видът папката „{{receiveEncrypted}}“ не може да бъде променян след нейното създаване. Трябва да я премахнете, изтриете или разшифровате съдържанието и да добавите папката отново.",
|
"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": "Папки",
|
"Folders": "Папки",
|
||||||
"For the following folders an error occurred while starting to watch for changes. It will be retried every minute, so the errors might go away soon. If they persist, try to fix the underlying issue and ask for help if you can't.": "Грешка при започване на наблюдението за промени на следните папки. Всяка минута ще бъде извършван нов опит, така че грешката скоро може да изчезне. Ако все пак не изчезне, отстранете първопричината или ако не съумявате потърсете помощ.",
|
"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": "Завинаги",
|
"Forever": "Завинаги",
|
||||||
"Full Rescan Interval (s)": "Интервал на пълно обхождане (секунди)",
|
"Full Rescan Interval (s)": "Интервал на пълно обхождане (секунди)",
|
||||||
"GUI": "Интерфейс",
|
"GUI": "Интерфейс",
|
||||||
@@ -214,7 +210,6 @@
|
|||||||
"Incoming Rate Limit (KiB/s)": "Ограничение при изтегляне (КиБ/с)",
|
"Incoming Rate Limit (KiB/s)": "Ограничение при изтегляне (КиБ/с)",
|
||||||
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Неправилни настройки могат да повредят файлове и да попречат на синхронизирането.",
|
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Неправилни настройки могат да повредят файлове и да попречат на синхронизирането.",
|
||||||
"Incorrect user name or password.": "Грешно потребителско име или парола.",
|
"Incorrect user name or password.": "Грешно потребителско име или парола.",
|
||||||
"Info": "Сведения",
|
|
||||||
"Internally used paths:": "Вътрешно използвани пътища:",
|
"Internally used paths:": "Вътрешно използвани пътища:",
|
||||||
"Introduced By": "Предложено от",
|
"Introduced By": "Предложено от",
|
||||||
"Introducer": "Поръчител",
|
"Introducer": "Поръчител",
|
||||||
@@ -232,7 +227,6 @@
|
|||||||
"Learn more": "Научете повече",
|
"Learn more": "Научете повече",
|
||||||
"Learn more at {%url%}": "Научете повече на {{url}}",
|
"Learn more at {%url%}": "Научете повече на {{url}}",
|
||||||
"Limit": "Ограничение",
|
"Limit": "Ограничение",
|
||||||
"Limit Bandwidth in LAN": "Огранич. на скоростта в местната мрежа",
|
|
||||||
"Listener Failures": "Грешки при очакване на връзка",
|
"Listener Failures": "Грешки при очакване на връзка",
|
||||||
"Listener Status": "Очакване на връзка",
|
"Listener Status": "Очакване на връзка",
|
||||||
"Listeners": "Очакване на връзка",
|
"Listeners": "Очакване на връзка",
|
||||||
@@ -252,7 +246,6 @@
|
|||||||
"Log tailing paused. Scroll to the bottom to continue.": "Добавяне на редове към дневника е спряно. Плъзнете най-долу за да продължи.",
|
"Log tailing paused. Scroll to the bottom to continue.": "Добавяне на редове към дневника е спряно. Плъзнете най-долу за да продължи.",
|
||||||
"Login failed, see Syncthing logs for details.": "Грешка при вход, за подробности проверете в дневника на Syncthing.",
|
"Login failed, see Syncthing logs for details.": "Грешка при вход, за подробности проверете в дневника на Syncthing.",
|
||||||
"Logs": "Дневници",
|
"Logs": "Дневници",
|
||||||
"Maintain an index of all blocks in the folder, enabling reuse of blocks from other files when syncing changes. Disable to reduce database size at the cost of not being able to reuse blocks across files.": "Поддържане на индекс на всички блокове в папката, което позволява повторното им използване от други файлове при синхронизиране на промените. Изключете, за да намалите размера на базата данни за сметка на невъзможността да използвате блокове между различните файлове.",
|
|
||||||
"Major Upgrade": "Обновяване на значимо издание",
|
"Major Upgrade": "Обновяване на значимо издание",
|
||||||
"Mass actions": "Мащабни действия",
|
"Mass actions": "Мащабни действия",
|
||||||
"Maximum Age": "Максимална възраст",
|
"Maximum Age": "Максимална възраст",
|
||||||
@@ -283,8 +276,6 @@
|
|||||||
"Off": "Изключено",
|
"Off": "Изключено",
|
||||||
"Oldest First": "Първо най-старите",
|
"Oldest First": "Първо най-старите",
|
||||||
"Optional descriptive label for the folder. Can be different on each device.": "Незадължително име на папката. Може да бъде различно на всяко устройство.",
|
"Optional descriptive label for the folder. Can be different on each device.": "Незадължително име на папката. Може да бъде различно на всяко устройство.",
|
||||||
"Optional group for the device. Can be different on each device.": "Незадължителна група за устройството. Може да е различна за всяко устройство.",
|
|
||||||
"Optional group for the folder. Can be different on each device.": "Незадължителна група за папката. Може да е различна на всяко устройство.",
|
|
||||||
"Options": "Настройки",
|
"Options": "Настройки",
|
||||||
"Out of Sync": "Несинхронизирано",
|
"Out of Sync": "Несинхронизирано",
|
||||||
"Out of Sync Items": "Несинхронизирани елементи",
|
"Out of Sync Items": "Несинхронизирани елементи",
|
||||||
@@ -379,7 +370,7 @@
|
|||||||
"Show diff with previous version": "Показване на разликите с предходната версия",
|
"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 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.": "В списъка на устройствата се показва вместо идентификатор. Ако бъде оставено празно ще бъде променено на името, което носи устройството.",
|
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "В списъка на устройствата се показва вместо идентификатор. Ако бъде оставено празно ще бъде променено на името, което носи устройството.",
|
||||||
"Shut Down": "Изключване",
|
"Shutdown": "Изключване",
|
||||||
"Shutdown Complete": "Спирането завършено",
|
"Shutdown Complete": "Спирането завършено",
|
||||||
"Simple": "Обикновени",
|
"Simple": "Обикновени",
|
||||||
"Simple File Versioning": "Обикновени версии",
|
"Simple File Versioning": "Обикновени версии",
|
||||||
@@ -396,7 +387,6 @@
|
|||||||
"Staggered": "Разпределени",
|
"Staggered": "Разпределени",
|
||||||
"Staggered File Versioning": "Разпределени версии",
|
"Staggered File Versioning": "Разпределени версии",
|
||||||
"Start Browser": "Отваряне на мрежов четец",
|
"Start Browser": "Отваряне на мрежов четец",
|
||||||
"Starting": "Стартиране",
|
|
||||||
"Statistics": "Статистика",
|
"Statistics": "Статистика",
|
||||||
"Stay logged in": "Оставане в системата",
|
"Stay logged in": "Оставане в системата",
|
||||||
"Stopped": "Спряна",
|
"Stopped": "Спряна",
|
||||||
@@ -538,7 +528,7 @@
|
|||||||
"You have unsaved changes. Do you really want to discard them?": "Има незапазени промени. Желаете ли да се откажете от тях?",
|
"You have unsaved changes. Do you really want to discard them?": "Има незапазени промени. Желаете ли да се откажете от тях?",
|
||||||
"You must keep at least one version.": "Необходимо е да запазите поне една версия.",
|
"You must keep at least one version.": "Необходимо е да запазите поне една версия.",
|
||||||
"You should never add or change anything locally in a \"{%receiveEncrypted%}\" folder.": "Никога не трябва да променяте нищо в папка от вида „{{receiveEncrypted}}“.",
|
"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.": "Приложението за SMS би трябвало да се отвори, да ви даде възможност да изберете получател, на когото да изпратите съобщението от вашия телефонен номер.",
|
"Your SMS app should open to let you choose the recipient and send it from your own number.": "Приложението за SMS би трябвало да се отвори, да ви даде възможност да изберете получател, за да изпратите съобщението от вашия телефонен номер.",
|
||||||
"Your email app should open to let you choose the recipient and send it from your own address.": "Пощенският клиент би трябвало да се отвори, да ви даде възможност да изберете получател, за да изпратите съобщението от вашия адрес за електронна поща.",
|
"Your email app should open to let you choose the recipient and send it from your own address.": "Пощенският клиент би трябвало да се отвори, да ви даде възможност да изберете получател, за да изпратите съобщението от вашия адрес за електронна поща.",
|
||||||
"days": "дни",
|
"days": "дни",
|
||||||
"deleted": "премахнато",
|
"deleted": "премахнато",
|
||||||
|
|||||||
@@ -27,7 +27,6 @@
|
|||||||
"Allowed Networks": "Xarxes permeses",
|
"Allowed Networks": "Xarxes permeses",
|
||||||
"Alphabetic": "Alfabètic",
|
"Alphabetic": "Alfabètic",
|
||||||
"Altered by ignoring deletes.": "S'ha alterat ignorant les supressions.",
|
"Altered by ignoring deletes.": "S'ha alterat ignorant les supressions.",
|
||||||
"Always turned on when the folder type is \"{%foldertype%}\".": "Sempre activat quan el tipus de carpeta és \"{{foldertype}}\".",
|
|
||||||
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "Una ordre externa gestiona la versió. Ha d'eliminar el fitxer de la carpeta compartida. Si el camí a l'aplicació conté espais, s'ha de citar.",
|
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "Una ordre externa gestiona la versió. Ha d'eliminar el fitxer de la carpeta compartida. Si el camí a l'aplicació conté espais, s'ha de citar.",
|
||||||
"Anonymous Usage Reporting": "Informe anònim d'ús",
|
"Anonymous Usage Reporting": "Informe anònim d'ús",
|
||||||
"Anonymous usage report format has changed. Would you like to move to the new format?": "El format de l'informe d'ús anònim ha canviat. Voleu canviar a aquest nou format?",
|
"Anonymous usage report format has changed. Would you like to move to the new format?": "El format de l'informe d'ús anònim ha canviat. Voleu canviar a aquest nou format?",
|
||||||
@@ -53,7 +52,6 @@
|
|||||||
"Body:": "Cos de text:",
|
"Body:": "Cos de text:",
|
||||||
"Bugs": "Errors (Bugs)",
|
"Bugs": "Errors (Bugs)",
|
||||||
"Cancel": "Cancel·la",
|
"Cancel": "Cancel·la",
|
||||||
"Cannot be enabled when the folder type is \"{%foldertype%}\".": "No es pot habilitar quan el tipus de carpeta és \"{{foldertype}}\".",
|
|
||||||
"Changelog": "Historial de canvis",
|
"Changelog": "Historial de canvis",
|
||||||
"Clean out after": "Netejar després",
|
"Clean out after": "Netejar després",
|
||||||
"Cleaning Versions": "Netejant versions",
|
"Cleaning Versions": "Netejant versions",
|
||||||
@@ -82,7 +80,6 @@
|
|||||||
"Custom Range": "Interval personalitzat",
|
"Custom Range": "Interval personalitzat",
|
||||||
"Danger!": "Perill!",
|
"Danger!": "Perill!",
|
||||||
"Database Location": "Ubicació de la base de dades",
|
"Database Location": "Ubicació de la base de dades",
|
||||||
"Debug": "Depuració",
|
|
||||||
"Debugging Facilities": "Utilitats de Depuració",
|
"Debugging Facilities": "Utilitats de Depuració",
|
||||||
"Default": "Per defecte",
|
"Default": "Per defecte",
|
||||||
"Default Configuration": "Configuració predeterminada",
|
"Default Configuration": "Configuració predeterminada",
|
||||||
@@ -211,7 +208,6 @@
|
|||||||
"Incoming Rate Limit (KiB/s)": "Límit de velocitat d'entrada (KiB/s)",
|
"Incoming Rate Limit (KiB/s)": "Límit de velocitat d'entrada (KiB/s)",
|
||||||
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Una configuració incorrecta pot malmetre els continguts de la teva carpeta i que Syncthing esdevingui inoperatiu.",
|
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Una configuració incorrecta pot malmetre els continguts de la teva carpeta i que Syncthing esdevingui inoperatiu.",
|
||||||
"Incorrect user name or password.": "Nom d'usuari o contrasenya incorrecta.",
|
"Incorrect user name or password.": "Nom d'usuari o contrasenya incorrecta.",
|
||||||
"Info": "Informació",
|
|
||||||
"Internally used paths:": "Camins utilitzats internament:",
|
"Internally used paths:": "Camins utilitzats internament:",
|
||||||
"Introduced By": "Introduït per",
|
"Introduced By": "Introduït per",
|
||||||
"Introducer": "Introductor",
|
"Introducer": "Introductor",
|
||||||
@@ -229,7 +225,6 @@
|
|||||||
"Learn more": "Aprèn més",
|
"Learn more": "Aprèn més",
|
||||||
"Learn more at {%url%}": "Més informació a {{url}}",
|
"Learn more at {%url%}": "Més informació a {{url}}",
|
||||||
"Limit": "Límit",
|
"Limit": "Límit",
|
||||||
"Limit Bandwidth in LAN": "Limita l'amplada de banda a la LAN",
|
|
||||||
"Listener Failures": "Errors en l'escolta",
|
"Listener Failures": "Errors en l'escolta",
|
||||||
"Listener Status": "Estat de l'oient",
|
"Listener Status": "Estat de l'oient",
|
||||||
"Listeners": "Oients",
|
"Listeners": "Oients",
|
||||||
@@ -373,7 +368,7 @@
|
|||||||
"Show diff with previous version": "Mostra la diferència amb la versió anterior",
|
"Show diff with previous version": "Mostra la diferència amb la versió anterior",
|
||||||
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Mostrat en comptes del ID del Node en l'estat del cluster. Serà advertit als altres dispositius com un nom opcional per defecte.",
|
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Mostrat en comptes del ID del Node en l'estat del cluster. Serà advertit als altres dispositius com un nom opcional per defecte.",
|
||||||
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Mostrat en comptes del ID del Node en l'estat del cluster. S'actualitzarà al nom del dispositiu si es deixa buit.",
|
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Mostrat en comptes del ID del Node en l'estat del cluster. S'actualitzarà al nom del dispositiu si es deixa buit.",
|
||||||
"Shut Down": "Apaga",
|
"Shutdown": "Apaga",
|
||||||
"Shutdown Complete": "Apagat complet",
|
"Shutdown Complete": "Apagat complet",
|
||||||
"Simple": "Simple",
|
"Simple": "Simple",
|
||||||
"Simple File Versioning": "Versionat de Fitxers Senzill",
|
"Simple File Versioning": "Versionat de Fitxers Senzill",
|
||||||
|
|||||||
@@ -67,7 +67,7 @@
|
|||||||
"Connection Type": "Tipus de connexió",
|
"Connection Type": "Tipus de connexió",
|
||||||
"Connections": "Connexions",
|
"Connections": "Connexions",
|
||||||
"Connections via relays might be rate limited by the relay": "Les connexions mitjançant relés poden estar limitades pel relé",
|
"Connections via relays might be rate limited by the relay": "Les connexions mitjançant relés poden estar limitades pel relé",
|
||||||
"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.": "Ara està disponible la revisió continua de canvis dins de Syncthing. Això detectarà els canvis i llançarà un escaneig sols a les rutes modificades. Els beneficis són que els canvis es propaguen més ràpidament i es necessiten menys escanejos complets.",
|
"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.": "Ara està disponible la revisió continua de canvix dins de Syncthing. Acò detectarà els canvis i llençarà un escaneig sols a les rutes modificades. Els beneficis són que els canvis es propaguen mé ràpidamente i es necessiten menys escanejos complets.",
|
||||||
"Copied from elsewhere": "Copiat de qualsevol lloc",
|
"Copied from elsewhere": "Copiat de qualsevol lloc",
|
||||||
"Copied from original": "Copiat de l'original",
|
"Copied from original": "Copiat de l'original",
|
||||||
"Copied!": "Copiat!",
|
"Copied!": "Copiat!",
|
||||||
@@ -353,7 +353,7 @@
|
|||||||
"Show diff with previous version": "Mostrar les diferències amb la versió prèvia",
|
"Show diff with previous version": "Mostrar les diferències amb la versió prèvia",
|
||||||
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Mostrat en lloc de l'ID del dispositiu en l'estat del grup (cluster). S'anunciarà als altres dispositius com el nom opcional per defecte.",
|
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Mostrat en lloc de l'ID del dispositiu en l'estat del grup (cluster). S'anunciarà als altres dispositius com el nom opcional per defecte.",
|
||||||
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Mostrat en lloc de l'ID del dispositiu en l'estat del grup (cluster). S'actualitzarà al nom que el dispositiu anuncia si es deixa buit.",
|
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Mostrat en lloc de l'ID del dispositiu en l'estat del grup (cluster). S'actualitzarà al nom que el dispositiu anuncia si es deixa buit.",
|
||||||
"Shut Down": "Apagar",
|
"Shutdown": "Apagar",
|
||||||
"Shutdown Complete": "Apagar completament",
|
"Shutdown Complete": "Apagar completament",
|
||||||
"Simple": "Senzill",
|
"Simple": "Senzill",
|
||||||
"Simple File Versioning": "Versionat de fitxers senzill",
|
"Simple File Versioning": "Versionat de fitxers senzill",
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
{
|
|
||||||
}
|
|
||||||
@@ -360,7 +360,7 @@
|
|||||||
"Show diff with previous version": "Ukázat rozdíl oproti předchozí verzi",
|
"Show diff with previous version": "Ukázat rozdíl oproti předchozí verzi",
|
||||||
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Zobrazeno místo identifikátoru zařízení na náhledu stavu clusteru. Bude odesíláno ostatním zařízením jako výchozí název zařízení.",
|
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Zobrazeno místo identifikátoru zařízení na náhledu stavu clusteru. Bude odesíláno ostatním zařízením jako výchozí název zařízení.",
|
||||||
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Zobrazeno místo identifikátoru zařízení na náhledu stavu clusteru. Pokud nebude vyplněno, bude nastaveno na název, který zařízení odesílá.",
|
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Zobrazeno místo identifikátoru zařízení na náhledu stavu clusteru. Pokud nebude vyplněno, bude nastaveno na název, který zařízení odesílá.",
|
||||||
"Shut Down": "Vypnout",
|
"Shutdown": "Vypnout",
|
||||||
"Shutdown Complete": "Vypnutí dokončeno",
|
"Shutdown Complete": "Vypnutí dokončeno",
|
||||||
"Simple": "Jednoduché",
|
"Simple": "Jednoduché",
|
||||||
"Simple File Versioning": "Jednoduchá správa verzí souborů",
|
"Simple File Versioning": "Jednoduchá správa verzí souborů",
|
||||||
|
|||||||
@@ -368,7 +368,7 @@
|
|||||||
"Show diff with previous version": "Vis forskelle fra tidligere version",
|
"Show diff with previous version": "Vis forskelle fra tidligere version",
|
||||||
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Vises i stedet for enheds-ID i klyngestatus. Vil blive sendt til andre enheder som valgfrit standardnavn.",
|
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Vises i stedet for enheds-ID i klyngestatus. Vil blive sendt til andre enheder som valgfrit standardnavn.",
|
||||||
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Vises i stedet for enheds-ID i klyngestatus. Vil blive opdateret til det navn, som enheden sender, hvis det ikke er udfyldt.",
|
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Vises i stedet for enheds-ID i klyngestatus. Vil blive opdateret til det navn, som enheden sender, hvis det ikke er udfyldt.",
|
||||||
"Shut Down": "Luk ned",
|
"Shutdown": "Luk ned",
|
||||||
"Shutdown Complete": "Nedlukning fuldført",
|
"Shutdown Complete": "Nedlukning fuldført",
|
||||||
"Simple": "Enkel",
|
"Simple": "Enkel",
|
||||||
"Simple File Versioning": "Simpel filversionering",
|
"Simple File Versioning": "Simpel filversionering",
|
||||||
|
|||||||
@@ -50,7 +50,6 @@
|
|||||||
"Automatically create or share folders that this device advertises at the default path.": "Automatisch Ordner im Standardpfad erstellen oder freigeben, die dieses Gerät ankündigt.",
|
"Automatically create or share folders that this device advertises at the default path.": "Automatisch Ordner im Standardpfad erstellen oder freigeben, die dieses Gerät ankündigt.",
|
||||||
"Available debug logging facilities:": "Verfügbare Debugging-Möglichkeiten:",
|
"Available debug logging facilities:": "Verfügbare Debugging-Möglichkeiten:",
|
||||||
"Be careful!": "Vorsicht!",
|
"Be careful!": "Vorsicht!",
|
||||||
"Block Indexing": "Blockindizierung",
|
|
||||||
"Body:": "Nachrichtentext:",
|
"Body:": "Nachrichtentext:",
|
||||||
"Bugs": "Fehler",
|
"Bugs": "Fehler",
|
||||||
"Cancel": "Abbrechen",
|
"Cancel": "Abbrechen",
|
||||||
@@ -62,7 +61,7 @@
|
|||||||
"Click to see full identification string and QR code.": "Klicken, um die vollständige Kennung und den QR-Code anzuzeigen.",
|
"Click to see full identification string and QR code.": "Klicken, um die vollständige Kennung und den QR-Code anzuzeigen.",
|
||||||
"Close": "Schließen",
|
"Close": "Schließen",
|
||||||
"Command": "Befehl",
|
"Command": "Befehl",
|
||||||
"Comment, when used at the start of a line": "Kommentar, wenn am Anfang der Zeile verwendet",
|
"Comment, when used at the start of a line": "Kommentar, wenn am Anfang der Zeile verwendet.",
|
||||||
"Compression": "Komprimierung",
|
"Compression": "Komprimierung",
|
||||||
"Configuration Directory": "Konfigurationsverzeichnis",
|
"Configuration Directory": "Konfigurationsverzeichnis",
|
||||||
"Configuration File": "Konfigurationsdatei",
|
"Configuration File": "Konfigurationsdatei",
|
||||||
@@ -83,7 +82,6 @@
|
|||||||
"Custom Range": "Eigener Zeitraum",
|
"Custom Range": "Eigener Zeitraum",
|
||||||
"Danger!": "Achtung!",
|
"Danger!": "Achtung!",
|
||||||
"Database Location": "Datenbank-Speicherort",
|
"Database Location": "Datenbank-Speicherort",
|
||||||
"Debug": "Debug",
|
|
||||||
"Debugging Facilities": "Debugging-Möglichkeiten",
|
"Debugging Facilities": "Debugging-Möglichkeiten",
|
||||||
"Default": "Vorgabe",
|
"Default": "Vorgabe",
|
||||||
"Default Configuration": "Vorgabekonfiguration",
|
"Default Configuration": "Vorgabekonfiguration",
|
||||||
@@ -100,14 +98,13 @@
|
|||||||
"Device": "Gerät",
|
"Device": "Gerät",
|
||||||
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Gerät „{{name}}“ ({{device}} {{address}}) möchte sich verbinden. Gerät hinzufügen?",
|
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Gerät „{{name}}“ ({{device}} {{address}}) möchte sich verbinden. Gerät hinzufügen?",
|
||||||
"Device Certificate": "Gerätezertifikat",
|
"Device Certificate": "Gerätezertifikat",
|
||||||
"Device Group": "Gerätegruppe",
|
|
||||||
"Device ID": "Gerätekennung",
|
"Device ID": "Gerätekennung",
|
||||||
"Device Identification": "Geräteidentifikation",
|
"Device Identification": "Geräteidentifikation",
|
||||||
"Device Name": "Gerätename",
|
"Device Name": "Gerätename",
|
||||||
"Device Status": "Gerätestatus",
|
"Device Status": "Gerätestatus",
|
||||||
"Device is untrusted, enter encryption password": "Gerät wird nicht vertraut, Verschlüsselungspasswort eingeben",
|
"Device is untrusted, enter encryption password": "Gerät wird nicht vertraut, Verschlüsselungspasswort eingeben",
|
||||||
"Device rate limits": "Datenratenbegrenzungen fürs Gerät",
|
"Device rate limits": "Datenratenbegrenzungen fürs Gerät",
|
||||||
"Device that last modified the item": "Gerät, welches das Element zuletzt geändert hat",
|
"Device that last modified the item": "Gerät, das das Element zuletzt geändert hat",
|
||||||
"Devices": "Geräte",
|
"Devices": "Geräte",
|
||||||
"Disable Crash Reporting": "Absturzmeldung deaktivieren",
|
"Disable Crash Reporting": "Absturzmeldung deaktivieren",
|
||||||
"Disabled": "Deaktiviert",
|
"Disabled": "Deaktiviert",
|
||||||
@@ -171,7 +168,6 @@
|
|||||||
"Filter by date": "Nach Datum sortieren",
|
"Filter by date": "Nach Datum sortieren",
|
||||||
"Filter by name": "Nach Namen sortieren",
|
"Filter by name": "Nach Namen sortieren",
|
||||||
"Folder": "Ordner",
|
"Folder": "Ordner",
|
||||||
"Folder Group": "Ordnergruppe",
|
|
||||||
"Folder ID": "Ordnerkennung",
|
"Folder ID": "Ordnerkennung",
|
||||||
"Folder Label": "Ordnerbezeichnung",
|
"Folder Label": "Ordnerbezeichnung",
|
||||||
"Folder Path": "Ordnerpfad",
|
"Folder Path": "Ordnerpfad",
|
||||||
@@ -187,7 +183,7 @@
|
|||||||
"GUI / API HTTPS Certificate": "GUI / API HTTPS-Zertifikat",
|
"GUI / API HTTPS Certificate": "GUI / API HTTPS-Zertifikat",
|
||||||
"GUI Authentication Password": "Passwort für Zugang zur Benutzeroberfläche",
|
"GUI Authentication Password": "Passwort für Zugang zur Benutzeroberfläche",
|
||||||
"GUI Authentication User": "Benutzername für Zugang zur Benutzeroberfläche",
|
"GUI Authentication User": "Benutzername für Zugang zur Benutzeroberfläche",
|
||||||
"GUI Authentication: Set User and Password": "Authentifizierung für die Benutzeroberfläche: Geben Sie Benutzer und Passwort ein",
|
"GUI Authentication: Set User and Password": "Authentifizierung für die Benutzeroberfläche: Geben Sie Benutzer und Passwort ein.",
|
||||||
"GUI Listen Address": "Adresse der Benutzeroberfläche",
|
"GUI Listen Address": "Adresse der Benutzeroberfläche",
|
||||||
"GUI Override Directory": "GUI-Ersatz-Verzeichnis",
|
"GUI Override Directory": "GUI-Ersatz-Verzeichnis",
|
||||||
"GUI Theme": "GUI-Design",
|
"GUI Theme": "GUI-Design",
|
||||||
@@ -214,7 +210,6 @@
|
|||||||
"Incoming Rate Limit (KiB/s)": "Eingehende Datenratenbegrenzung (KiB/s)",
|
"Incoming Rate Limit (KiB/s)": "Eingehende Datenratenbegrenzung (KiB/s)",
|
||||||
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Eine falsche Konfiguration kann den Ordnerinhalt beschädigen und Syncthing in einen unausführbaren Zustand versetzen.",
|
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Eine falsche Konfiguration kann den Ordnerinhalt beschädigen und Syncthing in einen unausführbaren Zustand versetzen.",
|
||||||
"Incorrect user name or password.": "Falscher Benutzername oder Passwort.",
|
"Incorrect user name or password.": "Falscher Benutzername oder Passwort.",
|
||||||
"Info": "Info",
|
|
||||||
"Internally used paths:": "Intern verwendete Pfade:",
|
"Internally used paths:": "Intern verwendete Pfade:",
|
||||||
"Introduced By": "Verteilt von",
|
"Introduced By": "Verteilt von",
|
||||||
"Introducer": "Verteilergerät",
|
"Introducer": "Verteilergerät",
|
||||||
@@ -232,7 +227,6 @@
|
|||||||
"Learn more": "Mehr erfahren",
|
"Learn more": "Mehr erfahren",
|
||||||
"Learn more at {%url%}": "Erfahren Sie mehr unter {{url}}",
|
"Learn more at {%url%}": "Erfahren Sie mehr unter {{url}}",
|
||||||
"Limit": "Limit",
|
"Limit": "Limit",
|
||||||
"Limit Bandwidth in LAN": "Bandbreite im LAN begrenzen",
|
|
||||||
"Listener Failures": "Fehler bei Listener",
|
"Listener Failures": "Fehler bei Listener",
|
||||||
"Listener Status": "Status der Listener",
|
"Listener Status": "Status der Listener",
|
||||||
"Listeners": "Zuhörer",
|
"Listeners": "Zuhörer",
|
||||||
@@ -252,7 +246,6 @@
|
|||||||
"Log tailing paused. Scroll to the bottom to continue.": "Protokolländerungsverfolgung angehalten. Zum Ende blättern, um fortzufahren.",
|
"Log tailing paused. Scroll to the bottom to continue.": "Protokolländerungsverfolgung angehalten. Zum Ende blättern, um fortzufahren.",
|
||||||
"Login failed, see Syncthing logs for details.": "Anmeldung fehlgeschlagen, siehe Syncthing-Protokolle für Details.",
|
"Login failed, see Syncthing logs for details.": "Anmeldung fehlgeschlagen, siehe Syncthing-Protokolle für Details.",
|
||||||
"Logs": "Protokolle",
|
"Logs": "Protokolle",
|
||||||
"Maintain an index of all blocks in the folder, enabling reuse of blocks from other files when syncing changes. Disable to reduce database size at the cost of not being able to reuse blocks across files.": "Ein Index über alle Blöcke im Ordner führen, sodass Blöcke aus anderen Dateien bei der Synchronisierung von Änderungen wiederverwendet werden können. Diese Option deaktivieren, um die Datenbankgröße zu verringern – allerdings können Blöcke dann nicht mehr dateiübergreifend wiederverwendet werden.",
|
|
||||||
"Major Upgrade": "Hauptversionsaktualisierung",
|
"Major Upgrade": "Hauptversionsaktualisierung",
|
||||||
"Mass actions": "Massenaktionen",
|
"Mass actions": "Massenaktionen",
|
||||||
"Maximum Age": "Höchstalter",
|
"Maximum Age": "Höchstalter",
|
||||||
@@ -283,8 +276,6 @@
|
|||||||
"Off": "Aus",
|
"Off": "Aus",
|
||||||
"Oldest First": "Älteste zuerst",
|
"Oldest First": "Älteste zuerst",
|
||||||
"Optional descriptive label for the folder. Can be different on each device.": "Optionale beschreibende Bezeichnung des Ordners. Kann auf jedem Gerät unterschiedlich sein.",
|
"Optional descriptive label for the folder. Can be different on each device.": "Optionale beschreibende Bezeichnung des Ordners. Kann auf jedem Gerät unterschiedlich sein.",
|
||||||
"Optional group for the device. Can be different on each device.": "Optionale Gruppe für das Gerät. Kann auf jedem Gerät unterschiedlich sein.",
|
|
||||||
"Optional group for the folder. Can be different on each device.": "Optionale Gruppe für den Ordner. Kann auf jedem Gerät unterschiedlich sein.",
|
|
||||||
"Options": "Optionen",
|
"Options": "Optionen",
|
||||||
"Out of Sync": "Nicht synchronisiert",
|
"Out of Sync": "Nicht synchronisiert",
|
||||||
"Out of Sync Items": "Nicht synchronisierte Elemente",
|
"Out of Sync Items": "Nicht synchronisierte Elemente",
|
||||||
@@ -297,7 +288,7 @@
|
|||||||
"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": "Pfad zum Ordner auf dem lokalen Gerät. Ordner wird erzeugt, wenn er nicht existiert. Das Tilde-Zeichen (~) kann als Abkürzung verwendet werden für",
|
"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": "Pfad zum Ordner auf dem lokalen Gerät. Ordner wird erzeugt, wenn er nicht existiert. Das Tilde-Zeichen (~) kann als Abkürzung verwendet werden für",
|
||||||
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Pfad, in dem Versionen gespeichert werden sollen (leer lassen für den Standardordner .stversions im freigegebenen Ordner).",
|
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Pfad, in dem Versionen gespeichert werden sollen (leer lassen für den Standardordner .stversions im freigegebenen Ordner).",
|
||||||
"Paths": "Pfade",
|
"Paths": "Pfade",
|
||||||
"Pause": "Pausiert",
|
"Pause": "Pause",
|
||||||
"Pause All": "Alles pausieren",
|
"Pause All": "Alles pausieren",
|
||||||
"Paused": "Pausiert",
|
"Paused": "Pausiert",
|
||||||
"Paused (Unused)": "Pausiert (nicht genutzt)",
|
"Paused (Unused)": "Pausiert (nicht genutzt)",
|
||||||
@@ -379,7 +370,7 @@
|
|||||||
"Show diff with previous version": "Unterschied zur vorherigen Version anzeigen",
|
"Show diff with previous version": "Unterschied zur vorherigen Version anzeigen",
|
||||||
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Wird anstelle der Gerätekennung im Verbundstatus angezeigt. Wird anderen Geräten als optionaler Standardname bekannt gegeben.",
|
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Wird anstelle der Gerätekennung im Verbundstatus angezeigt. Wird anderen Geräten als optionaler Standardname bekannt gegeben.",
|
||||||
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Wird anstelle der Gerätekennung im Verbundstatus angezeigt. Wird auf den Namen aktualisiert, den das Gerät anzeigt, wenn er leer bleibt.",
|
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Wird anstelle der Gerätekennung im Verbundstatus angezeigt. Wird auf den Namen aktualisiert, den das Gerät anzeigt, wenn er leer bleibt.",
|
||||||
"Shut Down": "Herunterfahren",
|
"Shutdown": "Herunterfahren",
|
||||||
"Shutdown Complete": "Vollständig heruntergefahren",
|
"Shutdown Complete": "Vollständig heruntergefahren",
|
||||||
"Simple": "Einfach",
|
"Simple": "Einfach",
|
||||||
"Simple File Versioning": "Einfache Dateiversionierung",
|
"Simple File Versioning": "Einfache Dateiversionierung",
|
||||||
@@ -396,7 +387,6 @@
|
|||||||
"Staggered": "Stufenweise",
|
"Staggered": "Stufenweise",
|
||||||
"Staggered File Versioning": "Stufenweise Dateiversionierung",
|
"Staggered File Versioning": "Stufenweise Dateiversionierung",
|
||||||
"Start Browser": "Browser starten",
|
"Start Browser": "Browser starten",
|
||||||
"Starting": "Startet",
|
|
||||||
"Statistics": "Statistiken",
|
"Statistics": "Statistiken",
|
||||||
"Stay logged in": "Angemeldet bleiben",
|
"Stay logged in": "Angemeldet bleiben",
|
||||||
"Stopped": "Gestoppt",
|
"Stopped": "Gestoppt",
|
||||||
|
|||||||
@@ -368,7 +368,7 @@
|
|||||||
"Show diff with previous version": "Εμφάνιση διαφορών με προηγούμενη έκδοση",
|
"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 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.": "Θα φαίνεται αντί για την ταυτότητα της συσκευής στην προβολή της κατάστασης ολόκληρης της συστάδας. Θα ενημερώνεται αυτόματα αν αλλάξει το όνομα της συσκευής.",
|
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Θα φαίνεται αντί για την ταυτότητα της συσκευής στην προβολή της κατάστασης ολόκληρης της συστάδας. Θα ενημερώνεται αυτόματα αν αλλάξει το όνομα της συσκευής.",
|
||||||
"Shut Down": "Απενεργοποίηση",
|
"Shutdown": "Απενεργοποίηση",
|
||||||
"Shutdown Complete": "Πλήρης απενεργοποίηση",
|
"Shutdown Complete": "Πλήρης απενεργοποίηση",
|
||||||
"Simple": "Απλό",
|
"Simple": "Απλό",
|
||||||
"Simple File Versioning": "Απλή τήρηση εκδόσεων",
|
"Simple File Versioning": "Απλή τήρηση εκδόσεων",
|
||||||
@@ -397,7 +397,7 @@
|
|||||||
"Sync Protocol Listen Addresses": "Διευθύνσεις για το πρωτόκολλο συγχρονισμού",
|
"Sync Protocol Listen Addresses": "Διευθύνσεις για το πρωτόκολλο συγχρονισμού",
|
||||||
"Sync Status": "Κατάσταση συγχρονισμού",
|
"Sync Status": "Κατάσταση συγχρονισμού",
|
||||||
"Syncing": "Συγχρονίζω",
|
"Syncing": "Συγχρονίζω",
|
||||||
"Syncthing device ID for \"{%devicename%}\"": "Αναγνωριστικό Syncthing για συσκευή \"{{devicename}}\"",
|
"Syncthing device ID for \"{%devicename%}\"": "Συγχρονισμός αναγνωριστικού συσκευής για \"{{devicename}}\"",
|
||||||
"Syncthing has been shut down.": "Το Syncthing έχει απενεργοποιηθεί.",
|
"Syncthing has been shut down.": "Το Syncthing έχει απενεργοποιηθεί.",
|
||||||
"Syncthing includes the following software or portions thereof:": "Το 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 Free and Open Source Software licensed as MPL v2.0.": "Το Syncthing είναι ελεύθερο λογισμικό και ανοικτού κώδικα, με άδεια MPL v2.0.",
|
||||||
|
|||||||
@@ -353,7 +353,7 @@
|
|||||||
"Show diff with previous version": "Show diff with previous version",
|
"Show diff with previous version": "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 advertised to other devices as an optional default name.",
|
"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 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.": "Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.",
|
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.",
|
||||||
"Shut Down": "Shut Down",
|
"Shutdown": "Shutdown",
|
||||||
"Shutdown Complete": "Shutdown Complete",
|
"Shutdown Complete": "Shutdown Complete",
|
||||||
"Simple": "Simple",
|
"Simple": "Simple",
|
||||||
"Simple File Versioning": "Simple File Versioning",
|
"Simple File Versioning": "Simple File Versioning",
|
||||||
|
|||||||
@@ -50,7 +50,6 @@
|
|||||||
"Automatically create or share folders that this device advertises at the default path.": "Automatically create or share folders that this device advertises at the default path.",
|
"Automatically create or share folders that this device advertises at the default path.": "Automatically create or share folders that this device advertises at the default path.",
|
||||||
"Available debug logging facilities:": "Available debug logging facilities:",
|
"Available debug logging facilities:": "Available debug logging facilities:",
|
||||||
"Be careful!": "Be careful!",
|
"Be careful!": "Be careful!",
|
||||||
"Block Indexing": "Block Indexing",
|
|
||||||
"Body:": "Body:",
|
"Body:": "Body:",
|
||||||
"Bugs": "Bugs",
|
"Bugs": "Bugs",
|
||||||
"Cancel": "Cancel",
|
"Cancel": "Cancel",
|
||||||
@@ -83,7 +82,6 @@
|
|||||||
"Custom Range": "Custom Range",
|
"Custom Range": "Custom Range",
|
||||||
"Danger!": "Danger!",
|
"Danger!": "Danger!",
|
||||||
"Database Location": "Database Location",
|
"Database Location": "Database Location",
|
||||||
"Debug": "Debug",
|
|
||||||
"Debugging Facilities": "Debugging Facilities",
|
"Debugging Facilities": "Debugging Facilities",
|
||||||
"Default": "Default",
|
"Default": "Default",
|
||||||
"Default Configuration": "Default Configuration",
|
"Default Configuration": "Default Configuration",
|
||||||
@@ -100,7 +98,6 @@
|
|||||||
"Device": "Device",
|
"Device": "Device",
|
||||||
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Device \"{{name}}\" ({{device}} at {{address}}) wants to connect. Add new device?",
|
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Device \"{{name}}\" ({{device}} at {{address}}) wants to connect. Add new device?",
|
||||||
"Device Certificate": "Device Certificate",
|
"Device Certificate": "Device Certificate",
|
||||||
"Device Group": "Device Group",
|
|
||||||
"Device ID": "Device ID",
|
"Device ID": "Device ID",
|
||||||
"Device Identification": "Device Identification",
|
"Device Identification": "Device Identification",
|
||||||
"Device Name": "Device Name",
|
"Device Name": "Device Name",
|
||||||
@@ -171,7 +168,6 @@
|
|||||||
"Filter by date": "Filter by date",
|
"Filter by date": "Filter by date",
|
||||||
"Filter by name": "Filter by name",
|
"Filter by name": "Filter by name",
|
||||||
"Folder": "Folder",
|
"Folder": "Folder",
|
||||||
"Folder Group": "Folder Group",
|
|
||||||
"Folder ID": "Folder ID",
|
"Folder ID": "Folder ID",
|
||||||
"Folder Label": "Folder Label",
|
"Folder Label": "Folder Label",
|
||||||
"Folder Path": "Folder Path",
|
"Folder Path": "Folder Path",
|
||||||
@@ -214,7 +210,6 @@
|
|||||||
"Incoming Rate Limit (KiB/s)": "Incoming Rate Limit (KiB/s)",
|
"Incoming Rate Limit (KiB/s)": "Incoming Rate Limit (KiB/s)",
|
||||||
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Incorrect configuration may damage your folder contents and render Syncthing inoperable.",
|
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Incorrect configuration may damage your folder contents and render Syncthing inoperable.",
|
||||||
"Incorrect user name or password.": "Incorrect username or password.",
|
"Incorrect user name or password.": "Incorrect username or password.",
|
||||||
"Info": "Info",
|
|
||||||
"Internally used paths:": "Internally used paths:",
|
"Internally used paths:": "Internally used paths:",
|
||||||
"Introduced By": "Introduced By",
|
"Introduced By": "Introduced By",
|
||||||
"Introducer": "Introducer",
|
"Introducer": "Introducer",
|
||||||
@@ -232,7 +227,6 @@
|
|||||||
"Learn more": "Learn more",
|
"Learn more": "Learn more",
|
||||||
"Learn more at {%url%}": "Learn more at {{url}}",
|
"Learn more at {%url%}": "Learn more at {{url}}",
|
||||||
"Limit": "Limit",
|
"Limit": "Limit",
|
||||||
"Limit Bandwidth in LAN": "Limit Bandwidth in LAN",
|
|
||||||
"Listener Failures": "Listener Failures",
|
"Listener Failures": "Listener Failures",
|
||||||
"Listener Status": "Listener Status",
|
"Listener Status": "Listener Status",
|
||||||
"Listeners": "Listeners",
|
"Listeners": "Listeners",
|
||||||
@@ -252,7 +246,6 @@
|
|||||||
"Log tailing paused. Scroll to the bottom to continue.": "Log tailing paused. Scroll to the bottom to continue.",
|
"Log tailing paused. Scroll to the bottom to continue.": "Log tailing paused. Scroll to the bottom to continue.",
|
||||||
"Login failed, see Syncthing logs for details.": "Login failed, see Syncthing logs for details.",
|
"Login failed, see Syncthing logs for details.": "Login failed, see Syncthing logs for details.",
|
||||||
"Logs": "Logs",
|
"Logs": "Logs",
|
||||||
"Maintain an index of all blocks in the folder, enabling reuse of blocks from other files when syncing changes. Disable to reduce database size at the cost of not being able to reuse blocks across files.": "Maintain an index of all blocks in the folder, enabling reuse of blocks from other files when syncing changes. Disable to reduce database size at the cost of not being able to reuse blocks across files.",
|
|
||||||
"Major Upgrade": "Major Upgrade",
|
"Major Upgrade": "Major Upgrade",
|
||||||
"Mass actions": "Mass actions",
|
"Mass actions": "Mass actions",
|
||||||
"Maximum Age": "Maximum Age",
|
"Maximum Age": "Maximum Age",
|
||||||
@@ -283,8 +276,6 @@
|
|||||||
"Off": "Off",
|
"Off": "Off",
|
||||||
"Oldest First": "Oldest First",
|
"Oldest First": "Oldest First",
|
||||||
"Optional descriptive label for the folder. Can be different on each device.": "Optional descriptive label for the folder. Can be different on each device.",
|
"Optional descriptive label for the folder. Can be different on each device.": "Optional descriptive label for the folder. Can be different on each device.",
|
||||||
"Optional group for the device. Can be different on each device.": "Optional group for the device. Can be different on each device.",
|
|
||||||
"Optional group for the folder. Can be different on each device.": "Optional group for the folder. Can be different on each device.",
|
|
||||||
"Options": "Options",
|
"Options": "Options",
|
||||||
"Out of Sync": "Out of Sync",
|
"Out of Sync": "Out of Sync",
|
||||||
"Out of Sync Items": "Out of Sync Items",
|
"Out of Sync Items": "Out of Sync Items",
|
||||||
@@ -379,7 +370,7 @@
|
|||||||
"Show diff with previous version": "Show diff with previous version",
|
"Show diff with previous version": "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 advertised to other devices as an optional default name.",
|
"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 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.": "Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.",
|
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.",
|
||||||
"Shut Down": "Shut Down",
|
"Shutdown": "Shutdown",
|
||||||
"Shutdown Complete": "Shutdown Complete",
|
"Shutdown Complete": "Shutdown Complete",
|
||||||
"Simple": "Simple",
|
"Simple": "Simple",
|
||||||
"Simple File Versioning": "Simple File Versioning",
|
"Simple File Versioning": "Simple File Versioning",
|
||||||
@@ -396,7 +387,6 @@
|
|||||||
"Staggered": "Staggered",
|
"Staggered": "Staggered",
|
||||||
"Staggered File Versioning": "Staggered File Versioning",
|
"Staggered File Versioning": "Staggered File Versioning",
|
||||||
"Start Browser": "Start Browser",
|
"Start Browser": "Start Browser",
|
||||||
"Starting": "Starting",
|
|
||||||
"Statistics": "Statistics",
|
"Statistics": "Statistics",
|
||||||
"Stay logged in": "Stay logged in",
|
"Stay logged in": "Stay logged in",
|
||||||
"Stopped": "Stopped",
|
"Stopped": "Stopped",
|
||||||
|
|||||||
@@ -50,7 +50,6 @@
|
|||||||
"Automatically create or share folders that this device advertises at the default path.": "Automatically create or share folders that this device advertises at the default path.",
|
"Automatically create or share folders that this device advertises at the default path.": "Automatically create or share folders that this device advertises at the default path.",
|
||||||
"Available debug logging facilities:": "Available debug logging facilities:",
|
"Available debug logging facilities:": "Available debug logging facilities:",
|
||||||
"Be careful!": "Be careful!",
|
"Be careful!": "Be careful!",
|
||||||
"Block Indexing": "Block Indexing",
|
|
||||||
"Body:": "Body:",
|
"Body:": "Body:",
|
||||||
"Bugs": "Bugs",
|
"Bugs": "Bugs",
|
||||||
"Cancel": "Cancel",
|
"Cancel": "Cancel",
|
||||||
@@ -83,7 +82,6 @@
|
|||||||
"Custom Range": "Custom Range",
|
"Custom Range": "Custom Range",
|
||||||
"Danger!": "Danger!",
|
"Danger!": "Danger!",
|
||||||
"Database Location": "Database Location",
|
"Database Location": "Database Location",
|
||||||
"Debug": "Debug",
|
|
||||||
"Debugging Facilities": "Debugging Facilities",
|
"Debugging Facilities": "Debugging Facilities",
|
||||||
"Default": "Default",
|
"Default": "Default",
|
||||||
"Default Configuration": "Default Configuration",
|
"Default Configuration": "Default Configuration",
|
||||||
@@ -100,7 +98,6 @@
|
|||||||
"Device": "Device",
|
"Device": "Device",
|
||||||
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Device \"{{name}}\" ({{device}} at {{address}}) wants to connect. Add new device?",
|
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Device \"{{name}}\" ({{device}} at {{address}}) wants to connect. Add new device?",
|
||||||
"Device Certificate": "Device Certificate",
|
"Device Certificate": "Device Certificate",
|
||||||
"Device Group": "Device Group",
|
|
||||||
"Device ID": "Device ID",
|
"Device ID": "Device ID",
|
||||||
"Device Identification": "Device Identification",
|
"Device Identification": "Device Identification",
|
||||||
"Device Name": "Device Name",
|
"Device Name": "Device Name",
|
||||||
@@ -171,7 +168,6 @@
|
|||||||
"Filter by date": "Filter by date",
|
"Filter by date": "Filter by date",
|
||||||
"Filter by name": "Filter by name",
|
"Filter by name": "Filter by name",
|
||||||
"Folder": "Folder",
|
"Folder": "Folder",
|
||||||
"Folder Group": "Folder Group",
|
|
||||||
"Folder ID": "Folder ID",
|
"Folder ID": "Folder ID",
|
||||||
"Folder Label": "Folder Label",
|
"Folder Label": "Folder Label",
|
||||||
"Folder Path": "Folder Path",
|
"Folder Path": "Folder Path",
|
||||||
@@ -214,7 +210,6 @@
|
|||||||
"Incoming Rate Limit (KiB/s)": "Incoming Rate Limit (KiB/s)",
|
"Incoming Rate Limit (KiB/s)": "Incoming Rate Limit (KiB/s)",
|
||||||
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Incorrect configuration may damage your folder contents and render Syncthing inoperable.",
|
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Incorrect configuration may damage your folder contents and render Syncthing inoperable.",
|
||||||
"Incorrect user name or password.": "Incorrect user name or password.",
|
"Incorrect user name or password.": "Incorrect user name or password.",
|
||||||
"Info": "Info",
|
|
||||||
"Internally used paths:": "Internally used paths:",
|
"Internally used paths:": "Internally used paths:",
|
||||||
"Introduced By": "Introduced By",
|
"Introduced By": "Introduced By",
|
||||||
"Introducer": "Introducer",
|
"Introducer": "Introducer",
|
||||||
@@ -232,7 +227,6 @@
|
|||||||
"Learn more": "Learn more",
|
"Learn more": "Learn more",
|
||||||
"Learn more at {%url%}": "Learn more at {{url}}",
|
"Learn more at {%url%}": "Learn more at {{url}}",
|
||||||
"Limit": "Limit",
|
"Limit": "Limit",
|
||||||
"Limit Bandwidth in LAN": "Limit Bandwidth in LAN",
|
|
||||||
"Listener Failures": "Listener Failures",
|
"Listener Failures": "Listener Failures",
|
||||||
"Listener Status": "Listener Status",
|
"Listener Status": "Listener Status",
|
||||||
"Listeners": "Listeners",
|
"Listeners": "Listeners",
|
||||||
@@ -252,7 +246,6 @@
|
|||||||
"Log tailing paused. Scroll to the bottom to continue.": "Log tailing paused. Scroll to the bottom to continue.",
|
"Log tailing paused. Scroll to the bottom to continue.": "Log tailing paused. Scroll to the bottom to continue.",
|
||||||
"Login failed, see Syncthing logs for details.": "Login failed, see Syncthing logs for details.",
|
"Login failed, see Syncthing logs for details.": "Login failed, see Syncthing logs for details.",
|
||||||
"Logs": "Logs",
|
"Logs": "Logs",
|
||||||
"Maintain an index of all blocks in the folder, enabling reuse of blocks from other files when syncing changes. Disable to reduce database size at the cost of not being able to reuse blocks across files.": "Maintain an index of all blocks in the folder, enabling reuse of blocks from other files when syncing changes. Disable to reduce database size at the cost of not being able to reuse blocks across files.",
|
|
||||||
"Major Upgrade": "Major Upgrade",
|
"Major Upgrade": "Major Upgrade",
|
||||||
"Mass actions": "Mass actions",
|
"Mass actions": "Mass actions",
|
||||||
"Maximum Age": "Maximum Age",
|
"Maximum Age": "Maximum Age",
|
||||||
@@ -283,8 +276,6 @@
|
|||||||
"Off": "Off",
|
"Off": "Off",
|
||||||
"Oldest First": "Oldest First",
|
"Oldest First": "Oldest First",
|
||||||
"Optional descriptive label for the folder. Can be different on each device.": "Optional descriptive label for the folder. Can be different on each device.",
|
"Optional descriptive label for the folder. Can be different on each device.": "Optional descriptive label for the folder. Can be different on each device.",
|
||||||
"Optional group for the device. Can be different on each device.": "Optional group for the device. Can be different on each device.",
|
|
||||||
"Optional group for the folder. Can be different on each device.": "Optional group for the folder. Can be different on each device.",
|
|
||||||
"Options": "Options",
|
"Options": "Options",
|
||||||
"Out of Sync": "Out of Sync",
|
"Out of Sync": "Out of Sync",
|
||||||
"Out of Sync Items": "Out of Sync Items",
|
"Out of Sync Items": "Out of Sync Items",
|
||||||
@@ -379,7 +370,7 @@
|
|||||||
"Show diff with previous version": "Show diff with previous version",
|
"Show diff with previous version": "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 advertised to other devices as an optional default name.",
|
"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 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.": "Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.",
|
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.",
|
||||||
"Shut Down": "Shut Down",
|
"Shutdown": "Shutdown",
|
||||||
"Shutdown Complete": "Shutdown Complete",
|
"Shutdown Complete": "Shutdown Complete",
|
||||||
"Simple": "Simple",
|
"Simple": "Simple",
|
||||||
"Simple File Versioning": "Simple File Versioning",
|
"Simple File Versioning": "Simple File Versioning",
|
||||||
@@ -396,7 +387,6 @@
|
|||||||
"Staggered": "Staggered",
|
"Staggered": "Staggered",
|
||||||
"Staggered File Versioning": "Staggered File Versioning",
|
"Staggered File Versioning": "Staggered File Versioning",
|
||||||
"Start Browser": "Start Browser",
|
"Start Browser": "Start Browser",
|
||||||
"Starting": "Starting",
|
|
||||||
"Statistics": "Statistics",
|
"Statistics": "Statistics",
|
||||||
"Stay logged in": "Stay logged in",
|
"Stay logged in": "Stay logged in",
|
||||||
"Stopped": "Stopped",
|
"Stopped": "Stopped",
|
||||||
|
|||||||
@@ -1,567 +1,369 @@
|
|||||||
{
|
{
|
||||||
"A device with that ID is already added.": "Aparato kun tiu identigilo estis jam aldonita.",
|
"A device with that ID is already added.": "Aparato kun samtia ID estis jam aldonita.",
|
||||||
"A negative number of days doesn't make sense.": "Negativa numero de tagoj ne havas sencon.",
|
"A negative number of days doesn't make sense.": "Negativa numero de tagoj ne havas sencon.",
|
||||||
"A new major version may not be compatible with previous versions.": "Nova ĉefa versio eble povas ne kongrui kun antaŭaj versioj.",
|
"A new major version may not be compatible with previous versions.": "Nova ĉefa versio eble ne kongruanta kun antaŭaj versioj.",
|
||||||
"API Key": "Ŝlosilo de aplikaĵa interfaco",
|
"API Key": "Ŝlosilo API",
|
||||||
"About": "Pri",
|
"About": "Pri",
|
||||||
"Action": "Ago",
|
"Action": "Ago",
|
||||||
"Actions": "Agoj",
|
"Actions": "Agoj",
|
||||||
"Active filter rules": "Aktivaj filtrilaj reguloj",
|
|
||||||
"Add": "Aldoni",
|
"Add": "Aldoni",
|
||||||
"Add Device": "Aldoni aparaton",
|
"Add Device": "Aldoni aparaton",
|
||||||
"Add Folder": "Aldoni dosierujon",
|
"Add Folder": "Aldoni dosierujon",
|
||||||
"Add Remote Device": "Aldoni foran aparaton",
|
"Add Remote Device": "Aldoni foran aparaton",
|
||||||
"Add devices from the introducer to our device list, for mutually shared folders.": "Aldoni aparatojn de la enkondukanto ĝis nia aparatlisto, por reciproke komunigitaj dosierujoj.",
|
"Add devices from the introducer to our device list, for mutually shared folders.": "Aldoni aparatojn de la enkondukanto ĝis nia aparatlisto, por reciproke komunigitaj dosierujoj.",
|
||||||
"Add filter entry": "Aldoni filtrilon",
|
"Add new folder?": "Ĉu aldoni novan dosierujon ?",
|
||||||
"Add ignore patterns": "Aldoni ŝablonon de ignorado",
|
"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.": "Aldone, plena reskana intervalo estos pliigita (60-oble, t.e. nova defaŭlto estas 1h). Vi povas ankaŭ agordi ĝin permane por ĉiu dosierujo poste post elekto de Ne.",
|
||||||
"Add new folder?": "Ĉu aldoni novan dosierujon?",
|
|
||||||
"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.": "Aldone, la plena intertempo de reskanado estos pliigita (60-oble, t.e. nova defaŭlto estas 1h). Vi povas ankaŭ agordi ĝin permane por ĉiu dosierujo poste post elekto de \"Ne\".",
|
|
||||||
"Address": "Adreso",
|
"Address": "Adreso",
|
||||||
"Addresses": "Adresoj",
|
"Addresses": "Adresoj",
|
||||||
"Advanced": "Altnivelaj",
|
"Advanced": "Altnivela",
|
||||||
"Advanced Configuration": "Altnivelaj agordoj",
|
"Advanced Configuration": "Altnivela Agordo",
|
||||||
"All Data": "Ĉiuj datumoj",
|
"All Data": "Ĉiuj Datumoj",
|
||||||
"All Time": "Ĉiam",
|
|
||||||
"All folders shared with this device must be protected by a password, such that all sent data is unreadable without the given password.": "Ĉiuj dosierujoj, kiuj estas dividitaj kun ĉi tiu aparato devas esti protektitaj per pasvorto, tiel ĉiuj senditaj datumoj estas nelegeblaj sen la pasvorto.",
|
"All folders shared with this device must be protected by a password, such that all sent data is unreadable without the given password.": "Ĉiuj dosierujoj, kiuj estas dividitaj kun ĉi tiu aparato devas esti protektitaj per pasvorto, tiel ĉiuj senditaj datumoj estas nelegeblaj sen la pasvorto.",
|
||||||
"Allow Anonymous Usage Reporting?": "Permesi anoniman raporton de uzado?",
|
"Allow Anonymous Usage Reporting?": "Permesi Anoniman Raporton de Uzado?",
|
||||||
"Allowed Networks": "Permesitaj retoj",
|
"Allowed Networks": "Permesitaj Retoj",
|
||||||
"Alphabetic": "Alfabeta",
|
"Alphabetic": "Alfabeta",
|
||||||
"Altered by ignoring deletes.": "Ŝanĝita per ignorado de forigadoj.",
|
"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.": "Ekstera komando manipulas la version. Ĝi devas forigi la dosieron el la komunigita dosierujo. Se la vojo al la apliko elhavas blankoj, ĝi devas esti inter citiloj.",
|
||||||
"Always turned on when the folder type is \"{%foldertype%}\".": "Ĉiam ŝaltita, kiam la tipo de dosierujo estas \"{{foldertype}}\".",
|
"Anonymous Usage Reporting": "Anonima Raporto de Uzado",
|
||||||
"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.": "Ekstera komando manipulas la konservadon de versioj. Ĝi devas forigi la dosieron el la komunigita dosierujo. Se la vojo al la aplikaĵo enhavas spacetojn, ĝi devas esti inter citiloj.",
|
"Anonymous usage report format has changed. Would you like to move to the new format?": "Formato de anonima raporto de uzado ŝanĝis. Ĉu vi ŝatus transiri al la nova formato?",
|
||||||
"Anonymous Usage Reporting": "Anonima raportado de uzado",
|
|
||||||
"Anonymous usage report format has changed. Would you like to move to the new format?": "Formato de anonima raporto de uzado ŝanĝiĝis. Ĉu vi ŝatus transiri al la nova formato?",
|
|
||||||
"Applied to LAN": "Aplikita en lokaj retoj",
|
|
||||||
"Apply": "Apliki",
|
|
||||||
"Are you sure you want to override all remote changes?": "Ĉu vi certas, ke vi volas transpasi ĉiujn forajn ŝanĝojn?",
|
"Are you sure you want to override all remote changes?": "Ĉu vi certas, ke vi volas transpasi ĉiujn forajn ŝanĝojn?",
|
||||||
"Are you sure you want to permanently delete all these files?": "Ĉu vi certas, ke vi volas porĉiame forigi ĉiujn ĉi tiujn dosierojn?",
|
"Are you sure you want to permanently delete all these files?": "Ĉu vi certas, ke vi volas porĉiame forigi ĉiujn ĉi tiujn dosierojn?",
|
||||||
"Are you sure you want to remove device {%name%}?": "Ĉu vi certas, ke vi volas forigi aparaton {{name}}?",
|
"Are you sure you want to remove device {%name%}?": "Ĉu vi certas, ke vi volas forigi aparaton {{name}}?",
|
||||||
"Are you sure you want to remove folder {%label%}?": "Ĉu vi certas, ke vi volas forigi dosierujon {{label}}?",
|
"Are you sure you want to remove folder {%label%}?": "Ĉu vi certas, ke vi volas forigi dosierujon {{label}}?",
|
||||||
"Are you sure you want to restore {%count%} files?": "Ĉu vi certas, ke vi volas restarigi {{count}} dosierojn?",
|
"Are you sure you want to restore {%count%} files?": "Ĉu vi certas, ke vi volas restarigi {{count}} dosierojn?",
|
||||||
"Are you sure you want to revert all local changes?": "Ĉu vi certas, ke vi volas malfari ĉiujn lokajn ŝanĝojn?",
|
"Are you sure you want to revert all local changes?": "Ĉu vi certas, ke vi volas malfari ĉiujn lokajn ŝanĝojn?",
|
||||||
"Are you sure you want to upgrade?": "Ĉu vi certe volas ĝisdatiĝi?",
|
"Are you sure you want to upgrade?": "Ĉu vi certe volas plinovigi ?",
|
||||||
"Authentication Required": "Aŭtentokontrolo bezonata",
|
"Auto Accept": "Akcepti Aŭtomate",
|
||||||
"Authors": "Aŭtoroj",
|
"Automatic Crash Reporting": "Aŭtomata raportado de kraŝoj",
|
||||||
"Auto Accept": "Akcepti aŭtomate",
|
|
||||||
"Automatic Crash Reporting": "Aŭtomata raportado de paneoj",
|
|
||||||
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Aŭtomata ĝisdatigo nun proponas la elekton inter stabilaj eldonoj kaj kandidataj eldonoj.",
|
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Aŭtomata ĝisdatigo nun proponas la elekton inter stabilaj eldonoj kaj kandidataj eldonoj.",
|
||||||
"Automatic upgrades": "Aŭtomataj ĝisdatigoj",
|
"Automatic upgrades": "Aŭtomataj ĝisdatigoj",
|
||||||
"Automatic upgrades are always enabled for candidate releases.": "Aŭtomataj ĝisdatigoj estas ĉiam ŝaltitaj por kandidataj eldonoj.",
|
"Automatic upgrades are always enabled for candidate releases.": "Aŭtomataj ĝisdatigoj ĉiam ŝaltitaj por kandidataj eldonoj.",
|
||||||
"Automatically create or share folders that this device advertises at the default path.": "Aŭtomate krei aŭ komunigi dosierujojn, kiujn ĉi tiu aparato anoncas ĉe la defaŭlta vojo.",
|
"Automatically create or share folders that this device advertises at the default path.": "Aŭtomate krei aŭ komunigi dosierujojn, kiujn ĉi tiu aparato anoncas, ĉe la defaŭlta vojo.",
|
||||||
"Available debug logging facilities:": "Disponeblaj sencimigaj protokoliloj:",
|
"Available debug logging facilities:": "Disponeblaj elpurigadaj protokoliloj:",
|
||||||
"Be careful!": "Atentu!",
|
"Be careful!": "Atentu!",
|
||||||
"Block Indexing": "Indeksado de blokoj",
|
|
||||||
"Body:": "Enhavo:",
|
|
||||||
"Bugs": "Cimoj",
|
"Bugs": "Cimoj",
|
||||||
"Cancel": "Nuligi",
|
"Cancel": "Nuligi",
|
||||||
"Cannot be enabled when the folder type is \"{%foldertype%}\".": "Ne povas esti ŝaltita, kiam la tipo de dosierujo estas \"{{foldertype}}\".",
|
|
||||||
"Changelog": "Ŝanĝoprotokolo",
|
"Changelog": "Ŝanĝoprotokolo",
|
||||||
"Clean out after": "Purigi post",
|
"Clean out after": "Purigi poste",
|
||||||
"Cleaning Versions": "Purigado de versioj",
|
"Click to see full identification string and QR code.": "Alklaku por vidi la plenan identigan signovicon kaj QR-kodo",
|
||||||
"Cleanup Interval": "Intertempo de purigado",
|
|
||||||
"Click to see full identification string and QR code.": "Alklaku por vidi la plenan identigan signovicon kaj QR-kodon.",
|
|
||||||
"Close": "Fermi",
|
"Close": "Fermi",
|
||||||
"Command": "Komando",
|
"Command": "Komando",
|
||||||
"Comment, when used at the start of a line": "Komento, kiam uzata ĉe la komenco de linio",
|
"Comment, when used at the start of a line": "Komento, kiam uzita ĉe la komenco de lineo",
|
||||||
"Compression": "Densigo",
|
"Compression": "Densigo",
|
||||||
"Configuration Directory": "Dosierujo de agordaro",
|
|
||||||
"Configuration File": "Dosiero de agordaro",
|
|
||||||
"Configured": "Agordita",
|
"Configured": "Agordita",
|
||||||
"Connected (Unused)": "Konektita (ne uzata)",
|
"Connection Error": "Eraro de Konekto",
|
||||||
"Connection Error": "Eraro de konekto",
|
"Connection Type": "Tipo de Konekto",
|
||||||
"Connection Management": "Mastrumado de konektoj",
|
|
||||||
"Connection Type": "Tipo de konekto",
|
|
||||||
"Connections": "Konektoj",
|
"Connections": "Konektoj",
|
||||||
"Connections via relays might be rate limited by the relay": "Konektoj, kiu uzas plusendilon, povas esti limigitaj en ofteco fare de la plusendilo",
|
|
||||||
"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.": "Daŭra rigardado je ŝanĝoj estas nun havebla ene Syncthing. Ĉi tio detektos ŝangoj sur disko kaj skanos nur modifitajn vojojn. La avantaĝo estas en pli rapifa propagiĝo de ŝanĝoj kaj bezono je malpli plenaj skanoj.",
|
"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.": "Daŭra rigardado je ŝanĝoj estas nun havebla ene Syncthing. Ĉi tio detektos ŝangoj sur disko kaj skanos nur modifitajn vojojn. La avantaĝo estas en pli rapifa propagiĝo de ŝanĝoj kaj bezono je malpli plenaj skanoj.",
|
||||||
"Copied from elsewhere": "Kopiita el aliloke",
|
"Copied from elsewhere": "Kopiita el aliloke",
|
||||||
"Copied from original": "Kopiita el la originalo",
|
"Copied from original": "Kopiita el la originalo",
|
||||||
"Copied!": "Kopiita!",
|
"Currently Shared With Devices": "Nune komunigita kun aparatoj",
|
||||||
"Copy": "Kopii",
|
|
||||||
"Copy failed! Try to select and copy manually.": "Kopiado malsukcesis! Klopodu elekti kaj kopii permane.",
|
|
||||||
"Currently Shared With Devices": "Nune kunhavata kun aparatoj",
|
|
||||||
"Custom Range": "Propra intervalo",
|
|
||||||
"Danger!": "Danĝero!",
|
"Danger!": "Danĝero!",
|
||||||
"Database Location": "Loko de datumbazo",
|
"Debugging Facilities": "Elpurigadiloj",
|
||||||
"Debug": "Sencimigo",
|
|
||||||
"Debugging Facilities": "Sencimigiloj",
|
|
||||||
"Default": "Defaŭlta",
|
|
||||||
"Default Configuration": "Defaŭlta agordo",
|
"Default Configuration": "Defaŭlta agordo",
|
||||||
"Default Device": "Defaŭlta aparato",
|
"Default Folder": "Defaŭlta Dosierujo",
|
||||||
"Default Folder": "Defaŭlta dosierujo",
|
|
||||||
"Default Ignore Patterns": "Defaŭltaj ŝablonoj de ignorado",
|
|
||||||
"Defaults": "Defaŭltoj",
|
|
||||||
"Delete": "Forigu",
|
"Delete": "Forigu",
|
||||||
"Delete Unexpected Items": "Forigi neatenditajn erojn",
|
|
||||||
"Deleted {%file%}": "Forigita: {{file}}",
|
|
||||||
"Deselect All": "Malelekti Ĉiujn",
|
"Deselect All": "Malelekti Ĉiujn",
|
||||||
"Deselect devices to stop sharing this folder with.": "Malelekti aparatojn por ĉesi kunhavadon de tiu ĉi dosierujo kun ili.",
|
"Deselect devices to stop sharing this folder with.": "Malelekti aparatojn por ĉesi komunigi tiun ĉi dosierujon kun ili.",
|
||||||
"Deselect folders to stop sharing with this device.": "Malelekti dosierujojn por ĉesi kunhavadon kun tiu ĉi aparato.",
|
|
||||||
"Device": "Aparato",
|
"Device": "Aparato",
|
||||||
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Aparato \"{{name}}\" ({{device}} ĉe {{address}}) volas konekti. Aldoni la novan aparaton?",
|
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Aparato \"{{name}}\" ({{device}} ĉe {{address}}) volas konekti. Aldoni la novan aparaton?",
|
||||||
"Device Certificate": "Atestilo de aparato",
|
"Device ID": "Aparato ID",
|
||||||
"Device Group": "Grupo de aparato",
|
"Device Identification": "Identigo de Aparato",
|
||||||
"Device ID": "Identigilo de aparato",
|
"Device Name": "Nomo de Aparato",
|
||||||
"Device Identification": "Identigado de aparato",
|
|
||||||
"Device Name": "Nomo de aparato",
|
|
||||||
"Device Status": "Stato de aparato",
|
|
||||||
"Device is untrusted, enter encryption password": "Aparato ne estas fidinda, entajpu pasvorto por ĉifrado",
|
"Device is untrusted, enter encryption password": "Aparato ne estas fidinda, entajpu pasvorto por ĉifrado",
|
||||||
"Device rate limits": "Limoj de rapideco de aparato",
|
"Device rate limits": "Limoj de rapideco de aparato",
|
||||||
"Device that last modified the item": "Aparato, kiu laste modifis la eron",
|
"Device that last modified the item": "Aparato kiu laste modifis la eron",
|
||||||
"Devices": "Aparatoj",
|
"Devices": "Aparatoj",
|
||||||
"Disable Crash Reporting": "Malŝalti raportadon de kraŝoj",
|
"Disable Crash Reporting": "Malŝalti raportadon de kraŝoj",
|
||||||
"Disabled": "Malŝaltita",
|
"Disabled": "Malebligita",
|
||||||
"Disabled periodic scanning and disabled watching for changes": "Perioda skanado kaj atendado por ŝanĝoj malŝaltitaj",
|
"Disabled periodic scanning and disabled watching for changes": "Malebligita perioda skanado kaj malebligita rigardado je ŝanĝoj",
|
||||||
"Disabled periodic scanning and enabled watching for changes": "Perioda skanado malŝaltita kaj atendado por ŝanĝoj ŝaltita",
|
"Disabled periodic scanning and enabled watching for changes": "Malebligita perioda skanado kaj ebligita rigardado je ŝanĝoj",
|
||||||
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Perioda skanado malŝaltita kaj agordado de atendado por ŝanĝoj malsukcesis, reprovado ĉiuminute:",
|
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Malebligita perioda skanado kaj malsukcesis agordi rigardadon je ŝanĝoj. Provante denove ĉiuminute:",
|
||||||
"Disables comparing and syncing file permissions. Useful on systems with nonexistent or custom permissions (e.g. FAT, exFAT, Synology, Android).": "Malŝaltas komparadon kaj sinkronigadon de dosieraj permesoj. Uzinda por operaciumoj kaj dosiersistemoj, en kiuj mankas aŭ nekongruas permesoj (ekz. FAT, exFAT, Synology, Android).",
|
|
||||||
"Discard": "Forĵeti",
|
"Discard": "Forĵeti",
|
||||||
"Disconnected": "Malkonektita",
|
"Disconnected": "Malkonektita",
|
||||||
"Disconnected (Inactive)": "Malkonektita (malaktiva)",
|
|
||||||
"Disconnected (Unused)": "Malkonektita (ne uzata)",
|
|
||||||
"Discovered": "Malkovrita",
|
"Discovered": "Malkovrita",
|
||||||
"Discovery": "Malkovro",
|
"Discovery": "Malkovro",
|
||||||
"Discovery Failures": "Malsukcesoj de malkovrado",
|
"Discovery Failures": "Malsukcesoj de Malkovro",
|
||||||
"Discovery Status": "Stato de malkovrado",
|
|
||||||
"Dismiss": "Forsendi",
|
|
||||||
"Do not add it to the ignore list, so this notification may recur.": "Ne aldonu ĝin al la listo de ignoraĵoj, por ke tiu ĉi sciigo povos reaperi poste.",
|
|
||||||
"Do not restore": "Ne restarigu",
|
"Do not restore": "Ne restarigu",
|
||||||
"Do not restore all": "Ne restarigu ĉion",
|
"Do not restore all": "Ne restarigu ĉion",
|
||||||
"Do you want to enable watching for changes for all your folders?": "Ĉu vi volas ebligi atendadon por ŝanĝoj por ĉiuj viaj dosierujoj?",
|
"Do you want to enable watching for changes for all your folders?": "Ĉu vi volas ebligi rigardado je ŝanĝoj por ĉiuj viaj dosierujoj?",
|
||||||
"Documentation": "Dokumentado",
|
"Documentation": "Dokumentado",
|
||||||
"Download Rate": "Elŝutrapideco",
|
"Download Rate": "Elŝutrapideco",
|
||||||
"Downloaded": "Elŝutita",
|
"Downloaded": "Elŝutita",
|
||||||
"Downloading": "Elŝutado",
|
"Downloading": "Elŝutado",
|
||||||
"Edit": "Redakti",
|
"Edit": "Redakti",
|
||||||
"Edit Device": "Redakti aparaton",
|
"Edit Device": "Redakti Aparaton",
|
||||||
"Edit Device Defaults": "Redakti aparatajn defaŭltojn",
|
"Edit Folder": "Redakti Dosierujon",
|
||||||
"Edit Folder": "Redakti dosierujon",
|
|
||||||
"Edit Folder Defaults": "Redakti dosierujajn defaŭltojn",
|
|
||||||
"Editing {%path%}.": "Redaktado de {{path}}.",
|
"Editing {%path%}.": "Redaktado de {{path}}.",
|
||||||
"Enable Crash Reporting": "Ŝalti raportadon de paneoj",
|
"Enable Crash Reporting": "Ŝalti raportadon de kraŝoj",
|
||||||
"Enable NAT traversal": "Ŝaltu trairadon de NAT",
|
"Enable NAT traversal": "Ŝaltu trairan NAT",
|
||||||
"Enable Relaying": "Ŝalti plusendadon",
|
"Enable Relaying": "Ŝaltu Relajsadon",
|
||||||
"Enabled": "Ŝaltita",
|
"Enabled": "Ebligita",
|
||||||
"Enables sending extended attributes to other devices, and applying incoming extended attributes. May require running with elevated privileges.": "Ŝaltas sendadon de aldonaj dosieraj atributoj al aliaj aparatoj, kaj aplikadon de alvenantaj atributoj. Ĉi tio povas postuli, ke la programo estas rulata kun pliigitaj permesoj.",
|
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Enigu ne negativan nombron (ekz. \"2.35\") kaj elektu uniton. Procentoj estas kiel parto de tuta grandeco de disko.",
|
||||||
"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.": "Ŝaltas sendadon de aldonaj dosieraj atributoj al aliaj aparatoj, sed ne aplikadon de alvenantaj atributoj. Ĉi tio povas grave influi rendimenton de la programo. Ĉiam ŝaltita, se \"Sinkronigi aldonajn atributojn\" estas ŝaltita.",
|
|
||||||
"Enables sending ownership information to other devices, and applying incoming ownership information. Typically requires running with elevated privileges.": "Ŝaltas sendadon de informoj pri propreco al aliaj aparatoj, kaj aplikadon de alvenantaj informoj. Ĉi tio ofte postulas, ke la programo estas rulata kun pliigitaj permesoj.",
|
|
||||||
"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.": "Ŝaltas sendadon de informoj pri propreco al aliaj aparatoj, sed ne aplikadon de alvenantaj informoj. Ĉi tio povas grave influi rendimenton de programo. Ĉiam ŝaltita, se \"Sinkronigi proprecon\" estas ŝaltita.",
|
|
||||||
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Enigu ne negativan nombron (ekz. \"2.35\") kaj elektu uniton. Elcentoj funkcias kiel parto de la tuta grandeco de disko.",
|
|
||||||
"Enter a non-privileged port number (1024 - 65535).": "Enigu ne privilegiitan numeron de pordo (1024- 65535).",
|
"Enter a non-privileged port number (1024 - 65535).": "Enigu ne privilegiitan numeron de pordo (1024- 65535).",
|
||||||
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Enigu adresojn, dividitajn per komoj (\"tcp://ip:port\", \"tcp://host:port\"), aŭ \"dynamic\" por elfari aŭtomatan malkovradon de la adreso.",
|
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Enigu adresojn dividitajn per komoj (\"tcp://ip:port\", \"tcp://host:port\") aŭ \"dynamic\" por elfari aŭtomatan malkovradon de la adreso.",
|
||||||
"Enter ignore patterns, one per line.": "Enigu ŝablonojn de ignorado, unu po linio.",
|
"Enter ignore patterns, one per line.": "Enigu ignorantajn ŝablonojn, unu po linio.",
|
||||||
"Enter up to three octal digits.": "Entajpu ĝis tri okumajn ciferojn.",
|
"Enter up to three octal digits.": "Entajpu ĝis tri okumajn ciferojn.",
|
||||||
"Error": "Eraro",
|
"Error": "Eraro",
|
||||||
"Extended Attributes": "Aldonaj atributoj",
|
"External File Versioning": "Ekstera Versionado de Dosiero",
|
||||||
"Extended Attributes Filter": "Filtrilo de aldonaj atributoj",
|
"Failed Items": "Malsukcesaj Eroj",
|
||||||
"External": "Ekstera",
|
"Failed to set up, retrying": "Malsukcesis agordi, provante denove",
|
||||||
"External File Versioning": "Ekstera versiado de dosieroj",
|
"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.",
|
||||||
"Failed Items": "Malsukcesaj eroj",
|
"File Pull Order": "Ordo por Tiri Dosieron",
|
||||||
"Failed to load file versions.": "Eraro dum ŝarĝado de dosieraj versioj.",
|
"File Versioning": "Versionado de Dosieroj",
|
||||||
"Failed to load ignore patterns.": "Eraro dum ŝarĝado de ŝablonoj de ignorado.",
|
"Files are moved to .stversions directory when replaced or deleted by Syncthing.": "Dosieroj estas movigitaj al .stversions dosierujo kiam anstataŭigitaj aŭ forigitaj en Syncthing.",
|
||||||
"Failed to set up, retrying": "Agordado malsukcesis, reprovado",
|
"Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.": "Dosieroj estas movigitaj al date stampitaj versioj en .stversions dosierujo kiam ili estas anstataŭigitaj aŭ forigitaj en Syncthing.",
|
||||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "Malsukceso dum konekto al serviloj de protokolo IPv6 estas atendebla, se ekzistas neniu konekteblo per IPv6.",
|
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Dosieroj estas protektataj kontraŭ ŝanĝoj faritaj en aliaj aparatoj, sed ŝanĝoj faritaj en ĉi tiu aparato estos senditaj al cetera parto de la grupo.",
|
||||||
"File Pull Order": "Ordo por tirado de dosieroj",
|
"Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.": "Dosieroj estas sinkronigitaj de la grupo, sed ajnaj ŝanĝoj faritaj loke ne estis senditaj al aliaj aparatoj.",
|
||||||
"File Versioning": "Versiado de dosieroj",
|
"Filesystem Watcher Errors": "Eraroj de Rigardanto de Dosiersistemo",
|
||||||
"Files are moved to .stversions directory when replaced or deleted by Syncthing.": "Dosieroj estas movigitaj al dosierujo \".stversions\", kiam anstataŭigitaj aŭ forigitaj de Syncthing.",
|
"Filter by date": "Filtri per daton",
|
||||||
"Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.": "Dosieroj estas movigitaj al dato-stampitaj versioj en dosierujo \".stversions\", kiam anstataŭigitaj aŭ forigitaj de Syncthing.",
|
"Filter by name": "Filtri per nomon",
|
||||||
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Dosieroj estas protektataj kontraŭ ŝanĝoj, faritaj en aliaj aparatoj, sed ŝanĝoj, faritaj en ĉi tiu aparato, estos senditaj al la restantaj aparatoj.",
|
|
||||||
"Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.": "Dosieroj estas sinkronigitaj de la aparataro, sed ajnaj ŝanĝoj, faritaj loke, ne estos senditaj al aliaj aparatoj.",
|
|
||||||
"Filesystem Watcher Errors": "Eraroj de atendado por dosiersistemo",
|
|
||||||
"Filter by date": "Filtri per dato",
|
|
||||||
"Filter by name": "Filtri per nomo",
|
|
||||||
"Folder": "Dosierujo",
|
"Folder": "Dosierujo",
|
||||||
"Folder Group": "Grupo de dosierujoj",
|
"Folder ID": "Dosieruja ID",
|
||||||
"Folder ID": "Dosieruja identigilo",
|
"Folder Label": "Dosieruja Etikedo",
|
||||||
"Folder Label": "Dosieruja etikedo",
|
"Folder Path": "Dosieruja Vojo",
|
||||||
"Folder Path": "Dosieruja vojo",
|
"Folder Type": "Dosieruja Tipo",
|
||||||
"Folder Status": "Stato de dosierujo",
|
|
||||||
"Folder Type": "Dosieruja tipo",
|
|
||||||
"Folder type \"{%receiveEncrypted%}\" can only be set when adding a new folder.": "Dosieruja tipo \"{{receiveEncrypted}}\" povas esti agordita nur dum aldonado de nova dosierujo.",
|
|
||||||
"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.": "Dosieruja tipo \"{{receiveEncrypted}}\" ne povas esti ŝanĝita post aldonado de dosierujo. Vi devas forigi la dosierujon, forigi aŭ malĉifri la datumoj en la disko, kaj aldoni la dosierujon denove.",
|
|
||||||
"Folders": "Dosierujoj",
|
"Folders": "Dosierujoj",
|
||||||
"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.": "Por la sekvantaj dosierujoj eraro okazis dum komencado de atendado por ŝanĝoj. Reprovado okazos ĉiuminute, do eraroj eble foriros baldaŭ. Se ili persistas, provu ripari la problemon, kaŭzantan ĉi tion, kaj petu helpon, se vi ne povas.",
|
"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.": "Por la sekvantaj dosierujoj eraro okazis dum komencado de rigardado je ŝanĝoj. Provante denove ĉiuminute, do eraroj eble foriros baldaŭ. Se ili persistas, provu ripari subkuŝantan problemon kaj petu helpon, se vi ne povas.",
|
||||||
"Forever": "Por ĉiam",
|
"Full Rescan Interval (s)": "Plena Reskana Intervalo (s)",
|
||||||
"Full Rescan Interval (s)": "Intertempo de plena reskanado (s)",
|
"GUI": "Grafika Interfaco",
|
||||||
"GUI": "Grafika interfaco",
|
"GUI Authentication Password": "Pasvorta Aŭtentigo en Grafika Interfaco",
|
||||||
"GUI / API HTTPS Certificate": "Atestilo de HTTPS por grafika / aplikaĵa interfaco",
|
"GUI Authentication User": "Uzanta Aŭtentigo en Grafika Interfaco",
|
||||||
"GUI Authentication Password": "Pasvorto por aŭtentigo per grafika interfaco",
|
"GUI Listen Address": "Adreso de Aŭskultado en Grafika Interfaco",
|
||||||
"GUI Authentication User": "Uzanto por aŭtentigo per grafika interfaco",
|
"GUI Theme": "Etoso de Grafika Interfaco",
|
||||||
"GUI Authentication: Set User and Password": "Aŭtentigo por grafika interfaco: Agordi uzantnomon kaj pasvorton",
|
"General": "Ĝenerala",
|
||||||
"GUI Listen Address": "Adreso de aŭskultado por grafika interfaco",
|
|
||||||
"GUI Override Directory": "Dosierujo de transpaso por grafika interfaco",
|
|
||||||
"GUI Theme": "Etoso de grafika interfaco",
|
|
||||||
"General": "Ĝeneralaj",
|
|
||||||
"Generate": "Generi",
|
"Generate": "Generi",
|
||||||
"Global Discovery": "Malloka malkovrado",
|
"Global Discovery": "Malloka Malkovro",
|
||||||
"Global Discovery Servers": "Serviloj de malloka malkovrado",
|
"Global Discovery Servers": "Serviloj de Malloka Malkovro",
|
||||||
"Global State": "Malloka stato",
|
"Global State": "Malloka Stato",
|
||||||
"Help": "Helpo",
|
"Help": "Helpo",
|
||||||
"Hint: only deny-rules detected while the default is deny. Consider adding \"permit any\" as last rule.": "Avizo: nur negaj reguloj estas detektita, dum la defaŭlto estas negado. Pripensu aldonadon de \"permesu ĉion\" kiel la lasta regulo.",
|
|
||||||
"Home page": "Hejma paĝo",
|
"Home page": "Hejma paĝo",
|
||||||
"However, your current settings indicate you might not want it enabled. We have disabled automatic crash reporting for you.": "Tamen, viaj aktualaj agordoj indikas, ke vi ne volas ŝalti ĝin. Aŭtomata paneoraportado estis malŝaltita por vi.",
|
"If you want to prevent other users on this computer from accessing Syncthing and through it your files, consider setting up authentication.": "Se vi volas malhelpi aliajn uzantojn sur ĉi tiu komputilo atingi Syncthing kaj per ĝi viajn dosierojn, konsideru agordi aŭtentokontrolon.",
|
||||||
"Identification": "Identigo",
|
"Ignore": "Ignoru",
|
||||||
"If untrusted, enter encryption password": "Se ne fidinda, entajpu pasvorton por ĉifrado",
|
"Ignore Patterns": "Ignorantaj Ŝablonoj",
|
||||||
"If you want to prevent other users on this computer from accessing Syncthing and through it your files, consider setting up authentication.": "Se vi volas malhelpi aliajn uzantojn sur ĉi tiu komputilo atingi la programon Syncthing kaj, per ĝi, viajn dosierojn, pripensu agordi aŭtentigon.",
|
"Ignore Permissions": "Ignori Permesojn",
|
||||||
"Ignore": "Ignori",
|
"Ignored Devices": "Ignoritaj Aparatoj",
|
||||||
"Ignore Patterns": "Ŝablonoj de ignorado",
|
"Ignored Folders": "Ignoritaj Dosierujoj",
|
||||||
"Ignore Permissions": "Ignori permesojn",
|
|
||||||
"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.": "Ŝablonoj de ignorado povas esti aldonitaj nur post kreado de dosierujo. Se ŝaltita, tekstokampo por enigo de ŝablonoj estos prezentita post konservado.",
|
|
||||||
"Ignored Devices": "Ignoritaj aparatoj",
|
|
||||||
"Ignored Folders": "Ignoritaj dosierujoj",
|
|
||||||
"Ignored at": "Ignorita ĉe",
|
"Ignored at": "Ignorita ĉe",
|
||||||
"Included Software": "Inkluzivita programaro",
|
"Incoming Rate Limit (KiB/s)": "Alvenanta Rapideco Limo (KiB/s)",
|
||||||
"Incoming Rate Limit (KiB/s)": "Limigo de alvenanta rapideco (KiB/s)",
|
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Erara agordo povas difekti viajn dosierujajn enhavojn kaj senefikigi Syncthing-n.",
|
||||||
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Erara agordo povas difekti viajn dosierujajn enhavojn kaj senefikigi la programon Syncthing.",
|
"Introduced By": "Enkondukita Per",
|
||||||
"Incorrect user name or password.": "Malĝusta uzantnomo aŭ pasvorto.",
|
"Introducer": "Enkondukanto",
|
||||||
"Info": "Informo",
|
|
||||||
"Internally used paths:": "Interne uzataj vojoj:",
|
|
||||||
"Introduced By": "Konatigita de",
|
|
||||||
"Introducer": "Konatiganto",
|
|
||||||
"Introduction": "Konatigo",
|
|
||||||
"Inversion of the given condition (i.e. do not exclude)": "Inversigo de la donita kondiĉo (t.e. ne ekskludi)",
|
"Inversion of the given condition (i.e. do not exclude)": "Inversigo de la donita kondiĉo (t.e. ne ekskludi)",
|
||||||
"Keep Versions": "Konservi versiojn",
|
"Keep Versions": "Konservi Versiojn",
|
||||||
"LDAP": "LDAP",
|
"LDAP": "LDAP",
|
||||||
"Largest First": "Plej grandaj unue",
|
"Largest First": "Plej Granda Unue",
|
||||||
"Last 30 Days": "Lastaj 30 tagoj",
|
"Last Scan": "Lasta Skano",
|
||||||
"Last 7 Days": "Lastaj 7 tagoj",
|
"Last seen": "Lasta vidita",
|
||||||
"Last Month": "Lasta monato",
|
"Latest Change": "Lasta Ŝanĝo",
|
||||||
"Last Scan": "Lasta skano",
|
"Learn more": "Lerni pli",
|
||||||
"Last seen": "Laste vidita je",
|
|
||||||
"Latest Change": "Lasta ŝanĝo",
|
|
||||||
"Learn more": "Lernu pli",
|
|
||||||
"Learn more at {%url%}": "Lernu pli ĉe {{url}}",
|
|
||||||
"Limit": "Limo",
|
"Limit": "Limo",
|
||||||
"Limit Bandwidth in LAN": "Limigi trafikon en lokaj retoj",
|
|
||||||
"Listener Failures": "Malsukcesoj de aŭskultiloj",
|
|
||||||
"Listener Status": "Stato de aŭskultiloj",
|
|
||||||
"Listeners": "Aŭskultantoj",
|
"Listeners": "Aŭskultantoj",
|
||||||
"Loading data...": "Ŝarĝado de datumoj...",
|
"Loading data...": "Ŝarĝas datumojn...",
|
||||||
"Loading...": "Ŝarĝado...",
|
"Loading...": "Ŝarĝas...",
|
||||||
"Local Additions": "Lokaj aldonoj",
|
"Local Additions": "Lokaj aldonoj",
|
||||||
"Local Discovery": "Loka malkovrado",
|
"Local Discovery": "Loka Malkovro",
|
||||||
"Local State": "Loka stato",
|
"Local State": "Loka Stato",
|
||||||
"Local State (Total)": "Loka stato (tuta)",
|
"Local State (Total)": "Loka Stato (Tuta)",
|
||||||
"Locally Changed Items": "Loke ŝanĝitaj eroj",
|
"Locally Changed Items": "Loke Ŝanĝitaj Eroj",
|
||||||
"Log": "Protokolo",
|
"Log": "Protokolo",
|
||||||
"Log File": "Protokola dosiero",
|
|
||||||
"Log In": "Ensaluti",
|
|
||||||
"Log Out": "Elsaluti",
|
|
||||||
"Log in to see paths information.": "Ensalutu por vidi informojn pri vojoj.",
|
|
||||||
"Log in to see version information.": "Ensalutu por vidi informojn pri versioj.",
|
|
||||||
"Log tailing paused. Scroll to the bottom to continue.": "Sekvado de protokolo paŭzita. Rulumu al la supro por daŭrigi.",
|
|
||||||
"Login failed, see Syncthing logs for details.": "Ensaluto malsukcesis, vidu la protokolojn de Syncthing por detaloj.",
|
|
||||||
"Logs": "Protokoloj",
|
"Logs": "Protokoloj",
|
||||||
"Maintain an index of all blocks in the folder, enabling reuse of blocks from other files when syncing changes. Disable to reduce database size at the cost of not being able to reuse blocks across files.": "Konservi indekson de ĉiuj blokoj en la dosierujo, ebligante reuzadon de blokoj el aliaj dosierojn dum sinkronigado de ŝanĝoj. Malŝalti por malpliigado de datumbazo, kun kosto de neebleco de reuzado de blokoj inter dosieroj.",
|
"Major Upgrade": "Ĉefa Ĝisdatigo",
|
||||||
"Major Upgrade": "Granda ĝisdatigo",
|
"Mass actions": "Amasa agoj",
|
||||||
"Mass actions": "Amasaj agoj",
|
"Maximum Age": "Maksimuma Aĝo",
|
||||||
"Maximum Age": "Maksimuma aĝo",
|
"Metadata Only": "Nur Metadatumoj",
|
||||||
"Maximum single entry size": "Maksimuma grando de unuopa ero",
|
"Minimum Free Disk Space": "Minimuma Libera Diskospaco",
|
||||||
"Maximum total size": "Maksimuma tuta grando",
|
"Mod. Device": "Mod. Aparato",
|
||||||
"Metadata Only": "Nur metadatumoj",
|
"Mod. Time": "Mod. Tempo",
|
||||||
"Minimum Free Disk Space": "Minimuma libera diskospaco",
|
|
||||||
"Mod. Device": "Mod. aparato",
|
|
||||||
"Mod. Time": "Tempo de mod.",
|
|
||||||
"More than a month ago": "Pli ol monato antaŭ",
|
|
||||||
"More than a week ago": "Pli ol semajno antaŭ",
|
|
||||||
"More than a year ago": "Pli ol jaro antaŭ",
|
|
||||||
"Move to top of queue": "Movi al la supro de atendovico",
|
"Move to top of queue": "Movi al la supro de atendovico",
|
||||||
"Multi level wildcard (matches multiple directory levels)": "Multnivela ĵokero (egalas multoblajn dosierujaj niveloj)",
|
"Multi level wildcard (matches multiple directory levels)": "Multnivela ĵokero (egalas multoblajn dosierujaj niveloj)",
|
||||||
"Never": "Neniam",
|
"Never": "Neniam",
|
||||||
"New Device": "Nova aparato",
|
"New Device": "Nova Aparato",
|
||||||
"New Folder": "Nova dosierujo",
|
"New Folder": "Nova Dosierujo",
|
||||||
"Newest First": "Plej novaj unue",
|
"Newest First": "Plejnova Unue",
|
||||||
"No": "Ne",
|
"No": "Ne",
|
||||||
"No File Versioning": "Sen versiado de dosieroj",
|
"No File Versioning": "Sen Dosiera Versionado",
|
||||||
"No files will be deleted as a result of this operation.": "Neniuj dosieroj estos forigitaj rezulte de ĉi tiu ago.",
|
"No files will be deleted as a result of this operation.": "Neniuj dosieroj estos forigitaj rezulte de ĉi tiu ago.",
|
||||||
"No rules set": "Neniuj reguloj agorditaj",
|
|
||||||
"No upgrades": "Sen ĝisdatigoj",
|
"No upgrades": "Sen ĝisdatigoj",
|
||||||
"Not shared": "Ne kunhavata",
|
|
||||||
"Notice": "Avizo",
|
"Notice": "Avizo",
|
||||||
"Number of Connections": "Nombro de konektoj",
|
|
||||||
"OK": "Bone",
|
"OK": "Bone",
|
||||||
"Off": "Malŝaltita",
|
"Off": "Malŝata",
|
||||||
"Oldest First": "Plej malnovaj unue",
|
"Oldest First": "Malnova Unue",
|
||||||
"Optional descriptive label for the folder. Can be different on each device.": "Laŭvola priskriba etikedo por la dosierujo. Povas esti malsama en ĉiu aparato.",
|
"Optional descriptive label for the folder. Can be different on each device.": "Laŭvola priskriba etikedo por la dosierujo. Povas esti malsama en ĉiu aparato.",
|
||||||
"Optional group for the device. Can be different on each device.": "Laŭvola grupo por la aparato. Povas esti malsama en ĉiu aparato.",
|
"Options": "Opcioj",
|
||||||
"Optional group for the folder. Can be different on each device.": "Laŭvola grupo por la dosierujo. Povas esti malsama en ĉiu aparato.",
|
"Out of Sync": "Elsinkronigita",
|
||||||
"Options": "Agordoj",
|
"Out of Sync Items": "Elsinkronigitaj Eroj",
|
||||||
"Out of Sync": "Desinkronigita",
|
"Outgoing Rate Limit (KiB/s)": "Eliranta Rapideco Limo (KiB/s)",
|
||||||
"Out of Sync Items": "Desinkronigitaj eroj",
|
"Override Changes": "Transpasi Ŝanĝojn",
|
||||||
"Outgoing Rate Limit (KiB/s)": "Limo de eliranta rapideco (KiB/s)",
|
|
||||||
"Override": "Transpasi",
|
|
||||||
"Override Changes": "Transpasi ŝanĝojn",
|
|
||||||
"Ownership": "Propreco",
|
|
||||||
"Password": "Pasvorto",
|
|
||||||
"Path": "Vojo",
|
"Path": "Vojo",
|
||||||
"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": "Vojo de la dosierujo en la loka komputilo. Estos kreita, se ne ekzistas. La tilda signo (~) povas esti uzata kiel mallongigilo por",
|
"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": "Vojo de la dosierujo en la loka komputilo. Kreiĝos se ne ekzistas. La tilda signo (~) povas esti uzata kiel mallongigilo por",
|
||||||
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Vojo, kie versioj devus esti stokitaj (lasu malplena por la defaŭlta dosierujo \".stversions\" en la kunhavata dosierujo).",
|
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Vojo kies versioj devus esti stokitaj (lasu malplena por la defaŭlta .stversions dosierujo en la komunigita dosierujo).",
|
||||||
"Paths": "Vojoj",
|
"Pause": "Paŭzu",
|
||||||
"Pause": "Paŭzigi",
|
"Pause All": "Paŭzu Ĉion",
|
||||||
"Pause All": "Paŭzigi ĉiujn",
|
|
||||||
"Paused": "Paŭzita",
|
"Paused": "Paŭzita",
|
||||||
"Paused (Unused)": "Paŭzita (ne uzata)",
|
|
||||||
"Pending changes": "Pritraktataj ŝanĝoj",
|
"Pending changes": "Pritraktataj ŝanĝoj",
|
||||||
"Periodic scanning at given interval and disabled watching for changes": "Perioda skanado ĉe donita intervalo ŝaltita kaj atendado por ŝanĝoj malŝaltita",
|
"Periodic scanning at given interval and disabled watching for changes": "Perioda skanado ĉe donita intervalo kaj malebligita rigardado je ŝanĝoj",
|
||||||
"Periodic scanning at given interval and enabled watching for changes": "Perioda skanado ĉe donita intertempo kaj atendado por ŝanĝoj ŝaltitaj",
|
"Periodic scanning at given interval and enabled watching for changes": "Perioda skanado ĉe donita intervalo kaj ebligita rigardado je ŝanĝoj",
|
||||||
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Perioda skanado ĉe donita intertempo kaj agordado de atendado por ŝanĝoj malsukcesis, reprovado ĉiuminute:",
|
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Perioda skanado ĉe donita intervalo kaj malsukcesis agordi rigardadon je ŝanĝoj. Provante denove ĉiuminute:",
|
||||||
"Permanently add it to the ignore list, suppressing further notifications.": "Porĉiame aldoni ĝin al la listo de ignoraĵoj, silentigante estontajn sciigojn.",
|
"Please consult the release notes before performing a major upgrade.": "Bonvolu konsulti la notojn de eldono antaŭ elfari ĉefan ĝisdatigon.",
|
||||||
"Please consult the release notes before performing a major upgrade.": "Bonvolu konsultu la notojn de eldono antaŭ elfarado de granda ĝisdatigo.",
|
"Please set a GUI Authentication User and Password in the Settings dialog.": "Bonvolu agordi GUI Authentication Uzanto kaj Pasvorto en la agordoj dialogo.",
|
||||||
"Please set a GUI Authentication User and Password in the Settings dialog.": "Bonvolu agordu la uzanton kaj pasvorton de aŭtentigo per grafika interfaco en la dialogo \"Agordoj\".",
|
"Please wait": "Bonvolu atendi",
|
||||||
"Please wait": "Bonvolu atendu",
|
"Prefix indicating that the file can be deleted if preventing directory removal": "Prefikso indikanta, ke la dosiero povas esti forigita, se ĝi malhelpas forigi dosierujon",
|
||||||
"Prefix indicating that the file can be deleted if preventing directory removal": "Prefikso, indikanta, ke la dosiero povas esti forigita, se ĝi malhelpas forigi dosierujon",
|
"Prefix indicating that the pattern should be matched without case sensitivity": "Prefikso indikanta, ke la ŝablono devus esti egalita usklecoblinde.",
|
||||||
"Prefix indicating that the pattern should be matched without case sensitivity": "Prefikso, indikanta, ke la ŝablono devus esti komparita usklecoblinde",
|
"Preparing to Sync": "Pretigante sinkronigadon",
|
||||||
"Preparing to Sync": "Pretigado por sinkronigado",
|
|
||||||
"Preview": "Antaŭrigardo",
|
"Preview": "Antaŭrigardo",
|
||||||
"Preview Usage Report": "Antaŭrigardo Uzada Raporto",
|
"Preview Usage Report": "Antaŭrigardo Uzada Raporto",
|
||||||
"QR code": "QR-kodo",
|
"Quick guide to supported patterns": "Rapida gvidilo pri subtenata ŝablonoj",
|
||||||
"QUIC LAN": "Loka reto per QUIC",
|
|
||||||
"QUIC WAN": "Malloka reto per QUIC",
|
|
||||||
"Quick guide to supported patterns": "Rapida gvidilo pri subtenataj ŝablonoj",
|
|
||||||
"Random": "Hazarda",
|
"Random": "Hazarda",
|
||||||
"Receive Encrypted": "Ricevi ĉifritajn",
|
"Receive Only": "Nur Ricevi",
|
||||||
"Receive Only": "Nur ricevi",
|
"Recent Changes": "Lastatempaj Ŝanĝoj",
|
||||||
"Received data is already encrypted": "Ricevataj datumoj estas jam ĉifritaj",
|
"Reduced by ignore patterns": "Malpliigita per ignorantaj ŝablonoj",
|
||||||
"Recent Changes": "Lastatempaj ŝanĝoj",
|
"Release Notes": "Notoj de Eldono",
|
||||||
"Reduced by ignore patterns": "Malpliigita per ŝablonoj de ignorado",
|
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Kandidataj eldonoj enhavas la lastajn trajtojn kaj korektojn. Ili estas similaj al la tradiciaj dusemajnaj Syncthing eldonoj.",
|
||||||
"Relay LAN": "Loka reto kun plusendado",
|
"Remote Devices": "Foraj Aparatoj",
|
||||||
"Relay WAN": "Malloka reto kun plusendado",
|
"Remove": "Forigu",
|
||||||
"Release Notes": "Notoj de eldono",
|
"Remove Device": "Forigi Aparaton",
|
||||||
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Kandidataj eldonoj enhavas la lastajn funkciojn kaj korektojn. Ili estas similaj al la tradiciaj dusemajnaj eldonoj de Syncthing.",
|
"Remove Folder": "Forigi Dosierujon",
|
||||||
"Remote Devices": "Foraj aparatoj",
|
"Required identifier for the folder. Must be the same on all cluster devices.": "Nepra identigilo por la dosierujo. Devas esti la sama en ĉiuj aparatoj de la grupo.",
|
||||||
"Remote GUI": "Fora grafika interfaco",
|
"Rescan": "Reskanu",
|
||||||
"Remove": "Forigi",
|
"Rescan All": "Reskanu Ĉion",
|
||||||
"Remove Device": "Forigi aparaton",
|
|
||||||
"Remove Folder": "Forigi dosierujon",
|
|
||||||
"Required identifier for the folder. Must be the same on all cluster devices.": "Nepra identigilo por la dosierujo. Devas esti la sama en ĉiuj aparatoj de la aparataro.",
|
|
||||||
"Rescan": "Reskani",
|
|
||||||
"Rescan All": "Reskani ĉiujn",
|
|
||||||
"Rescans": "Reskanoj",
|
"Rescans": "Reskanoj",
|
||||||
"Restart": "Restartigi",
|
"Restart": "Restartu",
|
||||||
"Restart Needed": "Restarto bezonata",
|
"Restart Needed": "Restarto Bezonata",
|
||||||
"Restarting": "Restartado",
|
"Restarting": "Restartado",
|
||||||
"Restore": "Restaŭri",
|
"Restore": "Restarigi",
|
||||||
"Restore Versions": "Restaŭri versiojn",
|
"Restore Versions": "Restarigi Versiojn",
|
||||||
"Resume": "Daŭrigi",
|
"Resume": "Daŭrigu",
|
||||||
"Resume All": "Daŭrigi ĉiujn",
|
"Resume All": "Daŭrigu Ĉion",
|
||||||
"Reused": "Reuzita",
|
"Reused": "Reuzita",
|
||||||
"Revert": "Malfari",
|
"Revert Local Changes": "Reverti Lokajn Ŝangojn",
|
||||||
"Revert Local Changes": "Malfari lokajn ŝanĝojn",
|
"Save": "Konservu",
|
||||||
"Save": "Konservi",
|
"Scan Time Remaining": "Restanta Tempo de Skano",
|
||||||
"Saving changes": "Konservado de ŝanĝoj",
|
|
||||||
"Scan Time Remaining": "Restanta tempo de skanado",
|
|
||||||
"Scanning": "Skanado",
|
"Scanning": "Skanado",
|
||||||
"See external versioning help for supported templated command line parameters.": "Vidu informlibron de ekstera versiado por subtenataj ŝablonaj parametroj de komandlinio.",
|
"See external versioning help for supported templated command line parameters.": "Vidu informlibron de ekstera versionado por subtenata ŝablona parametroj de komandlinio.",
|
||||||
"Select All": "Elekti ĉiujn",
|
"Select All": "Elekti Ĉiujn",
|
||||||
"Select a version": "Elekti version",
|
"Select a version": "Elekti version",
|
||||||
"Select additional devices to share this folder with.": "Elektu pliajn aparatojn por komunigi tiun ĉi dosierujon kun ili.",
|
"Select additional devices to share this folder with.": "Elektu pliajn aparatojn por komunigi tiun ĉi dosierujon kun ili.",
|
||||||
"Select additional folders to share with this device.": "Elektu aldonajn dosierujojn, kiuj estos kunhavita kun ĉi tiu aparato.",
|
|
||||||
"Select latest version": "Elekti plej novan version",
|
"Select latest version": "Elekti plej novan version",
|
||||||
"Select oldest version": "Elekti plej malnovan version",
|
"Select oldest version": "Elekti plej malnovan version",
|
||||||
"Send & Receive": "Sendi kaj ricevi",
|
"Send & Receive": "Sendi kaj Ricevi",
|
||||||
"Send Extended Attributes": "Sendi aldonajn atributojn",
|
"Send Only": "Nur Sendi",
|
||||||
"Send Only": "Nur sendi",
|
|
||||||
"Send Ownership": "Sendi proprecon",
|
|
||||||
"Set Ignores on Added Folder": "Agordi ignoraĵojn por aldonata dosierujo",
|
|
||||||
"Settings": "Agordoj",
|
"Settings": "Agordoj",
|
||||||
"Share": "Kunhavigi",
|
"Share": "Komunigi",
|
||||||
"Share Folder": "Kunhavigi dosierujon",
|
"Share Folder": "Komunigu Dosierujon",
|
||||||
"Share by Email": "Kunhavigi per retpoŝto",
|
"Share this folder?": "Komunigi ĉi tiun dosierujon?",
|
||||||
"Share by SMS": "Kunhavigi per SMS",
|
"Shared With": "Komunigita Kun",
|
||||||
"Share this folder?": "Ĉu kunhavigi ĉi tiun dosierujon?",
|
"Sharing": "Komunigo",
|
||||||
"Shared Folders": "Kunhavataj dosieroj",
|
"Show ID": "Montru ID",
|
||||||
"Shared With": "Kunhavata kun",
|
"Show QR": "Montru QR",
|
||||||
"Sharing": "Kunhavado",
|
"Show diff with previous version": "Montri diferenco kun antaŭa versio",
|
||||||
"Show ID": "Montri identigilon",
|
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Montrita anstataŭ ID de Aparato en la statuso de la grupo. Estos anoncita al aliaj aparatoj kiel laŭvola defaŭlta nomo.",
|
||||||
"Show QR": "Montri QR-kodon",
|
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Montri anstataŭ ID de Aparato en la statuso de la grupo. Estos ĝisdatigita al la nomo de la aparato sciigante se ĝi estas lasita malplena.",
|
||||||
"Show detailed discovery status": "Montri detalan staton de malkovrado",
|
"Shutdown": "Sistemfermo",
|
||||||
"Show detailed listener status": "Montri detalan staton de aŭskultiloj",
|
"Shutdown Complete": "Sistemfermo Tuta",
|
||||||
"Show diff with previous version": "Montri diferencon kun antaŭa versio",
|
"Simple File Versioning": "Simpla Versionado de Dosieroj",
|
||||||
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Montrita anstataŭ identigilo de aparato en la statuso de la aparatato. Estos anoncita al aliaj aparatoj kiel laŭvola defaŭlta nomo.",
|
|
||||||
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Montrita anstataŭ identigilo de aparato en la statuso de la aparataro. Estos ĝisdatigita al la nomo de la aparato, kiun ĝi sciigos, se lasita malplena.",
|
|
||||||
"Shut Down": "Malŝalti",
|
|
||||||
"Shutdown Complete": "Malŝaltado plenumita",
|
|
||||||
"Simple": "Simpla",
|
|
||||||
"Simple File Versioning": "Simpla versiado de dosieroj",
|
|
||||||
"Single level wildcard (matches within a directory only)": "Ununivela ĵokero (egalas nur ene de dosierujo)",
|
"Single level wildcard (matches within a directory only)": "Ununivela ĵokero (egalas nur ene de dosierujo)",
|
||||||
"Size": "Grando",
|
"Size": "Grandeco",
|
||||||
"Smallest First": "Plej malgrandaj unue",
|
"Smallest First": "Plej Malgranda Unue",
|
||||||
"Some discovery methods could not be established for finding other devices or announcing this device:": "Iuj metodoj de malkovrado ne povas esti starigitaj por trovado de aliaj aparatoj aŭ anoncado de tiu ĉi aparato:",
|
"Some items could not be restored:": "Iuj eroj ne povis esti restarigitaj:",
|
||||||
"Some items could not be restored:": "Iuj eroj ne povis esti restaŭritaj:",
|
|
||||||
"Some listening addresses could not be enabled to accept connections:": "Iuj adresoj de aŭskultado ne povas esti ŝaltitaj por akceptado de konektoj:",
|
|
||||||
"Source Code": "Fontkodo",
|
"Source Code": "Fontkodo",
|
||||||
"Stable releases and release candidates": "Stabilaj eldonoj kaj kandidataj eldonoj",
|
"Stable releases and release candidates": "Stabilaj eldonoj kaj kandidataj eldonoj",
|
||||||
"Stable releases are delayed by about two weeks. During this time they go through testing as release candidates.": "Stabilaj eldonoj prokrastas je ĉirkaŭ du semjanoj. Dum tiu tempo ili estos testataj kiel kandidataj eldonoj.",
|
"Stable releases are delayed by about two weeks. During this time they go through testing as release candidates.": "Stabilaj eldonoj prokrastas je ĉirkaŭ du semjanoj. Dum tiu tempo ili estos testataj kiel kandidataj eldonoj.",
|
||||||
"Stable releases only": "Nur stabilaj eldonoj",
|
"Stable releases only": "Nur stabilaj eldonoj",
|
||||||
"Staggered": "Gradigita",
|
"Staggered File Versioning": "Gradigita Dosiera Versionado",
|
||||||
"Staggered File Versioning": "Gradigita versiado de dosieroj",
|
"Start Browser": "Startu Retumilon",
|
||||||
"Start Browser": "Starti retumilon",
|
|
||||||
"Starting": "Komencado",
|
|
||||||
"Statistics": "Statistikoj",
|
"Statistics": "Statistikoj",
|
||||||
"Stay logged in": "Resti ensalutita",
|
|
||||||
"Stopped": "Haltita",
|
"Stopped": "Haltita",
|
||||||
"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.": "Enmemorigas kaj sinkronigas nur ĉifritajn datumojn. Dosierujoj en ĉiuj konektitaj aparatoj devas ankaŭ esti agordita kun la sama pasvorto aŭ havi tipon \"{{receiveEncrypted}}\".",
|
|
||||||
"Subject:": "Temo:",
|
|
||||||
"Support": "Subteno",
|
"Support": "Subteno",
|
||||||
"Support Bundle": "Pakaĵo por subteno",
|
"Support Bundle": "Pakaĵo por subteno",
|
||||||
"Sync Extended Attributes": "Sinkronigi aldonajn atributojn",
|
"Sync Protocol Listen Addresses": "Aŭskultado Adresoj de Sinkprotokolo",
|
||||||
"Sync Ownership": "Sinkronigi proprecon",
|
"Syncing": "Sinkronigas",
|
||||||
"Sync Protocol Listen Addresses": "Aŭskultataj adresoj de sinkroniga protokolo",
|
|
||||||
"Sync Status": "Stato de sinkronigado",
|
|
||||||
"Syncing": "Sinkronigado",
|
|
||||||
"Syncthing device ID for \"{%devicename%}\"": "Identigilo Syncthing de aparato \"{{devicename}}\"",
|
|
||||||
"Syncthing has been shut down.": "Syncthing estis malŝaltita.",
|
"Syncthing has been shut down.": "Syncthing estis malŝaltita.",
|
||||||
"Syncthing includes the following software or portions thereof:": "Syncthing inkluzivas la jenajn programarojn aŭ ties porciojn:",
|
"Syncthing includes the following software or portions thereof:": "Syncthing inkluzivas la jenajn programarojn aŭ porciojn ĝiajn:",
|
||||||
"Syncthing is Free and Open Source Software licensed as MPL v2.0.": "Syncthing estas libera kaj malfermitkoda programo, licencita per permesilo MPL v2.0.",
|
"Syncthing is Free and Open Source Software licensed as MPL v2.0.": "Syncthing estas libera kaj malferma fonta programaro licencita kiel 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 estas programo por daŭra sinkronigado de dosieroj. Ĝi sinkronigas dosierojn inter du aŭ pli komputiloj realtempe, sekure protektita de rigardantoj. Viaj datumoj restas viaj, kaj vi meritas elekti, kie ili estas enmemorigataj, kun kiuj eksteraj liverantoj ili estas kunhavataj, kaj kiel ili estas transmetataj tra la interreto.",
|
"Syncthing is restarting.": "Syncthing estas restartanta.",
|
||||||
"Syncthing is listening on the following network addresses for connection attempts from other devices:": "Syncthing aŭskultas per la sekvaj retaj adresoj por provoj de konekto el aliaj aparatoj:",
|
"Syncthing is upgrading.": "Syncthing estas ĝisdatigita.",
|
||||||
"Syncthing is not listening for connection attempts from other devices on any address. Only outgoing connections from this device may work.": "Syncthing ne aŭskultas por konektaj el aliaj aparatoj per iu ajn adreso. Nur elirantaj konektoj povos funkcii.",
|
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing ŝajnas nefunkcii, aŭ estas problemo kun via retkonekto. Reprovado...",
|
||||||
"Syncthing is restarting.": "Syncthing restartiĝas.",
|
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing ŝajnas renkonti problemon kun la traktado de via peto. Bonvolu refreŝigi la paĝon aŭ restarti Syncthing se la problemo daŭras.",
|
||||||
"Syncthing is saving changes.": "Syncthing konservas la ŝanĝojn.",
|
|
||||||
"Syncthing is upgrading.": "Syncthing ĝisdatiĝas.",
|
|
||||||
"Syncthing now supports automatically reporting crashes to the developers. This feature is enabled by default.": "Syncthing nun subtenas aŭtomatan raportadon de paneoj al la disvolvantoj. Ĉi tiu funkcio estas defaŭlte ŝaltita.",
|
|
||||||
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing ŝajnas nefunkcia, aŭ estas problemo kun via retkonekto. Reprovado…",
|
|
||||||
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing ŝajnas renkonti problemon kun la traktado de via peto. Bonvolu refreŝigu la paĝon aŭ restarti la programon Syncthing, se la problemo daŭras.",
|
|
||||||
"TCP LAN": "Loka reto per TCP",
|
|
||||||
"TCP WAN": "Malloka reto per TCP",
|
|
||||||
"Take me back": "Prenu min reen",
|
"Take me back": "Prenu min reen",
|
||||||
"The GUI address is overridden by startup options. Changes here will not take effect while the override is in place.": "La adreso de grafika interfaco estas superregita per startigaj agordoj. Ŝanĝoj ĉi tie ne efektiviĝas dum la superrego estas aktuala.",
|
"The GUI address is overridden by startup options. Changes here will not take effect while the override is in place.": "La adreso de grafika interfaco estas superregita per startigaj agordoj. Ŝanĝoj ĉi tie ne efektiviĝas dum la superrego estas aktuala.",
|
||||||
"The Syncthing Authors": "La aŭtoroj de Syncthing",
|
|
||||||
"The Syncthing admin interface is configured to allow remote access without a password.": "La administra interfaco de Syncthing estas agordita por permesi foran atingon sen pasvorto.",
|
"The Syncthing admin interface is configured to allow remote access without a password.": "La administra interfaco de Syncthing estas agordita por permesi foran atingon sen pasvorto.",
|
||||||
"The aggregated statistics are publicly available at the URL below.": "La agregita statistikoj estas publike disponebla ĉe la URL malsupre.",
|
"The aggregated statistics are publicly available at the URL below.": "La agregita statistikoj estas publike disponebla ĉe la URL malsupre.",
|
||||||
"The cleanup interval cannot be blank.": "La intertempo de purigado ne povas esti malplena.",
|
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "La agordo estis registrita sed ne aktivigita. Syncthing devas restarti por aktivigi la novan agordon.",
|
||||||
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "La agordo estis registrita, sed ne aktivigita. Syncthing devas restarti por aktivigi la novan agordaron.",
|
"The device ID cannot be blank.": "La aparato ID ne povas esti malplena.",
|
||||||
"The device ID cannot be blank.": "La aparata identigilo ne povas esti malplena.",
|
"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).": "La aparato ID por eniri ĉi tie estas trovebla per \"Agoj > Montru ID\" dialogo en la alia aparato. Interspacoj kaj streketoj estas opcio (ignorigita).",
|
||||||
"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).": "La identigilo de aparato por eniri ĉi tie estas trovebla per dialogo \"Agoj > Montri identigilon\" en la alia aparato. Spacetoj kaj streketoj estas opciaj (ignoritaj).",
|
"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.": "La ĉifrita raporto de uzado estas sendata ĉiutage. Ĝi estas uzata por sekvi komunajn platformojn, dosierujajn grandojn kaj aplikaĵajn versiojn. Se la raporto datumaro ŝanĝis, vi estos avertata per ĉi tiu dialogo denove.",
|
||||||
"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.": "La ĉifrita raporto de uzado estas sendata ĉiutage. Ĝi estas uzata por sekvi kutimajn platformojn, dosierujajn grandojn kaj aplikaĵajn versiojn. Se la raporto datumaro ŝanĝiĝis, vi estos avertata per ĉi tiu dialogo denove.",
|
"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.": "La enigita aparato ID ne ŝajnas valida. Ĝi devas esti signoĉeno el 52 aŭ 56 karaktroj longa enhavanta leterojn kaj nombrojn, kun interspacoj kaj streketoj opciaj.",
|
||||||
"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.": "La enigita aparata identigilo ne ŝajnas valida. Ĝi devas esti signoĉeno el 52 aŭ 56 simboloj longa, enhavanta leterojn kaj nombrojn, kun opciaj interspacoj kaj streketoj.",
|
"The folder ID cannot be blank.": "La dosierujo ID ne povas esti malplena.",
|
||||||
"The folder ID cannot be blank.": "La dosieruja identigilo ne povas esti malplena.",
|
"The folder ID must be unique.": "La dosierujo ID devas esti unika.",
|
||||||
"The folder ID must be unique.": "La dosieruja identigilo devas esti unika.",
|
|
||||||
"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.": "La enhavo de dosierujo en aliaj aparatoj estas superskribita por esti sama al ĉi tiu aparato. Dosieroj, ne ekzistantaj ĉi tie, estos forigitaj en aliaj aparatoj.",
|
|
||||||
"The folder content on this device will be overwritten to become identical with other devices. Files newly added here will be deleted.": "La enhavo de dosierujo en ĉi tiu aparato estas superskribita por esti sama al aliaj aparatoj. Dosieroj, freŝe aldonitaj ĉi tien, estos forigitaj.",
|
|
||||||
"The folder path cannot be blank.": "La vojo de dosierujo ne povas esti malplena.",
|
"The folder path cannot be blank.": "La vojo de dosierujo ne povas esti malplena.",
|
||||||
"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.": "La jenaj intervaloj estas uzataj: dum la unua horo version restas dum ĉiuj 30 sekundoj, dum la unua tago versio restas konservita dum ĉiu horo, dum la unuaj 30 tagoj versio estas konservita dum ĉiu tago, ĝis la maksimume aĝa versio restas konservita dum ĉiu semajno.",
|
"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.": "La jenaj intervaloj estas uzataj: dum la unua horo version restas dum ĉiuj 30 sekundoj, dum la unua tago versio restas konservita dum ĉiu horo, dum la unuaj 30 tagoj versio estas konservita dum ĉiu tago, ĝis la maksimume aĝa versio restas konservita dum ĉiu semajno.",
|
||||||
"The following items could not be synchronized.": "La sekvantaj eroj ne povas esti sinkronigitaj.",
|
"The following items could not be synchronized.": "La sekvantaj eroj ne povas esti sinkronigitaj.",
|
||||||
"The following items were changed locally.": "La sekvantaj eroj estis ŝanĝitaj loke.",
|
"The following items were changed locally.": "La sekvantaj eroj estis ŝanĝitaj loke.",
|
||||||
"The following methods are used to discover other devices on the network and announce this device to be found by others:": "La sekvaj metodoj estas uzataj por malkovri aliajn aparatojn en la reto kaj anonci ĉi tiun aparaton por esti trovita de aliaj:",
|
|
||||||
"The following text will automatically be inserted into a new message.": "La sekva teksto estos aŭtomate enmetita al novan mesaĝon.",
|
|
||||||
"The following unexpected items were found.": "La sekvaj neatenditaj eroj estis trovitaj.",
|
|
||||||
"The interval must be a positive number of seconds.": "La intertempo devas esti pozitiva nombro da sekundoj.",
|
|
||||||
"The interval, in seconds, for running cleanup in the versions directory. Zero to disable periodic cleaning.": "La intertempo, en sekundoj, por ruli purigadon en la dosierujo de versioj. Nul malŝaltas periodan purigadon.",
|
|
||||||
"The maximum age must be a number and cannot be blank.": "La maksimuma aĝo devas esti nombro kaj ne povas esti malplena.",
|
"The maximum age must be a number and cannot be blank.": "La maksimuma aĝo devas esti nombro kaj ne povas esti malplena.",
|
||||||
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "La maksimuma tempo por konservi version (en tagoj, agordi je 0 por konservi versiojn eterne).",
|
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "La maksimuma tempo por konservi version (en tagoj, agordi je 0 por konservi versiojn eterne).",
|
||||||
"The number of connections must be a non-negative number.": "La nombro de konektoj devas esti ne-negativan nombron.",
|
|
||||||
"The number of days must be a number and cannot be blank.": "La nombro da tagoj devas esti nombro kaj ne povas esti malplena.",
|
"The number of days must be a number and cannot be blank.": "La nombro da tagoj devas esti nombro kaj ne povas esti malplena.",
|
||||||
"The number of days to keep files in the trash can. Zero means forever.": "La nombro da tagoj por konservi dosierojn en la rubujo. Nulo signifas eterne.",
|
"The number of days to keep files in the trash can. Zero means forever.": "La nombro da tagoj por konservi dosierojn en la rubujo. Nulo signifas eterne.",
|
||||||
"The number of old versions to keep, per file.": "La nombro da malnovaj versioj por konservi, po ĉiu dosiero.",
|
"The number of old versions to keep, per file.": "La nombro da malnovaj versioj por konservi, po ĉiu dosiero.",
|
||||||
"The number of versions must be a number and cannot be blank.": "La nombro da versioj devas esti nombro kaj ne povas esti malplena.",
|
"The number of versions must be a number and cannot be blank.": "La nombro da versioj devas esti nombro kaj ne povas esti malplena.",
|
||||||
"The path cannot be blank.": "La vojo ne povas esti malplena.",
|
"The path cannot be blank.": "La vojo ne povas esti malplena.",
|
||||||
"The rate limit is applied to the accumulated traffic of all connections to this device.": "La limigo de ofteco estas aplikita al la amasigita trafiko de ĉiuj konektoj al ĉiu ti aparato.",
|
|
||||||
"The rate limit must be a non-negative number (0: no limit)": "La rapideca limo devas esti pozitiva nombro (0: senlimo)",
|
"The rate limit must be a non-negative number (0: no limit)": "La rapideca limo devas esti pozitiva nombro (0: senlimo)",
|
||||||
"The remote device has not accepted sharing this folder.": "La fora aparato ne akceptis kunhavadon de ĉiu ti dosierujo.",
|
|
||||||
"The remote device has paused this folder.": "La fora aparato paŭzigis ĉi tiun dosierujon.",
|
|
||||||
"The rescan interval must be a non-negative number of seconds.": "La intervalo de reskano devas esti pozitiva nombro da sekundoj.",
|
"The rescan interval must be a non-negative number of seconds.": "La intervalo de reskano devas esti pozitiva nombro da sekundoj.",
|
||||||
"There are no devices to share this folder with.": "Estas neniu aparato, kun kiu kunhavigi tiun ĉi dosierujon.",
|
"There are no devices to share this folder with.": "Estas neniu aparato kun kiu komunigi tiun ĉi dosierujon.",
|
||||||
"There are no file versions to restore.": "Estas neniuj dosieraj versioj por restaŭrado.",
|
|
||||||
"There are no folders to share with this device.": "Estas neniuj dosierujoj por kunhavado kun tiu ĉi aparato.",
|
|
||||||
"They are retried automatically and will be synced when the error is resolved.": "Ili estas reprovitaj aŭtomate kaj estos sinkronigitaj kiam la eraro estas solvita.",
|
"They are retried automatically and will be synced when the error is resolved.": "Ili estas reprovitaj aŭtomate kaj estos sinkronigitaj kiam la eraro estas solvita.",
|
||||||
"This Device": "Ĉi tiu aparato",
|
"This Device": "Ĉi Tiu Aparato",
|
||||||
"This Month": "Tiu ĉi monato",
|
|
||||||
"This can easily give hackers access to read and change any files on your computer.": "Ĉi tio povas facile doni al kodumuloj atingon por legi kaj ŝanĝi ajnajn dosierojn en via komputilo.",
|
"This can easily give hackers access to read and change any files on your computer.": "Ĉi tio povas facile doni al kodumuloj atingon por legi kaj ŝanĝi ajnajn dosierojn en via komputilo.",
|
||||||
"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.": "Ĉi tiu aparato ne povas aŭtomate malkovri aliajn aparatojn aŭ anonci sia adreso por esti trovita de aliaj. Nur aparatoj kun statike agorditaj adresoj povas konekti.",
|
"This is a major version upgrade.": "Ĉi tio estas ĉefversio ĝisdatigita.",
|
||||||
"This is a major version upgrade.": "Ĉi tio estas granda ĝisdatigo.",
|
"This setting controls the free space required on the home (i.e., index database) disk.": "Ĉi tiu agordo regas la libera spaco postulita sur la hejma (t.e. indeksa datumbaza) disko.",
|
||||||
"This setting controls the free space required on the home (i.e., index database) disk.": "Ĉi tiu agordo regas la liberan spacon, postulitan sur la hejma disko (t.e. en kiu la indeksa datumbazo estas enmemorigata).",
|
|
||||||
"Time": "Tempo",
|
"Time": "Tempo",
|
||||||
"Time the item was last modified": "Tempo de lasta modifo de la ero",
|
"Time the item was last modified": "Tempo de lasta modifo de la ero",
|
||||||
"To connect with the Syncthing device named \"{%devicename%}\", add a new remote device on your end with this ID:": "Por konekti al aparato Syncthing nomita \"{{devicename}}\", aldonu novan foran aparaton kun ĉi tiu identigilo en via aparato:",
|
"Trash Can File Versioning": "Rubuja Dosiera Versionado",
|
||||||
"To permit a rule, have the checkbox checked. To deny a rule, leave it unchecked.": "Por permesi regulon, ŝalti la markobutonon. Por negi regulon, malŝalti ĝin.",
|
|
||||||
"Today": "Hodiaŭ",
|
|
||||||
"Trash Can": "Rubujo",
|
|
||||||
"Trash Can File Versioning": "Rubuja versiado de dosieroj",
|
|
||||||
"Type": "Tipo",
|
"Type": "Tipo",
|
||||||
"UNIX Permissions": "Permesoj de UNIX",
|
"UNIX Permissions": "Permesoj UNIX",
|
||||||
"Unavailable": "Ne disponebla",
|
"Unavailable": "Ne disponebla",
|
||||||
"Unavailable/Disabled by administrator or maintainer": "Ne disponebla/Malebligita de administranto aŭ subtenanto",
|
"Unavailable/Disabled by administrator or maintainer": "Ne disponebla/Malebligita de administranto aŭ subtenanto",
|
||||||
"Undecided (will prompt)": "Hezitema (demandos)",
|
"Undecided (will prompt)": "Hezitema (demandos)",
|
||||||
"Unexpected Items": "Neatenditaj eroj",
|
"Unignore": "Malignoru",
|
||||||
"Unexpected items have been found in this folder.": "Neatenditaj eroj estis trovitaj en ĉi tiu dosierujo.",
|
|
||||||
"Unignore": "Malignori",
|
|
||||||
"Unknown": "Nekonata",
|
"Unknown": "Nekonata",
|
||||||
"Unshared": "Nekunhavata",
|
"Unshared": "Nekomunigita",
|
||||||
"Unshared Devices": "Nekunhavataj aparatoj",
|
"Unshared Devices": "Malkomunigitaj aparatoj",
|
||||||
"Unshared Folders": "Nekunhavataj dosierujoj",
|
|
||||||
"Untrusted": "Ne fidita",
|
|
||||||
"Up to Date": "Ĝisdata",
|
"Up to Date": "Ĝisdata",
|
||||||
"Updated {%file%}": "Ĝisdatigita: {{file}}",
|
"Upgrade": "Altgradigo",
|
||||||
"Upgrade": "Ĝisdatigo",
|
"Upgrade To {%version%}": "Altgradigi Al {{version}}",
|
||||||
"Upgrade To {%version%}": "Ĝisdatigi al {{version}}",
|
"Upgrading": "Altgradigata",
|
||||||
"Upgrading": "Ĝisdatigado",
|
|
||||||
"Upload Rate": "Alŝutrapideco",
|
"Upload Rate": "Alŝutrapideco",
|
||||||
"Uptime": "Daŭro de funkciado",
|
"Uptime": "Daŭro de funkciado",
|
||||||
"Usage reporting is always enabled for candidate releases.": "Uzada raportado ĉiam ŝaltita por kandidataj eldonoj.",
|
"Usage reporting is always enabled for candidate releases.": "Uzada raportado ĉiam ŝaltita por kandidataj eldonoj.",
|
||||||
"Use HTTPS for GUI": "Uzi HTTPS por grafika interfaco",
|
"Use HTTPS for GUI": "Uzi HTTPS por grafika interfaco.",
|
||||||
"Use notifications from the filesystem to detect changed items.": "Uzi sciigoj de la dosiersistemo por detekti ŝanĝitajn erojn.",
|
"Use notifications from the filesystem to detect changed items.": "Uzi sciigoj de la dosiersistemo por detekti ŝanĝitajn erojn.",
|
||||||
"User": "Uzanto",
|
|
||||||
"User Home": "Hejmo de uzanto",
|
|
||||||
"Username/Password has not been set for the GUI authentication. Please consider setting it up.": "Uzantnomo kaj pasvorto ne estis agorditaj por aŭtentokontrolo per grafika interfaco. Bonvolu pripensi agordi ilin.",
|
|
||||||
"Using a QUIC connection over LAN": "QUIC-konekto sur loka reto uzata",
|
|
||||||
"Using a QUIC connection over WAN": "QUIC-konekto sur malloka reto uzata",
|
|
||||||
"Using a direct TCP connection over LAN": "Rekta TCP-konekto sur loka reto uzata",
|
|
||||||
"Using a direct TCP connection over WAN": "Rekta TCP-konekto sur malloka reto uzata",
|
|
||||||
"Version": "Versio",
|
"Version": "Versio",
|
||||||
"Versions": "Versioj",
|
"Versions": "Versioj",
|
||||||
"Versions Path": "Vojo de versioj",
|
"Versions Path": "Vojo de Versioj",
|
||||||
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Versioj estas aŭtomate forigitaj, se ili estas pli malnovaj ol la maksimuma aĝo, aŭ superas la nombron da dosieroj, permesitaj en intertempo.",
|
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Versioj estas aŭtomate forigita se ili estas pli malnovaj ol la maksimuma aĝo aŭ superas la nombron da dosieroj permesita en intervalo.",
|
||||||
"Waiting to Clean": "Atendado por purigado",
|
"Waiting to Scan": "Atendante skanadon",
|
||||||
"Waiting to Scan": "Atendado por skanado",
|
"Waiting to Sync": "Atendante sinkronigadon",
|
||||||
"Waiting to Sync": "Atendado por sinkronigado",
|
"Warning, this path is a parent directory of an existing folder \"{%otherFolder%}\".": "Averto, ĉi tiu vojo estas parenta dosierujo de ekzistanta dosierujo \"{{otherFolder}}\".",
|
||||||
"Warning": "Averto",
|
"Warning, this path is a parent directory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Averto, ĉi tiu vojo estas parenta dosierujo de ekzistanta dosierujo \"{{otherFolderLabel}}\" ({{otherFolder}}).",
|
||||||
"Warning, this path is a parent directory of an existing folder \"{%otherFolder%}\".": "Averto, ĉi tiu vojo estas parenca dosierujo de ekzistanta dosierujo \"{{otherFolder}}\".",
|
|
||||||
"Warning, this path is a parent directory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Averto, ĉi tiu vojo estas parenca dosierujo de ekzistanta dosierujo \"{{otherFolderLabel}}\" ({{otherFolder}}).",
|
|
||||||
"Warning, this path is a subdirectory of an existing folder \"{%otherFolder%}\".": "Averto, ĉi tiu vojo estas subdosierujo de ekzistanta dosierujo \"{{otherFolder}}\".",
|
"Warning, this path is a subdirectory of an existing folder \"{%otherFolder%}\".": "Averto, ĉi tiu vojo estas subdosierujo de ekzistanta dosierujo \"{{otherFolder}}\".",
|
||||||
"Warning, this path is a subdirectory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Averto, ĉi tiu vojo estas subdosierujo de ekzistanta dosierujo \"{{otherFolderLabel}}\" ({{otherFolder}}).",
|
"Warning, this path is a subdirectory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Averto, ĉi tiu vojo estas subdosierujo de ekzistanta dosierujo \"{{otherFolderLabel}}\" ({{otherFolder}}).",
|
||||||
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Averto: se vi uzas eksteran rigardanton, similan al {{syncthingInotify}}, vi devas certiĝi, ke ĝi estas malaktiva.",
|
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Averto: se vi uzas ekstera rigardanto simila al {{syncthingInotify}}, vi devas certiĝi ĝi estas senaktivita.",
|
||||||
"Watch for Changes": "Atendi por ŝanĝoj",
|
"Watch for Changes": "Rigardi Ŝanĝojn",
|
||||||
"Watching for Changes": "Atendado por ŝanĝoj",
|
"Watching for Changes": "Rigardado je Ŝanĝoj",
|
||||||
"Watching for changes discovers most changes without periodic scanning.": "Atendado por ŝanĝoj malkovras plejparton de la ŝanĝoj sen perioda skanado.",
|
"Watching for changes discovers most changes without periodic scanning.": "Rigardado je ŝanĝoj malkovras plejparton de la ŝanĝoj sen perioda skanado.",
|
||||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "Dum la aldonado de nova aparato, memoru ke ĉi tiu aparato devas esti aldonita en la alia flanko ankaŭ.",
|
"When adding a new device, keep in mind that this device must be added on the other side too.": "Dum la aldonado de nova aparato, memoru ke ĉi tiu aparato devas esti aldonita en la alia flanko ankaŭ.",
|
||||||
"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.": "Dum la aldonado de nova dosierujo, memoru ke la dosieruja identigilo estas uzita por ligi la dosierujojn kune inter aparatoj. Ili estas usklecodistingaj kaj devas kongrui precize inter ĉiuj aparatoj.",
|
"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.": "Dum la aldonado de nova dosierujo, memoru ke la Dosieruja ID estas uzita por ligi la dosierujojn kune inter aparatoj. Ili estas literfakodistingaj kaj devas kongrui precize inter ĉiuj aparatoj.",
|
||||||
"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.": "Kiam agordita kiel pli ol unu en ambaŭ aparatoj, Syncthing provos starigi multajn samtempajn konektojn. Se la valoroj malsamaj, la pleja estos uzata. Agordu kiel nul, por ke Syncthing decidos mem.",
|
|
||||||
"Yes": "Jes",
|
"Yes": "Jes",
|
||||||
"Yesterday": "Hieraŭ",
|
|
||||||
"You can also copy and paste the text into a new message manually.": "Vi povas ankaŭ kopii kaj alglui la tekston en novan mesaĝon permane.",
|
|
||||||
"You can also select one of these nearby devices:": "Vi povas ankaŭ elekti unu el ĉi tiuj proksimaj aparatoj:",
|
"You can also select one of these nearby devices:": "Vi povas ankaŭ elekti unu el ĉi tiuj proksimaj aparatoj:",
|
||||||
"You can change your choice at any time in the Settings dialog.": "Vi povas ŝanĝi vian elekton iam ajn en la dialogo \"Agordoj\".",
|
"You can change your choice at any time in the Settings dialog.": "Vi povas ŝanĝi vian elekton iam ajn en la Agorda dialogo.",
|
||||||
"You can read more about the two release channels at the link below.": "Vi povas legi plu pri la du eldonkanaloj per la malsupra ligilo.",
|
"You can read more about the two release channels at the link below.": "Vi povas legi plu pri la du eldonkanaloj per la malsupra ligilo.",
|
||||||
"You have no ignored devices.": "Vi havas neniujn ignoratajn aparatojn.",
|
"You have no ignored devices.": "Vi havas neniujn ignoritajn aparatojn.",
|
||||||
"You have no ignored folders.": "Vi havas neniujn ignoratajn dosierujojn.",
|
"You have no ignored folders.": "Vi havas neniujn ignoritajn dosierujojn.",
|
||||||
"You have unsaved changes. Do you really want to discard them?": "Vi havas nekonservitajn ŝanĝojn. Ĉu vi vere volas forĵeti ilin?",
|
"You have unsaved changes. Do you really want to discard them?": "Vi havas ne konservitaj ŝanĝoj. Ĉu vi vere volas forĵeti ilin?",
|
||||||
"You must keep at least one version.": "Vi devas konservi almenaŭ unu version.",
|
"You must keep at least one version.": "Vi devas konservi almenaŭ unu version.",
|
||||||
"You should never add or change anything locally in a \"{%receiveEncrypted%}\" folder.": "Vi neniam devus aldoni aŭ ŝanĝi ion ajn loke en dosierujo \"{{receiveEncrypted}}\".",
|
|
||||||
"Your SMS app should open to let you choose the recipient and send it from your own number.": "Via SMS-apo devus malfermi, por ke vi povas elekti la adresaton kaj sendi la mesaĝon el via telefonnumero.",
|
|
||||||
"Your email app should open to let you choose the recipient and send it from your own address.": "Via retpoŝta apo devus malfermi, por ke vi povas elekti la adresaton kaj sendi la mesaĝon el via retpoŝtadreso.",
|
|
||||||
"days": "tagoj",
|
"days": "tagoj",
|
||||||
"deleted": "forigita",
|
|
||||||
"deny": "negi",
|
|
||||||
"directories": "dosierujoj",
|
"directories": "dosierujoj",
|
||||||
"file": "dosiero",
|
|
||||||
"files": "dosieroj",
|
"files": "dosieroj",
|
||||||
"folder": "dosierujo",
|
|
||||||
"full documentation": "tuta dokumentado",
|
"full documentation": "tuta dokumentado",
|
||||||
"items": "eroj",
|
"items": "eroj",
|
||||||
"modified": "modifita",
|
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} volas komunigi dosierujon \"{{folder}}\".",
|
||||||
"permit": "permesi",
|
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} volas komunigi dosierujon \"{{folderlabel}}\" ({{folder}})."
|
||||||
"seconds": "sekundoj",
|
|
||||||
"theme": {
|
|
||||||
"name": {
|
|
||||||
"black": "Nigra",
|
|
||||||
"dark": "Malhela",
|
|
||||||
"default": "Defaŭlta",
|
|
||||||
"light": "Hela"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"unknown device": "nekonata aparato",
|
|
||||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} volas kunhavigi dosierujon \"{{folder}}\".",
|
|
||||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} volas kunhavigi dosierujon \"{{folderlabel}}\" ({{folder}}).",
|
|
||||||
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} povas rekonatigi ĉi tiun aparaton."
|
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user