Compare commits
325 Commits
libsyncthi
...
libsyncthi
Author | SHA1 | Date |
---|---|---|
Martchus | 388f287fcb | |
Jakob Borg | 9562cfd5c2 | |
Syncthing Release Automation | ff9b24f388 | |
Syncthing Release Automation | 01b820dc78 | |
Jakob Borg | 79ae24df76 | |
Jakob Borg | 61b94b9ea5 | |
tomasz1986 | 6fb3c5ccf2 | |
Jakob Borg | 2e7c03420f | |
Jakob Borg | faa56b4bb7 | |
Syncthing Release Automation | d7ba5316b8 | |
Syncthing Release Automation | bdfd0f0548 | |
Tim Nordenfur | 5d27185083 | |
Jakob Borg | 4dfb9d7c83 | |
Emil Lundberg | 2f15670094 | |
Syncthing Release Automation | b49137ce36 | |
Jaspitta | 7e4e65ebf5 | |
Simon Frei | 8c8167a4ab | |
Simon Frei | 73cc5553b6 | |
Simon Frei | 2ab2488274 | |
Jakob Borg | eb9cd363d0 | |
Syncthing Release Automation | 7fe3906534 | |
André Colomb | 5fdab1bf11 | |
André Colomb | 13a6d43f0b | |
Jakob Borg | ac942e2481 | |
Luke Hamburg | bbd2a7fbc5 | |
Jakob Borg | 07a9fa2dbd | |
Thomas | aa559bf496 | |
Jakob Borg | 2d968d46b7 | |
Syncthing Release Automation | 86c4cafc96 | |
Beat Reichenbach | c4dfb66d84 | |
Syncthing Release Automation | f4d160684b | |
Syncthing Release Automation | b76e6ce70d | |
Jakob Borg | 6b4028eede | |
Jakob Borg | ad81ac8da7 | |
Jakob Borg | 7ebeaefe77 | |
Jakob Borg | e1dd36561d | |
Jakob Borg | 96c30f8387 | |
Jakob Borg | fc8b353011 | |
Jakob Borg | 416b9e8924 | |
gudvinr | 9f6d732587 | |
Syncthing Release Automation | f2f5786b33 | |
Jakob Borg | eb617865d2 | |
Jakob Borg | a49e318d25 | |
Jakob Borg | e74674a019 | |
dependabot[bot] | d98fa474ae | |
Jakob Borg | f16817632f | |
Jakob Borg | bda4016109 | |
Syncthing Release Automation | 8f5d07bd09 | |
kylosus | 302b352d78 | |
Jakob Borg | 45beb28fa5 | |
Syncthing Release Automation | ee9b20e47a | |
tomasz1986 | 0f55d5fc3e | |
bt90 | 35e153625c | |
bt90 | d5e1b99e6c | |
Jakob Borg | 3297624037 | |
Syncthing Release Automation | 445e8cc532 | |
Jakob Borg | e041877488 | |
Jakob Borg | 36e08f8eee | |
nf | 8b321387c0 | |
Julian Lehrhuber | 8edd67a569 | |
Syncthing Release Automation | e829a63295 | |
diemade | 6881a6897a | |
Daniel Padrta | 9387e107b3 | |
Jakob Borg | aa901790b9 | |
Jakob Borg | 17df4b8634 | |
tomasz1986 | 34ef30dd5c | |
Peter Badida | fc1c7a3c49 | |
Peter Badida | 2abfefc18c | |
dependabot[bot] | 86a08eb87d | |
dependabot[bot] | d330d65859 | |
Syncthing Release Automation | 4e1831fa3a | |
Simon Frei | 2f3eacdb6c | |
Sven Bachmann | 1ce2af1238 | |
Syncthing Release Automation | 683b48182c | |
Sertonix | 795aed306c | |
greatroar | cdefa535ed | |
gudvinr | 91084b83b4 | |
Syncthing Release Automation | 5360e7153b | |
Jakob Borg | 5d0ca19350 | |
Eric P | e8d3529fed | |
Jakob Borg | 935a28c961 | |
dependabot[bot] | d21a2de055 | |
Jakob Borg | 6f1023665c | |
Jakob Borg | c53a1f210c | |
cjc7373 | b71a930bfc | |
Jakob Borg | a2cbc62521 | |
Jakob Borg | 768fd6bff8 | |
Jakob Borg | 48883e0e32 | |
Jakob Borg | 30fe2cf514 | |
Jakob Borg | 3850a08252 | |
Jakob Borg | d0e407f3c3 | |
Maximilian | 16db6fcf3d | |
Syncthing Release Automation | 4c5528bd0e | |
tomasz1986 | d42fff1016 | |
Simon Frei | a28de73031 | |
Jakob Borg | 75310b58a0 | |
Jakob Borg | 8064957270 | |
Jakob Borg | c1ec9a8826 | |
Jakob Borg | 1625b44892 | |
Jakob Borg | d51760f410 | |
Jakob Borg | 7b1932d64e | |
Syncthing Release Automation | 5bfc540c88 | |
Jakob Borg | 4cba99fcd4 | |
Jakob Borg | 2ae15aa454 | |
Jakob Borg | 47bcf4f8f4 | |
Jakob Borg | a8b9096353 | |
Jakob Borg | 5328380691 | |
Syncthing Release Automation | 6069cf39e5 | |
Emil Lundberg | 1f7d236742 | |
Syncthing Release Automation | 8e9ee3fbe8 | |
Jakob Borg | 35b0afc131 | |
Simon Frei | 958ff67ccc | |
tomasz1986 | 13d9317a38 | |
Anatoli Babenia | b184d46d8a | |
Jakob Borg | 3f32c5cb4b | |
Jakob Borg | 5c65a1bc83 | |
Jakob Borg | d0a6dc5b13 | |
Jakob Borg | 439c6c5b7c | |
Jakob Borg | aaee0c126b | |
André Colomb | f3bd4d71de | |
Jakob Borg | 876d056705 | |
DerRockWolf | e988978fa1 | |
Jakob Borg | d5deede7a1 | |
André Colomb | 4f70f5c280 | |
Emil Lundberg | a1ad020b63 | |
Jakob Borg | 8f1b0df74b | |
Jakob Borg | 0f8dc6c1d3 | |
Jakob Borg | 8ae9db3b2d | |
Jakob Borg | e477777f49 | |
Syncthing Release Automation | 7a132bdf24 | |
Jakob Borg | 5e2b7825dc | |
Jakob Borg | 6d30c109e4 | |
Jakob Borg | 58bd931d90 | |
vapatel2 | 854499382e | |
Jakob Borg | cb4c1f9ad2 | |
Catfriend1 | b452fb3ad2 | |
Syncthing Release Automation | c17a1fea77 | |
Syncthing Release Automation | d50511c5c6 | |
Jakob Borg | bae6d5f375 | |
Jakob Borg | b5082f6af8 | |
dependabot[bot] | 9666e9701b | |
Syncthing Release Automation | 86e1c5ff18 | |
tomasz1986 | 16ae1fbe5e | |
Jakob Borg | 11f508d9be | |
Jakob Borg | 9ce6a73f42 | |
Syncthing Release Automation | c5a991cf0a | |
Emil Lundberg | 14569f12d3 | |
Jakob Borg | a405c21ebb | |
Jakob Borg | dc6a10dff4 | |
Jakob Borg | d4c2acf6f6 | |
Jakob Borg | 483ecada80 | |
Eric P | 9553365d31 | |
orangekame3 | 5eb20580b1 | |
Emil Lundberg | ea1ea366d2 | |
Syncthing Release Automation | 6e4574a9f7 | |
Jakob Borg | 3d0da5ac60 | |
Jakob Borg | 9f8e6966d8 | |
Jakob Borg | a64ae36bcc | |
Jakob Borg | 690b55360f | |
DeflateAwning | 2f6187dc0e | |
Emil Lundberg | 8294870ffc | |
bt90 | ac2e444a97 | |
Jakob Borg | 4f6b86a1c0 | |
Jakob Borg | 516c057d43 | |
Jakob Borg | d644dce4e7 | |
bt90 | 7c579880eb | |
Jakob Borg | 296db314f5 | |
Syncthing Release Automation | a8486b0468 | |
bt90 | f8a7a034a7 | |
bt90 | ceae56a860 | |
DeflateAwning | dcafd6ec72 | |
Jakob Borg | 8619a03f01 | |
Jakob Borg | b91d7711aa | |
d-volution | 9940c91ebf | |
tomasz1986 | 80a577b025 | |
tomasz1986 | d672175ce4 | |
tomasz1986 | a44b31d173 | |
Martin Polehla | 70065e6b13 | |
Syncthing Release Automation | adbb3ed2e9 | |
Jakob Borg | 6ed9c0c34c | |
tomasz1986 | 19bbf4f6bf | |
bt90 | cf46bf0297 | |
Jakob Borg | 051cbdc713 | |
Syncthing Release Automation | 58d1f3a471 | |
tomasz1986 | c9dfd75d8e | |
Jakob Borg | f47de83914 | |
tomasz1986 | caedb19307 | |
bt90 | e860d3b974 | |
bt90 | ed66fba42b | |
Jakob Borg | 415f320005 | |
Jakob Borg | 4812600098 | |
Jakob Borg | 5ff11ce142 | |
tomasz1986 | 541572781b | |
Syncthing Release Automation | e38679d9bf | |
Jakob Borg | f25a169c4c | |
bt90 | 06ac10ee37 | |
Jakob Borg | 7c0223bd06 | |
Jakob Borg | c6334e61aa | |
Jakob Borg | 38bbdebffa | |
Jakob Borg | e80d04845e | |
Syncthing Release Automation | 4138e22898 | |
Maximilian | c42c0e7ceb | |
Jakob Borg | 5118538179 | |
tomasz1986 | 4d93648f75 | |
tomasz1986 | 29f100c162 | |
tomasz1986 | cd98a43b80 | |
Jakob Borg | 4bf982376e | |
Jakob Borg | 29056d5873 | |
tomasz1986 | 2abba1dfb0 | |
tomasz1986 | 325b3b114f | |
tomasz1986 | 03590e5ac7 | |
tomasz1986 | 95b3c26da7 | |
tomasz1986 | 3e5f0b1d0e | |
Jakob Borg | 3130af3773 | |
Jakob Borg | abd89f15f7 | |
Jakob Borg | a80e6be353 | |
Jakob Borg | acc532fc60 | |
Syncthing Release Automation | 3cc3fb7504 | |
Jakob Borg | a04cc95005 | |
Jakob Borg | 480fa4b915 | |
Jakob Borg | 92a4931850 | |
Jakob Borg | bdfef9010f | |
bt90 | 467522d04d | |
bt90 | 3147285c60 | |
Jakob Borg | acd767b30b | |
Jakob Borg | 40b3b9ad15 | |
bt90 | c2c6133aa5 | |
Jakob Borg | ccec8a4cdb | |
Jakob Borg | cbf0e31f69 | |
Syncthing Release Automation | c40dae315b | |
Jakob Borg | ac0ce1c38f | |
Jakob Borg | 72c683aaca | |
Syncthing Release Automation | 8042bd1a54 | |
Jakob Borg | 462389934b | |
Jakob Borg | b347c14bd1 | |
Jakob Borg | 8dfec6983b | |
Jakob Borg | 9ebf2dae7b | |
André Colomb | a8cacdca94 | |
Jakob Borg | 8b87cd5229 | |
Syncthing Release Automation | e09146ee03 | |
Jakob Borg | b9c08d3814 | |
Jakob Borg | 58042b3129 | |
Keith Harrison | eed12f3ec5 | |
tomasz1986 | 5323928159 | |
Syncthing Release Automation | 97625ccc26 | |
Jakob Borg | 4fe746d9aa | |
Jakob Borg | 4f8cdd41ee | |
Jakob Borg | 406e3646e5 | |
Jakob Borg | 9d21b91124 | |
Chih-Hsuan Yen | b806026990 | |
tomasz1986 | 341b79814e | |
Jakob Borg | 319916124b | |
Emil Lundberg | b08b99e284 | |
Jakob Borg | f565df628c | |
Jakob Borg | 855c6dc67b | |
tomasz1986 | dc5e10fa2c | |
Syncthing Release Automation | b857e57a35 | |
tomasz1986 | f42f041f53 | |
Christian Kujau | 6b6b2c6194 | |
tomasz1986 | d70eb569f2 | |
deepsource-autofix[bot] | 21c074cc2c | |
deepsource-autofix[bot] | f23c41221b | |
deepsource-autofix[bot] | 24e230d455 | |
Syncthing Release Automation | 31daa20367 | |
Jakob Borg | e4d0f9dd6c | |
Jakob Borg | df2ac7aaeb | |
Jakob Borg | b96b23957b | |
bt90 | 265ce139c5 | |
Jakob Borg | 48c95eb41d | |
Jakob Borg | 937895be69 | |
Jakob Borg | a3886f778d | |
Jakob Borg | 6aecc2622c | |
Jakob Borg | c55b205a0b | |
Jakob Borg | 2fcf7006e6 | |
Jakob Borg | bf61e485a6 | |
Jakob Borg | b2886f11b1 | |
Syncthing Release Automation | 11ece5d89e | |
Jakob Borg | cf68dfac43 | |
Jakob Borg | c44de2cd58 | |
Jakob Borg | 6ff5ed6d23 | |
Jakob Borg | 25ec2b63ab | |
Jakob Borg | c5ab71d7a5 | |
Jakob Borg | 1fc4c9d9c5 | |
Jakob Borg | bebf2c259c | |
Jakob Borg | b7d526903e | |
Jakob Borg | 92917c9f62 | |
Jakob Borg | fdde05cf12 | |
Syncthing Release Automation | a7d7325e9b | |
Jakob Borg | 465823237f | |
Jakob Borg | 5a1f996e56 | |
Jakob Borg | 229b6a292c | |
Jakob Borg | c84e47a7b2 | |
otbutz | daf7baaeff | |
Jakob Borg | a4a1231e92 | |
Jakob Borg | b99dee3ac3 | |
Syncthing Release Automation | 89fc69249b | |
Jakob Borg | d421d66a3f | |
guangwu | 27aba3567b | |
Syncthing Release Automation | 5532532db9 | |
Felix | c369f8abb2 | |
Jakob Borg | 9e52f6cf2f | |
Jakob Borg | d22a38d947 | |
Jakob Borg | 439fa6c848 | |
Jakob Borg | 6b475bdb78 | |
Syncthing Release Automation | 7d56fba321 | |
Simon Frei | bf6ffbbd67 | |
Jakob Borg | a972811f54 | |
Jakob Borg | 88da67d7c3 | |
Jakob Borg | 1f07e05470 | |
Jakob Borg | 5cab08a36a | |
Jakob Borg | 4e2bb58e2d | |
Jakob Borg | ae176ea9cd | |
Syncthing Release Automation | 2b17db8aa3 | |
Jakob Borg | 81a4b22d43 | |
Jakob Borg | f7da96fb82 | |
Jakob Borg | f5e5af391a | |
Jakob Borg | 5a3ac86c3f | |
dependabot[bot] | 3d78ff9f68 | |
Jakob Borg | f3127a66ee | |
Syncthing Release Automation | 90b4711ad2 | |
Syncthing Release Automation | 716b42103a | |
Anthony Goeckner | 405cdedcd3 | |
Syncthing Release Automation | 0b3a101ccd | |
Eng Zer Jun | 089320aadc | |
Will Rouesnel | b2fb2ef276 |
|
@ -1,13 +0,0 @@
|
||||||
---
|
|
||||||
name: Feature request
|
|
||||||
about: If you're just not sure how to do something, see "ask a question".
|
|
||||||
labels: enhancement, needs-triage
|
|
||||||
---
|
|
||||||
|
|
||||||
### Include required information
|
|
||||||
|
|
||||||
Please be sure to include at least:
|
|
||||||
|
|
||||||
- what problem your new feature would solve
|
|
||||||
- how or why you think it is generally useful (i.e., not just for you)
|
|
||||||
- what alternatives or workarounds you considered
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
name: Feature request
|
||||||
|
description: File a new feature request
|
||||||
|
labels: ["enhancement", "needs-triage"]
|
||||||
|
body:
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: feature
|
||||||
|
attributes:
|
||||||
|
label: Feature description
|
||||||
|
description: Please describe the behavior you'd like to see.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: problem-usecase
|
||||||
|
attributes:
|
||||||
|
label: Problem or use case
|
||||||
|
description: Please explain which problem this would solve, or what the use case is for the feature. Keep in mind that it's more likely to be implemented if it's generally useful for a larger number of users.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: alternatives
|
||||||
|
attributes:
|
||||||
|
label: Alternatives or workarounds
|
||||||
|
description: Please describe any alternatives or workarounds you have considered and, possibly, rejected.
|
||||||
|
validations:
|
||||||
|
required: true
|
|
@ -1,23 +0,0 @@
|
||||||
---
|
|
||||||
name: Bug report
|
|
||||||
about: If you're actually looking for support, see "ask a question".
|
|
||||||
labels: bug, needs-triage
|
|
||||||
---
|
|
||||||
|
|
||||||
### Does your log mention database corruption?
|
|
||||||
|
|
||||||
If your Syncthing log reports panics because of database corruption it is
|
|
||||||
most likely a fault with your system's storage or memory. Affected log
|
|
||||||
entries will contain lines starting with `panic: leveldb`. You will need to
|
|
||||||
delete the index database to clear this, by running `syncthing
|
|
||||||
-reset-database`.
|
|
||||||
|
|
||||||
### Include required information
|
|
||||||
|
|
||||||
Please be sure to include at least:
|
|
||||||
|
|
||||||
- which version of Syncthing and what operating system you are using
|
|
||||||
- browser and version, if applicable
|
|
||||||
- what happened,
|
|
||||||
- what you expected to happen instead, and
|
|
||||||
- any steps to reproduce the problem.
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
name: Bug report
|
||||||
|
description: If you're actually looking for support instead, see "I need help / I have a question".
|
||||||
|
labels: ["bug", "needs-triage"]
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
:no_entry_sign: If you want to report a security issue, please see [our Security Policy](https://syncthing.net/security/) and do not report the issue here.
|
||||||
|
|
||||||
|
:interrobang: If you are not sure if there is a bug, but something isn't working right and you need help, please [use the forum](https://forum.syncthing.net/).
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: what-happened
|
||||||
|
attributes:
|
||||||
|
label: What happened?
|
||||||
|
description: Also tell us, what did you expect to happen, and any steps we might use to reproduce the problem.
|
||||||
|
placeholder: Tell us what you see!
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: version
|
||||||
|
attributes:
|
||||||
|
label: Syncthing version
|
||||||
|
description: What version of Syncthing are you running?
|
||||||
|
placeholder: v1.27.4
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: platform
|
||||||
|
attributes:
|
||||||
|
label: Platform & operating system
|
||||||
|
description: On what platform(s) are you seeing the problem?
|
||||||
|
placeholder: Linux arm64
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: browser
|
||||||
|
attributes:
|
||||||
|
label: Browser version
|
||||||
|
description: If the problem is related to the GUI, describe your browser and version.
|
||||||
|
placeholder: Safari 17.3.1
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: logs
|
||||||
|
attributes:
|
||||||
|
label: Relevant log output
|
||||||
|
description: Please copy and paste any relevant log output or crash backtrace. This will be automatically formatted into code, so no need for backticks.
|
||||||
|
render: shell
|
|
@ -0,0 +1,74 @@
|
||||||
|
name: Build Infrastructure Images
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- infrastructure
|
||||||
|
- infra-*
|
||||||
|
|
||||||
|
env:
|
||||||
|
GO_VERSION: "~1.22.3"
|
||||||
|
CGO_ENABLED: "0"
|
||||||
|
BUILD_USER: docker
|
||||||
|
BUILD_HOST: github.syncthing.net
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
docker-syncthing:
|
||||||
|
name: Build and push Docker images
|
||||||
|
if: github.repository == 'syncthing/syncthing'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
environment: docker
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
pkg:
|
||||||
|
- stcrashreceiver
|
||||||
|
- strelaypoolsrv
|
||||||
|
- stupgrades
|
||||||
|
- ursrv
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: ${{ env.GO_VERSION }}
|
||||||
|
check-latest: true
|
||||||
|
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build binaries
|
||||||
|
run: |
|
||||||
|
for arch in arm64 amd64; do
|
||||||
|
go run build.go -goos linux -goarch "$arch" build ${{ matrix.pkg }}
|
||||||
|
mv ${{ matrix.pkg }} ${{ matrix.pkg }}-linux-"$arch"
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Set Docker tags (all branches)
|
||||||
|
run: |
|
||||||
|
tags=syncthing/${{ matrix.pkg }}:${{ github.sha }}
|
||||||
|
echo "TAGS=$tags" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Set Docker tags (latest)
|
||||||
|
if: github.ref == 'refs/heads/infrastructure'
|
||||||
|
run: |
|
||||||
|
tags=syncthing/${{ matrix.pkg }}:latest,${{ env.TAGS }}
|
||||||
|
echo "TAGS=$tags" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Build and push
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./Dockerfile.${{ matrix.pkg }}
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
push: true
|
||||||
|
tags: ${{ env.TAGS }}
|
||||||
|
labels: |
|
||||||
|
org.opencontainers.image.revision=${{ github.sha }}
|
|
@ -3,10 +3,16 @@ name: Build Syncthing
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
push:
|
push:
|
||||||
|
schedule:
|
||||||
|
# Run nightly build at 05:00 UTC
|
||||||
|
- cron: '00 05 * * *'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
# The go version to use for builds.
|
# The go version to use for builds. We set check-latest to true when
|
||||||
GO_VERSION: "^1.20.3"
|
# installing, so we get the latest patch version that matches the
|
||||||
|
# expression.
|
||||||
|
GO_VERSION: "~1.22.3"
|
||||||
|
|
||||||
# Optimize compatibility on the slow archictures.
|
# Optimize compatibility on the slow archictures.
|
||||||
GO386: softfloat
|
GO386: softfloat
|
||||||
|
@ -42,7 +48,7 @@ jobs:
|
||||||
runner: ["windows-latest", "ubuntu-latest", "macos-latest"]
|
runner: ["windows-latest", "ubuntu-latest", "macos-latest"]
|
||||||
# The oldest version in this list should match what we have in our go.mod.
|
# The oldest version in this list should match what we have in our go.mod.
|
||||||
# Variables don't seem to be supported here, or we could have done something nice.
|
# Variables don't seem to be supported here, or we could have done something nice.
|
||||||
go: ["1.19", "1.20"]
|
go: ["~1.21.7", "~1.22.3"]
|
||||||
runs-on: ${{ matrix.runner }}
|
runs-on: ${{ matrix.runner }}
|
||||||
steps:
|
steps:
|
||||||
- name: Set git to use LF
|
- name: Set git to use LF
|
||||||
|
@ -55,24 +61,32 @@ jobs:
|
||||||
git config --global core.autocrlf false
|
git config --global core.autocrlf false
|
||||||
git config --global core.eol lf
|
git config --global core.eol lf
|
||||||
|
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- uses: actions/setup-go@v4
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go }}
|
go-version: ${{ matrix.go }}
|
||||||
cache: true
|
cache: true
|
||||||
|
check-latest: true
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
go run build.go
|
go run build.go
|
||||||
|
|
||||||
- name: Test
|
- name: Install go-test-json-to-loki
|
||||||
# Our Windows tests currently don't work on Go 1.20
|
|
||||||
# https://github.com/syncthing/syncthing/issues/8779
|
|
||||||
# https://github.com/syncthing/syncthing/issues/8778
|
|
||||||
if: "!(matrix.go == '1.20' && matrix.runner == 'windows-latest')"
|
|
||||||
run: |
|
run: |
|
||||||
go run build.go test
|
go install calmh.dev/go-test-json-to-loki@latest
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: |
|
||||||
|
go version
|
||||||
|
go run build.go test | go-test-json-to-loki
|
||||||
|
env:
|
||||||
|
GOFLAGS: "-json"
|
||||||
|
LOKI_URL: ${{ vars.LOKI_URL }}
|
||||||
|
LOKI_USER: ${{ vars.LOKI_USER }}
|
||||||
|
LOKI_PASSWORD: ${{ secrets.LOKI_PASSWORD }}
|
||||||
|
LOKI_LABELS: "go=${{ matrix.go }},runner=${{ matrix.runner }},repo=${{ github.repository }},ref=${{ github.ref }}"
|
||||||
|
|
||||||
#
|
#
|
||||||
# Meta checks for formatting, copyright, etc
|
# Meta checks for formatting, copyright, etc
|
||||||
|
@ -82,26 +96,49 @@ jobs:
|
||||||
name: Check correctness
|
name: Check correctness
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- uses: actions/setup-go@v4
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ${{ env.GO_VERSION }}
|
go-version: ${{ env.GO_VERSION }}
|
||||||
|
cache: false
|
||||||
|
check-latest: true
|
||||||
|
|
||||||
- name: Check correctness
|
- name: Check correctness
|
||||||
run: |
|
run: |
|
||||||
go test -v ./meta
|
go test -v ./meta
|
||||||
|
|
||||||
|
#
|
||||||
|
# The basic checks job is a virtual one that depends on the matrix tests,
|
||||||
|
# the correctness checks, and various builds that we always do. This makes
|
||||||
|
# it easy to have the PR process have a single test as a gatekeeper for
|
||||||
|
# merging, instead of having to add all the matrix tests and update them
|
||||||
|
# each time the version changes. (The top level test is not available for
|
||||||
|
# choosing there, only the matrix "children".)
|
||||||
|
#
|
||||||
|
|
||||||
|
basics:
|
||||||
|
name: Basic checks passed
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs:
|
||||||
|
- build-test
|
||||||
|
- correctness
|
||||||
|
- package-linux
|
||||||
|
- package-cross
|
||||||
|
- package-source
|
||||||
|
- package-debian
|
||||||
|
- govulncheck
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
#
|
#
|
||||||
# Windows
|
# Windows
|
||||||
#
|
#
|
||||||
|
|
||||||
package-windows:
|
package-windows:
|
||||||
name: Package for Windows
|
name: Package for Windows
|
||||||
if: github.event_name == 'push' && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/heads/release-'))
|
if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/heads/release-'))
|
||||||
environment: signing
|
environment: signing
|
||||||
needs:
|
|
||||||
- build-test
|
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Set git to use LF
|
- name: Set git to use LF
|
||||||
|
@ -113,15 +150,22 @@ jobs:
|
||||||
git config --global core.autocrlf false
|
git config --global core.autocrlf false
|
||||||
git config --global core.eol lf
|
git config --global core.eol lf
|
||||||
|
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- uses: actions/setup-go@v4
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ${{ env.GO_VERSION }}
|
go-version: ${{ env.GO_VERSION }}
|
||||||
|
cache: false
|
||||||
|
check-latest: true
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
- name: Get actual Go version
|
||||||
|
run: |
|
||||||
|
go version
|
||||||
|
echo "GO_VERSION=$(go version | sed 's#^.*go##;s# .*##')" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~\AppData\Local\go-build
|
~\AppData\Local\go-build
|
||||||
|
@ -146,7 +190,7 @@ jobs:
|
||||||
CODESIGN_TIMESTAMP_SERVER: ${{ secrets.CODESIGN_TIMESTAMP_SERVER }}
|
CODESIGN_TIMESTAMP_SERVER: ${{ secrets.CODESIGN_TIMESTAMP_SERVER }}
|
||||||
|
|
||||||
- name: Archive artifacts
|
- name: Archive artifacts
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: packages-windows
|
name: packages-windows
|
||||||
path: syncthing-windows-*.zip
|
path: syncthing-windows-*.zip
|
||||||
|
@ -157,19 +201,24 @@ jobs:
|
||||||
|
|
||||||
package-linux:
|
package-linux:
|
||||||
name: Package for Linux
|
name: Package for Linux
|
||||||
needs:
|
|
||||||
- build-test
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- uses: actions/setup-go@v4
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ${{ env.GO_VERSION }}
|
go-version: ${{ env.GO_VERSION }}
|
||||||
|
cache: false
|
||||||
|
check-latest: true
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
- name: Get actual Go version
|
||||||
|
run: |
|
||||||
|
go version
|
||||||
|
echo "GO_VERSION=$(go version | sed 's#^.*go##;s# .*##')" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.cache/go-build
|
~/.cache/go-build
|
||||||
|
@ -186,7 +235,7 @@ jobs:
|
||||||
CGO_ENABLED: "0"
|
CGO_ENABLED: "0"
|
||||||
|
|
||||||
- name: Archive artifacts
|
- name: Archive artifacts
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: packages-linux
|
name: packages-linux
|
||||||
path: syncthing-linux-*.tar.gz
|
path: syncthing-linux-*.tar.gz
|
||||||
|
@ -197,21 +246,26 @@ jobs:
|
||||||
|
|
||||||
package-macos:
|
package-macos:
|
||||||
name: Package for macOS
|
name: Package for macOS
|
||||||
if: github.event_name == 'push' && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/heads/release-'))
|
if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/heads/release-'))
|
||||||
environment: signing
|
environment: signing
|
||||||
needs:
|
|
||||||
- build-test
|
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- uses: actions/setup-go@v4
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ${{ env.GO_VERSION }}
|
go-version: ${{ env.GO_VERSION }}
|
||||||
|
cache: false
|
||||||
|
check-latest: true
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
- name: Get actual Go version
|
||||||
|
run: |
|
||||||
|
go version
|
||||||
|
echo "GO_VERSION=$(go version | sed 's#^.*go##;s# .*##')" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.cache/go-build
|
~/.cache/go-build
|
||||||
|
@ -280,21 +334,22 @@ jobs:
|
||||||
zip -r "../$universal.zip" "$universal"
|
zip -r "../$universal.zip" "$universal"
|
||||||
|
|
||||||
- name: Archive artifacts
|
- name: Archive artifacts
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: packages-macos
|
name: packages-macos
|
||||||
path: syncthing-*.zip
|
path: syncthing-*.zip
|
||||||
|
|
||||||
notarize-macos:
|
notarize-macos:
|
||||||
name: Notarize for macOS
|
name: Notarize for macOS
|
||||||
if: github.event_name == 'push' && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/heads/release-'))
|
if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/heads/release-'))
|
||||||
environment: signing
|
environment: signing
|
||||||
needs:
|
needs:
|
||||||
- package-macos
|
- package-macos
|
||||||
|
- basics
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Download artifacts
|
- name: Download artifacts
|
||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: packages-macos
|
name: packages-macos
|
||||||
|
|
||||||
|
@ -320,19 +375,24 @@ jobs:
|
||||||
|
|
||||||
package-cross:
|
package-cross:
|
||||||
name: Package cross compiled
|
name: Package cross compiled
|
||||||
needs:
|
|
||||||
- build-test
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- uses: actions/setup-go@v4
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ${{ env.GO_VERSION }}
|
go-version: ${{ env.GO_VERSION }}
|
||||||
|
cache: false
|
||||||
|
check-latest: true
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
- name: Get actual Go version
|
||||||
|
run: |
|
||||||
|
go version
|
||||||
|
echo "GO_VERSION=$(go version | sed 's#^.*go##;s# .*##')" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.cache/go-build
|
~/.cache/go-build
|
||||||
|
@ -351,20 +411,27 @@ jobs:
|
||||||
| grep -v nacl/ \
|
| grep -v nacl/ \
|
||||||
| grep -v plan9/ \
|
| grep -v plan9/ \
|
||||||
| grep -v windows/ \
|
| grep -v windows/ \
|
||||||
|
| grep -v /wasm \
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Build for each platform with errors silenced, because we expect
|
||||||
|
# some oddball platforms to fail. This avoids a bunch of errors in
|
||||||
|
# the GitHub Actions output, instead summarizing each build
|
||||||
|
# failure as a warning.
|
||||||
for plat in $platforms; do
|
for plat in $platforms; do
|
||||||
goos="${plat%/*}"
|
goos="${plat%/*}"
|
||||||
goarch="${plat#*/}"
|
goarch="${plat#*/}"
|
||||||
if ! go run build.go -goos "$goos" -goarch "$goarch" tar ; then
|
echo "::group ::$plat"
|
||||||
echo "*** $plat failed ***"
|
if ! go run build.go -goos "$goos" -goarch "$goarch" tar 2>/dev/null; then
|
||||||
|
echo "::warning ::Failed to build for $plat"
|
||||||
fi
|
fi
|
||||||
|
echo "::endgroup::"
|
||||||
done
|
done
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: "0"
|
CGO_ENABLED: "0"
|
||||||
|
|
||||||
- name: Archive artifacts
|
- name: Archive artifacts
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: packages-other
|
name: packages-other
|
||||||
path: syncthing-*.tar.gz
|
path: syncthing-*.tar.gz
|
||||||
|
@ -375,17 +442,17 @@ jobs:
|
||||||
|
|
||||||
package-source:
|
package-source:
|
||||||
name: Package source code
|
name: Package source code
|
||||||
needs:
|
|
||||||
- build-test
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- uses: actions/setup-go@v4
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ${{ env.GO_VERSION }}
|
go-version: ${{ env.GO_VERSION }}
|
||||||
|
cache: false
|
||||||
|
check-latest: true
|
||||||
|
|
||||||
- name: Package source
|
- name: Package source
|
||||||
run: |
|
run: |
|
||||||
|
@ -404,38 +471,46 @@ jobs:
|
||||||
mv "syncthing-source-$version.tar.gz" syncthing
|
mv "syncthing-source-$version.tar.gz" syncthing
|
||||||
|
|
||||||
- name: Archive artifacts
|
- name: Archive artifacts
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: packages-source
|
name: packages-source
|
||||||
path: syncthing-source-*.tar.gz
|
path: syncthing-source-*.tar.gz
|
||||||
|
|
||||||
#
|
#
|
||||||
# Sign binaries for auto upgrade
|
# Sign binaries for auto upgrade, generate ASC signature files
|
||||||
#
|
#
|
||||||
|
|
||||||
sign-for-upgrade:
|
sign-for-upgrade:
|
||||||
name: Sign for upgrade
|
name: Sign for upgrade
|
||||||
if: github.event_name == 'push' && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/heads/release-'))
|
if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/heads/release-'))
|
||||||
environment: signing
|
environment: signing
|
||||||
needs:
|
needs:
|
||||||
|
- basics
|
||||||
- package-windows
|
- package-windows
|
||||||
- package-linux
|
- package-linux
|
||||||
- package-macos
|
- package-macos
|
||||||
- package-cross
|
- package-cross
|
||||||
|
- package-source
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: syncthing/release-tools
|
repository: syncthing/release-tools
|
||||||
path: tools
|
path: tools
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Download artifacts
|
- name: Download artifacts
|
||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v4
|
||||||
|
|
||||||
|
- uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: ${{ env.GO_VERSION }}
|
||||||
|
cache: false
|
||||||
|
check-latest: true
|
||||||
|
|
||||||
- name: Install signing tool
|
- name: Install signing tool
|
||||||
run: |
|
run: |
|
||||||
|
@ -450,11 +525,31 @@ jobs:
|
||||||
mv packages-*/* packages
|
mv packages-*/* packages
|
||||||
pushd packages
|
pushd packages
|
||||||
"$GITHUB_WORKSPACE/tools/sign-only"
|
"$GITHUB_WORKSPACE/tools/sign-only"
|
||||||
|
rm -f "$PRIVATE_KEY"
|
||||||
env:
|
env:
|
||||||
STSIGTOOL_PRIVATE_KEY: ${{ secrets.STSIGTOOL_PRIVATE_KEY }}
|
STSIGTOOL_PRIVATE_KEY: ${{ secrets.STSIGTOOL_PRIVATE_KEY }}
|
||||||
|
|
||||||
|
- name: Create and sign .asc files
|
||||||
|
run: |
|
||||||
|
sudo apt update
|
||||||
|
sudo apt -y install gnupg
|
||||||
|
|
||||||
|
export SIGNING_KEY="$RUNNER_TEMP/gpg-secret.asc"
|
||||||
|
echo "$GNUPG_SIGNING_KEY_BASE64" | base64 -d > "$SIGNING_KEY"
|
||||||
|
gpg --import < "$SIGNING_KEY"
|
||||||
|
|
||||||
|
pushd packages
|
||||||
|
files=(*.tar.gz *.zip)
|
||||||
|
sha1sum "${files[@]}" | gpg --clearsign > sha1sum.txt.asc
|
||||||
|
sha256sum "${files[@]}" | gpg --clearsign > sha256sum.txt.asc
|
||||||
|
gpg --sign --armour --detach syncthing-source-*.tar.gz
|
||||||
|
popd
|
||||||
|
rm -f "$SIGNING_KEY" .gnupg
|
||||||
|
env:
|
||||||
|
GNUPG_SIGNING_KEY_BASE64: ${{ secrets.GNUPG_SIGNING_KEY_BASE64 }}
|
||||||
|
|
||||||
- name: Archive artifacts
|
- name: Archive artifacts
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: packages-signed
|
name: packages-signed
|
||||||
path: packages/*
|
path: packages/*
|
||||||
|
@ -465,17 +560,22 @@ jobs:
|
||||||
|
|
||||||
package-debian:
|
package-debian:
|
||||||
name: Package for Debian
|
name: Package for Debian
|
||||||
needs:
|
|
||||||
- build-test
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- uses: actions/setup-go@v4
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ${{ env.GO_VERSION }}
|
go-version: ${{ env.GO_VERSION }}
|
||||||
|
cache: false
|
||||||
|
check-latest: true
|
||||||
|
|
||||||
|
- name: Get actual Go version
|
||||||
|
run: |
|
||||||
|
go version
|
||||||
|
echo "GO_VERSION=$(go version | sed 's#^.*go##;s# .*##')" >> $GITHUB_ENV
|
||||||
|
|
||||||
- uses: ruby/setup-ruby@v1
|
- uses: ruby/setup-ruby@v1
|
||||||
with:
|
with:
|
||||||
|
@ -485,7 +585,7 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
gem install fpm
|
gem install fpm
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
- uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.cache/go-build
|
~/.cache/go-build
|
||||||
|
@ -494,14 +594,14 @@ jobs:
|
||||||
|
|
||||||
- name: Package for Debian
|
- name: Package for Debian
|
||||||
run: |
|
run: |
|
||||||
for goarch in amd64 arm64 arm ; do
|
for arch in amd64 i386 armhf armel arm64 ; do
|
||||||
go run build.go -goos linux -goarch "$goarch" deb
|
go run build.go -no-upgrade -installsuffix=no-upgrade -goarch "$arch" deb
|
||||||
done
|
done
|
||||||
env:
|
env:
|
||||||
BUILD_USER: debian
|
BUILD_USER: debian
|
||||||
|
|
||||||
- name: Archive artifacts
|
- name: Archive artifacts
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: debian-packages
|
name: debian-packages
|
||||||
path: "*.deb"
|
path: "*.deb"
|
||||||
|
@ -512,25 +612,31 @@ jobs:
|
||||||
|
|
||||||
publish-nightly:
|
publish-nightly:
|
||||||
name: Publish nightly build
|
name: Publish nightly build
|
||||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/heads/release-nightly')
|
if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && startsWith(github.ref, 'refs/heads/release-nightly')
|
||||||
environment: signing
|
environment: signing
|
||||||
needs:
|
needs:
|
||||||
- sign-for-upgrade
|
- sign-for-upgrade
|
||||||
- notarize-macos
|
- notarize-macos
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: syncthing/release-tools
|
repository: syncthing/release-tools
|
||||||
path: tools
|
path: tools
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Download artifacts
|
- name: Download artifacts
|
||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: packages-signed
|
name: packages-signed
|
||||||
path: packages
|
path: packages
|
||||||
|
|
||||||
|
- uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: ${{ env.GO_VERSION }}
|
||||||
|
cache: false
|
||||||
|
check-latest: true
|
||||||
|
|
||||||
- name: Create release json
|
- name: Create release json
|
||||||
run: |
|
run: |
|
||||||
cd packages
|
cd packages
|
||||||
|
@ -549,3 +655,194 @@ jobs:
|
||||||
RCLONE_CONFIG_SPACES_ACL: public-read
|
RCLONE_CONFIG_SPACES_ACL: public-read
|
||||||
with:
|
with:
|
||||||
args: sync packages spaces:syncthing/nightly
|
args: sync packages spaces:syncthing/nightly
|
||||||
|
|
||||||
|
#
|
||||||
|
# Push release artifacts to Spaces
|
||||||
|
#
|
||||||
|
|
||||||
|
publish-release-files:
|
||||||
|
name: Publish release files
|
||||||
|
if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/release'
|
||||||
|
environment: signing
|
||||||
|
needs:
|
||||||
|
- sign-for-upgrade
|
||||||
|
- package-debian
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Download signed packages
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: packages-signed
|
||||||
|
path: packages
|
||||||
|
|
||||||
|
- name: Download debian packages
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: debian-packages
|
||||||
|
path: packages
|
||||||
|
|
||||||
|
- uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: ${{ env.GO_VERSION }}
|
||||||
|
cache: false
|
||||||
|
check-latest: true
|
||||||
|
|
||||||
|
- name: Set version
|
||||||
|
run: |
|
||||||
|
version=$(go run build.go version)
|
||||||
|
echo "VERSION=$version" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Push to Spaces (${{ env.VERSION }})
|
||||||
|
uses: docker://docker.io/rclone/rclone:latest
|
||||||
|
env:
|
||||||
|
RCLONE_CONFIG_SPACES_TYPE: s3
|
||||||
|
RCLONE_CONFIG_SPACES_PROVIDER: DigitalOcean
|
||||||
|
RCLONE_CONFIG_SPACES_ACCESS_KEY_ID: ${{ secrets.SPACES_KEY }}
|
||||||
|
RCLONE_CONFIG_SPACES_SECRET_ACCESS_KEY: ${{ secrets.SPACES_SECRET }}
|
||||||
|
RCLONE_CONFIG_SPACES_ENDPOINT: ams3.digitaloceanspaces.com
|
||||||
|
RCLONE_CONFIG_SPACES_ACL: public-read
|
||||||
|
with:
|
||||||
|
args: sync packages spaces:syncthing/release/${{ env.VERSION }}
|
||||||
|
|
||||||
|
- name: Push to Spaces (latest)
|
||||||
|
uses: docker://docker.io/rclone/rclone:latest
|
||||||
|
env:
|
||||||
|
RCLONE_CONFIG_SPACES_TYPE: s3
|
||||||
|
RCLONE_CONFIG_SPACES_PROVIDER: DigitalOcean
|
||||||
|
RCLONE_CONFIG_SPACES_ACCESS_KEY_ID: ${{ secrets.SPACES_KEY }}
|
||||||
|
RCLONE_CONFIG_SPACES_SECRET_ACCESS_KEY: ${{ secrets.SPACES_SECRET }}
|
||||||
|
RCLONE_CONFIG_SPACES_ENDPOINT: ams3.digitaloceanspaces.com
|
||||||
|
RCLONE_CONFIG_SPACES_ACL: public-read
|
||||||
|
with:
|
||||||
|
args: sync spaces:syncthing/release/${{ env.VERSION }} spaces:syncthing/release/latest
|
||||||
|
|
||||||
|
#
|
||||||
|
# Build and push to Docker Hub
|
||||||
|
#
|
||||||
|
|
||||||
|
docker-syncthing:
|
||||||
|
name: Build and push Docker images
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release' || github.ref == 'refs/heads/main' || github.ref == 'refs/heads/infrastructure' || startsWith(github.ref, 'refs/heads/release-'))
|
||||||
|
environment: docker
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
pkg:
|
||||||
|
- syncthing
|
||||||
|
- strelaysrv
|
||||||
|
- stdiscosrv
|
||||||
|
include:
|
||||||
|
- pkg: syncthing
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
image: syncthing/syncthing
|
||||||
|
- pkg: strelaysrv
|
||||||
|
dockerfile: Dockerfile.strelaysrv
|
||||||
|
image: syncthing/relaysrv
|
||||||
|
- pkg: stdiscosrv
|
||||||
|
dockerfile: Dockerfile.stdiscosrv
|
||||||
|
image: syncthing/discosrv
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: ${{ env.GO_VERSION }}
|
||||||
|
cache: false
|
||||||
|
check-latest: true
|
||||||
|
|
||||||
|
- name: Get actual Go version
|
||||||
|
run: |
|
||||||
|
go version
|
||||||
|
echo "GO_VERSION=$(go version | sed 's#^.*go##;s# .*##')" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cache/go-build
|
||||||
|
~/go/pkg/mod
|
||||||
|
key: ${{ runner.os }}-go-${{ env.GO_VERSION }}-docker-${{ matrix.pkg }}-${{ hashFiles('**/go.sum') }}
|
||||||
|
|
||||||
|
- name: Build binaries
|
||||||
|
run: |
|
||||||
|
for arch in amd64 arm64 arm; do
|
||||||
|
go run build.go -goos linux -goarch "$arch" -no-upgrade build ${{ matrix.pkg }}
|
||||||
|
mv ${{ matrix.pkg }} ${{ matrix.pkg }}-linux-"$arch"
|
||||||
|
done
|
||||||
|
env:
|
||||||
|
CGO_ENABLED: "0"
|
||||||
|
BUILD_USER: docker
|
||||||
|
|
||||||
|
- name: Check if we will be able to push images
|
||||||
|
run: |
|
||||||
|
if [[ "${{ secrets.DOCKERHUB_TOKEN }}" != "" ]]; then
|
||||||
|
echo "DOCKER_PUSH=true" >> $GITHUB_ENV;
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
if: env.DOCKER_PUSH == 'true'
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Set version tags
|
||||||
|
run: |
|
||||||
|
version=$(go run build.go version)
|
||||||
|
version=${version#v}
|
||||||
|
if [[ $version == @([0-9]|[0-9][0-9]).@([0-9]|[0-9][0-9]).@([0-9]|[0-9][0-9]) ]] ; then
|
||||||
|
echo Release version, pushing to :latest and version tags
|
||||||
|
major=${version%.*.*}
|
||||||
|
minor=${version%.*}
|
||||||
|
tags=${{ matrix.image }}:$version,${{ matrix.image }}:$major,${{ matrix.image }}:$minor,${{ matrix.image }}:latest
|
||||||
|
elif [[ $version == *-rc.@([0-9]|[0-9][0-9]) ]] ; then
|
||||||
|
echo Release candidate, pushing to :rc
|
||||||
|
tags=${{ matrix.image }}:rc
|
||||||
|
else
|
||||||
|
echo Development version, pushing to :edge
|
||||||
|
tags=${{ matrix.image }}:edge
|
||||||
|
fi
|
||||||
|
echo "DOCKER_TAGS=$tags" >> $GITHUB_ENV
|
||||||
|
echo "VERSION=$version" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Build and push Docker image
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ${{ matrix.dockerfile }}
|
||||||
|
platforms: linux/amd64,linux/arm64,linux/arm/7
|
||||||
|
push: ${{ env.DOCKER_PUSH == 'true' }}
|
||||||
|
tags: ${{ env.DOCKER_TAGS }}
|
||||||
|
labels: |
|
||||||
|
org.opencontainers.image.version=${{ env.VERSION }}
|
||||||
|
org.opencontainers.image.revision=${{ github.sha }}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Check for known vulnerabilities in Go dependencies
|
||||||
|
#
|
||||||
|
|
||||||
|
govulncheck:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: Run govulncheck
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: ${{ env.GO_VERSION }}
|
||||||
|
cache: false
|
||||||
|
check-latest: true
|
||||||
|
|
||||||
|
- name: run govulncheck
|
||||||
|
run: |
|
||||||
|
go run build.go assets
|
||||||
|
go install golang.org/x/vuln/cmd/govulncheck@latest
|
||||||
|
govulncheck ./...
|
||||||
|
|
|
@ -12,7 +12,7 @@ jobs:
|
||||||
name: Push to release-nightly to trigger build
|
name: Push to release-nightly to trigger build
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.ACTIONS_GITHUB_TOKEN }}
|
token: ${{ secrets.ACTIONS_GITHUB_TOKEN }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
|
@ -10,13 +10,13 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
name: Update translations and documentation
|
name: Update translations and documentation
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
token: ${{ secrets.ACTIONS_GITHUB_TOKEN }}
|
token: ${{ secrets.ACTIONS_GITHUB_TOKEN }}
|
||||||
- uses: actions/setup-go@v4
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.19.6
|
go-version: stable
|
||||||
- run: |
|
- run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
git config --global user.name 'Syncthing Release Automation'
|
git config --global user.name 'Syncthing Release Automation'
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
/syncthing
|
/syncthing
|
||||||
/stdiscosrv
|
/stdiscosrv
|
||||||
syncthing.exe
|
|
||||||
stdiscosrv.exe
|
|
||||||
*.tar.gz
|
*.tar.gz
|
||||||
*.zip
|
*.zip
|
||||||
*.asc
|
*.asc
|
||||||
*.deb
|
*.deb
|
||||||
|
*.exe
|
||||||
.jshintrc
|
.jshintrc
|
||||||
coverage.out
|
coverage.out
|
||||||
files/pidx
|
files/pidx
|
||||||
|
@ -19,4 +18,3 @@ deb
|
||||||
/repos
|
/repos
|
||||||
/proto/scripts/protoc-gen-gosyncthing
|
/proto/scripts/protoc-gen-gosyncthing
|
||||||
/gui/next-gen-gui
|
/gui/next-gen-gui
|
||||||
.idea
|
|
||||||
|
|
36
AUTHORS
36
AUTHORS
|
@ -23,9 +23,11 @@ Alessandro G. (alessandro.g89) <alessandro.g89@gmail.com>
|
||||||
Alex Lindeman <139387+aelindeman@users.noreply.github.com>
|
Alex Lindeman <139387+aelindeman@users.noreply.github.com>
|
||||||
Alex Xu <alex.hello71@gmail.com>
|
Alex Xu <alex.hello71@gmail.com>
|
||||||
Alexander Graf (alex2108) <register-github@alex-graf.de>
|
Alexander Graf (alex2108) <register-github@alex-graf.de>
|
||||||
|
Alexander Seiler <seileralex@gmail.com>
|
||||||
Alexandre Alves <alexandrealvesdb.contact@gmail.com>
|
Alexandre Alves <alexandrealvesdb.contact@gmail.com>
|
||||||
Alexandre Viau (aviau) <alexandre@alexandreviau.net> <aviau@debian.org>
|
Alexandre Viau (aviau) <alexandre@alexandreviau.net> <aviau@debian.org>
|
||||||
Aman Gupta <aman@tmm1.net>
|
Aman Gupta <aman@tmm1.net>
|
||||||
|
Anatoli Babenia <anatoli@rainforce.org>
|
||||||
Anderson Mesquita (andersonvom) <andersonvom@gmail.com>
|
Anderson Mesquita (andersonvom) <andersonvom@gmail.com>
|
||||||
Andreas Sommer <andreas.sommer87@googlemail.com>
|
Andreas Sommer <andreas.sommer87@googlemail.com>
|
||||||
andresvia <andres.via@gmail.com>
|
andresvia <andres.via@gmail.com>
|
||||||
|
@ -49,6 +51,7 @@ Audrius Butkevicius (AudriusButkevicius) <audrius.butkevicius@gmail.com> <github
|
||||||
Aurélien Rainone <476650+arl@users.noreply.github.com>
|
Aurélien Rainone <476650+arl@users.noreply.github.com>
|
||||||
BAHADIR YILMAZ <bahadiryilmaz32@gmail.com>
|
BAHADIR YILMAZ <bahadiryilmaz32@gmail.com>
|
||||||
Bart De Vries (mogwa1) <devriesb@gmail.com>
|
Bart De Vries (mogwa1) <devriesb@gmail.com>
|
||||||
|
Beat Reichenbach <44111292+beatreichenbach@users.noreply.github.com>
|
||||||
Ben Curthoys (bencurthoys) <ben@bencurthoys.com>
|
Ben Curthoys (bencurthoys) <ben@bencurthoys.com>
|
||||||
Ben Schulz (uok) <ueomkail@gmail.com> <uok@users.noreply.github.com>
|
Ben Schulz (uok) <ueomkail@gmail.com> <uok@users.noreply.github.com>
|
||||||
Ben Shepherd (benshep) <bjashepherd@gmail.com>
|
Ben Shepherd (benshep) <bjashepherd@gmail.com>
|
||||||
|
@ -67,36 +70,45 @@ Brian R. Becker (brbecker) <brbecker@gmail.com>
|
||||||
bt90 <btom1990@googlemail.com>
|
bt90 <btom1990@googlemail.com>
|
||||||
Caleb Callaway (cqcallaw) <enlightened.despot@gmail.com>
|
Caleb Callaway (cqcallaw) <enlightened.despot@gmail.com>
|
||||||
Carsten Hagemann (carstenhag) <moter8@gmail.com> <carsten@chagemann.de>
|
Carsten Hagemann (carstenhag) <moter8@gmail.com> <carsten@chagemann.de>
|
||||||
|
Catfriend1 <16361913+Catfriend1@users.noreply.github.com>
|
||||||
Cathryne Linenweaver (Cathryne) <cathryne.linenweaver@gmail.com> <Cathryne@users.noreply.github.com> <katrinleinweber@MAC.local>
|
Cathryne Linenweaver (Cathryne) <cathryne.linenweaver@gmail.com> <Cathryne@users.noreply.github.com> <katrinleinweber@MAC.local>
|
||||||
Cedric Staniewski (xduugu) <cedric@gmx.ca>
|
Cedric Staniewski (xduugu) <cedric@gmx.ca>
|
||||||
chenrui <rui@meetup.com>
|
chenrui <rui@meetup.com>
|
||||||
Chih-Hsuan Yen <yan12125@gmail.com>
|
Chih-Hsuan Yen <yan12125@gmail.com> <1937689+yan12125@users.noreply.github.com>
|
||||||
Choongkyu <choongkyu.kim+gh@gmail.com> <vapidlyrapid+gh@gmail.com>
|
Choongkyu <choongkyu.kim+gh@gmail.com> <vapidlyrapid+gh@gmail.com>
|
||||||
Chris Howie (cdhowie) <me@chrishowie.com>
|
Chris Howie (cdhowie) <me@chrishowie.com>
|
||||||
Chris Joel (cdata) <chris@scriptolo.gy>
|
Chris Joel (cdata) <chris@scriptolo.gy>
|
||||||
Chris Tonkinson <chris@masterbran.ch>
|
Chris Tonkinson <chris@masterbran.ch>
|
||||||
|
Christian Kujau <ckujau@users.noreply.github.com>
|
||||||
Christian Prescott <me@christianprescott.com>
|
Christian Prescott <me@christianprescott.com>
|
||||||
chucic <chucic@seznam.cz>
|
chucic <chucic@seznam.cz>
|
||||||
|
cjc7373 <niuchangcun@gmail.com>
|
||||||
Colin Kennedy (moshen) <moshen.colin@gmail.com>
|
Colin Kennedy (moshen) <moshen.colin@gmail.com>
|
||||||
Cromefire_ <tim.l@nghorst.net> <26320625+cromefire@users.noreply.github.com>
|
Cromefire_ <tim.l@nghorst.net> <26320625+cromefire@users.noreply.github.com>
|
||||||
cui fliter <imcusg@gmail.com>
|
cui fliter <imcusg@gmail.com>
|
||||||
Cyprien Devillez <cypx@users.noreply.github.com>
|
Cyprien Devillez <cypx@users.noreply.github.com>
|
||||||
|
d-volution <49024624+d-volution@users.noreply.github.com>
|
||||||
Dale Visser <dale.visser@live.com>
|
Dale Visser <dale.visser@live.com>
|
||||||
Dan <benda.daniel@gmail.com>
|
Dan <benda.daniel@gmail.com>
|
||||||
Daniel Barczyk <46358936+DanielBarczyk@users.noreply.github.com>
|
Daniel Barczyk <46358936+DanielBarczyk@users.noreply.github.com>
|
||||||
Daniel Bergmann (brgmnn) <dan.arne.bergmann@gmail.com> <brgmnn@users.noreply.github.com>
|
Daniel Bergmann (brgmnn) <dan.arne.bergmann@gmail.com> <brgmnn@users.noreply.github.com>
|
||||||
Daniel Harte (norgeous) <daniel@harte.me> <daniel@danielharte.co.uk> <norgeous@users.noreply.github.com>
|
Daniel Harte (norgeous) <daniel@harte.me> <daniel@danielharte.co.uk> <norgeous@users.noreply.github.com>
|
||||||
Daniel Martí (mvdan) <mvdan@mvdan.cc>
|
Daniel Martí (mvdan) <mvdan@mvdan.cc>
|
||||||
|
Daniel Padrta <64928366+danpadcz@users.noreply.github.com>
|
||||||
Darshil Chanpura (dtchanpura) <dtchanpura@gmail.com> <dcprime314@gmail.com>
|
Darshil Chanpura (dtchanpura) <dtchanpura@gmail.com> <dcprime314@gmail.com>
|
||||||
David Rimmer (dinosore) <dinosore@dbrsoftware.co.uk>
|
David Rimmer (dinosore) <dinosore@dbrsoftware.co.uk>
|
||||||
deepsource-autofix[bot] <62050782+deepsource-autofix[bot]@users.noreply.github.com>
|
deepsource-autofix[bot] <62050782+deepsource-autofix[bot]@users.noreply.github.com>
|
||||||
|
DeflateAwning <11021263+DeflateAwning@users.noreply.github.com>
|
||||||
Denis A. (dva) <denisva@gmail.com>
|
Denis A. (dva) <denisva@gmail.com>
|
||||||
Dennis Wilson (snnd) <dw@risu.io>
|
Dennis Wilson (snnd) <dw@risu.io>
|
||||||
dependabot-preview[bot] <dependabot-preview[bot]@users.noreply.github.com> <27856297+dependabot-preview[bot]@users.noreply.github.com>
|
dependabot-preview[bot] <dependabot-preview[bot]@users.noreply.github.com> <27856297+dependabot-preview[bot]@users.noreply.github.com>
|
||||||
dependabot[bot] <dependabot[bot]@users.noreply.github.com> <49699333+dependabot[bot]@users.noreply.github.com>
|
dependabot[bot] <dependabot[bot]@users.noreply.github.com> <49699333+dependabot[bot]@users.noreply.github.com>
|
||||||
derekriemer <derek.riemer@colorado.edu>
|
derekriemer <derek.riemer@colorado.edu>
|
||||||
|
DerRockWolf <50499906+DerRockWolf@users.noreply.github.com>
|
||||||
desbma <desbma@users.noreply.github.com>
|
desbma <desbma@users.noreply.github.com>
|
||||||
Devon G. Redekopp <devon@redekopp.com>
|
Devon G. Redekopp <devon@redekopp.com>
|
||||||
|
diemade <spamkill@posteo.ch>
|
||||||
|
digital <didev@dinid.net>
|
||||||
Dimitri Papadopoulos Orfanos <3234522+DimitriPapadopoulos@users.noreply.github.com>
|
Dimitri Papadopoulos Orfanos <3234522+DimitriPapadopoulos@users.noreply.github.com>
|
||||||
Dmitry Saveliev (dsaveliev) <d.e.saveliev@gmail.com>
|
Dmitry Saveliev (dsaveliev) <d.e.saveliev@gmail.com>
|
||||||
Domenic Horner <domenic@tgxn.net>
|
Domenic Horner <domenic@tgxn.net>
|
||||||
|
@ -104,6 +116,7 @@ Dominik Heidler (asdil12) <dominik@heidler.eu>
|
||||||
Elias Jarlebring (jarlebring) <jarlebring@gmail.com>
|
Elias Jarlebring (jarlebring) <jarlebring@gmail.com>
|
||||||
Elliot Huffman <thelich2@gmail.com>
|
Elliot Huffman <thelich2@gmail.com>
|
||||||
Emil Hessman (ceh) <emil@hessman.se>
|
Emil Hessman (ceh) <emil@hessman.se>
|
||||||
|
Emil Lundberg <emil@emlun.se>
|
||||||
Eng Zer Jun <engzerjun@gmail.com>
|
Eng Zer Jun <engzerjun@gmail.com>
|
||||||
entity0xfe <109791748+entity0xfe@users.noreply.github.com> <entity0xfe@my.domain>
|
entity0xfe <109791748+entity0xfe@users.noreply.github.com> <entity0xfe@my.domain>
|
||||||
Eric Lesiuta <elesiuta@gmail.com>
|
Eric Lesiuta <elesiuta@gmail.com>
|
||||||
|
@ -112,6 +125,7 @@ Erik Meitner (WSGCSysadmin) <e.meitner@willystreet.coop>
|
||||||
Evan Spensley <94762716+0evan@users.noreply.github.com>
|
Evan Spensley <94762716+0evan@users.noreply.github.com>
|
||||||
Evgeny Kuznetsov <evgeny@kuznetsov.md>
|
Evgeny Kuznetsov <evgeny@kuznetsov.md>
|
||||||
Federico Castagnini (facastagnini) <federico.castagnini@gmail.com>
|
Federico Castagnini (facastagnini) <federico.castagnini@gmail.com>
|
||||||
|
Felix <53702818+f-eliks@users.noreply.github.com>
|
||||||
Felix Ableitner (Nutomic) <me@nutomic.com>
|
Felix Ableitner (Nutomic) <me@nutomic.com>
|
||||||
Felix Lampe <mail@flampe.de>
|
Felix Lampe <mail@flampe.de>
|
||||||
Felix Unterpaintner (bigbear2nd) <bigbear2nd@gmail.com>
|
Felix Unterpaintner (bigbear2nd) <bigbear2nd@gmail.com>
|
||||||
|
@ -125,6 +139,8 @@ Gleb Sinyavskiy <zhulik.gleb@gmail.com>
|
||||||
Graham Miln (grahammiln) <graham.miln@dssw.co.uk> <graham.miln@miln.eu>
|
Graham Miln (grahammiln) <graham.miln@dssw.co.uk> <graham.miln@miln.eu>
|
||||||
greatroar <61184462+greatroar@users.noreply.github.com>
|
greatroar <61184462+greatroar@users.noreply.github.com>
|
||||||
Greg <gco@jazzhaiku.com>
|
Greg <gco@jazzhaiku.com>
|
||||||
|
guangwu <guoguangwu@magic-shield.com>
|
||||||
|
gudvinr <gudvinr@gmail.com>
|
||||||
Han Boetes <han@boetes.org>
|
Han Boetes <han@boetes.org>
|
||||||
HansK-p <42314815+HansK-p@users.noreply.github.com>
|
HansK-p <42314815+HansK-p@users.noreply.github.com>
|
||||||
Harrison Jones (harrisonhjones) <harrisonhjones@users.noreply.github.com>
|
Harrison Jones (harrisonhjones) <harrisonhjones@users.noreply.github.com>
|
||||||
|
@ -141,13 +157,14 @@ Jacek Szafarkiewicz (hadogenes) <szafar@linux.pl>
|
||||||
Jack Croft <jccroft1@users.noreply.github.com>
|
Jack Croft <jccroft1@users.noreply.github.com>
|
||||||
Jacob <jyundt@gmail.com>
|
Jacob <jyundt@gmail.com>
|
||||||
Jake Peterson (acogdev) <jake@acogdev.com>
|
Jake Peterson (acogdev) <jake@acogdev.com>
|
||||||
Jakob Borg (calmh) <jakob@nym.se> <jakob@kastelo.net>
|
Jakob Borg (calmh) <jakob@nym.se> <jakob@kastelo.net> <jborg@coreweave.com>
|
||||||
James O'Beirne <wild-github@au92.org>
|
James O'Beirne <wild-github@au92.org>
|
||||||
James Patterson (jpjp) <jamespatterson@operamail.com> <jpjp@users.noreply.github.com>
|
James Patterson (jpjp) <jamespatterson@operamail.com> <jpjp@users.noreply.github.com>
|
||||||
janost <janost@tuta.io>
|
janost <janost@tuta.io>
|
||||||
Jaroslav Lichtblau <svetlemodry@users.noreply.github.com>
|
Jaroslav Lichtblau <svetlemodry@users.noreply.github.com>
|
||||||
Jaroslav Malec (dzarda) <dzardacz@gmail.com>
|
Jaroslav Malec (dzarda) <dzardacz@gmail.com>
|
||||||
jaseg <githubaccount@jaseg.net>
|
jaseg <githubaccount@jaseg.net>
|
||||||
|
Jaspitta <ste.scarpitta@gmail.com>
|
||||||
Jauder Ho <jauderho@users.noreply.github.com>
|
Jauder Ho <jauderho@users.noreply.github.com>
|
||||||
Jaya Chithra (jayachithra) <s.k.jayachithra@gmail.com>
|
Jaya Chithra (jayachithra) <s.k.jayachithra@gmail.com>
|
||||||
Jaya Kumar <jaya.kumar@ict.nl>
|
Jaya Kumar <jaya.kumar@ict.nl>
|
||||||
|
@ -166,12 +183,14 @@ Jonathan Cross <jcross@gmail.com>
|
||||||
Jonta <359397+Jonta@users.noreply.github.com>
|
Jonta <359397+Jonta@users.noreply.github.com>
|
||||||
Jose Manuel Delicado (jmdaweb) <jmdaweb@hotmail.com> <jmdaweb@users.noreply.github.com>
|
Jose Manuel Delicado (jmdaweb) <jmdaweb@hotmail.com> <jmdaweb@users.noreply.github.com>
|
||||||
jtagcat <git-514635f7@jtag.cat> <git-12dbd862@jtag.cat>
|
jtagcat <git-514635f7@jtag.cat> <git-12dbd862@jtag.cat>
|
||||||
|
Julian Lehrhuber <jul13579@users.noreply.github.com>
|
||||||
Jörg Thalheim <Mic92@users.noreply.github.com>
|
Jörg Thalheim <Mic92@users.noreply.github.com>
|
||||||
Jędrzej Kula <kula.jedrek@gmail.com>
|
Jędrzej Kula <kula.jedrek@gmail.com>
|
||||||
K.B.Dharun Krishna <kbdharunkrishna@gmail.com>
|
K.B.Dharun Krishna <kbdharunkrishna@gmail.com>
|
||||||
Kalle Laine <pahakalle@protonmail.com>
|
Kalle Laine <pahakalle@protonmail.com>
|
||||||
Karol Różycki (krozycki) <rozycki.karol@gmail.com>
|
Karol Różycki (krozycki) <rozycki.karol@gmail.com>
|
||||||
Kebin Liu <lkebin@gmail.com>
|
Kebin Liu <lkebin@gmail.com>
|
||||||
|
Keith Harrison <keithh@protonmail.com>
|
||||||
Keith Turner <kturner@apache.org>
|
Keith Turner <kturner@apache.org>
|
||||||
Kelong Cong (kc1212) <kc04bc@gmx.com> <kc1212@users.noreply.github.com>
|
Kelong Cong (kc1212) <kc04bc@gmx.com> <kc1212@users.noreply.github.com>
|
||||||
Ken'ichi Kamada (kamadak) <kamada@nanohz.org>
|
Ken'ichi Kamada (kamadak) <kamada@nanohz.org>
|
||||||
|
@ -180,6 +199,7 @@ Kevin Bushiri (keevBush) <keevbush@gmail.com> <36192217+keevBush@users.noreply.g
|
||||||
Kevin White, Jr. (kwhite17) <kevinwhite1710@gmail.com>
|
Kevin White, Jr. (kwhite17) <kevinwhite1710@gmail.com>
|
||||||
klemens <ka7@github.com>
|
klemens <ka7@github.com>
|
||||||
Kurt Fitzner (Kudalufi) <kurt@va1der.ca> <kurt.fitzner@gmail.com>
|
Kurt Fitzner (Kudalufi) <kurt@va1der.ca> <kurt.fitzner@gmail.com>
|
||||||
|
kylosus <33132401+kylosus@users.noreply.github.com>
|
||||||
Lars K.W. Gohlke (lkwg82) <lkwg82@gmx.de>
|
Lars K.W. Gohlke (lkwg82) <lkwg82@gmx.de>
|
||||||
Lars Lehtonen <lars.lehtonen@gmail.com>
|
Lars Lehtonen <lars.lehtonen@gmail.com>
|
||||||
Laurent Arnoud <laurent@spkdev.net>
|
Laurent Arnoud <laurent@spkdev.net>
|
||||||
|
@ -190,6 +210,7 @@ Lode Hoste (Zillode) <zillode@zillode.be>
|
||||||
Lord Landon Agahnim (LordLandon) <lordlandon@gmail.com>
|
Lord Landon Agahnim (LordLandon) <lordlandon@gmail.com>
|
||||||
LSmithx2 <42276854+lsmithx2@users.noreply.github.com>
|
LSmithx2 <42276854+lsmithx2@users.noreply.github.com>
|
||||||
Lukas Lihotzki <lukas@lihotzki.de>
|
Lukas Lihotzki <lukas@lihotzki.de>
|
||||||
|
Luke Hamburg <1992842+luckman212@users.noreply.github.com>
|
||||||
luzpaz <luzpaz@users.noreply.github.com>
|
luzpaz <luzpaz@users.noreply.github.com>
|
||||||
Majed Abdulaziz (majedev) <majed.alhajry@gmail.com>
|
Majed Abdulaziz (majedev) <majed.alhajry@gmail.com>
|
||||||
Marc Laporte (marclaporte) <marc@marclaporte.com> <marc@laporte.name>
|
Marc Laporte (marclaporte) <marc@marclaporte.com> <marc@laporte.name>
|
||||||
|
@ -200,6 +221,7 @@ Marcus Legendre <marcus.legendre@gmail.com>
|
||||||
Mario Majila <mariustshipichik@gmail.com>
|
Mario Majila <mariustshipichik@gmail.com>
|
||||||
Mark Pulford (mpx) <mark@kyne.com.au>
|
Mark Pulford (mpx) <mark@kyne.com.au>
|
||||||
Martchus <martchus@gmx.net>
|
Martchus <martchus@gmx.net>
|
||||||
|
Martin Polehla <p0l0us@users.noreply.github.com>
|
||||||
Mateusz Naściszewski (mateon1) <matin1111@wp.pl>
|
Mateusz Naściszewski (mateon1) <matin1111@wp.pl>
|
||||||
Mateusz Ż <thedead4fun@live.com>
|
Mateusz Ż <thedead4fun@live.com>
|
||||||
Matic Potočnik <hairyfotr@gmail.com>
|
Matic Potočnik <hairyfotr@gmail.com>
|
||||||
|
@ -211,12 +233,14 @@ Max <github@germancoding.com>
|
||||||
Max Schulze (kralo) <max.schulze@online.de> <kralo@users.noreply.github.com>
|
Max Schulze (kralo) <max.schulze@online.de> <kralo@users.noreply.github.com>
|
||||||
MaximAL <almaximal@ya.ru>
|
MaximAL <almaximal@ya.ru>
|
||||||
Maxime Thirouin <m@moox.io>
|
Maxime Thirouin <m@moox.io>
|
||||||
|
Maximilian <maxi.rostock@outlook.de> <public@complexvector.space>
|
||||||
mclang <1721600+mclang@users.noreply.github.com>
|
mclang <1721600+mclang@users.noreply.github.com>
|
||||||
Michael Jephcote (Rewt0r) <rewt0r@gmx.com> <Rewt0r@users.noreply.github.com>
|
Michael Jephcote (Rewt0r) <rewt0r@gmx.com> <Rewt0r@users.noreply.github.com>
|
||||||
Michael Ploujnikov (plouj) <ploujj@gmail.com>
|
Michael Ploujnikov (plouj) <ploujj@gmail.com>
|
||||||
Michael Rienstra <mrienstra@gmail.com>
|
Michael Rienstra <mrienstra@gmail.com>
|
||||||
Michael Tilli (pyfisch) <pyfisch@gmail.com>
|
Michael Tilli (pyfisch) <pyfisch@gmail.com>
|
||||||
MichaIng <micha@dietpi.com>
|
MichaIng <micha@dietpi.com>
|
||||||
|
Migelo <miha@filetki.si>
|
||||||
Mike Boone <mike@boonedocks.net>
|
Mike Boone <mike@boonedocks.net>
|
||||||
MikeLund <MikeLund@users.noreply.github.com>
|
MikeLund <MikeLund@users.noreply.github.com>
|
||||||
MikolajTwarog <43782609+MikolajTwarog@users.noreply.github.com>
|
MikolajTwarog <43782609+MikolajTwarog@users.noreply.github.com>
|
||||||
|
@ -224,6 +248,7 @@ Mingxuan Lin <gdlmx@users.noreply.github.com>
|
||||||
mv1005 <49659413+mv1005@users.noreply.github.com>
|
mv1005 <49659413+mv1005@users.noreply.github.com>
|
||||||
Nate Morrison (nrm21) <natemorrison@gmail.com>
|
Nate Morrison (nrm21) <natemorrison@gmail.com>
|
||||||
Naveen <172697+naveensrinivasan@users.noreply.github.com>
|
Naveen <172697+naveensrinivasan@users.noreply.github.com>
|
||||||
|
nf <nf@wh3rd.net>
|
||||||
Nicholas Rishel (PrototypeNM1) <rishel.nick@gmail.com> <PrototypeNM1@users.noreply.github.com>
|
Nicholas Rishel (PrototypeNM1) <rishel.nick@gmail.com> <PrototypeNM1@users.noreply.github.com>
|
||||||
Nick Busey <NickBusey@users.noreply.github.com>
|
Nick Busey <NickBusey@users.noreply.github.com>
|
||||||
Nico Stapelbroek <3368018+nstapelbroek@users.noreply.github.com>
|
Nico Stapelbroek <3368018+nstapelbroek@users.noreply.github.com>
|
||||||
|
@ -235,6 +260,7 @@ NinoM4ster <ninom4ster@gmail.com>
|
||||||
Nitroretro <43112364+Nitroretro@users.noreply.github.com>
|
Nitroretro <43112364+Nitroretro@users.noreply.github.com>
|
||||||
NoLooseEnds <jon.koslung@gmail.com>
|
NoLooseEnds <jon.koslung@gmail.com>
|
||||||
Oliver Freyermuth <o.freyermuth@googlemail.com>
|
Oliver Freyermuth <o.freyermuth@googlemail.com>
|
||||||
|
orangekame3 <miya.org.0309@gmail.com>
|
||||||
otbutz <tbutz@optitool.de>
|
otbutz <tbutz@optitool.de>
|
||||||
Otiel <Otiel@users.noreply.github.com>
|
Otiel <Otiel@users.noreply.github.com>
|
||||||
overkill <22098433+0verk1ll@users.noreply.github.com>
|
overkill <22098433+0verk1ll@users.noreply.github.com>
|
||||||
|
@ -273,6 +299,7 @@ Sacheendra Talluri (sacheendra) <sacheendra.t@gmail.com>
|
||||||
Scott Klupfel (kluppy) <kluppy@going2blue.com>
|
Scott Klupfel (kluppy) <kluppy@going2blue.com>
|
||||||
sec65 <106604020+sec65@users.noreply.github.com>
|
sec65 <106604020+sec65@users.noreply.github.com>
|
||||||
Sergey Mishin (ralder) <ralder@yandex.ru>
|
Sergey Mishin (ralder) <ralder@yandex.ru>
|
||||||
|
Sertonix <83883937+Sertonix@users.noreply.github.com>
|
||||||
Shaarad Dalvi <60266155+shaaraddalvi@users.noreply.github.com> <shdalv@microsoft.com>
|
Shaarad Dalvi <60266155+shaaraddalvi@users.noreply.github.com> <shdalv@microsoft.com>
|
||||||
Simon Frei (imsodin) <freisim93@gmail.com>
|
Simon Frei (imsodin) <freisim93@gmail.com>
|
||||||
Simon Mwepu <simonmwepu@gmail.com>
|
Simon Mwepu <simonmwepu@gmail.com>
|
||||||
|
@ -281,12 +308,15 @@ Stefan Kuntz (Stefan-Code) <stefan.github@gmail.com> <Stefan.github@gmail.com>
|
||||||
Stefan Tatschner (rumpelsepp) <stefan@sevenbyte.org> <rumpelsepp@sevenbyte.org> <stefan@rumpelsepp.org>
|
Stefan Tatschner (rumpelsepp) <stefan@sevenbyte.org> <rumpelsepp@sevenbyte.org> <stefan@rumpelsepp.org>
|
||||||
Steven Eckhoff <steven.eckhoff.opensource@gmail.com>
|
Steven Eckhoff <steven.eckhoff.opensource@gmail.com>
|
||||||
Suhas Gundimeda (snugghash) <suhas.gundimeda@gmail.com> <snugghash@gmail.com>
|
Suhas Gundimeda (snugghash) <suhas.gundimeda@gmail.com> <snugghash@gmail.com>
|
||||||
|
Sven Bachmann <dev@mcbachmann.de>
|
||||||
Syncthing Automation <automation@syncthing.net>
|
Syncthing Automation <automation@syncthing.net>
|
||||||
Syncthing Release Automation <release@syncthing.net>
|
Syncthing Release Automation <release@syncthing.net>
|
||||||
Taylor Khan (nelsonkhan) <nelsonkhan@gmail.com>
|
Taylor Khan (nelsonkhan) <nelsonkhan@gmail.com>
|
||||||
|
Thomas <9749173+uhthomas@users.noreply.github.com>
|
||||||
Thomas Hipp <thomashipp@gmail.com>
|
Thomas Hipp <thomashipp@gmail.com>
|
||||||
Tim Abell (timabell) <tim@timwise.co.uk>
|
Tim Abell (timabell) <tim@timwise.co.uk>
|
||||||
Tim Howes (timhowes) <timhowes@berkeley.edu>
|
Tim Howes (timhowes) <timhowes@berkeley.edu>
|
||||||
|
Tim Nordenfur <tim@gurka.se>
|
||||||
Tobias Klauser <tobias.klauser@gmail.com>
|
Tobias Klauser <tobias.klauser@gmail.com>
|
||||||
Tobias Nygren (tnn2) <tnn@nygren.pp.se>
|
Tobias Nygren (tnn2) <tnn@nygren.pp.se>
|
||||||
Tobias Tom (tobiastom) <t.tom@succont.de>
|
Tobias Tom (tobiastom) <t.tom@succont.de>
|
||||||
|
@ -297,6 +327,7 @@ Tully Robinson (tojrobinson) <tully@tojr.org>
|
||||||
Tyler Brazier (tylerbrazier) <tyler@tylerbrazier.com>
|
Tyler Brazier (tylerbrazier) <tyler@tylerbrazier.com>
|
||||||
Tyler Kropp <kropptyler@gmail.com>
|
Tyler Kropp <kropptyler@gmail.com>
|
||||||
Unrud (Unrud) <unrud@openaliasbox.org> <Unrud@users.noreply.github.com>
|
Unrud (Unrud) <unrud@openaliasbox.org> <Unrud@users.noreply.github.com>
|
||||||
|
vapatel2 <149737089+vapatel2@users.noreply.github.com>
|
||||||
Veeti Paananen (veeti) <veeti.paananen@rojekti.fi>
|
Veeti Paananen (veeti) <veeti.paananen@rojekti.fi>
|
||||||
Victor Buinsky (buinsky) <vix_booja@tut.by>
|
Victor Buinsky (buinsky) <vix_booja@tut.by>
|
||||||
Vik <63919734+ViktorOn@users.noreply.github.com>
|
Vik <63919734+ViktorOn@users.noreply.github.com>
|
||||||
|
@ -304,6 +335,7 @@ Vil Brekin (Vilbrekin) <vilbrekin@gmail.com>
|
||||||
villekalliomaki <53118179+villekalliomaki@users.noreply.github.com>
|
villekalliomaki <53118179+villekalliomaki@users.noreply.github.com>
|
||||||
Vladimir Rusinov <vrusinov@google.com> <vladimir.rusinov@gmail.com>
|
Vladimir Rusinov <vrusinov@google.com> <vladimir.rusinov@gmail.com>
|
||||||
wangguoliang <liangcszzu@163.com>
|
wangguoliang <liangcszzu@163.com>
|
||||||
|
Will Rouesnel <wrouesnel@wrouesnel.com>
|
||||||
William A. Kennington III (wkennington) <william@wkennington.com>
|
William A. Kennington III (wkennington) <william@wkennington.com>
|
||||||
wouter bolsterlee <wouter@bolsterl.ee>
|
wouter bolsterlee <wouter@bolsterl.ee>
|
||||||
Wulf Weich (wweich) <wweich@users.noreply.github.com> <wweich@gmx.de> <wulf@weich-kr.de>
|
Wulf Weich (wweich) <wweich@users.noreply.github.com> <wweich@gmx.de> <wulf@weich-kr.de>
|
||||||
|
|
37
Dockerfile
37
Dockerfile
|
@ -1,15 +1,41 @@
|
||||||
ARG GOVERSION=latest
|
ARG GOVERSION=latest
|
||||||
|
|
||||||
|
#
|
||||||
|
# Maybe build Syncthing. This is a bit ugly as we can't make an entire
|
||||||
|
# section of the Dockerfile conditional, so we end up always pulling the
|
||||||
|
# golang image as builder. Then we check if the executable we need already
|
||||||
|
# exists (pre-built) otherwise we build it.
|
||||||
|
#
|
||||||
|
|
||||||
FROM golang:$GOVERSION AS builder
|
FROM golang:$GOVERSION AS builder
|
||||||
|
ARG BUILD_USER
|
||||||
|
ARG BUILD_HOST
|
||||||
|
ARG TARGETARCH
|
||||||
|
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
ENV CGO_ENABLED=0
|
ENV CGO_ENABLED=0
|
||||||
ENV BUILD_HOST=syncthing.net
|
RUN if [ ! -f syncthing-linux-$TARGETARCH ] ; then \
|
||||||
ENV BUILD_USER=docker
|
go run build.go -no-upgrade build syncthing ; \
|
||||||
RUN rm -f syncthing && go run build.go -no-upgrade build syncthing
|
mv syncthing syncthing-linux-$TARGETARCH ; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
#
|
||||||
|
# The rest of the Dockerfile uses the binary from the builder, prebuilt or
|
||||||
|
# not.
|
||||||
|
#
|
||||||
|
|
||||||
FROM alpine
|
FROM alpine
|
||||||
|
ARG TARGETARCH
|
||||||
|
|
||||||
|
LABEL org.opencontainers.image.authors="The Syncthing Project" \
|
||||||
|
org.opencontainers.image.url="https://syncthing.net" \
|
||||||
|
org.opencontainers.image.documentation="https://docs.syncthing.net" \
|
||||||
|
org.opencontainers.image.source="https://github.com/syncthing/syncthing" \
|
||||||
|
org.opencontainers.image.vendor="The Syncthing Project" \
|
||||||
|
org.opencontainers.image.licenses="MPL-2.0" \
|
||||||
|
org.opencontainers.image.title="Syncthing"
|
||||||
|
|
||||||
EXPOSE 8384 22000/tcp 22000/udp 21027/udp
|
EXPOSE 8384 22000/tcp 22000/udp 21027/udp
|
||||||
|
|
||||||
|
@ -17,7 +43,7 @@ VOLUME ["/var/syncthing"]
|
||||||
|
|
||||||
RUN apk add --no-cache ca-certificates curl libcap su-exec tzdata
|
RUN apk add --no-cache ca-certificates curl libcap su-exec tzdata
|
||||||
|
|
||||||
COPY --from=builder /src/syncthing /bin/syncthing
|
COPY --from=builder /src/syncthing-linux-$TARGETARCH /bin/syncthing
|
||||||
COPY --from=builder /src/script/docker-entrypoint.sh /bin/entrypoint.sh
|
COPY --from=builder /src/script/docker-entrypoint.sh /bin/entrypoint.sh
|
||||||
|
|
||||||
ENV PUID=1000 PGID=1000 HOME=/var/syncthing
|
ENV PUID=1000 PGID=1000 HOME=/var/syncthing
|
||||||
|
@ -26,5 +52,6 @@ HEALTHCHECK --interval=1m --timeout=10s \
|
||||||
CMD curl -fkLsS -m 2 127.0.0.1:8384/rest/noauth/health | grep -o --color=never OK || exit 1
|
CMD curl -fkLsS -m 2 127.0.0.1:8384/rest/noauth/health | grep -o --color=never OK || exit 1
|
||||||
|
|
||||||
ENV STGUIADDRESS=0.0.0.0:8384
|
ENV STGUIADDRESS=0.0.0.0:8384
|
||||||
|
ENV STHOMEDIR=/var/syncthing/config
|
||||||
RUN chmod 755 /bin/entrypoint.sh
|
RUN chmod 755 /bin/entrypoint.sh
|
||||||
ENTRYPOINT ["/bin/entrypoint.sh", "/bin/syncthing", "-home", "/var/syncthing/config"]
|
ENTRYPOINT ["/bin/entrypoint.sh", "/bin/syncthing"]
|
||||||
|
|
|
@ -1,6 +1,14 @@
|
||||||
ARG GOVERSION=latest
|
ARG GOVERSION=latest
|
||||||
FROM golang:$GOVERSION
|
FROM golang:$GOVERSION
|
||||||
|
|
||||||
|
LABEL org.opencontainers.image.authors="The Syncthing Project" \
|
||||||
|
org.opencontainers.image.url="https://syncthing.net" \
|
||||||
|
org.opencontainers.image.documentation="https://docs.syncthing.net" \
|
||||||
|
org.opencontainers.image.source="https://github.com/syncthing/syncthing" \
|
||||||
|
org.opencontainers.image.vendor="The Syncthing Project" \
|
||||||
|
org.opencontainers.image.licenses="MPL-2.0" \
|
||||||
|
org.opencontainers.image.title="Syncthing Builder"
|
||||||
|
|
||||||
# FPM to build Debian packages
|
# FPM to build Debian packages
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
locales rubygems ruby-dev build-essential git \
|
locales rubygems ruby-dev build-essential git \
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
FROM alpine
|
|
||||||
ARG TARGETARCH
|
|
||||||
|
|
||||||
EXPOSE 8384 22000/tcp 22000/udp 21027/udp
|
|
||||||
|
|
||||||
VOLUME ["/var/syncthing"]
|
|
||||||
|
|
||||||
RUN apk add --no-cache ca-certificates curl libcap su-exec tzdata
|
|
||||||
|
|
||||||
COPY ./syncthing-linux-$TARGETARCH /bin/syncthing
|
|
||||||
COPY ./script/docker-entrypoint.sh /bin/entrypoint.sh
|
|
||||||
|
|
||||||
ENV PUID=1000 PGID=1000 HOME=/var/syncthing
|
|
||||||
|
|
||||||
HEALTHCHECK --interval=1m --timeout=10s \
|
|
||||||
CMD curl -fkLsS -m 2 127.0.0.1:8384/rest/noauth/health | grep -o --color=never OK || exit 1
|
|
||||||
|
|
||||||
ENV STGUIADDRESS=0.0.0.0:8384
|
|
||||||
ENTRYPOINT ["/bin/entrypoint.sh", "/bin/syncthing", "-home", "/var/syncthing/config"]
|
|
|
@ -1,18 +1,16 @@
|
||||||
ARG GOVERSION=latest
|
|
||||||
FROM golang:$GOVERSION AS builder
|
|
||||||
|
|
||||||
WORKDIR /src
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
ENV CGO_ENABLED=0
|
|
||||||
ENV BUILD_HOST=syncthing.net
|
|
||||||
ENV BUILD_USER=docker
|
|
||||||
RUN rm -f stcrashreceiver && go run build.go build stcrashreceiver
|
|
||||||
|
|
||||||
FROM alpine
|
FROM alpine
|
||||||
|
ARG TARGETARCH
|
||||||
|
|
||||||
|
LABEL org.opencontainers.image.authors="The Syncthing Project" \
|
||||||
|
org.opencontainers.image.url="https://syncthing.net" \
|
||||||
|
org.opencontainers.image.documentation="https://docs.syncthing.net" \
|
||||||
|
org.opencontainers.image.source="https://github.com/syncthing/syncthing" \
|
||||||
|
org.opencontainers.image.vendor="The Syncthing Project" \
|
||||||
|
org.opencontainers.image.licenses="MPL-2.0" \
|
||||||
|
org.opencontainers.image.title="Syncthing Crash Receiver"
|
||||||
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|
||||||
COPY --from=builder /src/stcrashreceiver /bin/stcrashreceiver
|
COPY stcrashreceiver-linux-${TARGETARCH} /bin/stcrashreceiver
|
||||||
|
|
||||||
ENTRYPOINT [ "/bin/stcrashreceiver" ]
|
ENTRYPOINT [ "/bin/stcrashreceiver" ]
|
||||||
|
|
|
@ -1,15 +1,28 @@
|
||||||
ARG GOVERSION=latest
|
ARG GOVERSION=latest
|
||||||
FROM golang:$GOVERSION AS builder
|
FROM golang:$GOVERSION AS builder
|
||||||
|
ARG BUILD_USER
|
||||||
|
ARG BUILD_HOST
|
||||||
|
ARG TARGETARCH
|
||||||
|
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
ENV CGO_ENABLED=0
|
ENV CGO_ENABLED=0
|
||||||
ENV BUILD_HOST=syncthing.net
|
RUN if [ ! -f stdiscosrv-linux-$TARGETARCH ] ; then \
|
||||||
ENV BUILD_USER=docker
|
go run build.go -no-upgrade build stdiscosrv ; \
|
||||||
RUN rm -f stdiscosrv && go run build.go -no-upgrade build stdiscosrv
|
mv stdiscosrv stdiscosrv-linux-$TARGETARCH ; \
|
||||||
|
fi
|
||||||
|
|
||||||
FROM alpine
|
FROM alpine
|
||||||
|
ARG TARGETARCH
|
||||||
|
|
||||||
|
LABEL org.opencontainers.image.authors="The Syncthing Project" \
|
||||||
|
org.opencontainers.image.url="https://syncthing.net" \
|
||||||
|
org.opencontainers.image.documentation="https://docs.syncthing.net" \
|
||||||
|
org.opencontainers.image.source="https://github.com/syncthing/syncthing" \
|
||||||
|
org.opencontainers.image.vendor="The Syncthing Project" \
|
||||||
|
org.opencontainers.image.licenses="MPL-2.0" \
|
||||||
|
org.opencontainers.image.title="Syncthing Discovery Server"
|
||||||
|
|
||||||
EXPOSE 19200 8443
|
EXPOSE 19200 8443
|
||||||
|
|
||||||
|
@ -17,7 +30,7 @@ VOLUME ["/var/stdiscosrv"]
|
||||||
|
|
||||||
RUN apk add --no-cache ca-certificates su-exec
|
RUN apk add --no-cache ca-certificates su-exec
|
||||||
|
|
||||||
COPY --from=builder /src/stdiscosrv /bin/stdiscosrv
|
COPY --from=builder /src/stdiscosrv-linux-$TARGETARCH /bin/stdiscosrv
|
||||||
COPY --from=builder /src/script/docker-entrypoint.sh /bin/entrypoint.sh
|
COPY --from=builder /src/script/docker-entrypoint.sh /bin/entrypoint.sh
|
||||||
|
|
||||||
ENV PUID=1000 PGID=1000 HOME=/var/stdiscosrv
|
ENV PUID=1000 PGID=1000 HOME=/var/stdiscosrv
|
||||||
|
|
|
@ -1,15 +1,13 @@
|
||||||
ARG GOVERSION=latest
|
|
||||||
FROM golang:$GOVERSION AS builder
|
|
||||||
|
|
||||||
WORKDIR /src
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
ENV CGO_ENABLED=0
|
|
||||||
ENV BUILD_HOST=syncthing.net
|
|
||||||
ENV BUILD_USER=docker
|
|
||||||
RUN rm -f strelaysrv && go run build.go -no-upgrade build strelaypoolsrv
|
|
||||||
|
|
||||||
FROM alpine
|
FROM alpine
|
||||||
|
ARG TARGETARCH
|
||||||
|
|
||||||
|
LABEL org.opencontainers.image.authors="The Syncthing Project" \
|
||||||
|
org.opencontainers.image.url="https://syncthing.net" \
|
||||||
|
org.opencontainers.image.documentation="https://docs.syncthing.net" \
|
||||||
|
org.opencontainers.image.source="https://github.com/syncthing/syncthing" \
|
||||||
|
org.opencontainers.image.vendor="The Syncthing Project" \
|
||||||
|
org.opencontainers.image.licenses="MPL-2.0" \
|
||||||
|
org.opencontainers.image.title="Syncthing Relay Pool Server"
|
||||||
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|
||||||
|
@ -19,8 +17,8 @@ ENV PUID=1000 PGID=1000 MAXMIND_KEY=
|
||||||
RUN mkdir /var/strelaypoolsrv && chown 1000 /var/strelaypoolsrv
|
RUN mkdir /var/strelaypoolsrv && chown 1000 /var/strelaypoolsrv
|
||||||
USER 1000
|
USER 1000
|
||||||
|
|
||||||
COPY --from=builder /src/strelaypoolsrv /bin/strelaypoolsrv
|
COPY strelaypoolsrv-linux-${TARGETARCH} /bin/strelaypoolsrv
|
||||||
COPY --from=builder /src/script/strelaypoolsrv-entrypoint.sh /bin/entrypoint.sh
|
COPY script/strelaypoolsrv-entrypoint.sh /bin/entrypoint.sh
|
||||||
|
|
||||||
WORKDIR /var/strelaypoolsrv
|
WORKDIR /var/strelaypoolsrv
|
||||||
ENTRYPOINT ["/bin/entrypoint.sh", "/bin/strelaypoolsrv", "-listen", ":8080"]
|
ENTRYPOINT ["/bin/entrypoint.sh", "/bin/strelaypoolsrv", "-listen", ":8080"]
|
||||||
|
|
|
@ -1,15 +1,28 @@
|
||||||
ARG GOVERSION=latest
|
ARG GOVERSION=latest
|
||||||
FROM golang:$GOVERSION AS builder
|
FROM golang:$GOVERSION AS builder
|
||||||
|
ARG BUILD_USER
|
||||||
|
ARG BUILD_HOST
|
||||||
|
ARG TARGETARCH
|
||||||
|
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
ENV CGO_ENABLED=0
|
ENV CGO_ENABLED=0
|
||||||
ENV BUILD_HOST=syncthing.net
|
RUN if [ ! -f strelaysrv-linux-$TARGETARCH ] ; then \
|
||||||
ENV BUILD_USER=docker
|
go run build.go -no-upgrade build strelaysrv ; \
|
||||||
RUN rm -f strelaysrv && go run build.go -no-upgrade build strelaysrv
|
mv strelaysrv strelaysrv-linux-$TARGETARCH ; \
|
||||||
|
fi
|
||||||
|
|
||||||
FROM alpine
|
FROM alpine
|
||||||
|
ARG TARGETARCH
|
||||||
|
|
||||||
|
LABEL org.opencontainers.image.authors="The Syncthing Project" \
|
||||||
|
org.opencontainers.image.url="https://syncthing.net" \
|
||||||
|
org.opencontainers.image.documentation="https://docs.syncthing.net" \
|
||||||
|
org.opencontainers.image.source="https://github.com/syncthing/syncthing" \
|
||||||
|
org.opencontainers.image.vendor="The Syncthing Project" \
|
||||||
|
org.opencontainers.image.licenses="MPL-2.0" \
|
||||||
|
org.opencontainers.image.title="Syncthing Relay Server"
|
||||||
|
|
||||||
EXPOSE 22067 22070
|
EXPOSE 22067 22070
|
||||||
|
|
||||||
|
@ -17,7 +30,7 @@ VOLUME ["/var/strelaysrv"]
|
||||||
|
|
||||||
RUN apk add --no-cache ca-certificates su-exec
|
RUN apk add --no-cache ca-certificates su-exec
|
||||||
|
|
||||||
COPY --from=builder /src/strelaysrv /bin/strelaysrv
|
COPY --from=builder /src/strelaysrv-linux-$TARGETARCH /bin/strelaysrv
|
||||||
COPY --from=builder /src/script/docker-entrypoint.sh /bin/entrypoint.sh
|
COPY --from=builder /src/script/docker-entrypoint.sh /bin/entrypoint.sh
|
||||||
|
|
||||||
ENV PUID=1000 PGID=1000 HOME=/var/strelaysrv
|
ENV PUID=1000 PGID=1000 HOME=/var/strelaysrv
|
||||||
|
|
|
@ -1,18 +1,16 @@
|
||||||
ARG GOVERSION=latest
|
|
||||||
FROM golang:$GOVERSION AS builder
|
|
||||||
|
|
||||||
WORKDIR /src
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
ENV CGO_ENABLED=0
|
|
||||||
ENV BUILD_HOST=syncthing.net
|
|
||||||
ENV BUILD_USER=docker
|
|
||||||
RUN rm -f stupgrades && go run build.go build stupgrades
|
|
||||||
|
|
||||||
FROM alpine
|
FROM alpine
|
||||||
|
ARG TARGETARCH
|
||||||
|
|
||||||
|
LABEL org.opencontainers.image.authors="The Syncthing Project" \
|
||||||
|
org.opencontainers.image.url="https://syncthing.net" \
|
||||||
|
org.opencontainers.image.documentation="https://docs.syncthing.net" \
|
||||||
|
org.opencontainers.image.source="https://github.com/syncthing/syncthing" \
|
||||||
|
org.opencontainers.image.vendor="The Syncthing Project" \
|
||||||
|
org.opencontainers.image.licenses="MPL-2.0" \
|
||||||
|
org.opencontainers.image.title="Syncthing Upgrades"
|
||||||
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|
||||||
COPY --from=builder /src/stupgrades /bin/stupgrades
|
COPY stupgrades-linux-${TARGETARCH} /bin/stupgrades
|
||||||
|
|
||||||
ENTRYPOINT [ "/bin/stupgrades" ]
|
ENTRYPOINT [ "/bin/stupgrades" ]
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
FROM alpine
|
||||||
|
ARG TARGETARCH
|
||||||
|
|
||||||
|
LABEL org.opencontainers.image.authors="The Syncthing Project" \
|
||||||
|
org.opencontainers.image.url="https://syncthing.net" \
|
||||||
|
org.opencontainers.image.documentation="https://docs.syncthing.net" \
|
||||||
|
org.opencontainers.image.source="https://github.com/syncthing/syncthing" \
|
||||||
|
org.opencontainers.image.vendor="The Syncthing Project" \
|
||||||
|
org.opencontainers.image.licenses="MPL-2.0" \
|
||||||
|
org.opencontainers.image.title="Syncthing Usage Reporting Server"
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
COPY ursrv-linux-${TARGETARCH} /bin/ursrv
|
||||||
|
|
||||||
|
ENTRYPOINT [ "/bin/ursrv" ]
|
12
GOALS.md
12
GOALS.md
|
@ -24,17 +24,17 @@ to avoid corrupting the user's files.
|
||||||
### 2. Secure Against Attackers
|
### 2. Secure Against Attackers
|
||||||
|
|
||||||
Again, protecting the user's data is paramount. Regardless of our other
|
Again, protecting the user's data is paramount. Regardless of our other
|
||||||
goals we must never allow the user's data to be susceptible to eavesdropping
|
goals, we must never allow the user's data to be susceptible to eavesdropping
|
||||||
or modification by unauthorized parties.
|
or modification by unauthorized parties.
|
||||||
|
|
||||||
> This should be understood in context. It is not necessarily reasonable to
|
> This should be understood in context. It is not necessarily reasonable to
|
||||||
> expect Syncthing to be resistant against well equipped state level
|
> expect Syncthing to be resistant against well equipped state level
|
||||||
> attackers. We will however do our best. Note also that this is different
|
> attackers. We will, however, do our best. Note also that this is different
|
||||||
> from anonymity which is not, currently, a goal.
|
> from anonymity which is not, currently, a goal.
|
||||||
|
|
||||||
### 3. Easy to Use
|
### 3. Easy to Use
|
||||||
|
|
||||||
Syncthing should be approachable, understandable and inclusive.
|
Syncthing should be approachable, understandable, and inclusive.
|
||||||
|
|
||||||
> Complex concepts and maths form the base of Syncthing's functionality.
|
> Complex concepts and maths form the base of Syncthing's functionality.
|
||||||
> This should nonetheless be abstracted or hidden to a degree where
|
> This should nonetheless be abstracted or hidden to a degree where
|
||||||
|
@ -52,18 +52,18 @@ User interaction should be required only when absolutely necessary.
|
||||||
### 5. Universally Available
|
### 5. Universally Available
|
||||||
|
|
||||||
Syncthing should run on every common computer. We are mindful that the
|
Syncthing should run on every common computer. We are mindful that the
|
||||||
latest technology is not always available to any given individual.
|
latest technology is not always available to every individual.
|
||||||
|
|
||||||
> Computers include desktops, laptops, servers, virtual machines, small
|
> Computers include desktops, laptops, servers, virtual machines, small
|
||||||
> general purpose computers such as Raspberry Pis and, *where possible*,
|
> general purpose computers such as Raspberry Pis and, *where possible*,
|
||||||
> tablets and phones. NAS appliances, toasters, cars, firearms, thermostats
|
> tablets and phones. NAS appliances, toasters, cars, firearms, thermostats,
|
||||||
> and so on may include computing capabilities but it is not our goal for
|
> and so on may include computing capabilities but it is not our goal for
|
||||||
> Syncthing to run smoothly on these devices.
|
> Syncthing to run smoothly on these devices.
|
||||||
|
|
||||||
### 6. For Individuals
|
### 6. For Individuals
|
||||||
|
|
||||||
Syncthing is primarily about empowering the individual user with safe,
|
Syncthing is primarily about empowering the individual user with safe,
|
||||||
secure and easy to use file synchronization.
|
secure, and easy to use file synchronization.
|
||||||
|
|
||||||
> We acknowledge that it's also useful in an enterprise setting and include
|
> We acknowledge that it's also useful in an enterprise setting and include
|
||||||
> functionality to support that. If this is in conflict with the
|
> functionality to support that. If this is in conflict with the
|
||||||
|
|
|
@ -15,6 +15,9 @@ To grant Syncthing additional capabilities without running as root, use the
|
||||||
`PCAP` environment variable with the same syntax as that for `setcap(8)`.
|
`PCAP` environment variable with the same syntax as that for `setcap(8)`.
|
||||||
For example, `PCAP=cap_chown,cap_fowner+ep`.
|
For example, `PCAP=cap_chown,cap_fowner+ep`.
|
||||||
|
|
||||||
|
To set a different umask value, use the `UMASK` environment variable. For
|
||||||
|
example `UMASK=002`.
|
||||||
|
|
||||||
## Example Usage
|
## Example Usage
|
||||||
|
|
||||||
**Docker cli**
|
**Docker cli**
|
||||||
|
|
33
README.md
33
README.md
|
@ -2,9 +2,6 @@
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
[![Latest Linux & Cross Build](https://img.shields.io/teamcity/https/build.syncthing.net/s/Syncthing_BuildLinuxCross.svg?style=flat-square&label=linux+%26+cross+build)](https://build.syncthing.net/viewType.html?buildTypeId=Syncthing_BuildLinuxCross&guest=1)
|
|
||||||
[![Latest Windows Build](https://img.shields.io/teamcity/https/build.syncthing.net/s/Syncthing_BuildWindows.svg?style=flat-square&label=windows+build)](https://build.syncthing.net/viewType.html?buildTypeId=Syncthing_BuildWindows&guest=1)
|
|
||||||
[![Latest Mac Build](https://img.shields.io/teamcity/https/build.syncthing.net/s/Syncthing_BuildMac.svg?style=flat-square&label=mac+build)](https://build.syncthing.net/viewType.html?buildTypeId=Syncthing_BuildMac&guest=1)
|
|
||||||
[![MPLv2 License](https://img.shields.io/badge/license-MPLv2-blue.svg?style=flat-square)](https://www.mozilla.org/MPL/2.0/)
|
[![MPLv2 License](https://img.shields.io/badge/license-MPLv2-blue.svg?style=flat-square)](https://www.mozilla.org/MPL/2.0/)
|
||||||
[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/88/badge)](https://bestpractices.coreinfrastructure.org/projects/88)
|
[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/88/badge)](https://bestpractices.coreinfrastructure.org/projects/88)
|
||||||
[![Go Report Card](https://goreportcard.com/badge/github.com/syncthing/syncthing)](https://goreportcard.com/report/github.com/syncthing/syncthing)
|
[![Go Report Card](https://goreportcard.com/badge/github.com/syncthing/syncthing)](https://goreportcard.com/report/github.com/syncthing/syncthing)
|
||||||
|
@ -13,8 +10,8 @@
|
||||||
|
|
||||||
Syncthing is a **continuous file synchronization program**. It synchronizes
|
Syncthing is a **continuous file synchronization program**. It synchronizes
|
||||||
files between two or more computers. We strive to fulfill the goals below.
|
files between two or more computers. We strive to fulfill the goals below.
|
||||||
The goals are listed in order of importance, the most important one being
|
The goals are listed in order of importance, the most important ones first.
|
||||||
the first. This is the summary version of the goal list - for more
|
This is the summary version of the goal list - for more
|
||||||
commentary, see the full [Goals document][13].
|
commentary, see the full [Goals document][13].
|
||||||
|
|
||||||
Syncthing should be:
|
Syncthing should be:
|
||||||
|
@ -27,12 +24,12 @@ Syncthing should be:
|
||||||
2. **Secure Against Attackers**
|
2. **Secure Against Attackers**
|
||||||
|
|
||||||
Again, protecting the user's data is paramount. Regardless of our other
|
Again, protecting the user's data is paramount. Regardless of our other
|
||||||
goals we must never allow the user's data to be susceptible to
|
goals, we must never allow the user's data to be susceptible to
|
||||||
eavesdropping or modification by unauthorized parties.
|
eavesdropping or modification by unauthorized parties.
|
||||||
|
|
||||||
3. **Easy to Use**
|
3. **Easy to Use**
|
||||||
|
|
||||||
Syncthing should be approachable, understandable and inclusive.
|
Syncthing should be approachable, understandable, and inclusive.
|
||||||
|
|
||||||
4. **Automatic**
|
4. **Automatic**
|
||||||
|
|
||||||
|
@ -41,12 +38,12 @@ Syncthing should be:
|
||||||
5. **Universally Available**
|
5. **Universally Available**
|
||||||
|
|
||||||
Syncthing should run on every common computer. We are mindful that the
|
Syncthing should run on every common computer. We are mindful that the
|
||||||
latest technology is not always available to any given individual.
|
latest technology is not always available to every individual.
|
||||||
|
|
||||||
6. **For Individuals**
|
6. **For Individuals**
|
||||||
|
|
||||||
Syncthing is primarily about empowering the individual user with safe,
|
Syncthing is primarily about empowering the individual user with safe,
|
||||||
secure and easy to use file synchronization.
|
secure, and easy to use file synchronization.
|
||||||
|
|
||||||
7. **Everything Else**
|
7. **Everything Else**
|
||||||
|
|
||||||
|
@ -60,23 +57,22 @@ Take a look at the [getting started guide][2].
|
||||||
|
|
||||||
There are a few examples for keeping Syncthing running in the background
|
There are a few examples for keeping Syncthing running in the background
|
||||||
on your system in [the etc directory][3]. There are also several [GUI
|
on your system in [the etc directory][3]. There are also several [GUI
|
||||||
implementations][11] for Windows, Mac and Linux.
|
implementations][11] for Windows, Mac, and Linux.
|
||||||
|
|
||||||
## Docker
|
## Docker
|
||||||
|
|
||||||
To run Syncthing in Docker, see [the Docker README][16].
|
To run Syncthing in Docker, see [the Docker README][16].
|
||||||
|
|
||||||
## Vote on features/bugs
|
|
||||||
|
|
||||||
We'd like to encourage you to [vote][12] on issues that matter to you.
|
|
||||||
This helps the team understand what are the biggest pain points for our users, and could potentially influence what is being worked on next.
|
|
||||||
|
|
||||||
## Getting in Touch
|
## Getting in Touch
|
||||||
|
|
||||||
The first and best point of contact is the [Forum][8].
|
The first and best point of contact is the [Forum][8].
|
||||||
If you've found something that is clearly a
|
If you've found something that is clearly a
|
||||||
bug, feel free to report it in the [GitHub issue tracker][10].
|
bug, feel free to report it in the [GitHub issue tracker][10].
|
||||||
|
|
||||||
|
If you believe that you’ve found a Syncthing-related security vulnerability,
|
||||||
|
please report it by emailing security@syncthing.net. Do not report it in the
|
||||||
|
Forum or issue tracker.
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
Building Syncthing from source is easy. After extracting the source bundle from
|
Building Syncthing from source is easy. After extracting the source bundle from
|
||||||
|
@ -86,11 +82,11 @@ build process.
|
||||||
|
|
||||||
## Signed Releases
|
## Signed Releases
|
||||||
|
|
||||||
As of v0.10.15 and onwards release binaries are GPG signed with the key
|
As of v0.10.15 and onwards, release binaries are GPG signed with the key
|
||||||
D26E6ED000654A3E, available from https://syncthing.net/security.html and
|
D26E6ED000654A3E, available from https://syncthing.net/security/ and
|
||||||
most key servers.
|
most key servers.
|
||||||
|
|
||||||
There is also a built in automatic upgrade mechanism (disabled in some
|
There is also a built-in automatic upgrade mechanism (disabled in some
|
||||||
distribution channels) which uses a compiled in ECDSA signature. macOS
|
distribution channels) which uses a compiled in ECDSA signature. macOS
|
||||||
binaries are also properly code signed.
|
binaries are also properly code signed.
|
||||||
|
|
||||||
|
@ -109,7 +105,6 @@ All code is licensed under the [MPLv2 License][7].
|
||||||
[8]: https://forum.syncthing.net/
|
[8]: https://forum.syncthing.net/
|
||||||
[10]: https://github.com/syncthing/syncthing/issues
|
[10]: https://github.com/syncthing/syncthing/issues
|
||||||
[11]: https://docs.syncthing.net/users/contrib.html#gui-wrappers
|
[11]: https://docs.syncthing.net/users/contrib.html#gui-wrappers
|
||||||
[12]: https://www.bountysource.com/teams/syncthing/issues
|
|
||||||
[13]: https://github.com/syncthing/syncthing/blob/main/GOALS.md
|
[13]: https://github.com/syncthing/syncthing/blob/main/GOALS.md
|
||||||
[14]: assets/logo-text-128.png
|
[14]: assets/logo-text-128.png
|
||||||
[15]: https://syncthing.net/
|
[15]: https://syncthing.net/
|
||||||
|
|
9
build.go
9
build.go
|
@ -33,6 +33,7 @@ import (
|
||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
_ "github.com/syncthing/syncthing/lib/automaxprocs"
|
||||||
buildpkg "github.com/syncthing/syncthing/lib/build"
|
buildpkg "github.com/syncthing/syncthing/lib/build"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -214,11 +215,17 @@ var targets = map[string]target{
|
||||||
binaryName: "stupgrades",
|
binaryName: "stupgrades",
|
||||||
},
|
},
|
||||||
"stcrashreceiver": {
|
"stcrashreceiver": {
|
||||||
name: "stupgrastcrashreceiverdes",
|
name: "stcrashreceiver",
|
||||||
description: "Syncthing Crash Server",
|
description: "Syncthing Crash Server",
|
||||||
buildPkgs: []string{"github.com/syncthing/syncthing/cmd/stcrashreceiver"},
|
buildPkgs: []string{"github.com/syncthing/syncthing/cmd/stcrashreceiver"},
|
||||||
binaryName: "stcrashreceiver",
|
binaryName: "stcrashreceiver",
|
||||||
},
|
},
|
||||||
|
"ursrv": {
|
||||||
|
name: "ursrv",
|
||||||
|
description: "Syncthing Usage Reporting Server",
|
||||||
|
buildPkgs: []string{"github.com/syncthing/syncthing/cmd/ursrv"},
|
||||||
|
binaryName: "ursrv",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func initTargets() {
|
func initTargets() {
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
#include "c_bindings.h"
|
||||||
|
|
||||||
|
libst_logging_callback_function_t libst_logging_callback_function = NULL;
|
||||||
|
|
||||||
|
void libst_invoke_logging_callback(int log_level, const char *message, size_t message_size)
|
||||||
|
{
|
||||||
|
if (!libst_logging_callback_function) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
libst_logging_callback_function(log_level, message, message_size);
|
||||||
|
}
|
|
@ -0,0 +1,241 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"unsafe"
|
||||||
|
"path/filepath"
|
||||||
|
_ "net/http/pprof" // Need to import this to support STPROFILER.
|
||||||
|
|
||||||
|
"github.com/syncthing/syncthing/lib/build"
|
||||||
|
"github.com/syncthing/syncthing/lib/config"
|
||||||
|
"github.com/syncthing/syncthing/lib/events"
|
||||||
|
"github.com/syncthing/syncthing/lib/logger"
|
||||||
|
"github.com/syncthing/syncthing/lib/locations"
|
||||||
|
"github.com/syncthing/syncthing/lib/protocol"
|
||||||
|
"github.com/syncthing/syncthing/lib/svcutil"
|
||||||
|
"github.com/syncthing/syncthing/lib/syncthing"
|
||||||
|
"github.com/syncthing/syncthing/cmd/syncthing/cli"
|
||||||
|
"github.com/syncthing/syncthing/cmd/syncthing"
|
||||||
|
"github.com/thejerf/suture/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
// include header for required C helper functions (so the following comment is NO comment)
|
||||||
|
|
||||||
|
// #include "c_bindings.h"
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
var theApp *syncthing.App
|
||||||
|
var myID protocol.DeviceID
|
||||||
|
var cliArgs []string
|
||||||
|
|
||||||
|
const (
|
||||||
|
tlsDefaultCommonName = "syncthing"
|
||||||
|
)
|
||||||
|
|
||||||
|
//export libst_own_device_id
|
||||||
|
func libst_own_device_id() string {
|
||||||
|
return myID.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
//export libst_init_logging
|
||||||
|
func libst_init_logging() {
|
||||||
|
l.AddHandler(logger.LevelVerbose, func(level logger.LogLevel, msg string) {
|
||||||
|
runes := []byte(msg)
|
||||||
|
length := len(runes)
|
||||||
|
if length <= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
C.libst_invoke_logging_callback(C.int(level), (*C.char)(unsafe.Pointer(&runes[0])), C.size_t(len(runes)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//export libst_clear_cli_args
|
||||||
|
func libst_clear_cli_args(command string) {
|
||||||
|
if command == "cli" {
|
||||||
|
cliArgs = []string{}
|
||||||
|
} else {
|
||||||
|
cliArgs = []string{command}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//export libst_append_cli_arg
|
||||||
|
func libst_append_cli_arg(arg string) {
|
||||||
|
cliArgs = append(cliArgs, arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
//export libst_run_cli
|
||||||
|
func libst_run_cli() int {
|
||||||
|
if err := cli.RunWithArgs(cliArgs); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
//export libst_run_main
|
||||||
|
func libst_run_main() int {
|
||||||
|
if err := syncthing_main.RunWithArgs(cliArgs); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
//export libst_run_syncthing
|
||||||
|
func libst_run_syncthing(configDir string, dataDir string, guiAddress string, guiApiKey string, verbose bool, allowNewerConfig bool, noDefaultConfig bool, skipPortProbing bool, ensureConfigDirExists bool, ensureDataDirExists bool) int {
|
||||||
|
// return if already running (for simplicity we only allow one Syncthing instance at at time for now)
|
||||||
|
if theApp != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// set specified GUI address and API key
|
||||||
|
if guiAddress != "" {
|
||||||
|
os.Setenv("STGUIADDRESS", guiAddress)
|
||||||
|
}
|
||||||
|
if guiApiKey != "" {
|
||||||
|
os.Setenv("STGUIAPIKEY", guiApiKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// set specified config dir
|
||||||
|
if configDir != "" {
|
||||||
|
if !filepath.IsAbs(configDir) {
|
||||||
|
var err error
|
||||||
|
configDir, err = filepath.Abs(configDir)
|
||||||
|
if err != nil {
|
||||||
|
l.Warnln("Failed to make config path absolute:", err)
|
||||||
|
return 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := locations.SetBaseDir(locations.ConfigBaseDir, configDir); err != nil {
|
||||||
|
l.Warnln(err)
|
||||||
|
return 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// set specified database dir
|
||||||
|
if dataDir != "" {
|
||||||
|
if !filepath.IsAbs(dataDir) {
|
||||||
|
var err error
|
||||||
|
dataDir, err = filepath.Abs(dataDir)
|
||||||
|
if err != nil {
|
||||||
|
l.Warnln("Failed to make database path absolute:", err)
|
||||||
|
return 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := locations.SetBaseDir(locations.DataBaseDir, dataDir); err != nil {
|
||||||
|
l.Warnln(err)
|
||||||
|
return 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure that the config directory exists
|
||||||
|
if ensureConfigDirExists {
|
||||||
|
if err := syncthing.EnsureDir(locations.GetBaseDir(locations.ConfigBaseDir), 0700); err != nil {
|
||||||
|
l.Warnln("Failed to create config directory:", err)
|
||||||
|
return 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure that the database directory exists
|
||||||
|
if dataDir != "" && ensureDataDirExists {
|
||||||
|
if err := syncthing.EnsureDir(locations.GetBaseDir(locations.DataBaseDir), 0700); err != nil {
|
||||||
|
l.Warnln("Failed to create database directory:", err)
|
||||||
|
return 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure that we have a certificate and key
|
||||||
|
cert, certErr := syncthing.LoadOrGenerateCertificate(
|
||||||
|
locations.Get(locations.CertFile),
|
||||||
|
locations.Get(locations.KeyFile),
|
||||||
|
)
|
||||||
|
if certErr != nil {
|
||||||
|
l.Warnln("Failed to load/generate certificate:", certErr)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// earlyService is a supervisor that runs the services needed for or
|
||||||
|
// before app startup; the event logger, and the config service.
|
||||||
|
spec := svcutil.SpecWithDebugLogger(l)
|
||||||
|
earlyService := suture.New("early", spec)
|
||||||
|
earlyService.ServeBackground(ctx)
|
||||||
|
|
||||||
|
evLogger := events.NewLogger()
|
||||||
|
earlyService.Add(evLogger)
|
||||||
|
|
||||||
|
// load config
|
||||||
|
configLocation := locations.Get(locations.ConfigFile)
|
||||||
|
l.Infoln("Loading config from:", configLocation)
|
||||||
|
cfgWrapper, cfgErr := syncthing.LoadConfigAtStartup(configLocation, cert, evLogger, allowNewerConfig, noDefaultConfig, skipPortProbing)
|
||||||
|
if cfgErr != nil {
|
||||||
|
l.Warnln("Failed to initialize config:", cfgErr)
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
if cfgService, ok := cfgWrapper.(suture.Service); ok {
|
||||||
|
earlyService.Add(cfgService)
|
||||||
|
}
|
||||||
|
|
||||||
|
// open database
|
||||||
|
dbFile := locations.Get(locations.Database)
|
||||||
|
l.Infoln("Opening database from:", dbFile)
|
||||||
|
ldb, dbErr := syncthing.OpenDBBackend(dbFile, config.TuningAuto)
|
||||||
|
if dbErr != nil {
|
||||||
|
l.Warnln("Error opening database:", dbErr)
|
||||||
|
return 4
|
||||||
|
}
|
||||||
|
|
||||||
|
appOpts := syncthing.Options{
|
||||||
|
ProfilerAddr: os.Getenv("STPROFILER"),
|
||||||
|
NoUpgrade: true,
|
||||||
|
Verbose: verbose,
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
theApp, err = syncthing.New(cfgWrapper, ldb, evLogger, cert, appOpts)
|
||||||
|
if err != nil {
|
||||||
|
l.Warnln("Failed to start Syncthing:", err)
|
||||||
|
return svcutil.ExitError.AsInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
// start Syncthing and block until it has finished
|
||||||
|
returnCode := 0
|
||||||
|
if err := theApp.Start(); err != nil {
|
||||||
|
returnCode = svcutil.ExitError.AsInt()
|
||||||
|
}
|
||||||
|
returnCode = theApp.Wait().AsInt();
|
||||||
|
theApp = nil
|
||||||
|
return returnCode
|
||||||
|
}
|
||||||
|
|
||||||
|
//export libst_stop_syncthing
|
||||||
|
func libst_stop_syncthing() int {
|
||||||
|
if theApp != nil {
|
||||||
|
return int(theApp.Stop(svcutil.ExitSuccess))
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//export libst_reset_database
|
||||||
|
func libst_reset_database() {
|
||||||
|
os.RemoveAll(locations.Get(locations.Database))
|
||||||
|
}
|
||||||
|
|
||||||
|
//export libst_syncthing_version
|
||||||
|
func libst_syncthing_version() *C.char {
|
||||||
|
return C.CString(build.Version)
|
||||||
|
}
|
||||||
|
|
||||||
|
//export libst_long_syncthing_version
|
||||||
|
func libst_long_syncthing_version() *C.char {
|
||||||
|
return C.CString(build.LongVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// prevent "runtime.main_main·f: function main is undeclared in the main package"
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
#ifndef LIBSYNCTHING_INTERNAL_H
|
||||||
|
#define LIBSYNCTHING_INTERNAL_H
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
// allow registration of callback function
|
||||||
|
typedef void (*libst_logging_callback_function_t) (int logLevel, const char *msg, size_t msgSize);
|
||||||
|
extern libst_logging_callback_function_t libst_logging_callback_function;
|
||||||
|
extern void libst_invoke_logging_callback(int log_level, const char *message, size_t message_size);
|
||||||
|
|
||||||
|
#endif // LIBSYNCTHING_INTERNAL_H
|
|
@ -0,0 +1,15 @@
|
||||||
|
// Copyright (C) 2014 The Syncthing Authors.
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/syncthing/syncthing/lib/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
l = logger.DefaultLogger.NewFacility("main", "Main package")
|
||||||
|
)
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
_ "github.com/syncthing/syncthing/lib/automaxprocs"
|
||||||
"github.com/syncthing/syncthing/lib/sha256"
|
"github.com/syncthing/syncthing/lib/sha256"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
|
@ -40,7 +41,7 @@ type currentFile struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *diskStore) Serve(ctx context.Context) {
|
func (d *diskStore) Serve(ctx context.Context) {
|
||||||
if err := os.MkdirAll(d.dir, 0750); err != nil {
|
if err := os.MkdirAll(d.dir, 0o700); err != nil {
|
||||||
log.Println("Creating directory:", err)
|
log.Println("Creating directory:", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -60,7 +61,7 @@ func (d *diskStore) Serve(ctx context.Context) {
|
||||||
case entry := <-d.inbox:
|
case entry := <-d.inbox:
|
||||||
path := d.fullPath(entry.path)
|
path := d.fullPath(entry.path)
|
||||||
|
|
||||||
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
|
||||||
log.Println("Creating directory:", err)
|
log.Println("Creating directory:", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -75,7 +76,7 @@ func (d *diskStore) Serve(ctx context.Context) {
|
||||||
log.Println("Failed to compress crash report:", err)
|
log.Println("Failed to compress crash report:", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := os.WriteFile(path, buf.Bytes(), 0644); err != nil {
|
if err := os.WriteFile(path, buf.Bytes(), 0o600); err != nil {
|
||||||
log.Printf("Failed to write %s: %v", entry.path, err)
|
log.Printf("Failed to write %s: %v", entry.path, err)
|
||||||
_ = os.Remove(path)
|
_ = os.Remove(path)
|
||||||
continue
|
continue
|
||||||
|
@ -147,6 +148,11 @@ func (d *diskStore) clean() {
|
||||||
if len(d.currentFiles) > 0 {
|
if len(d.currentFiles) > 0 {
|
||||||
oldest = time.Since(time.Unix(d.currentFiles[0].mtime, 0)).Truncate(time.Minute)
|
oldest = time.Since(time.Unix(d.currentFiles[0].mtime, 0)).Truncate(time.Minute)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
metricDiskstoreFilesTotal.Set(float64(len(d.currentFiles)))
|
||||||
|
metricDiskstoreBytesTotal.Set(float64(d.currentSize))
|
||||||
|
metricDiskstoreOldestAgeSeconds.Set(math.Round(oldest.Seconds()))
|
||||||
|
|
||||||
log.Printf("Clean complete: %d files, %d MB, oldest is %v ago", len(d.currentFiles), d.currentSize>>20, oldest)
|
log.Printf("Clean complete: %d files, %d MB, oldest is %v ago", len(d.currentFiles), d.currentSize>>20, oldest)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,6 +184,11 @@ func (d *diskStore) inventory() error {
|
||||||
if len(d.currentFiles) > 0 {
|
if len(d.currentFiles) > 0 {
|
||||||
oldest = time.Since(time.Unix(d.currentFiles[0].mtime, 0)).Truncate(time.Minute)
|
oldest = time.Since(time.Unix(d.currentFiles[0].mtime, 0)).Truncate(time.Minute)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
metricDiskstoreFilesTotal.Set(float64(len(d.currentFiles)))
|
||||||
|
metricDiskstoreBytesTotal.Set(float64(d.currentSize))
|
||||||
|
metricDiskstoreOldestAgeSeconds.Set(math.Round(oldest.Seconds()))
|
||||||
|
|
||||||
log.Printf("Inventory complete: %d files, %d MB, oldest is %v ago", len(d.currentFiles), d.currentSize>>20, oldest)
|
log.Printf("Inventory complete: %d files, %d MB, oldest is %v ago", len(d.currentFiles), d.currentSize>>20, oldest)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,26 +21,25 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/alecthomas/kong"
|
"github.com/alecthomas/kong"
|
||||||
|
raven "github.com/getsentry/raven-go"
|
||||||
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
|
_ "github.com/syncthing/syncthing/lib/automaxprocs"
|
||||||
"github.com/syncthing/syncthing/lib/sha256"
|
"github.com/syncthing/syncthing/lib/sha256"
|
||||||
"github.com/syncthing/syncthing/lib/ur"
|
"github.com/syncthing/syncthing/lib/ur"
|
||||||
|
|
||||||
raven "github.com/getsentry/raven-go"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const maxRequestSize = 1 << 20 // 1 MiB
|
const maxRequestSize = 1 << 20 // 1 MiB
|
||||||
|
|
||||||
type cli struct {
|
type cli struct {
|
||||||
Dir string `help:"Parent directory to store crash and failure reports in" env:"REPORTS_DIR" default:"."`
|
Dir string `help:"Parent directory to store crash and failure reports in" env:"REPORTS_DIR" default:"."`
|
||||||
DSN string `help:"Sentry DSN" env:"SENTRY_DSN"`
|
DSN string `help:"Sentry DSN" env:"SENTRY_DSN"`
|
||||||
Listen string `help:"HTTP listen address" default:":8080" env:"LISTEN_ADDRESS"`
|
Listen string `help:"HTTP listen address" default:":8080" env:"LISTEN_ADDRESS"`
|
||||||
MaxDiskFiles int `help:"Maximum number of reports on disk" default:"100000" env:"MAX_DISK_FILES"`
|
MaxDiskFiles int `help:"Maximum number of reports on disk" default:"100000" env:"MAX_DISK_FILES"`
|
||||||
MaxDiskSizeMB int64 `help:"Maximum disk space to use for reports" default:"1024" env:"MAX_DISK_SIZE_MB"`
|
MaxDiskSizeMB int64 `help:"Maximum disk space to use for reports" default:"1024" env:"MAX_DISK_SIZE_MB"`
|
||||||
CleanInterval time.Duration `help:"Interval between cleaning up old reports" default:"12h" env:"CLEAN_INTERVAL"`
|
SentryQueue int `help:"Maximum number of reports to queue for sending to Sentry" default:"64" env:"SENTRY_QUEUE"`
|
||||||
SentryQueue int `help:"Maximum number of reports to queue for sending to Sentry" default:"64" env:"SENTRY_QUEUE"`
|
DiskQueue int `help:"Maximum number of reports to queue for writing to disk" default:"64" env:"DISK_QUEUE"`
|
||||||
DiskQueue int `help:"Maximum number of reports to queue for writing to disk" default:"64" env:"DISK_QUEUE"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -69,6 +68,10 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
mux.Handle("/", cr)
|
mux.Handle("/", cr)
|
||||||
|
mux.HandleFunc("/ping", func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
w.Write([]byte("OK"))
|
||||||
|
})
|
||||||
|
mux.Handle("/metrics", promhttp.Handler())
|
||||||
|
|
||||||
if params.DSN != "" {
|
if params.DSN != "" {
|
||||||
mux.HandleFunc("/newcrash/failure", handleFailureFn(params.DSN, filepath.Join(params.Dir, "failure_reports")))
|
mux.HandleFunc("/newcrash/failure", handleFailureFn(params.DSN, filepath.Join(params.Dir, "failure_reports")))
|
||||||
|
@ -82,6 +85,11 @@ func main() {
|
||||||
|
|
||||||
func handleFailureFn(dsn, failureDir string) func(w http.ResponseWriter, req *http.Request) {
|
func handleFailureFn(dsn, failureDir string) func(w http.ResponseWriter, req *http.Request) {
|
||||||
return func(w http.ResponseWriter, req *http.Request) {
|
return func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
result := "failure"
|
||||||
|
defer func() {
|
||||||
|
metricFailureReportsTotal.WithLabelValues(result).Inc()
|
||||||
|
}()
|
||||||
|
|
||||||
lr := io.LimitReader(req.Body, maxRequestSize)
|
lr := io.LimitReader(req.Body, maxRequestSize)
|
||||||
bs, err := io.ReadAll(lr)
|
bs, err := io.ReadAll(lr)
|
||||||
req.Body.Close()
|
req.Body.Close()
|
||||||
|
@ -132,6 +140,7 @@ func handleFailureFn(dsn, failureDir string) func(w http.ResponseWriter, req *ht
|
||||||
log.Println("Failed to send failure report:", err)
|
log.Println("Failed to send failure report:", err)
|
||||||
} else {
|
} else {
|
||||||
log.Println("Sent failure report:", r.Description)
|
log.Println("Sent failure report:", r.Description)
|
||||||
|
result = "success"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
// Copyright (C) 2023 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 main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
metricCrashReportsTotal = promauto.NewCounterVec(prometheus.CounterOpts{
|
||||||
|
Namespace: "syncthing",
|
||||||
|
Subsystem: "crashreceiver",
|
||||||
|
Name: "crash_reports_total",
|
||||||
|
}, []string{"result"})
|
||||||
|
metricFailureReportsTotal = promauto.NewCounterVec(prometheus.CounterOpts{
|
||||||
|
Namespace: "syncthing",
|
||||||
|
Subsystem: "crashreceiver",
|
||||||
|
Name: "failure_reports_total",
|
||||||
|
}, []string{"result"})
|
||||||
|
metricDiskstoreFilesTotal = promauto.NewGauge(prometheus.GaugeOpts{
|
||||||
|
Namespace: "syncthing",
|
||||||
|
Subsystem: "crashreceiver",
|
||||||
|
Name: "diskstore_files_total",
|
||||||
|
})
|
||||||
|
metricDiskstoreBytesTotal = promauto.NewGauge(prometheus.GaugeOpts{
|
||||||
|
Namespace: "syncthing",
|
||||||
|
Subsystem: "crashreceiver",
|
||||||
|
Name: "diskstore_bytes_total",
|
||||||
|
})
|
||||||
|
metricDiskstoreOldestAgeSeconds = promauto.NewGauge(prometheus.GaugeOpts{
|
||||||
|
Namespace: "syncthing",
|
||||||
|
Subsystem: "crashreceiver",
|
||||||
|
Name: "diskstore_oldest_age_seconds",
|
||||||
|
})
|
||||||
|
)
|
|
@ -40,6 +40,7 @@ type sentryService struct {
|
||||||
|
|
||||||
type sentryRequest struct {
|
type sentryRequest struct {
|
||||||
reportID string
|
reportID string
|
||||||
|
userID string
|
||||||
data []byte
|
data []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,7 +53,7 @@ func (s *sentryService) Serve(ctx context.Context) {
|
||||||
log.Println("Failed to parse crash report:", err)
|
log.Println("Failed to parse crash report:", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := sendReport(s.dsn, pkt, req.reportID); err != nil {
|
if err := sendReport(s.dsn, pkt, req.userID); err != nil {
|
||||||
log.Println("Failed to send crash report:", err)
|
log.Println("Failed to send crash report:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,9 +63,9 @@ func (s *sentryService) Serve(ctx context.Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *sentryService) Send(reportID string, data []byte) bool {
|
func (s *sentryService) Send(reportID, userID string, data []byte) bool {
|
||||||
select {
|
select {
|
||||||
case s.inbox <- sentryRequest{reportID, data}:
|
case s.inbox <- sentryRequest{reportID, userID, data}:
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
|
@ -215,7 +216,13 @@ func crashReportFingerprint(message string) []string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// syncthing v1.1.4-rc.1+30-g6aaae618-dirty-crashrep "Erbium Earthworm" (go1.12.5 darwin-amd64) jb@kvin.kastelo.net 2019-05-23 16:08:14 UTC [foo, bar]
|
// syncthing v1.1.4-rc.1+30-g6aaae618-dirty-crashrep "Erbium Earthworm" (go1.12.5 darwin-amd64) jb@kvin.kastelo.net 2019-05-23 16:08:14 UTC [foo, bar]
|
||||||
var longVersionRE = regexp.MustCompile(`syncthing\s+(v[^\s]+)\s+"([^"]+)"\s\(([^\s]+)\s+([^-]+)-([^)]+)\)\s+([^\s]+)[^\[]*(?:\[(.+)\])?$`)
|
// or, somewhere along the way the "+" in the version tag disappeared:
|
||||||
|
// syncthing v1.23.7-dev.26.gdf7b56ae.dirty-stversionextra "Fermium Flea" (go1.20.5 darwin-arm64) jb@ok.kastelo.net 2023-07-12 06:55:26 UTC [Some Wrapper, purego, stnoupgrade]
|
||||||
|
var (
|
||||||
|
longVersionRE = regexp.MustCompile(`syncthing\s+(v[^\s]+)\s+"([^"]+)"\s\(([^\s]+)\s+([^-]+)-([^)]+)\)\s+([^\s]+)[^\[]*(?:\[(.+)\])?$`)
|
||||||
|
gitExtraRE = regexp.MustCompile(`\.\d+\.g[0-9a-f]+`) // ".1.g6aaae618"
|
||||||
|
gitExtraSepRE = regexp.MustCompile(`[.-]`) // dot or dash
|
||||||
|
)
|
||||||
|
|
||||||
type version struct {
|
type version struct {
|
||||||
version string // "v1.1.4-rc.1+30-g6aaae618-dirty-crashrep"
|
version string // "v1.1.4-rc.1+30-g6aaae618-dirty-crashrep"
|
||||||
|
@ -257,10 +264,21 @@ func parseVersion(line string) (version, error) {
|
||||||
builder: m[6],
|
builder: m[6],
|
||||||
}
|
}
|
||||||
|
|
||||||
parts := strings.Split(v.version, "+")
|
// Split the version tag into tag and commit. This is old style
|
||||||
|
// v1.2.3-something.4+11-g12345678 or newer with just dots
|
||||||
|
// v1.2.3-something.4.11.g12345678 or v1.2.3-dev.11.g12345678.
|
||||||
|
parts := []string{v.version}
|
||||||
|
if strings.Contains(v.version, "+") {
|
||||||
|
parts = strings.Split(v.version, "+")
|
||||||
|
} else {
|
||||||
|
idxs := gitExtraRE.FindStringIndex(v.version)
|
||||||
|
if len(idxs) > 0 {
|
||||||
|
parts = []string{v.version[:idxs[0]], v.version[idxs[0]+1:]}
|
||||||
|
}
|
||||||
|
}
|
||||||
v.tag = parts[0]
|
v.tag = parts[0]
|
||||||
if len(parts) > 1 {
|
if len(parts) > 1 {
|
||||||
fields := strings.Split(parts[1], "-")
|
fields := gitExtraSepRE.Split(parts[1], -1)
|
||||||
if len(fields) >= 2 && strings.HasPrefix(fields[1], "g") {
|
if len(fields) >= 2 && strings.HasPrefix(fields[1], "g") {
|
||||||
v.commit = fields[1][1:]
|
v.commit = fields[1][1:]
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,20 @@ func TestParseVersion(t *testing.T) {
|
||||||
extra: []string{"foo", "bar"},
|
extra: []string{"foo", "bar"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
longVersion: `syncthing v1.23.7-dev.26.gdf7b56ae-stversionextra "Fermium Flea" (go1.20.5 darwin-arm64) jb@ok.kastelo.net 2023-07-12 06:55:26 UTC [Some Wrapper, purego, stnoupgrade]`,
|
||||||
|
parsed: version{
|
||||||
|
version: "v1.23.7-dev.26.gdf7b56ae-stversionextra",
|
||||||
|
tag: "v1.23.7-dev",
|
||||||
|
commit: "df7b56ae",
|
||||||
|
codename: "Fermium Flea",
|
||||||
|
runtime: "go1.20.5",
|
||||||
|
goos: "darwin",
|
||||||
|
goarch: "arm64",
|
||||||
|
builder: "jb@ok.kastelo.net",
|
||||||
|
extra: []string{"Some Wrapper", "purego", "stnoupgrade"},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
|
|
|
@ -71,6 +71,11 @@ func (r *crashReceiver) serveHead(reportID string, w http.ResponseWriter, _ *htt
|
||||||
|
|
||||||
// servePut accepts and stores the given report.
|
// servePut accepts and stores the given report.
|
||||||
func (r *crashReceiver) servePut(reportID string, w http.ResponseWriter, req *http.Request) {
|
func (r *crashReceiver) servePut(reportID string, w http.ResponseWriter, req *http.Request) {
|
||||||
|
result := "receive_failure"
|
||||||
|
defer func() {
|
||||||
|
metricCrashReportsTotal.WithLabelValues(result).Inc()
|
||||||
|
}()
|
||||||
|
|
||||||
// Read at most maxRequestSize of report data.
|
// Read at most maxRequestSize of report data.
|
||||||
log.Println("Receiving report", reportID)
|
log.Println("Receiving report", reportID)
|
||||||
lr := io.LimitReader(req.Body, maxRequestSize)
|
lr := io.LimitReader(req.Body, maxRequestSize)
|
||||||
|
@ -81,13 +86,17 @@ func (r *crashReceiver) servePut(reportID string, w http.ResponseWriter, req *ht
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result = "success"
|
||||||
|
|
||||||
// Store the report
|
// Store the report
|
||||||
if !r.store.Put(reportID, bs) {
|
if !r.store.Put(reportID, bs) {
|
||||||
log.Println("Failed to store report (queue full):", reportID)
|
log.Println("Failed to store report (queue full):", reportID)
|
||||||
|
result = "queue_failure"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the report to Sentry
|
// Send the report to Sentry
|
||||||
if !r.sentry.Send(reportID, bs) {
|
if !r.sentry.Send(reportID, userIDFor(req), bs) {
|
||||||
log.Println("Failed to send report to sentry (queue full):", reportID)
|
log.Println("Failed to send report to sentry (queue full):", reportID)
|
||||||
|
result = "sentry_failure"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
_ "github.com/syncthing/syncthing/lib/automaxprocs"
|
||||||
"github.com/syncthing/syncthing/lib/beacon"
|
"github.com/syncthing/syncthing/lib/beacon"
|
||||||
"github.com/syncthing/syncthing/lib/discover"
|
"github.com/syncthing/syncthing/lib/discover"
|
||||||
"github.com/syncthing/syncthing/lib/protocol"
|
"github.com/syncthing/syncthing/lib/protocol"
|
||||||
|
|
|
@ -8,6 +8,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"compress/gzip"
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
@ -15,6 +16,7 @@ import (
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
io "io"
|
||||||
"log"
|
"log"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
|
@ -27,6 +29,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/syncthing/syncthing/lib/protocol"
|
"github.com/syncthing/syncthing/lib/protocol"
|
||||||
|
"github.com/syncthing/syncthing/lib/stringutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
// announcement is the format received from and sent to clients
|
// announcement is the format received from and sent to clients
|
||||||
|
@ -78,18 +81,10 @@ func (s *apiSrv) Serve(_ context.Context) error {
|
||||||
s.listener = listener
|
s.listener = listener
|
||||||
} else {
|
} else {
|
||||||
tlsCfg := &tls.Config{
|
tlsCfg := &tls.Config{
|
||||||
Certificates: []tls.Certificate{s.cert},
|
Certificates: []tls.Certificate{s.cert},
|
||||||
ClientAuth: tls.RequestClientCert,
|
ClientAuth: tls.RequestClientCert,
|
||||||
SessionTicketsDisabled: true,
|
MinVersion: tls.VersionTLS12,
|
||||||
MinVersion: tls.VersionTLS12,
|
NextProtos: []string{"h2", "http/1.1"},
|
||||||
CipherSuites: []uint16{
|
|
||||||
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
|
||||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
|
||||||
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
|
|
||||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
|
|
||||||
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
|
||||||
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tlsListener, err := tls.Listen("tcp", s.addr, tlsCfg)
|
tlsListener, err := tls.Listen("tcp", s.addr, tlsCfg)
|
||||||
|
@ -107,6 +102,7 @@ func (s *apiSrv) Serve(_ context.Context) error {
|
||||||
ReadTimeout: httpReadTimeout,
|
ReadTimeout: httpReadTimeout,
|
||||||
WriteTimeout: httpWriteTimeout,
|
WriteTimeout: httpWriteTimeout,
|
||||||
MaxHeaderBytes: httpMaxHeaderBytes,
|
MaxHeaderBytes: httpMaxHeaderBytes,
|
||||||
|
ErrorLog: log.New(io.Discard, "", 0),
|
||||||
}
|
}
|
||||||
|
|
||||||
err := srv.Serve(s.listener)
|
err := srv.Serve(s.listener)
|
||||||
|
@ -116,8 +112,6 @@ func (s *apiSrv) Serve(_ context.Context) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var topCtx = context.Background()
|
|
||||||
|
|
||||||
func (s *apiSrv) handler(w http.ResponseWriter, req *http.Request) {
|
func (s *apiSrv) handler(w http.ResponseWriter, req *http.Request) {
|
||||||
t0 := time.Now()
|
t0 := time.Now()
|
||||||
|
|
||||||
|
@ -130,10 +124,10 @@ func (s *apiSrv) handler(w http.ResponseWriter, req *http.Request) {
|
||||||
}()
|
}()
|
||||||
|
|
||||||
reqID := requestID(rand.Int63())
|
reqID := requestID(rand.Int63())
|
||||||
ctx := context.WithValue(topCtx, idKey, reqID)
|
req = req.WithContext(context.WithValue(req.Context(), idKey, reqID))
|
||||||
|
|
||||||
if debug {
|
if debug {
|
||||||
log.Println(reqID, req.Method, req.URL)
|
log.Println(reqID, req.Method, req.URL, req.Proto)
|
||||||
}
|
}
|
||||||
|
|
||||||
remoteAddr := &net.TCPAddr{
|
remoteAddr := &net.TCPAddr{
|
||||||
|
@ -142,7 +136,12 @@ func (s *apiSrv) handler(w http.ResponseWriter, req *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.useHTTP {
|
if s.useHTTP {
|
||||||
remoteAddr.IP = net.ParseIP(req.Header.Get("X-Forwarded-For"))
|
// X-Forwarded-For can have multiple client IPs; split using the comma separator
|
||||||
|
forwardIP, _, _ := strings.Cut(req.Header.Get("X-Forwarded-For"), ",")
|
||||||
|
|
||||||
|
// net.ParseIP will return nil if leading/trailing whitespace exists; use strings.TrimSpace()
|
||||||
|
remoteAddr.IP = net.ParseIP(strings.TrimSpace(forwardIP))
|
||||||
|
|
||||||
if parsedPort, err := strconv.ParseInt(req.Header.Get("X-Client-Port"), 10, 0); err == nil {
|
if parsedPort, err := strconv.ParseInt(req.Header.Get("X-Client-Port"), 10, 0); err == nil {
|
||||||
remoteAddr.Port = int(parsedPort)
|
remoteAddr.Port = int(parsedPort)
|
||||||
}
|
}
|
||||||
|
@ -159,17 +158,17 @@ func (s *apiSrv) handler(w http.ResponseWriter, req *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch req.Method {
|
switch req.Method {
|
||||||
case "GET":
|
case http.MethodGet:
|
||||||
s.handleGET(ctx, lw, req)
|
s.handleGET(lw, req)
|
||||||
case "POST":
|
case http.MethodPost:
|
||||||
s.handlePOST(ctx, remoteAddr, lw, req)
|
s.handlePOST(remoteAddr, lw, req)
|
||||||
default:
|
default:
|
||||||
http.Error(lw, "Method Not Allowed", http.StatusMethodNotAllowed)
|
http.Error(lw, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *apiSrv) handleGET(ctx context.Context, w http.ResponseWriter, req *http.Request) {
|
func (s *apiSrv) handleGET(w http.ResponseWriter, req *http.Request) {
|
||||||
reqID := ctx.Value(idKey).(requestID)
|
reqID := req.Context().Value(idKey).(requestID)
|
||||||
|
|
||||||
deviceID, err := protocol.DeviceIDFromString(req.URL.Query().Get("device"))
|
deviceID, err := protocol.DeviceIDFromString(req.URL.Query().Get("device"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -213,23 +212,34 @@ func (s *apiSrv) handleGET(ctx context.Context, w http.ResponseWriter, req *http
|
||||||
s.db.put(key, rec)
|
s.db.put(key, rec)
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Retry-After", notFoundRetryAfterString(int(misses)))
|
afterS := notFoundRetryAfterSeconds(int(misses))
|
||||||
|
retryAfterHistogram.Observe(float64(afterS))
|
||||||
|
w.Header().Set("Retry-After", strconv.Itoa(afterS))
|
||||||
http.Error(w, "Not Found", http.StatusNotFound)
|
http.Error(w, "Not Found", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
lookupRequestsTotal.WithLabelValues("success").Inc()
|
lookupRequestsTotal.WithLabelValues("success").Inc()
|
||||||
|
|
||||||
bs, _ := json.Marshal(announcement{
|
w.Header().Set("Content-Type", "application/json")
|
||||||
Seen: time.Unix(0, rec.Seen),
|
var bw io.Writer = w
|
||||||
|
|
||||||
|
// Use compression if the client asks for it
|
||||||
|
if strings.Contains(req.Header.Get("Accept-Encoding"), "gzip") {
|
||||||
|
w.Header().Set("Content-Encoding", "gzip")
|
||||||
|
gw := gzip.NewWriter(bw)
|
||||||
|
defer gw.Close()
|
||||||
|
bw = gw
|
||||||
|
}
|
||||||
|
|
||||||
|
json.NewEncoder(bw).Encode(announcement{
|
||||||
|
Seen: time.Unix(0, rec.Seen).Truncate(time.Second),
|
||||||
Addresses: addressStrs(rec.Addresses),
|
Addresses: addressStrs(rec.Addresses),
|
||||||
})
|
})
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
w.Write(bs)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *apiSrv) handlePOST(ctx context.Context, remoteAddr *net.TCPAddr, w http.ResponseWriter, req *http.Request) {
|
func (s *apiSrv) handlePOST(remoteAddr *net.TCPAddr, w http.ResponseWriter, req *http.Request) {
|
||||||
reqID := ctx.Value(idKey).(requestID)
|
reqID := req.Context().Value(idKey).(requestID)
|
||||||
|
|
||||||
rawCert, err := certificateBytes(req)
|
rawCert, err := certificateBytes(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -351,13 +361,16 @@ func certificateBytes(req *http.Request) ([]byte, error) {
|
||||||
bs = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: hdr})
|
bs = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: hdr})
|
||||||
} else if hdr := req.Header.Get("X-Forwarded-Tls-Client-Cert"); hdr != "" {
|
} else if hdr := req.Header.Get("X-Forwarded-Tls-Client-Cert"); hdr != "" {
|
||||||
// Traefik 2 passtlsclientcert
|
// Traefik 2 passtlsclientcert
|
||||||
// The certificate is in PEM format with url encoding but without newlines
|
//
|
||||||
// and start/end statements. We need to decode, reinstate the newlines every 64
|
// The certificate is in PEM format, maybe with URL encoding
|
||||||
|
// (depends on Traefik version) but without newlines and start/end
|
||||||
|
// statements. We need to decode, reinstate the newlines every 64
|
||||||
// character and add statements for the PEM decoder
|
// character and add statements for the PEM decoder
|
||||||
hdr, err := url.QueryUnescape(hdr)
|
|
||||||
if err != nil {
|
if strings.Contains(hdr, "%") {
|
||||||
// Decoding failed
|
if unesc, err := url.QueryUnescape(hdr); err == nil {
|
||||||
return nil, err
|
hdr = unesc
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 64; i < len(hdr); i += 65 {
|
for i := 64; i < len(hdr); i += 65 {
|
||||||
|
@ -365,7 +378,7 @@ func certificateBytes(req *http.Request) ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
hdr = "-----BEGIN CERTIFICATE-----\n" + hdr
|
hdr = "-----BEGIN CERTIFICATE-----\n" + hdr
|
||||||
hdr = hdr + "\n-----END CERTIFICATE-----\n"
|
hdr += "\n-----END CERTIFICATE-----\n"
|
||||||
bs = []byte(hdr)
|
bs = []byte(hdr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -404,13 +417,13 @@ func fixupAddresses(remote *net.TCPAddr, addresses []string) []string {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if remote != nil {
|
if host == "" || ip.IsUnspecified() {
|
||||||
if host == "" || ip.IsUnspecified() {
|
if remote != nil {
|
||||||
// Replace the unspecified IP with the request source.
|
// Replace the unspecified IP with the request source.
|
||||||
|
|
||||||
// ... unless the request source is the loopback address or
|
// ... unless the request source is the loopback address or
|
||||||
// multicast/unspecified (can't happen, really).
|
// multicast/unspecified (can't happen, really).
|
||||||
if remote.IP.IsLoopback() || remote.IP.IsMulticast() || remote.IP.IsUnspecified() {
|
if remote.IP == nil || remote.IP.IsLoopback() || remote.IP.IsMulticast() || remote.IP.IsUnspecified() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -426,11 +439,22 @@ func fixupAddresses(remote *net.TCPAddr, addresses []string) []string {
|
||||||
}
|
}
|
||||||
|
|
||||||
host = remote.IP.String()
|
host = remote.IP.String()
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// remote is nil, unable to determine host IP
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// If zero port was specified, use remote port.
|
}
|
||||||
if port == "0" && remote.Port > 0 {
|
|
||||||
|
// If zero port was specified, use remote port.
|
||||||
|
if port == "0" {
|
||||||
|
if remote != nil && remote.Port > 0 {
|
||||||
|
// use remote port
|
||||||
port = strconv.Itoa(remote.Port)
|
port = strconv.Itoa(remote.Port)
|
||||||
|
} else {
|
||||||
|
// unable to determine remote port
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -438,6 +462,9 @@ func fixupAddresses(remote *net.TCPAddr, addresses []string) []string {
|
||||||
fixed = append(fixed, uri.String())
|
fixed = append(fixed, uri.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove duplicate addresses
|
||||||
|
fixed = stringutil.UniqueTrimmedStrings(fixed)
|
||||||
|
|
||||||
return fixed
|
return fixed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -467,13 +494,13 @@ func errorRetryAfterString() string {
|
||||||
return strconv.Itoa(errorRetryAfterSeconds + rand.Intn(errorRetryFuzzSeconds))
|
return strconv.Itoa(errorRetryAfterSeconds + rand.Intn(errorRetryFuzzSeconds))
|
||||||
}
|
}
|
||||||
|
|
||||||
func notFoundRetryAfterString(misses int) string {
|
func notFoundRetryAfterSeconds(misses int) int {
|
||||||
retryAfterS := notFoundRetryMinSeconds + notFoundRetryIncSeconds*misses
|
retryAfterS := notFoundRetryMinSeconds + notFoundRetryIncSeconds*misses
|
||||||
if retryAfterS > notFoundRetryMaxSeconds {
|
if retryAfterS > notFoundRetryMaxSeconds {
|
||||||
retryAfterS = notFoundRetryMaxSeconds
|
retryAfterS = notFoundRetryMaxSeconds
|
||||||
}
|
}
|
||||||
retryAfterS += rand.Intn(notFoundRetryFuzzSeconds)
|
retryAfterS += rand.Intn(notFoundRetryFuzzSeconds)
|
||||||
return strconv.Itoa(retryAfterS)
|
return retryAfterS
|
||||||
}
|
}
|
||||||
|
|
||||||
func reannounceAfterString() string {
|
func reannounceAfterString() string {
|
||||||
|
|
|
@ -69,6 +69,14 @@ func TestFixupAddresses(t *testing.T) {
|
||||||
remote: addr("123.123.123.123", 9000),
|
remote: addr("123.123.123.123", 9000),
|
||||||
in: []string{"tcp://44.44.44.44:0"},
|
in: []string{"tcp://44.44.44.44:0"},
|
||||||
out: []string{"tcp://44.44.44.44:9000"},
|
out: []string{"tcp://44.44.44.44:9000"},
|
||||||
|
}, { // remote ip nil
|
||||||
|
remote: addr("", 9000),
|
||||||
|
in: []string{"tcp://:22000", "tcp://44.44.44.44:9000"},
|
||||||
|
out: []string{"tcp://44.44.44.44:9000"},
|
||||||
|
}, { // remote port 0
|
||||||
|
remote: addr("123.123.123.123", 0),
|
||||||
|
in: []string{"tcp://:22000", "tcp://44.44.44.44"},
|
||||||
|
out: []string{"tcp://123.123.123.123:22000"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,9 +12,12 @@ package main
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"log"
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
"sort"
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/syncthing/syncthing/lib/sliceutil"
|
||||||
"github.com/syndtr/goleveldb/leveldb"
|
"github.com/syndtr/goleveldb/leveldb"
|
||||||
"github.com/syndtr/goleveldb/leveldb/storage"
|
"github.com/syndtr/goleveldb/leveldb/storage"
|
||||||
"github.com/syndtr/goleveldb/leveldb/util"
|
"github.com/syndtr/goleveldb/leveldb/util"
|
||||||
|
@ -217,7 +220,7 @@ func (s *levelDBStore) statisticsServe(trigger <-chan struct{}, done chan<- stru
|
||||||
cutoff24h := t0.Add(-24 * time.Hour).UnixNano()
|
cutoff24h := t0.Add(-24 * time.Hour).UnixNano()
|
||||||
cutoff1w := t0.Add(-7 * 24 * time.Hour).UnixNano()
|
cutoff1w := t0.Add(-7 * 24 * time.Hour).UnixNano()
|
||||||
cutoff2Mon := t0.Add(-60 * 24 * time.Hour).UnixNano()
|
cutoff2Mon := t0.Add(-60 * 24 * time.Hour).UnixNano()
|
||||||
current, last24h, last1w, inactive, errors := 0, 0, 0, 0, 0
|
current, currentIPv4, currentIPv6, last24h, last1w, inactive, errors := 0, 0, 0, 0, 0, 0, 0
|
||||||
|
|
||||||
iter := s.db.NewIterator(&util.Range{}, nil)
|
iter := s.db.NewIterator(&util.Range{}, nil)
|
||||||
for iter.Next() {
|
for iter.Next() {
|
||||||
|
@ -232,9 +235,35 @@ func (s *levelDBStore) statisticsServe(trigger <-chan struct{}, done chan<- stru
|
||||||
// If there are addresses that have not expired it's a current
|
// If there are addresses that have not expired it's a current
|
||||||
// record, otherwise account it based on when it was last seen
|
// record, otherwise account it based on when it was last seen
|
||||||
// (last 24 hours or last week) or finally as inactice.
|
// (last 24 hours or last week) or finally as inactice.
|
||||||
|
addrs := expire(rec.Addresses, nowNanos)
|
||||||
switch {
|
switch {
|
||||||
case len(expire(rec.Addresses, nowNanos)) > 0:
|
case len(addrs) > 0:
|
||||||
current++
|
current++
|
||||||
|
seenIPv4, seenIPv6 := false, false
|
||||||
|
for _, addr := range addrs {
|
||||||
|
uri, err := url.Parse(addr.Address)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
host, _, err := net.SplitHostPort(uri.Host)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ip := net.ParseIP(host); ip != nil && ip.To4() != nil {
|
||||||
|
seenIPv4 = true
|
||||||
|
} else if ip != nil {
|
||||||
|
seenIPv6 = true
|
||||||
|
}
|
||||||
|
if seenIPv4 && seenIPv6 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if seenIPv4 {
|
||||||
|
currentIPv4++
|
||||||
|
}
|
||||||
|
if seenIPv6 {
|
||||||
|
currentIPv6++
|
||||||
|
}
|
||||||
case rec.Seen > cutoff24h:
|
case rec.Seen > cutoff24h:
|
||||||
last24h++
|
last24h++
|
||||||
case rec.Seen > cutoff1w:
|
case rec.Seen > cutoff1w:
|
||||||
|
@ -258,6 +287,8 @@ func (s *levelDBStore) statisticsServe(trigger <-chan struct{}, done chan<- stru
|
||||||
iter.Release()
|
iter.Release()
|
||||||
|
|
||||||
databaseKeys.WithLabelValues("current").Set(float64(current))
|
databaseKeys.WithLabelValues("current").Set(float64(current))
|
||||||
|
databaseKeys.WithLabelValues("currentIPv4").Set(float64(currentIPv4))
|
||||||
|
databaseKeys.WithLabelValues("currentIPv6").Set(float64(currentIPv6))
|
||||||
databaseKeys.WithLabelValues("last24h").Set(float64(last24h))
|
databaseKeys.WithLabelValues("last24h").Set(float64(last24h))
|
||||||
databaseKeys.WithLabelValues("last1w").Set(float64(last1w))
|
databaseKeys.WithLabelValues("last1w").Set(float64(last1w))
|
||||||
databaseKeys.WithLabelValues("inactive").Set(float64(inactive))
|
databaseKeys.WithLabelValues("inactive").Set(float64(inactive))
|
||||||
|
@ -352,14 +383,7 @@ func expire(addrs []DatabaseAddress, now int64) []DatabaseAddress {
|
||||||
i := 0
|
i := 0
|
||||||
for i < len(addrs) {
|
for i < len(addrs) {
|
||||||
if addrs[i].Expires < now {
|
if addrs[i].Expires < now {
|
||||||
// This item is expired. Replace it with the last in the list
|
addrs = sliceutil.RemoveAndZero(addrs, i)
|
||||||
// (noop if we are at the last item).
|
|
||||||
addrs[i] = addrs[len(addrs)-1]
|
|
||||||
// Wipe the last item of the list to release references to
|
|
||||||
// strings and stuff.
|
|
||||||
addrs[len(addrs)-1] = DatabaseAddress{}
|
|
||||||
// Shorten the slice.
|
|
||||||
addrs = addrs[:len(addrs)-1]
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
i++
|
i++
|
||||||
|
|
|
@ -185,7 +185,7 @@ func TestFilter(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
a: []DatabaseAddress{{Address: "a", Expires: 5}, {Address: "b", Expires: 15}, {Address: "c", Expires: 5}, {Address: "d", Expires: 15}, {Address: "e", Expires: 5}},
|
a: []DatabaseAddress{{Address: "a", Expires: 5}, {Address: "b", Expires: 15}, {Address: "c", Expires: 5}, {Address: "d", Expires: 15}, {Address: "e", Expires: 5}},
|
||||||
b: []DatabaseAddress{{Address: "d", Expires: 15}, {Address: "b", Expires: 15}}, // gets reordered
|
b: []DatabaseAddress{{Address: "b", Expires: 15}, {Address: "d", Expires: 15}},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,10 +14,12 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
|
_ "github.com/syncthing/syncthing/lib/automaxprocs"
|
||||||
"github.com/syncthing/syncthing/lib/build"
|
"github.com/syncthing/syncthing/lib/build"
|
||||||
"github.com/syncthing/syncthing/lib/protocol"
|
"github.com/syncthing/syncthing/lib/protocol"
|
||||||
"github.com/syncthing/syncthing/lib/tlsutil"
|
"github.com/syncthing/syncthing/lib/tlsutil"
|
||||||
|
@ -64,9 +66,7 @@ var levelDBOptions = &opt.Options{
|
||||||
WriteBuffer: 32 << 20, // default 4<<20
|
WriteBuffer: 32 << 20, // default 4<<20
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var debug = false
|
||||||
debug = false
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var listen string
|
var listen string
|
||||||
|
@ -76,20 +76,26 @@ func main() {
|
||||||
var replicationPeers string
|
var replicationPeers string
|
||||||
var certFile string
|
var certFile string
|
||||||
var keyFile string
|
var keyFile string
|
||||||
|
var replCertFile string
|
||||||
|
var replKeyFile string
|
||||||
var useHTTP bool
|
var useHTTP bool
|
||||||
|
var largeDB bool
|
||||||
|
|
||||||
log.SetOutput(os.Stdout)
|
log.SetOutput(os.Stdout)
|
||||||
log.SetFlags(0)
|
log.SetFlags(0)
|
||||||
|
|
||||||
flag.StringVar(&certFile, "cert", "./cert.pem", "Certificate file")
|
flag.StringVar(&certFile, "cert", "./cert.pem", "Certificate file")
|
||||||
|
flag.StringVar(&keyFile, "key", "./key.pem", "Key file")
|
||||||
flag.StringVar(&dir, "db-dir", "./discovery.db", "Database directory")
|
flag.StringVar(&dir, "db-dir", "./discovery.db", "Database directory")
|
||||||
flag.BoolVar(&debug, "debug", false, "Print debug output")
|
flag.BoolVar(&debug, "debug", false, "Print debug output")
|
||||||
flag.BoolVar(&useHTTP, "http", false, "Listen on HTTP (behind an HTTPS proxy)")
|
flag.BoolVar(&useHTTP, "http", false, "Listen on HTTP (behind an HTTPS proxy)")
|
||||||
flag.StringVar(&listen, "listen", ":8443", "Listen address")
|
flag.StringVar(&listen, "listen", ":8443", "Listen address")
|
||||||
flag.StringVar(&keyFile, "key", "./key.pem", "Key file")
|
|
||||||
flag.StringVar(&metricsListen, "metrics-listen", "", "Metrics listen address")
|
flag.StringVar(&metricsListen, "metrics-listen", "", "Metrics listen address")
|
||||||
flag.StringVar(&replicationPeers, "replicate", "", "Replication peers, id@address, comma separated")
|
flag.StringVar(&replicationPeers, "replicate", "", "Replication peers, id@address, comma separated")
|
||||||
flag.StringVar(&replicationListen, "replication-listen", ":19200", "Replication listen address")
|
flag.StringVar(&replicationListen, "replication-listen", ":19200", "Replication listen address")
|
||||||
|
flag.StringVar(&replCertFile, "replication-cert", "", "Certificate file for replication")
|
||||||
|
flag.StringVar(&replKeyFile, "replication-key", "", "Key file for replication")
|
||||||
|
flag.BoolVar(&largeDB, "large-db", false, "Use larger database settings")
|
||||||
showVersion := flag.Bool("version", false, "Show version")
|
showVersion := flag.Bool("version", false, "Show version")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
|
@ -98,6 +104,17 @@ func main() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buildInfo.WithLabelValues(build.Version, runtime.Version(), build.User, build.Date.UTC().Format("2006-01-02T15:04:05Z")).Set(1)
|
||||||
|
|
||||||
|
if largeDB {
|
||||||
|
levelDBOptions.BlockCacheCapacity = 64 << 20
|
||||||
|
levelDBOptions.BlockSize = 64 << 10
|
||||||
|
levelDBOptions.CompactionTableSize = 16 << 20
|
||||||
|
levelDBOptions.CompactionTableSizeMultiplier = 2.0
|
||||||
|
levelDBOptions.WriteBuffer = 64 << 20
|
||||||
|
levelDBOptions.CompactionL0Trigger = 8
|
||||||
|
}
|
||||||
|
|
||||||
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
log.Println("Failed to load keypair. Generating one, this might take a while...")
|
log.Println("Failed to load keypair. Generating one, this might take a while...")
|
||||||
|
@ -111,6 +128,16 @@ func main() {
|
||||||
devID := protocol.NewDeviceID(cert.Certificate[0])
|
devID := protocol.NewDeviceID(cert.Certificate[0])
|
||||||
log.Println("Server device ID is", devID)
|
log.Println("Server device ID is", devID)
|
||||||
|
|
||||||
|
replCert := cert
|
||||||
|
if replCertFile != "" && replKeyFile != "" {
|
||||||
|
replCert, err = tls.LoadX509KeyPair(replCertFile, replKeyFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("Failed to load replication keypair:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
replDevID := protocol.NewDeviceID(replCert.Certificate[0])
|
||||||
|
log.Println("Replication device ID is", replDevID)
|
||||||
|
|
||||||
// Parse the replication specs, if any.
|
// Parse the replication specs, if any.
|
||||||
var allowedReplicationPeers []protocol.DeviceID
|
var allowedReplicationPeers []protocol.DeviceID
|
||||||
var replicationDestinations []string
|
var replicationDestinations []string
|
||||||
|
@ -165,14 +192,14 @@ func main() {
|
||||||
// Start any replication senders.
|
// Start any replication senders.
|
||||||
var repl replicationMultiplexer
|
var repl replicationMultiplexer
|
||||||
for _, dst := range replicationDestinations {
|
for _, dst := range replicationDestinations {
|
||||||
rs := newReplicationSender(dst, cert, allowedReplicationPeers)
|
rs := newReplicationSender(dst, replCert, allowedReplicationPeers)
|
||||||
main.Add(rs)
|
main.Add(rs)
|
||||||
repl = append(repl, rs)
|
repl = append(repl, rs)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have replication configured, start the replication listener.
|
// If we have replication configured, start the replication listener.
|
||||||
if len(allowedReplicationPeers) > 0 {
|
if len(allowedReplicationPeers) > 0 {
|
||||||
rl := newReplicationListener(replicationListen, cert, allowedReplicationPeers, db)
|
rl := newReplicationListener(replicationListen, replCert, allowedReplicationPeers, db)
|
||||||
main.Add(rl)
|
main.Add(rl)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,8 +19,11 @@ import (
|
||||||
"github.com/syncthing/syncthing/lib/protocol"
|
"github.com/syncthing/syncthing/lib/protocol"
|
||||||
)
|
)
|
||||||
|
|
||||||
const replicationReadTimeout = time.Minute
|
const (
|
||||||
const replicationHeartbeatInterval = time.Second * 30
|
replicationReadTimeout = time.Minute
|
||||||
|
replicationWriteTimeout = 30 * time.Second
|
||||||
|
replicationHeartbeatInterval = time.Second * 30
|
||||||
|
)
|
||||||
|
|
||||||
type replicator interface {
|
type replicator interface {
|
||||||
send(key string, addrs []DatabaseAddress, seen int64)
|
send(key string, addrs []DatabaseAddress, seen int64)
|
||||||
|
@ -68,6 +71,12 @@ func (s *replicationSender) Serve(ctx context.Context) error {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// The replication stream is not especially latency sensitive, but it is
|
||||||
|
// quite a lot of data in small writes. Make it more efficient.
|
||||||
|
if tcpc, ok := conn.NetConn().(*net.TCPConn); ok {
|
||||||
|
_ = tcpc.SetNoDelay(false)
|
||||||
|
}
|
||||||
|
|
||||||
// Get the other side device ID.
|
// Get the other side device ID.
|
||||||
remoteID, err := deviceID(conn)
|
remoteID, err := deviceID(conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -116,7 +125,7 @@ func (s *replicationSender) Serve(ctx context.Context) error {
|
||||||
binary.BigEndian.PutUint32(buf, uint32(n))
|
binary.BigEndian.PutUint32(buf, uint32(n))
|
||||||
|
|
||||||
// Send
|
// Send
|
||||||
conn.SetWriteDeadline(time.Now().Add(5 * time.Second))
|
conn.SetWriteDeadline(time.Now().Add(replicationWriteTimeout))
|
||||||
if _, err := conn.Write(buf[:4+n]); err != nil {
|
if _, err := conn.Write(buf[:4+n]); err != nil {
|
||||||
replicationSendsTotal.WithLabelValues("error").Inc()
|
replicationSendsTotal.WithLabelValues("error").Inc()
|
||||||
log.Println("Replication write:", err)
|
log.Println("Replication write:", err)
|
||||||
|
|
|
@ -14,6 +14,14 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
buildInfo = prometheus.NewGaugeVec(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Namespace: "syncthing",
|
||||||
|
Subsystem: "discovery",
|
||||||
|
Name: "build_info",
|
||||||
|
Help: "A metric with a constant '1' value labeled by version, goversion, builduser and builddate from which stdiscosrv was built.",
|
||||||
|
}, []string{"version", "goversion", "builduser", "builddate"})
|
||||||
|
|
||||||
apiRequestsTotal = prometheus.NewCounterVec(
|
apiRequestsTotal = prometheus.NewCounterVec(
|
||||||
prometheus.CounterOpts{
|
prometheus.CounterOpts{
|
||||||
Namespace: "syncthing",
|
Namespace: "syncthing",
|
||||||
|
@ -90,6 +98,14 @@ var (
|
||||||
Help: "Latency of database operations.",
|
Help: "Latency of database operations.",
|
||||||
Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
|
Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
|
||||||
}, []string{"operation"})
|
}, []string{"operation"})
|
||||||
|
|
||||||
|
retryAfterHistogram = prometheus.NewHistogram(prometheus.HistogramOpts{
|
||||||
|
Namespace: "syncthing",
|
||||||
|
Subsystem: "discovery",
|
||||||
|
Name: "retry_after_seconds",
|
||||||
|
Help: "Retry-After header value in seconds.",
|
||||||
|
Buckets: prometheus.ExponentialBuckets(60, 2, 7), // 60, 120, 240, 480, 960, 1920, 3840
|
||||||
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -104,11 +120,13 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
prometheus.MustRegister(apiRequestsTotal, apiRequestsSeconds,
|
prometheus.MustRegister(buildInfo,
|
||||||
|
apiRequestsTotal, apiRequestsSeconds,
|
||||||
lookupRequestsTotal, announceRequestsTotal,
|
lookupRequestsTotal, announceRequestsTotal,
|
||||||
replicationSendsTotal, replicationRecvsTotal,
|
replicationSendsTotal, replicationRecvsTotal,
|
||||||
databaseKeys, databaseStatisticsSeconds,
|
databaseKeys, databaseStatisticsSeconds,
|
||||||
databaseOperations, databaseOperationSeconds)
|
databaseOperations, databaseOperationSeconds,
|
||||||
|
retryAfterHistogram)
|
||||||
|
|
||||||
processCollectorOpts := collectors.ProcessCollectorOpts{
|
processCollectorOpts := collectors.ProcessCollectorOpts{
|
||||||
Namespace: "syncthing_discovery",
|
Namespace: "syncthing_discovery",
|
||||||
|
@ -120,5 +138,4 @@ func init() {
|
||||||
prometheus.MustRegister(
|
prometheus.MustRegister(
|
||||||
collectors.NewProcessCollector(processCollectorOpts),
|
collectors.NewProcessCollector(processCollectorOpts),
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,8 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
_ "github.com/syncthing/syncthing/lib/automaxprocs"
|
||||||
)
|
)
|
||||||
|
|
||||||
type event struct {
|
type event struct {
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
_ "github.com/syncthing/syncthing/lib/automaxprocs"
|
||||||
"github.com/syncthing/syncthing/lib/protocol"
|
"github.com/syncthing/syncthing/lib/protocol"
|
||||||
"github.com/syncthing/syncthing/lib/scanner"
|
"github.com/syncthing/syncthing/lib/scanner"
|
||||||
)
|
)
|
||||||
|
|
|
@ -16,6 +16,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
_ "github.com/syncthing/syncthing/lib/automaxprocs"
|
||||||
"github.com/syncthing/syncthing/lib/config"
|
"github.com/syncthing/syncthing/lib/config"
|
||||||
"github.com/syncthing/syncthing/lib/discover"
|
"github.com/syncthing/syncthing/lib/discover"
|
||||||
"github.com/syncthing/syncthing/lib/events"
|
"github.com/syncthing/syncthing/lib/events"
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
_ "github.com/syncthing/syncthing/lib/automaxprocs"
|
||||||
"github.com/syncthing/syncthing/lib/fs"
|
"github.com/syncthing/syncthing/lib/fs"
|
||||||
"github.com/syncthing/syncthing/lib/ignore"
|
"github.com/syncthing/syncthing/lib/ignore"
|
||||||
)
|
)
|
||||||
|
|
|
@ -15,6 +15,8 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
_ "github.com/syncthing/syncthing/lib/automaxprocs"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -43,7 +45,7 @@ func generateFiles(dir string, files, maxexp int, srcname string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
p0 := filepath.Join(dir, string(n[0]), n[0:2])
|
p0 := filepath.Join(dir, string(n[0]), n[0:2])
|
||||||
err = os.MkdirAll(p0, 0755)
|
err = os.MkdirAll(p0, 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -82,7 +84,7 @@ func generateOneFile(fd io.ReadSeeker, p1 string, s int64) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
os.Chmod(p1, os.FileMode(rand.Intn(0777)|0400))
|
os.Chmod(p1, os.FileMode(rand.Intn(0o777)|0o400))
|
||||||
|
|
||||||
t := time.Now().Add(-time.Duration(rand.Intn(30*86400)) * time.Second)
|
t := time.Now().Add(-time.Duration(rand.Intn(30*86400)) * time.Second)
|
||||||
return os.Chtimes(p1, t, t)
|
return os.Chtimes(p1, t, t)
|
||||||
|
|
|
@ -237,7 +237,7 @@
|
||||||
uptimeSeconds: 0,
|
uptimeSeconds: 0,
|
||||||
};
|
};
|
||||||
$scope.map = L.map('map').setView([40.90296, 1.90925], 2);
|
$scope.map = L.map('map').setView([40.90296, 1.90925], 2);
|
||||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
|
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png',
|
||||||
{
|
{
|
||||||
attribution: 'Leaflet',
|
attribution: 'Leaflet',
|
||||||
maxZoom: 17
|
maxZoom: 17
|
||||||
|
|
|
@ -21,14 +21,14 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
lru "github.com/hashicorp/golang-lru/v2"
|
lru "github.com/hashicorp/golang-lru/v2"
|
||||||
"github.com/syncthing/syncthing/lib/httpcache"
|
|
||||||
"github.com/syncthing/syncthing/lib/protocol"
|
|
||||||
|
|
||||||
"github.com/oschwald/geoip2-golang"
|
"github.com/oschwald/geoip2-golang"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
"github.com/syncthing/syncthing/cmd/strelaypoolsrv/auto"
|
"github.com/syncthing/syncthing/cmd/strelaypoolsrv/auto"
|
||||||
"github.com/syncthing/syncthing/lib/assets"
|
"github.com/syncthing/syncthing/lib/assets"
|
||||||
|
_ "github.com/syncthing/syncthing/lib/automaxprocs"
|
||||||
|
"github.com/syncthing/syncthing/lib/httpcache"
|
||||||
|
"github.com/syncthing/syncthing/lib/protocol"
|
||||||
"github.com/syncthing/syncthing/lib/rand"
|
"github.com/syncthing/syncthing/lib/rand"
|
||||||
"github.com/syncthing/syncthing/lib/relay/client"
|
"github.com/syncthing/syncthing/lib/relay/client"
|
||||||
"github.com/syncthing/syncthing/lib/sync"
|
"github.com/syncthing/syncthing/lib/sync"
|
||||||
|
|
|
@ -36,8 +36,14 @@ func listener(_, addr string, config *tls.Config, token string) {
|
||||||
for {
|
for {
|
||||||
conn, isTLS, err := listener.AcceptNoWrapTLS()
|
conn, isTLS, err := listener.AcceptNoWrapTLS()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
// Conn may be nil if accept failed, or non-nil if the initial
|
||||||
|
// read to figure out if it's TLS or not failed. In the latter
|
||||||
|
// case, close the connection before moving on.
|
||||||
|
if conn != nil {
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
if debug {
|
if debug {
|
||||||
log.Println("Listener failed to accept connection from", conn.RemoteAddr(), ". Possibly a TCP Ping.")
|
log.Println("Listener failed to accept:", err)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,19 +19,18 @@ import (
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
_ "github.com/syncthing/syncthing/lib/automaxprocs"
|
||||||
"github.com/syncthing/syncthing/lib/build"
|
"github.com/syncthing/syncthing/lib/build"
|
||||||
|
"github.com/syncthing/syncthing/lib/config"
|
||||||
"github.com/syncthing/syncthing/lib/events"
|
"github.com/syncthing/syncthing/lib/events"
|
||||||
|
"github.com/syncthing/syncthing/lib/nat"
|
||||||
"github.com/syncthing/syncthing/lib/osutil"
|
"github.com/syncthing/syncthing/lib/osutil"
|
||||||
|
_ "github.com/syncthing/syncthing/lib/pmp"
|
||||||
|
syncthingprotocol "github.com/syncthing/syncthing/lib/protocol"
|
||||||
"github.com/syncthing/syncthing/lib/relay/protocol"
|
"github.com/syncthing/syncthing/lib/relay/protocol"
|
||||||
"github.com/syncthing/syncthing/lib/tlsutil"
|
"github.com/syncthing/syncthing/lib/tlsutil"
|
||||||
"golang.org/x/time/rate"
|
|
||||||
|
|
||||||
"github.com/syncthing/syncthing/lib/config"
|
|
||||||
"github.com/syncthing/syncthing/lib/nat"
|
|
||||||
_ "github.com/syncthing/syncthing/lib/pmp"
|
|
||||||
_ "github.com/syncthing/syncthing/lib/upnp"
|
_ "github.com/syncthing/syncthing/lib/upnp"
|
||||||
|
"golang.org/x/time/rate"
|
||||||
syncthingprotocol "github.com/syncthing/syncthing/lib/protocol"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -194,7 +193,15 @@ func main() {
|
||||||
cfg.Options.NATTimeoutS = natTimeout
|
cfg.Options.NATTimeoutS = natTimeout
|
||||||
})
|
})
|
||||||
natSvc := nat.NewService(id, wrapper)
|
natSvc := nat.NewService(id, wrapper)
|
||||||
mapping := mapping{natSvc.NewMapping(nat.TCP, addr.IP, addr.Port)}
|
var ipVersion nat.IPVersion
|
||||||
|
if strings.HasSuffix(proto, "4") {
|
||||||
|
ipVersion = nat.IPv4Only
|
||||||
|
} else if strings.HasSuffix(proto, "6") {
|
||||||
|
ipVersion = nat.IPv6Only
|
||||||
|
} else {
|
||||||
|
ipVersion = nat.IPvAny
|
||||||
|
}
|
||||||
|
mapping := mapping{natSvc.NewMapping(nat.TCP, ipVersion, addr.IP, addr.Port)}
|
||||||
|
|
||||||
if natEnabled {
|
if natEnabled {
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
|
@ -14,6 +14,7 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
_ "github.com/syncthing/syncthing/lib/automaxprocs"
|
||||||
syncthingprotocol "github.com/syncthing/syncthing/lib/protocol"
|
syncthingprotocol "github.com/syncthing/syncthing/lib/protocol"
|
||||||
"github.com/syncthing/syncthing/lib/relay/client"
|
"github.com/syncthing/syncthing/lib/relay/client"
|
||||||
"github.com/syncthing/syncthing/lib/relay/protocol"
|
"github.com/syncthing/syncthing/lib/relay/protocol"
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
_ "github.com/syncthing/syncthing/lib/automaxprocs"
|
||||||
"github.com/syncthing/syncthing/lib/signature"
|
"github.com/syncthing/syncthing/lib/signature"
|
||||||
"github.com/syncthing/syncthing/lib/upgrade"
|
"github.com/syncthing/syncthing/lib/upgrade"
|
||||||
)
|
)
|
||||||
|
|
|
@ -19,6 +19,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/alecthomas/kong"
|
"github.com/alecthomas/kong"
|
||||||
|
_ "github.com/syncthing/syncthing/lib/automaxprocs"
|
||||||
"github.com/syncthing/syncthing/lib/httpcache"
|
"github.com/syncthing/syncthing/lib/httpcache"
|
||||||
"github.com/syncthing/syncthing/lib/upgrade"
|
"github.com/syncthing/syncthing/lib/upgrade"
|
||||||
)
|
)
|
||||||
|
@ -57,7 +58,7 @@ type githubReleases struct {
|
||||||
url string
|
url string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *githubReleases) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
func (p *githubReleases) ServeHTTP(w http.ResponseWriter, _ *http.Request) {
|
||||||
log.Println("Fetching", p.url)
|
log.Println("Fetching", p.url)
|
||||||
rels := upgrade.FetchLatestReleases(p.url, "")
|
rels := upgrade.FetchLatestReleases(p.url, "")
|
||||||
if rels == nil {
|
if rels == nil {
|
||||||
|
@ -68,6 +69,16 @@ func (p *githubReleases) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
sort.Sort(upgrade.SortByRelease(rels))
|
sort.Sort(upgrade.SortByRelease(rels))
|
||||||
rels = filterForLatest(rels)
|
rels = filterForLatest(rels)
|
||||||
|
|
||||||
|
// Move the URL used for browser downloads to the URL field, and remove
|
||||||
|
// the browser URL field. This avoids going via the GitHub API for
|
||||||
|
// downloads, since Syncthing uses the URL field.
|
||||||
|
for _, rel := range rels {
|
||||||
|
for j, asset := range rel.Assets {
|
||||||
|
rel.Assets[j].URL = asset.BrowserURL
|
||||||
|
rel.Assets[j].BrowserURL = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
_ = json.NewEncoder(buf).Encode(rels)
|
_ = json.NewEncoder(buf).Encode(rels)
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ import (
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
_ "github.com/syncthing/syncthing/lib/automaxprocs"
|
||||||
"github.com/syncthing/syncthing/lib/protocol"
|
"github.com/syncthing/syncthing/lib/protocol"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -157,7 +158,7 @@ func saveCert(priv interface{}, derBytes []byte) {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
keyOut, err := os.OpenFile("key.pem", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
keyOut, err := os.OpenFile("key.pem", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
_ "github.com/syncthing/syncthing/lib/automaxprocs"
|
||||||
"github.com/syncthing/syncthing/lib/sha256"
|
"github.com/syncthing/syncthing/lib/sha256"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
package main
|
package syncthing_main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"github.com/AudriusButkevicius/recli"
|
"github.com/AudriusButkevicius/recli"
|
||||||
|
"github.com/alecthomas/kong"
|
||||||
"github.com/syncthing/syncthing/lib/config"
|
"github.com/syncthing/syncthing/lib/config"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
@ -23,9 +24,20 @@ type configHandler struct {
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
func getConfigCommand(f *apiClientFactory) (cli.Command, error) {
|
type configCommand struct {
|
||||||
|
Args []string `arg:"" default:"-h"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *configCommand) Run(ctx Context, _ *kong.Context) error {
|
||||||
|
app := cli.NewApp()
|
||||||
|
app.Name = "syncthing"
|
||||||
|
app.Author = "The Syncthing Authors"
|
||||||
|
app.Metadata = map[string]interface{}{
|
||||||
|
"clientFactory": ctx.clientFactory,
|
||||||
|
}
|
||||||
|
|
||||||
h := new(configHandler)
|
h := new(configHandler)
|
||||||
h.client, h.err = f.getClient()
|
h.client, h.err = ctx.clientFactory.getClient()
|
||||||
if h.err == nil {
|
if h.err == nil {
|
||||||
h.cfg, h.err = getConfig(h.client)
|
h.cfg, h.err = getConfig(h.client)
|
||||||
}
|
}
|
||||||
|
@ -38,17 +50,15 @@ func getConfigCommand(f *apiClientFactory) (cli.Command, error) {
|
||||||
|
|
||||||
commands, err := recli.New(recliCfg).Construct(&h.cfg)
|
commands, err := recli.New(recliCfg).Construct(&h.cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cli.Command{}, fmt.Errorf("config reflect: %w", err)
|
return fmt.Errorf("config reflect: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return cli.Command{
|
app.Commands = commands
|
||||||
Name: "config",
|
app.HideHelp = true
|
||||||
HideHelp: true,
|
app.Before = h.configBefore
|
||||||
Usage: "Configuration modification command group",
|
app.After = h.configAfter
|
||||||
Subcommands: commands,
|
|
||||||
Before: h.configBefore,
|
return app.Run(append([]string{app.Name}, c.Args...))
|
||||||
After: h.configAfter,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *configHandler) configBefore(c *cli.Context) error {
|
func (h *configHandler) configBefore(c *cli.Context) error {
|
||||||
|
|
|
@ -9,47 +9,37 @@ package cli
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"github.com/urfave/cli"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var debugCommand = cli.Command{
|
type fileCommand struct {
|
||||||
Name: "debug",
|
FolderID string `arg:""`
|
||||||
HideHelp: true,
|
Path string `arg:""`
|
||||||
Usage: "Debug command group",
|
|
||||||
Subcommands: []cli.Command{
|
|
||||||
{
|
|
||||||
Name: "file",
|
|
||||||
Usage: "Show information about a file (or directory/symlink)",
|
|
||||||
ArgsUsage: "FOLDER-ID PATH",
|
|
||||||
Action: expects(2, debugFile()),
|
|
||||||
},
|
|
||||||
indexCommand,
|
|
||||||
{
|
|
||||||
Name: "profile",
|
|
||||||
Usage: "Save a profile to help figuring out what Syncthing does.",
|
|
||||||
ArgsUsage: "cpu | heap",
|
|
||||||
Action: expects(1, profile()),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func debugFile() cli.ActionFunc {
|
func (f *fileCommand) Run(ctx Context) error {
|
||||||
return func(c *cli.Context) error {
|
indexDumpOutput := indexDumpOutputWrapper(ctx.clientFactory)
|
||||||
query := make(url.Values)
|
|
||||||
query.Set("folder", c.Args()[0])
|
query := make(url.Values)
|
||||||
query.Set("file", normalizePath(c.Args()[1]))
|
query.Set("folder", f.FolderID)
|
||||||
return indexDumpOutput("debug/file?" + query.Encode())(c)
|
query.Set("file", normalizePath(f.Path))
|
||||||
|
return indexDumpOutput("debug/file?" + query.Encode())
|
||||||
|
}
|
||||||
|
|
||||||
|
type profileCommand struct {
|
||||||
|
Type string `arg:"" help:"cpu | heap"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *profileCommand) Run(ctx Context) error {
|
||||||
|
switch t := p.Type; t {
|
||||||
|
case "cpu", "heap":
|
||||||
|
return saveToFile(fmt.Sprintf("debug/%vprof", p.Type), ctx.clientFactory)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("expected cpu or heap as argument, got %v", t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func profile() cli.ActionFunc {
|
type debugCommand struct {
|
||||||
return func(c *cli.Context) error {
|
File fileCommand `cmd:"" help:"Show information about a file (or directory/symlink)"`
|
||||||
switch t := c.Args()[0]; t {
|
Profile profileCommand `cmd:"" help:"Save a profile to help figuring out what Syncthing does"`
|
||||||
case "cpu", "heap":
|
Index indexCommand `cmd:"" help:"Show information about the index (database)"`
|
||||||
return saveToFile(fmt.Sprintf("debug/%vprof", c.Args()[0]))(c)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("expected cpu or heap as argument, got %v", t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,36 +11,25 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/urfave/cli"
|
"github.com/alecthomas/kong"
|
||||||
)
|
)
|
||||||
|
|
||||||
var errorsCommand = cli.Command{
|
type errorsCommand struct {
|
||||||
Name: "errors",
|
Show struct{} `cmd:"" help:"Show pending errors"`
|
||||||
HideHelp: true,
|
Push errorsPushCommand `cmd:"" help:"Push an error to active clients"`
|
||||||
Usage: "Error command group",
|
Clear struct{} `cmd:"" help:"Clear pending errors"`
|
||||||
Subcommands: []cli.Command{
|
|
||||||
{
|
|
||||||
Name: "show",
|
|
||||||
Usage: "Show pending errors",
|
|
||||||
Action: expects(0, indexDumpOutput("system/error")),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "push",
|
|
||||||
Usage: "Push an error to active clients",
|
|
||||||
ArgsUsage: "ERROR-MESSAGE",
|
|
||||||
Action: expects(1, errorsPush),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "clear",
|
|
||||||
Usage: "Clear pending errors",
|
|
||||||
Action: expects(0, emptyPost("system/error/clear")),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func errorsPush(c *cli.Context) error {
|
type errorsPushCommand struct {
|
||||||
client := c.App.Metadata["client"].(APIClient)
|
ErrorMessage string `arg:""`
|
||||||
errStr := strings.Join(c.Args(), " ")
|
}
|
||||||
|
|
||||||
|
func (e *errorsPushCommand) Run(ctx Context) error {
|
||||||
|
client, err := ctx.clientFactory.getClient()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
errStr := e.ErrorMessage
|
||||||
response, err := client.Post("system/error", strings.TrimSpace(errStr))
|
response, err := client.Post("system/error", strings.TrimSpace(errStr))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -59,3 +48,13 @@ func errorsPush(c *cli.Context) error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (*errorsCommand) Run(ctx Context, kongCtx *kong.Context) error {
|
||||||
|
switch kongCtx.Selected().Name {
|
||||||
|
case "show":
|
||||||
|
return indexDumpOutput("system/error", ctx.clientFactory)
|
||||||
|
case "clear":
|
||||||
|
return emptyPost("system/error/clear", ctx.clientFactory)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -7,32 +7,26 @@
|
||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/urfave/cli"
|
"github.com/alecthomas/kong"
|
||||||
)
|
)
|
||||||
|
|
||||||
var indexCommand = cli.Command{
|
type indexCommand struct {
|
||||||
Name: "index",
|
Dump struct{} `cmd:"" help:"Print the entire db"`
|
||||||
Usage: "Show information about the index (database)",
|
DumpSize struct{} `cmd:"" help:"Print the db size of different categories of information"`
|
||||||
Subcommands: []cli.Command{
|
Check struct{} `cmd:"" help:"Check the database for inconsistencies"`
|
||||||
{
|
Account struct{} `cmd:"" help:"Print key and value size statistics per key type"`
|
||||||
Name: "dump",
|
}
|
||||||
Usage: "Print the entire db",
|
|
||||||
Action: expects(0, indexDump),
|
func (*indexCommand) Run(kongCtx *kong.Context) error {
|
||||||
},
|
switch kongCtx.Selected().Name {
|
||||||
{
|
case "dump":
|
||||||
Name: "dump-size",
|
return indexDump()
|
||||||
Usage: "Print the db size of different categories of information",
|
case "dump-size":
|
||||||
Action: expects(0, indexDumpSize),
|
return indexDumpSize()
|
||||||
},
|
case "check":
|
||||||
{
|
return indexCheck()
|
||||||
Name: "check",
|
case "account":
|
||||||
Usage: "Check the database for inconsistencies",
|
return indexAccount()
|
||||||
Action: expects(0, indexCheck),
|
}
|
||||||
},
|
return nil
|
||||||
{
|
|
||||||
Name: "account",
|
|
||||||
Usage: "Print key and value size statistics per key type",
|
|
||||||
Action: expects(0, indexAccount),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,12 +10,10 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
|
||||||
"github.com/urfave/cli"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// indexAccount prints key and data size statistics per class
|
// indexAccount prints key and data size statistics per class
|
||||||
func indexAccount(*cli.Context) error {
|
func indexAccount() error {
|
||||||
ldb, err := getDB()
|
ldb, err := getDB()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -11,13 +11,11 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/urfave/cli"
|
|
||||||
|
|
||||||
"github.com/syncthing/syncthing/lib/db"
|
"github.com/syncthing/syncthing/lib/db"
|
||||||
"github.com/syncthing/syncthing/lib/protocol"
|
"github.com/syncthing/syncthing/lib/protocol"
|
||||||
)
|
)
|
||||||
|
|
||||||
func indexDump(*cli.Context) error {
|
func indexDump() error {
|
||||||
ldb, err := getDB()
|
ldb, err := getDB()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -11,12 +11,10 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"github.com/urfave/cli"
|
|
||||||
|
|
||||||
"github.com/syncthing/syncthing/lib/db"
|
"github.com/syncthing/syncthing/lib/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
func indexDumpSize(*cli.Context) error {
|
func indexDumpSize() error {
|
||||||
type sizedElement struct {
|
type sizedElement struct {
|
||||||
key string
|
key string
|
||||||
size int
|
size int
|
||||||
|
|
|
@ -13,8 +13,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"github.com/urfave/cli"
|
|
||||||
|
|
||||||
"github.com/syncthing/syncthing/lib/db"
|
"github.com/syncthing/syncthing/lib/db"
|
||||||
"github.com/syncthing/syncthing/lib/protocol"
|
"github.com/syncthing/syncthing/lib/protocol"
|
||||||
)
|
)
|
||||||
|
@ -35,7 +33,7 @@ type sequenceKey struct {
|
||||||
sequence uint64
|
sequence uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
func indexCheck(*cli.Context) (err error) {
|
func indexCheck() (err error) {
|
||||||
ldb, err := getDB()
|
ldb, err := getDB()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -7,166 +7,72 @@
|
||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/alecthomas/kong"
|
"github.com/alecthomas/kong"
|
||||||
"github.com/flynn-archive/go-shlex"
|
"github.com/willabides/kongplete"
|
||||||
"github.com/urfave/cli"
|
|
||||||
|
|
||||||
"github.com/syncthing/syncthing/cmd/syncthing/cmdutil"
|
"github.com/syncthing/syncthing/cmd/syncthing/cmdutil"
|
||||||
"github.com/syncthing/syncthing/lib/config"
|
"github.com/syncthing/syncthing/lib/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
type preCli struct {
|
type CLI struct {
|
||||||
|
cmdutil.CommonOptions
|
||||||
|
DataDir string `name:"data" placeholder:"PATH" env:"STDATADIR" help:"Set data directory (database and logs)"`
|
||||||
GUIAddress string `name:"gui-address"`
|
GUIAddress string `name:"gui-address"`
|
||||||
GUIAPIKey string `name:"gui-apikey"`
|
GUIAPIKey string `name:"gui-apikey"`
|
||||||
HomeDir string `name:"home"`
|
|
||||||
ConfDir string `name:"config"`
|
Show showCommand `cmd:"" help:"Show command group"`
|
||||||
DataDir string `name:"data"`
|
Debug debugCommand `cmd:"" help:"Debug command group"`
|
||||||
|
Operations operationCommand `cmd:"" help:"Operation command group"`
|
||||||
|
Errors errorsCommand `cmd:"" help:"Error command group"`
|
||||||
|
Config configCommand `cmd:"" help:"Configuration modification command group" passthrough:""`
|
||||||
|
Stdin stdinCommand `cmd:"" name:"-" help:"Read commands from stdin"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func Run() error {
|
type Context struct {
|
||||||
// This is somewhat a hack around a chicken and egg problem. We need to set
|
clientFactory *apiClientFactory
|
||||||
// the home directory and potentially other flags to know where the
|
|
||||||
// syncthing instance is running in order to get it's config ... which we
|
|
||||||
// then use to construct the actual CLI ... at which point it's too late to
|
|
||||||
// add flags there...
|
|
||||||
c := preCli{}
|
|
||||||
parseFlags(&c)
|
|
||||||
return runInternal(c, os.Args)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func RunWithArgs(cliArgs []string) error {
|
func (cli CLI) AfterApply(kongCtx *kong.Context) error {
|
||||||
c := preCli{}
|
err := cmdutil.SetConfigDataLocationsFromFlags(cli.HomeDir, cli.ConfDir, cli.DataDir)
|
||||||
parseFlagsWithArgs(cliArgs, &c)
|
|
||||||
return runInternal(c, cliArgs)
|
|
||||||
}
|
|
||||||
|
|
||||||
func runInternal(c preCli, cliArgs []string) error {
|
|
||||||
// Not set as default above because the strings can be really long.
|
|
||||||
err := cmdutil.SetConfigDataLocationsFromFlags(c.HomeDir, c.ConfDir, c.DataDir)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Command line options: %w", err)
|
return fmt.Errorf("command line options: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
clientFactory := &apiClientFactory{
|
clientFactory := &apiClientFactory{
|
||||||
cfg: config.GUIConfiguration{
|
cfg: config.GUIConfiguration{
|
||||||
RawAddress: c.GUIAddress,
|
RawAddress: cli.GUIAddress,
|
||||||
APIKey: c.GUIAPIKey,
|
APIKey: cli.GUIAPIKey,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
configCommand, err := getConfigCommand(clientFactory)
|
context := Context{
|
||||||
|
clientFactory: clientFactory,
|
||||||
|
}
|
||||||
|
|
||||||
|
kongCtx.Bind(context)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type stdinCommand struct{}
|
||||||
|
|
||||||
|
func RunWithArgs(args []string) error {
|
||||||
|
var cli CLI
|
||||||
|
p, err := kong.New(&cli)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
// can't happen, really
|
||||||
|
return fmt.Errorf("creating parser: %w", err)
|
||||||
|
}
|
||||||
|
kongplete.Complete(p)
|
||||||
|
ctx, err := p.Parse(args)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error:", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := ctx.Run(); err != nil {
|
||||||
// Implement the same flags at the upper CLI, but do nothing with them.
|
fmt.Println("Error:", err)
|
||||||
// This is so that the usage text is the same
|
|
||||||
fakeFlags := []cli.Flag{
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "gui-address",
|
|
||||||
Usage: "Override GUI address to `URL` (e.g. \"192.0.2.42:8443\")",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "gui-apikey",
|
|
||||||
Usage: "Override GUI API key to `API-KEY`",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "home",
|
|
||||||
Usage: "Set configuration and data directory to `PATH`",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "config",
|
|
||||||
Usage: "Set configuration directory (config and keys) to `PATH`",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "data",
|
|
||||||
Usage: "Set data directory (database and logs) to `PATH`",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Construct the actual CLI
|
|
||||||
app := cli.NewApp()
|
|
||||||
app.Author = "The Syncthing Authors"
|
|
||||||
app.Metadata = map[string]interface{}{
|
|
||||||
"clientFactory": clientFactory,
|
|
||||||
}
|
|
||||||
app.Commands = []cli.Command{{
|
|
||||||
Name: "cli",
|
|
||||||
Usage: "Syncthing command line interface",
|
|
||||||
Flags: fakeFlags,
|
|
||||||
Subcommands: []cli.Command{
|
|
||||||
configCommand,
|
|
||||||
showCommand,
|
|
||||||
operationCommand,
|
|
||||||
errorsCommand,
|
|
||||||
debugCommand,
|
|
||||||
{
|
|
||||||
Name: "-",
|
|
||||||
HideHelp: true,
|
|
||||||
Usage: "Read commands from stdin",
|
|
||||||
Action: func(ctx *cli.Context) error {
|
|
||||||
if ctx.NArg() > 0 {
|
|
||||||
return errors.New("command does not expect any arguments")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Drop the `-` not to recurse into self.
|
|
||||||
args := make([]string, len(cliArgs)-1)
|
|
||||||
copy(args, cliArgs)
|
|
||||||
|
|
||||||
fmt.Println("Reading commands from stdin...", args)
|
|
||||||
scanner := bufio.NewScanner(os.Stdin)
|
|
||||||
for scanner.Scan() {
|
|
||||||
input, err := shlex.Split(scanner.Text())
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("parsing input: %w", err)
|
|
||||||
}
|
|
||||||
if len(input) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
err = app.Run(append(args, input...))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return scanner.Err()
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
|
|
||||||
return app.Run(cliArgs)
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseFlags(c *preCli) error {
|
|
||||||
// kong only needs to parse the global arguments after "cli" and before the
|
|
||||||
// subcommand (if any).
|
|
||||||
if len(os.Args) <= 2 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return parseFlagsWithArgs(os.Args[2:], c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseFlagsWithArgs(args []string, c *preCli) error {
|
|
||||||
for i := 0; i < len(args); i++ {
|
|
||||||
if !strings.HasPrefix(args[i], "--") {
|
|
||||||
args = args[:i]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if !strings.Contains(args[i], "=") {
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// We don't want kong to print anything nor os.Exit (e.g. on -h)
|
|
||||||
parser, err := kong.New(c, kong.Writers(io.Discard, io.Discard), kong.Exit(func(int) {}))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = parser.Parse(args)
|
return nil
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,48 +12,43 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/alecthomas/kong"
|
||||||
"github.com/syncthing/syncthing/lib/config"
|
"github.com/syncthing/syncthing/lib/config"
|
||||||
"github.com/syncthing/syncthing/lib/fs"
|
"github.com/syncthing/syncthing/lib/fs"
|
||||||
"github.com/urfave/cli"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var operationCommand = cli.Command{
|
type folderOverrideCommand struct {
|
||||||
Name: "operations",
|
FolderID string `arg:""`
|
||||||
HideHelp: true,
|
|
||||||
Usage: "Operation command group",
|
|
||||||
Subcommands: []cli.Command{
|
|
||||||
{
|
|
||||||
Name: "restart",
|
|
||||||
Usage: "Restart syncthing",
|
|
||||||
Action: expects(0, emptyPost("system/restart")),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "shutdown",
|
|
||||||
Usage: "Shutdown syncthing",
|
|
||||||
Action: expects(0, emptyPost("system/shutdown")),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "upgrade",
|
|
||||||
Usage: "Upgrade syncthing (if a newer version is available)",
|
|
||||||
Action: expects(0, emptyPost("system/upgrade")),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "folder-override",
|
|
||||||
Usage: "Override changes on folder (remote for sendonly, local for receiveonly). WARNING: Destructive - deletes/changes your data.",
|
|
||||||
ArgsUsage: "FOLDER-ID",
|
|
||||||
Action: expects(1, foldersOverride),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "default-ignores",
|
|
||||||
Usage: "Set the default ignores (config) from a file",
|
|
||||||
ArgsUsage: "PATH",
|
|
||||||
Action: expects(1, setDefaultIgnores),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func foldersOverride(c *cli.Context) error {
|
type defaultIgnoresCommand struct {
|
||||||
client, err := getClientFactory(c).getClient()
|
Path string `arg:""`
|
||||||
|
}
|
||||||
|
|
||||||
|
type operationCommand struct {
|
||||||
|
Restart struct{} `cmd:"" help:"Restart syncthing"`
|
||||||
|
Shutdown struct{} `cmd:"" help:"Shutdown syncthing"`
|
||||||
|
Upgrade struct{} `cmd:"" help:"Upgrade syncthing (if a newer version is available)"`
|
||||||
|
FolderOverride folderOverrideCommand `cmd:"" help:"Override changes on folder (remote for sendonly, local for receiveonly). WARNING: Destructive - deletes/changes your data"`
|
||||||
|
DefaultIgnores defaultIgnoresCommand `cmd:"" help:"Set the default ignores (config) from a file"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*operationCommand) Run(ctx Context, kongCtx *kong.Context) error {
|
||||||
|
f := ctx.clientFactory
|
||||||
|
|
||||||
|
switch kongCtx.Selected().Name {
|
||||||
|
case "restart":
|
||||||
|
return emptyPost("system/restart", f)
|
||||||
|
case "shutdown":
|
||||||
|
return emptyPost("system/shutdown", f)
|
||||||
|
case "upgrade":
|
||||||
|
return emptyPost("system/upgrade", f)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *folderOverrideCommand) Run(ctx Context) error {
|
||||||
|
client, err := ctx.clientFactory.getClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -61,7 +56,7 @@ func foldersOverride(c *cli.Context) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
rid := c.Args()[0]
|
rid := f.FolderID
|
||||||
for _, folder := range cfg.Folders {
|
for _, folder := range cfg.Folders {
|
||||||
if folder.ID == rid {
|
if folder.ID == rid {
|
||||||
response, err := client.Post("db/override", "")
|
response, err := client.Post("db/override", "")
|
||||||
|
@ -86,12 +81,12 @@ func foldersOverride(c *cli.Context) error {
|
||||||
return fmt.Errorf("Folder %q not found", rid)
|
return fmt.Errorf("Folder %q not found", rid)
|
||||||
}
|
}
|
||||||
|
|
||||||
func setDefaultIgnores(c *cli.Context) error {
|
func (d *defaultIgnoresCommand) Run(ctx Context) error {
|
||||||
client, err := getClientFactory(c).getClient()
|
client, err := ctx.clientFactory.getClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
dir, file := filepath.Split(c.Args()[0])
|
dir, file := filepath.Split(d.Path)
|
||||||
filesystem := fs.NewFilesystem(fs.FilesystemTypeBasic, dir)
|
filesystem := fs.NewFilesystem(fs.FilesystemTypeBasic, dir)
|
||||||
|
|
||||||
fd, err := filesystem.Open(file)
|
fd, err := filesystem.Open(file)
|
||||||
|
|
|
@ -9,37 +9,30 @@ package cli
|
||||||
import (
|
import (
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"github.com/urfave/cli"
|
"github.com/alecthomas/kong"
|
||||||
)
|
)
|
||||||
|
|
||||||
var pendingCommand = cli.Command{
|
type pendingCommand struct {
|
||||||
Name: "pending",
|
Devices struct{} `cmd:"" help:"Show pending devices"`
|
||||||
HideHelp: true,
|
Folders struct {
|
||||||
Usage: "Pending subcommand group",
|
Device string `help:"Show pending folders offered by given device"`
|
||||||
Subcommands: []cli.Command{
|
} `cmd:"" help:"Show pending folders"`
|
||||||
{
|
|
||||||
Name: "devices",
|
|
||||||
Usage: "Show pending devices",
|
|
||||||
Action: expects(0, indexDumpOutput("cluster/pending/devices")),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "folders",
|
|
||||||
Usage: "Show pending folders",
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
cli.StringFlag{Name: "device", Usage: "Show pending folders offered by given device"},
|
|
||||||
},
|
|
||||||
Action: expects(0, folders()),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func folders() cli.ActionFunc {
|
func (p *pendingCommand) Run(ctx Context, kongCtx *kong.Context) error {
|
||||||
return func(c *cli.Context) error {
|
indexDumpOutput := indexDumpOutputWrapper(ctx.clientFactory)
|
||||||
if c.String("device") != "" {
|
|
||||||
|
switch kongCtx.Selected().Name {
|
||||||
|
case "devices":
|
||||||
|
return indexDumpOutput("cluster/pending/devices")
|
||||||
|
case "folders":
|
||||||
|
if p.Folders.Device != "" {
|
||||||
query := make(url.Values)
|
query := make(url.Values)
|
||||||
query.Set("device", c.String("device"))
|
query.Set("device", p.Folders.Device)
|
||||||
return indexDumpOutput("cluster/pending/folders?" + query.Encode())(c)
|
return indexDumpOutput("cluster/pending/folders?" + query.Encode())
|
||||||
}
|
}
|
||||||
return indexDumpOutput("cluster/pending/folders")(c)
|
return indexDumpOutput("cluster/pending/folders")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,44 +7,36 @@
|
||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/urfave/cli"
|
"github.com/alecthomas/kong"
|
||||||
)
|
)
|
||||||
|
|
||||||
var showCommand = cli.Command{
|
type showCommand struct {
|
||||||
Name: "show",
|
Version struct{} `cmd:"" help:"Show syncthing client version"`
|
||||||
HideHelp: true,
|
ConfigStatus struct{} `cmd:"" help:"Show configuration status, whether or not a restart is required for changes to take effect"`
|
||||||
Usage: "Show command group",
|
System struct{} `cmd:"" help:"Show system status"`
|
||||||
Subcommands: []cli.Command{
|
Connections struct{} `cmd:"" help:"Report about connections to other devices"`
|
||||||
{
|
Discovery struct{} `cmd:"" help:"Show the discovered addresses of remote devices (from cache of the running syncthing instance)"`
|
||||||
Name: "version",
|
Usage struct{} `cmd:"" help:"Show usage report"`
|
||||||
Usage: "Show syncthing client version",
|
Pending pendingCommand `cmd:"" help:"Pending subcommand group"`
|
||||||
Action: expects(0, indexDumpOutput("system/version")),
|
}
|
||||||
},
|
|
||||||
{
|
func (*showCommand) Run(ctx Context, kongCtx *kong.Context) error {
|
||||||
Name: "config-status",
|
indexDumpOutput := indexDumpOutputWrapper(ctx.clientFactory)
|
||||||
Usage: "Show configuration status, whether or not a restart is required for changes to take effect",
|
|
||||||
Action: expects(0, indexDumpOutput("config/restart-required")),
|
switch kongCtx.Selected().Name {
|
||||||
},
|
case "version":
|
||||||
{
|
return indexDumpOutput("system/version")
|
||||||
Name: "system",
|
case "config-status":
|
||||||
Usage: "Show system status",
|
return indexDumpOutput("config/restart-required")
|
||||||
Action: expects(0, indexDumpOutput("system/status")),
|
case "system":
|
||||||
},
|
return indexDumpOutput("system/status")
|
||||||
{
|
case "connections":
|
||||||
Name: "connections",
|
return indexDumpOutput("system/connections")
|
||||||
Usage: "Report about connections to other devices",
|
case "discovery":
|
||||||
Action: expects(0, indexDumpOutput("system/connections")),
|
return indexDumpOutput("system/discovery")
|
||||||
},
|
case "usage":
|
||||||
{
|
return indexDumpOutput("svc/report")
|
||||||
Name: "discovery",
|
}
|
||||||
Usage: "Show the discovered addresses of remote devices (from cache of the running syncthing instance)",
|
|
||||||
Action: expects(0, indexDumpOutput("system/discovery")),
|
return nil
|
||||||
},
|
|
||||||
pendingCommand,
|
|
||||||
{
|
|
||||||
Name: "usage",
|
|
||||||
Usage: "Show usage report",
|
|
||||||
Action: expects(0, indexDumpOutput("svc/report")),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,6 @@ import (
|
||||||
"github.com/syncthing/syncthing/lib/config"
|
"github.com/syncthing/syncthing/lib/config"
|
||||||
"github.com/syncthing/syncthing/lib/db/backend"
|
"github.com/syncthing/syncthing/lib/db/backend"
|
||||||
"github.com/syncthing/syncthing/lib/locations"
|
"github.com/syncthing/syncthing/lib/locations"
|
||||||
"github.com/urfave/cli"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func responseToBArray(response *http.Response) ([]byte, error) {
|
func responseToBArray(response *http.Response) ([]byte, error) {
|
||||||
|
@ -30,68 +29,72 @@ func responseToBArray(response *http.Response) ([]byte, error) {
|
||||||
return bytes, response.Body.Close()
|
return bytes, response.Body.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func emptyPost(url string) cli.ActionFunc {
|
func emptyPost(url string, apiClientFactory *apiClientFactory) error {
|
||||||
return func(c *cli.Context) error {
|
client, err := apiClientFactory.getClient()
|
||||||
client, err := getClientFactory(c).getClient()
|
if err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = client.Post(url, "")
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
_, err = client.Post(url, "")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func indexDumpOutputWrapper(apiClientFactory *apiClientFactory) func(url string) error {
|
||||||
|
return func(url string) error {
|
||||||
|
return indexDumpOutput(url, apiClientFactory)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func indexDumpOutput(url string) cli.ActionFunc {
|
func indexDumpOutput(url string, apiClientFactory *apiClientFactory) error {
|
||||||
return func(c *cli.Context) error {
|
client, err := apiClientFactory.getClient()
|
||||||
client, err := getClientFactory(c).getClient()
|
if err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
response, err := client.Get(url)
|
|
||||||
if errors.Is(err, errNotFound) {
|
|
||||||
return errors.New("not found (folder/file not in database)")
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return prettyPrintResponse(response)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func saveToFile(url string) cli.ActionFunc {
|
|
||||||
return func(c *cli.Context) error {
|
|
||||||
client, err := getClientFactory(c).getClient()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
response, err := client.Get(url)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, params, err := mime.ParseMediaType(response.Header.Get("Content-Disposition"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
filename := params["filename"]
|
|
||||||
if filename == "" {
|
|
||||||
return errors.New("Missing filename in response")
|
|
||||||
}
|
|
||||||
bs, err := responseToBArray(response)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
f, err := os.Create(filename)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
_, err = f.Write(bs)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Println("Wrote results to", filename)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
response, err := client.Get(url)
|
||||||
|
if errors.Is(err, errNotFound) {
|
||||||
|
return errors.New("not found (folder/file not in database)")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return prettyPrintResponse(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
func saveToFile(url string, apiClientFactory *apiClientFactory) error {
|
||||||
|
client, err := apiClientFactory.getClient()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
response, err := client.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, params, err := mime.ParseMediaType(response.Header.Get("Content-Disposition"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
filename := params["filename"]
|
||||||
|
if filename == "" {
|
||||||
|
return errors.New("Missing filename in response")
|
||||||
|
}
|
||||||
|
bs, err := responseToBArray(response)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f, err := os.Create(filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = f.Write(bs)
|
||||||
|
if err != nil {
|
||||||
|
_ = f.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = f.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Println("Wrote results to", filename)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getConfig(c APIClient) (config.Configuration, error) {
|
func getConfig(c APIClient) (config.Configuration, error) {
|
||||||
|
@ -111,19 +114,6 @@ func getConfig(c APIClient) (config.Configuration, error) {
|
||||||
return cfg, nil
|
return cfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func expects(n int, actionFunc cli.ActionFunc) cli.ActionFunc {
|
|
||||||
return func(ctx *cli.Context) error {
|
|
||||||
if ctx.NArg() != n {
|
|
||||||
plural := ""
|
|
||||||
if n != 1 {
|
|
||||||
plural = "s"
|
|
||||||
}
|
|
||||||
return fmt.Errorf("expected %d argument%s, got %d", n, plural, ctx.NArg())
|
|
||||||
}
|
|
||||||
return actionFunc(ctx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func prettyPrintJSON(data interface{}) error {
|
func prettyPrintJSON(data interface{}) error {
|
||||||
enc := json.NewEncoder(os.Stdout)
|
enc := json.NewEncoder(os.Stdout)
|
||||||
enc.SetIndent("", " ")
|
enc.SetIndent("", " ")
|
||||||
|
@ -159,7 +149,3 @@ func nulString(bs []byte) string {
|
||||||
func normalizePath(path string) string {
|
func normalizePath(path string) string {
|
||||||
return filepath.ToSlash(filepath.Clean(path))
|
return filepath.ToSlash(filepath.Clean(path))
|
||||||
}
|
}
|
||||||
|
|
||||||
func getClientFactory(c *cli.Context) *apiClientFactory {
|
|
||||||
return c.App.Metadata["clientFactory"].(*apiClientFactory)
|
|
||||||
}
|
|
||||||
|
|
|
@ -9,8 +9,8 @@ package cmdutil
|
||||||
// CommonOptions are reused among several subcommands
|
// CommonOptions are reused among several subcommands
|
||||||
type CommonOptions struct {
|
type CommonOptions struct {
|
||||||
buildCommonOptions
|
buildCommonOptions
|
||||||
ConfDir string `name:"config" placeholder:"PATH" help:"Set configuration directory (config and keys)"`
|
ConfDir string `name:"config" placeholder:"PATH" env:"STCONFDIR" help:"Set configuration directory (config and keys)"`
|
||||||
HomeDir string `name:"home" placeholder:"PATH" help:"Set configuration and data directory"`
|
HomeDir string `name:"home" placeholder:"PATH" env:"STHOMEDIR" help:"Set configuration and data directory"`
|
||||||
NoDefaultFolder bool `env:"STNODEFAULTFOLDER" help:"Don't create the \"default\" folder on first startup"`
|
NoDefaultFolder bool `env:"STNODEFAULTFOLDER" help:"Don't create the \"default\" folder on first startup"`
|
||||||
SkipPortProbing bool `help:"Don't try to find free ports for GUI and listen addresses on first startup"`
|
SkipPortProbing bool `help:"Don't try to find free ports for GUI and listen addresses on first startup"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
package main
|
package syncthing_main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
package main
|
package syncthing_main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
|
|
@ -4,12 +4,12 @@
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
package main
|
package syncthing_main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/syncthing/syncthing/lib/logger"
|
"github.com/syncthing/syncthing/lib/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
l = logger.DefaultLogger.NewFacility("main", "Main package")
|
l = logger.DefaultLogger.NewFacility("syncthing_main", "Syncthing package")
|
||||||
)
|
)
|
||||||
|
|
|
@ -69,7 +69,7 @@ func Generate(l logger.Logger, confDir, guiUser, guiPassword string, noDefaultFo
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := syncthing.EnsureDir(dir, 0700); err != nil {
|
if err := syncthing.EnsureDir(dir, 0o700); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
locations.SetBaseDir(locations.ConfigBaseDir, dir)
|
locations.SetBaseDir(locations.ConfigBaseDir, dir)
|
||||||
|
@ -127,7 +127,7 @@ func updateGUIAuthentication(l logger.Logger, guiCfg *config.GUIConfiguration, g
|
||||||
}
|
}
|
||||||
|
|
||||||
if guiPassword != "" && guiCfg.Password != guiPassword {
|
if guiPassword != "" && guiCfg.Password != guiPassword {
|
||||||
if err := guiCfg.HashAndSetPassword(guiPassword); err != nil {
|
if err := guiCfg.SetPassword(guiPassword); err != nil {
|
||||||
return fmt.Errorf("failed to set GUI authentication password: %w", err)
|
return fmt.Errorf("failed to set GUI authentication password: %w", err)
|
||||||
}
|
}
|
||||||
l.Infoln("Updated GUI authentication password.")
|
l.Infoln("Updated GUI authentication password.")
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
package main
|
package syncthing_main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
package main
|
package syncthing_main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
@ -22,7 +22,6 @@ import (
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
|
||||||
"runtime/pprof"
|
"runtime/pprof"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -31,9 +30,10 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/alecthomas/kong"
|
"github.com/alecthomas/kong"
|
||||||
|
_ "github.com/syncthing/syncthing/lib/automaxprocs"
|
||||||
"github.com/thejerf/suture/v4"
|
"github.com/thejerf/suture/v4"
|
||||||
|
"github.com/willabides/kongplete"
|
||||||
|
|
||||||
"github.com/syncthing/syncthing/cmd/syncthing/cli"
|
|
||||||
"github.com/syncthing/syncthing/cmd/syncthing/cmdutil"
|
"github.com/syncthing/syncthing/cmd/syncthing/cmdutil"
|
||||||
"github.com/syncthing/syncthing/cmd/syncthing/decrypt"
|
"github.com/syncthing/syncthing/cmd/syncthing/decrypt"
|
||||||
"github.com/syncthing/syncthing/cmd/syncthing/generate"
|
"github.com/syncthing/syncthing/cmd/syncthing/generate"
|
||||||
|
@ -88,9 +88,6 @@ above.
|
||||||
STTRACE A comma separated string of facilities to trace. The valid
|
STTRACE A comma separated string of facilities to trace. The valid
|
||||||
facility strings are listed below.
|
facility strings are listed below.
|
||||||
|
|
||||||
STDEADLOCKTIMEOUT Used for debugging internal deadlocks; sets debug
|
|
||||||
sensitivity. Use only under direction of a developer.
|
|
||||||
|
|
||||||
STLOCKTHRESHOLD Used for debugging internal deadlocks; sets debug
|
STLOCKTHRESHOLD Used for debugging internal deadlocks; sets debug
|
||||||
sensitivity. Use only under direction of a developer.
|
sensitivity. Use only under direction of a developer.
|
||||||
|
|
||||||
|
@ -99,6 +96,11 @@ above.
|
||||||
"minio" for the github.com/minio/sha256-simd implementation,
|
"minio" for the github.com/minio/sha256-simd implementation,
|
||||||
and blank (the default) for auto detection.
|
and blank (the default) for auto detection.
|
||||||
|
|
||||||
|
STVERSIONEXTRA Add extra information to the version string in logs and the
|
||||||
|
version line in the GUI. Can be set to the name of a wrapper
|
||||||
|
or tool controlling syncthing to communicate this to the end
|
||||||
|
user.
|
||||||
|
|
||||||
GOMAXPROCS Set the maximum number of CPU cores to use. Defaults to all
|
GOMAXPROCS Set the maximum number of CPU cores to use. Defaults to all
|
||||||
available CPU cores.
|
available CPU cores.
|
||||||
|
|
||||||
|
@ -131,10 +133,9 @@ var (
|
||||||
// commands and options here are top level commands to syncthing.
|
// commands and options here are top level commands to syncthing.
|
||||||
// Cli is just a placeholder for the help text (see main).
|
// Cli is just a placeholder for the help text (see main).
|
||||||
var entrypoint struct {
|
var entrypoint struct {
|
||||||
Serve serveOptions `cmd:"" help:"Run Syncthing"`
|
Serve serveOptions `cmd:"" help:"Run Syncthing"`
|
||||||
Generate generate.CLI `cmd:"" help:"Generate key and config, then exit"`
|
Generate generate.CLI `cmd:"" help:"Generate key and config, then exit"`
|
||||||
Decrypt decrypt.CLI `cmd:"" help:"Decrypt or verify an encrypted folder"`
|
Decrypt decrypt.CLI `cmd:"" help:"Decrypt or verify an encrypted folder"`
|
||||||
Cli struct{} `cmd:"" help:"Command line interface for Syncthing"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// serveOptions are the options for the `syncthing serve` command.
|
// serveOptions are the options for the `syncthing serve` command.
|
||||||
|
@ -144,9 +145,9 @@ type serveOptions struct {
|
||||||
Audit bool `help:"Write events to audit file"`
|
Audit bool `help:"Write events to audit file"`
|
||||||
AuditFile string `name:"auditfile" placeholder:"PATH" help:"Specify audit file (use \"-\" for stdout, \"--\" for stderr)"`
|
AuditFile string `name:"auditfile" placeholder:"PATH" help:"Specify audit file (use \"-\" for stdout, \"--\" for stderr)"`
|
||||||
BrowserOnly bool `help:"Open GUI in browser"`
|
BrowserOnly bool `help:"Open GUI in browser"`
|
||||||
DataDir string `name:"data" placeholder:"PATH" help:"Set data directory (database and logs)"`
|
DataDir string `name:"data" placeholder:"PATH" env:"STDATADIR" help:"Set data directory (database and logs)"`
|
||||||
DeviceID bool `help:"Show the device ID"`
|
DeviceID bool `help:"Show the device ID"`
|
||||||
GenerateDir string `name:"generate" placeholder:"PATH" help:"Generate key and config in specified dir, then exit"` //DEPRECATED: replaced by subcommand!
|
GenerateDir string `name:"generate" placeholder:"PATH" help:"Generate key and config in specified dir, then exit"` // DEPRECATED: replaced by subcommand!
|
||||||
GUIAddress string `name:"gui-address" placeholder:"URL" help:"Override GUI address (e.g. \"http://192.0.2.42:8443\")"`
|
GUIAddress string `name:"gui-address" placeholder:"URL" help:"Override GUI address (e.g. \"http://192.0.2.42:8443\")"`
|
||||||
GUIAPIKey string `name:"gui-apikey" placeholder:"API-KEY" help:"Override GUI API key"`
|
GUIAPIKey string `name:"gui-apikey" placeholder:"API-KEY" help:"Override GUI API key"`
|
||||||
LogFile string `name:"logfile" default:"${logFile}" placeholder:"PATH" help:"Log file name (see below)"`
|
LogFile string `name:"logfile" default:"${logFile}" placeholder:"PATH" help:"Log file name (see below)"`
|
||||||
|
@ -168,7 +169,6 @@ type serveOptions struct {
|
||||||
// Debug options below
|
// Debug options below
|
||||||
DebugDBIndirectGCInterval time.Duration `env:"STGCINDIRECTEVERY" help:"Database indirection GC interval"`
|
DebugDBIndirectGCInterval time.Duration `env:"STGCINDIRECTEVERY" help:"Database indirection GC interval"`
|
||||||
DebugDBRecheckInterval time.Duration `env:"STRECHECKDBEVERY" help:"Database metadata recalculation interval"`
|
DebugDBRecheckInterval time.Duration `env:"STRECHECKDBEVERY" help:"Database metadata recalculation interval"`
|
||||||
DebugDeadlockTimeout int `placeholder:"SECONDS" env:"STDEADLOCKTIMEOUT" help:"Used for debugging internal deadlocks"`
|
|
||||||
DebugGUIAssetsDir string `placeholder:"PATH" help:"Directory to load GUI assets from" env:"STGUIASSETS"`
|
DebugGUIAssetsDir string `placeholder:"PATH" help:"Directory to load GUI assets from" env:"STGUIASSETS"`
|
||||||
DebugPerfStats bool `env:"STPERFSTATS" help:"Write running performance statistics to perf-$pid.csv (Unix only)"`
|
DebugPerfStats bool `env:"STPERFSTATS" help:"Write running performance statistics to perf-$pid.csv (Unix only)"`
|
||||||
DebugProfileBlock bool `env:"STBLOCKPROFILE" help:"Write block profiles to block-$pid-$timestamp.pprof every 20 seconds"`
|
DebugProfileBlock bool `env:"STBLOCKPROFILE" help:"Write block profiles to block-$pid-$timestamp.pprof every 20 seconds"`
|
||||||
|
@ -207,23 +207,10 @@ func defaultVars() kong.Vars {
|
||||||
return vars
|
return vars
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func RunWithArgs(args []string) error {
|
||||||
// The "cli" subcommand uses a different command line parser, and e.g. help
|
|
||||||
// gets mangled when integrating it as a subcommand -> detect it here at the
|
|
||||||
// beginning.
|
|
||||||
if len(os.Args) > 1 && os.Args[1] == "cli" {
|
|
||||||
if err := cli.Run(); err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// First some massaging of the raw command line to fit the new model.
|
// First some massaging of the raw command line to fit the new model.
|
||||||
// Basically this means adding the default command at the front, and
|
// Basically this means adding the default command at the front, and
|
||||||
// converting -options to --options.
|
// converting -options to --options.
|
||||||
|
|
||||||
args := os.Args[1:]
|
|
||||||
switch {
|
switch {
|
||||||
case len(args) == 0:
|
case len(args) == 0:
|
||||||
// Empty command line is equivalent to just calling serve
|
// Empty command line is equivalent to just calling serve
|
||||||
|
@ -244,16 +231,26 @@ func main() {
|
||||||
|
|
||||||
// Create a parser with an overridden help function to print our extra
|
// Create a parser with an overridden help function to print our extra
|
||||||
// help info.
|
// help info.
|
||||||
parser, err := kong.New(&entrypoint, kong.Help(helpHandler), defaultVars())
|
parser, err := kong.New(
|
||||||
|
&entrypoint,
|
||||||
|
kong.ConfigureHelp(kong.HelpOptions{
|
||||||
|
NoExpandSubcommands: true,
|
||||||
|
Compact: true,
|
||||||
|
}),
|
||||||
|
kong.Help(helpHandler),
|
||||||
|
defaultVars(),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
kongplete.Complete(parser)
|
||||||
ctx, err := parser.Parse(args)
|
ctx, err := parser.Parse(args)
|
||||||
parser.FatalIfErrorf(err)
|
parser.FatalIfErrorf(err)
|
||||||
ctx.BindTo(l, (*logger.Logger)(nil)) // main logger available to subcommands
|
ctx.BindTo(l, (*logger.Logger)(nil)) // main logger available to subcommands
|
||||||
err = ctx.Run()
|
err = ctx.Run()
|
||||||
parser.FatalIfErrorf(err)
|
parser.FatalIfErrorf(err)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func helpHandler(options kong.HelpOptions, ctx *kong.Context) error {
|
func helpHandler(options kong.HelpOptions, ctx *kong.Context) error {
|
||||||
|
@ -354,7 +351,7 @@ func (options serveOptions) Run() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that our home directory exists.
|
// Ensure that our home directory exists.
|
||||||
if err := syncthing.EnsureDir(locations.GetBaseDir(locations.ConfigBaseDir), 0700); err != nil {
|
if err := syncthing.EnsureDir(locations.GetBaseDir(locations.ConfigBaseDir), 0o700); err != nil {
|
||||||
l.Warnln("Failure on home directory:", err)
|
l.Warnln("Failure on home directory:", err)
|
||||||
os.Exit(svcutil.ExitError.AsInt())
|
os.Exit(svcutil.ExitError.AsInt())
|
||||||
}
|
}
|
||||||
|
@ -621,7 +618,6 @@ func syncthingMain(options serveOptions) {
|
||||||
}
|
}
|
||||||
|
|
||||||
appOpts := syncthing.Options{
|
appOpts := syncthing.Options{
|
||||||
DeadlockTimeoutS: options.DebugDeadlockTimeout,
|
|
||||||
NoUpgrade: options.NoUpgrade,
|
NoUpgrade: options.NoUpgrade,
|
||||||
ProfilerAddr: options.DebugProfilerListen,
|
ProfilerAddr: options.DebugProfilerListen,
|
||||||
ResetDeltaIdxs: options.DebugResetDeltaIdxs,
|
ResetDeltaIdxs: options.DebugResetDeltaIdxs,
|
||||||
|
@ -632,10 +628,6 @@ func syncthingMain(options serveOptions) {
|
||||||
if options.Audit {
|
if options.Audit {
|
||||||
appOpts.AuditWriter = auditWriter(options.AuditFile)
|
appOpts.AuditWriter = auditWriter(options.AuditFile)
|
||||||
}
|
}
|
||||||
if t := os.Getenv("STDEADLOCKTIMEOUT"); t != "" {
|
|
||||||
secs, _ := strconv.Atoi(t)
|
|
||||||
appOpts.DeadlockTimeoutS = secs
|
|
||||||
}
|
|
||||||
if dur, err := time.ParseDuration(os.Getenv("STRECHECKDBEVERY")); err == nil {
|
if dur, err := time.ParseDuration(os.Getenv("STRECHECKDBEVERY")); err == nil {
|
||||||
appOpts.DBRecheckInterval = dur
|
appOpts.DBRecheckInterval = dur
|
||||||
}
|
}
|
||||||
|
@ -655,10 +647,6 @@ func syncthingMain(options serveOptions) {
|
||||||
|
|
||||||
setupSignalHandling(app)
|
setupSignalHandling(app)
|
||||||
|
|
||||||
if os.Getenv("GOMAXPROCS") == "" {
|
|
||||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
|
||||||
}
|
|
||||||
|
|
||||||
if options.DebugProfileCPU {
|
if options.DebugProfileCPU {
|
||||||
f, err := os.Create(fmt.Sprintf("cpu-%d.pprof", os.Getpid()))
|
f, err := os.Create(fmt.Sprintf("cpu-%d.pprof", os.Getpid()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -722,7 +710,6 @@ func setupSignalHandling(app *syncthing.App) {
|
||||||
func loadOrDefaultConfig() (config.Wrapper, error) {
|
func loadOrDefaultConfig() (config.Wrapper, error) {
|
||||||
cfgFile := locations.Get(locations.ConfigFile)
|
cfgFile := locations.Get(locations.ConfigFile)
|
||||||
cfg, _, err := config.Load(cfgFile, protocol.EmptyDeviceID, events.NoopLogger)
|
cfg, _, err := config.Load(cfgFile, protocol.EmptyDeviceID, events.NoopLogger)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
newCfg := config.New(protocol.EmptyDeviceID)
|
newCfg := config.New(protocol.EmptyDeviceID)
|
||||||
return config.Wrap(cfgFile, newCfg, protocol.EmptyDeviceID, events.NoopLogger), nil
|
return config.Wrap(cfgFile, newCfg, protocol.EmptyDeviceID, events.NoopLogger), nil
|
||||||
|
@ -750,7 +737,7 @@ func auditWriter(auditFile string) io.Writer {
|
||||||
} else {
|
} else {
|
||||||
auditFlags = os.O_WRONLY | os.O_CREATE | os.O_APPEND
|
auditFlags = os.O_WRONLY | os.O_CREATE | os.O_APPEND
|
||||||
}
|
}
|
||||||
fd, err = os.OpenFile(auditFile, auditFlags, 0600)
|
fd, err = os.OpenFile(auditFile, auditFlags, 0o600)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Warnln("Audit:", err)
|
l.Warnln("Audit:", err)
|
||||||
os.Exit(svcutil.ExitError.AsInt())
|
os.Exit(svcutil.ExitError.AsInt())
|
||||||
|
@ -870,6 +857,7 @@ func cleanConfigDirectory() {
|
||||||
"backup-of-v0.8": 30 * 24 * time.Hour, // these neither
|
"backup-of-v0.8": 30 * 24 * time.Hour, // these neither
|
||||||
"tmp-index-sorter.*": time.Minute, // these should never exist on startup
|
"tmp-index-sorter.*": time.Minute, // these should never exist on startup
|
||||||
"support-bundle-*": 30 * 24 * time.Hour, // keep old support bundle zip or folder for a month
|
"support-bundle-*": 30 * 24 * time.Hour, // keep old support bundle zip or folder for a month
|
||||||
|
"csrftokens.txt": 0, // deprecated, remove immediately
|
||||||
}
|
}
|
||||||
|
|
||||||
for pat, dur := range patterns {
|
for pat, dur := range patterns {
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
package main
|
package syncthing_main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
@ -346,7 +346,7 @@ func restartMonitor(binary string, args []string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func restartMonitorUnix(binary string, args []string) error {
|
func restartMonitorUnix(binary string, args []string) error {
|
||||||
return syscall.Exec(args[0], args, os.Environ())
|
return syscall.Exec(binary, args, os.Environ())
|
||||||
}
|
}
|
||||||
|
|
||||||
func restartMonitorWindows(binary string, args []string) error {
|
func restartMonitorWindows(binary string, args []string) error {
|
||||||
|
@ -521,7 +521,7 @@ func (f *autoclosedFile) ensureOpenLocked() error {
|
||||||
// We open the file for write only, and create it if it doesn't exist.
|
// We open the file for write only, and create it if it doesn't exist.
|
||||||
flags := os.O_WRONLY | os.O_CREATE | os.O_APPEND
|
flags := os.O_WRONLY | os.O_CREATE | os.O_APPEND
|
||||||
|
|
||||||
fd, err := os.OpenFile(f.name, flags, 0644)
|
fd, err := os.OpenFile(f.name, flags, 0o644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
package main
|
package syncthing_main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
//go:build !windows
|
//go:build !windows
|
||||||
// +build !windows
|
// +build !windows
|
||||||
|
|
||||||
package main
|
package syncthing_main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
//go:build windows
|
//go:build windows
|
||||||
// +build windows
|
// +build windows
|
||||||
|
|
||||||
package main
|
package syncthing_main
|
||||||
|
|
||||||
import "os/exec"
|
import "os/exec"
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
//go:build !solaris && !windows
|
//go:build !solaris && !windows
|
||||||
// +build !solaris,!windows
|
// +build !solaris,!windows
|
||||||
|
|
||||||
package main
|
package syncthing_main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
//go:build solaris || windows
|
//go:build solaris || windows
|
||||||
// +build solaris windows
|
// +build solaris windows
|
||||||
|
|
||||||
package main
|
package syncthing_main
|
||||||
|
|
||||||
func startPerfStats() {
|
func startPerfStats() {
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
//go:build go1.7
|
//go:build go1.7
|
||||||
// +build go1.7
|
// +build go1.7
|
||||||
|
|
||||||
package main
|
package syncthing_main
|
||||||
|
|
||||||
import "runtime/debug"
|
import "runtime/debug"
|
||||||
|
|
||||||
|
|
|
@ -4,10 +4,11 @@
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
package main
|
package aggregate
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
@ -15,26 +16,21 @@ import (
|
||||||
_ "github.com/lib/pq"
|
_ "github.com/lib/pq"
|
||||||
)
|
)
|
||||||
|
|
||||||
var dbConn = getEnvDefault("UR_DB_URL", "postgres://user:password@localhost/ur?sslmode=disable")
|
type CLI struct {
|
||||||
|
DBConn string `env:"UR_DB_URL" default:"postgres://user:password@localhost/ur?sslmode=disable"`
|
||||||
func getEnvDefault(key, def string) string {
|
|
||||||
if val := os.Getenv(key); val != "" {
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
return def
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func (cli *CLI) Run() error {
|
||||||
log.SetFlags(log.Ltime | log.Ldate)
|
log.SetFlags(log.Ltime | log.Ldate)
|
||||||
log.SetOutput(os.Stdout)
|
log.SetOutput(os.Stdout)
|
||||||
|
|
||||||
db, err := sql.Open("postgres", dbConn)
|
db, err := sql.Open("postgres", cli.DBConn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln("database:", err)
|
return fmt.Errorf("database: %w", err)
|
||||||
}
|
}
|
||||||
err = setupDB(db)
|
err = setupDB(db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln("database:", err)
|
return fmt.Errorf("database: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
@ -87,16 +83,6 @@ func setupDB(db *sql.DB) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS UserMovement (
|
|
||||||
Day TIMESTAMP NOT NULL,
|
|
||||||
Added INTEGER NOT NULL,
|
|
||||||
Bounced INTEGER NOT NULL,
|
|
||||||
Removed INTEGER NOT NULL
|
|
||||||
)`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS Performance (
|
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS Performance (
|
||||||
Day TIMESTAMP NOT NULL,
|
Day TIMESTAMP NOT NULL,
|
||||||
TotFiles INTEGER NOT NULL,
|
TotFiles INTEGER NOT NULL,
|
||||||
|
@ -136,11 +122,6 @@ func setupDB(db *sql.DB) error {
|
||||||
_, _ = db.Exec(`CREATE INDEX VersionDayIndex ON VersionSummary (Day)`)
|
_, _ = db.Exec(`CREATE INDEX VersionDayIndex ON VersionSummary (Day)`)
|
||||||
}
|
}
|
||||||
|
|
||||||
row = db.QueryRow(`SELECT 'MovementDayIndex'::regclass`)
|
|
||||||
if err := row.Scan(&t); err != nil {
|
|
||||||
_, _ = db.Exec(`CREATE INDEX MovementDayIndex ON UserMovement (Day)`)
|
|
||||||
}
|
|
||||||
|
|
||||||
row = db.QueryRow(`SELECT 'PerformanceDayIndex'::regclass`)
|
row = db.QueryRow(`SELECT 'PerformanceDayIndex'::regclass`)
|
||||||
if err := row.Scan(&t); err != nil {
|
if err := row.Scan(&t); err != nil {
|
||||||
_, _ = db.Exec(`CREATE INDEX PerformanceDayIndex ON Performance (Day)`)
|
_, _ = db.Exec(`CREATE INDEX PerformanceDayIndex ON Performance (Day)`)
|
||||||
|
@ -185,87 +166,6 @@ func aggregateVersionSummary(db *sql.DB, since time.Time) (int64, error) {
|
||||||
return res.RowsAffected()
|
return res.RowsAffected()
|
||||||
}
|
}
|
||||||
|
|
||||||
func aggregateUserMovement(db *sql.DB) (int64, error) {
|
|
||||||
rows, err := db.Query(`SELECT
|
|
||||||
DATE_TRUNC('day', Received) AS Day,
|
|
||||||
Report->>'uniqueID'
|
|
||||||
FROM ReportsJson
|
|
||||||
WHERE
|
|
||||||
Report->>'uniqueID' IS NOT NULL
|
|
||||||
AND Received < DATE_TRUNC('day', NOW())
|
|
||||||
AND Report->>'version' like 'v_.%'
|
|
||||||
ORDER BY Day
|
|
||||||
`)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
firstSeen := make(map[string]time.Time)
|
|
||||||
lastSeen := make(map[string]time.Time)
|
|
||||||
var minTs time.Time
|
|
||||||
minTs = minTs.In(time.UTC)
|
|
||||||
|
|
||||||
for rows.Next() {
|
|
||||||
var ts time.Time
|
|
||||||
var id string
|
|
||||||
if err := rows.Scan(&ts, &id); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if minTs.IsZero() {
|
|
||||||
minTs = ts
|
|
||||||
}
|
|
||||||
if _, ok := firstSeen[id]; !ok {
|
|
||||||
firstSeen[id] = ts
|
|
||||||
}
|
|
||||||
lastSeen[id] = ts
|
|
||||||
}
|
|
||||||
|
|
||||||
type sumRow struct {
|
|
||||||
day time.Time
|
|
||||||
added int
|
|
||||||
removed int
|
|
||||||
bounced int
|
|
||||||
}
|
|
||||||
var sumRows []sumRow
|
|
||||||
for t := minTs; t.Before(time.Now().Truncate(24 * time.Hour)); t = t.AddDate(0, 0, 1) {
|
|
||||||
var added, removed, bounced int
|
|
||||||
old := t.Before(time.Now().AddDate(0, 0, -30))
|
|
||||||
for id, first := range firstSeen {
|
|
||||||
last := lastSeen[id]
|
|
||||||
if first.Equal(t) && last.Equal(t) && old {
|
|
||||||
bounced++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if first.Equal(t) {
|
|
||||||
added++
|
|
||||||
}
|
|
||||||
if last == t && old {
|
|
||||||
removed++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sumRows = append(sumRows, sumRow{t, added, removed, bounced})
|
|
||||||
}
|
|
||||||
|
|
||||||
tx, err := db.Begin()
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
if _, err := tx.Exec("DELETE FROM UserMovement"); err != nil {
|
|
||||||
tx.Rollback()
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
for _, r := range sumRows {
|
|
||||||
if _, err := tx.Exec("INSERT INTO UserMovement (Day, Added, Removed, Bounced) VALUES ($1, $2, $3, $4)", r.day, r.added, r.removed, r.bounced); err != nil {
|
|
||||||
tx.Rollback()
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return int64(len(sumRows)), tx.Commit()
|
|
||||||
}
|
|
||||||
|
|
||||||
func aggregatePerformance(db *sql.DB, since time.Time) (int64, error) {
|
func aggregatePerformance(db *sql.DB, since time.Time) (int64, error) {
|
||||||
res, err := db.Exec(`INSERT INTO Performance (
|
res, err := db.Exec(`INSERT INTO Performance (
|
||||||
SELECT
|
SELECT
|
1170
cmd/ursrv/main.go
1170
cmd/ursrv/main.go
File diff suppressed because it is too large
Load Diff
|
@ -4,7 +4,7 @@
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
package main
|
package serve
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"regexp"
|
"regexp"
|
||||||
|
@ -145,7 +145,7 @@ func statsForFloats(data []float64) [4]float64 {
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
func group(by func(string) string, as []analytic, perGroup int) []analytic {
|
func group(by func(string) string, as []analytic, perGroup int, otherPct float64) []analytic {
|
||||||
var res []analytic
|
var res []analytic
|
||||||
|
|
||||||
next:
|
next:
|
||||||
|
@ -170,6 +170,25 @@ next:
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Sort(analyticList(res))
|
sort.Sort(analyticList(res))
|
||||||
|
|
||||||
|
if otherPct > 0 {
|
||||||
|
// Groups with less than otherPCt go into "Other"
|
||||||
|
other := analytic{
|
||||||
|
Key: "Other",
|
||||||
|
}
|
||||||
|
for i := 0; i < len(res); i++ {
|
||||||
|
if res[i].Percentage < otherPct || res[i].Key == "Other" {
|
||||||
|
other.Count += res[i].Count
|
||||||
|
other.Percentage += res[i].Percentage
|
||||||
|
res = append(res[:i], res[i+1:]...)
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if other.Count > 0 {
|
||||||
|
res = append(res, other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
package main
|
package serve
|
||||||
|
|
||||||
import "testing"
|
import "testing"
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
package main
|
package serve
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
|
@ -0,0 +1,26 @@
|
||||||
|
// Copyright (C) 2023 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 serve
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||||
|
)
|
||||||
|
|
||||||
|
var metricReportsTotal = promauto.NewCounterVec(prometheus.CounterOpts{
|
||||||
|
Namespace: "syncthing",
|
||||||
|
Subsystem: "ursrv",
|
||||||
|
Name: "reports_total",
|
||||||
|
}, []string{"version"})
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
metricReportsTotal.WithLabelValues("fail")
|
||||||
|
metricReportsTotal.WithLabelValues("duplicate")
|
||||||
|
metricReportsTotal.WithLabelValues("v1")
|
||||||
|
metricReportsTotal.WithLabelValues("v2")
|
||||||
|
metricReportsTotal.WithLabelValues("v3")
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.8 KiB |
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
|
@ -197,7 +197,7 @@ found in the LICENSE file.
|
||||||
};
|
};
|
||||||
|
|
||||||
var baseLayer = L.tileLayer(
|
var baseLayer = L.tileLayer(
|
||||||
'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',{
|
'https://tile.openstreetmap.org/{z}/{x}/{y}.png',{
|
||||||
attribution: '...',
|
attribution: '...',
|
||||||
maxZoom: 18
|
maxZoom: 18
|
||||||
}
|
}
|
||||||
|
@ -454,13 +454,13 @@ found in the LICENSE file.
|
||||||
<table class="table table-striped">
|
<table class="table table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Builder</th>
|
<th>Distribution Channel</th>
|
||||||
<th class="text-right">Devices</th>
|
<th class="text-right">Devices</th>
|
||||||
<th class="text-right">Share</th>
|
<th class="text-right">Share</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{{range .builders}}
|
{{range .distributions}}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{.Key}}</td>
|
<td>{{.Key}}</td>
|
||||||
<td class="text-right">{{.Count}}</td>
|
<td class="text-right">{{.Count}}</td>
|
||||||
|
@ -475,13 +475,13 @@ found in the LICENSE file.
|
||||||
<table class="table table-striped">
|
<table class="table table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Distribution Channel</th>
|
<th>Builder</th>
|
||||||
<th class="text-right">Devices</th>
|
<th class="text-right">Devices</th>
|
||||||
<th class="text-right">Share</th>
|
<th class="text-right">Share</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{{range .distributions}}
|
{{range .builders}}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{.Key}}</td>
|
<td>{{.Key}}</td>
|
||||||
<td class="text-right">{{.Count}}</td>
|
<td class="text-right">{{.Count}}</td>
|
||||||
|
@ -611,6 +611,7 @@ found in the LICENSE file.
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
<p>
|
<p>
|
||||||
|
<a href="https://github.com/syncthing/syncthing/tree/main/cmd/ursrv">Source code</a>.
|
||||||
This product includes GeoLite2 data created by MaxMind, available from
|
This product includes GeoLite2 data created by MaxMind, available from
|
||||||
<a href="http://www.maxmind.com">http://www.maxmind.com</a>.
|
<a href="http://www.maxmind.com">http://www.maxmind.com</a>.
|
||||||
</p>
|
</p>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue