Compare commits
20
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0945304a79 | ||
|
|
9703dd9f57 | ||
|
|
259e9ef08e | ||
|
|
6a0c6128d8 | ||
|
|
b05ece0681 | ||
|
|
e9133ef82b | ||
|
|
67ba20d777 | ||
|
|
21da0d7890 | ||
|
|
ebbe57d0ab | ||
|
|
f4abc71dcc | ||
|
|
8aa02da93a | ||
|
|
0e560486db | ||
|
|
57d413099d | ||
|
|
1fdf07933c | ||
|
|
c50678618f | ||
|
|
8094b459e4 | ||
|
|
6765867a2e | ||
|
|
4fb8ee6a6f | ||
|
|
674834ccf4 | ||
|
|
3bd2bff23b |
@@ -1,6 +1,7 @@
|
|||||||
name: Feature request
|
name: Feature request
|
||||||
description: File a new feature request
|
description: File a new feature request
|
||||||
labels: ["enhancement", "needs-triage"]
|
labels: ["enhancement", "needs-triage"]
|
||||||
|
type: Feature
|
||||||
body:
|
body:
|
||||||
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
name: Bug report
|
name: Bug report
|
||||||
description: If you're actually looking for support instead, see "I need help / I have a question".
|
description: If you're actually looking for support instead, see "I need help / I have a question".
|
||||||
labels: ["bug", "needs-triage"]
|
labels: ["bug", "needs-triage"]
|
||||||
|
type: Bug
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
|
|||||||
@@ -716,7 +716,7 @@ jobs:
|
|||||||
RCLONE_CONFIG_OBJSTORE_REGION: ${{ secrets.S3_REGION }}
|
RCLONE_CONFIG_OBJSTORE_REGION: ${{ secrets.S3_REGION }}
|
||||||
RCLONE_CONFIG_OBJSTORE_ACL: public-read
|
RCLONE_CONFIG_OBJSTORE_ACL: public-read
|
||||||
with:
|
with:
|
||||||
args: sync -v packages objstore:nightly
|
args: sync -v --no-update-modtime packages objstore:nightly
|
||||||
|
|
||||||
#
|
#
|
||||||
# Push release artifacts to Spaces
|
# Push release artifacts to Spaces
|
||||||
@@ -772,7 +772,7 @@ jobs:
|
|||||||
RCLONE_CONFIG_OBJSTORE_REGION: ${{ secrets.S3_REGION }}
|
RCLONE_CONFIG_OBJSTORE_REGION: ${{ secrets.S3_REGION }}
|
||||||
RCLONE_CONFIG_OBJSTORE_ACL: public-read
|
RCLONE_CONFIG_OBJSTORE_ACL: public-read
|
||||||
with:
|
with:
|
||||||
args: sync -v packages objstore:release/${{ env.VERSION }}
|
args: sync -v --no-update-modtime packages objstore:release/${{ env.VERSION }}
|
||||||
|
|
||||||
- name: Push to object store (latest)
|
- name: Push to object store (latest)
|
||||||
uses: docker://docker.io/rclone/rclone:latest
|
uses: docker://docker.io/rclone/rclone:latest
|
||||||
@@ -785,9 +785,9 @@ jobs:
|
|||||||
RCLONE_CONFIG_OBJSTORE_REGION: ${{ secrets.S3_REGION }}
|
RCLONE_CONFIG_OBJSTORE_REGION: ${{ secrets.S3_REGION }}
|
||||||
RCLONE_CONFIG_OBJSTORE_ACL: public-read
|
RCLONE_CONFIG_OBJSTORE_ACL: public-read
|
||||||
with:
|
with:
|
||||||
args: sync -v objstore:release/${{ env.VERSION }} objstore:release/latest
|
args: sync -v --no-update-modtime objstore:release/${{ env.VERSION }} objstore:release/latest
|
||||||
|
|
||||||
- name: Create GitHub release and push binaries
|
- name: Create GitHub releases and push binaries
|
||||||
run: |
|
run: |
|
||||||
maybePrerelease=""
|
maybePrerelease=""
|
||||||
if [[ $VERSION == *-* ]]; then
|
if [[ $VERSION == *-* ]]; then
|
||||||
@@ -795,19 +795,33 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
export GH_PROMPT_DISABLED=1
|
export GH_PROMPT_DISABLED=1
|
||||||
if ! gh release view --json name "$VERSION" >/dev/null 2>&1 ; then
|
if ! gh release view --json name "$VERSION" >/dev/null 2>&1 ; then
|
||||||
gh release create \
|
gh release create "$VERSION" \
|
||||||
"$VERSION" \
|
|
||||||
$maybePrerelease \
|
$maybePrerelease \
|
||||||
--title "$VERSION" \
|
--title "$VERSION" \
|
||||||
--notes-from-tag
|
--notes-from-tag
|
||||||
fi
|
fi
|
||||||
gh release upload "$VERSION" \
|
gh release upload --clobber "$VERSION" \
|
||||||
packages/*.asc packages/*.json \
|
packages/*.asc packages/*.json \
|
||||||
packages/syncthing-*.tar.gz \
|
packages/syncthing-*.tar.gz \
|
||||||
packages/syncthing-*.zip \
|
packages/syncthing-*.zip \
|
||||||
packages/syncthing*.deb
|
packages/syncthing_*.deb
|
||||||
|
|
||||||
|
PKGS=$(pwd)/packages
|
||||||
|
cd /tmp # gh will not release for repo x while inside repo y
|
||||||
|
for repo in relaysrv discosrv ; do
|
||||||
|
export GH_REPO="syncthing/$repo"
|
||||||
|
if ! gh release view --json name "$VERSION" >/dev/null 2>&1 ; then
|
||||||
|
gh release create "$VERSION" \
|
||||||
|
$maybePrerelease \
|
||||||
|
--title "$VERSION" \
|
||||||
|
--notes "https://github.com/syncthing/syncthing/releases/tag/$VERSION"
|
||||||
|
fi
|
||||||
|
gh release upload --clobber "$VERSION" \
|
||||||
|
$PKGS/*.asc \
|
||||||
|
$PKGS/*${repo}*
|
||||||
|
done
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.ACTIONS_GITHUB_TOKEN }}
|
||||||
|
|
||||||
#
|
#
|
||||||
# Push Debian/APT archive
|
# Push Debian/APT archive
|
||||||
@@ -885,7 +899,7 @@ jobs:
|
|||||||
RCLONE_CONFIG_OBJSTORE_REGION: ${{ secrets.S3_REGION }}
|
RCLONE_CONFIG_OBJSTORE_REGION: ${{ secrets.S3_REGION }}
|
||||||
RCLONE_CONFIG_OBJSTORE_ACL: public-read
|
RCLONE_CONFIG_OBJSTORE_ACL: public-read
|
||||||
with:
|
with:
|
||||||
args: sync -v dists objstore:apt/dists
|
args: sync -v --no-update-modtime dists objstore:apt/dists
|
||||||
|
|
||||||
#
|
#
|
||||||
# Build and push to Docker Hub
|
# Build and push to Docker Hub
|
||||||
|
|||||||
@@ -7,12 +7,9 @@ on:
|
|||||||
- reopened
|
- reopened
|
||||||
- edited
|
- edited
|
||||||
- synchronize
|
- synchronize
|
||||||
schedule:
|
|
||||||
- cron: "42 7 * * *"
|
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
issues: write
|
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|||||||
@@ -19,22 +19,18 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
ref: ${{ github.ref }} # https://github.com/actions/checkout/issues/882
|
ref: ${{ github.ref }} # https://github.com/actions/checkout/issues/882
|
||||||
token: ${{ secrets.STRELEASE_GITHUB_TOKEN }}
|
token: ${{ secrets.ACTIONS_GITHUB_TOKEN }}
|
||||||
|
|
||||||
- uses: actions/setup-go@v5
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: stable
|
go-version: stable
|
||||||
|
|
||||||
- name: Get svu
|
|
||||||
run: |
|
|
||||||
go install github.com/caarlos0/svu@latest
|
|
||||||
|
|
||||||
- name: Determine version to release
|
- name: Determine version to release
|
||||||
run: |
|
run: |
|
||||||
if [[ "$GITHUB_REF_NAME" == "release" ]] ; then
|
if [[ "$GITHUB_REF_NAME" == "release" ]] ; then
|
||||||
next=$(svu next)
|
next=$(go run ./script/next-version.go)
|
||||||
else
|
else
|
||||||
next=$(svu prerelease --pre-release rc)
|
next=$(go run ./script/next-version.go --pre)
|
||||||
fi
|
fi
|
||||||
echo "NEXT=$next" >> $GITHUB_ENV
|
echo "NEXT=$next" >> $GITHUB_ENV
|
||||||
echo "Next version is $next"
|
echo "Next version is $next"
|
||||||
@@ -47,7 +43,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
go run ./script/relnotes.go --new-ver "$NEXT" --branch "$GITHUB_REF_NAME" --prev-ver "$PREV" > notes.md
|
go run ./script/relnotes.go --new-ver "$NEXT" --branch "$GITHUB_REF_NAME" --prev-ver "$PREV" > notes.md
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.STRELEASE_GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.ACTIONS_GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Create and push tag
|
- name: Create and push tag
|
||||||
run: |
|
run: |
|
||||||
@@ -55,3 +51,10 @@ jobs:
|
|||||||
git config --global user.email 'release@syncthing.net'
|
git config --global user.email 'release@syncthing.net'
|
||||||
git tag -a -F notes.md --cleanup=whitespace "$NEXT"
|
git tag -a -F notes.md --cleanup=whitespace "$NEXT"
|
||||||
git push origin "$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 }}
|
||||||
|
|||||||
@@ -224,6 +224,7 @@ 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>
|
Marc Pujol (kilburn) <kilburn@la3.org>
|
||||||
|
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>
|
marco-m <marco.molteni@laposte.net>
|
||||||
Marcus B Spencer <marcus@marcusspencer.xyz> <marcus@marcusspencer.us>
|
Marcus B Spencer <marcus@marcusspencer.xyz> <marcus@marcusspencer.us>
|
||||||
|
|||||||
@@ -330,7 +330,7 @@ func runCommand(cmd string, target target) {
|
|||||||
writeCompatJSON()
|
writeCompatJSON()
|
||||||
|
|
||||||
case "deb":
|
case "deb":
|
||||||
buildDeb(target)
|
buildDeb(target, tags)
|
||||||
|
|
||||||
case "vet":
|
case "vet":
|
||||||
metalintShort()
|
metalintShort()
|
||||||
@@ -609,7 +609,7 @@ func buildZip(target target, tags []string) {
|
|||||||
fmt.Println(filename)
|
fmt.Println(filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildDeb(target target) {
|
func buildDeb(target target, tags []string) {
|
||||||
os.RemoveAll("deb")
|
os.RemoveAll("deb")
|
||||||
|
|
||||||
// "goarch" here is set to whatever the Debian packages expect. We correct
|
// "goarch" here is set to whatever the Debian packages expect. We correct
|
||||||
@@ -623,7 +623,7 @@ func buildDeb(target target) {
|
|||||||
goarch = "arm"
|
goarch = "arm"
|
||||||
}
|
}
|
||||||
|
|
||||||
build(target, []string{"noupgrade"})
|
build(target, append(tags, "noupgrade"))
|
||||||
|
|
||||||
for i := range target.installationFiles {
|
for i := range target.installationFiles {
|
||||||
target.installationFiles[i].src = strings.Replace(target.installationFiles[i].src, "{{binary}}", target.BinaryName(), 1)
|
target.installationFiles[i].src = strings.Replace(target.installationFiles[i].src, "{{binary}}", target.BinaryName(), 1)
|
||||||
|
|||||||
@@ -201,17 +201,21 @@ func (p *proxy) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||||||
// looking for a prerelease at all.
|
// looking for a prerelease at all.
|
||||||
func filterForLatest(rels []upgrade.Release) []upgrade.Release {
|
func filterForLatest(rels []upgrade.Release) []upgrade.Release {
|
||||||
var filtered []upgrade.Release
|
var filtered []upgrade.Release
|
||||||
var havePre bool
|
havePre := make(map[string]bool)
|
||||||
|
haveStable := make(map[string]bool)
|
||||||
for _, rel := range rels {
|
for _, rel := range rels {
|
||||||
if !rel.Prerelease {
|
major, _, _ := strings.Cut(rel.Tag, ".")
|
||||||
// We found a stable version, we're good now.
|
if !rel.Prerelease && !haveStable[major] {
|
||||||
|
// Remember the first non-pre for each major
|
||||||
filtered = append(filtered, rel)
|
filtered = append(filtered, rel)
|
||||||
break
|
haveStable[major] = true
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
if rel.Prerelease && !havePre {
|
if rel.Prerelease && !havePre[major] && !haveStable[major] {
|
||||||
// We remember the first prerelease we find.
|
// We remember the first prerelease we find, unless we've
|
||||||
|
// already found a non-pre of the same major.
|
||||||
filtered = append(filtered, rel)
|
filtered = append(filtered, rel)
|
||||||
havePre = true
|
havePre[major] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return filtered
|
return filtered
|
||||||
|
|||||||
@@ -578,6 +578,7 @@ func syncthingMain(options serveOptions) {
|
|||||||
os.Exit(svcutil.ExitError.AsInt())
|
os.Exit(svcutil.ExitError.AsInt())
|
||||||
}
|
}
|
||||||
earlyService.Add(cfgWrapper)
|
earlyService.Add(cfgWrapper)
|
||||||
|
config.RegisterInfoMetrics(cfgWrapper)
|
||||||
|
|
||||||
// Candidate builds should auto upgrade. Make sure the option is set,
|
// Candidate builds should auto upgrade. Make sure the option is set,
|
||||||
// unless we are in a build where it's disabled or the STNOUPGRADE
|
// unless we are in a build where it's disabled or the STNOUPGRADE
|
||||||
|
|||||||
+23
-25
@@ -238,19 +238,18 @@ func copyStderr(stderr io.Reader, dst io.Writer) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if panicFd == nil {
|
dst.Write([]byte(line))
|
||||||
dst.Write([]byte(line))
|
|
||||||
|
|
||||||
if 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 {
|
||||||
l.Warnln("Create panic log:", err)
|
l.Warnln("Create panic log:", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
l.Warnf("Panic detected, writing to \"%s\"", panicFd.Name())
|
l.Warnf("Panic detected, writing to \"%s\"", panicFd.Name())
|
||||||
if strings.Contains(line, "leveldb") && strings.Contains(line, "corrupt") {
|
if strings.Contains(line, "leveldb") && strings.Contains(line, "corrupt") {
|
||||||
l.Warnln(`
|
l.Warnln(`
|
||||||
*********************************************************************************
|
*********************************************************************************
|
||||||
* Crash due to corrupt database. *
|
* Crash due to corrupt database. *
|
||||||
* *
|
* *
|
||||||
@@ -263,22 +262,21 @@ func copyStderr(stderr io.Reader, dst io.Writer) {
|
|||||||
* https://docs.syncthing.net/users/faq.html#my-syncthing-database-is-corrupt *
|
* https://docs.syncthing.net/users/faq.html#my-syncthing-database-is-corrupt *
|
||||||
*********************************************************************************
|
*********************************************************************************
|
||||||
`)
|
`)
|
||||||
} else {
|
} else {
|
||||||
l.Warnln("Please check for existing issues with similar panic message at https://github.com/syncthing/syncthing/issues/")
|
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")
|
l.Warnln("If no issue with similar panic message exists, please create a new issue with the panic log attached")
|
||||||
}
|
|
||||||
|
|
||||||
stdoutMut.Lock()
|
|
||||||
for _, line := range stdoutFirstLines {
|
|
||||||
panicFd.WriteString(line)
|
|
||||||
}
|
|
||||||
panicFd.WriteString("...\n")
|
|
||||||
for _, line := range stdoutLastLines {
|
|
||||||
panicFd.WriteString(line)
|
|
||||||
}
|
|
||||||
stdoutMut.Unlock()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stdoutMut.Lock()
|
||||||
|
for _, line := range stdoutFirstLines {
|
||||||
|
panicFd.WriteString(line)
|
||||||
|
}
|
||||||
|
panicFd.WriteString("...\n")
|
||||||
|
for _, line := range stdoutLastLines {
|
||||||
|
panicFd.WriteString(line)
|
||||||
|
}
|
||||||
|
stdoutMut.Unlock()
|
||||||
|
|
||||||
panicFd.WriteString("Panic at " + time.Now().Format(time.RFC3339) + "\n")
|
panicFd.WriteString("Panic at " + time.Now().Format(time.RFC3339) + "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ require (
|
|||||||
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/coreos/go-semver v0.3.1 // indirect
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.5 // 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/ebitengine/purego v0.8.3 // indirect
|
github.com/ebitengine/purego v0.8.3 // indirect
|
||||||
|
|||||||
@@ -46,6 +46,8 @@ github.com/chmduquesne/rollinghash v4.0.0+incompatible/go.mod h1:Uc2I36RRfTAf7Dg
|
|||||||
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/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc=
|
github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
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=
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
"Allowed Networks": "Mga Pinapayagang Network",
|
"Allowed Networks": "Mga Pinapayagang Network",
|
||||||
"Alphabetic": "Alpabetiko",
|
"Alphabetic": "Alpabetiko",
|
||||||
"Altered by ignoring deletes.": "Binago sa pamamagitan ng hindi pagpansin sa mga pagtanggal.",
|
"Altered by ignoring deletes.": "Binago sa pamamagitan ng hindi pagpansin sa mga pagtanggal.",
|
||||||
|
"Always turned on when the folder type is \"{%foldertype%}\".": "Palaging nakabukas kung ang uri ng folder ay nakatakda bilang \"{{foldertype}}\".",
|
||||||
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "Pinapamahala ng external na command ang file versioning. Kailangan nitong tanggalin ang file mula sa binabahaging folder. Kung may mga space ang path sa application, kailangan itong i-quote.",
|
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "Pinapamahala ng external na command ang file versioning. Kailangan nitong tanggalin ang file mula sa binabahaging folder. Kung may mga space ang path sa application, kailangan itong i-quote.",
|
||||||
"Anonymous Usage Reporting": "Anonymous na Pag-uulat ng Paggamit",
|
"Anonymous Usage Reporting": "Anonymous na Pag-uulat ng Paggamit",
|
||||||
"Anonymous usage report format has changed. Would you like to move to the new format?": "Nagbago ang pormat ng anonymous na ulat ng paggamit. Gusto mo bang lumipat sa bagong pormat?",
|
"Anonymous usage report format has changed. Would you like to move to the new format?": "Nagbago ang pormat ng anonymous na ulat ng paggamit. Gusto mo bang lumipat sa bagong pormat?",
|
||||||
@@ -52,6 +53,7 @@
|
|||||||
"Body:": "Body:",
|
"Body:": "Body:",
|
||||||
"Bugs": "Mga Bug",
|
"Bugs": "Mga Bug",
|
||||||
"Cancel": "Kanselahin",
|
"Cancel": "Kanselahin",
|
||||||
|
"Cannot be enabled when the folder type is \"{%foldertype%}\".": "Hindi maaaring paganahin kapag ang uri ng folder ay \"{{foldertype}}\".",
|
||||||
"Changelog": "Mga Pagbabago",
|
"Changelog": "Mga Pagbabago",
|
||||||
"Clean out after": "Linisin pagkatapos",
|
"Clean out after": "Linisin pagkatapos",
|
||||||
"Cleaning Versions": "Mga Bersyon ng Paglinis",
|
"Cleaning Versions": "Mga Bersyon ng Paglinis",
|
||||||
@@ -311,7 +313,7 @@
|
|||||||
"Receive Encrypted": "Makatanggap Naka-Encrypt",
|
"Receive Encrypted": "Makatanggap Naka-Encrypt",
|
||||||
"Receive Only": "Makatanggap Lamang",
|
"Receive Only": "Makatanggap Lamang",
|
||||||
"Received data is already encrypted": "Naka-encrypt na ang natanggap na data",
|
"Received data is already encrypted": "Naka-encrypt na ang natanggap na data",
|
||||||
"Recent Changes": "Mga Kamakilang Pagbabago",
|
"Recent Changes": "Mga Kamakailang Pagbabago",
|
||||||
"Reduced by ignore patterns": "Binabawasan ng mga ignore pattern",
|
"Reduced by ignore patterns": "Binabawasan ng mga ignore pattern",
|
||||||
"Relay LAN": "Relay na LAN",
|
"Relay LAN": "Relay na LAN",
|
||||||
"Relay WAN": "Relay na WAN",
|
"Relay WAN": "Relay na WAN",
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
<h4 class="text-center" translate>The Syncthing Authors</h4>
|
<h4 class="text-center" translate>The Syncthing Authors</h4>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12" id="contributor-list">
|
<div class="col-md-12" id="contributor-list">
|
||||||
Jakob Borg, Audrius Butkevicius, Jesse Lucas, Simon Frei, Tomasz Wilczyński, Alexander Graf, Alexandre Viau, Anderson Mesquita, André Colomb, Antony Male, Ben Schulz, Caleb Callaway, Daniel Harte, Emil Lundberg, Eric P, Evgeny Kuznetsov, Lars K.W. Gohlke, Lode Hoste, Michael Ploujnikov, Nate Morrison, Philippe Schommers, Ross Smith II, Ryan Sullivan, Sergey Mishin, Stefan Tatschner, Wulf Weich, bt90, greatroar, Aaron Bieber, Adam Piggott, Adel Qalieh, Alan Pope, Alberto Donato, Aleksey Vasenev, Alessandro G., Alex Ionescu, Alex Lindeman, Alex Xu, Alexander Seiler, Alexandre Alves, Aman Gupta, Anatoli Babenia, Andreas Sommer, Andrew Dunham, Andrew Meyer, Andrew Rabert, Andrey D, Anjan Momi, Anthony Goeckner, Antoine Lamielle, Anur, Aranjedeath, Arkadiusz Tymiński, Aroun, Arthur Axel fREW Schmidt, Artur Zubilewicz, Ashish Bhate, Aurélien Rainone, BAHADIR YILMAZ, Bart De Vries, Beat Reichenbach, Ben Curthoys, Ben Shepherd, Ben Sidhom, Benedikt Heine, Benedikt Morbach, Benjamin Nater, Benno Fünfstück, Benny Ng, Boqin Qin, Boris Rybalkin, Brandon Philips, Brendan Long, Brian R. Becker, Carsten Hagemann, Catfriend1, Cathryne Linenweaver, Cedric Staniewski, Chih-Hsuan Yen, Choongkyu, Chris Howie, Chris Joel, Chris Tonkinson, Christian Kujau, Christian Prescott, Colin Kennedy, Cromefire_, Cyprien Devillez, Dale Visser, Dan, Daniel Barczyk, Daniel Bergmann, Daniel Martí, Daniel Padrta, Darshil Chanpura, David Rimmer, DeflateAwning, Denis A., Dennis Wilson, DerRockWolf, Devon G. Redekopp, Dimitri Papadopoulos Orfanos, Dmitry Saveliev, Domenic Horner, Dominik Heidler, Elias Jarlebring, Elliot Huffman, Emil Hessman, Eng Zer Jun, Eric Lesiuta, Erik Meitner, Evan Spensley, Federico Castagnini, Felix, Felix Ableitner, Felix Lampe, Felix Unterpaintner, Francois-Xavier Gsell, Frank Isemann, Gahl Saraf, Gilli Sigurdsson, Gleb Sinyavskiy, Graham Miln, Greg, Gusted, Han Boetes, HansK-p, Harrison Jones, Hazem Krimi, Heiko Zuerker, Hireworks, Hugo Locurcio, Iain Barnett, Ian Johnson, Ikko Ashimine, Ilya Brin, Iskander Sharipov, Jaakko Hannikainen, Jacek Szafarkiewicz, Jack Croft, Jacob, Jake Peterson, James O'Beirne, James Patterson, Jaroslav Lichtblau, Jaroslav Malec, Jaspitta, Jauder Ho, Jaya Chithra, Jaya Kumar, Jeffery To, Jens Diemer, Jerry Jacobs, Jochen Voss, Johan Andersson, Johan Vromans, John Rinehart, Jonas Thelemann, Jonathan, Jonathan Cross, Jonta, Jose Manuel Delicado, Julian Lehrhuber, Jörg Thalheim, Jędrzej Kula, K.B.Dharun Krishna, Kalle Laine, Kapil Sareen, Karol Różycki, Kebin Liu, Keith Harrison, Keith Turner, Kelong Cong, Ken'ichi Kamada, Kevin Allen, Kevin Bushiri, Kevin White, Jr., Kurt Fitzner, LSmithx2, Lars Lehtonen, Laurent Arnoud, Laurent Etiemble, Leo Arias, Liu Siyuan, Lord Landon Agahnim, Lukas Lihotzki, Luke Hamburg, Majed Abdulaziz, Marc Laporte, Marc Pujol, Marcin Dziadus, Marcus B Spencer, Marcus Legendre, Mario Majila, Mark Pulford, Martchus, Martin Polehla, Mateusz Naściszewski, Mateusz Ż, Matic Potočnik, Matt Burke, Matt Robenolt, Matteo Ruina, Maurizio Tomasi, Max, Max Schulze, MaximAL, Maxime Thirouin, Maximilian, MichaIng, Michael Jephcote, Michael Rienstra, Michael Tilli, Migelo, Mike Boone, MikeLund, MikolajTwarog, Mingxuan Lin, Naveen, Nicholas Rishel, Nick Busey, Nico Stapelbroek, Nicolas Braud-Santoni, Nicolas Perraut, Niels Peter Roest, Nils Jakobi, NinoM4ster, Nitroretro, NoLooseEnds, Oliver Freyermuth, Otiel, Oyebanji Jacob Mayowa, Pablo, Pascal Jungblut, Paul Brit, Paul Donald, Pawel Palenica, Paweł Rozlach, Peter Badida, Peter Dave Hello, Peter Hoeg, Peter Marquardt, Phani Rithvij, Phil Davis, Phill Luby, Pier Paolo Ramon, Piotr Bejda, Pramodh KP, Quentin Hibon, Rahmi Pruitt, Richard Hartmann, Robert Carosi, Roberto Santalla, Robin Schoonover, Roman Zaynetdinov, Ruslan Yevdokymov, Ryan Qian, Sacheendra Talluri, Scott Klupfel, Sertonix, Severin von Wnuck-Lipinski, Shaarad Dalvi, Simon Mwepu, Simon Pickup, Sly_tom_cat, Sonu Kumar Saw, Stefan Kuntz, Steven Eckhoff, Suhas Gundimeda, Sven Bachmann, Sébastien WENSKE, Taylor Khan, Terrance, TheCreeper, Thomas, Thomas Hipp, Tim Abell, Tim Howes, Tim Nordenfur, Tobias Frölich, Tobias Klauser, Tobias Nygren, Tobias Tom, Tom Jakubowski, Tommy Thorn, Tommy van der Vorst, Tully Robinson, Tyler Brazier, Tyler Kropp, Unrud, Veeti Paananen, Victor Buinsky, Vik, Vil Brekin, Vladimir Rusinov, WangXi, Will Rouesnel, William A. Kennington III, Xavier O., Yannic A., andresvia, andyleap, boomsquared, chenrui, chucic, cjc7373, cui fliter, d-volution, dashangcun, derekriemer, desbma, diemade, digital, domain, entity0xfe, georgespatton, ghjklw, guangwu, gudvinr, ignacy123, janost, jaseg, jelle van der Waa, jtagcat, klemens, kylosus, luchenhan, luzpaz, marco-m, mathias4833, maxice8, mclang, mv1005, nf, orangekame3, otbutz, overkill, perewa, polyfloyd, pullmerge, red_led, rubenbe, sec65, vapatel2, villekalliomaki, wangguoliang, wouter bolsterlee, xarx00, xjtdy888, 佛跳墙, 落心
|
Jakob Borg, Audrius Butkevicius, Jesse Lucas, Simon Frei, Tomasz Wilczyński, Alexander Graf, Alexandre Viau, Anderson Mesquita, André Colomb, Antony Male, Ben Schulz, Caleb Callaway, Daniel Harte, Emil Lundberg, Eric P, Evgeny Kuznetsov, Lars K.W. Gohlke, Lode Hoste, Michael Ploujnikov, Nate Morrison, Philippe Schommers, Ross Smith II, Ryan Sullivan, Sergey Mishin, Stefan Tatschner, Wulf Weich, bt90, greatroar, Aaron Bieber, Adam Piggott, Adel Qalieh, Alan Pope, Alberto Donato, Aleksey Vasenev, Alessandro G., Alex Ionescu, Alex Lindeman, Alex Xu, Alexander Seiler, Alexandre Alves, Aman Gupta, Anatoli Babenia, Andreas Sommer, Andrew Dunham, Andrew Meyer, Andrew Rabert, Andrey D, Anjan Momi, Anthony Goeckner, Antoine Lamielle, Anur, Aranjedeath, Arkadiusz Tymiński, Aroun, Arthur Axel fREW Schmidt, Artur Zubilewicz, Ashish Bhate, Aurélien Rainone, BAHADIR YILMAZ, Bart De Vries, Beat Reichenbach, Ben Curthoys, Ben Shepherd, Ben Sidhom, Benedikt Heine, Benedikt Morbach, Benjamin Nater, Benno Fünfstück, Benny Ng, Boqin Qin, Boris Rybalkin, Brandon Philips, Brendan Long, Brian R. Becker, Carsten Hagemann, Catfriend1, Cathryne Linenweaver, Cedric Staniewski, Chih-Hsuan Yen, Choongkyu, Chris Howie, Chris Joel, Chris Tonkinson, Christian Kujau, Christian Prescott, Colin Kennedy, Cromefire_, Cyprien Devillez, Dale Visser, Dan, Daniel Barczyk, Daniel Bergmann, Daniel Martí, Daniel Padrta, Darshil Chanpura, David Rimmer, DeflateAwning, Denis A., Dennis Wilson, DerRockWolf, Devon G. Redekopp, Dimitri Papadopoulos Orfanos, Dmitry Saveliev, Domenic Horner, Dominik Heidler, Elias Jarlebring, Elliot Huffman, Emil Hessman, Eng Zer Jun, Eric Lesiuta, Erik Meitner, Evan Spensley, Federico Castagnini, Felix, Felix Ableitner, Felix Lampe, Felix Unterpaintner, Francois-Xavier Gsell, Frank Isemann, Gahl Saraf, Gilli Sigurdsson, Gleb Sinyavskiy, Graham Miln, Greg, Gusted, Han Boetes, HansK-p, Harrison Jones, Hazem Krimi, Heiko Zuerker, Hireworks, Hugo Locurcio, Iain Barnett, Ian Johnson, Ikko Ashimine, Ilya Brin, Iskander Sharipov, Jaakko Hannikainen, Jacek Szafarkiewicz, Jack Croft, Jacob, Jake Peterson, James O'Beirne, James Patterson, Jaroslav Lichtblau, Jaroslav Malec, Jaspitta, Jauder Ho, Jaya Chithra, Jaya Kumar, Jeffery To, Jens Diemer, Jerry Jacobs, Jochen Voss, Johan Andersson, Johan Vromans, John Rinehart, Jonas Thelemann, Jonathan, Jonathan Cross, Jonta, Jose Manuel Delicado, Julian Lehrhuber, Jörg Thalheim, Jędrzej Kula, K.B.Dharun Krishna, Kalle Laine, Kapil Sareen, Karol Różycki, Kebin Liu, Keith Harrison, Keith Turner, Kelong Cong, Ken'ichi Kamada, Kevin Allen, Kevin Bushiri, Kevin White, Jr., Kurt Fitzner, LSmithx2, Lars Lehtonen, Laurent Arnoud, Laurent Etiemble, Leo Arias, Liu Siyuan, Lord Landon Agahnim, Lukas Lihotzki, Luke Hamburg, Majed Abdulaziz, Marc Laporte, Marc Pujol, Marcel Meyer, Marcin Dziadus, Marcus B Spencer, Marcus Legendre, Mario Majila, Mark Pulford, Martchus, Martin Polehla, Mateusz Naściszewski, Mateusz Ż, Matic Potočnik, Matt Burke, Matt Robenolt, Matteo Ruina, Maurizio Tomasi, Max, Max Schulze, MaximAL, Maxime Thirouin, Maximilian, MichaIng, Michael Jephcote, Michael Rienstra, Michael Tilli, Migelo, Mike Boone, MikeLund, MikolajTwarog, Mingxuan Lin, Naveen, Nicholas Rishel, Nick Busey, Nico Stapelbroek, Nicolas Braud-Santoni, Nicolas Perraut, Niels Peter Roest, Nils Jakobi, NinoM4ster, Nitroretro, NoLooseEnds, Oliver Freyermuth, Otiel, Oyebanji Jacob Mayowa, Pablo, Pascal Jungblut, Paul Brit, Paul Donald, Pawel Palenica, Paweł Rozlach, Peter Badida, Peter Dave Hello, Peter Hoeg, Peter Marquardt, Phani Rithvij, Phil Davis, Phill Luby, Pier Paolo Ramon, Piotr Bejda, Pramodh KP, Quentin Hibon, Rahmi Pruitt, Richard Hartmann, Robert Carosi, Roberto Santalla, Robin Schoonover, Roman Zaynetdinov, Ruslan Yevdokymov, Ryan Qian, Sacheendra Talluri, Scott Klupfel, Sertonix, Severin von Wnuck-Lipinski, Shaarad Dalvi, Simon Mwepu, Simon Pickup, Sly_tom_cat, Sonu Kumar Saw, Stefan Kuntz, Steven Eckhoff, Suhas Gundimeda, Sven Bachmann, Sébastien WENSKE, Taylor Khan, Terrance, TheCreeper, Thomas, Thomas Hipp, Tim Abell, Tim Howes, Tim Nordenfur, Tobias Frölich, Tobias Klauser, Tobias Nygren, Tobias Tom, Tom Jakubowski, Tommy Thorn, Tommy van der Vorst, Tully Robinson, Tyler Brazier, Tyler Kropp, Unrud, Veeti Paananen, Victor Buinsky, Vik, Vil Brekin, Vladimir Rusinov, WangXi, Will Rouesnel, William A. Kennington III, Xavier O., Yannic A., andresvia, andyleap, boomsquared, chenrui, chucic, cjc7373, cui fliter, d-volution, dashangcun, derekriemer, desbma, diemade, digital, domain, entity0xfe, georgespatton, ghjklw, guangwu, gudvinr, ignacy123, janost, jaseg, jelle van der Waa, jtagcat, klemens, kylosus, luchenhan, luzpaz, marco-m, mathias4833, maxice8, mclang, mv1005, nf, orangekame3, otbutz, overkill, perewa, polyfloyd, pullmerge, red_led, rubenbe, sec65, vapatel2, villekalliomaki, wangguoliang, wouter bolsterlee, xarx00, xjtdy888, 佛跳墙, 落心
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
// Copyright (C) 2025 The Syncthing Authors.
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RegisterInfoMetrics registers Prometheus metrics for the given config
|
||||||
|
// wrapper.
|
||||||
|
func RegisterInfoMetrics(cfg Wrapper) {
|
||||||
|
prometheus.DefaultRegisterer.MustRegister(prometheus.CollectorFunc((&folderInfoMetric{cfg}).Collect))
|
||||||
|
prometheus.DefaultRegisterer.MustRegister(prometheus.CollectorFunc((&folderDeviceMetric{cfg}).Collect))
|
||||||
|
}
|
||||||
|
|
||||||
|
type folderInfoMetric struct {
|
||||||
|
cfg Wrapper
|
||||||
|
}
|
||||||
|
|
||||||
|
var folderInfoMetricDesc = prometheus.NewDesc(
|
||||||
|
"syncthing_config_folder_info",
|
||||||
|
"Provides additional information labels on folders",
|
||||||
|
[]string{"folder", "label", "type", "path", "paused"},
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
|
||||||
|
func (m *folderInfoMetric) Collect(ch chan<- prometheus.Metric) {
|
||||||
|
for _, folder := range m.cfg.FolderList() {
|
||||||
|
ch <- prometheus.MustNewConstMetric(
|
||||||
|
folderInfoMetricDesc,
|
||||||
|
prometheus.GaugeValue, 1,
|
||||||
|
folder.ID, folder.Label, folder.Type.String(), folder.Path, strconv.FormatBool(folder.Paused),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type folderDeviceMetric struct {
|
||||||
|
cfg Wrapper
|
||||||
|
}
|
||||||
|
|
||||||
|
var folderDeviceMetricDesc = prometheus.NewDesc(
|
||||||
|
"syncthing_config_device_info",
|
||||||
|
"Provides additional information labels on devices",
|
||||||
|
[]string{"device", "name", "introducer", "paused", "untrusted"},
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
|
||||||
|
func (m *folderDeviceMetric) Collect(ch chan<- prometheus.Metric) {
|
||||||
|
for _, device := range m.cfg.DeviceList() {
|
||||||
|
ch <- prometheus.MustNewConstMetric(
|
||||||
|
folderDeviceMetricDesc,
|
||||||
|
prometheus.GaugeValue, 1,
|
||||||
|
device.DeviceID.String(), device.Name, strconv.FormatBool(device.Introducer), strconv.FormatBool(device.Paused), strconv.FormatBool(device.Untrusted),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -444,7 +444,7 @@ func (s *service) handleHellos(ctx context.Context) error {
|
|||||||
// connections are limited.
|
// connections are limited.
|
||||||
rd, wr := s.limiter.getLimiters(remoteID, c, c.IsLocal())
|
rd, wr := s.limiter.getLimiters(remoteID, c, c.IsLocal())
|
||||||
|
|
||||||
protoConn := protocol.NewConnection(remoteID, rd, wr, c, s.model, c, deviceCfg.Compression.ToProtocol(), s.cfg.FolderPasswords(remoteID), s.keyGen)
|
protoConn := protocol.NewConnection(remoteID, rd, wr, c, s.model, c, deviceCfg.Compression.ToProtocol(), s.keyGen)
|
||||||
s.accountAddedConnection(protoConn, hello, s.cfg.Options().ConnectionPriorityUpgradeThreshold)
|
s.accountAddedConnection(protoConn, hello, s.cfg.Options().ConnectionPriorityUpgradeThreshold)
|
||||||
go func() {
|
go func() {
|
||||||
<-protoConn.Closed()
|
<-protoConn.Closed()
|
||||||
|
|||||||
@@ -31,17 +31,17 @@ func TestIndexhandlerConcurrency(t *testing.T) {
|
|||||||
ci := &protomock.ConnectionInfo{}
|
ci := &protomock.ConnectionInfo{}
|
||||||
|
|
||||||
m1 := &mocks.Model{}
|
m1 := &mocks.Model{}
|
||||||
c1 := protocol.NewConnection(protocol.EmptyDeviceID, ar, bw, testutil.NoopCloser{}, m1, ci, protocol.CompressionNever, nil, nil)
|
c1 := protocol.NewConnection(protocol.EmptyDeviceID, ar, bw, testutil.NoopCloser{}, m1, ci, protocol.CompressionNever, nil)
|
||||||
c1.Start()
|
c1.Start()
|
||||||
defer c1.Close(io.EOF)
|
defer c1.Close(io.EOF)
|
||||||
|
|
||||||
m2 := &mocks.Model{}
|
m2 := &mocks.Model{}
|
||||||
c2 := protocol.NewConnection(protocol.EmptyDeviceID, br, aw, testutil.NoopCloser{}, m2, ci, protocol.CompressionNever, nil, nil)
|
c2 := protocol.NewConnection(protocol.EmptyDeviceID, br, aw, testutil.NoopCloser{}, m2, ci, protocol.CompressionNever, nil)
|
||||||
c2.Start()
|
c2.Start()
|
||||||
defer c2.Close(io.EOF)
|
defer c2.Close(io.EOF)
|
||||||
|
|
||||||
c1.ClusterConfig(&protocol.ClusterConfig{})
|
c1.ClusterConfig(&protocol.ClusterConfig{}, nil)
|
||||||
c2.ClusterConfig(&protocol.ClusterConfig{})
|
c2.ClusterConfig(&protocol.ClusterConfig{}, nil)
|
||||||
c1.Index(ctx, &protocol.Index{Folder: "foo"})
|
c1.Index(ctx, &protocol.Index{Folder: "foo"})
|
||||||
c2.Index(ctx, &protocol.Index{Folder: "foo"})
|
c2.Index(ctx, &protocol.Index{Folder: "foo"})
|
||||||
|
|
||||||
|
|||||||
+24
-11
@@ -1629,8 +1629,7 @@ func (m *model) sendClusterConfig(ids []protocol.DeviceID) {
|
|||||||
// Generating cluster-configs acquires the mutex.
|
// Generating cluster-configs acquires the mutex.
|
||||||
for _, conn := range ccConns {
|
for _, conn := range ccConns {
|
||||||
cm, passwords := m.generateClusterConfig(conn.DeviceID())
|
cm, passwords := m.generateClusterConfig(conn.DeviceID())
|
||||||
conn.SetFolderPasswords(passwords)
|
go conn.ClusterConfig(cm, passwords)
|
||||||
go conn.ClusterConfig(cm)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2384,8 +2383,14 @@ func (m *model) scheduleConnectionPromotion() {
|
|||||||
// be called after adding new connections, and after closing a primary
|
// be called after adding new connections, and after closing a primary
|
||||||
// device connection.
|
// device connection.
|
||||||
func (m *model) promoteConnections() {
|
func (m *model) promoteConnections() {
|
||||||
|
// Slice of actions to take on connections after releasing the main
|
||||||
|
// mutex. We do this so that we do not perform blocking network actions
|
||||||
|
// inside the loop, and also to avoid a possible deadlock with calling
|
||||||
|
// Start() on connections that are already executing a Close() with a
|
||||||
|
// callback into the model...
|
||||||
|
var postLockActions []func()
|
||||||
|
|
||||||
m.mut.Lock()
|
m.mut.Lock()
|
||||||
defer m.mut.Unlock()
|
|
||||||
|
|
||||||
for deviceID, connIDs := range m.deviceConnIDs {
|
for deviceID, connIDs := range m.deviceConnIDs {
|
||||||
cm, passwords := m.generateClusterConfigRLocked(deviceID)
|
cm, passwords := m.generateClusterConfigRLocked(deviceID)
|
||||||
@@ -2398,11 +2403,12 @@ func (m *model) promoteConnections() {
|
|||||||
// on where we get ClusterConfigs from the peer.)
|
// on where we get ClusterConfigs from the peer.)
|
||||||
conn := m.connections[connIDs[0]]
|
conn := m.connections[connIDs[0]]
|
||||||
l.Debugf("Promoting connection to %s at %s", deviceID.Short(), conn)
|
l.Debugf("Promoting connection to %s at %s", deviceID.Short(), conn)
|
||||||
if conn.Statistics().StartedAt.IsZero() {
|
postLockActions = append(postLockActions, func() {
|
||||||
conn.SetFolderPasswords(passwords)
|
if conn.Statistics().StartedAt.IsZero() {
|
||||||
conn.Start()
|
conn.Start()
|
||||||
}
|
}
|
||||||
conn.ClusterConfig(cm)
|
conn.ClusterConfig(cm, passwords)
|
||||||
|
})
|
||||||
m.promotedConnID[deviceID] = connIDs[0]
|
m.promotedConnID[deviceID] = connIDs[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2411,12 +2417,19 @@ func (m *model) promoteConnections() {
|
|||||||
for _, connID := range connIDs[1:] {
|
for _, connID := range connIDs[1:] {
|
||||||
conn := m.connections[connID]
|
conn := m.connections[connID]
|
||||||
if conn.Statistics().StartedAt.IsZero() {
|
if conn.Statistics().StartedAt.IsZero() {
|
||||||
conn.SetFolderPasswords(passwords)
|
postLockActions = append(postLockActions, func() {
|
||||||
conn.Start()
|
conn.Start()
|
||||||
conn.ClusterConfig(&protocol.ClusterConfig{Secondary: true})
|
conn.ClusterConfig(&protocol.ClusterConfig{Secondary: true}, passwords)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m.mut.Unlock()
|
||||||
|
|
||||||
|
for _, action := range postLockActions {
|
||||||
|
action()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *model) DownloadProgress(conn protocol.Connection, p *protocol.DownloadProgress) error {
|
func (m *model) DownloadProgress(conn protocol.Connection, p *protocol.DownloadProgress) error {
|
||||||
|
|||||||
@@ -2969,7 +2969,7 @@ func TestConnCloseOnRestart(t *testing.T) {
|
|||||||
nw := &testutil.NoopRW{}
|
nw := &testutil.NoopRW{}
|
||||||
ci := &protocolmocks.ConnectionInfo{}
|
ci := &protocolmocks.ConnectionInfo{}
|
||||||
ci.ConnectionIDReturns(srand.String(16))
|
ci.ConnectionIDReturns(srand.String(16))
|
||||||
m.AddConnection(protocol.NewConnection(device1, br, nw, testutil.NoopCloser{}, m, ci, protocol.CompressionNever, nil, m.keyGen), protocol.Hello{})
|
m.AddConnection(protocol.NewConnection(device1, br, nw, testutil.NoopCloser{}, m, ci, protocol.CompressionNever, m.keyGen), protocol.Hello{})
|
||||||
m.mut.RLock()
|
m.mut.RLock()
|
||||||
if len(m.closed) != 1 {
|
if len(m.closed) != 1 {
|
||||||
t.Fatalf("Expected just one conn (len(m.closed) == %v)", len(m.closed))
|
t.Fatalf("Expected just one conn (len(m.closed) == %v)", len(m.closed))
|
||||||
@@ -3628,11 +3628,11 @@ func testConfigChangeTriggersClusterConfigs(t *testing.T, expectFirst, expectSec
|
|||||||
cc1 := make(chan struct{}, 1)
|
cc1 := make(chan struct{}, 1)
|
||||||
cc2 := make(chan struct{}, 1)
|
cc2 := make(chan struct{}, 1)
|
||||||
fc1 := newFakeConnection(device1, m)
|
fc1 := newFakeConnection(device1, m)
|
||||||
fc1.ClusterConfigCalls(func(_ *protocol.ClusterConfig) {
|
fc1.ClusterConfigCalls(func(_ *protocol.ClusterConfig, _ map[string]string) {
|
||||||
cc1 <- struct{}{}
|
cc1 <- struct{}{}
|
||||||
})
|
})
|
||||||
fc2 := newFakeConnection(device2, m)
|
fc2 := newFakeConnection(device2, m)
|
||||||
fc2.ClusterConfigCalls(func(_ *protocol.ClusterConfig) {
|
fc2.ClusterConfigCalls(func(_ *protocol.ClusterConfig, _ map[string]string) {
|
||||||
cc2 <- struct{}{}
|
cc2 <- struct{}{}
|
||||||
})
|
})
|
||||||
m.AddConnection(fc1, protocol.Hello{})
|
m.AddConnection(fc1, protocol.Hello{})
|
||||||
|
|||||||
@@ -1240,7 +1240,7 @@ func TestRequestIndexSenderClusterConfigBeforeStart(t *testing.T) {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
fc.ClusterConfigCalls(func(cc *protocol.ClusterConfig) {
|
fc.ClusterConfigCalls(func(cc *protocol.ClusterConfig, _ map[string]string) {
|
||||||
select {
|
select {
|
||||||
case ccChan <- cc:
|
case ccChan <- cc:
|
||||||
case <-done:
|
case <-done:
|
||||||
|
|||||||
@@ -64,14 +64,14 @@ func benchmarkRequestsTLS(b *testing.B, conn0, conn1 net.Conn) {
|
|||||||
|
|
||||||
func benchmarkRequestsConnPair(b *testing.B, conn0, conn1 net.Conn) {
|
func benchmarkRequestsConnPair(b *testing.B, conn0, conn1 net.Conn) {
|
||||||
// Start up Connections on them
|
// Start up Connections on them
|
||||||
c0 := NewConnection(LocalDeviceID, conn0, conn0, testutil.NoopCloser{}, new(fakeModel), new(mockedConnectionInfo), CompressionMetadata, nil, testKeyGen)
|
c0 := NewConnection(LocalDeviceID, conn0, conn0, testutil.NoopCloser{}, new(fakeModel), new(mockedConnectionInfo), CompressionMetadata, testKeyGen)
|
||||||
c0.Start()
|
c0.Start()
|
||||||
c1 := NewConnection(LocalDeviceID, conn1, conn1, testutil.NoopCloser{}, new(fakeModel), new(mockedConnectionInfo), CompressionMetadata, nil, testKeyGen)
|
c1 := NewConnection(LocalDeviceID, conn1, conn1, testutil.NoopCloser{}, new(fakeModel), new(mockedConnectionInfo), CompressionMetadata, testKeyGen)
|
||||||
c1.Start()
|
c1.Start()
|
||||||
|
|
||||||
// Satisfy the assertions in the protocol by sending an initial cluster config
|
// Satisfy the assertions in the protocol by sending an initial cluster config
|
||||||
c0.ClusterConfig(&ClusterConfig{})
|
c0.ClusterConfig(&ClusterConfig{}, nil)
|
||||||
c1.ClusterConfig(&ClusterConfig{})
|
c1.ClusterConfig(&ClusterConfig{}, nil)
|
||||||
|
|
||||||
// Report some useful stats and reset the timer for the actual test
|
// Report some useful stats and reset the timer for the actual test
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
|
|||||||
@@ -849,9 +849,13 @@ func unixOwnershipEqual(a, b *UnixData) bool {
|
|||||||
if a == nil || b == nil {
|
if a == nil || b == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
ownerEqual := a.OwnerName == "" || b.OwnerName == "" || a.OwnerName == b.OwnerName
|
if a.UID == b.UID && a.GID == b.GID {
|
||||||
groupEqual := a.GroupName == "" || b.GroupName == "" || a.GroupName == b.GroupName
|
return true
|
||||||
return a.UID == b.UID && a.GID == b.GID && ownerEqual && groupEqual
|
}
|
||||||
|
if a.OwnerName == b.OwnerName && a.GroupName == b.GroupName {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func windowsOwnershipEqual(a, b *WindowsData) bool {
|
func windowsOwnershipEqual(a, b *WindowsData) bool {
|
||||||
|
|||||||
@@ -196,6 +196,42 @@ func TestIsEquivalent(t *testing.T) {
|
|||||||
b: FileInfo{Type: FileInfoTypeFile, SymlinkTarget: []byte("b")},
|
b: FileInfo{Type: FileInfoTypeFile, SymlinkTarget: []byte("b")},
|
||||||
eq: true,
|
eq: true,
|
||||||
},
|
},
|
||||||
|
// Unix Ownership should be the same
|
||||||
|
{
|
||||||
|
a: FileInfo{Platform: PlatformData{Unix: &UnixData{OwnerName: "A", GroupName: "A", UID: 1000, GID: 1000}}},
|
||||||
|
b: FileInfo{Platform: PlatformData{Unix: &UnixData{OwnerName: "A", GroupName: "A", UID: 1000, GID: 1000}}},
|
||||||
|
eq: true,
|
||||||
|
},
|
||||||
|
// ... but matching ID is enough
|
||||||
|
{
|
||||||
|
a: FileInfo{Platform: PlatformData{Unix: &UnixData{OwnerName: "A", GroupName: "A", UID: 1000, GID: 1000}}},
|
||||||
|
b: FileInfo{Platform: PlatformData{Unix: &UnixData{OwnerName: "B", GroupName: "B", UID: 1000, GID: 1000}}},
|
||||||
|
eq: true,
|
||||||
|
},
|
||||||
|
// ... or matching name
|
||||||
|
{
|
||||||
|
a: FileInfo{Platform: PlatformData{Unix: &UnixData{OwnerName: "A", GroupName: "A", UID: 1000, GID: 1000}}},
|
||||||
|
b: FileInfo{Platform: PlatformData{Unix: &UnixData{OwnerName: "A", GroupName: "A", UID: 1001, GID: 1001}}},
|
||||||
|
eq: true,
|
||||||
|
},
|
||||||
|
// ... or empty name
|
||||||
|
{
|
||||||
|
a: FileInfo{Platform: PlatformData{Unix: &UnixData{OwnerName: "A", GroupName: "A", UID: 1000, GID: 1000}}},
|
||||||
|
b: FileInfo{Platform: PlatformData{Unix: &UnixData{OwnerName: "", GroupName: "", UID: 1000, GID: 1000}}},
|
||||||
|
eq: true,
|
||||||
|
},
|
||||||
|
// ... but not different ownership
|
||||||
|
{
|
||||||
|
a: FileInfo{Platform: PlatformData{Unix: &UnixData{OwnerName: "A", GroupName: "A", UID: 1000, GID: 1000}}},
|
||||||
|
b: FileInfo{Platform: PlatformData{Unix: &UnixData{OwnerName: "B", GroupName: "B", UID: 1001, GID: 1001}}},
|
||||||
|
eq: false,
|
||||||
|
},
|
||||||
|
// or missing ownership
|
||||||
|
{
|
||||||
|
a: FileInfo{Platform: PlatformData{Unix: &UnixData{OwnerName: "A", GroupName: "A", UID: 1000, GID: 1000}}},
|
||||||
|
b: FileInfo{Platform: PlatformData{}},
|
||||||
|
eq: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if build.IsWindows {
|
if build.IsWindows {
|
||||||
|
|||||||
+11
-16
@@ -50,10 +50,10 @@ type encryptedModel struct {
|
|||||||
keyGen *KeyGenerator
|
keyGen *KeyGenerator
|
||||||
}
|
}
|
||||||
|
|
||||||
func newEncryptedModel(model rawModel, folderKeys *folderKeyRegistry, keyGen *KeyGenerator) encryptedModel {
|
func newEncryptedModel(model rawModel, keyGen *KeyGenerator) encryptedModel {
|
||||||
return encryptedModel{
|
return encryptedModel{
|
||||||
model: model,
|
model: model,
|
||||||
folderKeys: folderKeys,
|
folderKeys: newFolderKeyRegistry(),
|
||||||
keyGen: keyGen,
|
keyGen: keyGen,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -187,10 +187,6 @@ func (e encryptedConnection) Start() {
|
|||||||
e.conn.Start()
|
e.conn.Start()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e encryptedConnection) SetFolderPasswords(passwords map[string]string) {
|
|
||||||
e.folderKeys.setPasswords(passwords)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e encryptedConnection) DeviceID() DeviceID {
|
func (e encryptedConnection) DeviceID() DeviceID {
|
||||||
return e.conn.DeviceID()
|
return e.conn.DeviceID()
|
||||||
}
|
}
|
||||||
@@ -262,8 +258,9 @@ func (e encryptedConnection) DownloadProgress(ctx context.Context, dp *DownloadP
|
|||||||
// No need to send these
|
// No need to send these
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e encryptedConnection) ClusterConfig(config *ClusterConfig) {
|
func (e encryptedConnection) ClusterConfig(config *ClusterConfig, passwords map[string]string) {
|
||||||
e.conn.ClusterConfig(config)
|
e.folderKeys.setPasswords(e.keyGen, passwords)
|
||||||
|
e.conn.ClusterConfig(config, passwords)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e encryptedConnection) Close(err error) {
|
func (e encryptedConnection) Close(err error) {
|
||||||
@@ -680,15 +677,13 @@ func IsEncryptedParent(pathComponents []string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type folderKeyRegistry struct {
|
type folderKeyRegistry struct {
|
||||||
keyGen *KeyGenerator
|
keys map[string]*[keySize]byte // folder ID -> key
|
||||||
keys map[string]*[keySize]byte // folder ID -> key
|
mut sync.RWMutex
|
||||||
mut sync.RWMutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newFolderKeyRegistry(keyGen *KeyGenerator, passwords map[string]string) *folderKeyRegistry {
|
func newFolderKeyRegistry() *folderKeyRegistry {
|
||||||
return &folderKeyRegistry{
|
return &folderKeyRegistry{
|
||||||
keyGen: keyGen,
|
keys: make(map[string]*[keySize]byte),
|
||||||
keys: keysFromPasswords(keyGen, passwords),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -699,8 +694,8 @@ func (r *folderKeyRegistry) get(folder string) (*[keySize]byte, bool) {
|
|||||||
return key, ok
|
return key, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *folderKeyRegistry) setPasswords(passwords map[string]string) {
|
func (r *folderKeyRegistry) setPasswords(keyGen *KeyGenerator, passwords map[string]string) {
|
||||||
r.mut.Lock()
|
r.mut.Lock()
|
||||||
r.keys = keysFromPasswords(r.keyGen, passwords)
|
r.keys = keysFromPasswords(keyGen, passwords)
|
||||||
r.mut.Unlock()
|
r.mut.Unlock()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,10 +26,11 @@ type Connection struct {
|
|||||||
closedReturnsOnCall map[int]struct {
|
closedReturnsOnCall map[int]struct {
|
||||||
result1 <-chan struct{}
|
result1 <-chan struct{}
|
||||||
}
|
}
|
||||||
ClusterConfigStub func(*protocol.ClusterConfig)
|
ClusterConfigStub func(*protocol.ClusterConfig, map[string]string)
|
||||||
clusterConfigMutex sync.RWMutex
|
clusterConfigMutex sync.RWMutex
|
||||||
clusterConfigArgsForCall []struct {
|
clusterConfigArgsForCall []struct {
|
||||||
arg1 *protocol.ClusterConfig
|
arg1 *protocol.ClusterConfig
|
||||||
|
arg2 map[string]string
|
||||||
}
|
}
|
||||||
ConnectionIDStub func() string
|
ConnectionIDStub func() string
|
||||||
connectionIDMutex sync.RWMutex
|
connectionIDMutex sync.RWMutex
|
||||||
@@ -145,11 +146,6 @@ type Connection struct {
|
|||||||
result1 []byte
|
result1 []byte
|
||||||
result2 error
|
result2 error
|
||||||
}
|
}
|
||||||
SetFolderPasswordsStub func(map[string]string)
|
|
||||||
setFolderPasswordsMutex sync.RWMutex
|
|
||||||
setFolderPasswordsArgsForCall []struct {
|
|
||||||
arg1 map[string]string
|
|
||||||
}
|
|
||||||
StartStub func()
|
StartStub func()
|
||||||
startMutex sync.RWMutex
|
startMutex sync.RWMutex
|
||||||
startArgsForCall []struct {
|
startArgsForCall []struct {
|
||||||
@@ -283,16 +279,17 @@ func (fake *Connection) ClosedReturnsOnCall(i int, result1 <-chan struct{}) {
|
|||||||
}{result1}
|
}{result1}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fake *Connection) ClusterConfig(arg1 *protocol.ClusterConfig) {
|
func (fake *Connection) ClusterConfig(arg1 *protocol.ClusterConfig, arg2 map[string]string) {
|
||||||
fake.clusterConfigMutex.Lock()
|
fake.clusterConfigMutex.Lock()
|
||||||
fake.clusterConfigArgsForCall = append(fake.clusterConfigArgsForCall, struct {
|
fake.clusterConfigArgsForCall = append(fake.clusterConfigArgsForCall, struct {
|
||||||
arg1 *protocol.ClusterConfig
|
arg1 *protocol.ClusterConfig
|
||||||
}{arg1})
|
arg2 map[string]string
|
||||||
|
}{arg1, arg2})
|
||||||
stub := fake.ClusterConfigStub
|
stub := fake.ClusterConfigStub
|
||||||
fake.recordInvocation("ClusterConfig", []interface{}{arg1})
|
fake.recordInvocation("ClusterConfig", []interface{}{arg1, arg2})
|
||||||
fake.clusterConfigMutex.Unlock()
|
fake.clusterConfigMutex.Unlock()
|
||||||
if stub != nil {
|
if stub != nil {
|
||||||
fake.ClusterConfigStub(arg1)
|
fake.ClusterConfigStub(arg1, arg2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,17 +299,17 @@ func (fake *Connection) ClusterConfigCallCount() int {
|
|||||||
return len(fake.clusterConfigArgsForCall)
|
return len(fake.clusterConfigArgsForCall)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fake *Connection) ClusterConfigCalls(stub func(*protocol.ClusterConfig)) {
|
func (fake *Connection) ClusterConfigCalls(stub func(*protocol.ClusterConfig, map[string]string)) {
|
||||||
fake.clusterConfigMutex.Lock()
|
fake.clusterConfigMutex.Lock()
|
||||||
defer fake.clusterConfigMutex.Unlock()
|
defer fake.clusterConfigMutex.Unlock()
|
||||||
fake.ClusterConfigStub = stub
|
fake.ClusterConfigStub = stub
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fake *Connection) ClusterConfigArgsForCall(i int) *protocol.ClusterConfig {
|
func (fake *Connection) ClusterConfigArgsForCall(i int) (*protocol.ClusterConfig, map[string]string) {
|
||||||
fake.clusterConfigMutex.RLock()
|
fake.clusterConfigMutex.RLock()
|
||||||
defer fake.clusterConfigMutex.RUnlock()
|
defer fake.clusterConfigMutex.RUnlock()
|
||||||
argsForCall := fake.clusterConfigArgsForCall[i]
|
argsForCall := fake.clusterConfigArgsForCall[i]
|
||||||
return argsForCall.arg1
|
return argsForCall.arg1, argsForCall.arg2
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fake *Connection) ConnectionID() string {
|
func (fake *Connection) ConnectionID() string {
|
||||||
@@ -908,38 +905,6 @@ func (fake *Connection) RequestReturnsOnCall(i int, result1 []byte, result2 erro
|
|||||||
}{result1, result2}
|
}{result1, result2}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fake *Connection) SetFolderPasswords(arg1 map[string]string) {
|
|
||||||
fake.setFolderPasswordsMutex.Lock()
|
|
||||||
fake.setFolderPasswordsArgsForCall = append(fake.setFolderPasswordsArgsForCall, struct {
|
|
||||||
arg1 map[string]string
|
|
||||||
}{arg1})
|
|
||||||
stub := fake.SetFolderPasswordsStub
|
|
||||||
fake.recordInvocation("SetFolderPasswords", []interface{}{arg1})
|
|
||||||
fake.setFolderPasswordsMutex.Unlock()
|
|
||||||
if stub != nil {
|
|
||||||
fake.SetFolderPasswordsStub(arg1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fake *Connection) SetFolderPasswordsCallCount() int {
|
|
||||||
fake.setFolderPasswordsMutex.RLock()
|
|
||||||
defer fake.setFolderPasswordsMutex.RUnlock()
|
|
||||||
return len(fake.setFolderPasswordsArgsForCall)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fake *Connection) SetFolderPasswordsCalls(stub func(map[string]string)) {
|
|
||||||
fake.setFolderPasswordsMutex.Lock()
|
|
||||||
defer fake.setFolderPasswordsMutex.Unlock()
|
|
||||||
fake.SetFolderPasswordsStub = stub
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fake *Connection) SetFolderPasswordsArgsForCall(i int) map[string]string {
|
|
||||||
fake.setFolderPasswordsMutex.RLock()
|
|
||||||
defer fake.setFolderPasswordsMutex.RUnlock()
|
|
||||||
argsForCall := fake.setFolderPasswordsArgsForCall[i]
|
|
||||||
return argsForCall.arg1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fake *Connection) Start() {
|
func (fake *Connection) Start() {
|
||||||
fake.startMutex.Lock()
|
fake.startMutex.Lock()
|
||||||
fake.startArgsForCall = append(fake.startArgsForCall, struct {
|
fake.startArgsForCall = append(fake.startArgsForCall, struct {
|
||||||
@@ -1207,8 +1172,6 @@ func (fake *Connection) Invocations() map[string][][]interface{} {
|
|||||||
defer fake.remoteAddrMutex.RUnlock()
|
defer fake.remoteAddrMutex.RUnlock()
|
||||||
fake.requestMutex.RLock()
|
fake.requestMutex.RLock()
|
||||||
defer fake.requestMutex.RUnlock()
|
defer fake.requestMutex.RUnlock()
|
||||||
fake.setFolderPasswordsMutex.RLock()
|
|
||||||
defer fake.setFolderPasswordsMutex.RUnlock()
|
|
||||||
fake.startMutex.RLock()
|
fake.startMutex.RLock()
|
||||||
defer fake.startMutex.RUnlock()
|
defer fake.startMutex.RUnlock()
|
||||||
fake.statisticsMutex.RLock()
|
fake.statisticsMutex.RLock()
|
||||||
|
|||||||
@@ -129,7 +129,9 @@ type Connection interface {
|
|||||||
// Send a Cluster Configuration message to the peer device. The message
|
// Send a Cluster Configuration message to the peer device. The message
|
||||||
// in the parameter may be altered by the connection and should not be
|
// in the parameter may be altered by the connection and should not be
|
||||||
// used further by the caller.
|
// used further by the caller.
|
||||||
ClusterConfig(config *ClusterConfig)
|
// For any folder that must be encrypted for the connected device, the
|
||||||
|
// password must be provided.
|
||||||
|
ClusterConfig(config *ClusterConfig, passwords map[string]string)
|
||||||
|
|
||||||
// Send a Download Progress message to the peer device. The message in
|
// Send a Download Progress message to the peer device. The message in
|
||||||
// the parameter may be altered by the connection and should not be used
|
// the parameter may be altered by the connection and should not be used
|
||||||
@@ -137,7 +139,6 @@ type Connection interface {
|
|||||||
DownloadProgress(ctx context.Context, dp *DownloadProgress)
|
DownloadProgress(ctx context.Context, dp *DownloadProgress)
|
||||||
|
|
||||||
Start()
|
Start()
|
||||||
SetFolderPasswords(passwords map[string]string)
|
|
||||||
Close(err error)
|
Close(err error)
|
||||||
DeviceID() DeviceID
|
DeviceID() DeviceID
|
||||||
Statistics() Statistics
|
Statistics() Statistics
|
||||||
@@ -215,7 +216,7 @@ const (
|
|||||||
// Should not be modified in production code, just for testing.
|
// Should not be modified in production code, just for testing.
|
||||||
var CloseTimeout = 10 * time.Second
|
var CloseTimeout = 10 * time.Second
|
||||||
|
|
||||||
func NewConnection(deviceID DeviceID, reader io.Reader, writer io.Writer, closer io.Closer, model Model, connInfo ConnectionInfo, compress Compression, passwords map[string]string, keyGen *KeyGenerator) Connection {
|
func NewConnection(deviceID DeviceID, reader io.Reader, writer io.Writer, closer io.Closer, model Model, connInfo ConnectionInfo, compress Compression, keyGen *KeyGenerator) Connection {
|
||||||
// We create the wrapper for the model first, as it needs to be passed
|
// We create the wrapper for the model first, as it needs to be passed
|
||||||
// in at the lowest level in the stack. At the end of construction,
|
// in at the lowest level in the stack. At the end of construction,
|
||||||
// before returning, we add the connection to cwm so that it can be used
|
// before returning, we add the connection to cwm so that it can be used
|
||||||
@@ -225,7 +226,7 @@ func NewConnection(deviceID DeviceID, reader io.Reader, writer io.Writer, closer
|
|||||||
// Encryption / decryption is first (outermost) before conversion to
|
// Encryption / decryption is first (outermost) before conversion to
|
||||||
// native path formats.
|
// native path formats.
|
||||||
nm := makeNative(cwm)
|
nm := makeNative(cwm)
|
||||||
em := newEncryptedModel(nm, newFolderKeyRegistry(keyGen, passwords), keyGen)
|
em := newEncryptedModel(nm, keyGen)
|
||||||
|
|
||||||
// We do the wire format conversion first (outermost) so that the
|
// We do the wire format conversion first (outermost) so that the
|
||||||
// metadata is in wire format when it reaches the encryption step.
|
// metadata is in wire format when it reaches the encryption step.
|
||||||
@@ -265,10 +266,22 @@ func newRawConnection(deviceID DeviceID, reader io.Reader, writer io.Writer, clo
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Start creates the goroutines for sending and receiving of messages. It must
|
// Start creates the goroutines for sending and receiving of messages. It must
|
||||||
// be called exactly once after creating a connection.
|
// be called once after creating a connection. It should only be called once,
|
||||||
|
// subsequent calls will have no effect.
|
||||||
func (c *rawConnection) Start() {
|
func (c *rawConnection) Start() {
|
||||||
c.startStopMut.Lock()
|
c.startStopMut.Lock()
|
||||||
defer c.startStopMut.Unlock()
|
defer c.startStopMut.Unlock()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-c.started:
|
||||||
|
return
|
||||||
|
case <-c.closed:
|
||||||
|
// we have already closed the connection before starting processing
|
||||||
|
// on it.
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
c.loopWG.Add(5)
|
c.loopWG.Add(5)
|
||||||
go func() {
|
go func() {
|
||||||
c.readerLoop()
|
c.readerLoop()
|
||||||
@@ -291,6 +304,7 @@ func (c *rawConnection) Start() {
|
|||||||
c.pingReceiver()
|
c.pingReceiver()
|
||||||
c.loopWG.Done()
|
c.loopWG.Done()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
c.startTime = time.Now().Truncate(time.Second)
|
c.startTime = time.Now().Truncate(time.Second)
|
||||||
close(c.started)
|
close(c.started)
|
||||||
}
|
}
|
||||||
@@ -369,7 +383,7 @@ func (c *rawConnection) Request(ctx context.Context, req *Request) ([]byte, erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ClusterConfig sends the cluster configuration message to the peer.
|
// ClusterConfig sends the cluster configuration message to the peer.
|
||||||
func (c *rawConnection) ClusterConfig(config *ClusterConfig) {
|
func (c *rawConnection) ClusterConfig(config *ClusterConfig, _ map[string]string) {
|
||||||
select {
|
select {
|
||||||
case c.clusterConfigBox <- config:
|
case c.clusterConfigBox <- config:
|
||||||
case <-c.closed:
|
case <-c.closed:
|
||||||
@@ -950,9 +964,9 @@ func (c *rawConnection) Close(err error) {
|
|||||||
|
|
||||||
// internalClose is called if there is an unexpected error during normal operation.
|
// internalClose is called if there is an unexpected error during normal operation.
|
||||||
func (c *rawConnection) internalClose(err error) {
|
func (c *rawConnection) internalClose(err error) {
|
||||||
c.startStopMut.Lock()
|
|
||||||
defer c.startStopMut.Unlock()
|
|
||||||
c.closeOnce.Do(func() {
|
c.closeOnce.Do(func() {
|
||||||
|
c.startStopMut.Lock()
|
||||||
|
|
||||||
l.Debugf("close connection to %s at %s due to %v", c.deviceID.Short(), c.ConnectionInfo, err)
|
l.Debugf("close connection to %s at %s due to %v", c.deviceID.Short(), c.ConnectionInfo, err)
|
||||||
if cerr := c.closer.Close(); cerr != nil {
|
if cerr := c.closer.Close(); cerr != nil {
|
||||||
l.Debugf("failed to close underlying conn %s at %s %v:", c.deviceID.Short(), c.ConnectionInfo, cerr)
|
l.Debugf("failed to close underlying conn %s at %s %v:", c.deviceID.Short(), c.ConnectionInfo, cerr)
|
||||||
@@ -974,6 +988,10 @@ func (c *rawConnection) internalClose(err error) {
|
|||||||
<-c.dispatcherLoopStopped
|
<-c.dispatcherLoopStopped
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.startStopMut.Unlock()
|
||||||
|
|
||||||
|
// We don't want to call into the model while holding the
|
||||||
|
// startStopMut.
|
||||||
c.model.Closed(err)
|
c.model.Closed(err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,14 +35,14 @@ func TestPing(t *testing.T) {
|
|||||||
ar, aw := io.Pipe()
|
ar, aw := io.Pipe()
|
||||||
br, bw := io.Pipe()
|
br, bw := io.Pipe()
|
||||||
|
|
||||||
c0 := getRawConnection(NewConnection(c0ID, ar, bw, testutil.NoopCloser{}, newTestModel(), new(mockedConnectionInfo), CompressionAlways, nil, testKeyGen))
|
c0 := getRawConnection(NewConnection(c0ID, ar, bw, testutil.NoopCloser{}, newTestModel(), new(mockedConnectionInfo), CompressionAlways, testKeyGen))
|
||||||
c0.Start()
|
c0.Start()
|
||||||
defer closeAndWait(c0, ar, bw)
|
defer closeAndWait(c0, ar, bw)
|
||||||
c1 := getRawConnection(NewConnection(c1ID, br, aw, testutil.NoopCloser{}, newTestModel(), new(mockedConnectionInfo), CompressionAlways, nil, testKeyGen))
|
c1 := getRawConnection(NewConnection(c1ID, br, aw, testutil.NoopCloser{}, newTestModel(), new(mockedConnectionInfo), CompressionAlways, testKeyGen))
|
||||||
c1.Start()
|
c1.Start()
|
||||||
defer closeAndWait(c1, ar, bw)
|
defer closeAndWait(c1, ar, bw)
|
||||||
c0.ClusterConfig(&ClusterConfig{})
|
c0.ClusterConfig(&ClusterConfig{}, nil)
|
||||||
c1.ClusterConfig(&ClusterConfig{})
|
c1.ClusterConfig(&ClusterConfig{}, nil)
|
||||||
|
|
||||||
if ok := c0.ping(); !ok {
|
if ok := c0.ping(); !ok {
|
||||||
t.Error("c0 ping failed")
|
t.Error("c0 ping failed")
|
||||||
@@ -61,14 +61,14 @@ func TestClose(t *testing.T) {
|
|||||||
ar, aw := io.Pipe()
|
ar, aw := io.Pipe()
|
||||||
br, bw := io.Pipe()
|
br, bw := io.Pipe()
|
||||||
|
|
||||||
c0 := getRawConnection(NewConnection(c0ID, ar, bw, testutil.NoopCloser{}, m0, new(mockedConnectionInfo), CompressionAlways, nil, testKeyGen))
|
c0 := getRawConnection(NewConnection(c0ID, ar, bw, testutil.NoopCloser{}, m0, new(mockedConnectionInfo), CompressionAlways, testKeyGen))
|
||||||
c0.Start()
|
c0.Start()
|
||||||
defer closeAndWait(c0, ar, bw)
|
defer closeAndWait(c0, ar, bw)
|
||||||
c1 := NewConnection(c1ID, br, aw, testutil.NoopCloser{}, m1, new(mockedConnectionInfo), CompressionAlways, nil, testKeyGen)
|
c1 := NewConnection(c1ID, br, aw, testutil.NoopCloser{}, m1, new(mockedConnectionInfo), CompressionAlways, testKeyGen)
|
||||||
c1.Start()
|
c1.Start()
|
||||||
defer closeAndWait(c1, ar, bw)
|
defer closeAndWait(c1, ar, bw)
|
||||||
c0.ClusterConfig(&ClusterConfig{})
|
c0.ClusterConfig(&ClusterConfig{}, nil)
|
||||||
c1.ClusterConfig(&ClusterConfig{})
|
c1.ClusterConfig(&ClusterConfig{}, nil)
|
||||||
|
|
||||||
c0.internalClose(errManual)
|
c0.internalClose(errManual)
|
||||||
|
|
||||||
@@ -106,7 +106,7 @@ func TestCloseOnBlockingSend(t *testing.T) {
|
|||||||
m := newTestModel()
|
m := newTestModel()
|
||||||
|
|
||||||
rw := testutil.NewBlockingRW()
|
rw := testutil.NewBlockingRW()
|
||||||
c := getRawConnection(NewConnection(c0ID, rw, rw, testutil.NoopCloser{}, m, new(mockedConnectionInfo), CompressionAlways, nil, testKeyGen))
|
c := getRawConnection(NewConnection(c0ID, rw, rw, testutil.NoopCloser{}, m, new(mockedConnectionInfo), CompressionAlways, testKeyGen))
|
||||||
c.Start()
|
c.Start()
|
||||||
defer closeAndWait(c, rw)
|
defer closeAndWait(c, rw)
|
||||||
|
|
||||||
@@ -114,7 +114,7 @@ func TestCloseOnBlockingSend(t *testing.T) {
|
|||||||
|
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
c.ClusterConfig(&ClusterConfig{})
|
c.ClusterConfig(&ClusterConfig{}, nil)
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -157,14 +157,14 @@ func TestCloseRace(t *testing.T) {
|
|||||||
ar, aw := io.Pipe()
|
ar, aw := io.Pipe()
|
||||||
br, bw := io.Pipe()
|
br, bw := io.Pipe()
|
||||||
|
|
||||||
c0 := getRawConnection(NewConnection(c0ID, ar, bw, testutil.NoopCloser{}, m0, new(mockedConnectionInfo), CompressionNever, nil, testKeyGen))
|
c0 := getRawConnection(NewConnection(c0ID, ar, bw, testutil.NoopCloser{}, m0, new(mockedConnectionInfo), CompressionNever, testKeyGen))
|
||||||
c0.Start()
|
c0.Start()
|
||||||
defer closeAndWait(c0, ar, bw)
|
defer closeAndWait(c0, ar, bw)
|
||||||
c1 := NewConnection(c1ID, br, aw, testutil.NoopCloser{}, m1, new(mockedConnectionInfo), CompressionNever, nil, testKeyGen)
|
c1 := NewConnection(c1ID, br, aw, testutil.NoopCloser{}, m1, new(mockedConnectionInfo), CompressionNever, testKeyGen)
|
||||||
c1.Start()
|
c1.Start()
|
||||||
defer closeAndWait(c1, ar, bw)
|
defer closeAndWait(c1, ar, bw)
|
||||||
c0.ClusterConfig(&ClusterConfig{})
|
c0.ClusterConfig(&ClusterConfig{}, nil)
|
||||||
c1.ClusterConfig(&ClusterConfig{})
|
c1.ClusterConfig(&ClusterConfig{}, nil)
|
||||||
|
|
||||||
c1.Index(context.Background(), &Index{Folder: "default"})
|
c1.Index(context.Background(), &Index{Folder: "default"})
|
||||||
select {
|
select {
|
||||||
@@ -197,7 +197,7 @@ func TestClusterConfigFirst(t *testing.T) {
|
|||||||
m := newTestModel()
|
m := newTestModel()
|
||||||
|
|
||||||
rw := testutil.NewBlockingRW()
|
rw := testutil.NewBlockingRW()
|
||||||
c := getRawConnection(NewConnection(c0ID, rw, &testutil.NoopRW{}, testutil.NoopCloser{}, m, new(mockedConnectionInfo), CompressionAlways, nil, testKeyGen))
|
c := getRawConnection(NewConnection(c0ID, rw, &testutil.NoopRW{}, testutil.NoopCloser{}, m, new(mockedConnectionInfo), CompressionAlways, testKeyGen))
|
||||||
c.Start()
|
c.Start()
|
||||||
defer closeAndWait(c, rw)
|
defer closeAndWait(c, rw)
|
||||||
|
|
||||||
@@ -208,7 +208,7 @@ func TestClusterConfigFirst(t *testing.T) {
|
|||||||
// Allow some time for c.writerLoop to set up after c.Start
|
// Allow some time for c.writerLoop to set up after c.Start
|
||||||
}
|
}
|
||||||
|
|
||||||
c.ClusterConfig(&ClusterConfig{})
|
c.ClusterConfig(&ClusterConfig{}, nil)
|
||||||
|
|
||||||
done := make(chan struct{})
|
done := make(chan struct{})
|
||||||
if ok := c.send(context.Background(), &bep.Ping{}, done); !ok {
|
if ok := c.send(context.Background(), &bep.Ping{}, done); !ok {
|
||||||
@@ -249,7 +249,7 @@ func TestCloseTimeout(t *testing.T) {
|
|||||||
m := newTestModel()
|
m := newTestModel()
|
||||||
|
|
||||||
rw := testutil.NewBlockingRW()
|
rw := testutil.NewBlockingRW()
|
||||||
c := getRawConnection(NewConnection(c0ID, rw, rw, testutil.NoopCloser{}, m, new(mockedConnectionInfo), CompressionAlways, nil, testKeyGen))
|
c := getRawConnection(NewConnection(c0ID, rw, rw, testutil.NoopCloser{}, m, new(mockedConnectionInfo), CompressionAlways, testKeyGen))
|
||||||
c.Start()
|
c.Start()
|
||||||
defer closeAndWait(c, rw)
|
defer closeAndWait(c, rw)
|
||||||
|
|
||||||
@@ -531,7 +531,7 @@ func TestClusterConfigAfterClose(t *testing.T) {
|
|||||||
m := newTestModel()
|
m := newTestModel()
|
||||||
|
|
||||||
rw := testutil.NewBlockingRW()
|
rw := testutil.NewBlockingRW()
|
||||||
c := getRawConnection(NewConnection(c0ID, rw, rw, testutil.NoopCloser{}, m, new(mockedConnectionInfo), CompressionAlways, nil, testKeyGen))
|
c := getRawConnection(NewConnection(c0ID, rw, rw, testutil.NoopCloser{}, m, new(mockedConnectionInfo), CompressionAlways, testKeyGen))
|
||||||
c.Start()
|
c.Start()
|
||||||
defer closeAndWait(c, rw)
|
defer closeAndWait(c, rw)
|
||||||
|
|
||||||
@@ -539,7 +539,7 @@ func TestClusterConfigAfterClose(t *testing.T) {
|
|||||||
|
|
||||||
done := make(chan struct{})
|
done := make(chan struct{})
|
||||||
go func() {
|
go func() {
|
||||||
c.ClusterConfig(&ClusterConfig{})
|
c.ClusterConfig(&ClusterConfig{}, nil)
|
||||||
close(done)
|
close(done)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -555,7 +555,7 @@ func TestDispatcherToCloseDeadlock(t *testing.T) {
|
|||||||
// the model callbacks (ClusterConfig).
|
// the model callbacks (ClusterConfig).
|
||||||
m := newTestModel()
|
m := newTestModel()
|
||||||
rw := testutil.NewBlockingRW()
|
rw := testutil.NewBlockingRW()
|
||||||
c := getRawConnection(NewConnection(c0ID, rw, &testutil.NoopRW{}, testutil.NoopCloser{}, m, new(mockedConnectionInfo), CompressionAlways, nil, testKeyGen))
|
c := getRawConnection(NewConnection(c0ID, rw, &testutil.NoopRW{}, testutil.NoopCloser{}, m, new(mockedConnectionInfo), CompressionAlways, testKeyGen))
|
||||||
m.ccFn = func(*ClusterConfig) {
|
m.ccFn = func(*ClusterConfig) {
|
||||||
c.Close(errManual)
|
c.Close(errManual)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -440,7 +440,7 @@ func (a *aggregator) updateConfig(folderCfg config.FolderConfiguration) {
|
|||||||
if maxDelay := folderCfg.FSWatcherTimeoutS; maxDelay > 0 {
|
if maxDelay := folderCfg.FSWatcherTimeoutS; maxDelay > 0 {
|
||||||
// FSWatcherTimeoutS is set explicitly so use that, but it also
|
// FSWatcherTimeoutS is set explicitly so use that, but it also
|
||||||
// can't be lower than FSWatcherDelayS
|
// can't be lower than FSWatcherDelayS
|
||||||
a.notifyTimeout = time.Duration(max(maxDelay, folderCfg.FSWatcherDelayS)) * time.Second
|
a.notifyTimeout = time.Duration(max(maxDelay, folderCfg.FSWatcherDelayS) * float64(time.Second))
|
||||||
} else {
|
} else {
|
||||||
// Use the default FSWatcherTimeoutS calculation
|
// Use the default FSWatcherTimeoutS calculation
|
||||||
a.notifyTimeout = notifyTimeout(folderCfg.FSWatcherDelayS)
|
a.notifyTimeout = notifyTimeout(folderCfg.FSWatcherDelayS)
|
||||||
@@ -471,10 +471,10 @@ func notifyTimeout(eventDelayS float64) time.Duration {
|
|||||||
longDelayTimeout = time.Minute
|
longDelayTimeout = time.Minute
|
||||||
)
|
)
|
||||||
if eventDelayS < shortDelayS {
|
if eventDelayS < shortDelayS {
|
||||||
return time.Duration(eventDelayS*shortDelayMultiplicator) * time.Second
|
return time.Duration(eventDelayS * shortDelayMultiplicator * float64(time.Second))
|
||||||
}
|
}
|
||||||
if eventDelayS < longDelayS {
|
if eventDelayS < longDelayS {
|
||||||
return longDelayTimeout
|
return longDelayTimeout
|
||||||
}
|
}
|
||||||
return time.Duration(eventDelayS) * time.Second
|
return time.Duration(eventDelayS * float64(time.Second))
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
|||||||
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||||
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
||||||
..
|
..
|
||||||
.TH "STDISCOSRV" "1" "May 25, 2025" "v1.29.6" "Syncthing"
|
.TH "STDISCOSRV" "1" "Jun 01, 2025" "v1.29.6" "Syncthing"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
stdiscosrv \- Syncthing Discovery Server
|
stdiscosrv \- Syncthing Discovery Server
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
|
|||||||
+1
-1
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
|||||||
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||||
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
||||||
..
|
..
|
||||||
.TH "STRELAYSRV" "1" "May 25, 2025" "v1.29.6" "Syncthing"
|
.TH "STRELAYSRV" "1" "Jun 01, 2025" "v1.29.6" "Syncthing"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
strelaysrv \- Syncthing Relay Server
|
strelaysrv \- Syncthing Relay Server
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
|
|||||||
+4
-3
@@ -28,7 +28,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
|||||||
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||||
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
||||||
..
|
..
|
||||||
.TH "SYNCTHING-BEP" "7" "May 25, 2025" "v1.29.6" "Syncthing"
|
.TH "SYNCTHING-BEP" "7" "Jun 01, 2025" "v1.29.6" "Syncthing"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
syncthing-bep \- Block Exchange Protocol v1
|
syncthing-bep \- Block Exchange Protocol v1
|
||||||
.SH INTRODUCTION AND DEFINITIONS
|
.SH INTRODUCTION AND DEFINITIONS
|
||||||
@@ -254,8 +254,9 @@ protocol buffer message just as in the uncompressed case.
|
|||||||
This informational message provides information about the cluster
|
This informational message provides information about the cluster
|
||||||
configuration as it pertains to the current connection. A Cluster Config
|
configuration as it pertains to the current connection. A Cluster Config
|
||||||
message MUST be the first post authentication message sent on a BEP
|
message MUST be the first post authentication message sent on a BEP
|
||||||
connection. Additional Cluster Config messages MUST NOT be sent after the
|
connection. Additional Cluster Config messages MAY be sent after the initial
|
||||||
initial exchange.
|
exchange to change the connection in place with the update configuration. If an
|
||||||
|
implementation does not support that, it SHOULD close the connection.
|
||||||
.SS Protocol Buffer Schema
|
.SS Protocol Buffer Schema
|
||||||
.INDENT 0.0
|
.INDENT 0.0
|
||||||
.INDENT 3.5
|
.INDENT 3.5
|
||||||
|
|||||||
+18
-1
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
|||||||
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||||
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
||||||
..
|
..
|
||||||
.TH "SYNCTHING-CONFIG" "5" "May 25, 2025" "v1.29.6" "Syncthing"
|
.TH "SYNCTHING-CONFIG" "5" "Jun 01, 2025" "v1.29.6" "Syncthing"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
syncthing-config \- Syncthing Configuration
|
syncthing-config \- Syncthing Configuration
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
@@ -1537,6 +1537,23 @@ no limit. Affects incoming connections and prevents attempting outgoing
|
|||||||
connections. The mechanism is described in detail in a \fI\%separate
|
connections. The mechanism is described in detail in a \fI\%separate
|
||||||
chapter\fP\&.
|
chapter\fP\&.
|
||||||
.UNINDENT
|
.UNINDENT
|
||||||
|
.INDENT 0.0
|
||||||
|
.TP
|
||||||
|
.B auditEnabled
|
||||||
|
When \fBtrue\fP, analogous to \fI\%\-\-audit\fP being set.
|
||||||
|
Defaults to \fBfalse\fP\&.
|
||||||
|
.sp
|
||||||
|
When either this option, or \fI\%\-\-audit\fP (or both) are enabled,
|
||||||
|
auditing is enabled.
|
||||||
|
.UNINDENT
|
||||||
|
.INDENT 0.0
|
||||||
|
.TP
|
||||||
|
.B auditFile
|
||||||
|
Analogous to \fI\%\-\-auditfile\fP\&. Defaults to being unset.
|
||||||
|
.sp
|
||||||
|
For compatibility reasons, if both this option and \fI\%\-\-auditfile\fP
|
||||||
|
are set, \fI\%\-\-auditfile\fP takes priority.
|
||||||
|
.UNINDENT
|
||||||
.SH DEFAULTS ELEMENT
|
.SH DEFAULTS ELEMENT
|
||||||
.INDENT 0.0
|
.INDENT 0.0
|
||||||
.INDENT 3.5
|
.INDENT 3.5
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
|||||||
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||||
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
||||||
..
|
..
|
||||||
.TH "SYNCTHING-DEVICE-IDS" "7" "May 25, 2025" "v1.29.6" "Syncthing"
|
.TH "SYNCTHING-DEVICE-IDS" "7" "Jun 01, 2025" "v1.29.6" "Syncthing"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
syncthing-device-ids \- Understanding Device IDs
|
syncthing-device-ids \- Understanding Device IDs
|
||||||
.sp
|
.sp
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
|||||||
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||||
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
||||||
..
|
..
|
||||||
.TH "SYNCTHING-EVENT-API" "7" "May 25, 2025" "v1.29.6" "Syncthing"
|
.TH "SYNCTHING-EVENT-API" "7" "Jun 01, 2025" "v1.29.6" "Syncthing"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
syncthing-event-api \- Event API
|
syncthing-event-api \- Event API
|
||||||
.SH DESCRIPTION
|
.SH DESCRIPTION
|
||||||
|
|||||||
+1
-1
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
|||||||
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||||
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
||||||
..
|
..
|
||||||
.TH "SYNCTHING-FAQ" "7" "May 25, 2025" "v1.29.6" "Syncthing"
|
.TH "SYNCTHING-FAQ" "7" "Jun 01, 2025" "v1.29.6" "Syncthing"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
syncthing-faq \- Frequently Asked Questions
|
syncthing-faq \- Frequently Asked Questions
|
||||||
.INDENT 0.0
|
.INDENT 0.0
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
|||||||
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||||
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
||||||
..
|
..
|
||||||
.TH "SYNCTHING-GLOBALDISCO" "7" "May 25, 2025" "v1.29.6" "Syncthing"
|
.TH "SYNCTHING-GLOBALDISCO" "7" "Jun 01, 2025" "v1.29.6" "Syncthing"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
syncthing-globaldisco \- Global Discovery Protocol v3
|
syncthing-globaldisco \- Global Discovery Protocol v3
|
||||||
.SH ANNOUNCEMENTS
|
.SH ANNOUNCEMENTS
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
|||||||
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||||
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
||||||
..
|
..
|
||||||
.TH "SYNCTHING-LOCALDISCO" "7" "May 25, 2025" "v1.29.6" "Syncthing"
|
.TH "SYNCTHING-LOCALDISCO" "7" "Jun 01, 2025" "v1.29.6" "Syncthing"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
syncthing-localdisco \- Local Discovery Protocol v4
|
syncthing-localdisco \- Local Discovery Protocol v4
|
||||||
.SH MODE OF OPERATION
|
.SH MODE OF OPERATION
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
|||||||
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||||
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
||||||
..
|
..
|
||||||
.TH "SYNCTHING-NETWORKING" "7" "May 25, 2025" "v1.29.6" "Syncthing"
|
.TH "SYNCTHING-NETWORKING" "7" "Jun 01, 2025" "v1.29.6" "Syncthing"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
syncthing-networking \- Firewall Setup
|
syncthing-networking \- Firewall Setup
|
||||||
.SH ROUTER SETUP
|
.SH ROUTER SETUP
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
|||||||
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||||
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
||||||
..
|
..
|
||||||
.TH "SYNCTHING-RELAY" "7" "May 25, 2025" "v1.29.6" "Syncthing"
|
.TH "SYNCTHING-RELAY" "7" "Jun 01, 2025" "v1.29.6" "Syncthing"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
syncthing-relay \- Relay Protocol v1
|
syncthing-relay \- Relay Protocol v1
|
||||||
.SH WHAT IS A RELAY?
|
.SH WHAT IS A RELAY?
|
||||||
@@ -246,8 +246,11 @@ After the successful response, all the bytes written and received will be
|
|||||||
relayed between the two devices in the session directly.
|
relayed between the two devices in the session directly.
|
||||||
.SS Example Exchange
|
.SS Example Exchange
|
||||||
.sp
|
.sp
|
||||||
Client A \- Permanent protocol mode
|
Client A is the first to join the session.
|
||||||
Client B \- Temporary protocol mode
|
.sp
|
||||||
|
Client B is the second to join the session.
|
||||||
|
.sp
|
||||||
|
Both are in session mode.
|
||||||
.TS
|
.TS
|
||||||
box center;
|
box center;
|
||||||
l|l|l|l.
|
l|l|l|l.
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
|||||||
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||||
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
||||||
..
|
..
|
||||||
.TH "SYNCTHING-REST-API" "7" "May 25, 2025" "v1.29.6" "Syncthing"
|
.TH "SYNCTHING-REST-API" "7" "Jun 01, 2025" "v1.29.6" "Syncthing"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
syncthing-rest-api \- REST API
|
syncthing-rest-api \- REST API
|
||||||
.sp
|
.sp
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
|||||||
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||||
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
||||||
..
|
..
|
||||||
.TH "SYNCTHING-SECURITY" "7" "May 25, 2025" "v1.29.6" "Syncthing"
|
.TH "SYNCTHING-SECURITY" "7" "Jun 01, 2025" "v1.29.6" "Syncthing"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
syncthing-security \- Security Principles
|
syncthing-security \- Security Principles
|
||||||
.sp
|
.sp
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
|||||||
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||||
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
||||||
..
|
..
|
||||||
.TH "SYNCTHING-STIGNORE" "5" "May 25, 2025" "v1.29.6" "Syncthing"
|
.TH "SYNCTHING-STIGNORE" "5" "Jun 01, 2025" "v1.29.6" "Syncthing"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
syncthing-stignore \- Prevent files from being synchronized to other nodes
|
syncthing-stignore \- Prevent files from being synchronized to other nodes
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
|||||||
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||||
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
||||||
..
|
..
|
||||||
.TH "SYNCTHING-VERSIONING" "7" "May 25, 2025" "v1.29.6" "Syncthing"
|
.TH "SYNCTHING-VERSIONING" "7" "Jun 01, 2025" "v1.29.6" "Syncthing"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
syncthing-versioning \- Keep automatic backups of deleted files by other nodes
|
syncthing-versioning \- Keep automatic backups of deleted files by other nodes
|
||||||
.sp
|
.sp
|
||||||
|
|||||||
+1
-1
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
|||||||
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||||
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
||||||
..
|
..
|
||||||
.TH "SYNCTHING" "1" "May 25, 2025" "v1.29.6" "Syncthing"
|
.TH "SYNCTHING" "1" "Jun 01, 2025" "v1.29.6" "Syncthing"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
syncthing \- Syncthing
|
syncthing \- Syncthing
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
|
|||||||
@@ -6,3 +6,10 @@ protocol compatible with Syncthing 1.
|
|||||||
|
|
||||||
More detailed information about Syncthing 2 can be found in the release
|
More detailed information about Syncthing 2 can be found in the release
|
||||||
notes at https://github.com/syncthing/syncthing/releases.
|
notes at https://github.com/syncthing/syncthing/releases.
|
||||||
|
|
||||||
|
This release is also available as:
|
||||||
|
|
||||||
|
* APT repository: https://apt.syncthing.net/
|
||||||
|
|
||||||
|
* Docker image: `docker.io/syncthing/syncthing:{{.version}}` or `ghcr.io/syncthing/syncthing:{{.version}}`
|
||||||
|
(`{docker,ghcr}.io/syncthing/syncthing:1` to follow just the major version)
|
||||||
|
|||||||
@@ -0,0 +1,124 @@
|
|||||||
|
// Copyright (C) 2025 The Syncthing Authors.
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
//go:build ignore
|
||||||
|
// +build ignore
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/coreos/go-semver/semver"
|
||||||
|
)
|
||||||
|
|
||||||
|
const suffix = "rc"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
pre := flag.Bool("pre", false, "Create a prerelease")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
// Get the latest "v1.22.3" or "v1.22.3-rc.1" style tag.
|
||||||
|
latestTag, err := cmd("git", "describe", "--abbrev=0", "--match", "v[0-9].*")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
latest, err := semver.NewVersion(latestTag[1:])
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the latest "v1.22.3" style tag, excludeing prereleases.
|
||||||
|
latestStableTag, err := cmd("git", "describe", "--abbrev=0", "--match", "v[0-9].*", "--exclude", "*-*")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
latestStable, err := semver.NewVersion(latestStableTag[1:])
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the commit logs since the latest stable tag.
|
||||||
|
logsSinceLatest, err := cmd("git", "log", "--pretty=format:%s", latestStableTag+"..HEAD")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the next version should be a feature or a patch release
|
||||||
|
nextIsFeature := false
|
||||||
|
for _, line := range strings.Split(logsSinceLatest, "\n") {
|
||||||
|
if strings.HasPrefix(line, "feat") {
|
||||||
|
nextIsFeature = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
next := *latestStable
|
||||||
|
if nextIsFeature {
|
||||||
|
next.BumpMinor()
|
||||||
|
} else {
|
||||||
|
next.BumpPatch()
|
||||||
|
}
|
||||||
|
|
||||||
|
if latest.PreRelease != "" {
|
||||||
|
if !*pre {
|
||||||
|
// We want a stable release. Simply remove the prerelease
|
||||||
|
// suffix.
|
||||||
|
latest.PreRelease = ""
|
||||||
|
fmt.Println("v" + latest.String())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// We want the next prerelease. We are already on a prerelease. If
|
||||||
|
// it's the correct prerelease compared to the logs we just got, we
|
||||||
|
// should just bump the prerelease counter.
|
||||||
|
if next.Major == latest.Major && next.Minor == latest.Minor && next.Patch == latest.Patch {
|
||||||
|
parts := latest.PreRelease.Slice()
|
||||||
|
for i, p := range parts {
|
||||||
|
if v, err := strconv.Atoi(p); err == nil {
|
||||||
|
parts[i] = strconv.Itoa(v + 1)
|
||||||
|
latest.PreRelease = semver.PreRelease(strings.Join(parts, "."))
|
||||||
|
fmt.Println("v" + latest.String())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise we generate a new rc.1 for the correct next version.
|
||||||
|
next.PreRelease = suffix + ".1"
|
||||||
|
fmt.Println("v" + next.String())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if nextIsFeature {
|
||||||
|
latest.BumpMinor()
|
||||||
|
} else {
|
||||||
|
latest.BumpPatch()
|
||||||
|
}
|
||||||
|
if *pre {
|
||||||
|
latest.PreRelease = suffix + ".1"
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("v" + latest.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmd(name string, args ...string) (string, error) {
|
||||||
|
cmd := exec.Command(name, args...)
|
||||||
|
bs, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(string(bs)), nil
|
||||||
|
}
|
||||||
+14
-1
@@ -22,6 +22,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"text/template"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -59,12 +60,24 @@ func main() {
|
|||||||
|
|
||||||
// Load potential additional release notes from within the repo
|
// Load potential additional release notes from within the repo
|
||||||
func additionalNotes(newVer string) ([]string, error) {
|
func additionalNotes(newVer string) ([]string, error) {
|
||||||
|
data := map[string]string{
|
||||||
|
"version": strings.TrimLeft(newVer, "v"),
|
||||||
|
}
|
||||||
|
|
||||||
var notes []string
|
var notes []string
|
||||||
ver, _, _ := strings.Cut(newVer, "-")
|
ver, _, _ := strings.Cut(newVer, "-")
|
||||||
for {
|
for {
|
||||||
file := fmt.Sprintf("relnotes/%s.md", ver)
|
file := fmt.Sprintf("relnotes/%s.md", ver)
|
||||||
if bs, err := os.ReadFile(file); err == nil {
|
if bs, err := os.ReadFile(file); err == nil {
|
||||||
notes = append(notes, strings.TrimSpace(string(bs)))
|
tpl, err := template.New("notes").Parse(string(bs))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
if err := tpl.Execute(buf, data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
notes = append(notes, strings.TrimSpace(buf.String()))
|
||||||
} else if !os.IsNotExist(err) {
|
} else if !os.IsNotExist(err) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user