Compare commits
639 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d57694dc04 | ||
|
|
5f40879a75 | ||
|
|
0b65a616ba | ||
|
|
6b4fe5c063 | ||
|
|
3065b127b5 | ||
|
|
74ea9c5f67 | ||
|
|
46536509d7 | ||
|
|
607bcc0b0e | ||
|
|
1950efb790 | ||
|
|
1b77ab2b52 | ||
|
|
4f367e4376 | ||
|
|
98418c9b5c | ||
|
|
3e4a90a2ba | ||
|
|
fac4dec840 | ||
|
|
79bf1f1056 | ||
|
|
9ef17322be | ||
|
|
c80e0bfc28 | ||
|
|
28d5c84599 | ||
|
|
d7c3d81dfb | ||
|
|
b033c36b31 | ||
|
|
bfc9478965 | ||
|
|
d9cb7e2739 | ||
|
|
1f8e6c55f6 | ||
|
|
1eea076f5c | ||
|
|
94beed5c10 | ||
|
|
ed6bfc5417 | ||
|
|
04ff890263 | ||
|
|
9c0825c0d9 | ||
|
|
f78133b8e9 | ||
|
|
b784f5b9e3 | ||
|
|
1c089a4d11 | ||
|
|
baa38eea7a | ||
|
|
c3b5eba205 | ||
|
|
cf75329067 | ||
|
|
8343db6766 | ||
|
|
8c74177699 | ||
|
|
9a5f7fbadf | ||
|
|
8f344d0915 | ||
|
|
77dd874383 | ||
|
|
5b34c31cb3 | ||
|
|
668979605b | ||
|
|
5ffa012410 | ||
|
|
5c54d879a1 | ||
|
|
1c5af3a4bd | ||
|
|
22c222bf75 | ||
|
|
651ee2ce74 | ||
|
|
5c9dc4c883 | ||
|
|
258341f8bf | ||
|
|
f5ca213682 | ||
|
|
a1c5b44c74 | ||
|
|
de9489585f | ||
|
|
438f687591 | ||
|
|
7a8cc5fc99 | ||
|
|
f05ccd775a | ||
|
|
e5cc55ce09 | ||
|
|
299b9d8883 | ||
|
|
074097a8e7 | ||
|
|
ee445e35a0 | ||
|
|
3ad049184e | ||
|
|
6768daec07 | ||
|
|
974551375e | ||
|
|
531ceb2b0f | ||
|
|
ba4462a70b | ||
|
|
8419c05794 | ||
|
|
c84f60f949 | ||
|
|
6201eebc98 | ||
|
|
decb967969 | ||
|
|
aac3750298 | ||
|
|
a94951becd | ||
|
|
7dc290c3ed | ||
|
|
50faa8f7ef | ||
|
|
5c87ceb392 | ||
|
|
6ffc8255b6 | ||
|
|
3354e60461 | ||
|
|
06365e5635 | ||
|
|
da99203dcd | ||
|
|
57ea8a1bf5 | ||
|
|
f72832d591 | ||
|
|
c0c18a568c | ||
|
|
b0b3abf76b | ||
|
|
c20ed80dc4 | ||
|
|
e2febf246e | ||
|
|
2cdeb1bf70 | ||
|
|
876609a0f0 | ||
|
|
744ef0d8ac | ||
|
|
8d6fb86ee0 | ||
|
|
13c3dac89c | ||
|
|
b5fc332782 | ||
|
|
4a8a8b294d | ||
|
|
92905d30e8 | ||
|
|
914eb77ca4 | ||
|
|
f560e8c850 | ||
|
|
2e3975e956 | ||
|
|
bd0c2bf237 | ||
|
|
f86deedd9c | ||
|
|
782bd08aad | ||
|
|
22242d51be | ||
|
|
ac7338f1f2 | ||
|
|
bdb25f9ba5 | ||
|
|
0e2a07d71a | ||
|
|
5e1cd0e71a | ||
|
|
5224f07ac8 | ||
|
|
d9664a946d | ||
|
|
8c61e0d6ab | ||
|
|
6c73617974 | ||
|
|
037934ec74 | ||
|
|
78a741d0be | ||
|
|
ebad9e2073 | ||
|
|
d68fa84055 | ||
|
|
d3ed4de4ed | ||
|
|
6bbd24de12 | ||
|
|
c63ca4f563 | ||
|
|
0e5ba3ca05 | ||
|
|
44b0f0b456 | ||
|
|
4aa2199d5b | ||
|
|
49798552f2 | ||
|
|
171b8139ab | ||
|
|
7fa699e159 | ||
|
|
5373e38ac8 | ||
|
|
41ef945b2b | ||
|
|
81ff31b8fc | ||
|
|
82fbcb96f8 | ||
|
|
37ede49077 | ||
|
|
0ba3abdee4 | ||
|
|
ab92f8520c | ||
|
|
0e67c036bb | ||
|
|
046bbdfbd4 | ||
|
|
c6c74e8291 | ||
|
|
59b1b0e1dc | ||
|
|
4b17c511f9 | ||
|
|
0f532a5607 | ||
|
|
df318ed370 | ||
|
|
0275cbd66a | ||
|
|
670a9809fa | ||
|
|
07ce3572a0 | ||
|
|
b64052bc26 | ||
|
|
7956e7d0ef | ||
|
|
d400e51422 | ||
|
|
674a99e9ae | ||
|
|
88cabb9e0a | ||
|
|
b7ba401c0b | ||
|
|
7505ea79a0 | ||
|
|
e1324a0e23 | ||
|
|
b7b9476e5a | ||
|
|
d1db7e3dd2 | ||
|
|
362da59396 | ||
|
|
66262392c3 | ||
|
|
48f9d323fa | ||
|
|
f69c0b550c | ||
|
|
32245435e2 | ||
|
|
2658369051 | ||
|
|
d50adb225b | ||
|
|
7da898f2d6 | ||
|
|
0d919bd79c | ||
|
|
f91e90a94f | ||
|
|
7ce20f197b | ||
|
|
2f26a95973 | ||
|
|
123941cf62 | ||
|
|
9c67d57c28 | ||
|
|
5b3466dc6e | ||
|
|
bca6854c03 | ||
|
|
c930b2e9e2 | ||
|
|
d7a257b391 | ||
|
|
7709ac33a7 | ||
|
|
1e2379df1b | ||
|
|
ea5c9176e1 | ||
|
|
cc1b003f21 | ||
|
|
38bd90e6f2 | ||
|
|
1c47fae206 | ||
|
|
79a758be3c | ||
|
|
c7cf3ef899 | ||
|
|
2c2e6cd0d5 | ||
|
|
b7dffc051e | ||
|
|
963e9a4071 | ||
|
|
b28899ac07 | ||
|
|
83f6da8dca | ||
|
|
1a29296d9d | ||
|
|
a7de4c68e3 | ||
|
|
7f23de4f03 | ||
|
|
ca89f12be6 | ||
|
|
ddfa82e990 | ||
|
|
61302c467c | ||
|
|
d6b4873eed | ||
|
|
1ea98a16b1 | ||
|
|
e2f3500df9 | ||
|
|
8b025af1e5 | ||
|
|
f1b253fc00 | ||
|
|
c4abe6f815 | ||
|
|
4c5e9cf921 | ||
|
|
b33d5e57c6 | ||
|
|
0060840249 | ||
|
|
71faae67f2 | ||
|
|
74706bb02b | ||
|
|
5975772ed8 | ||
|
|
cf11fa4327 | ||
|
|
40580d8b9b | ||
|
|
e25e71cdde | ||
|
|
00b2340f9a | ||
|
|
cc2a55892f | ||
|
|
80107d5f5e | ||
|
|
f10e85d0c2 | ||
|
|
f4a6e4439a | ||
|
|
2ae3ea0d52 | ||
|
|
1e68ab3f90 | ||
|
|
f08d09f607 | ||
|
|
e053db6a5e | ||
|
|
1bd4ea0cbb | ||
|
|
a1cb1d70c4 | ||
|
|
c101a04179 | ||
|
|
16698b12b1 | ||
|
|
0bc571b2fd | ||
|
|
20aaa5927b | ||
|
|
d612c35290 | ||
|
|
5ab257fb60 | ||
|
|
db02545ef3 | ||
|
|
2faa1ad360 | ||
|
|
860ae7f395 | ||
|
|
135c71ca87 | ||
|
|
c7d6a6d780 | ||
|
|
92533dd9f0 | ||
|
|
dd92b2b8f4 | ||
|
|
eddc8d3ff2 | ||
|
|
dfdd5af7a6 | ||
|
|
6b5c281dd5 | ||
|
|
52e72e0122 | ||
|
|
c08e253e7c | ||
|
|
d1e0a38c04 | ||
|
|
ac19cdb2cd | ||
|
|
58607486af | ||
|
|
0b610017ea | ||
|
|
5de6f6d349 | ||
|
|
daf05c6509 | ||
|
|
9a1df97c69 | ||
|
|
ee61da5b6a | ||
|
|
883497966e | ||
|
|
4f7a77597e | ||
|
|
c4b9046eaa | ||
|
|
299a80d328 | ||
|
|
4e4b9a872a | ||
|
|
cb624dbf5d | ||
|
|
71aecc5cd4 | ||
|
|
10af09e4b4 | ||
|
|
680b0b14db | ||
|
|
55238e3b5b | ||
|
|
f0e33d052a | ||
|
|
7b8622c2e9 | ||
|
|
40e1835927 | ||
|
|
a5e12a0a3d | ||
|
|
10cb14fcb8 | ||
|
|
4f29180e7c | ||
|
|
32e12abb43 | ||
|
|
4cc1b7f42c | ||
|
|
0fb2cd52ff | ||
|
|
6489feb1d7 | ||
|
|
a4bd4d118a | ||
|
|
fae7425bbf | ||
|
|
7b5551248a | ||
|
|
4026625c2d | ||
|
|
3e0241ea31 | ||
|
|
bb375b1aff | ||
|
|
05e23f1991 | ||
|
|
71de6fe290 | ||
|
|
6a840a040b | ||
|
|
c3637f2191 | ||
|
|
ca90f4e6af | ||
|
|
51fa36d61f | ||
|
|
d95a087829 | ||
|
|
a728743c86 | ||
|
|
ce27780a4c | ||
|
|
0df39ddc72 | ||
|
|
b84aa114be | ||
|
|
a596e5e2f0 | ||
|
|
04e648fee6 | ||
|
|
29736b1e33 | ||
|
|
b61da487e4 | ||
|
|
b4064e07dc | ||
|
|
b4dc15bc06 | ||
|
|
3304e0f832 | ||
|
|
b8a5e1a244 | ||
|
|
5823e7a5ce | ||
|
|
55937b61ca | ||
|
|
9cef283151 | ||
|
|
e0d4cdc9a3 | ||
|
|
ac3879e2b0 | ||
|
|
d91c4b010b | ||
|
|
84920bff63 | ||
|
|
bf4c8439e8 | ||
|
|
8fc2dfad0c | ||
|
|
17b441c993 | ||
|
|
b9879e2013 | ||
|
|
08f0e125ef | ||
|
|
d62a0cf692 | ||
|
|
ddd26f5c42 | ||
|
|
69da11a263 | ||
|
|
879d757850 | ||
|
|
6c8e8f0391 | ||
|
|
39891cdf42 | ||
|
|
e782bab9fc | ||
|
|
9cc49aea77 | ||
|
|
29690502f0 | ||
|
|
d323e9c106 | ||
|
|
a79de840bd | ||
|
|
f454e8b609 | ||
|
|
c6cef168a5 | ||
|
|
4de6b94de7 | ||
|
|
08bb730ad0 | ||
|
|
1b52197f71 | ||
|
|
71882765f2 | ||
|
|
119d76d035 | ||
|
|
08753ccabe | ||
|
|
ceb9475668 | ||
|
|
f56a5545d4 | ||
|
|
7a8e73d599 | ||
|
|
1e69c31d87 | ||
|
|
3dc3e01f80 | ||
|
|
fb5f1bb56a | ||
|
|
0f1e0eff05 | ||
|
|
a963bc8b86 | ||
|
|
de64ffddab | ||
|
|
8140350094 | ||
|
|
82ed8e702c | ||
|
|
fca2876795 | ||
|
|
633ddba2b2 | ||
|
|
325c3c1fa7 | ||
|
|
be0508cf26 | ||
|
|
6fd5e78740 | ||
|
|
a9e490adfa | ||
|
|
d8e7e92512 | ||
|
|
eca156fd7f | ||
|
|
b3fd9a8d53 | ||
|
|
0bec01b827 | ||
|
|
e82a7e3dfa | ||
|
|
928767e316 | ||
|
|
1c277fc096 | ||
|
|
c71116ee94 | ||
|
|
a5bbc12625 | ||
|
|
606154b183 | ||
|
|
f9c380d45b | ||
|
|
a04f54a16a | ||
|
|
509d123251 | ||
|
|
b32821a586 | ||
|
|
8ced8ad562 | ||
|
|
1bae4b7f50 | ||
|
|
4e151d380c | ||
|
|
f747ba6d69 | ||
|
|
33258b06f4 | ||
|
|
4340589501 | ||
|
|
4d368a37e2 | ||
|
|
999647b7d6 | ||
|
|
45a711570e | ||
|
|
cf312abc72 | ||
|
|
e2f6d0d6c4 | ||
|
|
65d4dd32cb | ||
|
|
de886b3f22 | ||
|
|
8c91e012c7 | ||
|
|
6d27cf6563 | ||
|
|
57d668ed1d | ||
|
|
90d85fd0a2 | ||
|
|
552ea68672 | ||
|
|
80eac473d9 | ||
|
|
c1db8b2680 | ||
|
|
df866e10c8 | ||
|
|
0d14ee4142 | ||
|
|
28edf2f5bb | ||
|
|
e7100bc573 | ||
|
|
5edf4660e2 | ||
|
|
a5699d40a8 | ||
|
|
f80ce17497 | ||
|
|
ce72bee576 | ||
|
|
0cc77feabb | ||
|
|
d19b12d3fe | ||
|
|
1d406d62e3 | ||
|
|
1d99e5277a | ||
|
|
879f51b027 | ||
|
|
d3d7408b17 | ||
|
|
9b01e64c66 | ||
|
|
65c172cd8d | ||
|
|
85e6a77f25 | ||
|
|
88244b0c1f | ||
|
|
cd290d2d05 | ||
|
|
bee7cce081 | ||
|
|
f15a1528fc | ||
|
|
6be6de4b4a | ||
|
|
6755a9ca63 | ||
|
|
98a1adebe1 | ||
|
|
31569debeb | ||
|
|
cf420e135e | ||
|
|
3b5dff3f34 | ||
|
|
56cdf2f2d9 | ||
|
|
b1dbe925d4 | ||
|
|
bbdda059bd | ||
|
|
72f26c1e45 | ||
|
|
72194d137c | ||
|
|
8b5bd45a29 | ||
|
|
9084510e1b | ||
|
|
c4f161d8c5 | ||
|
|
ad2d3702ae | ||
|
|
95acb26249 | ||
|
|
4736cccda1 | ||
|
|
1a06ab68eb | ||
|
|
b8907b49f9 | ||
|
|
7b33294955 | ||
|
|
031684116b | ||
|
|
a0c9db1d09 | ||
|
|
aa4b918224 | ||
|
|
7043b1fbba | ||
|
|
9d6b663d1c | ||
|
|
7dc4ac6e1f | ||
|
|
7bad9b3a11 | ||
|
|
6b570ee8dc | ||
|
|
6408a116f9 | ||
|
|
67b8ef1f3e | ||
|
|
999d4a0e23 | ||
|
|
96bb1c8e29 | ||
|
|
8fb576ed54 | ||
|
|
5e31e6356f | ||
|
|
1b5a61e03e | ||
|
|
755e689627 | ||
|
|
3f5c9b578c | ||
|
|
a2a14c8424 | ||
|
|
cff7a091f5 | ||
|
|
757d9a5333 | ||
|
|
2c88e473cb | ||
|
|
875377981d | ||
|
|
52d80d8144 | ||
|
|
fd2e91c82d | ||
|
|
c744a75cdd | ||
|
|
35b699dc77 | ||
|
|
7127c13f18 | ||
|
|
dab29287da | ||
|
|
c0b5a70ce3 | ||
|
|
7bcdc5b08e | ||
|
|
db0ba2555a | ||
|
|
1398fbb681 | ||
|
|
f653f540f8 | ||
|
|
078923bd1a | ||
|
|
80a83b605c | ||
|
|
28b6e8b063 | ||
|
|
f7b2e79fdc | ||
|
|
c0b3de2680 | ||
|
|
9a9bcff3e9 | ||
|
|
88482b29ee | ||
|
|
22dff7207c | ||
|
|
0104e78589 | ||
|
|
80894948f6 | ||
|
|
e945e65b13 | ||
|
|
ebd2e5bf30 | ||
|
|
60c07b259e | ||
|
|
fe50f1a158 | ||
|
|
5851aabe02 | ||
|
|
0832285d79 | ||
|
|
c2ea9d119d | ||
|
|
534f07d9ca | ||
|
|
24d4290d03 | ||
|
|
09b872cef4 | ||
|
|
2d124e053c | ||
|
|
90b70c7a16 | ||
|
|
e910acdc17 | ||
|
|
96350d7600 | ||
|
|
77a5980747 | ||
|
|
b677464dfa | ||
|
|
b1c74860e8 | ||
|
|
f6f696c6c5 | ||
|
|
cf40ed6cec | ||
|
|
6fa02d5081 | ||
|
|
86e35f1879 | ||
|
|
720a6bf62e | ||
|
|
4a619e74f2 | ||
|
|
58ef5368f8 | ||
|
|
7b37d453f9 | ||
|
|
edf2399ce6 | ||
|
|
d43b0a4395 | ||
|
|
f2efd08e0f | ||
|
|
61b9f7bd55 | ||
|
|
1475c0344a | ||
|
|
77cc87dfca | ||
|
|
bc7dd02e2b | ||
|
|
8a06cf0973 | ||
|
|
05835ed81f | ||
|
|
df522576ac | ||
|
|
d681ac11fe | ||
|
|
7d5f7d508d | ||
|
|
1d182e4631 | ||
|
|
710f5c199f | ||
|
|
fd847d4efe | ||
|
|
15e51fc045 | ||
|
|
c1c976aa2b | ||
|
|
159d1a68e1 | ||
|
|
dd850f66bb | ||
|
|
d0c3697152 | ||
|
|
669bcb748f | ||
|
|
4e22a96602 | ||
|
|
a992559abc | ||
|
|
46e72d76b5 | ||
|
|
7a4c88d4e4 | ||
|
|
35f40e9a58 | ||
|
|
5de9b677c2 | ||
|
|
6f08162376 | ||
|
|
7b3d9a8dca | ||
|
|
942659fb06 | ||
|
|
15c262184b | ||
|
|
484fa0592e | ||
|
|
b5b54ff057 | ||
|
|
4d3432af3e | ||
|
|
1cb55904bc | ||
|
|
2b622d0774 | ||
|
|
1894123d3c | ||
|
|
e7e177a6fa | ||
|
|
eed1edcca0 | ||
|
|
0025e9ccfb | ||
|
|
82b70b9fae | ||
|
|
def4b8cee5 | ||
|
|
f1a7dd766e | ||
|
|
20c8dbd9ed | ||
|
|
4b3f9b1af9 | ||
|
|
3446d50201 | ||
|
|
9fef1552fc | ||
|
|
85318f3b82 | ||
|
|
485acda63b | ||
|
|
05e9e0bfa9 | ||
|
|
ba056578ec | ||
|
|
d0ab65a178 | ||
|
|
4cba433852 | ||
|
|
863fe23347 | ||
|
|
43b6ac9501 | ||
|
|
1cf352a722 | ||
|
|
b58f6ca886 | ||
|
|
5cbc9089fd | ||
|
|
20eab36a33 | ||
|
|
2b4df6b874 | ||
|
|
3c7e7e971d | ||
|
|
afde0727fe | ||
|
|
bf744ded31 | ||
|
|
0d86166890 | ||
|
|
cea5962417 | ||
|
|
02752af862 | ||
|
|
6b1d7ac727 | ||
|
|
abd363e8bb | ||
|
|
bff1a5f5e4 | ||
|
|
38302270d4 | ||
|
|
1b4fe39a89 | ||
|
|
6b74cdc613 | ||
|
|
13a746e0fb | ||
|
|
21f50e2f8f | ||
|
|
b7c70a9817 | ||
|
|
42ce6be9b9 | ||
|
|
93e57bd357 | ||
|
|
eb4fe808c5 | ||
|
|
1054ce9354 | ||
|
|
97ad575b1f | ||
|
|
ee746263fb | ||
|
|
41ff4b323e | ||
|
|
997bb5e7e1 | ||
|
|
ca2fa8de4e | ||
|
|
64185484b2 | ||
|
|
5541697d18 | ||
|
|
e39d3f95dd | ||
|
|
6e8aa0ec25 | ||
|
|
97057eb9de | ||
|
|
6664e01acf | ||
|
|
e2a647a6a4 | ||
|
|
5ce5b2c94a | ||
|
|
e714df013f | ||
|
|
d8fa61e27c | ||
|
|
d3f583c8c9 | ||
|
|
e096a14ff5 | ||
|
|
3775a64d5c | ||
|
|
129df0613b | ||
|
|
486230768e | ||
|
|
9e6db72535 | ||
|
|
d91da8feee | ||
|
|
64518b0f7e | ||
|
|
5d35b2c540 | ||
|
|
224775ca81 | ||
|
|
98cda96b1c | ||
|
|
5b306510a0 | ||
|
|
f593ac387c | ||
|
|
eb8df7f632 | ||
|
|
78d6eee74a | ||
|
|
1b2b970f32 | ||
|
|
5ffbb7668d | ||
|
|
1df8701c46 | ||
|
|
cc36621b11 | ||
|
|
441ea109a1 | ||
|
|
8015f3f937 | ||
|
|
2c866277a2 | ||
|
|
1da9317a09 | ||
|
|
e16a65bacb | ||
|
|
c02aed0a21 | ||
|
|
e4956358fb | ||
|
|
ad44d77a21 | ||
|
|
6bb5d140fa | ||
|
|
d082b2ba4c | ||
|
|
638789899c | ||
|
|
dfbbb286fc | ||
|
|
fbd445fe0a | ||
|
|
2b246eeb52 | ||
|
|
2558b021e5 | ||
|
|
31be810eb6 | ||
|
|
62a6d619e7 | ||
|
|
a04fcfe749 | ||
|
|
283f39ae5f | ||
|
|
59e1349499 | ||
|
|
b45d77b6be | ||
|
|
79e67b7f79 | ||
|
|
36a4a9fd34 | ||
|
|
92ed31fe21 | ||
|
|
5fa8467756 | ||
|
|
5954b105cd | ||
|
|
defc5dca65 | ||
|
|
9f358ecae0 | ||
|
|
fe4daf242b | ||
|
|
ec7c88ca55 | ||
|
|
26e6d94c00 | ||
|
|
e2470c8bc8 | ||
|
|
19b51c9b92 | ||
|
|
0ca1f26ff8 | ||
|
|
2984d40641 | ||
|
|
5da41f75fa | ||
|
|
32dec4a00d | ||
|
|
04b927104f | ||
|
|
110806842c | ||
|
|
d9b3415dec | ||
|
|
d3d43d90f6 | ||
|
|
e7d11adf3c | ||
|
|
afeb606b5b | ||
|
|
c6a179fa4d | ||
|
|
e302ccf4b4 | ||
|
|
926e9228ed | ||
|
|
86e72d9973 | ||
|
|
952c8becf5 | ||
|
|
32ee8d783d | ||
|
|
3bea59b0d9 | ||
|
|
8a4b65b937 | ||
|
|
fca895a632 | ||
|
|
79360e2205 | ||
|
|
9b2a73f9ab | ||
|
|
c305265c62 | ||
|
|
1954239ffa |
42
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,42 +0,0 @@
|
||||
### DO NOT REPORT SECURITY ISSUES IN THIS ISSUE TRACKER
|
||||
|
||||
Instead, contact security@syncthing.net directly - see
|
||||
https://syncthing.net/security.html for more information.
|
||||
|
||||
### DO NOT POST SUPPORT REQUESTS OR GENERAL QUESTIONS IN THIS ISSUE TRACKER
|
||||
|
||||
Please use the forum at https://forum.syncthing.net/ where a large number of
|
||||
helpful people hang out. This issue tracker is for reporting bugs or feature
|
||||
requests directly to the developers. Worst case you might get a short
|
||||
"that's a bug, please report it on GitHub" response on the forum, in which
|
||||
case we thank you for your patience and following our advice. :)
|
||||
|
||||
### Please use the correct issue tracker
|
||||
|
||||
If your problem relates to a Syncthing wrapper or [sub-project](https://github.com/syncthing) such as [Syncthing for Android](https://github.com/syncthing/syncthing-android/issues), [SyncTrayzor](https://github.com/canton7/synctrayzor) or the [documentation](https://github.com/syncthing/docs/issues), please use their respective issue trackers.
|
||||
|
||||
### 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`.
|
||||
|
||||
### Please do post actual bug reports and feature requests.
|
||||
|
||||
If your issue is a bug report, replace this boilerplate with a description
|
||||
of the problem, being sure to include at least:
|
||||
|
||||
- what happened,
|
||||
- what you expected to happen instead, and
|
||||
- any steps to reproduce the problem.
|
||||
|
||||
Also fill out the version information below and add log output or
|
||||
screenshots as appropriate.
|
||||
|
||||
If your issue is a feature request, simply replace this template text in
|
||||
its entirety.
|
||||
|
||||
### Version Information
|
||||
|
||||
Syncthing Version: v0.x.y
|
||||
OS Version: Windows 7 / Ubuntu 14.04 / ...
|
||||
Browser Version: (if applicable, for GUI issues)
|
||||
|
||||
13
.github/ISSUE_TEMPLATE/01-feature.md
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
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
|
||||
23
.github/ISSUE_TEMPLATE/02-bug.md
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
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.
|
||||
8
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Ask a question
|
||||
url: https://forum.syncthing.net/
|
||||
about: Ask questions, get support, and discuss with other community members.
|
||||
- name: Android issues
|
||||
url: https://github.com/syncthing/syncthing-android/issues/
|
||||
about: The Android app has its own issue tracker.
|
||||
10
.github/SECURITY.md
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you believe that you've found a Syncthing-related security vulnerability,
|
||||
please report it by sending email to the address security@syncthing.net. The
|
||||
[PGP key for security@syncthing.net
|
||||
(B683AD7B76CAB013)](https://syncthing.net/security-key.txt) can be used to
|
||||
send encrypted mail or to verify responses received from that address.
|
||||
|
||||
You can read more about Syncthing security at
|
||||
https://syncthing.net/security/.
|
||||
7
.gitignore
vendored
@@ -15,12 +15,5 @@ coverage.xml
|
||||
syncthing.sig
|
||||
RELEASE
|
||||
deb
|
||||
lib/auto/gui.files.go
|
||||
snapcraft.yaml
|
||||
prime/
|
||||
snap/
|
||||
parts/
|
||||
stage/
|
||||
*.snap
|
||||
*.bz2
|
||||
/repos
|
||||
|
||||
@@ -1,5 +1,26 @@
|
||||
linters-settings:
|
||||
maligned:
|
||||
suggest-new: true
|
||||
|
||||
linters:
|
||||
enable-all: true
|
||||
disable:
|
||||
- goimports
|
||||
- depguard
|
||||
- lll
|
||||
- gochecknoinits
|
||||
- gochecknoglobals
|
||||
- gofmt
|
||||
- scopelint
|
||||
- gocyclo
|
||||
- funlen
|
||||
- wsl
|
||||
- gocognit
|
||||
- godox
|
||||
|
||||
service:
|
||||
golangci-lint-version: 1.13.x
|
||||
golangci-lint-version: 1.21.x
|
||||
prepare:
|
||||
- rm -f go.sum # 1.12 -> 1.13 issues with QUIC-go
|
||||
- GO111MODULE=on go mod vendor
|
||||
- go run build.go assets
|
||||
- GO111MODULE=on go mod vendor
|
||||
56
AUTHORS
@@ -16,20 +16,29 @@
|
||||
Aaron Bieber (qbit) <qbit@deftly.net>
|
||||
Adam Piggott (ProactiveServices) <aD@simplypeachy.co.uk> <simplypeachy@users.noreply.github.com> <ProactiveServices@users.noreply.github.com> <adam@proactiveservices.co.uk>
|
||||
Adel Qalieh (adelq) <aqalieh95@gmail.com> <adelq@users.noreply.github.com>
|
||||
Alan Pope <alan@popey.com>
|
||||
Alberto Donato <albertodonato@users.noreply.github.com>
|
||||
Alessandro G. (alessandro.g89) <alessandro.g89@gmail.com>
|
||||
Alex Xu <alex.hello71@gmail.com>
|
||||
Alexander Graf (alex2108) <register-github@alex-graf.de>
|
||||
Alexandre Viau (aviau) <alexandre@alexandreviau.net> <aviau@debian.org>
|
||||
Aman Gupta <aman@tmm1.net>
|
||||
Anderson Mesquita (andersonvom) <andersonvom@gmail.com>
|
||||
andresvia <andres.via@gmail.com>
|
||||
Andrew Dunham (andrew-d) <andrew@du.nham.ca>
|
||||
Andrew Rabert (nvllsvm) <ar@nullsum.net> <6550543+nvllsvm@users.noreply.github.com>
|
||||
Andrey D (scienmind) <scintertech@cryptolab.net> <scienmind@users.noreply.github.com>
|
||||
André Colomb (acolomb) <src@andre.colomb.de> <github.com@andre.colomb.de>
|
||||
andyleap <andyleap@gmail.com>
|
||||
Anjan Momi <anjan@momi.ca>
|
||||
Antoine Lamielle (0x010C) <antoine.lamielle@0x010c.fr> <gh@0x010c.fr>
|
||||
Antony Male (canton7) <antony.male@gmail.com>
|
||||
Aranjedeath <Aranjedeath@users.noreply.github.com>
|
||||
Arkadiusz Tymiński <gevleeog@gmail.com>
|
||||
Arthur Axel fREW Schmidt (frioux) <frew@afoolishmanifesto.com> <frioux@gmail.com>
|
||||
Audrius Butkevicius (AudriusButkevicius) <audrius.butkevicius@gmail.com>
|
||||
Artur Zubilewicz <AkaZecik@users.noreply.github.com>
|
||||
Audrius Butkevicius (AudriusButkevicius) <audrius.butkevicius@gmail.com> <github@audrius.rocks>
|
||||
Aurélien Rainone <476650+arl@users.noreply.github.com>
|
||||
BAHADIR YILMAZ <bahadiryilmaz32@gmail.com>
|
||||
Bart De Vries (mogwa1) <devriesb@gmail.com>
|
||||
Ben Curthoys (bencurthoys) <ben@bencurthoys.com>
|
||||
@@ -40,21 +49,26 @@ Benedikt Heine (bebehei) <bebe@bebehei.de>
|
||||
Benedikt Morbach <benedikt.morbach@googlemail.com>
|
||||
Benno Fünfstück <benno.fuenfstueck@gmail.com>
|
||||
Benny Ng (tpng) <benny.tpng@gmail.com>
|
||||
boomsquared <54829195+boomsquared@users.noreply.github.com>
|
||||
Boqin Qin <bobbqqin@bupt.edu.cn>
|
||||
Boris Rybalkin <ribalkin@gmail.com>
|
||||
Brandon Philips (philips) <brandon@ifup.org>
|
||||
Brendan Long (brendanlong) <self@brendanlong.com>
|
||||
Brian R. Becker (brbecker) <brbecker@gmail.com>
|
||||
Caleb Callaway (cqcallaw) <enlightened.despot@gmail.com>
|
||||
Carsten Hagemann (Moter8) <moter8@gmail.com>
|
||||
Carsten Hagemann (carstenhag) <moter8@gmail.com> <carsten@chagemann.de>
|
||||
Cathryne Linenweaver (Cathryne) <cathryne.linenweaver@gmail.com> <Cathryne@users.noreply.github.com> <katrinleinweber@MAC.local>
|
||||
Cedric Staniewski (xduugu) <cedric@gmx.ca>
|
||||
chenrui <rui@meetup.com>
|
||||
Chris Howie (cdhowie) <me@chrishowie.com>
|
||||
Chris Joel (cdata) <chris@scriptolo.gy>
|
||||
Chris Tonkinson <chris@masterbran.ch>
|
||||
chucic <chucic@seznam.cz>
|
||||
Colin Kennedy (moshen) <moshen.colin@gmail.com>
|
||||
Cromefire_ <tim.l@nghorst.net>
|
||||
Cromefire_ <tim.l@nghorst.net> <26320625+cromefire@users.noreply.github.com>
|
||||
Cyprien Devillez <cypx@users.noreply.github.com>
|
||||
Dale Visser <dale.visser@live.com>
|
||||
Dan <benda.daniel@gmail.com>
|
||||
Daniel Bergmann (brgmnn) <dan.arne.bergmann@gmail.com> <brgmnn@users.noreply.github.com>
|
||||
Daniel Harte (norgeous) <daniel@harte.me> <daniel@danielharte.co.uk> <norgeous@users.noreply.github.com>
|
||||
Daniel Martí (mvdan) <mvdan@mvdan.cc>
|
||||
@@ -62,9 +76,12 @@ Darshil Chanpura (dtchanpura) <dtchanpura@gmail.com> <dcprime314@gmail.com>
|
||||
David Rimmer (dinosore) <dinosore@dbrsoftware.co.uk>
|
||||
Denis A. (dva) <denisva@gmail.com>
|
||||
Dennis Wilson (snnd) <dw@risu.io>
|
||||
dependabot-preview[bot] <dependabot-preview[bot]@users.noreply.github.com> <27856297+dependabot-preview[bot]@users.noreply.github.com>
|
||||
dependabot[bot] <dependabot[bot]@users.noreply.github.com>
|
||||
derekriemer <derek.riemer@colorado.edu>
|
||||
desbma <desbma@users.noreply.github.com>
|
||||
Dmitry Saveliev (dsaveliev) <d.e.saveliev@gmail.com>
|
||||
Domenic Horner <domenic@tgxn.net>
|
||||
Dominik Heidler (asdil12) <dominik@heidler.eu>
|
||||
Elias Jarlebring (jarlebring) <jarlebring@gmail.com>
|
||||
Elliot Huffman <thelich2@gmail.com>
|
||||
@@ -77,17 +94,21 @@ Felix Unterpaintner (bigbear2nd) <bigbear2nd@gmail.com>
|
||||
Francois-Xavier Gsell (zukoo) <fxgsell@gmail.com>
|
||||
Frank Isemann (fti7) <frank@isemann.name>
|
||||
georgespatton <georgespatton@users.noreply.github.com>
|
||||
ghjklw <malo@jaffre.info>
|
||||
Gilli Sigurdsson (gillisig) <gilli@vx.is>
|
||||
Graham Miln (grahammiln) <graham.miln@dssw.co.uk> <graham.miln@miln.eu>
|
||||
greatroar <61184462+greatroar@users.noreply.github.com>
|
||||
Han Boetes <han@boetes.org>
|
||||
Harrison Jones (harrisonhjones) <harrisonhjones@users.noreply.github.com>
|
||||
Heiko Zuerker (Smiley73) <heiko@zuerker.org>
|
||||
Hugo Locurcio <hugo.locurcio@hugo.pro>
|
||||
Iain Barnett <iainspeed@gmail.com>
|
||||
Ian Johnson (anonymouse64) <ian.johnson@canonical.com> <person.uwsome@gmail.com>
|
||||
Ilya Brin <464157+ilyabrin@users.noreply.github.com>
|
||||
Iskander Sharipov (Alex) <quasilyte@gmail.com>
|
||||
Jaakko Hannikainen (jgke) <jgke@jgke.fi>
|
||||
Jacek Szafarkiewicz (hadogenes) <szafar@linux.pl>
|
||||
Jacob <jyundt@gmail.com>
|
||||
Jake Peterson (acogdev) <jake@acogdev.com>
|
||||
Jakob Borg (calmh) <jakob@nym.se> <jakob@kastelo.net>
|
||||
James Patterson (jpjp) <jamespatterson@operamail.com> <jpjp@users.noreply.github.com>
|
||||
@@ -95,6 +116,7 @@ janost <janost@tuta.io>
|
||||
Jaroslav Malec (dzarda) <dzardacz@gmail.com>
|
||||
jaseg <githubaccount@jaseg.net>
|
||||
Jaya Chithra (jayachithra) <s.k.jayachithra@gmail.com>
|
||||
jelle van der Waa <jelle@vdwaa.nl>
|
||||
Jens Diemer (jedie) <github.com@jensdiemer.de> <git@jensdiemer.de>
|
||||
Jerry Jacobs (xor-gate) <jerry.jacobs@xor-gate.org> <xor-gate@users.noreply.github.com>
|
||||
Jochen Voss (seehuhn) <voss@seehuhn.de>
|
||||
@@ -102,14 +124,18 @@ Johan Andersson <j@i19.se>
|
||||
Johan Vromans (sciurius) <jvromans@squirrel.nl>
|
||||
John Rinehart (fuzzybear3965) <johnrichardrinehart@gmail.com>
|
||||
Jonas Thelemann <e-mail@jonas-thelemann.de>
|
||||
Jonathan <artback@protonmail.com>
|
||||
Jonathan Cross <jcross@gmail.com>
|
||||
Jose Manuel Delicado (jmdaweb) <jmdaweb@hotmail.com> <jmdaweb@users.noreply.github.com>
|
||||
Jörg Thalheim <Mic92@users.noreply.github.com>
|
||||
Jędrzej Kula <kula.jedrek@gmail.com>
|
||||
Kalle Laine <pahakalle@protonmail.com>
|
||||
Karol Różycki (krozycki) <rozycki.karol@gmail.com>
|
||||
Keith Turner <kturner@apache.org>
|
||||
Kelong Cong (kc1212) <kc04bc@gmx.com> <kc1212@users.noreply.github.com>
|
||||
Ken'ichi Kamada (kamadak) <kamada@nanohz.org>
|
||||
Kevin Allen (ironmig) <kma1660@gmail.com>
|
||||
Kevin Bushiri (keevBush) <keevbush@gmail.com> <36192217+keevBush@users.noreply.github.com>
|
||||
Kevin White, Jr. (kwhite17) <kevinwhite1710@gmail.com>
|
||||
klemens <ka7@github.com>
|
||||
Kurt Fitzner (Kudalufi) <kurt@va1der.ca> <kurt.fitzner@gmail.com>
|
||||
@@ -120,13 +146,17 @@ Leo Arias (elopio) <yo@elopio.net>
|
||||
Liu Siyuan (liusy182) <liusy182@gmail.com> <liusy182@hotmail.com>
|
||||
Lode Hoste (Zillode) <zillode@zillode.be>
|
||||
Lord Landon Agahnim (LordLandon) <lordlandon@gmail.com>
|
||||
Lukas Lihotzki <lukas@lihotzki.de>
|
||||
Majed Abdulaziz (majedev) <majed.alhajry@gmail.com>
|
||||
Marc Laporte (marclaporte) <marc@marclaporte.com> <marc@laporte.name>
|
||||
Marc Pujol (kilburn) <kilburn@la3.org>
|
||||
Marcin Dziadus (marcindziadus) <dziadus.marcin@gmail.com>
|
||||
marco-m <marco.molteni@laposte.net>
|
||||
Marcus Legendre <marcus.legendre@gmail.com>
|
||||
Mario Majila <mariustshipichik@gmail.com>
|
||||
Mark Pulford (mpx) <mark@kyne.com.au>
|
||||
Mateusz Naściszewski (mateon1) <matin1111@wp.pl>
|
||||
Mateusz Ż <thedead4fun@live.com>
|
||||
Matic Potočnik <hairyfotr@gmail.com>
|
||||
Matt Burke (burkemw3) <mburke@amplify.com> <burkemw3@gmail.com>
|
||||
Matt Robenolt <matt@ydekproductions.com>
|
||||
@@ -137,19 +167,30 @@ MaximAL <almaximal@ya.ru>
|
||||
Maxime Thirouin <m@moox.io>
|
||||
Michael Jephcote (Rewt0r) <rewt0r@gmx.com> <Rewt0r@users.noreply.github.com>
|
||||
Michael Ploujnikov (plouj) <ploujj@gmail.com>
|
||||
Michael Rienstra <mrienstra@gmail.com>
|
||||
Michael Tilli (pyfisch) <pyfisch@gmail.com>
|
||||
Mike Boone <mike@boonedocks.net>
|
||||
MikeLund <MikeLund@users.noreply.github.com>
|
||||
MikolajTwarog <43782609+MikolajTwarog@users.noreply.github.com>
|
||||
Mingxuan Lin <gdlmx@users.noreply.github.com>
|
||||
mv1005 <49659413+mv1005@users.noreply.github.com>
|
||||
Nate Morrison (nrm21) <natemorrison@gmail.com>
|
||||
Nicholas Rishel (PrototypeNM1) <rishel.nick@gmail.com> <PrototypeNM1@users.noreply.github.com>
|
||||
Nico Stapelbroek <3368018+nstapelbroek@users.noreply.github.com>
|
||||
Nicolas Braud-Santoni <nicolas@braud-santoni.eu>
|
||||
Nicolas Perraut <n.perraut@gmail.com>
|
||||
Niels Peter Roest (Niller303) <nielsproest@hotmail.com> <seje.niels@hotmail.com>
|
||||
Nils Jakobi (thunderstorm99) <jakobi.nils@gmail.com>
|
||||
NinoM4ster <ninom4ster@gmail.com>
|
||||
Nitroretro <43112364+Nitroretro@users.noreply.github.com>
|
||||
NoLooseEnds <jon.koslung@gmail.com>
|
||||
Oliver Freyermuth <o.freyermuth@googlemail.com>
|
||||
otbutz <tbutz@optitool.de>
|
||||
Otiel <Otiel@users.noreply.github.com>
|
||||
Oyebanji Jacob Mayowa <oyebanji05@gmail.com>
|
||||
Pablo <pbaeyens31+github@gmail.com>
|
||||
Pascal Jungblut (pascalj) <github@pascalj.com> <mail@pascal-jungblut.com>
|
||||
Paul Brit <paulbrit44@gmail.com>
|
||||
Pawel Palenica (qepasa) <pawelpalenica11@gmail.com>
|
||||
Paweł Rozlach <vespian@users.noreply.github.com>
|
||||
perewa <cavalcante.ten@gmail.com>
|
||||
@@ -165,14 +206,18 @@ Piotr Bejda (piobpl) <piotrb10@gmail.com>
|
||||
Pramodh KP (pramodhkp) <pramodh.p@directi.com> <1507241+pramodhkp@users.noreply.github.com>
|
||||
Richard Hartmann <RichiH@users.noreply.github.com>
|
||||
Robert Carosi (nov1n) <robert@carosi.nl>
|
||||
Robin Schoonover <robin@cornhooves.org>
|
||||
Roman Zaynetdinov (zaynetro) <romanznet@gmail.com>
|
||||
Ross Smith II (rasa) <ross@smithii.com>
|
||||
rubenbe <github-com-00ff86@vandamme.email>
|
||||
Ruslan Yevdokymov <38809160+ruslanye@users.noreply.github.com>
|
||||
Ryan Sullivan (KayoticSully) <kayoticsully@gmail.com>
|
||||
Sacheendra Talluri (sacheendra) <sacheendra.t@gmail.com>
|
||||
Scott Klupfel (kluppy) <kluppy@going2blue.com>
|
||||
Sergey Mishin (ralder) <ralder@yandex.ru>
|
||||
Shaarad Dalvi <60266155+shaaraddalvi@users.noreply.github.com>
|
||||
Simon Frei (imsodin) <freisim93@gmail.com>
|
||||
Simon Mwepu <simonmwepu@gmail.com>
|
||||
Sly_tom_cat <slytomcat@mail.ru>
|
||||
Stefan Kuntz (Stefan-Code) <stefan.github@gmail.com> <Stefan.github@gmail.com>
|
||||
Stefan Tatschner (rumpelsepp) <stefan@sevenbyte.org> <rumpelsepp@sevenbyte.org> <stefan@rumpelsepp.org>
|
||||
@@ -183,10 +228,12 @@ Tim Abell (timabell) <tim@timwise.co.uk>
|
||||
Tim Howes (timhowes) <timhowes@berkeley.edu>
|
||||
Tobias Nygren (tnn2) <tnn@nygren.pp.se>
|
||||
Tobias Tom (tobiastom) <t.tom@succont.de>
|
||||
Tomas Cerveny (kozec) <kozec@kozec.com>
|
||||
Tom Jakubowski <tom@crystae.net>
|
||||
Tomasz Wilczyński <5626656+tomasz1986@users.noreply.github.com>
|
||||
Tommy Thorn <tommy-github-email@thorn.ws>
|
||||
Tully Robinson (tojrobinson) <tully@tojr.org>
|
||||
Tyler Brazier (tylerbrazier) <tyler@tylerbrazier.com>
|
||||
Tyler Kropp <kropptyler@gmail.com>
|
||||
Unrud (Unrud) <unrud@openaliasbox.org> <Unrud@users.noreply.github.com>
|
||||
Veeti Paananen (veeti) <veeti.paananen@rojekti.fi>
|
||||
Victor Buinsky (buinsky) <vix_booja@tut.by>
|
||||
@@ -195,6 +242,7 @@ Vladimir Rusinov <vrusinov@google.com>
|
||||
wangguoliang <liangcszzu@163.com>
|
||||
William A. Kennington III (wkennington) <william@wkennington.com>
|
||||
Wulf Weich (wweich) <wweich@users.noreply.github.com> <wweich@gmx.de> <wulf@weich-kr.de>
|
||||
xarx00 <xarx00@users.noreply.github.com>
|
||||
Xavier O. (damajor) <damajor@gmail.com>
|
||||
xjtdy888 (xjtdy888) <xjtdy888@163.com>
|
||||
Yannic A. (eipiminus1) <eipiminusone+github@gmail.com> <eipiminus1@users.noreply.github.com>
|
||||
|
||||
19
Dockerfile
@@ -1,4 +1,5 @@
|
||||
FROM golang:1.12 AS builder
|
||||
ARG GOVERSION=latest
|
||||
FROM golang:$GOVERSION AS builder
|
||||
|
||||
WORKDIR /src
|
||||
COPY . .
|
||||
@@ -14,19 +15,15 @@ EXPOSE 8384 22000 21027/udp
|
||||
|
||||
VOLUME ["/var/syncthing"]
|
||||
|
||||
RUN apk add --no-cache ca-certificates su-exec
|
||||
RUN apk add --no-cache ca-certificates su-exec tzdata
|
||||
|
||||
COPY --from=builder /src/syncthing /bin/syncthing
|
||||
COPY --from=builder /src/script/docker-entrypoint.sh /bin/entrypoint.sh
|
||||
|
||||
ENV PUID=1000 PGID=1000
|
||||
ENV PUID=1000 PGID=1000 HOME=/var/syncthing
|
||||
|
||||
HEALTHCHECK --interval=1m --timeout=10s \
|
||||
CMD nc -z localhost 8384 || exit 1
|
||||
CMD nc -z 127.0.0.1 8384 || exit 1
|
||||
|
||||
ENTRYPOINT \
|
||||
chown "${PUID}:${PGID}" /var/syncthing \
|
||||
&& su-exec "${PUID}:${PGID}" \
|
||||
env HOME=/var/syncthing \
|
||||
/bin/syncthing \
|
||||
-home /var/syncthing/config \
|
||||
-gui-address 0.0.0.0:8384
|
||||
ENV STGUIADDRESS=0.0.0.0:8384
|
||||
ENTRYPOINT ["/bin/entrypoint.sh", "/bin/syncthing", "-home", "/var/syncthing/config"]
|
||||
|
||||
9
Dockerfile.builder
Normal file
@@ -0,0 +1,9 @@
|
||||
ARG GOVERSION=latest
|
||||
FROM golang:$GOVERSION
|
||||
|
||||
# FPM to build Debian packages
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
locales rubygems ruby-dev build-essential git \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& gem install --no-ri --no-rdoc fpm
|
||||
29
Dockerfile.stdiscosrv
Normal file
@@ -0,0 +1,29 @@
|
||||
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 stdiscosrv && go run build.go -no-upgrade build stdiscosrv
|
||||
|
||||
FROM alpine
|
||||
|
||||
EXPOSE 19200 8443
|
||||
|
||||
VOLUME ["/var/stdiscosrv"]
|
||||
|
||||
RUN apk add --no-cache ca-certificates su-exec
|
||||
|
||||
COPY --from=builder /src/stdiscosrv /bin/stdiscosrv
|
||||
COPY --from=builder /src/script/docker-entrypoint.sh /bin/entrypoint.sh
|
||||
|
||||
ENV PUID=1000 PGID=1000 HOME=/var/stdiscosrv
|
||||
|
||||
HEALTHCHECK --interval=1m --timeout=10s \
|
||||
CMD nc -z localhost 8443 || exit 1
|
||||
|
||||
WORKDIR /var/stdiscosrv
|
||||
ENTRYPOINT ["/bin/entrypoint.sh", "/bin/stdiscosrv"]
|
||||
29
Dockerfile.strelaysrv
Normal file
@@ -0,0 +1,29 @@
|
||||
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 strelaysrv
|
||||
|
||||
FROM alpine
|
||||
|
||||
EXPOSE 22067 22070
|
||||
|
||||
VOLUME ["/var/strelaysrv"]
|
||||
|
||||
RUN apk add --no-cache ca-certificates su-exec
|
||||
|
||||
COPY --from=builder /src/strelaysrv /bin/strelaysrv
|
||||
COPY --from=builder /src/script/docker-entrypoint.sh /bin/entrypoint.sh
|
||||
|
||||
ENV PUID=1000 PGID=1000 HOME=/var/strelaysrv
|
||||
|
||||
HEALTHCHECK --interval=1m --timeout=10s \
|
||||
CMD nc -z localhost 22067 || exit 1
|
||||
|
||||
WORKDIR /var/strelaysrv
|
||||
ENTRYPOINT ["/bin/entrypoint.sh", "/bin/strelaysrv"]
|
||||
@@ -18,7 +18,11 @@ $ docker run -p 8384:8384 -p 22000:22000 \
|
||||
syncthing/syncthing:latest
|
||||
```
|
||||
|
||||
Note that local device discovery will not work with the above command, resulting in poor local transfer rates if local device addresses are not manually configured.
|
||||
## Discovery
|
||||
|
||||
Note that local device discovery will not work with the above command,
|
||||
resulting in poor local transfer rates if local device addresses are not
|
||||
manually configured.
|
||||
|
||||
To allow local discovery, the docker host network can be used instead:
|
||||
|
||||
@@ -32,3 +36,24 @@ $ docker run --network=host \
|
||||
Be aware that syncthing alone is now in control of what interfaces and ports it
|
||||
listens on. You can edit the syncthing configuration to change the defaults if
|
||||
there are conflicts.
|
||||
|
||||
## GUI Security
|
||||
|
||||
By default Syncthing inside the Docker image listens on 0.0.0.0:8384 to
|
||||
allow GUI connections via the Docker proxy. This is set by the
|
||||
`STGUIADDRESS` environment variable in the Dockerfile, as it differs from
|
||||
what Syncthing would otherwise use by default. This means you should set up
|
||||
authentication in the GUI, like for any other externally reachable Syncthing
|
||||
instance. If you do not require the GUI, or you use host networking, you can
|
||||
unset the `STGUIADDRESS` variable to have Syncthing fall back to listening
|
||||
on 127.0.0.1:
|
||||
|
||||
```
|
||||
$ docker pull syncthing/syncthing
|
||||
$ docker run -e STGUIADDRESS= \
|
||||
-v /wherever/st-sync:/var/syncthing \
|
||||
syncthing/syncthing:latest
|
||||
```
|
||||
|
||||
With the environment variable unset Syncthing will follow what is set in the
|
||||
configuration file / GUI settings dialog.
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
---
|
||||
|
||||
[](https://build.syncthing.net/latest/)
|
||||
[](https://build.syncthing.net/viewType.html?buildTypeId=Syncthing_BuildLinuxCross&guest=1)
|
||||
[](https://build.syncthing.net/viewType.html?buildTypeId=Syncthing_BuildWindows&guest=1)
|
||||
[](https://build.syncthing.net/viewType.html?buildTypeId=Syncthing_BuildMac&guest=1)
|
||||
@@ -63,6 +62,10 @@ 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
|
||||
implementations][11] for Windows, Mac and Linux.
|
||||
|
||||
## Docker
|
||||
|
||||
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.
|
||||
@@ -111,4 +114,5 @@ All code is licensed under the [MPLv2 License][7].
|
||||
[13]: https://github.com/syncthing/syncthing/blob/master/GOALS.md
|
||||
[14]: assets/logo-text-128.png
|
||||
[15]: https://syncthing.net/
|
||||
[16]: https://github.com/syncthing/syncthing/blob/master/README-Docker.md
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 8.1 KiB |
BIN
assets/logo.pdf
349
build.go
@@ -15,6 +15,7 @@ import (
|
||||
"compress/flate"
|
||||
"compress/gzip"
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
@@ -29,39 +30,39 @@ import (
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
versionRe = regexp.MustCompile(`-[0-9]{1,3}-g[0-9a-f]{5,10}`)
|
||||
goarch string
|
||||
goos string
|
||||
noupgrade bool
|
||||
version string
|
||||
goCmd string
|
||||
goVersion float64
|
||||
race bool
|
||||
debug = os.Getenv("BUILDDEBUG") != ""
|
||||
extraTags string
|
||||
installSuffix string
|
||||
pkgdir string
|
||||
cc string
|
||||
debugBinary bool
|
||||
coverage bool
|
||||
timeout = "120s"
|
||||
gogoProtoVersion = "v1.2.0"
|
||||
versionRe = regexp.MustCompile(`-[0-9]{1,3}-g[0-9a-f]{5,10}`)
|
||||
goarch string
|
||||
goos string
|
||||
noupgrade bool
|
||||
version string
|
||||
goCmd string
|
||||
race bool
|
||||
debug = os.Getenv("BUILDDEBUG") != ""
|
||||
extraTags string
|
||||
installSuffix string
|
||||
pkgdir string
|
||||
cc string
|
||||
debugBinary bool
|
||||
coverage bool
|
||||
timeout = "120s"
|
||||
numVersions = 5
|
||||
)
|
||||
|
||||
type target struct {
|
||||
name string
|
||||
debname string
|
||||
debdeps []string
|
||||
debpre string
|
||||
debpost string
|
||||
description string
|
||||
buildPkg string
|
||||
buildPkgs []string
|
||||
binaryName string
|
||||
archiveFiles []archiveFile
|
||||
systemdServices []string
|
||||
installationFiles []archiveFile
|
||||
tags []string
|
||||
}
|
||||
@@ -75,9 +76,8 @@ type archiveFile struct {
|
||||
var targets = map[string]target{
|
||||
"all": {
|
||||
// Only valid for the "build" and "install" commands as it lacks all
|
||||
// the archive creation stuff.
|
||||
buildPkg: "github.com/syncthing/syncthing/cmd/...",
|
||||
tags: []string{"purego"},
|
||||
// the archive creation stuff. buildPkgs gets filled out in init()
|
||||
tags: []string{"purego"},
|
||||
},
|
||||
"syncthing": {
|
||||
// The default target for "build", "install", "tar", "zip", "deb", etc.
|
||||
@@ -86,7 +86,7 @@ var targets = map[string]target{
|
||||
debdeps: []string{"libc6", "procps"},
|
||||
debpost: "script/post-upgrade",
|
||||
description: "Open Source Continuous File Synchronization",
|
||||
buildPkg: "github.com/syncthing/syncthing/cmd/syncthing",
|
||||
buildPkgs: []string{"github.com/syncthing/syncthing/cmd/syncthing"},
|
||||
binaryName: "syncthing", // .exe will be added automatically for Windows builds
|
||||
archiveFiles: []archiveFile{
|
||||
{src: "{{binary}}", dst: "{{binary}}", perm: 0755},
|
||||
@@ -128,8 +128,9 @@ var targets = map[string]target{
|
||||
name: "stdiscosrv",
|
||||
debname: "syncthing-discosrv",
|
||||
debdeps: []string{"libc6"},
|
||||
debpre: "cmd/stdiscosrv/scripts/preinst",
|
||||
description: "Syncthing Discovery Server",
|
||||
buildPkg: "github.com/syncthing/syncthing/cmd/stdiscosrv",
|
||||
buildPkgs: []string{"github.com/syncthing/syncthing/cmd/stdiscosrv"},
|
||||
binaryName: "stdiscosrv", // .exe will be added automatically for Windows builds
|
||||
archiveFiles: []archiveFile{
|
||||
{src: "{{binary}}", dst: "{{binary}}", perm: 0755},
|
||||
@@ -137,12 +138,17 @@ var targets = map[string]target{
|
||||
{src: "LICENSE", dst: "LICENSE.txt", perm: 0644},
|
||||
{src: "AUTHORS", dst: "AUTHORS.txt", perm: 0644},
|
||||
},
|
||||
systemdServices: []string{
|
||||
"cmd/stdiscosrv/etc/linux-systemd/stdiscosrv.service",
|
||||
},
|
||||
installationFiles: []archiveFile{
|
||||
{src: "{{binary}}", dst: "deb/usr/bin/{{binary}}", perm: 0755},
|
||||
{src: "cmd/stdiscosrv/README.md", dst: "deb/usr/share/doc/syncthing-discosrv/README.txt", perm: 0644},
|
||||
{src: "LICENSE", dst: "deb/usr/share/doc/syncthing-discosrv/LICENSE.txt", perm: 0644},
|
||||
{src: "AUTHORS", dst: "deb/usr/share/doc/syncthing-discosrv/AUTHORS.txt", perm: 0644},
|
||||
{src: "man/stdiscosrv.1", dst: "deb/usr/share/man/man1/stdiscosrv.1", perm: 0644},
|
||||
{src: "cmd/stdiscosrv/etc/linux-systemd/default", dst: "deb/etc/default/syncthing-discosrv", perm: 0644},
|
||||
{src: "cmd/stdiscosrv/etc/firewall-ufw/stdiscosrv", dst: "deb/etc/ufw/applications.d/stdiscosrv", perm: 0644},
|
||||
},
|
||||
tags: []string{"purego"},
|
||||
},
|
||||
@@ -150,8 +156,9 @@ var targets = map[string]target{
|
||||
name: "strelaysrv",
|
||||
debname: "syncthing-relaysrv",
|
||||
debdeps: []string{"libc6"},
|
||||
debpre: "cmd/strelaysrv/scripts/preinst",
|
||||
description: "Syncthing Relay Server",
|
||||
buildPkg: "github.com/syncthing/syncthing/cmd/strelaysrv",
|
||||
buildPkgs: []string{"github.com/syncthing/syncthing/cmd/strelaysrv"},
|
||||
binaryName: "strelaysrv", // .exe will be added automatically for Windows builds
|
||||
archiveFiles: []archiveFile{
|
||||
{src: "{{binary}}", dst: "{{binary}}", perm: 0755},
|
||||
@@ -160,6 +167,9 @@ var targets = map[string]target{
|
||||
{src: "LICENSE", dst: "LICENSE.txt", perm: 0644},
|
||||
{src: "AUTHORS", dst: "AUTHORS.txt", perm: 0644},
|
||||
},
|
||||
systemdServices: []string{
|
||||
"cmd/strelaysrv/etc/linux-systemd/strelaysrv.service",
|
||||
},
|
||||
installationFiles: []archiveFile{
|
||||
{src: "{{binary}}", dst: "deb/usr/bin/{{binary}}", perm: 0755},
|
||||
{src: "cmd/strelaysrv/README.md", dst: "deb/usr/share/doc/syncthing-relaysrv/README.txt", perm: 0644},
|
||||
@@ -167,6 +177,8 @@ var targets = map[string]target{
|
||||
{src: "LICENSE", dst: "deb/usr/share/doc/syncthing-relaysrv/LICENSE.txt", perm: 0644},
|
||||
{src: "AUTHORS", dst: "deb/usr/share/doc/syncthing-relaysrv/AUTHORS.txt", perm: 0644},
|
||||
{src: "man/strelaysrv.1", dst: "deb/usr/share/man/man1/strelaysrv.1", perm: 0644},
|
||||
{src: "cmd/strelaysrv/etc/linux-systemd/default", dst: "deb/etc/default/syncthing-relaysrv", perm: 0644},
|
||||
{src: "cmd/strelaysrv/etc/firewall-ufw/strelaysrv", dst: "deb/etc/ufw/applications.d/strelaysrv", perm: 0644},
|
||||
},
|
||||
},
|
||||
"strelaypoolsrv": {
|
||||
@@ -174,7 +186,7 @@ var targets = map[string]target{
|
||||
debname: "syncthing-relaypoolsrv",
|
||||
debdeps: []string{"libc6"},
|
||||
description: "Syncthing Relay Pool Server",
|
||||
buildPkg: "github.com/syncthing/syncthing/cmd/strelaypoolsrv",
|
||||
buildPkgs: []string{"github.com/syncthing/syncthing/cmd/strelaypoolsrv"},
|
||||
binaryName: "strelaypoolsrv", // .exe will be added automatically for Windows builds
|
||||
archiveFiles: []archiveFile{
|
||||
{src: "{{binary}}", dst: "{{binary}}", perm: 0755},
|
||||
@@ -200,11 +212,22 @@ type dependencyRepo struct {
|
||||
}
|
||||
|
||||
var dependencyRepos = []dependencyRepo{
|
||||
{path: "protobuf", repo: "https://github.com/gogo/protobuf.git", commit: gogoProtoVersion},
|
||||
{path: "xdr", repo: "https://github.com/calmh/xdr.git", commit: "08e072f9cb16"},
|
||||
}
|
||||
|
||||
func init() {
|
||||
all := targets["all"]
|
||||
pkgs, _ := filepath.Glob("cmd/*")
|
||||
for _, pkg := range pkgs {
|
||||
pkg = filepath.Base(pkg)
|
||||
if strings.HasPrefix(pkg, ".") {
|
||||
// ignore dotfiles
|
||||
continue
|
||||
}
|
||||
all.buildPkgs = append(all.buildPkgs, fmt.Sprintf("github.com/syncthing/syncthing/cmd/%s", pkg))
|
||||
}
|
||||
targets["all"] = all
|
||||
|
||||
// The "syncthing" target includes a few more files found in the "etc"
|
||||
// and "extra" dirs.
|
||||
syncthingPkg := targets["syncthing"]
|
||||
@@ -299,9 +322,6 @@ func runCommand(cmd string, target target) {
|
||||
case "deb":
|
||||
buildDeb(target)
|
||||
|
||||
case "snap":
|
||||
buildSnap(target)
|
||||
|
||||
case "vet":
|
||||
metalintShort()
|
||||
|
||||
@@ -314,6 +334,20 @@ func runCommand(cmd string, target target) {
|
||||
case "version":
|
||||
fmt.Println(getVersion())
|
||||
|
||||
case "changelog":
|
||||
vers, err := currentAndLatestVersions(numVersions)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
for _, ver := range vers {
|
||||
underline := strings.Repeat("=", len(ver))
|
||||
msg, err := tagMessage(ver)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("%s\n%s\n\n%s\n\n", ver, underline, msg)
|
||||
}
|
||||
|
||||
default:
|
||||
log.Fatalf("Unknown command %q", cmd)
|
||||
}
|
||||
@@ -332,6 +366,7 @@ func parseFlags() {
|
||||
flag.StringVar(&cc, "cc", os.Getenv("CC"), "Set CC environment variable for `go build`")
|
||||
flag.BoolVar(&debugBinary, "debug-binary", debugBinary, "Create unoptimized binary to use with delve, set -gcflags='-N -l' and omit -ldflags")
|
||||
flag.BoolVar(&coverage, "coverage", coverage, "Write coverage profile of tests to coverage.txt")
|
||||
flag.IntVar(&numVersions, "num-versions", numVersions, "Number of versions for changelog command")
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
@@ -348,7 +383,7 @@ func test(pkgs ...string) {
|
||||
}
|
||||
|
||||
if coverage {
|
||||
args = append(args, "-covermode", "atomic", "-coverprofile", "coverage.txt")
|
||||
args = append(args, "-covermode", "atomic", "-coverprofile", "coverage.txt", "-coverpkg", strings.Join(pkgs, ","))
|
||||
}
|
||||
|
||||
runPrint(goCmd, append(args, pkgs...)...)
|
||||
@@ -370,9 +405,6 @@ func install(target target, tags []string) {
|
||||
}
|
||||
os.Setenv("GOBIN", filepath.Join(cwd, "bin"))
|
||||
|
||||
args := []string{"install", "-v"}
|
||||
args = appendParameters(args, tags, target)
|
||||
|
||||
os.Setenv("GOOS", goos)
|
||||
os.Setenv("GOARCH", goarch)
|
||||
os.Setenv("CC", cc)
|
||||
@@ -388,19 +420,17 @@ func install(target target, tags []string) {
|
||||
defer shouldCleanupSyso(sysoPath)
|
||||
}
|
||||
|
||||
args := []string{"install", "-v"}
|
||||
args = appendParameters(args, tags, target.buildPkgs...)
|
||||
runPrint(goCmd, args...)
|
||||
}
|
||||
|
||||
func build(target target, tags []string) {
|
||||
lazyRebuildAssets()
|
||||
|
||||
tags = append(target.tags, tags...)
|
||||
|
||||
rmr(target.BinaryName())
|
||||
|
||||
args := []string{"build", "-v"}
|
||||
args = appendParameters(args, tags, target)
|
||||
|
||||
os.Setenv("GOOS", goos)
|
||||
os.Setenv("GOARCH", goarch)
|
||||
os.Setenv("CC", cc)
|
||||
@@ -420,10 +450,12 @@ func build(target target, tags []string) {
|
||||
defer shouldCleanupSyso(sysoPath)
|
||||
}
|
||||
|
||||
args := []string{"build", "-v"}
|
||||
args = appendParameters(args, tags, target.buildPkgs...)
|
||||
runPrint(goCmd, args...)
|
||||
}
|
||||
|
||||
func appendParameters(args []string, tags []string, target target) []string {
|
||||
func appendParameters(args []string, tags []string, pkgs ...string) []string {
|
||||
if pkgdir != "" {
|
||||
args = append(args, "-pkgdir", pkgdir)
|
||||
}
|
||||
@@ -448,7 +480,7 @@ func appendParameters(args []string, tags []string, target target) []string {
|
||||
args = append(args, "-gcflags", "-N -l")
|
||||
}
|
||||
|
||||
return append(args, target.buildPkg)
|
||||
return append(args, pkgs...)
|
||||
}
|
||||
|
||||
func buildTar(target target) {
|
||||
@@ -462,10 +494,7 @@ func buildTar(target target) {
|
||||
}
|
||||
|
||||
build(target, tags)
|
||||
|
||||
if goos == "darwin" {
|
||||
macosCodesign(target.BinaryName())
|
||||
}
|
||||
codesign(target)
|
||||
|
||||
for i := range target.archiveFiles {
|
||||
target.archiveFiles[i].src = strings.Replace(target.archiveFiles[i].src, "{{binary}}", target.BinaryName(), 1)
|
||||
@@ -488,10 +517,7 @@ func buildZip(target target) {
|
||||
}
|
||||
|
||||
build(target, tags)
|
||||
|
||||
if goos == "windows" {
|
||||
windowsCodesign(target.BinaryName())
|
||||
}
|
||||
codesign(target)
|
||||
|
||||
for i := range target.archiveFiles {
|
||||
target.archiveFiles[i].src = strings.Replace(target.archiveFiles[i].src, "{{binary}}", target.BinaryName(), 1)
|
||||
@@ -555,79 +581,55 @@ func buildDeb(target target) {
|
||||
for _, dep := range target.debdeps {
|
||||
args = append(args, "-d", dep)
|
||||
}
|
||||
for _, service := range target.systemdServices {
|
||||
args = append(args, "--deb-systemd", service)
|
||||
}
|
||||
if target.debpost != "" {
|
||||
args = append(args, "--after-upgrade", target.debpost)
|
||||
}
|
||||
if target.debpre != "" {
|
||||
args = append(args, "--before-install", target.debpre)
|
||||
}
|
||||
runPrint("fpm", args...)
|
||||
}
|
||||
|
||||
func buildSnap(target target) {
|
||||
os.RemoveAll("snap")
|
||||
|
||||
tmpl, err := template.ParseFiles("snapcraft.yaml.template")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
f, err := os.Create("snapcraft.yaml")
|
||||
defer f.Close()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
snaparch := goarch
|
||||
if snaparch == "armhf" {
|
||||
goarch = "arm"
|
||||
} else if snaparch == "i386" {
|
||||
goarch = "386"
|
||||
}
|
||||
snapver := version
|
||||
if strings.HasPrefix(snapver, "v") {
|
||||
snapver = snapver[1:]
|
||||
}
|
||||
snapgrade := "devel"
|
||||
if matched, _ := regexp.MatchString(`^\d+\.\d+\.\d+(-rc.\d+)?$`, snapver); matched {
|
||||
snapgrade = "stable"
|
||||
}
|
||||
err = tmpl.Execute(f, map[string]string{
|
||||
"Version": snapver,
|
||||
"HostArchitecture": runtime.GOARCH,
|
||||
"TargetArchitecture": snaparch,
|
||||
"Grade": snapgrade,
|
||||
func shouldBuildSyso(dir string) (string, error) {
|
||||
type M map[string]interface{}
|
||||
version := getVersion()
|
||||
version = strings.TrimPrefix(version, "v")
|
||||
major, minor, patch := semanticVersion()
|
||||
bs, err := json.Marshal(M{
|
||||
"FixedFileInfo": M{
|
||||
"FileVersion": M{
|
||||
"Major": major,
|
||||
"Minor": minor,
|
||||
"Patch": patch,
|
||||
},
|
||||
"ProductVersion": M{
|
||||
"Major": major,
|
||||
"Minor": minor,
|
||||
"Patch": patch,
|
||||
},
|
||||
},
|
||||
"StringFileInfo": M{
|
||||
"FileDescription": "Open Source Continuous File Synchronization",
|
||||
"LegalCopyright": "The Syncthing Authors",
|
||||
"FileVersion": version,
|
||||
"ProductVersion": version,
|
||||
"ProductName": "Syncthing",
|
||||
},
|
||||
"IconPath": "assets/logo.ico",
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return "", err
|
||||
}
|
||||
runPrint("snapcraft", "clean")
|
||||
build(target, []string{"noupgrade"})
|
||||
runPrint("snapcraft")
|
||||
}
|
||||
|
||||
func shouldBuildSyso(dir string) (string, error) {
|
||||
jsonPath := filepath.Join(dir, "versioninfo.json")
|
||||
file, err := os.Create(filepath.Join(dir, "versioninfo.json"))
|
||||
err = ioutil.WriteFile(jsonPath, bs, 0644)
|
||||
if err != nil {
|
||||
return "", errors.New("failed to create " + jsonPath + ": " + err.Error())
|
||||
}
|
||||
|
||||
major, minor, patch, build := semanticVersion()
|
||||
fmt.Fprintf(file, `{
|
||||
"FixedFileInfo": {
|
||||
"FileVersion": {
|
||||
"Major": %s,
|
||||
"Minor": %s,
|
||||
"Patch": %s,
|
||||
"Build": %s
|
||||
}
|
||||
},
|
||||
"StringFileInfo": {
|
||||
"FileDescription": "Open Source Continuous File Synchronization",
|
||||
"LegalCopyright": "The Syncthing Authors",
|
||||
"ProductVersion": "%s",
|
||||
"ProductName": "Syncthing"
|
||||
},
|
||||
"IconPath": "assets/logo.ico"
|
||||
}`, major, minor, patch, build, getVersion())
|
||||
file.Close()
|
||||
defer func() {
|
||||
if err := os.Remove(jsonPath); err != nil {
|
||||
log.Printf("Warning: unable to remove generated %s: %v. Please remove it manually.", jsonPath, err)
|
||||
@@ -690,6 +692,7 @@ func listFiles(dir string) []string {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if fi.Mode().IsRegular() {
|
||||
res = append(res, path)
|
||||
}
|
||||
@@ -700,11 +703,11 @@ func listFiles(dir string) []string {
|
||||
|
||||
func rebuildAssets() {
|
||||
os.Setenv("SOURCE_DATE_EPOCH", fmt.Sprint(buildStamp()))
|
||||
runPrint(goCmd, "generate", "github.com/syncthing/syncthing/lib/auto", "github.com/syncthing/syncthing/cmd/strelaypoolsrv/auto")
|
||||
runPrint(goCmd, "generate", "github.com/syncthing/syncthing/lib/api/auto", "github.com/syncthing/syncthing/cmd/strelaypoolsrv/auto")
|
||||
}
|
||||
|
||||
func lazyRebuildAssets() {
|
||||
if shouldRebuildAssets("lib/auto/gui.files.go", "gui") || shouldRebuildAssets("cmd/strelaypoolsrv/auto/gui.files.go", "cmd/strelaypoolsrv/auto/gui") {
|
||||
if shouldRebuildAssets("lib/api/auto/gui.files.go", "gui") || shouldRebuildAssets("cmd/strelaypoolsrv/auto/gui.files.go", "cmd/strelaypoolsrv/gui") {
|
||||
rebuildAssets()
|
||||
}
|
||||
}
|
||||
@@ -736,14 +739,21 @@ func shouldRebuildAssets(target, srcdir string) bool {
|
||||
}
|
||||
|
||||
func proto() {
|
||||
runPrint(goCmd, "get", fmt.Sprintf("github.com/gogo/protobuf/protoc-gen-gogofast@%v", gogoProtoVersion))
|
||||
pv := protobufVersion()
|
||||
dependencyRepos = append(dependencyRepos,
|
||||
dependencyRepo{path: "protobuf", repo: "https://github.com/gogo/protobuf.git", commit: pv},
|
||||
)
|
||||
|
||||
runPrint(goCmd, "get", fmt.Sprintf("github.com/gogo/protobuf/protoc-gen-gogofast@%v", pv))
|
||||
os.MkdirAll("repos", 0755)
|
||||
for _, dep := range dependencyRepos {
|
||||
path := filepath.Join("repos", dep.path)
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
runPrintInDir("repos", "git", "clone", dep.repo, dep.path)
|
||||
runPrintInDir(path, "git", "checkout", dep.commit)
|
||||
} else {
|
||||
runPrintInDir(path, "git", "fetch")
|
||||
}
|
||||
runPrintInDir(path, "git", "checkout", dep.commit)
|
||||
}
|
||||
runPrint(goCmd, "generate", "github.com/syncthing/syncthing/lib/...", "github.com/syncthing/syncthing/cmd/stdiscosrv")
|
||||
}
|
||||
@@ -765,17 +775,15 @@ func transifex() {
|
||||
}
|
||||
|
||||
func ldflags() string {
|
||||
sep := '='
|
||||
if goVersion > 0 && goVersion < 1.5 {
|
||||
sep = ' '
|
||||
}
|
||||
|
||||
b := new(bytes.Buffer)
|
||||
b := new(strings.Builder)
|
||||
b.WriteString("-w")
|
||||
fmt.Fprintf(b, " -X github.com/syncthing/syncthing/lib/build.Version%c%s", sep, version)
|
||||
fmt.Fprintf(b, " -X github.com/syncthing/syncthing/lib/build.Stamp%c%d", sep, buildStamp())
|
||||
fmt.Fprintf(b, " -X github.com/syncthing/syncthing/lib/build.User%c%s", sep, buildUser())
|
||||
fmt.Fprintf(b, " -X github.com/syncthing/syncthing/lib/build.Host%c%s", sep, buildHost())
|
||||
fmt.Fprintf(b, " -X github.com/syncthing/syncthing/lib/build.Version=%s", version)
|
||||
fmt.Fprintf(b, " -X github.com/syncthing/syncthing/lib/build.Stamp=%d", buildStamp())
|
||||
fmt.Fprintf(b, " -X github.com/syncthing/syncthing/lib/build.User=%s", buildUser())
|
||||
fmt.Fprintf(b, " -X github.com/syncthing/syncthing/lib/build.Host=%s", buildHost())
|
||||
if v := os.Getenv("EXTRA_LDFLAGS"); v != "" {
|
||||
fmt.Fprintf(b, " %s", v)
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
@@ -789,13 +797,7 @@ func rmr(paths ...string) {
|
||||
}
|
||||
|
||||
func getReleaseVersion() (string, error) {
|
||||
fd, err := os.Open("RELEASE")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer fd.Close()
|
||||
|
||||
bs, err := ioutil.ReadAll(fd)
|
||||
bs, err := ioutil.ReadFile("RELEASE")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -832,13 +834,18 @@ func getVersion() string {
|
||||
return "unknown-dev"
|
||||
}
|
||||
|
||||
func semanticVersion() (major, minor, patch, build string) {
|
||||
r := regexp.MustCompile(`v(?P<Major>\d+)\.(?P<Minor>\d+).(?P<Patch>\d+).*\+(?P<CommitsAhead>\d+)`)
|
||||
func semanticVersion() (major, minor, patch int) {
|
||||
r := regexp.MustCompile(`v(\d+)\.(\d+).(\d+)`)
|
||||
matches := r.FindStringSubmatch(getVersion())
|
||||
if len(matches) != 5 {
|
||||
return "0", "0", "0", "0"
|
||||
if len(matches) != 4 {
|
||||
return 0, 0, 0
|
||||
}
|
||||
return matches[1], matches[2], matches[3], matches[4]
|
||||
|
||||
var ints [3]int
|
||||
for i, s := range matches[1:] {
|
||||
ints[i], _ = strconv.Atoi(s)
|
||||
}
|
||||
return ints[0], ints[1], ints[2]
|
||||
}
|
||||
|
||||
func getBranchSuffix() string {
|
||||
@@ -1134,6 +1141,15 @@ func zipFile(out string, files []archiveFile) {
|
||||
}
|
||||
}
|
||||
|
||||
func codesign(target target) {
|
||||
switch goos {
|
||||
case "windows":
|
||||
windowsCodesign(target.BinaryName())
|
||||
case "darwin":
|
||||
macosCodesign(target.BinaryName())
|
||||
}
|
||||
}
|
||||
|
||||
func macosCodesign(file string) {
|
||||
if pass := os.Getenv("CODESIGN_KEYCHAIN_PASS"); pass != "" {
|
||||
bs, err := runError("security", "unlock-keychain", "-p", pass)
|
||||
@@ -1144,7 +1160,7 @@ func macosCodesign(file string) {
|
||||
}
|
||||
|
||||
if id := os.Getenv("CODESIGN_IDENTITY"); id != "" {
|
||||
bs, err := runError("codesign", "-s", id, file)
|
||||
bs, err := runError("codesign", "--options=runtime", "-s", id, file)
|
||||
if err != nil {
|
||||
log.Println("Codesign: signing failed:", string(bs))
|
||||
return
|
||||
@@ -1232,3 +1248,70 @@ func (t target) BinaryName() string {
|
||||
}
|
||||
return t.binaryName
|
||||
}
|
||||
|
||||
func protobufVersion() string {
|
||||
bs, err := runError(goCmd, "list", "-f", "{{.Version}}", "-m", "github.com/gogo/protobuf")
|
||||
if err != nil {
|
||||
log.Fatal("Getting protobuf version:", err)
|
||||
}
|
||||
return string(bs)
|
||||
}
|
||||
|
||||
func currentAndLatestVersions(n int) ([]string, error) {
|
||||
bs, err := runError("git", "tag", "--sort", "taggerdate")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lines := strings.Split(string(bs), "\n")
|
||||
reverseStrings(lines)
|
||||
|
||||
// The one at the head is the latest version. We always keep that one.
|
||||
// Then we filter out remaining ones with dashes (pre-releases etc).
|
||||
|
||||
latest := lines[:1]
|
||||
nonPres := filterStrings(lines[1:], func(s string) bool { return !strings.Contains(s, "-") })
|
||||
vers := append(latest, nonPres...)
|
||||
return vers[:n], nil
|
||||
}
|
||||
|
||||
func reverseStrings(ss []string) {
|
||||
for i := 0; i < len(ss)/2; i++ {
|
||||
ss[i], ss[len(ss)-1-i] = ss[len(ss)-1-i], ss[i]
|
||||
}
|
||||
}
|
||||
|
||||
func filterStrings(ss []string, op func(string) bool) []string {
|
||||
n := ss[:0]
|
||||
for _, s := range ss {
|
||||
if op(s) {
|
||||
n = append(n, s)
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func tagMessage(tag string) (string, error) {
|
||||
hash, err := runError("git", "rev-parse", tag)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
obj, err := runError("git", "cat-file", "-p", string(hash))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return trimTagMessage(string(obj), tag), nil
|
||||
}
|
||||
|
||||
func trimTagMessage(msg, tag string) string {
|
||||
firstBlank := strings.Index(msg, "\n\n")
|
||||
if firstBlank > 0 {
|
||||
msg = msg[firstBlank+2:]
|
||||
}
|
||||
msg = strings.TrimPrefix(msg, tag)
|
||||
beginSig := strings.Index(msg, "-----BEGIN PGP")
|
||||
if beginSig > 0 {
|
||||
msg = msg[:beginSig]
|
||||
}
|
||||
return strings.TrimSpace(msg)
|
||||
}
|
||||
|
||||
20
build.ps1
Normal file
@@ -0,0 +1,20 @@
|
||||
function build {
|
||||
go run build.go @args
|
||||
}
|
||||
|
||||
$cmd, $rest = $args
|
||||
switch ($cmd) {
|
||||
"test" {
|
||||
$env:LOGGER_DISCARD=1
|
||||
build test
|
||||
}
|
||||
|
||||
"bench" {
|
||||
$env:LOGGER_DISCARD=1
|
||||
build bench
|
||||
}
|
||||
|
||||
default {
|
||||
build @rest
|
||||
}
|
||||
}
|
||||
73
build.sh
@@ -2,8 +2,6 @@
|
||||
set -euo pipefail
|
||||
IFS=$'\n\t'
|
||||
|
||||
STTRACE=${STTRACE:-}
|
||||
|
||||
script() {
|
||||
name="$1"
|
||||
shift
|
||||
@@ -15,88 +13,23 @@ build() {
|
||||
}
|
||||
|
||||
case "${1:-default}" in
|
||||
default)
|
||||
build
|
||||
;;
|
||||
|
||||
clean)
|
||||
build "$@"
|
||||
;;
|
||||
|
||||
tar)
|
||||
build "$@"
|
||||
;;
|
||||
|
||||
assets)
|
||||
build "$@"
|
||||
;;
|
||||
|
||||
xdr)
|
||||
build "$@"
|
||||
;;
|
||||
|
||||
translate)
|
||||
build "$@"
|
||||
;;
|
||||
|
||||
deb)
|
||||
build "$@"
|
||||
;;
|
||||
|
||||
setup)
|
||||
build "$@"
|
||||
;;
|
||||
|
||||
test)
|
||||
LOGGER_DISCARD=1 build test
|
||||
;;
|
||||
|
||||
bench)
|
||||
LOGGER_DISCARD=1 build bench | script benchfilter
|
||||
LOGGER_DISCARD=1 build bench
|
||||
;;
|
||||
|
||||
prerelease)
|
||||
go run script/authors.go
|
||||
script authors
|
||||
build transifex
|
||||
pushd man ; ./refresh.sh ; popd
|
||||
git add -A gui man AUTHORS
|
||||
git commit -m 'gui, man, authors: Update docs, translations, and contributors'
|
||||
;;
|
||||
|
||||
noupgrade)
|
||||
build -no-upgrade tar
|
||||
;;
|
||||
|
||||
all)
|
||||
platforms=(
|
||||
darwin-amd64 dragonfly-amd64 freebsd-amd64 linux-amd64 netbsd-amd64 openbsd-amd64 solaris-amd64 windows-amd64
|
||||
freebsd-386 linux-386 netbsd-386 openbsd-386 windows-386
|
||||
linux-arm linux-arm64 linux-ppc64 linux-ppc64le
|
||||
)
|
||||
|
||||
for plat in "${platforms[@]}"; do
|
||||
echo Building "$plat"
|
||||
|
||||
goos="${plat%-*}"
|
||||
goarch="${plat#*-}"
|
||||
dist="tar"
|
||||
|
||||
if [[ $goos == "windows" ]]; then
|
||||
dist="zip"
|
||||
fi
|
||||
|
||||
build -goos "$goos" -goarch "$goarch" "$dist"
|
||||
echo
|
||||
done
|
||||
;;
|
||||
|
||||
test-xunit)
|
||||
|
||||
(GOPATH="$(pwd)/Godeps/_workspace:$GOPATH" go test -v -race ./lib/... ./cmd/... || true) > tests.out
|
||||
go2xunit -output tests.xml -fail < tests.out
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Unknown build command $1"
|
||||
build "$@"
|
||||
;;
|
||||
esac
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
// Copyright (C) 2016 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/.
|
||||
|
||||
// This doesn't build on Windows due to the Rusage stuff.
|
||||
|
||||
// +build !windows
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/rc"
|
||||
)
|
||||
|
||||
var homeDir = "h1"
|
||||
var syncthingBin = "./bin/syncthing"
|
||||
var test = "scan"
|
||||
|
||||
func main() {
|
||||
flag.StringVar(&homeDir, "home", homeDir, "Home directory location")
|
||||
flag.StringVar(&syncthingBin, "bin", syncthingBin, "Binary location")
|
||||
flag.StringVar(&test, "test", test, "Test to run")
|
||||
flag.Parse()
|
||||
|
||||
switch test {
|
||||
case "scan":
|
||||
// scan measures the resource usage required to perform the initial
|
||||
// scan, without cleaning away the database first.
|
||||
testScan()
|
||||
}
|
||||
}
|
||||
|
||||
// testScan starts a process and reports on the resource usage required to
|
||||
// perform the initial scan.
|
||||
func testScan() {
|
||||
log.Println("Starting...")
|
||||
p := rc.NewProcess("127.0.0.1:8081")
|
||||
if err := p.Start(syncthingBin, "-home", homeDir, "-no-browser"); err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
defer p.Stop()
|
||||
|
||||
wallTime := awaitScanComplete(p)
|
||||
|
||||
report(p, wallTime)
|
||||
}
|
||||
|
||||
// awaitScanComplete waits for a folder to transition idle->scanning and
|
||||
// then scanning->idle and returns the time taken for the scan.
|
||||
func awaitScanComplete(p *rc.Process) time.Duration {
|
||||
log.Println("Awaiting scan completion...")
|
||||
var t0, t1 time.Time
|
||||
lastEvent := 0
|
||||
loop:
|
||||
for {
|
||||
evs, err := p.Events(lastEvent)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, ev := range evs {
|
||||
if ev.Type == "StateChanged" {
|
||||
data := ev.Data.(map[string]interface{})
|
||||
log.Println(ev)
|
||||
|
||||
if data["to"].(string) == "scanning" {
|
||||
t0 = ev.Time
|
||||
continue
|
||||
}
|
||||
|
||||
if !t0.IsZero() && data["to"].(string) == "idle" {
|
||||
t1 = ev.Time
|
||||
break loop
|
||||
}
|
||||
}
|
||||
lastEvent = ev.ID
|
||||
}
|
||||
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
}
|
||||
|
||||
return t1.Sub(t0)
|
||||
}
|
||||
|
||||
// report stops the given process and reports on its resource usage in two
|
||||
// ways: human readable to stderr, and CSV to stdout.
|
||||
func report(p *rc.Process, wallTime time.Duration) {
|
||||
sv, err := p.SystemVersion()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
ss, err := p.SystemStatus()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
proc, err := p.Stop()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
rusage, ok := proc.SysUsage().(*syscall.Rusage)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
log.Println("Version:", sv.Version)
|
||||
log.Println("Alloc:", ss.Alloc/1024, "KiB")
|
||||
log.Println("Sys:", ss.Sys/1024, "KiB")
|
||||
log.Println("Goroutines:", ss.Goroutines)
|
||||
log.Println("Wall time:", wallTime)
|
||||
log.Println("Utime:", time.Duration(rusage.Utime.Nano()))
|
||||
log.Println("Stime:", time.Duration(rusage.Stime.Nano()))
|
||||
if runtime.GOOS == "darwin" {
|
||||
// Darwin reports in bytes, Linux seems to report in KiB even
|
||||
// though the manpage says otherwise.
|
||||
rusage.Maxrss /= 1024
|
||||
}
|
||||
log.Println("MaxRSS:", rusage.Maxrss, "KiB")
|
||||
|
||||
fmt.Printf("%s,%d,%d,%d,%.02f,%.02f,%.02f,%d\n",
|
||||
sv.Version,
|
||||
ss.Alloc/1024,
|
||||
ss.Sys/1024,
|
||||
ss.Goroutines,
|
||||
wallTime.Seconds(),
|
||||
time.Duration(rusage.Utime.Nano()).Seconds(),
|
||||
time.Duration(rusage.Stime.Nano()).Seconds(),
|
||||
rusage.Maxrss)
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -80,16 +81,16 @@ func (c *APIClient) Post(url, body string) (*http.Response, error) {
|
||||
|
||||
func checkResponse(response *http.Response) error {
|
||||
if response.StatusCode == 404 {
|
||||
return fmt.Errorf("Invalid endpoint or API call")
|
||||
return errors.New("invalid endpoint or API call")
|
||||
} else if response.StatusCode == 403 {
|
||||
return fmt.Errorf("Invalid API key")
|
||||
return errors.New("invalid API key")
|
||||
} else if response.StatusCode != 200 {
|
||||
data, err := responseToBArray(response)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
body := strings.TrimSpace(string(data))
|
||||
return fmt.Errorf("Unexpected HTTP status returned: %s\n%s", response.Status, body)
|
||||
return fmt.Errorf("unexpected HTTP status returned: %s\n%s", response.Status, body)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
@@ -54,7 +55,7 @@ func errorsPush(c *cli.Context) error {
|
||||
if body != "" {
|
||||
errStr += "\nBody: " + body
|
||||
}
|
||||
return fmt.Errorf(errStr)
|
||||
return errors.New(errStr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/AudriusButkevicius/recli"
|
||||
"github.com/flynn-archive/go-shlex"
|
||||
@@ -22,6 +21,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/syncthing/syncthing/lib/build"
|
||||
"github.com/syncthing/syncthing/lib/config"
|
||||
"github.com/syncthing/syncthing/lib/events"
|
||||
"github.com/syncthing/syncthing/lib/locations"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
"github.com/urfave/cli"
|
||||
@@ -85,7 +85,7 @@ func main() {
|
||||
myID := protocol.NewDeviceID(cert.Certificate[0])
|
||||
|
||||
// Load the config
|
||||
cfg, err := config.Load(locations.Get(locations.ConfigFile), myID)
|
||||
cfg, err := config.Load(locations.Get(locations.ConfigFile), myID, events.NoopLogger)
|
||||
if err != nil {
|
||||
log.Fatalln(errors.Wrap(err, "loading config"))
|
||||
}
|
||||
@@ -127,7 +127,7 @@ func main() {
|
||||
app.HelpName = app.Name
|
||||
app.Author = "The Syncthing Authors"
|
||||
app.Usage = "Syncthing command line interface"
|
||||
app.Version = strings.Replace(build.LongVersion, "syncthing", app.Name, 1)
|
||||
app.Version = build.Version
|
||||
app.Flags = fakeFlags
|
||||
app.Metadata = map[string]interface{}{
|
||||
"client": client,
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/config"
|
||||
"github.com/urfave/cli"
|
||||
@@ -45,12 +44,6 @@ func dumpOutput(url string) cli.ActionFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func newTableWriter() *tabwriter.Writer {
|
||||
writer := new(tabwriter.Writer)
|
||||
writer.Init(os.Stdout, 0, 8, 0, '\t', 0)
|
||||
return writer
|
||||
}
|
||||
|
||||
func getConfig(c *APIClient) (config.Configuration, error) {
|
||||
cfg := config.Configuration{}
|
||||
response, err := c.Get("system/config")
|
||||
|
||||
@@ -60,7 +60,7 @@ func compareDirectories(dirs ...string) error {
|
||||
} else if res[i].name > res[0].name {
|
||||
return fmt.Errorf("%s missing %v (present in %s)", dirs[i], res[0], dirs[0])
|
||||
}
|
||||
return fmt.Errorf("Mismatch; %v (%s) != %v (%s)", res[i], dirs[i], res[0], dirs[0])
|
||||
return fmt.Errorf("mismatch; %v (%s) != %v (%s)", res[i], dirs[i], res[0], dirs[0])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
1172
cmd/stcrashreceiver/_testdata/panic.log
Normal file
219
cmd/stcrashreceiver/sentry.go
Normal file
@@ -0,0 +1,219 @@
|
||||
// Copyright (C) 2019 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 (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
raven "github.com/getsentry/raven-go"
|
||||
"github.com/maruel/panicparse/stack"
|
||||
)
|
||||
|
||||
const reportServer = "https://crash.syncthing.net/report/"
|
||||
|
||||
var loader = newGithubSourceCodeLoader()
|
||||
|
||||
func init() {
|
||||
raven.SetSourceCodeLoader(loader)
|
||||
}
|
||||
|
||||
var (
|
||||
clients = make(map[string]*raven.Client)
|
||||
clientsMut sync.Mutex
|
||||
)
|
||||
|
||||
func sendReport(dsn, path string, report []byte, userID string) error {
|
||||
pkt, err := parseReport(path, report)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pkt.Interfaces = append(pkt.Interfaces, &raven.User{ID: userID})
|
||||
|
||||
clientsMut.Lock()
|
||||
defer clientsMut.Unlock()
|
||||
|
||||
cli, ok := clients[dsn]
|
||||
if !ok {
|
||||
cli, err = raven.New(dsn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
clients[dsn] = cli
|
||||
}
|
||||
|
||||
// The client sets release and such on the packet before sending, in the
|
||||
// misguided idea that it knows this better than than the packet we give
|
||||
// it. So we copy the values from the packet to the client first...
|
||||
cli.SetRelease(pkt.Release)
|
||||
cli.SetEnvironment(pkt.Environment)
|
||||
|
||||
defer cli.Wait()
|
||||
_, errC := cli.Capture(pkt, nil)
|
||||
return <-errC
|
||||
}
|
||||
|
||||
func parseReport(path string, report []byte) (*raven.Packet, error) {
|
||||
parts := bytes.SplitN(report, []byte("\n"), 2)
|
||||
if len(parts) != 2 {
|
||||
return nil, errors.New("no first line")
|
||||
}
|
||||
|
||||
version, err := parseVersion(string(parts[0]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
report = parts[1]
|
||||
|
||||
foundPanic := false
|
||||
var subjectLine []byte
|
||||
for {
|
||||
parts = bytes.SplitN(report, []byte("\n"), 2)
|
||||
if len(parts) != 2 {
|
||||
return nil, errors.New("no panic line found")
|
||||
}
|
||||
|
||||
line := parts[0]
|
||||
report = parts[1]
|
||||
|
||||
if foundPanic {
|
||||
// The previous line was our "Panic at ..." header. We are now
|
||||
// at the beginning of the real panic trace and this is our
|
||||
// subject line.
|
||||
subjectLine = line
|
||||
break
|
||||
} else if bytes.HasPrefix(line, []byte("Panic at")) {
|
||||
foundPanic = true
|
||||
}
|
||||
}
|
||||
|
||||
r := bytes.NewReader(report)
|
||||
ctx, err := stack.ParseDump(r, ioutil.Discard, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Lock the source code loader to the version we are processing here.
|
||||
if version.commit != "" {
|
||||
// We have a commit hash, so we know exactly which source to use
|
||||
loader.LockWithVersion(version.commit)
|
||||
} else if strings.HasPrefix(version.tag, "v") {
|
||||
// Lets hope the tag is close enough
|
||||
loader.LockWithVersion(version.tag)
|
||||
} else {
|
||||
// Last resort
|
||||
loader.LockWithVersion("master")
|
||||
}
|
||||
defer loader.Unlock()
|
||||
|
||||
var trace raven.Stacktrace
|
||||
for _, gr := range ctx.Goroutines {
|
||||
if gr.First {
|
||||
trace.Frames = make([]*raven.StacktraceFrame, len(gr.Stack.Calls))
|
||||
for i, sc := range gr.Stack.Calls {
|
||||
trace.Frames[len(trace.Frames)-1-i] = raven.NewStacktraceFrame(0, sc.Func.Name(), sc.SrcPath, sc.Line, 3, nil)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
pkt := &raven.Packet{
|
||||
Message: string(subjectLine),
|
||||
Platform: "go",
|
||||
Release: version.tag,
|
||||
Environment: version.environment(),
|
||||
Tags: raven.Tags{
|
||||
raven.Tag{Key: "version", Value: version.version},
|
||||
raven.Tag{Key: "tag", Value: version.tag},
|
||||
raven.Tag{Key: "codename", Value: version.codename},
|
||||
raven.Tag{Key: "runtime", Value: version.runtime},
|
||||
raven.Tag{Key: "goos", Value: version.goos},
|
||||
raven.Tag{Key: "goarch", Value: version.goarch},
|
||||
raven.Tag{Key: "builder", Value: version.builder},
|
||||
},
|
||||
Extra: raven.Extra{
|
||||
"url": reportServer + path,
|
||||
},
|
||||
Interfaces: []raven.Interface{&trace},
|
||||
}
|
||||
if version.commit != "" {
|
||||
pkt.Tags = append(pkt.Tags, raven.Tag{Key: "commit", Value: version.commit})
|
||||
}
|
||||
for _, tag := range version.extra {
|
||||
pkt.Tags = append(pkt.Tags, raven.Tag{Key: tag, Value: "1"})
|
||||
}
|
||||
|
||||
return pkt, nil
|
||||
}
|
||||
|
||||
// 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]+)[^\[]*(?:\[(.+)\])?$`)
|
||||
|
||||
type version struct {
|
||||
version string // "v1.1.4-rc.1+30-g6aaae618-dirty-crashrep"
|
||||
tag string // "v1.1.4-rc.1"
|
||||
commit string // "6aaae618", blank when absent
|
||||
codename string // "Erbium Earthworm"
|
||||
runtime string // "go1.12.5"
|
||||
goos string // "darwin"
|
||||
goarch string // "amd64"
|
||||
builder string // "jb@kvin.kastelo.net"
|
||||
extra []string // "foo", "bar"
|
||||
}
|
||||
|
||||
func (v version) environment() string {
|
||||
if v.commit != "" {
|
||||
return "Development"
|
||||
}
|
||||
if strings.Contains(v.tag, "-rc.") {
|
||||
return "Candidate"
|
||||
}
|
||||
if strings.Contains(v.tag, "-") {
|
||||
return "Beta"
|
||||
}
|
||||
return "Stable"
|
||||
}
|
||||
|
||||
func parseVersion(line string) (version, error) {
|
||||
m := longVersionRE.FindStringSubmatch(line)
|
||||
if len(m) == 0 {
|
||||
return version{}, errors.New("unintelligeble version string")
|
||||
}
|
||||
|
||||
v := version{
|
||||
version: m[1],
|
||||
codename: m[2],
|
||||
runtime: m[3],
|
||||
goos: m[4],
|
||||
goarch: m[5],
|
||||
builder: m[6],
|
||||
}
|
||||
|
||||
parts := strings.Split(v.version, "+")
|
||||
v.tag = parts[0]
|
||||
if len(parts) > 1 {
|
||||
fields := strings.Split(parts[1], "-")
|
||||
if len(fields) >= 2 && strings.HasPrefix(fields[1], "g") {
|
||||
v.commit = fields[1][1:]
|
||||
}
|
||||
}
|
||||
|
||||
if len(m) >= 8 && m[7] != "" {
|
||||
tags := strings.Split(m[7], ",")
|
||||
for i := range tags {
|
||||
tags[i] = strings.TrimSpace(tags[i])
|
||||
}
|
||||
v.extra = tags
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
78
cmd/stcrashreceiver/sentry_test.go
Normal file
@@ -0,0 +1,78 @@
|
||||
// Copyright (C) 2019 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 (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseVersion(t *testing.T) {
|
||||
cases := []struct {
|
||||
longVersion string
|
||||
parsed version
|
||||
}{
|
||||
{
|
||||
longVersion: `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`,
|
||||
parsed: version{
|
||||
version: "v1.1.4-rc.1+30-g6aaae618-dirty-crashrep",
|
||||
tag: "v1.1.4-rc.1",
|
||||
commit: "6aaae618",
|
||||
codename: "Erbium Earthworm",
|
||||
runtime: "go1.12.5",
|
||||
goos: "darwin",
|
||||
goarch: "amd64",
|
||||
builder: "jb@kvin.kastelo.net",
|
||||
},
|
||||
},
|
||||
{
|
||||
longVersion: `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]`,
|
||||
parsed: version{
|
||||
version: "v1.1.4-rc.1+30-g6aaae618-dirty-crashrep",
|
||||
tag: "v1.1.4-rc.1",
|
||||
commit: "6aaae618",
|
||||
codename: "Erbium Earthworm",
|
||||
runtime: "go1.12.5",
|
||||
goos: "darwin",
|
||||
goarch: "amd64",
|
||||
builder: "jb@kvin.kastelo.net",
|
||||
extra: []string{"foo", "bar"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
v, err := parseVersion(tc.longVersion)
|
||||
if err != nil {
|
||||
t.Errorf("%s\nerror: %v\n", tc.longVersion, err)
|
||||
continue
|
||||
}
|
||||
if fmt.Sprint(v) != fmt.Sprint(tc.parsed) {
|
||||
t.Errorf("%s\nA: %v\nE: %v\n", tc.longVersion, v, tc.parsed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseReport(t *testing.T) {
|
||||
bs, err := ioutil.ReadFile("_testdata/panic.log")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
pkt, err := parseReport("1/2/345", bs)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
bs, err = pkt.JSON()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Printf("%s\n", bs)
|
||||
}
|
||||
114
cmd/stcrashreceiver/sourcecodeloader.go
Normal file
@@ -0,0 +1,114 @@
|
||||
// Copyright (C) 2019 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 (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
urlPrefix = "https://raw.githubusercontent.com/syncthing/syncthing/"
|
||||
httpTimeout = 10 * time.Second
|
||||
)
|
||||
|
||||
type githubSourceCodeLoader struct {
|
||||
mut sync.Mutex
|
||||
version string
|
||||
cache map[string]map[string][][]byte // version -> file -> lines
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
func newGithubSourceCodeLoader() *githubSourceCodeLoader {
|
||||
return &githubSourceCodeLoader{
|
||||
cache: make(map[string]map[string][][]byte),
|
||||
client: &http.Client{Timeout: httpTimeout},
|
||||
}
|
||||
}
|
||||
|
||||
func (l *githubSourceCodeLoader) LockWithVersion(version string) {
|
||||
l.mut.Lock()
|
||||
l.version = version
|
||||
if _, ok := l.cache[version]; !ok {
|
||||
l.cache[version] = make(map[string][][]byte)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *githubSourceCodeLoader) Unlock() {
|
||||
l.mut.Unlock()
|
||||
}
|
||||
|
||||
func (l *githubSourceCodeLoader) Load(filename string, line, context int) ([][]byte, int) {
|
||||
filename = filepath.ToSlash(filename)
|
||||
lines, ok := l.cache[l.version][filename]
|
||||
if !ok {
|
||||
// Cache whatever we managed to find (or nil if nothing, so we don't try again)
|
||||
defer func() {
|
||||
l.cache[l.version][filename] = lines
|
||||
}()
|
||||
|
||||
knownPrefixes := []string{"/lib/", "/cmd/"}
|
||||
var idx int
|
||||
for _, pref := range knownPrefixes {
|
||||
idx = strings.Index(filename, pref)
|
||||
if idx >= 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if idx == -1 {
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
url := urlPrefix + l.version + filename[idx:]
|
||||
resp, err := l.client.Get(url)
|
||||
|
||||
if err != nil || resp.StatusCode != http.StatusOK {
|
||||
fmt.Println("Loading source:", err.Error())
|
||||
return nil, 0
|
||||
}
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
_ = resp.Body.Close()
|
||||
if err != nil {
|
||||
fmt.Println("Loading source:", err.Error())
|
||||
return nil, 0
|
||||
}
|
||||
lines = bytes.Split(data, []byte{'\n'})
|
||||
}
|
||||
|
||||
return getLineFromLines(lines, line, context)
|
||||
}
|
||||
|
||||
func getLineFromLines(lines [][]byte, line, context int) ([][]byte, int) {
|
||||
if lines == nil {
|
||||
// cached error from ReadFile: return no lines
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
line-- // stack trace lines are 1-indexed
|
||||
start := line - context
|
||||
var idx int
|
||||
if start < 0 {
|
||||
start = 0
|
||||
idx = line
|
||||
} else {
|
||||
idx = context
|
||||
}
|
||||
end := line + context + 1
|
||||
if line >= len(lines) {
|
||||
return nil, 0
|
||||
}
|
||||
if end > len(lines) {
|
||||
end = len(lines)
|
||||
}
|
||||
return lines[start:end], idx
|
||||
}
|
||||
185
cmd/stcrashreceiver/stcrashreceiver.go
Normal file
@@ -0,0 +1,185 @@
|
||||
// Copyright (C) 2019 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/.
|
||||
|
||||
// Command stcrashreceiver is a trivial HTTP server that allows two things:
|
||||
//
|
||||
// - uploading files (crash reports) named like a SHA256 hash using a PUT request
|
||||
// - checking whether such file exists using a HEAD request
|
||||
//
|
||||
// Typically this should be deployed behind something that manages HTTPS.
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/sha256"
|
||||
)
|
||||
|
||||
const maxRequestSize = 1 << 20 // 1 MiB
|
||||
|
||||
func main() {
|
||||
dir := flag.String("dir", ".", "Directory to store reports in")
|
||||
dsn := flag.String("dsn", "", "Sentry DSN")
|
||||
listen := flag.String("listen", ":22039", "HTTP listen address")
|
||||
flag.Parse()
|
||||
|
||||
cr := &crashReceiver{
|
||||
dir: *dir,
|
||||
dsn: *dsn,
|
||||
}
|
||||
|
||||
log.SetOutput(os.Stdout)
|
||||
if err := http.ListenAndServe(*listen, cr); err != nil {
|
||||
log.Fatalln("HTTP serve:", err)
|
||||
}
|
||||
}
|
||||
|
||||
type crashReceiver struct {
|
||||
dir string
|
||||
dsn string
|
||||
}
|
||||
|
||||
func (r *crashReceiver) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
// The final path component should be a SHA256 hash in hex, so 64 hex
|
||||
// characters. We don't care about case on the request but use lower
|
||||
// case internally.
|
||||
reportID := strings.ToLower(path.Base(req.URL.Path))
|
||||
if len(reportID) != 64 {
|
||||
http.Error(w, "Bad request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
for _, c := range reportID {
|
||||
if c >= 'a' && c <= 'f' {
|
||||
continue
|
||||
}
|
||||
if c >= '0' && c <= '9' {
|
||||
continue
|
||||
}
|
||||
http.Error(w, "Bad request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// The location of the report on disk, compressed
|
||||
fullPath := filepath.Join(r.dir, r.dirFor(reportID), reportID) + ".gz"
|
||||
|
||||
switch req.Method {
|
||||
case http.MethodGet:
|
||||
r.serveGet(fullPath, w, req)
|
||||
case http.MethodHead:
|
||||
r.serveHead(fullPath, w, req)
|
||||
case http.MethodPut:
|
||||
r.servePut(reportID, fullPath, w, req)
|
||||
default:
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
}
|
||||
}
|
||||
|
||||
// serveGet responds to GET requests by serving the uncompressed report.
|
||||
func (r *crashReceiver) serveGet(fullPath string, w http.ResponseWriter, _ *http.Request) {
|
||||
fd, err := os.Open(fullPath)
|
||||
if err != nil {
|
||||
http.Error(w, "Not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
defer fd.Close()
|
||||
gr, err := gzip.NewReader(fd)
|
||||
if err != nil {
|
||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
_, _ = io.Copy(w, gr) // best effort
|
||||
}
|
||||
|
||||
// serveHead responds to HEAD requests by checking if the named report
|
||||
// already exists in the system.
|
||||
func (r *crashReceiver) serveHead(fullPath string, w http.ResponseWriter, _ *http.Request) {
|
||||
if _, err := os.Lstat(fullPath); err != nil {
|
||||
http.Error(w, "Not found", http.StatusNotFound)
|
||||
}
|
||||
}
|
||||
|
||||
// servePut accepts and stores the given report.
|
||||
func (r *crashReceiver) servePut(reportID, fullPath string, w http.ResponseWriter, req *http.Request) {
|
||||
// Ensure the destination directory exists
|
||||
if err := os.MkdirAll(filepath.Dir(fullPath), 0755); err != nil {
|
||||
log.Println("Creating directory:", err)
|
||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Read at most maxRequestSize of report data.
|
||||
log.Println("Receiving report", reportID)
|
||||
lr := io.LimitReader(req.Body, maxRequestSize)
|
||||
bs, err := ioutil.ReadAll(lr)
|
||||
if err != nil {
|
||||
log.Println("Reading report:", err)
|
||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Compress the report for storage
|
||||
buf := new(bytes.Buffer)
|
||||
gw := gzip.NewWriter(buf)
|
||||
_, _ = gw.Write(bs) // can't fail
|
||||
gw.Close()
|
||||
|
||||
// Create an output file with the compressed report
|
||||
err = ioutil.WriteFile(fullPath, buf.Bytes(), 0644)
|
||||
if err != nil {
|
||||
log.Println("Saving report:", err)
|
||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Send the report to Sentry
|
||||
if r.dsn != "" {
|
||||
// Remote ID
|
||||
user := userIDFor(req)
|
||||
|
||||
go func() {
|
||||
// There's no need for the client to have to wait for this part.
|
||||
if err := sendReport(r.dsn, reportID, bs, user); err != nil {
|
||||
log.Println("Failed to send report:", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// 01234567890abcdef... => 01/23
|
||||
func (r *crashReceiver) dirFor(base string) string {
|
||||
return filepath.Join(base[0:2], base[2:4])
|
||||
}
|
||||
|
||||
// userIDFor returns a string we can use as the user ID for the purpose of
|
||||
// counting affected users. It's the truncated hash of a salt, the user
|
||||
// remote IP, and the current month.
|
||||
func userIDFor(req *http.Request) string {
|
||||
addr := req.RemoteAddr
|
||||
if fwd := req.Header.Get("x-forwarded-for"); fwd != "" {
|
||||
addr = fwd
|
||||
}
|
||||
if host, _, err := net.SplitHostPort(addr); err == nil {
|
||||
addr = host
|
||||
}
|
||||
now := time.Now().Format("200601")
|
||||
salt := "stcrashreporter"
|
||||
hash := sha256.Sum256([]byte(salt + addr + now))
|
||||
return fmt.Sprintf("%x", hash[:8])
|
||||
}
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -279,6 +280,10 @@ func (s *apiSrv) handleAnnounce(remote net.IP, deviceID protocol.DeviceID, addre
|
||||
dbAddrs[i].Expires = expire
|
||||
}
|
||||
|
||||
// The address slice must always be sorted for database merges to work
|
||||
// properly.
|
||||
sort.Sort(databaseAddressOrder(dbAddrs))
|
||||
|
||||
seen := now.UnixNano()
|
||||
if s.repl != nil {
|
||||
s.repl.send(key, dbAddrs, seen)
|
||||
@@ -295,28 +300,66 @@ func certificateBytes(req *http.Request) []byte {
|
||||
return req.TLS.PeerCertificates[0].Raw
|
||||
}
|
||||
|
||||
var bs []byte
|
||||
|
||||
if hdr := req.Header.Get("X-SSL-Cert"); hdr != "" {
|
||||
bs := []byte(hdr)
|
||||
// The certificate is in PEM format but with spaces for newlines. We
|
||||
// need to reinstate the newlines for the PEM decoder. But we need to
|
||||
// leave the spaces in the BEGIN and END lines - the first and last
|
||||
// space - alone.
|
||||
firstSpace := bytes.Index(bs, []byte(" "))
|
||||
lastSpace := bytes.LastIndex(bs, []byte(" "))
|
||||
for i := firstSpace + 1; i < lastSpace; i++ {
|
||||
if bs[i] == ' ' {
|
||||
bs[i] = '\n'
|
||||
if strings.Contains(hdr, "%") {
|
||||
// Nginx using $ssl_client_escaped_cert
|
||||
// The certificate is in PEM format with url encoding.
|
||||
// We need to decode for the PEM decoder
|
||||
hdr, err := url.QueryUnescape(hdr)
|
||||
if err != nil {
|
||||
// Decoding failed
|
||||
return nil
|
||||
}
|
||||
|
||||
bs = []byte(hdr)
|
||||
} else {
|
||||
// Nginx using $ssl_client_cert
|
||||
// The certificate is in PEM format but with spaces for newlines. We
|
||||
// need to reinstate the newlines for the PEM decoder. But we need to
|
||||
// leave the spaces in the BEGIN and END lines - the first and last
|
||||
// space - alone.
|
||||
bs = []byte(hdr)
|
||||
firstSpace := bytes.Index(bs, []byte(" "))
|
||||
lastSpace := bytes.LastIndex(bs, []byte(" "))
|
||||
for i := firstSpace + 1; i < lastSpace; i++ {
|
||||
if bs[i] == ' ' {
|
||||
bs[i] = '\n'
|
||||
}
|
||||
}
|
||||
}
|
||||
block, _ := pem.Decode(bs)
|
||||
if block == nil {
|
||||
} else if hdr := req.Header.Get("X-Forwarded-Tls-Client-Cert"); hdr != "" {
|
||||
// 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
|
||||
// character and add statements for the PEM decoder
|
||||
hdr, err := url.QueryUnescape(hdr)
|
||||
if err != nil {
|
||||
// Decoding failed
|
||||
return nil
|
||||
}
|
||||
return block.Bytes
|
||||
|
||||
for i := 64; i < len(hdr); i += 65 {
|
||||
hdr = hdr[:i] + "\n" + hdr[i:]
|
||||
}
|
||||
|
||||
hdr = "-----BEGIN CERTIFICATE-----\n" + hdr
|
||||
hdr = hdr + "\n-----END CERTIFICATE-----\n"
|
||||
bs = []byte(hdr)
|
||||
}
|
||||
|
||||
return nil
|
||||
if bs == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
block, _ := pem.Decode(bs)
|
||||
if block == nil {
|
||||
// Decoding failed
|
||||
return nil
|
||||
}
|
||||
|
||||
return block.Bytes
|
||||
}
|
||||
|
||||
// fixupAddresses checks the list of addresses, removing invalid ones and
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
@@ -263,12 +264,15 @@ func (s *levelDBStore) Stop() {
|
||||
// chosen for any duplicates.
|
||||
func merge(a, b DatabaseRecord) DatabaseRecord {
|
||||
// Both lists must be sorted for this to work.
|
||||
sort.Slice(a.Addresses, func(i, j int) bool {
|
||||
return a.Addresses[i].Address < a.Addresses[j].Address
|
||||
})
|
||||
sort.Slice(b.Addresses, func(i, j int) bool {
|
||||
return b.Addresses[i].Address < b.Addresses[j].Address
|
||||
})
|
||||
if !sort.IsSorted(databaseAddressOrder(a.Addresses)) {
|
||||
log.Println("Warning: bug: addresses not correctly sorted in merge")
|
||||
a.Addresses = sortedAddressCopy(a.Addresses)
|
||||
}
|
||||
if !sort.IsSorted(databaseAddressOrder(b.Addresses)) {
|
||||
// no warning because this is the side we read from disk and it may
|
||||
// legitimately predate correct sorting.
|
||||
b.Addresses = sortedAddressCopy(b.Addresses)
|
||||
}
|
||||
|
||||
res := DatabaseRecord{
|
||||
Addresses: make([]DatabaseAddress, 0, len(a.Addresses)+len(b.Addresses)),
|
||||
@@ -352,3 +356,24 @@ func expire(addrs []DatabaseAddress, now int64) []DatabaseAddress {
|
||||
}
|
||||
return addrs
|
||||
}
|
||||
|
||||
func sortedAddressCopy(addrs []DatabaseAddress) []DatabaseAddress {
|
||||
sorted := make([]DatabaseAddress, len(addrs))
|
||||
copy(sorted, addrs)
|
||||
sort.Sort(databaseAddressOrder(sorted))
|
||||
return sorted
|
||||
}
|
||||
|
||||
type databaseAddressOrder []DatabaseAddress
|
||||
|
||||
func (s databaseAddressOrder) Less(a, b int) bool {
|
||||
return s[a].Address < s[b].Address
|
||||
}
|
||||
|
||||
func (s databaseAddressOrder) Swap(a, b int) {
|
||||
s[a], s[b] = s[b], s[a]
|
||||
}
|
||||
|
||||
func (s databaseAddressOrder) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
|
||||
package main
|
||||
|
||||
import proto "github.com/gogo/protobuf/proto"
|
||||
import fmt "fmt"
|
||||
import math "math"
|
||||
import _ "github.com/gogo/protobuf/gogoproto"
|
||||
|
||||
import io "io"
|
||||
import (
|
||||
fmt "fmt"
|
||||
_ "github.com/gogo/protobuf/gogoproto"
|
||||
proto "github.com/gogo/protobuf/proto"
|
||||
io "io"
|
||||
math "math"
|
||||
math_bits "math/bits"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
@@ -19,7 +21,7 @@ var _ = math.Inf
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package
|
||||
const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
|
||||
|
||||
type DatabaseRecord struct {
|
||||
Addresses []DatabaseAddress `protobuf:"bytes,1,rep,name=addresses,proto3" json:"addresses"`
|
||||
@@ -32,7 +34,7 @@ func (m *DatabaseRecord) Reset() { *m = DatabaseRecord{} }
|
||||
func (m *DatabaseRecord) String() string { return proto.CompactTextString(m) }
|
||||
func (*DatabaseRecord) ProtoMessage() {}
|
||||
func (*DatabaseRecord) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_database_0f49e029703a04f5, []int{0}
|
||||
return fileDescriptor_b90fe3356ea5df07, []int{0}
|
||||
}
|
||||
func (m *DatabaseRecord) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
@@ -42,15 +44,15 @@ func (m *DatabaseRecord) XXX_Marshal(b []byte, deterministic bool) ([]byte, erro
|
||||
return xxx_messageInfo_DatabaseRecord.Marshal(b, m, deterministic)
|
||||
} else {
|
||||
b = b[:cap(b)]
|
||||
n, err := m.MarshalTo(b)
|
||||
n, err := m.MarshalToSizedBuffer(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b[:n], nil
|
||||
}
|
||||
}
|
||||
func (dst *DatabaseRecord) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_DatabaseRecord.Merge(dst, src)
|
||||
func (m *DatabaseRecord) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_DatabaseRecord.Merge(m, src)
|
||||
}
|
||||
func (m *DatabaseRecord) XXX_Size() int {
|
||||
return m.Size()
|
||||
@@ -71,7 +73,7 @@ func (m *ReplicationRecord) Reset() { *m = ReplicationRecord{} }
|
||||
func (m *ReplicationRecord) String() string { return proto.CompactTextString(m) }
|
||||
func (*ReplicationRecord) ProtoMessage() {}
|
||||
func (*ReplicationRecord) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_database_0f49e029703a04f5, []int{1}
|
||||
return fileDescriptor_b90fe3356ea5df07, []int{1}
|
||||
}
|
||||
func (m *ReplicationRecord) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
@@ -81,15 +83,15 @@ func (m *ReplicationRecord) XXX_Marshal(b []byte, deterministic bool) ([]byte, e
|
||||
return xxx_messageInfo_ReplicationRecord.Marshal(b, m, deterministic)
|
||||
} else {
|
||||
b = b[:cap(b)]
|
||||
n, err := m.MarshalTo(b)
|
||||
n, err := m.MarshalToSizedBuffer(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b[:n], nil
|
||||
}
|
||||
}
|
||||
func (dst *ReplicationRecord) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_ReplicationRecord.Merge(dst, src)
|
||||
func (m *ReplicationRecord) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_ReplicationRecord.Merge(m, src)
|
||||
}
|
||||
func (m *ReplicationRecord) XXX_Size() int {
|
||||
return m.Size()
|
||||
@@ -109,7 +111,7 @@ func (m *DatabaseAddress) Reset() { *m = DatabaseAddress{} }
|
||||
func (m *DatabaseAddress) String() string { return proto.CompactTextString(m) }
|
||||
func (*DatabaseAddress) ProtoMessage() {}
|
||||
func (*DatabaseAddress) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_database_0f49e029703a04f5, []int{2}
|
||||
return fileDescriptor_b90fe3356ea5df07, []int{2}
|
||||
}
|
||||
func (m *DatabaseAddress) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
@@ -119,15 +121,15 @@ func (m *DatabaseAddress) XXX_Marshal(b []byte, deterministic bool) ([]byte, err
|
||||
return xxx_messageInfo_DatabaseAddress.Marshal(b, m, deterministic)
|
||||
} else {
|
||||
b = b[:cap(b)]
|
||||
n, err := m.MarshalTo(b)
|
||||
n, err := m.MarshalToSizedBuffer(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b[:n], nil
|
||||
}
|
||||
}
|
||||
func (dst *DatabaseAddress) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_DatabaseAddress.Merge(dst, src)
|
||||
func (m *DatabaseAddress) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_DatabaseAddress.Merge(m, src)
|
||||
}
|
||||
func (m *DatabaseAddress) XXX_Size() int {
|
||||
return m.Size()
|
||||
@@ -143,10 +145,34 @@ func init() {
|
||||
proto.RegisterType((*ReplicationRecord)(nil), "main.ReplicationRecord")
|
||||
proto.RegisterType((*DatabaseAddress)(nil), "main.DatabaseAddress")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("database.proto", fileDescriptor_b90fe3356ea5df07) }
|
||||
|
||||
var fileDescriptor_b90fe3356ea5df07 = []byte{
|
||||
// 270 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x90, 0x41, 0x4a, 0xc4, 0x30,
|
||||
0x18, 0x85, 0x9b, 0x49, 0x1d, 0x99, 0x08, 0xa3, 0x06, 0x94, 0x20, 0x12, 0x4b, 0xdd, 0x74, 0xd5,
|
||||
0x01, 0x5d, 0xb9, 0x74, 0xd0, 0x0b, 0xe4, 0x06, 0xe9, 0xe4, 0x77, 0x08, 0x3a, 0x4d, 0x49, 0x2a,
|
||||
0xe8, 0x29, 0xf4, 0x58, 0x5d, 0xce, 0xd2, 0x95, 0x68, 0x7b, 0x11, 0x69, 0x26, 0x55, 0x14, 0x37,
|
||||
0xb3, 0x7b, 0xdf, 0xff, 0xbf, 0x97, 0xbc, 0x84, 0x4c, 0x95, 0xac, 0x65, 0x21, 0x1d, 0xe4, 0x95,
|
||||
0x35, 0xb5, 0xa1, 0xf1, 0x4a, 0xea, 0xf2, 0xe4, 0xdc, 0x42, 0x65, 0xdc, 0xcc, 0x8f, 0x8a, 0xc7,
|
||||
0xbb, 0xd9, 0xd2, 0x2c, 0x8d, 0x07, 0xaf, 0x36, 0xd6, 0xf4, 0x05, 0x91, 0xe9, 0x4d, 0x48, 0x0b,
|
||||
0x58, 0x18, 0xab, 0xe8, 0x15, 0x99, 0x48, 0xa5, 0x2c, 0x38, 0x07, 0x8e, 0xa1, 0x04, 0x67, 0x7b,
|
||||
0x17, 0x47, 0x79, 0x7f, 0x62, 0x3e, 0x18, 0xaf, 0x37, 0xeb, 0x79, 0xdc, 0xbc, 0x9f, 0x45, 0xe2,
|
||||
0xc7, 0x4d, 0x8f, 0xc9, 0x78, 0xa5, 0x7d, 0x6e, 0x94, 0xa0, 0x6c, 0x47, 0x04, 0xa2, 0x94, 0xc4,
|
||||
0x0e, 0xa0, 0x64, 0x38, 0x41, 0x19, 0x16, 0x5e, 0x7f, 0x7b, 0x15, 0x8b, 0xfd, 0x34, 0x50, 0x5a,
|
||||
0x93, 0x43, 0x01, 0xd5, 0x83, 0x5e, 0xc8, 0x5a, 0x9b, 0x32, 0x74, 0x3a, 0x20, 0xf8, 0x1e, 0x9e,
|
||||
0x19, 0x4a, 0x50, 0x36, 0x11, 0xbd, 0xfc, 0xdd, 0x72, 0xb4, 0x55, 0xcb, 0x7f, 0xda, 0xa4, 0xb7,
|
||||
0x64, 0xff, 0x4f, 0x8e, 0x32, 0xb2, 0x1b, 0x32, 0xe1, 0xde, 0x01, 0xfb, 0x0d, 0x3c, 0x55, 0xda,
|
||||
0x86, 0x77, 0x62, 0x31, 0xe0, 0xfc, 0xb4, 0xf9, 0xe4, 0x51, 0xd3, 0x72, 0xb4, 0x6e, 0x39, 0xfa,
|
||||
0x68, 0x39, 0x7a, 0xed, 0x78, 0xb4, 0xee, 0x78, 0xf4, 0xd6, 0xf1, 0xa8, 0x18, 0xfb, 0x3f, 0xbf,
|
||||
0xfc, 0x0a, 0x00, 0x00, 0xff, 0xff, 0x7a, 0xa2, 0xf6, 0x1e, 0xb0, 0x01, 0x00, 0x00,
|
||||
}
|
||||
|
||||
func (m *DatabaseRecord) Marshal() (dAtA []byte, err error) {
|
||||
size := m.Size()
|
||||
dAtA = make([]byte, size)
|
||||
n, err := m.MarshalTo(dAtA)
|
||||
n, err := m.MarshalToSizedBuffer(dAtA[:size])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -154,44 +180,51 @@ func (m *DatabaseRecord) Marshal() (dAtA []byte, err error) {
|
||||
}
|
||||
|
||||
func (m *DatabaseRecord) MarshalTo(dAtA []byte) (int, error) {
|
||||
var i int
|
||||
size := m.Size()
|
||||
return m.MarshalToSizedBuffer(dAtA[:size])
|
||||
}
|
||||
|
||||
func (m *DatabaseRecord) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
i := len(dAtA)
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
if len(m.Addresses) > 0 {
|
||||
for _, msg := range m.Addresses {
|
||||
dAtA[i] = 0xa
|
||||
i++
|
||||
i = encodeVarintDatabase(dAtA, i, uint64(msg.Size()))
|
||||
n, err := msg.MarshalTo(dAtA[i:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i += n
|
||||
}
|
||||
}
|
||||
if m.Misses != 0 {
|
||||
dAtA[i] = 0x10
|
||||
i++
|
||||
i = encodeVarintDatabase(dAtA, i, uint64(m.Misses))
|
||||
if m.Missed != 0 {
|
||||
i = encodeVarintDatabase(dAtA, i, uint64(m.Missed))
|
||||
i--
|
||||
dAtA[i] = 0x20
|
||||
}
|
||||
if m.Seen != 0 {
|
||||
dAtA[i] = 0x18
|
||||
i++
|
||||
i = encodeVarintDatabase(dAtA, i, uint64(m.Seen))
|
||||
i--
|
||||
dAtA[i] = 0x18
|
||||
}
|
||||
if m.Missed != 0 {
|
||||
dAtA[i] = 0x20
|
||||
i++
|
||||
i = encodeVarintDatabase(dAtA, i, uint64(m.Missed))
|
||||
if m.Misses != 0 {
|
||||
i = encodeVarintDatabase(dAtA, i, uint64(m.Misses))
|
||||
i--
|
||||
dAtA[i] = 0x10
|
||||
}
|
||||
return i, nil
|
||||
if len(m.Addresses) > 0 {
|
||||
for iNdEx := len(m.Addresses) - 1; iNdEx >= 0; iNdEx-- {
|
||||
{
|
||||
size, err := m.Addresses[iNdEx].MarshalToSizedBuffer(dAtA[:i])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i -= size
|
||||
i = encodeVarintDatabase(dAtA, i, uint64(size))
|
||||
}
|
||||
i--
|
||||
dAtA[i] = 0xa
|
||||
}
|
||||
}
|
||||
return len(dAtA) - i, nil
|
||||
}
|
||||
|
||||
func (m *ReplicationRecord) Marshal() (dAtA []byte, err error) {
|
||||
size := m.Size()
|
||||
dAtA = make([]byte, size)
|
||||
n, err := m.MarshalTo(dAtA)
|
||||
n, err := m.MarshalToSizedBuffer(dAtA[:size])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -199,40 +232,48 @@ func (m *ReplicationRecord) Marshal() (dAtA []byte, err error) {
|
||||
}
|
||||
|
||||
func (m *ReplicationRecord) MarshalTo(dAtA []byte) (int, error) {
|
||||
var i int
|
||||
size := m.Size()
|
||||
return m.MarshalToSizedBuffer(dAtA[:size])
|
||||
}
|
||||
|
||||
func (m *ReplicationRecord) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
i := len(dAtA)
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
if len(m.Key) > 0 {
|
||||
dAtA[i] = 0xa
|
||||
i++
|
||||
i = encodeVarintDatabase(dAtA, i, uint64(len(m.Key)))
|
||||
i += copy(dAtA[i:], m.Key)
|
||||
if m.Seen != 0 {
|
||||
i = encodeVarintDatabase(dAtA, i, uint64(m.Seen))
|
||||
i--
|
||||
dAtA[i] = 0x18
|
||||
}
|
||||
if len(m.Addresses) > 0 {
|
||||
for _, msg := range m.Addresses {
|
||||
dAtA[i] = 0x12
|
||||
i++
|
||||
i = encodeVarintDatabase(dAtA, i, uint64(msg.Size()))
|
||||
n, err := msg.MarshalTo(dAtA[i:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
for iNdEx := len(m.Addresses) - 1; iNdEx >= 0; iNdEx-- {
|
||||
{
|
||||
size, err := m.Addresses[iNdEx].MarshalToSizedBuffer(dAtA[:i])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i -= size
|
||||
i = encodeVarintDatabase(dAtA, i, uint64(size))
|
||||
}
|
||||
i += n
|
||||
i--
|
||||
dAtA[i] = 0x12
|
||||
}
|
||||
}
|
||||
if m.Seen != 0 {
|
||||
dAtA[i] = 0x18
|
||||
i++
|
||||
i = encodeVarintDatabase(dAtA, i, uint64(m.Seen))
|
||||
if len(m.Key) > 0 {
|
||||
i -= len(m.Key)
|
||||
copy(dAtA[i:], m.Key)
|
||||
i = encodeVarintDatabase(dAtA, i, uint64(len(m.Key)))
|
||||
i--
|
||||
dAtA[i] = 0xa
|
||||
}
|
||||
return i, nil
|
||||
return len(dAtA) - i, nil
|
||||
}
|
||||
|
||||
func (m *DatabaseAddress) Marshal() (dAtA []byte, err error) {
|
||||
size := m.Size()
|
||||
dAtA = make([]byte, size)
|
||||
n, err := m.MarshalTo(dAtA)
|
||||
n, err := m.MarshalToSizedBuffer(dAtA[:size])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -240,32 +281,40 @@ func (m *DatabaseAddress) Marshal() (dAtA []byte, err error) {
|
||||
}
|
||||
|
||||
func (m *DatabaseAddress) MarshalTo(dAtA []byte) (int, error) {
|
||||
var i int
|
||||
size := m.Size()
|
||||
return m.MarshalToSizedBuffer(dAtA[:size])
|
||||
}
|
||||
|
||||
func (m *DatabaseAddress) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
i := len(dAtA)
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
if len(m.Address) > 0 {
|
||||
dAtA[i] = 0xa
|
||||
i++
|
||||
i = encodeVarintDatabase(dAtA, i, uint64(len(m.Address)))
|
||||
i += copy(dAtA[i:], m.Address)
|
||||
}
|
||||
if m.Expires != 0 {
|
||||
dAtA[i] = 0x10
|
||||
i++
|
||||
i = encodeVarintDatabase(dAtA, i, uint64(m.Expires))
|
||||
i--
|
||||
dAtA[i] = 0x10
|
||||
}
|
||||
return i, nil
|
||||
if len(m.Address) > 0 {
|
||||
i -= len(m.Address)
|
||||
copy(dAtA[i:], m.Address)
|
||||
i = encodeVarintDatabase(dAtA, i, uint64(len(m.Address)))
|
||||
i--
|
||||
dAtA[i] = 0xa
|
||||
}
|
||||
return len(dAtA) - i, nil
|
||||
}
|
||||
|
||||
func encodeVarintDatabase(dAtA []byte, offset int, v uint64) int {
|
||||
offset -= sovDatabase(v)
|
||||
base := offset
|
||||
for v >= 1<<7 {
|
||||
dAtA[offset] = uint8(v&0x7f | 0x80)
|
||||
v >>= 7
|
||||
offset++
|
||||
}
|
||||
dAtA[offset] = uint8(v)
|
||||
return offset + 1
|
||||
return base
|
||||
}
|
||||
func (m *DatabaseRecord) Size() (n int) {
|
||||
if m == nil {
|
||||
@@ -330,14 +379,7 @@ func (m *DatabaseAddress) Size() (n int) {
|
||||
}
|
||||
|
||||
func sovDatabase(x uint64) (n int) {
|
||||
for {
|
||||
n++
|
||||
x >>= 7
|
||||
if x == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return n
|
||||
return (math_bits.Len64(x|1) + 6) / 7
|
||||
}
|
||||
func sozDatabase(x uint64) (n int) {
|
||||
return sovDatabase(uint64((x << 1) ^ uint64((int64(x) >> 63))))
|
||||
@@ -357,7 +399,7 @@ func (m *DatabaseRecord) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
wire |= (uint64(b) & 0x7F) << shift
|
||||
wire |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
@@ -385,7 +427,7 @@ func (m *DatabaseRecord) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
msglen |= (int(b) & 0x7F) << shift
|
||||
msglen |= int(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
@@ -394,6 +436,9 @@ func (m *DatabaseRecord) Unmarshal(dAtA []byte) error {
|
||||
return ErrInvalidLengthDatabase
|
||||
}
|
||||
postIndex := iNdEx + msglen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthDatabase
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
@@ -416,7 +461,7 @@ func (m *DatabaseRecord) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
m.Misses |= (int32(b) & 0x7F) << shift
|
||||
m.Misses |= int32(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
@@ -435,7 +480,7 @@ func (m *DatabaseRecord) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
m.Seen |= (int64(b) & 0x7F) << shift
|
||||
m.Seen |= int64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
@@ -454,7 +499,7 @@ func (m *DatabaseRecord) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
m.Missed |= (int64(b) & 0x7F) << shift
|
||||
m.Missed |= int64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
@@ -468,6 +513,9 @@ func (m *DatabaseRecord) Unmarshal(dAtA []byte) error {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthDatabase
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthDatabase
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
@@ -495,7 +543,7 @@ func (m *ReplicationRecord) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
wire |= (uint64(b) & 0x7F) << shift
|
||||
wire |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
@@ -523,7 +571,7 @@ func (m *ReplicationRecord) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
stringLen |= (uint64(b) & 0x7F) << shift
|
||||
stringLen |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
@@ -533,6 +581,9 @@ func (m *ReplicationRecord) Unmarshal(dAtA []byte) error {
|
||||
return ErrInvalidLengthDatabase
|
||||
}
|
||||
postIndex := iNdEx + intStringLen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthDatabase
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
@@ -552,7 +603,7 @@ func (m *ReplicationRecord) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
msglen |= (int(b) & 0x7F) << shift
|
||||
msglen |= int(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
@@ -561,6 +612,9 @@ func (m *ReplicationRecord) Unmarshal(dAtA []byte) error {
|
||||
return ErrInvalidLengthDatabase
|
||||
}
|
||||
postIndex := iNdEx + msglen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthDatabase
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
@@ -583,7 +637,7 @@ func (m *ReplicationRecord) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
m.Seen |= (int64(b) & 0x7F) << shift
|
||||
m.Seen |= int64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
@@ -597,6 +651,9 @@ func (m *ReplicationRecord) Unmarshal(dAtA []byte) error {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthDatabase
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthDatabase
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
@@ -624,7 +681,7 @@ func (m *DatabaseAddress) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
wire |= (uint64(b) & 0x7F) << shift
|
||||
wire |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
@@ -652,7 +709,7 @@ func (m *DatabaseAddress) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
stringLen |= (uint64(b) & 0x7F) << shift
|
||||
stringLen |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
@@ -662,6 +719,9 @@ func (m *DatabaseAddress) Unmarshal(dAtA []byte) error {
|
||||
return ErrInvalidLengthDatabase
|
||||
}
|
||||
postIndex := iNdEx + intStringLen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthDatabase
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
@@ -681,7 +741,7 @@ func (m *DatabaseAddress) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
m.Expires |= (int64(b) & 0x7F) << shift
|
||||
m.Expires |= int64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
@@ -695,6 +755,9 @@ func (m *DatabaseAddress) Unmarshal(dAtA []byte) error {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthDatabase
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthDatabase
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
@@ -710,6 +773,7 @@ func (m *DatabaseAddress) Unmarshal(dAtA []byte) error {
|
||||
func skipDatabase(dAtA []byte) (n int, err error) {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
depth := 0
|
||||
for iNdEx < l {
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
@@ -741,10 +805,8 @@ func skipDatabase(dAtA []byte) (n int, err error) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return iNdEx, nil
|
||||
case 1:
|
||||
iNdEx += 8
|
||||
return iNdEx, nil
|
||||
case 2:
|
||||
var length int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
@@ -761,76 +823,34 @@ func skipDatabase(dAtA []byte) (n int, err error) {
|
||||
break
|
||||
}
|
||||
}
|
||||
iNdEx += length
|
||||
if length < 0 {
|
||||
return 0, ErrInvalidLengthDatabase
|
||||
}
|
||||
return iNdEx, nil
|
||||
iNdEx += length
|
||||
case 3:
|
||||
for {
|
||||
var innerWire uint64
|
||||
var start int = iNdEx
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowDatabase
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
innerWire |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
innerWireType := int(innerWire & 0x7)
|
||||
if innerWireType == 4 {
|
||||
break
|
||||
}
|
||||
next, err := skipDatabase(dAtA[start:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
iNdEx = start + next
|
||||
}
|
||||
return iNdEx, nil
|
||||
depth++
|
||||
case 4:
|
||||
return iNdEx, nil
|
||||
if depth == 0 {
|
||||
return 0, ErrUnexpectedEndOfGroupDatabase
|
||||
}
|
||||
depth--
|
||||
case 5:
|
||||
iNdEx += 4
|
||||
return iNdEx, nil
|
||||
default:
|
||||
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
|
||||
}
|
||||
if iNdEx < 0 {
|
||||
return 0, ErrInvalidLengthDatabase
|
||||
}
|
||||
if depth == 0 {
|
||||
return iNdEx, nil
|
||||
}
|
||||
}
|
||||
panic("unreachable")
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
|
||||
var (
|
||||
ErrInvalidLengthDatabase = fmt.Errorf("proto: negative length found during unmarshaling")
|
||||
ErrIntOverflowDatabase = fmt.Errorf("proto: integer overflow")
|
||||
ErrInvalidLengthDatabase = fmt.Errorf("proto: negative length found during unmarshaling")
|
||||
ErrIntOverflowDatabase = fmt.Errorf("proto: integer overflow")
|
||||
ErrUnexpectedEndOfGroupDatabase = fmt.Errorf("proto: unexpected end of group")
|
||||
)
|
||||
|
||||
func init() { proto.RegisterFile("database.proto", fileDescriptor_database_0f49e029703a04f5) }
|
||||
|
||||
var fileDescriptor_database_0f49e029703a04f5 = []byte{
|
||||
// 270 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x90, 0x41, 0x4a, 0xc4, 0x30,
|
||||
0x18, 0x85, 0x9b, 0x49, 0x1d, 0x99, 0x08, 0xa3, 0x06, 0x94, 0x20, 0x12, 0x4b, 0xdd, 0x74, 0xd5,
|
||||
0x01, 0x5d, 0xb9, 0x74, 0xd0, 0x0b, 0xe4, 0x06, 0xe9, 0xe4, 0x77, 0x08, 0x3a, 0x4d, 0x49, 0x2a,
|
||||
0xe8, 0x29, 0xf4, 0x58, 0x5d, 0xce, 0xd2, 0x95, 0x68, 0x7b, 0x11, 0x69, 0x26, 0x55, 0x14, 0x37,
|
||||
0xb3, 0x7b, 0xdf, 0xff, 0xbf, 0x97, 0xbc, 0x84, 0x4c, 0x95, 0xac, 0x65, 0x21, 0x1d, 0xe4, 0x95,
|
||||
0x35, 0xb5, 0xa1, 0xf1, 0x4a, 0xea, 0xf2, 0xe4, 0xdc, 0x42, 0x65, 0xdc, 0xcc, 0x8f, 0x8a, 0xc7,
|
||||
0xbb, 0xd9, 0xd2, 0x2c, 0x8d, 0x07, 0xaf, 0x36, 0xd6, 0xf4, 0x05, 0x91, 0xe9, 0x4d, 0x48, 0x0b,
|
||||
0x58, 0x18, 0xab, 0xe8, 0x15, 0x99, 0x48, 0xa5, 0x2c, 0x38, 0x07, 0x8e, 0xa1, 0x04, 0x67, 0x7b,
|
||||
0x17, 0x47, 0x79, 0x7f, 0x62, 0x3e, 0x18, 0xaf, 0x37, 0xeb, 0x79, 0xdc, 0xbc, 0x9f, 0x45, 0xe2,
|
||||
0xc7, 0x4d, 0x8f, 0xc9, 0x78, 0xa5, 0x7d, 0x6e, 0x94, 0xa0, 0x6c, 0x47, 0x04, 0xa2, 0x94, 0xc4,
|
||||
0x0e, 0xa0, 0x64, 0x38, 0x41, 0x19, 0x16, 0x5e, 0x7f, 0x7b, 0x15, 0x8b, 0xfd, 0x34, 0x50, 0x5a,
|
||||
0x93, 0x43, 0x01, 0xd5, 0x83, 0x5e, 0xc8, 0x5a, 0x9b, 0x32, 0x74, 0x3a, 0x20, 0xf8, 0x1e, 0x9e,
|
||||
0x19, 0x4a, 0x50, 0x36, 0x11, 0xbd, 0xfc, 0xdd, 0x72, 0xb4, 0x55, 0xcb, 0x7f, 0xda, 0xa4, 0xb7,
|
||||
0x64, 0xff, 0x4f, 0x8e, 0x32, 0xb2, 0x1b, 0x32, 0xe1, 0xde, 0x01, 0xfb, 0x0d, 0x3c, 0x55, 0xda,
|
||||
0x86, 0x77, 0x62, 0x31, 0xe0, 0xfc, 0xb4, 0xf9, 0xe4, 0x51, 0xd3, 0x72, 0xb4, 0x6e, 0x39, 0xfa,
|
||||
0x68, 0x39, 0x7a, 0xed, 0x78, 0xb4, 0xee, 0x78, 0xf4, 0xd6, 0xf1, 0xa8, 0x18, 0xfb, 0x3f, 0xbf,
|
||||
0xfc, 0x0a, 0x00, 0x00, 0xff, 0xff, 0x7a, 0xa2, 0xf6, 0x1e, 0xb0, 0x01, 0x00, 0x00,
|
||||
}
|
||||
|
||||
4
cmd/stdiscosrv/etc/firewall-ufw/stdiscosrv
Normal file
@@ -0,0 +1,4 @@
|
||||
[stdiscosrv]
|
||||
title=Syncthing discovery server
|
||||
description=Lets syncthing clients discover each other
|
||||
ports=8443/tcp
|
||||
3
cmd/stdiscosrv/etc/linux-systemd/default
Normal file
@@ -0,0 +1,3 @@
|
||||
# Default settings for syncthing-discosrv (stdiscosrv).
|
||||
## Add Options here:
|
||||
DISCOSRV_OPTS=
|
||||
25
cmd/stdiscosrv/etc/linux-systemd/stdiscosrv.service
Normal file
@@ -0,0 +1,25 @@
|
||||
[Unit]
|
||||
Description=Syncthing Discovery Server
|
||||
After=network.target
|
||||
Documentation=man:stdiscosrv(1)
|
||||
|
||||
[Service]
|
||||
WorkingDirectory=/var/lib/syncthing-discosrv
|
||||
EnvironmentFile=/etc/default/syncthing-discosrv
|
||||
ExecStart=/usr/bin/stdiscosrv $DISCOSRV_OPTS
|
||||
|
||||
# Hardening
|
||||
User=syncthing-discosrv
|
||||
Group=syncthing
|
||||
ProtectSystem=strict
|
||||
ReadWritePaths=/var/lib/syncthing-discosrv
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
PrivateDevices=true
|
||||
ProtectHome=true
|
||||
SystemCallArchitectures=native
|
||||
MemoryDenyWriteExecute=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Alias=syncthing-discosrv.service
|
||||
@@ -9,17 +9,15 @@ package main
|
||||
import (
|
||||
"crypto/tls"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"github.com/syncthing/syncthing/lib/build"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
"github.com/syncthing/syncthing/lib/tlsutil"
|
||||
"github.com/syndtr/goleveldb/leveldb/opt"
|
||||
@@ -65,24 +63,6 @@ var levelDBOptions = &opt.Options{
|
||||
WriteBuffer: 32 << 20, // default 4<<20
|
||||
}
|
||||
|
||||
var (
|
||||
Version string
|
||||
BuildStamp string
|
||||
BuildUser string
|
||||
BuildHost string
|
||||
|
||||
BuildDate time.Time
|
||||
LongVersion string
|
||||
)
|
||||
|
||||
func init() {
|
||||
stamp, _ := strconv.Atoi(BuildStamp)
|
||||
BuildDate = time.Unix(int64(stamp), 0)
|
||||
|
||||
date := BuildDate.UTC().Format("2006-01-02 15:04:05 MST")
|
||||
LongVersion = fmt.Sprintf(`stdiscosrv %s (%s %s-%s) %s@%s %s`, Version, runtime.Version(), runtime.GOOS, runtime.GOARCH, BuildUser, BuildHost, date)
|
||||
}
|
||||
|
||||
var (
|
||||
debug = false
|
||||
)
|
||||
@@ -109,19 +89,24 @@ func main() {
|
||||
flag.StringVar(&metricsListen, "metrics-listen", "", "Metrics listen address")
|
||||
flag.StringVar(&replicationPeers, "replicate", "", "Replication peers, id@address, comma separated")
|
||||
flag.StringVar(&replicationListen, "replication-listen", ":19200", "Replication listen address")
|
||||
showVersion := flag.Bool("version", false, "Show version")
|
||||
flag.Parse()
|
||||
|
||||
log.Println(LongVersion)
|
||||
log.Println(build.LongVersionFor("stdiscosrv"))
|
||||
if *showVersion {
|
||||
return
|
||||
}
|
||||
|
||||
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
log.Println("Failed to load keypair. Generating one, this might take a while...")
|
||||
cert, err = tlsutil.NewCertificate(certFile, keyFile, "stdiscosrv")
|
||||
cert, err = tlsutil.NewCertificate(certFile, keyFile, "stdiscosrv", 20*365)
|
||||
if err != nil {
|
||||
log.Fatalln("Failed to generate X509 key pair:", err)
|
||||
}
|
||||
} else if err != nil {
|
||||
log.Fatalln("Failed to load keypair:", err)
|
||||
}
|
||||
|
||||
devID := protocol.NewDeviceID(cert.Certificate[0])
|
||||
log.Println("Server device ID is", devID)
|
||||
|
||||
|
||||
4
cmd/stdiscosrv/scripts/preinst
Normal file
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
addgroup --system syncthing
|
||||
adduser --system --home /var/lib/syncthing-discosrv --ingroup syncthing syncthing-discosrv
|
||||
@@ -7,6 +7,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"flag"
|
||||
@@ -17,6 +18,7 @@ import (
|
||||
|
||||
"github.com/syncthing/syncthing/lib/config"
|
||||
"github.com/syncthing/syncthing/lib/discover"
|
||||
"github.com/syncthing/syncthing/lib/events"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
)
|
||||
|
||||
@@ -82,7 +84,7 @@ func checkServers(deviceID protocol.DeviceID, servers ...string) {
|
||||
}
|
||||
|
||||
func checkServer(deviceID protocol.DeviceID, server string) checkResult {
|
||||
disco, err := discover.NewGlobal(server, tls.Certificate{}, nil)
|
||||
disco, err := discover.NewGlobal(server, tls.Certificate{}, nil, events.NoopLogger)
|
||||
if err != nil {
|
||||
return checkResult{error: err}
|
||||
}
|
||||
@@ -94,7 +96,7 @@ func checkServer(deviceID protocol.DeviceID, server string) checkResult {
|
||||
})
|
||||
|
||||
go func() {
|
||||
addresses, err := disco.Lookup(deviceID)
|
||||
addresses, err := disco.Lookup(context.Background(), deviceID)
|
||||
res <- checkResult{addresses: addresses, error: err}
|
||||
}()
|
||||
|
||||
|
||||
58
cmd/stindex/accounting.go
Normal file
@@ -0,0 +1,58 @@
|
||||
// Copyright (C) 2020 The Syncthing Authors.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/db/backend"
|
||||
)
|
||||
|
||||
// account prints key and data size statistics per class
|
||||
func account(ldb backend.Backend) {
|
||||
it, err := ldb.NewPrefixIterator(nil)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
var ksizes [256]int
|
||||
var dsizes [256]int
|
||||
var counts [256]int
|
||||
var max [256]int
|
||||
|
||||
for it.Next() {
|
||||
key := it.Key()
|
||||
t := key[0]
|
||||
ds := len(it.Value())
|
||||
ks := len(key)
|
||||
s := ks + ds
|
||||
|
||||
counts[t]++
|
||||
ksizes[t] += ks
|
||||
dsizes[t] += ds
|
||||
if s > max[t] {
|
||||
max[t] = s
|
||||
}
|
||||
}
|
||||
|
||||
tw := tabwriter.NewWriter(os.Stdout, 1, 1, 1, ' ', tabwriter.AlignRight)
|
||||
toti, totds, totks := 0, 0, 0
|
||||
for t := range ksizes {
|
||||
if ksizes[t] > 0 {
|
||||
// yes metric kilobytes 🤘
|
||||
fmt.Fprintf(tw, "0x%02x:\t%d items,\t%d KB keys +\t%d KB data,\t%d B +\t%d B avg,\t%d B max\t\n", t, counts[t], ksizes[t]/1000, dsizes[t]/1000, ksizes[t]/counts[t], dsizes[t]/counts[t], max[t])
|
||||
toti += counts[t]
|
||||
totds += dsizes[t]
|
||||
totks += ksizes[t]
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(tw, "Total\t%d items,\t%d KB keys +\t%d KB data.\t\n", toti, totks/1000, totds/1000)
|
||||
tw.Flush()
|
||||
}
|
||||
@@ -13,11 +13,15 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/db"
|
||||
"github.com/syncthing/syncthing/lib/db/backend"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
)
|
||||
|
||||
func dump(ldb *db.Lowlevel) {
|
||||
it := ldb.NewIterator(nil, nil)
|
||||
func dump(ldb backend.Backend) {
|
||||
it, err := ldb.NewPrefixIterator(nil)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
for it.Next() {
|
||||
key := it.Key()
|
||||
switch key[0] {
|
||||
@@ -48,10 +52,10 @@ func dump(ldb *db.Lowlevel) {
|
||||
fmt.Printf("[block] F:%d H:%x N:%q I:%d\n", folder, hash, name, binary.BigEndian.Uint32(it.Value()))
|
||||
|
||||
case db.KeyTypeDeviceStatistic:
|
||||
fmt.Printf("[dstat] K:%x V:%x\n", it.Key(), it.Value())
|
||||
fmt.Printf("[dstat] K:%x V:%x\n", key, it.Value())
|
||||
|
||||
case db.KeyTypeFolderStatistic:
|
||||
fmt.Printf("[fstat] K:%x V:%x\n", it.Key(), it.Value())
|
||||
fmt.Printf("[fstat] K:%x V:%x\n", key, it.Value())
|
||||
|
||||
case db.KeyTypeVirtualMtime:
|
||||
folder := binary.BigEndian.Uint32(key[1:])
|
||||
@@ -63,21 +67,59 @@ func dump(ldb *db.Lowlevel) {
|
||||
fmt.Printf("[mtime] F:%d N:%q R:%v V:%v\n", folder, name, real, virt)
|
||||
|
||||
case db.KeyTypeFolderIdx:
|
||||
key := binary.BigEndian.Uint32(it.Key()[1:])
|
||||
key := binary.BigEndian.Uint32(key[1:])
|
||||
fmt.Printf("[folderidx] K:%d V:%q\n", key, it.Value())
|
||||
|
||||
case db.KeyTypeDeviceIdx:
|
||||
key := binary.BigEndian.Uint32(it.Key()[1:])
|
||||
key := binary.BigEndian.Uint32(key[1:])
|
||||
val := it.Value()
|
||||
if len(val) == 0 {
|
||||
fmt.Printf("[deviceidx] K:%d V:<nil>\n", key)
|
||||
} else {
|
||||
dev := protocol.DeviceIDFromBytes(val)
|
||||
fmt.Printf("[deviceidx] K:%d V:%s\n", key, dev)
|
||||
device := "<nil>"
|
||||
if len(val) > 0 {
|
||||
dev, err := protocol.DeviceIDFromBytes(val)
|
||||
if err != nil {
|
||||
device = fmt.Sprintf("<invalid %d bytes>", len(val))
|
||||
} else {
|
||||
device = dev.String()
|
||||
}
|
||||
}
|
||||
fmt.Printf("[deviceidx] K:%d V:%s\n", key, device)
|
||||
|
||||
case db.KeyTypeIndexID:
|
||||
device := binary.BigEndian.Uint32(key[1:])
|
||||
folder := binary.BigEndian.Uint32(key[5:])
|
||||
fmt.Printf("[indexid] D:%d F:%d I:%x\n", device, folder, it.Value())
|
||||
|
||||
case db.KeyTypeFolderMeta:
|
||||
folder := binary.BigEndian.Uint32(key[1:])
|
||||
fmt.Printf("[foldermeta] F:%d V:%x\n", folder, it.Value())
|
||||
|
||||
case db.KeyTypeMiscData:
|
||||
fmt.Printf("[miscdata] K:%q V:%q\n", key[1:], it.Value())
|
||||
|
||||
case db.KeyTypeSequence:
|
||||
folder := binary.BigEndian.Uint32(key[1:])
|
||||
seq := binary.BigEndian.Uint64(key[5:])
|
||||
fmt.Printf("[sequence] F:%d S:%d V:%q\n", folder, seq, it.Value())
|
||||
|
||||
case db.KeyTypeNeed:
|
||||
folder := binary.BigEndian.Uint32(key[1:])
|
||||
file := string(key[5:])
|
||||
fmt.Printf("[need] F:%d V:%q\n", folder, file)
|
||||
|
||||
case db.KeyTypeBlockList:
|
||||
fmt.Printf("[blocklist] H:%x\n", key[1:])
|
||||
|
||||
case db.KeyTypeBlockListMap:
|
||||
folder := binary.BigEndian.Uint32(key[1:])
|
||||
hash := key[5:37]
|
||||
fileName := string(key[37:])
|
||||
fmt.Printf("[blocklistmap] F:%d H:%x N:%s\n", folder, hash, fileName)
|
||||
|
||||
case db.KeyTypeVersion:
|
||||
fmt.Printf("[version] H:%x\n", key[1:])
|
||||
|
||||
default:
|
||||
fmt.Printf("[???]\n %x\n %x\n", it.Key(), it.Value())
|
||||
fmt.Printf("[??? %d]\n %x\n %x\n", key[0], key, it.Value())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,8 +10,10 @@ import (
|
||||
"container/heap"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/db"
|
||||
"github.com/syncthing/syncthing/lib/db/backend"
|
||||
)
|
||||
|
||||
type SizedElement struct {
|
||||
@@ -37,11 +39,14 @@ func (h *ElementHeap) Pop() interface{} {
|
||||
return x
|
||||
}
|
||||
|
||||
func dumpsize(ldb *db.Lowlevel) {
|
||||
func dumpsize(ldb backend.Backend) {
|
||||
h := &ElementHeap{}
|
||||
heap.Init(h)
|
||||
|
||||
it := ldb.NewIterator(nil, nil)
|
||||
it, err := ldb.NewPrefixIterator(nil)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
var ele SizedElement
|
||||
for it.Next() {
|
||||
key := it.Key()
|
||||
|
||||
@@ -10,8 +10,11 @@ import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"log"
|
||||
"sort"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/db"
|
||||
"github.com/syncthing/syncthing/lib/db/backend"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
)
|
||||
|
||||
@@ -31,7 +34,7 @@ type sequenceKey struct {
|
||||
sequence uint64
|
||||
}
|
||||
|
||||
func idxck(ldb *db.Lowlevel) (success bool) {
|
||||
func idxck(ldb backend.Backend) (success bool) {
|
||||
folders := make(map[uint32]string)
|
||||
devices := make(map[uint32]string)
|
||||
deviceToIDs := make(map[string]uint32)
|
||||
@@ -39,10 +42,17 @@ func idxck(ldb *db.Lowlevel) (success bool) {
|
||||
globals := make(map[globalKey]db.VersionList)
|
||||
sequences := make(map[sequenceKey]string)
|
||||
needs := make(map[globalKey]struct{})
|
||||
blocklists := make(map[string]struct{})
|
||||
versions := make(map[string]protocol.Vector)
|
||||
usedBlocklists := make(map[string]struct{})
|
||||
usedVersions := make(map[string]struct{})
|
||||
var localDeviceKey uint32
|
||||
success = true
|
||||
|
||||
it := ldb.NewIterator(nil, nil)
|
||||
it, err := ldb.NewPrefixIterator(nil)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
for it.Next() {
|
||||
key := it.Key()
|
||||
switch key[0] {
|
||||
@@ -94,6 +104,20 @@ func idxck(ldb *db.Lowlevel) (success bool) {
|
||||
folder := binary.BigEndian.Uint32(key[1:])
|
||||
name := nulString(key[1+4:])
|
||||
needs[globalKey{folder, name}] = struct{}{}
|
||||
|
||||
case db.KeyTypeBlockList:
|
||||
hash := string(key[1:])
|
||||
blocklists[hash] = struct{}{}
|
||||
|
||||
case db.KeyTypeVersion:
|
||||
hash := string(key[1:])
|
||||
var v protocol.Vector
|
||||
if err := v.Unmarshal(it.Value()); err != nil {
|
||||
fmt.Println("Unable to unmarshal Vector:", err)
|
||||
success = false
|
||||
continue
|
||||
}
|
||||
versions[hash] = v
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,6 +127,7 @@ func idxck(ldb *db.Lowlevel) (success bool) {
|
||||
return
|
||||
}
|
||||
|
||||
var missingSeq []sequenceKey
|
||||
for fk, fi := range fileInfos {
|
||||
if fk.name != fi.Name {
|
||||
fmt.Printf("Mismatching FileInfo name, %q (key) != %q (actual)\n", fk.name, fi.Name)
|
||||
@@ -121,9 +146,11 @@ func idxck(ldb *db.Lowlevel) (success bool) {
|
||||
}
|
||||
|
||||
if fk.device == localDeviceKey {
|
||||
name, ok := sequences[sequenceKey{fk.folder, uint64(fi.Sequence)}]
|
||||
sk := sequenceKey{fk.folder, uint64(fi.Sequence)}
|
||||
name, ok := sequences[sk]
|
||||
if !ok {
|
||||
fmt.Printf("Sequence entry missing for FileInfo %q, folder %q, seq %d\n", fi.Name, folder, fi.Sequence)
|
||||
missingSeq = append(missingSeq, sk)
|
||||
success = false
|
||||
continue
|
||||
}
|
||||
@@ -132,6 +159,58 @@ func idxck(ldb *db.Lowlevel) (success bool) {
|
||||
success = false
|
||||
}
|
||||
}
|
||||
|
||||
if len(fi.Blocks) == 0 && len(fi.BlocksHash) != 0 {
|
||||
key := string(fi.BlocksHash)
|
||||
if _, ok := blocklists[key]; !ok {
|
||||
fmt.Printf("Missing block list for file %q, block list hash %x\n", fi.Name, fi.BlocksHash)
|
||||
success = false
|
||||
} else {
|
||||
usedBlocklists[key] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
if fi.VersionHash != nil {
|
||||
key := string(fi.VersionHash)
|
||||
if _, ok := versions[key]; !ok {
|
||||
fmt.Printf("Missing version vector for file %q, version hash %x\n", fi.Name, fi.VersionHash)
|
||||
success = false
|
||||
} else {
|
||||
usedVersions[key] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
_, ok := globals[globalKey{fk.folder, fk.name}]
|
||||
if !ok {
|
||||
fmt.Printf("Missing global for file %q\n", fi.Name)
|
||||
success = false
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Aggregate the ranges of missing sequence entries, print them
|
||||
|
||||
sort.Slice(missingSeq, func(a, b int) bool {
|
||||
if missingSeq[a].folder != missingSeq[b].folder {
|
||||
return missingSeq[a].folder < missingSeq[b].folder
|
||||
}
|
||||
return missingSeq[a].sequence < missingSeq[b].sequence
|
||||
})
|
||||
|
||||
var folder uint32
|
||||
var startSeq, prevSeq uint64
|
||||
for _, sk := range missingSeq {
|
||||
if folder != sk.folder || sk.sequence != prevSeq+1 {
|
||||
if folder != 0 {
|
||||
fmt.Printf("Folder %d missing %d sequence entries: #%d - #%d\n", folder, prevSeq-startSeq+1, startSeq, prevSeq)
|
||||
}
|
||||
startSeq = sk.sequence
|
||||
folder = sk.folder
|
||||
}
|
||||
prevSeq = sk.sequence
|
||||
}
|
||||
if folder != 0 {
|
||||
fmt.Printf("Folder %d missing %d sequence entries: #%d - #%d\n", folder, prevSeq-startSeq+1, startSeq, prevSeq)
|
||||
}
|
||||
|
||||
for gk, vl := range globals {
|
||||
@@ -140,10 +219,10 @@ func idxck(ldb *db.Lowlevel) (success bool) {
|
||||
fmt.Printf("Unknown folder ID %d for VersionList %q\n", gk.folder, gk.name)
|
||||
success = false
|
||||
}
|
||||
for i, fv := range vl.Versions {
|
||||
dev, ok := deviceToIDs[string(fv.Device)]
|
||||
checkGlobal := func(i int, device []byte, version protocol.Vector, invalid, deleted bool) {
|
||||
dev, ok := deviceToIDs[string(device)]
|
||||
if !ok {
|
||||
fmt.Printf("VersionList %q, folder %q refers to unknown device %q\n", gk.name, folder, fv.Device)
|
||||
fmt.Printf("VersionList %q, folder %q refers to unknown device %q\n", gk.name, folder, device)
|
||||
success = false
|
||||
}
|
||||
fi, ok := fileInfos[fileInfoKey{gk.folder, dev, gk.name}]
|
||||
@@ -151,14 +230,31 @@ func idxck(ldb *db.Lowlevel) (success bool) {
|
||||
fmt.Printf("VersionList %q, folder %q, entry %d refers to unknown FileInfo\n", gk.name, folder, i)
|
||||
success = false
|
||||
}
|
||||
if !fi.Version.Equal(fv.Version) {
|
||||
fmt.Printf("VersionList %q, folder %q, entry %d, FileInfo version mismatch, %v (VersionList) != %v (FileInfo)\n", gk.name, folder, i, fv.Version, fi.Version)
|
||||
|
||||
fiv := fi.Version
|
||||
if fi.VersionHash != nil {
|
||||
fiv = versions[string(fi.VersionHash)]
|
||||
}
|
||||
if !fiv.Equal(version) {
|
||||
fmt.Printf("VersionList %q, folder %q, entry %d, FileInfo version mismatch, %v (VersionList) != %v (FileInfo)\n", gk.name, folder, i, version, fi.Version)
|
||||
success = false
|
||||
}
|
||||
if fi.IsInvalid() != fv.Invalid {
|
||||
fmt.Printf("VersionList %q, folder %q, entry %d, FileInfo invalid mismatch, %v (VersionList) != %v (FileInfo)\n", gk.name, folder, i, fv.Invalid, fi.IsInvalid())
|
||||
if fi.IsInvalid() != invalid {
|
||||
fmt.Printf("VersionList %q, folder %q, entry %d, FileInfo invalid mismatch, %v (VersionList) != %v (FileInfo)\n", gk.name, folder, i, invalid, fi.IsInvalid())
|
||||
success = false
|
||||
}
|
||||
if fi.IsDeleted() != deleted {
|
||||
fmt.Printf("VersionList %q, folder %q, entry %d, FileInfo deleted mismatch, %v (VersionList) != %v (FileInfo)\n", gk.name, folder, i, deleted, fi.IsDeleted())
|
||||
success = false
|
||||
}
|
||||
}
|
||||
for i, fv := range vl.RawVersions {
|
||||
for _, device := range fv.Devices {
|
||||
checkGlobal(i, device, fv.Version, false, fv.Deleted)
|
||||
}
|
||||
for _, device := range fv.InvalidDevices {
|
||||
checkGlobal(i, device, fv.Version, true, fv.Deleted)
|
||||
}
|
||||
}
|
||||
|
||||
// If we need this file we should have a need entry for it. False
|
||||
@@ -167,7 +263,9 @@ func idxck(ldb *db.Lowlevel) (success bool) {
|
||||
if needsLocally(vl) {
|
||||
_, ok := needs[gk]
|
||||
if !ok {
|
||||
dev := deviceToIDs[string(vl.Versions[0].Device)]
|
||||
fv, _ := vl.GetGlobal()
|
||||
devB, _ := fv.FirstDevice()
|
||||
dev := deviceToIDs[string(devB)]
|
||||
fi := fileInfos[fileInfoKey{gk.folder, dev, gk.name}]
|
||||
if !fi.IsDeleted() && !fi.IsIgnored() {
|
||||
fmt.Printf("Missing need entry for needed file %q, folder %q\n", gk.name, folder)
|
||||
@@ -224,19 +322,21 @@ func idxck(ldb *db.Lowlevel) (success bool) {
|
||||
}
|
||||
}
|
||||
|
||||
if d := len(blocklists) - len(usedBlocklists); d > 0 {
|
||||
fmt.Printf("%d block list entries out of %d needs GC\n", d, len(blocklists))
|
||||
}
|
||||
if d := len(versions) - len(usedVersions); d > 0 {
|
||||
fmt.Printf("%d version entries out of %d needs GC\n", d, len(versions))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func needsLocally(vl db.VersionList) bool {
|
||||
var lv *protocol.Vector
|
||||
for _, fv := range vl.Versions {
|
||||
if bytes.Equal(fv.Device, protocol.LocalDeviceID[:]) {
|
||||
lv = &fv.Version
|
||||
break
|
||||
}
|
||||
}
|
||||
if lv == nil {
|
||||
fv, ok := vl.Get(protocol.LocalDeviceID[:])
|
||||
if !ok {
|
||||
return true // proviosinally, it looks like we need the file
|
||||
}
|
||||
return !lv.GreaterEqual(vl.Versions[0].Version)
|
||||
gfv, _ := vl.GetGlobal() // Can't not have a global if we got something above
|
||||
return !fv.Version.GreaterEqual(gfv.Version)
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/db"
|
||||
"github.com/syncthing/syncthing/lib/db/backend"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -30,20 +30,34 @@ func main() {
|
||||
path = filepath.Join(defaultConfigDir(), "index-v0.14.0.db")
|
||||
}
|
||||
|
||||
ldb, err := db.OpenRO(path)
|
||||
var ldb backend.Backend
|
||||
var err error
|
||||
if looksLikeBadger(path) {
|
||||
ldb, err = backend.OpenBadger(path)
|
||||
} else {
|
||||
ldb, err = backend.OpenLevelDBRO(path)
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if mode == "dump" {
|
||||
switch mode {
|
||||
case "dump":
|
||||
dump(ldb)
|
||||
} else if mode == "dumpsize" {
|
||||
case "dumpsize":
|
||||
dumpsize(ldb)
|
||||
} else if mode == "idxck" {
|
||||
case "idxck":
|
||||
if !idxck(ldb) {
|
||||
os.Exit(1)
|
||||
}
|
||||
} else {
|
||||
case "account":
|
||||
account(ldb)
|
||||
default:
|
||||
fmt.Println("Unknown mode")
|
||||
}
|
||||
}
|
||||
|
||||
func looksLikeBadger(path string) bool {
|
||||
_, err := os.Stat(filepath.Join(path, "KEYREGISTRY"))
|
||||
return err == nil
|
||||
}
|
||||
|
||||
@@ -9,8 +9,15 @@
|
||||
<meta name="author" content=""/>
|
||||
|
||||
<title>Relay stats</title>
|
||||
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet"/>
|
||||
<link rel="stylesheet" href="//use.fontawesome.com/releases/v5.0.13/css/all.css"/>
|
||||
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.0.13/css/all.css"/>
|
||||
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet"/>
|
||||
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.0.13/css/all.css"/>
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.6.0/dist/leaflet.css"
|
||||
integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ=="
|
||||
crossorigin=""/>
|
||||
<script src="https://unpkg.com/leaflet@1.6.0/dist/leaflet.js"
|
||||
integrity="sha512-gZwIG9x3wUXg2hdXF6+rVkLF/0Vi9U8D2Ntg4Ga5I5BZpVkVxlJWbSQtXPSiUTtC0TjtGOmxa1AJPuV0CPthew=="
|
||||
crossorigin=""></script>
|
||||
|
||||
<style>
|
||||
#map {
|
||||
@@ -38,13 +45,15 @@
|
||||
<div class="container">
|
||||
<h1>Relay Pool Data</h1>
|
||||
<div ng-if="relays === undefined" class="text-center">
|
||||
<img src="//cdnjs.cloudflare.com/ajax/libs/galleriffic/2.0.1/css/loader.gif" alt=""/>
|
||||
<img src="https://cdnjs.cloudflare.com/ajax/libs/galleriffic/2.0.1/css/loader.gif" alt=""/>
|
||||
<p>Please wait while we gather data</p>
|
||||
</div>
|
||||
<div>
|
||||
<div ng-show="relays !== undefined" class="ng-hide">
|
||||
<p>
|
||||
Currently {{ relays.length }} relays online ({{ totals.goMaxProcs }} cores in total).
|
||||
The relays listed on this page are not managed or vetted by the Syncthing project.
|
||||
Each relay is the responsibility of the relay operator.
|
||||
Currently {{ relays.length }} relays online.
|
||||
</p>
|
||||
</div>
|
||||
<div id="map"></div> <!-- Can't hide the map, otherwise it freaks out -->
|
||||
@@ -184,18 +193,17 @@
|
||||
</div>
|
||||
|
||||
|
||||
<script type="text/javascript" src="//code.jquery.com/jquery-2.1.4.min.js"></script>
|
||||
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.8/angular.min.js"></script>
|
||||
<script type="text/javascript" src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
|
||||
<script type="text/javascript" src="//maps.googleapis.com/maps/api/js?key=AIzaSyDk5WJ8s7ueLKb99X5DbQ-vkWtPDAKqYs0"></script>
|
||||
<script type="text/javascript" src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
|
||||
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.8/angular.min.js"></script>
|
||||
<script type="text/javascript" src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
|
||||
</body>
|
||||
|
||||
<script>
|
||||
angular.module('syncthing', [
|
||||
])
|
||||
.config(function($httpProvider) {
|
||||
.config(['$httpProvider', function($httpProvider) {
|
||||
$httpProvider.defaults.timeout = 5000;
|
||||
})
|
||||
}])
|
||||
.filter('bytes', function() {
|
||||
return function(bytes, precision) {
|
||||
if (isNaN(parseFloat(bytes)) || !isFinite(bytes)) return '-';
|
||||
@@ -228,11 +236,12 @@
|
||||
numProxies: 0,
|
||||
uptimeSeconds: 0,
|
||||
};
|
||||
$scope.map = new google.maps.Map(document.getElementById('map'), {
|
||||
zoom: 1,
|
||||
mapTypeId: google.maps.MapTypeId.ROADMAP
|
||||
});
|
||||
$scope.mapBounds = new google.maps.LatLngBounds();
|
||||
$scope.map = L.map('map').setView([40.90296, 1.90925], 2);
|
||||
L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
|
||||
{
|
||||
attribution: 'Leaflet',
|
||||
maxZoom: 17
|
||||
}).addTo($scope.map);
|
||||
$scope.tooltipTemplate = $('#infoTemplate').html();
|
||||
$scope.usedLocations = {};
|
||||
$scope.sortType = 'stats.numActiveSessions';
|
||||
@@ -279,8 +288,9 @@
|
||||
}
|
||||
});
|
||||
|
||||
$scope.map.fitBounds($scope.mapBounds);
|
||||
if ($scope.relays.length == 1) {
|
||||
//Center to only relay with zoom
|
||||
$scope.map.panTo(new L.LatLng(relays[0].location.latitude, relays[0].location.longitude));
|
||||
$scope.map.setZoom(13);
|
||||
}
|
||||
});
|
||||
@@ -300,44 +310,50 @@
|
||||
|
||||
var locParts = loc.split(',');
|
||||
|
||||
relay.marker = new google.maps.Marker({
|
||||
map: $scope.map,
|
||||
position: new google.maps.LatLng(locParts[0], locParts[1]),
|
||||
relay.marker = new L.Marker([relay.location.latitude, relay.location.longitude],{
|
||||
title: relay.url,
|
||||
});
|
||||
|
||||
var scope = $rootScope.$new(true);
|
||||
scope.relay = relay;
|
||||
|
||||
relay.marker.info = new google.maps.InfoWindow({
|
||||
content: $compile($scope.tooltipTemplate)(scope)[0],
|
||||
var icon = new L.Icon({
|
||||
iconSize: [18, 28], // size of the icon
|
||||
iconAnchor: [9, 28], // point of the icon which will correspond to marker's location
|
||||
shadowAnchor: [0, 0], // the same for the shadow
|
||||
popupAnchor: [0, -27], // popup anchor
|
||||
shadowSize: [0,0],
|
||||
iconUrl: 'https://cdn.rawgit.com/pointhi/leaflet-color-markers/master/img/marker-icon-red.png',
|
||||
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png',
|
||||
});
|
||||
|
||||
relay.marker = new L.marker(new L.latLng(locParts[0], locParts[1]),{icon})
|
||||
.bindPopup($compile($scope.tooltipTemplate)(scope)[0],{})
|
||||
.on('mouseover', function (e) {
|
||||
this.openPopup();
|
||||
}).on('mouseout', function (e) {
|
||||
this.closePopup();
|
||||
}).addTo($scope.map);
|
||||
|
||||
relay.showMarker = function() {
|
||||
relay.marker.info.open($scope.map, relay.marker);
|
||||
relay.marker.openPopup();
|
||||
}
|
||||
|
||||
|
||||
relay.hideMarker = function() {
|
||||
relay.marker.info.close();
|
||||
relay.marker.closePopup();
|
||||
}
|
||||
}
|
||||
|
||||
relay.marker.addListener('mouseover', relay.showMarker);
|
||||
relay.marker.addListener('mouseout', relay.hideMarker);
|
||||
|
||||
$scope.mapBounds.extend(relay.marker.position);
|
||||
}
|
||||
|
||||
function addCircleToMap(relay) {
|
||||
relay.marker.circle = new google.maps.Circle({
|
||||
strokeColor: '#FF0000',
|
||||
strokeOpacity: 0.8,
|
||||
strokeWeight: 2,
|
||||
fillColor: '#FF0000',
|
||||
fillOpacity: 0.35,
|
||||
map: $scope.map,
|
||||
center: relay.marker.position,
|
||||
radius: ((relay.stats.bytesProxied * 100) / $scope.totals.bytesProxied) * 10000
|
||||
});
|
||||
console.log(relay.location.latitude)
|
||||
L.circle([relay.location.latitude, relay.location.longitude],
|
||||
{
|
||||
radius: ((relay.stats.bytesProxied * 100) / $scope.totals.bytesProxied) * 10000,
|
||||
color: "FF0000",
|
||||
fillColor: "#FF0000",
|
||||
fillOpacity: 0.35,
|
||||
}).addTo($scope.map);
|
||||
}
|
||||
|
||||
function constructURI(url) {
|
||||
|
||||
@@ -1,20 +1,17 @@
|
||||
// Copyright (C) 2015 Audrius Butkevicius and Contributors (see the CONTRIBUTORS file).
|
||||
|
||||
//go:generate go run ../../script/genassets.go gui >auto/gui.go
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math/rand"
|
||||
"mime"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -29,6 +26,8 @@ import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"github.com/syncthing/syncthing/cmd/strelaypoolsrv/auto"
|
||||
"github.com/syncthing/syncthing/lib/assets"
|
||||
"github.com/syncthing/syncthing/lib/rand"
|
||||
"github.com/syncthing/syncthing/lib/relay/client"
|
||||
"github.com/syncthing/syncthing/lib/sync"
|
||||
"github.com/syncthing/syncthing/lib/tlsutil"
|
||||
@@ -92,33 +91,35 @@ type result struct {
|
||||
}
|
||||
|
||||
var (
|
||||
testCert tls.Certificate
|
||||
knownRelaysFile = filepath.Join(os.TempDir(), "strelaypoolsrv_known_relays")
|
||||
listen = ":80"
|
||||
dir string
|
||||
evictionTime = time.Hour
|
||||
debug bool
|
||||
getLRUSize = 10 << 10
|
||||
getLimitBurst = 10
|
||||
getLimitAvg = 2
|
||||
postLRUSize = 1 << 10
|
||||
postLimitBurst = 2
|
||||
postLimitAvg = 2
|
||||
getLimit time.Duration
|
||||
postLimit time.Duration
|
||||
permRelaysFile string
|
||||
ipHeader string
|
||||
geoipPath string
|
||||
proto string
|
||||
statsRefresh = time.Minute / 2
|
||||
testCert tls.Certificate
|
||||
knownRelaysFile = filepath.Join(os.TempDir(), "strelaypoolsrv_known_relays")
|
||||
listen = ":80"
|
||||
dir string
|
||||
evictionTime = time.Hour
|
||||
debug bool
|
||||
getLRUSize = 10 << 10
|
||||
getLimitBurst = 10
|
||||
getLimitAvg = 2
|
||||
postLRUSize = 1 << 10
|
||||
postLimitBurst = 2
|
||||
postLimitAvg = 2
|
||||
getLimit time.Duration
|
||||
postLimit time.Duration
|
||||
permRelaysFile string
|
||||
ipHeader string
|
||||
geoipPath string
|
||||
proto string
|
||||
statsRefresh = time.Minute / 2
|
||||
requestQueueLen = 10
|
||||
requestProcessors = 1
|
||||
|
||||
getMut = sync.NewRWMutex()
|
||||
getMut = sync.NewMutex()
|
||||
getLRUCache *lru.Cache
|
||||
|
||||
postMut = sync.NewRWMutex()
|
||||
postMut = sync.NewMutex()
|
||||
postLRUCache *lru.Cache
|
||||
|
||||
requests = make(chan request, 10)
|
||||
requests chan request
|
||||
|
||||
mut = sync.NewRWMutex()
|
||||
knownRelays = make([]*relay, 0)
|
||||
@@ -131,6 +132,9 @@ const (
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.SetOutput(os.Stdout)
|
||||
log.SetFlags(log.Lshortfile)
|
||||
|
||||
flag.StringVar(&listen, "listen", listen, "Listen address")
|
||||
flag.StringVar(&dir, "keys", dir, "Directory where http-cert.pem and http-key.pem is stored for TLS listening")
|
||||
flag.BoolVar(&debug, "debug", debug, "Enable debug output")
|
||||
@@ -146,9 +150,13 @@ func main() {
|
||||
flag.StringVar(&geoipPath, "geoip", "GeoLite2-City.mmdb", "Path to GeoLite2-City database")
|
||||
flag.StringVar(&proto, "protocol", "tcp", "Protocol used for listening. 'tcp' for IPv4 and IPv6, 'tcp4' for IPv4, 'tcp6' for IPv6")
|
||||
flag.DurationVar(&statsRefresh, "stats-refresh", statsRefresh, "Interval at which to refresh relay stats")
|
||||
flag.IntVar(&requestQueueLen, "request-queue", requestQueueLen, "Queue length for incoming test requests")
|
||||
flag.IntVar(&requestProcessors, "request-processors", requestProcessors, "Number of request processor routines")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
requests = make(chan request, requestQueueLen)
|
||||
|
||||
getLimit = 10 * time.Second / time.Duration(getLimitAvg)
|
||||
postLimit = time.Minute / time.Duration(postLimitAvg)
|
||||
|
||||
@@ -164,7 +172,9 @@ func main() {
|
||||
|
||||
testCert = createTestCertificate()
|
||||
|
||||
go requestProcessor()
|
||||
for i := 0; i < requestProcessors; i++ {
|
||||
go requestProcessor()
|
||||
}
|
||||
|
||||
// Load relays from cache in the background.
|
||||
// Load them in a serial fashion to make sure any genuine requests
|
||||
@@ -253,88 +263,27 @@ func handleMetrics(w http.ResponseWriter, r *http.Request) {
|
||||
func handleAssets(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Cache-Control", "no-cache, must-revalidate")
|
||||
|
||||
assets := auto.Assets()
|
||||
path := r.URL.Path[1:]
|
||||
if path == "" {
|
||||
path = "index.html"
|
||||
}
|
||||
|
||||
bs, ok := assets[path]
|
||||
as, ok := auto.Assets()[path]
|
||||
if !ok {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
etag := fmt.Sprintf("%d", auto.Generated)
|
||||
modified := time.Unix(auto.Generated, 0).UTC()
|
||||
|
||||
w.Header().Set("Last-Modified", modified.Format(http.TimeFormat))
|
||||
w.Header().Set("Etag", etag)
|
||||
|
||||
mtype := mimeTypeForFile(path)
|
||||
if len(mtype) != 0 {
|
||||
w.Header().Set("Content-Type", mtype)
|
||||
}
|
||||
|
||||
if t, err := time.Parse(http.TimeFormat, r.Header.Get("If-Modified-Since")); err == nil && modified.Add(time.Second).After(t) {
|
||||
w.WriteHeader(http.StatusNotModified)
|
||||
return
|
||||
}
|
||||
|
||||
if match := r.Header.Get("If-None-Match"); match != "" {
|
||||
if strings.Contains(match, etag) {
|
||||
w.WriteHeader(http.StatusNotModified)
|
||||
return
|
||||
}
|
||||
}
|
||||
if strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
|
||||
w.Header().Set("Content-Encoding", "gzip")
|
||||
} else {
|
||||
// ungzip if browser not send gzip accepted header
|
||||
var gr *gzip.Reader
|
||||
gr, _ = gzip.NewReader(bytes.NewReader(bs))
|
||||
bs, _ = ioutil.ReadAll(gr)
|
||||
gr.Close()
|
||||
}
|
||||
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(bs)))
|
||||
|
||||
w.Write(bs)
|
||||
}
|
||||
|
||||
func mimeTypeForFile(file string) string {
|
||||
// We use a built in table of the common types since the system
|
||||
// TypeByExtension might be unreliable. But if we don't know, we delegate
|
||||
// to the system.
|
||||
ext := filepath.Ext(file)
|
||||
switch ext {
|
||||
case ".htm", ".html":
|
||||
return "text/html"
|
||||
case ".css":
|
||||
return "text/css"
|
||||
case ".js":
|
||||
return "application/javascript"
|
||||
case ".json":
|
||||
return "application/json"
|
||||
case ".png":
|
||||
return "image/png"
|
||||
case ".ttf":
|
||||
return "application/x-font-ttf"
|
||||
case ".woff":
|
||||
return "application/x-font-woff"
|
||||
case ".svg":
|
||||
return "image/svg+xml"
|
||||
default:
|
||||
return mime.TypeByExtension(ext)
|
||||
}
|
||||
assets.Serve(w, r, as)
|
||||
}
|
||||
|
||||
func handleRequest(w http.ResponseWriter, r *http.Request) {
|
||||
timer := prometheus.NewTimer(apiRequestsSeconds.WithLabelValues(r.Method))
|
||||
|
||||
lw := NewLoggingResponseWriter(w)
|
||||
|
||||
w = NewLoggingResponseWriter(w)
|
||||
defer func() {
|
||||
timer.ObserveDuration()
|
||||
lw := w.(*loggingResponseWriter)
|
||||
apiRequestsTotal.WithLabelValues(r.Method, strconv.Itoa(lw.statusCode)).Inc()
|
||||
}()
|
||||
|
||||
@@ -363,19 +312,24 @@ func handleRequest(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func handleGetRequest(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
func handleGetRequest(rw http.ResponseWriter, r *http.Request) {
|
||||
rw.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
mut.RLock()
|
||||
relays := append(permanentRelays, knownRelays...)
|
||||
mut.RUnlock()
|
||||
|
||||
// Shuffle
|
||||
for i := range relays {
|
||||
j := rand.Intn(i + 1)
|
||||
relays[i], relays[j] = relays[j], relays[i]
|
||||
rand.Shuffle(relays)
|
||||
|
||||
w := io.Writer(rw)
|
||||
if strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
|
||||
rw.Header().Set("Content-Encoding", "gzip")
|
||||
gw := gzip.NewWriter(rw)
|
||||
defer gw.Close()
|
||||
w = gw
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(map[string][]*relay{
|
||||
_ = json.NewEncoder(w).Encode(map[string][]*relay{
|
||||
"relays": relays,
|
||||
})
|
||||
}
|
||||
@@ -389,7 +343,7 @@ func handlePostRequest(w http.ResponseWriter, r *http.Request) {
|
||||
if debug {
|
||||
log.Println("Failed to parse payload")
|
||||
}
|
||||
http.Error(w, err.Error(), 500)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -398,7 +352,7 @@ func handlePostRequest(w http.ResponseWriter, r *http.Request) {
|
||||
if debug {
|
||||
log.Println("Failed to parse URI", newRelay.URL)
|
||||
}
|
||||
http.Error(w, err.Error(), 500)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -407,7 +361,7 @@ func handlePostRequest(w http.ResponseWriter, r *http.Request) {
|
||||
if debug {
|
||||
log.Println("Failed to split URI", newRelay.URL)
|
||||
}
|
||||
http.Error(w, err.Error(), 500)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -483,11 +437,11 @@ func handleRelayTest(request request) {
|
||||
if debug {
|
||||
log.Println("Request for", request.relay)
|
||||
}
|
||||
if !client.TestRelay(request.relay.uri, []tls.Certificate{testCert}, time.Second, 2*time.Second, 3) {
|
||||
if err := client.TestRelay(context.TODO(), request.relay.uri, []tls.Certificate{testCert}, time.Second, 2*time.Second, 3); err != nil {
|
||||
if debug {
|
||||
log.Println("Test for relay", request.relay, "failed")
|
||||
log.Println("Test for relay", request.relay, "failed:", err)
|
||||
}
|
||||
request.result <- result{fmt.Errorf("connection test failed"), 0}
|
||||
request.result <- result{err, 0}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -565,26 +519,21 @@ func evict(relay *relay) func() {
|
||||
}
|
||||
}
|
||||
|
||||
func limit(addr string, cache *lru.Cache, lock sync.RWMutex, intv time.Duration, burst int) bool {
|
||||
func limit(addr string, cache *lru.Cache, lock sync.Mutex, intv time.Duration, burst int) bool {
|
||||
if host, _, err := net.SplitHostPort(addr); err == nil {
|
||||
addr = host
|
||||
}
|
||||
|
||||
lock.RLock()
|
||||
bkt, ok := cache.Get(addr)
|
||||
lock.RUnlock()
|
||||
if ok {
|
||||
bkt := bkt.(*rate.Limiter)
|
||||
if !bkt.Allow() {
|
||||
// Rate limit
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
lock.Lock()
|
||||
cache.Add(addr, rate.NewLimiter(rate.Every(intv), burst))
|
||||
lock.Unlock()
|
||||
lock.Lock()
|
||||
v, _ := cache.Get(addr)
|
||||
bkt, ok := v.(*rate.Limiter)
|
||||
if !ok {
|
||||
bkt = rate.NewLimiter(rate.Every(intv), burst)
|
||||
cache.Add(addr, bkt)
|
||||
}
|
||||
return false
|
||||
lock.Unlock()
|
||||
|
||||
return !bkt.Allow()
|
||||
}
|
||||
|
||||
func loadRelays(file string) []*relay {
|
||||
@@ -636,7 +585,7 @@ func createTestCertificate() tls.Certificate {
|
||||
}
|
||||
|
||||
certFile, keyFile := filepath.Join(tmpDir, "cert.pem"), filepath.Join(tmpDir, "key.pem")
|
||||
cert, err := tlsutil.NewCertificate(certFile, keyFile, "relaypoolsrv")
|
||||
cert, err := tlsutil.NewCertificate(certFile, keyFile, "relaypoolsrv", 20*365)
|
||||
if err != nil {
|
||||
log.Fatalln("Failed to create test X509 key pair:", err)
|
||||
}
|
||||
|
||||
9
cmd/strelaysrv/etc/firewall-ufw/strelaysrv
Normal file
@@ -0,0 +1,9 @@
|
||||
[strelaysrv]
|
||||
title=Syncthing relay server
|
||||
description=Proxies traffic of syncthing client behind firewalls
|
||||
ports=22067/tcp
|
||||
|
||||
[strelaysrv-metrics]
|
||||
title=Syncthing relay metrics
|
||||
description=Provides metrics about the syncthing relay server
|
||||
ports=22070/tcp
|
||||
5
cmd/strelaysrv/etc/linux-systemd/default
Normal file
@@ -0,0 +1,5 @@
|
||||
# Default settings for syncthing-relaysrv (strelaysrv).
|
||||
NAT=true
|
||||
|
||||
## Add Options here:
|
||||
RELAYSRV_OPTS=
|
||||
@@ -1,17 +1,25 @@
|
||||
[Unit]
|
||||
Description=Syncthing relay server
|
||||
Description=Syncthing Relay Server
|
||||
After=network.target
|
||||
Documentation=man:strelaysrv(1)
|
||||
|
||||
[Service]
|
||||
User=strelaysrv
|
||||
Group=strelaysrv
|
||||
ExecStart=/usr/bin/strelaysrv
|
||||
WorkingDirectory=/var/lib/strelaysrv
|
||||
WorkingDirectory=/var/lib/syncthing-relaysrv
|
||||
EnvironmentFile=/etc/default/syncthing-relaysrv
|
||||
ExecStart=/usr/bin/strelaysrv -nat=${NAT} $RELAYSRV_OPTS
|
||||
|
||||
PrivateTmp=true
|
||||
ProtectSystem=full
|
||||
ProtectHome=true
|
||||
# Hardening
|
||||
User=syncthing-relaysrv
|
||||
Group=syncthing
|
||||
ProtectSystem=strict
|
||||
ReadWritePaths=/var/lib/syncthing-relaysrv
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
PrivateDevices=true
|
||||
ProtectHome=true
|
||||
SystemCallArchitectures=native
|
||||
MemoryDenyWriteExecute=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Alias=syncthing-relaysrv.service
|
||||
|
||||
@@ -149,7 +149,15 @@ func protocolConnectionHandler(tcpConn net.Conn, config *tls.Config) {
|
||||
protocol.WriteMessage(conn, protocol.ResponseSuccess)
|
||||
|
||||
case protocol.ConnectRequest:
|
||||
requestedPeer := syncthingprotocol.DeviceIDFromBytes(msg.ID)
|
||||
requestedPeer, err := syncthingprotocol.DeviceIDFromBytes(msg.ID)
|
||||
if err != nil {
|
||||
if debug {
|
||||
log.Println(id, "is looking for an invalid peer ID")
|
||||
}
|
||||
protocol.WriteMessage(conn, protocol.ResponseNotFound)
|
||||
conn.Close()
|
||||
continue
|
||||
}
|
||||
outboxesMut.RLock()
|
||||
peerOutbox, ok := outboxes[requestedPeer]
|
||||
outboxesMut.RUnlock()
|
||||
|
||||
@@ -14,12 +14,13 @@ import (
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/build"
|
||||
"github.com/syncthing/syncthing/lib/events"
|
||||
"github.com/syncthing/syncthing/lib/osutil"
|
||||
"github.com/syncthing/syncthing/lib/relay/protocol"
|
||||
"github.com/syncthing/syncthing/lib/tlsutil"
|
||||
@@ -33,24 +34,6 @@ import (
|
||||
syncthingprotocol "github.com/syncthing/syncthing/lib/protocol"
|
||||
)
|
||||
|
||||
var (
|
||||
Version string
|
||||
BuildStamp string
|
||||
BuildUser string
|
||||
BuildHost string
|
||||
|
||||
BuildDate time.Time
|
||||
LongVersion string
|
||||
)
|
||||
|
||||
func init() {
|
||||
stamp, _ := strconv.Atoi(BuildStamp)
|
||||
BuildDate = time.Unix(int64(stamp), 0)
|
||||
|
||||
date := BuildDate.UTC().Format("2006-01-02 15:04:05 MST")
|
||||
LongVersion = fmt.Sprintf(`strelaysrv %s (%s %s-%s) %s@%s %s`, Version, runtime.Version(), runtime.GOOS, runtime.GOARCH, BuildUser, BuildHost, date)
|
||||
}
|
||||
|
||||
var (
|
||||
listen string
|
||||
debug bool
|
||||
@@ -116,8 +99,15 @@ func main() {
|
||||
flag.IntVar(&natTimeout, "nat-timeout", 10, "NAT discovery timeout in seconds")
|
||||
flag.BoolVar(&pprofEnabled, "pprof", false, "Enable the built in profiling on the status server")
|
||||
flag.IntVar(&networkBufferSize, "network-buffer", 2048, "Network buffer size (two of these per proxied connection)")
|
||||
showVersion := flag.Bool("version", false, "Show version")
|
||||
flag.Parse()
|
||||
|
||||
longVer := build.LongVersionFor("strelaysrv")
|
||||
if *showVersion {
|
||||
fmt.Println(longVer)
|
||||
return
|
||||
}
|
||||
|
||||
if extAddress == "" {
|
||||
extAddress = listen
|
||||
}
|
||||
@@ -146,7 +136,7 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
log.Println(LongVersion)
|
||||
log.Println(longVer)
|
||||
|
||||
maxDescriptors, err := osutil.MaximizeOpenFileLimit()
|
||||
if maxDescriptors > 0 {
|
||||
@@ -166,7 +156,7 @@ func main() {
|
||||
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||
if err != nil {
|
||||
log.Println("Failed to load keypair. Generating one, this might take a while...")
|
||||
cert, err = tlsutil.NewCertificate(certFile, keyFile, "strelaysrv")
|
||||
cert, err = tlsutil.NewCertificate(certFile, keyFile, "strelaysrv", 20*365)
|
||||
if err != nil {
|
||||
log.Fatalln("Failed to generate X509 key pair:", err)
|
||||
}
|
||||
@@ -194,7 +184,7 @@ func main() {
|
||||
log.Println("ID:", id)
|
||||
}
|
||||
|
||||
wrapper := config.Wrap("config", config.New(id))
|
||||
wrapper := config.Wrap("config", config.New(id), events.NoopLogger)
|
||||
wrapper.SetOptions(config.OptionsConfiguration{
|
||||
NATLeaseM: natLease,
|
||||
NATRenewalM: natRenewal,
|
||||
|
||||
@@ -7,10 +7,15 @@ import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
httpStatusEnhanceYourCalm = 429
|
||||
)
|
||||
|
||||
func poolHandler(pool string, uri *url.URL, mapping mapping) {
|
||||
if debug {
|
||||
log.Println("Joining", pool)
|
||||
@@ -28,38 +33,62 @@ func poolHandler(pool string, uri *url.URL, mapping mapping) {
|
||||
|
||||
resp, err := httpClient.Post(pool, "application/json", &b)
|
||||
if err != nil {
|
||||
log.Println("Error joining pool", pool, err)
|
||||
} else if resp.StatusCode == 500 {
|
||||
bs, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Println("Failed to join", pool, "due to an internal server error. Could not read response:", err)
|
||||
} else {
|
||||
log.Println("Failed to join", pool, "due to an internal server error:", string(bs))
|
||||
}
|
||||
resp.Body.Close()
|
||||
} else if resp.StatusCode == 429 {
|
||||
log.Println(pool, "under load, will retry in a minute")
|
||||
log.Printf("Error joining pool %v: HTTP request: %v", pool, err)
|
||||
time.Sleep(time.Minute)
|
||||
continue
|
||||
} else if resp.StatusCode == 401 {
|
||||
log.Println(pool, "failed to join due to IP address not matching external address. Aborting")
|
||||
return
|
||||
} else if resp.StatusCode == 200 {
|
||||
}
|
||||
|
||||
bs, err := ioutil.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
if err != nil {
|
||||
log.Printf("Error joining pool %v: reading response: %v", pool, err)
|
||||
time.Sleep(time.Minute)
|
||||
continue
|
||||
}
|
||||
|
||||
switch resp.StatusCode {
|
||||
case http.StatusOK:
|
||||
var x struct {
|
||||
EvictionIn time.Duration `json:"evictionIn"`
|
||||
}
|
||||
err := json.NewDecoder(resp.Body).Decode(&x)
|
||||
if err == nil {
|
||||
if err := json.NewDecoder(resp.Body).Decode(&x); err == nil {
|
||||
rejoin := x.EvictionIn - (x.EvictionIn / 5)
|
||||
log.Println("Joined", pool, "rejoining in", rejoin)
|
||||
log.Printf("Joined pool %s, rejoining in %v", pool, rejoin)
|
||||
time.Sleep(rejoin)
|
||||
continue
|
||||
} else {
|
||||
log.Println("Failed to deserialize response", err)
|
||||
log.Printf("Joined pool %s, failed to deserialize response: %v", pool, err)
|
||||
}
|
||||
} else {
|
||||
log.Println(pool, "unknown response type from server", resp.StatusCode)
|
||||
|
||||
case http.StatusInternalServerError:
|
||||
log.Printf("Failed to join %v: server error", pool)
|
||||
log.Printf("Response data: %s", bs)
|
||||
time.Sleep(time.Minute)
|
||||
continue
|
||||
|
||||
case http.StatusBadRequest:
|
||||
log.Printf("Failed to join %v: request or check error", pool)
|
||||
log.Printf("Response data: %s", bs)
|
||||
time.Sleep(time.Minute)
|
||||
continue
|
||||
|
||||
case httpStatusEnhanceYourCalm:
|
||||
log.Printf("Failed to join %v: under load (rate limiting)", pool)
|
||||
time.Sleep(time.Minute)
|
||||
continue
|
||||
|
||||
case http.StatusUnauthorized:
|
||||
log.Printf("Failed to join %v: IP address not matching external address", pool)
|
||||
log.Println("Aborting")
|
||||
return
|
||||
|
||||
default:
|
||||
log.Printf("Failed to join %v: unexpected status code from server: %d", pool, resp.StatusCode)
|
||||
log.Printf("Response data: %s", bs)
|
||||
time.Sleep(time.Minute)
|
||||
continue
|
||||
}
|
||||
|
||||
time.Sleep(time.Hour)
|
||||
}
|
||||
}
|
||||
|
||||
4
cmd/strelaysrv/scripts/preinst
Normal file
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
addgroup --system syncthing
|
||||
adduser --system --home /var/lib/syncthing-relaysrv --ingroup syncthing syncthing-relaysrv
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
"runtime"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/build"
|
||||
)
|
||||
|
||||
var rc *rateCalculator
|
||||
@@ -40,10 +42,10 @@ func getStatus(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
sessionMut.Lock()
|
||||
// This can potentially be double the number of pending sessions, as each session has two keys, one for each side.
|
||||
status["version"] = Version
|
||||
status["buildHost"] = BuildHost
|
||||
status["buildUser"] = BuildUser
|
||||
status["buildDate"] = BuildDate
|
||||
status["version"] = build.Version
|
||||
status["buildHost"] = build.Host
|
||||
status["buildUser"] = build.User
|
||||
status["buildDate"] = build.Date
|
||||
status["startTime"] = rc.startTime
|
||||
status["uptimeSeconds"] = time.Since(rc.startTime) / time.Second
|
||||
status["numPendingSessionKeys"] = len(pendingSessions)
|
||||
@@ -86,9 +88,9 @@ func getStatus(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
type rateCalculator struct {
|
||||
counter *int64 // atomic, must remain 64-bit aligned
|
||||
rates []int64
|
||||
prev int64
|
||||
counter *int64
|
||||
startTime time.Time
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"flag"
|
||||
"log"
|
||||
@@ -19,6 +20,8 @@ import (
|
||||
)
|
||||
|
||||
func main() {
|
||||
ctx := context.Background()
|
||||
|
||||
log.SetOutput(os.Stdout)
|
||||
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
||||
|
||||
@@ -76,7 +79,7 @@ func main() {
|
||||
}()
|
||||
|
||||
for {
|
||||
conn, err := client.JoinSession(<-recv)
|
||||
conn, err := client.JoinSession(ctx, <-recv)
|
||||
if err != nil {
|
||||
log.Fatalln("Failed to join", err)
|
||||
}
|
||||
@@ -90,13 +93,13 @@ func main() {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
invite, err := client.GetInvitationFromRelay(uri, id, []tls.Certificate{cert}, 10*time.Second)
|
||||
invite, err := client.GetInvitationFromRelay(ctx, uri, id, []tls.Certificate{cert}, 10*time.Second)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
log.Println("Received invitation", invite)
|
||||
conn, err := client.JoinSession(invite)
|
||||
conn, err := client.JoinSession(ctx, invite)
|
||||
if err != nil {
|
||||
log.Fatalln("Failed to join", err)
|
||||
}
|
||||
@@ -104,10 +107,10 @@ func main() {
|
||||
connectToStdio(stdin, conn)
|
||||
log.Println("Finished", conn.RemoteAddr(), conn.LocalAddr())
|
||||
} else if test {
|
||||
if client.TestRelay(uri, []tls.Certificate{cert}, time.Second, 2*time.Second, 4) {
|
||||
if err := client.TestRelay(ctx, uri, []tls.Certificate{cert}, time.Second, 2*time.Second, 4); err == nil {
|
||||
log.Println("OK")
|
||||
} else {
|
||||
log.Println("FAIL")
|
||||
log.Println("FAIL:", err)
|
||||
}
|
||||
} else {
|
||||
log.Fatal("Requires either join or connect")
|
||||
|
||||
57
cmd/stupgrades/main.go
Normal file
@@ -0,0 +1,57 @@
|
||||
// Copyright (C) 2019 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 (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"os"
|
||||
"sort"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/upgrade"
|
||||
)
|
||||
|
||||
const defaultURL = "https://api.github.com/repos/syncthing/syncthing/releases?per_page=25"
|
||||
|
||||
func main() {
|
||||
url := flag.String("u", defaultURL, "GitHub releases url")
|
||||
flag.Parse()
|
||||
|
||||
rels := upgrade.FetchLatestReleases(*url, "")
|
||||
if rels == nil {
|
||||
// An error was already logged
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
sort.Sort(upgrade.SortByRelease(rels))
|
||||
rels = filterForLatest(rels)
|
||||
|
||||
if err := json.NewEncoder(os.Stdout).Encode(rels); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// filterForLatest returns the latest stable and prerelease only. If the
|
||||
// stable version is newer (comes first in the list) there is no need to go
|
||||
// looking for a prerelease at all.
|
||||
func filterForLatest(rels []upgrade.Release) []upgrade.Release {
|
||||
var filtered []upgrade.Release
|
||||
var havePre bool
|
||||
for _, rel := range rels {
|
||||
if !rel.Prerelease {
|
||||
// We found a stable version, we're good now.
|
||||
filtered = append(filtered, rel)
|
||||
break
|
||||
}
|
||||
if rel.Prerelease && !havePre {
|
||||
// We remember the first prerelease we find.
|
||||
filtered = append(filtered, rel)
|
||||
havePre = true
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"math/big"
|
||||
@@ -191,7 +192,7 @@ func pemBlockForKey(priv interface{}) (*pem.Block, error) {
|
||||
}
|
||||
return &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown key type")
|
||||
return nil, errors.New("unknown key type")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
// Copyright (C) 2015 The Syncthing Authors.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/events"
|
||||
)
|
||||
|
||||
// The auditService subscribes to events and writes these in JSON format, one
|
||||
// event per line, to the specified writer.
|
||||
type auditService struct {
|
||||
w io.Writer // audit destination
|
||||
stop chan struct{} // signals time to stop
|
||||
started chan struct{} // signals startup complete
|
||||
stopped chan struct{} // signals stop complete
|
||||
}
|
||||
|
||||
func newAuditService(w io.Writer) *auditService {
|
||||
return &auditService{
|
||||
w: w,
|
||||
stop: make(chan struct{}),
|
||||
started: make(chan struct{}),
|
||||
stopped: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// Serve runs the audit service.
|
||||
func (s *auditService) Serve() {
|
||||
defer close(s.stopped)
|
||||
sub := events.Default.Subscribe(events.AllEvents)
|
||||
defer events.Default.Unsubscribe(sub)
|
||||
enc := json.NewEncoder(s.w)
|
||||
|
||||
// We're ready to start processing events.
|
||||
close(s.started)
|
||||
|
||||
for {
|
||||
select {
|
||||
case ev := <-sub.C():
|
||||
enc.Encode(ev)
|
||||
case <-s.stop:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stop stops the audit service.
|
||||
func (s *auditService) Stop() {
|
||||
close(s.stop)
|
||||
}
|
||||
|
||||
// WaitForStart returns once the audit service is ready to receive events, or
|
||||
// immediately if it's already running.
|
||||
func (s *auditService) WaitForStart() {
|
||||
<-s.started
|
||||
}
|
||||
|
||||
// WaitForStop returns once the audit service has stopped.
|
||||
// (Needed by the tests.)
|
||||
func (s *auditService) WaitForStop() {
|
||||
<-s.stopped
|
||||
}
|
||||
@@ -22,11 +22,15 @@ func init() {
|
||||
panic("Couldn't find block profiler")
|
||||
}
|
||||
l.Debugln("Starting block profiling")
|
||||
go saveBlockingProfiles(profiler)
|
||||
go func() {
|
||||
err := saveBlockingProfiles(profiler) // Only returns on error
|
||||
l.Warnln("Block profiler failed:", err)
|
||||
panic("Block profiler failed")
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func saveBlockingProfiles(profiler *pprof.Profile) {
|
||||
func saveBlockingProfiles(profiler *pprof.Profile) error {
|
||||
runtime.SetBlockProfileRate(1)
|
||||
|
||||
t0 := time.Now()
|
||||
@@ -35,16 +39,16 @@ func saveBlockingProfiles(profiler *pprof.Profile) {
|
||||
|
||||
fd, err := os.Create(fmt.Sprintf("block-%05d-%07d.pprof", syscall.Getpid(), startms))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return err
|
||||
}
|
||||
err = profiler.WriteTo(fd, 0)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return err
|
||||
}
|
||||
err = fd.Close()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
// Copyright (C) 2017 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 (
|
||||
"math"
|
||||
"time"
|
||||
|
||||
metrics "github.com/rcrowley/go-metrics"
|
||||
)
|
||||
|
||||
const cpuTickRate = 5 * time.Second
|
||||
|
||||
type cpuService struct {
|
||||
avg metrics.EWMA
|
||||
stop chan struct{}
|
||||
}
|
||||
|
||||
func newCPUService() *cpuService {
|
||||
return &cpuService{
|
||||
// 10 second average. Magic alpha value comes from looking at EWMA package
|
||||
// definitions of EWMA1, EWMA5. The tick rate *must* be five seconds (hard
|
||||
// coded in the EWMA package).
|
||||
avg: metrics.NewEWMA(1 - math.Exp(-float64(cpuTickRate)/float64(time.Second)/10.0)),
|
||||
stop: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *cpuService) Serve() {
|
||||
// Initialize prevUsage to an actual value returned by cpuUsage
|
||||
// instead of zero, because at least Windows returns a huge negative
|
||||
// number here that then slowly increments...
|
||||
prevUsage := cpuUsage()
|
||||
ticker := time.NewTicker(cpuTickRate)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
curUsage := cpuUsage()
|
||||
s.avg.Update(int64((curUsage - prevUsage) / time.Millisecond))
|
||||
prevUsage = curUsage
|
||||
s.avg.Tick()
|
||||
case <-s.stop:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *cpuService) Stop() {
|
||||
close(s.stop)
|
||||
}
|
||||
|
||||
func (s *cpuService) Rate() float64 {
|
||||
return s.avg.Rate()
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
// Copyright (C) 2014 The Syncthing Authors.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
//+build solaris
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
type id_t int32
|
||||
type ulong_t uint32
|
||||
|
||||
type timestruc_t struct {
|
||||
Tv_sec int64
|
||||
Tv_nsec int64
|
||||
}
|
||||
|
||||
func (tv timestruc_t) Nano() int64 {
|
||||
return tv.Tv_sec*1e9 + tv.Tv_nsec
|
||||
}
|
||||
|
||||
type prusage_t struct {
|
||||
Pr_lwpid id_t /* lwp id. 0: process or defunct */
|
||||
Pr_count int32 /* number of contributing lwps */
|
||||
Pr_tstamp timestruc_t /* real time stamp, time of read() */
|
||||
Pr_create timestruc_t /* process/lwp creation time stamp */
|
||||
Pr_term timestruc_t /* process/lwp termination time stamp */
|
||||
Pr_rtime timestruc_t /* total lwp real (elapsed) time */
|
||||
Pr_utime timestruc_t /* user level CPU time */
|
||||
Pr_stime timestruc_t /* system call CPU time */
|
||||
Pr_ttime timestruc_t /* other system trap CPU time */
|
||||
Pr_tftime timestruc_t /* text page fault sleep time */
|
||||
Pr_dftime timestruc_t /* data page fault sleep time */
|
||||
Pr_kftime timestruc_t /* kernel page fault sleep time */
|
||||
Pr_ltime timestruc_t /* user lock wait sleep time */
|
||||
Pr_slptime timestruc_t /* all other sleep time */
|
||||
Pr_wtime timestruc_t /* wait-cpu (latency) time */
|
||||
Pr_stoptime timestruc_t /* stopped time */
|
||||
Pr_minf ulong_t /* minor page faults */
|
||||
Pr_majf ulong_t /* major page faults */
|
||||
Pr_nswap ulong_t /* swaps */
|
||||
Pr_inblk ulong_t /* input blocks */
|
||||
Pr_oublk ulong_t /* output blocks */
|
||||
Pr_msnd ulong_t /* messages sent */
|
||||
Pr_mrcv ulong_t /* messages received */
|
||||
Pr_sigs ulong_t /* signals received */
|
||||
Pr_vctx ulong_t /* voluntary context switches */
|
||||
Pr_ictx ulong_t /* involuntary context switches */
|
||||
Pr_sysc ulong_t /* system calls */
|
||||
Pr_ioch ulong_t /* chars read and written */
|
||||
|
||||
}
|
||||
|
||||
var procFile = fmt.Sprintf("/proc/%d/usage", os.Getpid())
|
||||
|
||||
func cpuUsage() time.Duration {
|
||||
fd, err := os.Open(procFile)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
var rusage prusage_t
|
||||
err = binary.Read(fd, binary.LittleEndian, rusage)
|
||||
fd.Close()
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return time.Duration(rusage.Pr_utime.Nano() + rusage.Pr_stime.Nano())
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
// Copyright (C) 2014 The Syncthing Authors.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
//+build windows
|
||||
|
||||
package main
|
||||
|
||||
import "syscall"
|
||||
import "time"
|
||||
|
||||
func cpuUsage() time.Duration {
|
||||
handle, err := syscall.GetCurrentProcess()
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
defer syscall.CloseHandle(handle)
|
||||
|
||||
var ctime, etime, ktime, utime syscall.Filetime
|
||||
if err := syscall.GetProcessTimes(handle, &ctime, &etime, &ktime, &utime); err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return time.Duration(ktime.Nanoseconds() + utime.Nanoseconds())
|
||||
}
|
||||
152
cmd/syncthing/crash_reporting.go
Normal file
@@ -0,0 +1,152 @@
|
||||
// Copyright (C) 2019 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 (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/sha256"
|
||||
)
|
||||
|
||||
const (
|
||||
headRequestTimeout = 10 * time.Second
|
||||
putRequestTimeout = time.Minute
|
||||
)
|
||||
|
||||
// uploadPanicLogs attempts to upload all the panic logs in the named
|
||||
// directory to the crash reporting server as urlBase. Uploads are attempted
|
||||
// with the newest log first.
|
||||
//
|
||||
// This can can block for a long time. The context can set a final deadline
|
||||
// for this.
|
||||
func uploadPanicLogs(ctx context.Context, urlBase, dir string) {
|
||||
files, err := filepath.Glob(filepath.Join(dir, "panic-*.log"))
|
||||
if err != nil {
|
||||
l.Warnln("Failed to list panic logs:", err)
|
||||
return
|
||||
}
|
||||
|
||||
sort.Sort(sort.Reverse(sort.StringSlice(files)))
|
||||
for _, file := range files {
|
||||
if strings.Contains(file, ".reported.") {
|
||||
// We've already sent this file. It'll be cleaned out at some
|
||||
// point.
|
||||
continue
|
||||
}
|
||||
|
||||
if err := uploadPanicLog(ctx, urlBase, file); err != nil {
|
||||
l.Warnln("Reporting crash:", err)
|
||||
} else {
|
||||
// Rename the log so we don't have to try to report it again. This
|
||||
// succeeds, or it does not. There is no point complaining about it.
|
||||
_ = os.Rename(file, strings.Replace(file, ".log", ".reported.log", 1))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// uploadPanicLog attempts to upload the named panic log to the crash
|
||||
// reporting server at urlBase. The panic ID is constructed as the sha256 of
|
||||
// the log contents. A HEAD request is made to see if the log has already
|
||||
// been reported. If not, a PUT is made with the log contents.
|
||||
func uploadPanicLog(ctx context.Context, urlBase, file string) error {
|
||||
data, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove log lines, for privacy.
|
||||
data = filterLogLines(data)
|
||||
|
||||
hash := fmt.Sprintf("%x", sha256.Sum256(data))
|
||||
l.Infof("Reporting crash found in %s (report ID %s) ...\n", filepath.Base(file), hash[:8])
|
||||
|
||||
url := fmt.Sprintf("%s/%s", urlBase, hash)
|
||||
headReq, err := http.NewRequest(http.MethodHead, url, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set a reasonable timeout on the HEAD request
|
||||
headCtx, headCancel := context.WithTimeout(ctx, headRequestTimeout)
|
||||
defer headCancel()
|
||||
headReq = headReq.WithContext(headCtx)
|
||||
|
||||
resp, err := http.DefaultClient.Do(headReq)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
// It's known, we're done
|
||||
return nil
|
||||
}
|
||||
|
||||
putReq, err := http.NewRequest(http.MethodPut, url, bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set a reasonable timeout on the PUT request
|
||||
putCtx, putCancel := context.WithTimeout(ctx, putRequestTimeout)
|
||||
defer putCancel()
|
||||
putReq = putReq.WithContext(putCtx)
|
||||
|
||||
resp, err = http.DefaultClient.Do(putReq)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("upload: %s", resp.Status)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// filterLogLines returns the data without any log lines between the first
|
||||
// line and the panic trace. This is done in-place: the original data slice
|
||||
// is destroyed.
|
||||
func filterLogLines(data []byte) []byte {
|
||||
filtered := data[:0]
|
||||
matched := false
|
||||
for _, line := range bytes.Split(data, []byte("\n")) {
|
||||
switch {
|
||||
case !matched && bytes.HasPrefix(line, []byte("Panic ")):
|
||||
// This begins the panic trace, set the matched flag and append.
|
||||
matched = true
|
||||
fallthrough
|
||||
case len(filtered) == 0 || matched:
|
||||
// This is the first line or inside the panic trace.
|
||||
if len(filtered) > 0 {
|
||||
// We add the newline before rather than after because
|
||||
// bytes.Split sees the \n as *separator* and not line
|
||||
// ender, so ir will generate a last empty line that we
|
||||
// don't really want. (We want to keep blank lines in the
|
||||
// middle of the trace though.)
|
||||
filtered = append(filtered, '\n')
|
||||
}
|
||||
// Remove the device ID prefix. The "plus two" stuff is because
|
||||
// the line will look like "[foo] whatever" and the end variable
|
||||
// will end up pointing at the ] and we want to step over that
|
||||
// and the following space.
|
||||
if end := bytes.Index(line, []byte("]")); end > 1 && end < len(line)-2 && bytes.HasPrefix(line, []byte("[")) {
|
||||
line = line[end+2:]
|
||||
}
|
||||
filtered = append(filtered, line...)
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
37
cmd/syncthing/crash_reporting_test.go
Normal file
@@ -0,0 +1,37 @@
|
||||
// Copyright (C) 2019 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 (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFilterLogLines(t *testing.T) {
|
||||
in := []byte(`[ABCD123] syncthing version whatever
|
||||
here is more log data
|
||||
and more
|
||||
...
|
||||
and some more
|
||||
yet more
|
||||
Panic detected at like right now
|
||||
here is panic data
|
||||
and yet more panic stuff
|
||||
`)
|
||||
|
||||
filtered := []byte(`syncthing version whatever
|
||||
Panic detected at like right now
|
||||
here is panic data
|
||||
and yet more panic stuff
|
||||
`)
|
||||
|
||||
result := filterLogLines(in)
|
||||
if !bytes.Equal(result, filtered) {
|
||||
t.Logf("%q\n", result)
|
||||
t.Error("it should have been filtered")
|
||||
}
|
||||
}
|
||||
@@ -7,16 +7,9 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/logger"
|
||||
)
|
||||
|
||||
var (
|
||||
l = logger.DefaultLogger.NewFacility("main", "Main package")
|
||||
)
|
||||
|
||||
func init() {
|
||||
l.SetDebug("main", strings.Contains(os.Getenv("STTRACE"), "main") || os.Getenv("STTRACE") == "all")
|
||||
}
|
||||
|
||||
@@ -23,11 +23,15 @@ func init() {
|
||||
rate = i
|
||||
}
|
||||
l.Debugln("Starting heap profiling")
|
||||
go saveHeapProfiles(rate)
|
||||
go func() {
|
||||
err := saveHeapProfiles(rate) // Only returns on error
|
||||
l.Warnln("Heap profiler failed:", err)
|
||||
panic("Heap profiler failed")
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func saveHeapProfiles(rate int) {
|
||||
func saveHeapProfiles(rate int) error {
|
||||
runtime.MemProfileRate = rate
|
||||
var memstats, prevMemstats runtime.MemStats
|
||||
|
||||
@@ -38,21 +42,21 @@ func saveHeapProfiles(rate int) {
|
||||
if memstats.HeapInuse > prevMemstats.HeapInuse {
|
||||
fd, err := os.Create(name + ".tmp")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return err
|
||||
}
|
||||
err = pprof.WriteHeapProfile(fd)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return err
|
||||
}
|
||||
err = fd.Close()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return err
|
||||
}
|
||||
|
||||
os.Remove(name) // Error deliberately ignored
|
||||
err = os.Rename(name+".tmp", name)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return err
|
||||
}
|
||||
|
||||
prevMemstats = memstats
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
// Copyright (C) 2014 The Syncthing Authors.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/config"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
)
|
||||
|
||||
func TestShortIDCheck(t *testing.T) {
|
||||
cfg := config.Wrap("/tmp/test", config.Configuration{
|
||||
Devices: []config.DeviceConfiguration{
|
||||
{DeviceID: protocol.DeviceID{8, 16, 24, 32, 40, 48, 56, 0, 0}},
|
||||
{DeviceID: protocol.DeviceID{8, 16, 24, 32, 40, 48, 56, 1, 1}}, // first 56 bits same, differ in the first 64 bits
|
||||
},
|
||||
})
|
||||
|
||||
if err := checkShortIDs(cfg); err != nil {
|
||||
t.Error("Unexpected error:", err)
|
||||
}
|
||||
|
||||
cfg = config.Wrap("/tmp/test", config.Configuration{
|
||||
Devices: []config.DeviceConfiguration{
|
||||
{DeviceID: protocol.DeviceID{8, 16, 24, 32, 40, 48, 56, 64, 0}},
|
||||
{DeviceID: protocol.DeviceID{8, 16, 24, 32, 40, 48, 56, 64, 1}}, // first 64 bits same
|
||||
},
|
||||
})
|
||||
|
||||
if err := checkShortIDs(cfg); err == nil {
|
||||
t.Error("Should have gotten an error")
|
||||
}
|
||||
}
|
||||
@@ -8,18 +8,25 @@ package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/events"
|
||||
"github.com/syncthing/syncthing/lib/fs"
|
||||
"github.com/syncthing/syncthing/lib/locations"
|
||||
"github.com/syncthing/syncthing/lib/osutil"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
"github.com/syncthing/syncthing/lib/sync"
|
||||
"github.com/syncthing/syncthing/lib/syncthing"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -33,6 +40,8 @@ const (
|
||||
loopThreshold = 60 * time.Second
|
||||
logFileAutoCloseDelay = 5 * time.Second
|
||||
logFileMaxOpenTime = time.Minute
|
||||
panicUploadMaxWait = 30 * time.Second
|
||||
panicUploadNoticeWait = 10 * time.Second
|
||||
)
|
||||
|
||||
func monitorMain(runtimeOptions RuntimeOptions) {
|
||||
@@ -42,28 +51,42 @@ func monitorMain(runtimeOptions RuntimeOptions) {
|
||||
|
||||
logFile := runtimeOptions.logFile
|
||||
if logFile != "-" {
|
||||
var fileDst io.Writer = newAutoclosedFile(logFile, logFileAutoCloseDelay, logFileMaxOpenTime)
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
// Translate line breaks to Windows standard
|
||||
fileDst = osutil.ReplacingWriter{
|
||||
Writer: fileDst,
|
||||
From: '\n',
|
||||
To: []byte{'\r', '\n'},
|
||||
}
|
||||
if expanded, err := fs.ExpandTilde(logFile); err == nil {
|
||||
logFile = expanded
|
||||
}
|
||||
var fileDst io.Writer
|
||||
var err error
|
||||
open := func(name string) (io.WriteCloser, error) {
|
||||
return newAutoclosedFile(name, logFileAutoCloseDelay, logFileMaxOpenTime)
|
||||
}
|
||||
if runtimeOptions.logMaxSize > 0 {
|
||||
fileDst, err = newRotatedFile(logFile, open, int64(runtimeOptions.logMaxSize), runtimeOptions.logMaxFiles)
|
||||
} else {
|
||||
fileDst, err = open(logFile)
|
||||
}
|
||||
if err != nil {
|
||||
l.Warnln("Failed to setup logging to file, proceeding with logging to stdout only:", err)
|
||||
} else {
|
||||
if runtime.GOOS == "windows" {
|
||||
// Translate line breaks to Windows standard
|
||||
fileDst = osutil.ReplacingWriter{
|
||||
Writer: fileDst,
|
||||
From: '\n',
|
||||
To: []byte{'\r', '\n'},
|
||||
}
|
||||
}
|
||||
|
||||
// Log to both stdout and file.
|
||||
dst = io.MultiWriter(dst, fileDst)
|
||||
// Log to both stdout and file.
|
||||
dst = io.MultiWriter(dst, fileDst)
|
||||
|
||||
l.Infof(`Log output saved to file "%s"`, logFile)
|
||||
l.Infof(`Log output saved to file "%s"`, logFile)
|
||||
}
|
||||
}
|
||||
|
||||
args := os.Args
|
||||
var restarts [countRestarts]time.Time
|
||||
|
||||
stopSign := make(chan os.Signal, 1)
|
||||
sigTerm := syscall.Signal(15)
|
||||
signal.Notify(stopSign, os.Interrupt, sigTerm)
|
||||
restartSign := make(chan os.Signal, 1)
|
||||
sigHup := syscall.Signal(1)
|
||||
@@ -72,9 +95,11 @@ func monitorMain(runtimeOptions RuntimeOptions) {
|
||||
childEnv := childEnv()
|
||||
first := true
|
||||
for {
|
||||
maybeReportPanics()
|
||||
|
||||
if t := time.Since(restarts[0]); t < loopThreshold {
|
||||
l.Warnf("%d restarts in %v; not retrying further", countRestarts, t)
|
||||
os.Exit(exitError)
|
||||
os.Exit(syncthing.ExitError.AsInt())
|
||||
}
|
||||
|
||||
copy(restarts[0:], restarts[1:])
|
||||
@@ -93,10 +118,11 @@ func monitorMain(runtimeOptions RuntimeOptions) {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
l.Infoln("Starting syncthing")
|
||||
l.Debugln("Starting syncthing")
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
l.Warnln("Error starting the main Syncthing process:", err)
|
||||
panic("Error starting the main Syncthing process")
|
||||
}
|
||||
|
||||
stdoutMut.Lock()
|
||||
@@ -125,12 +151,13 @@ func monitorMain(runtimeOptions RuntimeOptions) {
|
||||
exit <- cmd.Wait()
|
||||
}()
|
||||
|
||||
stopped := false
|
||||
select {
|
||||
case s := <-stopSign:
|
||||
l.Infof("Signal %d received; exiting", s)
|
||||
cmd.Process.Signal(sigTerm)
|
||||
<-exit
|
||||
return
|
||||
err = <-exit
|
||||
stopped = true
|
||||
|
||||
case s := <-restartSign:
|
||||
l.Infof("Signal %d received; restarting", s)
|
||||
@@ -138,23 +165,31 @@ func monitorMain(runtimeOptions RuntimeOptions) {
|
||||
err = <-exit
|
||||
|
||||
case err = <-exit:
|
||||
if err == nil {
|
||||
// Successful exit indicates an intentional shutdown
|
||||
return
|
||||
} else if exiterr, ok := err.(*exec.ExitError); ok {
|
||||
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
|
||||
switch status.ExitStatus() {
|
||||
case exitUpgrading:
|
||||
// Restart the monitor process to release the .old
|
||||
// binary as part of the upgrade process.
|
||||
l.Infoln("Restarting monitor...")
|
||||
if err = restartMonitor(args); err != nil {
|
||||
l.Warnln("Restart:", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
// Successful exit indicates an intentional shutdown
|
||||
os.Exit(syncthing.ExitSuccess.AsInt())
|
||||
}
|
||||
|
||||
if exiterr, ok := err.(*exec.ExitError); ok {
|
||||
exitCode := exiterr.ExitCode()
|
||||
if stopped || runtimeOptions.noRestart {
|
||||
os.Exit(exitCode)
|
||||
}
|
||||
if exitCode == syncthing.ExitUpgrade.AsInt() {
|
||||
// Restart the monitor process to release the .old
|
||||
// binary as part of the upgrade process.
|
||||
l.Infoln("Restarting monitor...")
|
||||
if err = restartMonitor(args); err != nil {
|
||||
l.Warnln("Restart:", err)
|
||||
}
|
||||
os.Exit(exitCode)
|
||||
}
|
||||
}
|
||||
|
||||
if runtimeOptions.noRestart {
|
||||
os.Exit(syncthing.ExitError.AsInt())
|
||||
}
|
||||
|
||||
l.Infoln("Syncthing exited:", err)
|
||||
@@ -173,6 +208,13 @@ func copyStderr(stderr io.Reader, dst io.Writer) {
|
||||
br := bufio.NewReader(stderr)
|
||||
|
||||
var panicFd *os.File
|
||||
defer func() {
|
||||
if panicFd != nil {
|
||||
_ = panicFd.Close()
|
||||
maybeReportPanics()
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
line, err := br.ReadString('\n')
|
||||
if err != nil {
|
||||
@@ -269,6 +311,11 @@ func copyStdout(stdout io.Reader, dst io.Writer) {
|
||||
}
|
||||
|
||||
func restartMonitor(args []string) error {
|
||||
// Set the STRESTART environment variable to indicate to the next
|
||||
// process that this is a restart and not initial start. This prevents
|
||||
// opening the browser on startup.
|
||||
os.Setenv("STRESTART", "yes")
|
||||
|
||||
if runtime.GOOS != "windows" {
|
||||
// syscall.Exec is the cleanest way to restart on Unixes as it
|
||||
// replaces the current process with the new one, keeping the pid and
|
||||
@@ -304,6 +351,89 @@ func restartMonitorWindows(args []string) error {
|
||||
return cmd.Start()
|
||||
}
|
||||
|
||||
// rotatedFile keeps a set of rotating logs. There will be the base file plus up
|
||||
// to maxFiles rotated ones, each ~ maxSize bytes large.
|
||||
type rotatedFile struct {
|
||||
name string
|
||||
create createFn
|
||||
maxSize int64 // bytes
|
||||
maxFiles int
|
||||
currentFile io.WriteCloser
|
||||
currentSize int64
|
||||
}
|
||||
|
||||
type createFn func(name string) (io.WriteCloser, error)
|
||||
|
||||
func newRotatedFile(name string, create createFn, maxSize int64, maxFiles int) (*rotatedFile, error) {
|
||||
var size int64
|
||||
if info, err := os.Lstat(name); err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
size = 0
|
||||
} else {
|
||||
size = info.Size()
|
||||
}
|
||||
writer, err := create(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rotatedFile{
|
||||
name: name,
|
||||
create: create,
|
||||
maxSize: maxSize,
|
||||
maxFiles: maxFiles,
|
||||
currentFile: writer,
|
||||
currentSize: size,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *rotatedFile) Write(bs []byte) (int, error) {
|
||||
// Check if we're about to exceed the max size, and if so close this
|
||||
// file so we'll start on a new one.
|
||||
if r.currentSize+int64(len(bs)) > r.maxSize {
|
||||
r.currentFile.Close()
|
||||
r.currentSize = 0
|
||||
r.rotate()
|
||||
f, err := r.create(r.name)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
r.currentFile = f
|
||||
}
|
||||
|
||||
n, err := r.currentFile.Write(bs)
|
||||
r.currentSize += int64(n)
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (r *rotatedFile) rotate() {
|
||||
// The files are named "name", "name.0", "name.1", ...
|
||||
// "name.(r.maxFiles-1)". Increase the numbers on the
|
||||
// suffixed ones.
|
||||
for i := r.maxFiles - 1; i > 0; i-- {
|
||||
from := numberedFile(r.name, i-1)
|
||||
to := numberedFile(r.name, i)
|
||||
err := os.Rename(from, to)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
fmt.Println("LOG: Rotating logs:", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Rename the base to base.0
|
||||
err := os.Rename(r.name, numberedFile(r.name, 0))
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
fmt.Println("LOG: Rotating logs:", err)
|
||||
}
|
||||
}
|
||||
|
||||
// numberedFile adds the number between the file name and the extension.
|
||||
func numberedFile(name string, num int) string {
|
||||
ext := filepath.Ext(name) // contains the dot
|
||||
withoutExt := name[:len(name)-len(ext)]
|
||||
return fmt.Sprintf("%s.%d%s", withoutExt, num, ext)
|
||||
}
|
||||
|
||||
// An autoclosedFile is an io.WriteCloser that opens itself for appending on
|
||||
// Write() and closes itself after an interval of no writes (closeDelay) or
|
||||
// when the file has been open for too long (maxOpenTime). A call to Write()
|
||||
@@ -322,7 +452,7 @@ type autoclosedFile struct {
|
||||
mut sync.Mutex
|
||||
}
|
||||
|
||||
func newAutoclosedFile(name string, closeDelay, maxOpenTime time.Duration) *autoclosedFile {
|
||||
func newAutoclosedFile(name string, closeDelay, maxOpenTime time.Duration) (*autoclosedFile, error) {
|
||||
f := &autoclosedFile{
|
||||
name: name,
|
||||
closeDelay: closeDelay,
|
||||
@@ -331,8 +461,13 @@ func newAutoclosedFile(name string, closeDelay, maxOpenTime time.Duration) *auto
|
||||
closed: make(chan struct{}),
|
||||
closeTimer: time.NewTimer(time.Minute),
|
||||
}
|
||||
f.mut.Lock()
|
||||
defer f.mut.Unlock()
|
||||
if err := f.ensureOpenLocked(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
go f.closerLoop()
|
||||
return f
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func (f *autoclosedFile) Write(bs []byte) (int, error) {
|
||||
@@ -340,7 +475,7 @@ func (f *autoclosedFile) Write(bs []byte) (int, error) {
|
||||
defer f.mut.Unlock()
|
||||
|
||||
// Make sure the file is open for appending
|
||||
if err := f.ensureOpen(); err != nil {
|
||||
if err := f.ensureOpenLocked(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
@@ -370,22 +505,14 @@ func (f *autoclosedFile) Close() error {
|
||||
}
|
||||
|
||||
// Must be called with f.mut held!
|
||||
func (f *autoclosedFile) ensureOpen() error {
|
||||
func (f *autoclosedFile) ensureOpenLocked() error {
|
||||
if f.fd != nil {
|
||||
// File is already open
|
||||
return nil
|
||||
}
|
||||
|
||||
// We open the file for write only, and create it if it doesn't exist.
|
||||
flags := os.O_WRONLY | os.O_CREATE
|
||||
if f.opened.IsZero() {
|
||||
// This is the first time we are opening the file. We should truncate
|
||||
// it to better emulate an os.Create() call.
|
||||
flags |= os.O_TRUNC
|
||||
} else {
|
||||
// The file was already opened once, so we should append to it.
|
||||
flags |= os.O_APPEND
|
||||
}
|
||||
flags := os.O_WRONLY | os.O_CREATE | os.O_APPEND
|
||||
|
||||
fd, err := os.OpenFile(f.name, flags, 0644)
|
||||
if err != nil {
|
||||
@@ -430,3 +557,39 @@ func childEnv() []string {
|
||||
env = append(env, "STMONITORED=yes")
|
||||
return env
|
||||
}
|
||||
|
||||
// maybeReportPanics tries to figure out if crash reporting is on or off,
|
||||
// and reports any panics it can find if it's enabled. We spend at most
|
||||
// panicUploadMaxWait uploading panics...
|
||||
func maybeReportPanics() {
|
||||
// Try to get a config to see if/where panics should be reported.
|
||||
cfg, err := loadOrDefaultConfig(protocol.EmptyDeviceID, events.NoopLogger)
|
||||
if err != nil {
|
||||
l.Warnln("Couldn't load config; not reporting crash")
|
||||
return
|
||||
}
|
||||
|
||||
// Bail if we're not supposed to report panics.
|
||||
opts := cfg.Options()
|
||||
if !opts.CREnabled {
|
||||
return
|
||||
}
|
||||
|
||||
// Set up a timeout on the whole operation.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), panicUploadMaxWait)
|
||||
defer cancel()
|
||||
|
||||
// Print a notice if the upload takes a long time.
|
||||
go func() {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-time.After(panicUploadNoticeWait):
|
||||
l.Warnln("Uploading crash reports is taking a while, please wait...")
|
||||
}
|
||||
}()
|
||||
|
||||
// Report the panics.
|
||||
dir := locations.GetBaseDir(locations.ConfigBaseDir)
|
||||
uploadPanicLogs(ctx, opts.CRURL, dir)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -14,6 +15,126 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestRotatedFile(t *testing.T) {
|
||||
// Verify that log rotation happens.
|
||||
|
||||
dir, err := ioutil.TempDir("", "syncthing")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
open := func(name string) (io.WriteCloser, error) {
|
||||
return os.Create(name)
|
||||
}
|
||||
|
||||
logName := filepath.Join(dir, "log.txt")
|
||||
testData := []byte("12345678\n")
|
||||
maxSize := int64(len(testData) + len(testData)/2)
|
||||
|
||||
// We allow the log file plus two rotated copies.
|
||||
rf, err := newRotatedFile(logName, open, maxSize, 2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Write some bytes.
|
||||
if _, err := rf.Write(testData); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// They should be in the log.
|
||||
checkSize(t, logName, len(testData))
|
||||
checkNotExist(t, logName+".0")
|
||||
|
||||
// Write some more bytes. We should rotate and write into a new file as the
|
||||
// new bytes don't fit.
|
||||
if _, err := rf.Write(testData); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
checkSize(t, logName, len(testData))
|
||||
checkSize(t, numberedFile(logName, 0), len(testData))
|
||||
checkNotExist(t, logName+".1")
|
||||
|
||||
// Write another byte. That should fit without causing an extra rotate.
|
||||
_, _ = rf.Write([]byte{42})
|
||||
checkSize(t, logName, len(testData)+1)
|
||||
checkSize(t, numberedFile(logName, 0), len(testData))
|
||||
checkNotExist(t, numberedFile(logName, 1))
|
||||
|
||||
// Write some more bytes. We should rotate and write into a new file as the
|
||||
// new bytes don't fit.
|
||||
if _, err := rf.Write(testData); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
checkSize(t, logName, len(testData))
|
||||
checkSize(t, numberedFile(logName, 0), len(testData)+1) // the one we wrote extra to, now rotated
|
||||
checkSize(t, numberedFile(logName, 1), len(testData))
|
||||
checkNotExist(t, numberedFile(logName, 2))
|
||||
|
||||
// Write some more bytes. We should rotate and write into a new file as the
|
||||
// new bytes don't fit.
|
||||
if _, err := rf.Write(testData); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
checkSize(t, logName, len(testData))
|
||||
checkSize(t, numberedFile(logName, 0), len(testData))
|
||||
checkSize(t, numberedFile(logName, 1), len(testData)+1)
|
||||
checkNotExist(t, numberedFile(logName, 2)) // exceeds maxFiles so deleted
|
||||
}
|
||||
|
||||
func TestNumberedFile(t *testing.T) {
|
||||
// Mostly just illustrates where the number ends up and makes sure it
|
||||
// doesn't crash without an extension.
|
||||
|
||||
cases := []struct {
|
||||
in string
|
||||
num int
|
||||
out string
|
||||
}{
|
||||
{
|
||||
in: "syncthing.log",
|
||||
num: 42,
|
||||
out: "syncthing.42.log",
|
||||
},
|
||||
{
|
||||
in: filepath.Join("asdfasdf", "syncthing.log.txt"),
|
||||
num: 42,
|
||||
out: filepath.Join("asdfasdf", "syncthing.log.42.txt"),
|
||||
},
|
||||
{
|
||||
in: "syncthing-log",
|
||||
num: 42,
|
||||
out: "syncthing-log.42",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
res := numberedFile(tc.in, tc.num)
|
||||
if res != tc.out {
|
||||
t.Errorf("numberedFile(%q, %d) => %q, expected %q", tc.in, tc.num, res, tc.out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func checkSize(t *testing.T, name string, size int) {
|
||||
t.Helper()
|
||||
info, err := os.Lstat(name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if info.Size() != int64(size) {
|
||||
t.Errorf("%s wrong size: %d != expected %d", name, info.Size(), size)
|
||||
}
|
||||
}
|
||||
|
||||
func checkNotExist(t *testing.T, name string) {
|
||||
t.Helper()
|
||||
_, err := os.Lstat(name)
|
||||
if !os.IsNotExist(err) {
|
||||
t.Errorf("%s should not exist", name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAutoClosedFile(t *testing.T) {
|
||||
os.RemoveAll("_autoclose")
|
||||
defer os.RemoveAll("_autoclose")
|
||||
@@ -22,7 +143,10 @@ func TestAutoClosedFile(t *testing.T) {
|
||||
data := []byte("hello, world\n")
|
||||
|
||||
// An autoclosed file that closes very quickly
|
||||
ac := newAutoclosedFile(file, time.Millisecond, time.Millisecond)
|
||||
ac, err := newAutoclosedFile(file, time.Millisecond, time.Millisecond)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Write some data.
|
||||
if _, err := ac.Write(data); err != nil {
|
||||
@@ -64,21 +188,23 @@ func TestAutoClosedFile(t *testing.T) {
|
||||
}
|
||||
|
||||
// Open the file again.
|
||||
ac = newAutoclosedFile(file, time.Second, time.Second)
|
||||
ac, err = newAutoclosedFile(file, time.Second, time.Second)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Write something
|
||||
if _, err := ac.Write(data); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// It should now contain only one write, because the first open
|
||||
// should be a truncate.
|
||||
// It should now contain three writes, as the file is always opened for appending
|
||||
bs, err = ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(bs) != len(data) {
|
||||
t.Fatalf("Write failed, expected %d bytes, not %d", len(data), len(bs))
|
||||
if len(bs) != 3*len(data) {
|
||||
t.Fatalf("Write failed, expected %d bytes, not %d", 3*len(data), len(bs))
|
||||
}
|
||||
|
||||
// Close.
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
// Copyright (C) 2014 The Syncthing Authors.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
buf := make([]byte, 4096)
|
||||
var err error
|
||||
for err == nil {
|
||||
n, err := io.ReadFull(os.Stdin, buf)
|
||||
if n > 0 {
|
||||
buf = buf[:n]
|
||||
repl := bytes.Replace(buf, []byte("\n"), []byte("\r\n"), -1)
|
||||
_, err = os.Stdout.Write(repl)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
if err == io.EOF {
|
||||
return
|
||||
}
|
||||
buf = buf[:cap(buf)]
|
||||
}
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
@@ -24,10 +24,10 @@ type analyticList []analytic
|
||||
|
||||
func (l analyticList) Less(a, b int) bool {
|
||||
if l[a].Key == "Others" {
|
||||
return true
|
||||
return false
|
||||
}
|
||||
if l[b].Key == "Others" {
|
||||
return false
|
||||
return true
|
||||
}
|
||||
return l[b].Count < l[a].Count // inverse
|
||||
}
|
||||
|
||||
@@ -21,11 +21,14 @@ const (
|
||||
)
|
||||
|
||||
func number(ntype NumberType, v float64) string {
|
||||
if ntype == NumberDuration {
|
||||
switch ntype {
|
||||
case NumberMetric:
|
||||
return metric(v)
|
||||
case NumberDuration:
|
||||
return duration(v)
|
||||
} else if ntype == NumberBinary {
|
||||
case NumberBinary:
|
||||
return binary(v)
|
||||
} else {
|
||||
default:
|
||||
return metric(v)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"os"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -33,20 +34,42 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
useHTTP = os.Getenv("UR_USE_HTTP") != ""
|
||||
debug = os.Getenv("UR_DEBUG") != ""
|
||||
keyFile = getEnvDefault("UR_KEY_FILE", "key.pem")
|
||||
certFile = getEnvDefault("UR_CRT_FILE", "crt.pem")
|
||||
dbConn = getEnvDefault("UR_DB_URL", "postgres://user:password@localhost/ur?sslmode=disable")
|
||||
listenAddr = getEnvDefault("UR_LISTEN", "0.0.0.0:8443")
|
||||
geoIPPath = getEnvDefault("UR_GEOIP", "GeoLite2-City.mmdb")
|
||||
tpl *template.Template
|
||||
compilerRe = regexp.MustCompile(`\(([A-Za-z0-9()., -]+) \w+-\w+(?:| android| default)\) ([\w@.-]+)`)
|
||||
progressBarClass = []string{"", "progress-bar-success", "progress-bar-info", "progress-bar-warning", "progress-bar-danger"}
|
||||
featureOrder = []string{"Various", "Folder", "Device", "Connection", "GUI"}
|
||||
knownVersions = []string{"v2", "v3"}
|
||||
useHTTP = os.Getenv("UR_USE_HTTP") != ""
|
||||
debug = os.Getenv("UR_DEBUG") != ""
|
||||
keyFile = getEnvDefault("UR_KEY_FILE", "key.pem")
|
||||
certFile = getEnvDefault("UR_CRT_FILE", "crt.pem")
|
||||
dbConn = getEnvDefault("UR_DB_URL", "postgres://user:password@localhost/ur?sslmode=disable")
|
||||
listenAddr = getEnvDefault("UR_LISTEN", "0.0.0.0:8443")
|
||||
geoIPPath = getEnvDefault("UR_GEOIP", "GeoLite2-City.mmdb")
|
||||
tpl *template.Template
|
||||
compilerRe = regexp.MustCompile(`\(([A-Za-z0-9()., -]+) \w+-\w+(?:| android| default)\) ([\w@.-]+)`)
|
||||
progressBarClass = []string{"", "progress-bar-success", "progress-bar-info", "progress-bar-warning", "progress-bar-danger"}
|
||||
featureOrder = []string{"Various", "Folder", "Device", "Connection", "GUI"}
|
||||
knownVersions = []string{"v2", "v3"}
|
||||
knownDistributions = []distributionMatch{
|
||||
// Maps well known builders to the official distribution method that
|
||||
// they represent
|
||||
{regexp.MustCompile("android-.*teamcity@build.syncthing.net"), "Google Play"},
|
||||
{regexp.MustCompile("teamcity@build.syncthing.net"), "GitHub"},
|
||||
{regexp.MustCompile("deb@build.syncthing.net"), "APT"},
|
||||
{regexp.MustCompile("docker@syncthing.net"), "Docker Hub"},
|
||||
{regexp.MustCompile("jenkins@build.syncthing.net"), "GitHub"},
|
||||
{regexp.MustCompile("snap@build.syncthing.net"), "Snapcraft"},
|
||||
{regexp.MustCompile("android-.*vagrant@basebox-stretch64"), "F-Droid"},
|
||||
{regexp.MustCompile("builduser@svetlemodry"), "Arch (3rd party)"},
|
||||
{regexp.MustCompile("synology@kastelo.net"), "Synology (Kastelo)"},
|
||||
{regexp.MustCompile("@debian"), "Debian (3rd party)"},
|
||||
{regexp.MustCompile("@fedora"), "Fedora (3rd party)"},
|
||||
{regexp.MustCompile(`\bbrew@`), "Homebrew (3rd party)"},
|
||||
{regexp.MustCompile("."), "Others"},
|
||||
}
|
||||
)
|
||||
|
||||
type distributionMatch struct {
|
||||
matcher *regexp.Regexp
|
||||
distribution string
|
||||
}
|
||||
|
||||
var funcs = map[string]interface{}{
|
||||
"commatize": commatize,
|
||||
"number": number,
|
||||
@@ -245,10 +268,10 @@ type report struct {
|
||||
|
||||
func (r *report) Validate() error {
|
||||
if r.UniqueID == "" || r.Version == "" || r.Platform == "" {
|
||||
return fmt.Errorf("missing required field")
|
||||
return errors.New("missing required field")
|
||||
}
|
||||
if len(r.Date) != 8 {
|
||||
return fmt.Errorf("date not initialized")
|
||||
return errors.New("date not initialized")
|
||||
}
|
||||
|
||||
// Some fields may not be null.
|
||||
@@ -705,7 +728,8 @@ func main() {
|
||||
if useHTTP {
|
||||
listener, err = net.Listen("tcp", listenAddr)
|
||||
} else {
|
||||
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||
var cert tls.Certificate
|
||||
cert, err = tls.LoadX509KeyPair(certFile, keyFile)
|
||||
if err != nil {
|
||||
log.Fatalln("tls:", err)
|
||||
}
|
||||
@@ -731,8 +755,11 @@ func main() {
|
||||
http.HandleFunc("/movement.json", withDB(db, movementHandler))
|
||||
http.HandleFunc("/performance.json", withDB(db, performanceHandler))
|
||||
http.HandleFunc("/blockstats.json", withDB(db, blockStatsHandler))
|
||||
http.HandleFunc("/locations.json", withDB(db, locationsHandler))
|
||||
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
|
||||
|
||||
go cacheRefresher(db)
|
||||
|
||||
err = srv.Serve(listener)
|
||||
if err != nil {
|
||||
log.Fatalln("https:", err)
|
||||
@@ -740,12 +767,44 @@ func main() {
|
||||
}
|
||||
|
||||
var (
|
||||
cacheData []byte
|
||||
cacheTime time.Time
|
||||
cacheMut sync.Mutex
|
||||
cachedIndex []byte
|
||||
cachedLocations []byte
|
||||
cacheTime time.Time
|
||||
cacheMut sync.Mutex
|
||||
)
|
||||
|
||||
const maxCacheTime = 5 * 60 * time.Second
|
||||
const maxCacheTime = 15 * time.Minute
|
||||
|
||||
func cacheRefresher(db *sql.DB) {
|
||||
ticker := time.NewTicker(maxCacheTime - time.Minute)
|
||||
defer ticker.Stop()
|
||||
for range ticker.C {
|
||||
cacheMut.Lock()
|
||||
if err := refreshCacheLocked(db); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
cacheMut.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func refreshCacheLocked(db *sql.DB) error {
|
||||
rep := getReport(db)
|
||||
buf := new(bytes.Buffer)
|
||||
err := tpl.Execute(buf, rep)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cachedIndex = buf.Bytes()
|
||||
cacheTime = time.Now()
|
||||
|
||||
locs := rep["locations"].(map[location]int)
|
||||
wlocs := make([]weightedLocation, 0, len(locs))
|
||||
for loc, w := range locs {
|
||||
wlocs = append(wlocs, weightedLocation{loc, w})
|
||||
}
|
||||
cachedLocations, _ = json.Marshal(wlocs)
|
||||
return nil
|
||||
}
|
||||
|
||||
func rootHandler(db *sql.DB, w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/" || r.URL.Path == "/index.html" {
|
||||
@@ -753,26 +812,37 @@ func rootHandler(db *sql.DB, w http.ResponseWriter, r *http.Request) {
|
||||
defer cacheMut.Unlock()
|
||||
|
||||
if time.Since(cacheTime) > maxCacheTime {
|
||||
rep := getReport(db)
|
||||
buf := new(bytes.Buffer)
|
||||
err := tpl.Execute(buf, rep)
|
||||
if err != nil {
|
||||
if err := refreshCacheLocked(db); err != nil {
|
||||
log.Println(err)
|
||||
http.Error(w, "Template Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
cacheData = buf.Bytes()
|
||||
cacheTime = time.Now()
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
w.Write(cacheData)
|
||||
w.Write(cachedIndex)
|
||||
} else {
|
||||
http.Error(w, "Not found", 404)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func locationsHandler(db *sql.DB, w http.ResponseWriter, r *http.Request) {
|
||||
cacheMut.Lock()
|
||||
defer cacheMut.Unlock()
|
||||
|
||||
if time.Since(cacheTime) > maxCacheTime {
|
||||
if err := refreshCacheLocked(db); err != nil {
|
||||
log.Println(err)
|
||||
http.Error(w, "Template Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.Write(cachedLocations)
|
||||
}
|
||||
|
||||
func newDataHandler(db *sql.DB, w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
|
||||
@@ -831,7 +901,8 @@ func newDataHandler(db *sql.DB, w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func summaryHandler(db *sql.DB, w http.ResponseWriter, r *http.Request) {
|
||||
s, err := getSummary(db)
|
||||
min, _ := strconv.Atoi(r.URL.Query().Get("min"))
|
||||
s, err := getSummary(db, min)
|
||||
if err != nil {
|
||||
log.Println("summaryHandler:", err)
|
||||
http.Error(w, "Database Error", http.StatusInternalServerError)
|
||||
@@ -972,8 +1043,13 @@ func inc(storage map[string]int, key string, i interface{}) {
|
||||
}
|
||||
|
||||
type location struct {
|
||||
Latitude float64
|
||||
Longitude float64
|
||||
Latitude float64 `json:"lat"`
|
||||
Longitude float64 `json:"lon"`
|
||||
}
|
||||
|
||||
type weightedLocation struct {
|
||||
location
|
||||
Weight int `json:"weight"`
|
||||
}
|
||||
|
||||
func getReport(db *sql.DB) map[string]interface{} {
|
||||
@@ -1001,6 +1077,7 @@ func getReport(db *sql.DB) map[string]interface{} {
|
||||
var uptime []int
|
||||
var compilers []string
|
||||
var builders []string
|
||||
var distributions []string
|
||||
locations := make(map[location]int)
|
||||
countries := make(map[string]int)
|
||||
|
||||
@@ -1070,10 +1147,19 @@ func getReport(db *sql.DB) map[string]interface{} {
|
||||
nodes++
|
||||
versions = append(versions, transformVersion(rep.Version))
|
||||
platforms = append(platforms, rep.Platform)
|
||||
|
||||
if m := compilerRe.FindStringSubmatch(rep.LongVersion); len(m) == 3 {
|
||||
compilers = append(compilers, m[1])
|
||||
builders = append(builders, m[2])
|
||||
loop:
|
||||
for _, d := range knownDistributions {
|
||||
if d.matcher.MatchString(rep.LongVersion) {
|
||||
distributions = append(distributions, d.distribution)
|
||||
break loop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if rep.NumFolders > 0 {
|
||||
numFolders = append(numFolders, rep.NumFolders)
|
||||
}
|
||||
@@ -1363,9 +1449,10 @@ func getReport(db *sql.DB) map[string]interface{} {
|
||||
r["categories"] = categories
|
||||
r["versions"] = group(byVersion, analyticsFor(versions, 2000), 10)
|
||||
r["versionPenetrations"] = penetrationLevels(analyticsFor(versions, 2000), []float64{50, 75, 90, 95})
|
||||
r["platforms"] = group(byPlatform, analyticsFor(platforms, 2000), 5)
|
||||
r["platforms"] = group(byPlatform, analyticsFor(platforms, 2000), 10)
|
||||
r["compilers"] = group(byCompiler, analyticsFor(compilers, 2000), 5)
|
||||
r["builders"] = analyticsFor(builders, 12)
|
||||
r["distributions"] = analyticsFor(distributions, len(knownDistributions))
|
||||
r["featureOrder"] = featureOrder
|
||||
r["locations"] = locations
|
||||
r["contries"] = countryList
|
||||
@@ -1373,15 +1460,6 @@ func getReport(db *sql.DB) map[string]interface{} {
|
||||
return r
|
||||
}
|
||||
|
||||
func ensureDir(dir string, mode int) {
|
||||
fi, err := os.Stat(dir)
|
||||
if os.IsNotExist(err) {
|
||||
os.MkdirAll(dir, 0700)
|
||||
} else if mode >= 0 && err == nil && int(fi.Mode()&0777) != mode {
|
||||
os.Chmod(dir, os.FileMode(mode))
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
plusRe = regexp.MustCompile(`\+.*$`)
|
||||
plusStr = "(+dev)"
|
||||
@@ -1482,7 +1560,21 @@ func (s *summary) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(table)
|
||||
}
|
||||
|
||||
func getSummary(db *sql.DB) (summary, error) {
|
||||
// filter removes versions that never reach the specified min count.
|
||||
func (s *summary) filter(min int) {
|
||||
// We cheat and just remove the versions from the "index" and leave the
|
||||
// data points alone. The version index is used to build the table when
|
||||
// we do the serialization, so at that point the data points are
|
||||
// filtered out as well.
|
||||
for ver := range s.versions {
|
||||
if s.max[ver] < min {
|
||||
delete(s.versions, ver)
|
||||
delete(s.max, ver)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getSummary(db *sql.DB, min int) (summary, error) {
|
||||
s := newSummary()
|
||||
|
||||
rows, err := db.Query(`SELECT Day, Version, Count FROM VersionSummary WHERE Day > now() - '2 year'::INTERVAL;`)
|
||||
@@ -1513,6 +1605,7 @@ func getSummary(db *sql.DB) (summary, error) {
|
||||
s.setCount(day.Format("2006-01-02"), ver, num)
|
||||
}
|
||||
|
||||
s.filter(min)
|
||||
return s, nil
|
||||
}
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 4.8 KiB |
@@ -17,7 +17,11 @@ found in the LICENSE file.
|
||||
<link href="static/bootstrap/css/bootstrap.min.css" rel="stylesheet">
|
||||
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="static/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?libraries=visualization&key=AIzaSyDk5WJ8s7ueLKb99X5DbQ-vkWtPDAKqYs0"></script>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/leaflet.css">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/leaflet.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/heatmapjs@2.0.2/heatmap.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/leaflet-heatmap@1.0.0/leaflet-heatmap.js"></script>
|
||||
|
||||
<style type="text/css">
|
||||
body {
|
||||
margin: 40px;
|
||||
@@ -49,10 +53,12 @@ found in the LICENSE file.
|
||||
google.setOnLoadCallback(drawMovementChart);
|
||||
google.setOnLoadCallback(drawBlockStatsChart);
|
||||
google.setOnLoadCallback(drawPerformanceCharts);
|
||||
google.setOnLoadCallback(drawHeatMap);
|
||||
|
||||
function drawVersionChart() {
|
||||
var jsonData = $.ajax({url: "summary.json", dataType:"json", async: false}).responseText;
|
||||
// Summary version chart for versions that at some point in the chart
|
||||
// reaches 250 devices. This filters out versions that are old and
|
||||
// uninteresting yet linger forever with like four users.
|
||||
var jsonData = $.ajax({url: "summary.json?min=250", dataType:"json", async: false}).responseText;
|
||||
var rows = JSON.parse(jsonData);
|
||||
|
||||
var data = new google.visualization.DataTable();
|
||||
@@ -211,47 +217,46 @@ found in the LICENSE file.
|
||||
|
||||
var locations = [];
|
||||
{{range $location, $weight := .locations}}
|
||||
locations.push({location: new google.maps.LatLng({{- $location.Latitude -}}, {{- $location.Longitude -}}), weight: {{- $weight -}}});
|
||||
locations.push({lat:{{- $location.Latitude -}},lng:{{- $location.Longitude -}},count:Math.min(100, {{- $weight -}})});
|
||||
{{- end}}
|
||||
|
||||
function drawHeatMap() {
|
||||
if (locations.length == 0) {
|
||||
return;
|
||||
}
|
||||
var mapBounds = new google.maps.LatLngBounds();
|
||||
var map = new google.maps.Map(document.getElementById('map'), {
|
||||
zoom: 1,
|
||||
mapTypeId: google.maps.MapTypeId.ROADMAP
|
||||
});
|
||||
var heatmap = new google.maps.visualization.HeatmapLayer({
|
||||
var testData = {
|
||||
data: locations
|
||||
};
|
||||
|
||||
var baseLayer = L.tileLayer(
|
||||
'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',{
|
||||
attribution: '...',
|
||||
maxZoom: 18
|
||||
}
|
||||
);
|
||||
var cfg = {
|
||||
"radius": 1,
|
||||
"minOpacity": .25,
|
||||
"maxOpacity": .8,
|
||||
"scaleRadius": true,
|
||||
"useLocalExtrema": true,
|
||||
latField: 'lat',
|
||||
lngField: 'lng',
|
||||
valueField: 'count',
|
||||
gradient: {
|
||||
'.1': 'cyan',
|
||||
'.8': 'blue',
|
||||
'.95': 'red'
|
||||
}
|
||||
};
|
||||
var heatmapLayer = new HeatmapOverlay(cfg);
|
||||
|
||||
var map = new L.Map('map', {
|
||||
center: new L.LatLng(25, 0),
|
||||
zoom: 1,
|
||||
layers: [baseLayer, heatmapLayer]
|
||||
});
|
||||
heatmap.set('radius', 10);
|
||||
heatmap.set('maxIntensity', 20);
|
||||
heatmap.set('gradient', [
|
||||
'rgba(0, 255, 255, 0)',
|
||||
'rgba(0, 255, 255, 1)',
|
||||
'rgba(0, 191, 255, 1)',
|
||||
'rgba(0, 127, 255, 1)',
|
||||
'rgba(0, 63, 255, 1)',
|
||||
'rgba(0, 0, 255, 1)',
|
||||
'rgba(0, 0, 223, 1)',
|
||||
'rgba(0, 0, 191, 1)',
|
||||
'rgba(0, 0, 159, 1)',
|
||||
'rgba(0, 0, 127, 1)',
|
||||
'rgba(63, 0, 91, 1)',
|
||||
'rgba(127, 0, 63, 1)',
|
||||
'rgba(191, 0, 31, 1)',
|
||||
'rgba(255, 0, 0, 1)'
|
||||
]);
|
||||
heatmap.setMap(map);
|
||||
for (var x = 0; x < locations.length; x++) {
|
||||
mapBounds.extend(locations[x].location);
|
||||
}
|
||||
map.fitBounds(mapBounds);
|
||||
if (locations.length == 1) {
|
||||
map.setZoom(13);
|
||||
}
|
||||
heatmapLayer.setData(testData);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
@@ -296,7 +301,7 @@ found in the LICENSE file.
|
||||
{{if .locations}}
|
||||
<div class="img-thumbnail" id="map" style="width: 1130px; height: 400px; padding: 10px;"></div>
|
||||
<p class="text-muted">
|
||||
Heatmap max intensity is capped at 20 reports within a location.
|
||||
Heatmap max intensity is capped at 100 reports within a location.
|
||||
</p>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
@@ -506,6 +511,27 @@ found in the LICENSE file.
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Distribution Channel</th>
|
||||
<th class="text-right">Devices</th>
|
||||
<th class="text-right">Share</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .distributions}}
|
||||
<tr>
|
||||
<td>{{.Key}}</td>
|
||||
<td class="text-right">{{.Count}}</td>
|
||||
<td class="text-right">{{.Percentage | printf "%.01f"}}%</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
@@ -630,6 +656,7 @@ found in the LICENSE file.
|
||||
</p>
|
||||
<script type="text/javascript">
|
||||
$('[data-toggle="tooltip"]').tooltip({html:true});
|
||||
drawHeatMap();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Name=Start Syncthing
|
||||
GenericName=File synchronization
|
||||
Comment=Starts the main syncthing process in the background.
|
||||
Exec=/usr/bin/syncthing -no-browser
|
||||
Exec=/usr/bin/syncthing -no-browser -logfile=default
|
||||
Icon=syncthing
|
||||
Terminal=false
|
||||
Type=Application
|
||||
|
||||
0
etc/linux-runit/run
Normal file → Executable file
@@ -7,6 +7,7 @@ After=network.target
|
||||
User=%i
|
||||
ExecStart=/usr/bin/syncthing -no-browser -no-restart -logflags=0
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
SuccessExitStatus=3 4
|
||||
RestartForceExitStatus=3 4
|
||||
|
||||
|
||||
@@ -5,12 +5,11 @@ Documentation=man:syncthing(1)
|
||||
[Service]
|
||||
ExecStart=/usr/bin/syncthing -no-browser -no-restart -logflags=0
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
SuccessExitStatus=3 4
|
||||
RestartForceExitStatus=3 4
|
||||
|
||||
# Hardening
|
||||
ProtectSystem=full
|
||||
PrivateTmp=true
|
||||
SystemCallArchitectures=native
|
||||
MemoryDenyWriteExecute=true
|
||||
NoNewPrivileges=true
|
||||
|
||||
71
go.mod
@@ -1,47 +1,50 @@
|
||||
module github.com/syncthing/syncthing
|
||||
|
||||
require (
|
||||
github.com/AudriusButkevicius/go-nat-pmp v0.0.0-20160522074932-452c97607362
|
||||
github.com/AudriusButkevicius/pfilter v0.0.0-20190627213056-c55ef6137fc6
|
||||
github.com/AudriusButkevicius/recli v0.0.5
|
||||
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect
|
||||
github.com/bkaradzic/go-lz4 v0.0.0-20160924222819-7224d8d8f27e
|
||||
github.com/calmh/du v1.0.1
|
||||
github.com/calmh/xdr v1.1.0
|
||||
github.com/ccding/go-stun v0.0.0-20180726100737-be486d185f3d
|
||||
github.com/certifi/gocertifi v0.0.0-20190905060710-a5e0173ced67 // indirect
|
||||
github.com/chmduquesne/rollinghash v0.0.0-20180912150627-a60f8e7142b5
|
||||
github.com/d4l3k/messagediff v1.2.1
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dgraph-io/badger/v2 v2.0.3
|
||||
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568
|
||||
github.com/gobwas/glob v0.0.0-20170212200151-51eb1ee00b6d
|
||||
github.com/gogo/protobuf v1.2.0
|
||||
github.com/golang/groupcache v0.0.0-20171101203131-84a468cf14b4
|
||||
github.com/golang/snappy v0.0.0-20170215233205-553a64147049 // indirect
|
||||
github.com/jackpal/gateway v0.0.0-20161225004348-5795ac81146e
|
||||
github.com/kballard/go-shellquote v0.0.0-20170619183022-cd60e84ee657
|
||||
github.com/kr/pretty v0.1.0 // indirect
|
||||
github.com/lib/pq v1.0.0
|
||||
github.com/mattn/go-isatty v0.0.4
|
||||
github.com/minio/sha256-simd v0.0.0-20190117184323-cc1980cb0338
|
||||
github.com/onsi/ginkgo v0.0.0-20171221013426-6c46eb8334b3 // indirect
|
||||
github.com/onsi/gomega v0.0.0-20171227184521-ba3724c94e4d // indirect
|
||||
github.com/oschwald/geoip2-golang v1.1.0
|
||||
github.com/oschwald/maxminddb-golang v0.0.0-20170901134056-26fe5ace1c70 // indirect
|
||||
github.com/petermattis/goid v0.0.0-20170816195418-3db12ebb2a59 // indirect
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_golang v0.9.2
|
||||
github.com/rcrowley/go-metrics v0.0.0-20171128170426-e181e095bae9
|
||||
github.com/getsentry/raven-go v0.2.0
|
||||
github.com/go-ldap/ldap/v3 v3.1.10
|
||||
github.com/go-ole/go-ole v1.2.4 // indirect
|
||||
github.com/gobwas/glob v0.2.3
|
||||
github.com/gogo/protobuf v1.3.1
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6
|
||||
github.com/greatroar/blobloom v0.2.1
|
||||
github.com/jackpal/gateway v1.0.6
|
||||
github.com/jackpal/go-nat-pmp v1.0.2
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||
github.com/kr/pretty v0.2.0 // indirect
|
||||
github.com/lib/pq v1.2.0
|
||||
github.com/lucas-clemente/quic-go v0.16.0
|
||||
github.com/maruel/panicparse v1.3.0
|
||||
github.com/mattn/go-isatty v0.0.11
|
||||
github.com/minio/sha256-simd v0.1.1
|
||||
github.com/oschwald/geoip2-golang v1.4.0
|
||||
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/prometheus/client_golang v1.2.1
|
||||
github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563
|
||||
github.com/sasha-s/go-deadlock v0.2.0
|
||||
github.com/stretchr/testify v1.2.2 // indirect
|
||||
github.com/syncthing/notify v0.0.0-20181107104724-4e389ea6c0d8
|
||||
github.com/syndtr/goleveldb v0.0.0-20171214120811-34011bf325bc
|
||||
github.com/shirou/gopsutil v0.0.0-20190714054239-47ef3260b6bf
|
||||
github.com/syncthing/notify v0.0.0-20190709140112-69c7a957d3e2
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d
|
||||
github.com/thejerf/suture v3.0.2+incompatible
|
||||
github.com/urfave/cli v1.20.0
|
||||
github.com/urfave/cli v1.22.2
|
||||
github.com/vitrun/qart v0.0.0-20160531060029-bf64b92db6b0
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2
|
||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc
|
||||
golang.org/x/text v0.0.0-20171227012246-e19ae1496984
|
||||
golang.org/x/time v0.0.0-20170927054726-6dc17368e09b
|
||||
gopkg.in/asn1-ber.v1 v1.0.0-20170511165959-379148ca0225 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
||||
gopkg.in/ldap.v2 v2.5.1
|
||||
gopkg.in/yaml.v2 v2.0.0-20171116090243-287cf08546ab // indirect
|
||||
golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae
|
||||
golang.org/x/text v0.3.2
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4
|
||||
)
|
||||
|
||||
go 1.13
|
||||
|
||||
488
go.sum
@@ -1,110 +1,476 @@
|
||||
github.com/AudriusButkevicius/go-nat-pmp v0.0.0-20160522074932-452c97607362 h1:l4qGIzSY0WhdXdR74XMYAtfc0Ri/RJVM4p6x/E/+WkA=
|
||||
github.com/AudriusButkevicius/go-nat-pmp v0.0.0-20160522074932-452c97607362/go.mod h1:CEaBhA5lh1spxbPOELh5wNLKGsVQoahjUhVrJViVK8s=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=
|
||||
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
|
||||
dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
|
||||
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
|
||||
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
|
||||
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
||||
github.com/AudriusButkevicius/pfilter v0.0.0-20190627213056-c55ef6137fc6 h1:Apvc4kyfdrOxG+F5dn8osz+45kwGJa6CySQn0tB38SU=
|
||||
github.com/AudriusButkevicius/pfilter v0.0.0-20190627213056-c55ef6137fc6/go.mod h1:1N0EEx/irz4B1qV17wW82TFbjQrE7oX316Cki6eDY0Q=
|
||||
github.com/AudriusButkevicius/recli v0.0.5 h1:xUa55PvWTHBm17T6RvjElRO3y5tALpdceH86vhzQ5wg=
|
||||
github.com/AudriusButkevicius/recli v0.0.5/go.mod h1:Q2E26yc6RvWWEz/TJ/goUp6yXvipYdJI096hpoaqsNs=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/DataDog/zstd v1.4.1 h1:3oxKN3wbHibqx897utPC2LTQU4J+IHWWJO+glkAkpFM=
|
||||
github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8=
|
||||
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
||||
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk=
|
||||
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
||||
github.com/alangpierce/go-forceexport v0.0.0-20160317203124-8f1d6941cd75 h1:3ILjVyslFbc4jl1w5TWuvvslFD/nDfR2H8tVaMVLrEY=
|
||||
github.com/alangpierce/go-forceexport v0.0.0-20160317203124-8f1d6941cd75/go.mod h1:uAXEEpARkRhCZfEvy/y0Jcc888f9tHCc1W7/UeEtreE=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 h1:Hs82Z41s6SdL1CELW+XaDYmOH4hkBN4/N9og/AsOv7E=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bkaradzic/go-lz4 v0.0.0-20160924222819-7224d8d8f27e h1:2augTYh6E+XoNrrivZJBadpThP/dsvYKj0nzqfQ8tM4=
|
||||
github.com/bkaradzic/go-lz4 v0.0.0-20160924222819-7224d8d8f27e/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4=
|
||||
github.com/calmh/du v1.0.1 h1:uDCrDbXVVPrzxSNRkpj6nqSfwrl5uRWH3zvrJgl7RRo=
|
||||
github.com/calmh/du v1.0.1/go.mod h1:pHNccp4cXQeyDaiV3S7t5GN+eGOgynF0VSLxJjk9tLU=
|
||||
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625 h1:ckJgFhFWywOx+YLEMIJsTb+NV6NexWICk5+AMSuz3ss=
|
||||
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
|
||||
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
|
||||
github.com/calmh/xdr v1.1.0 h1:U/Dd4CXNLoo8EiQ4ulJUXkgO1/EyQLgDKLgpY1SOoJE=
|
||||
github.com/calmh/xdr v1.1.0/go.mod h1:E8sz2ByAdXC8MbANf1LCRYzedSnnc+/sXXJs/PVqoeg=
|
||||
github.com/ccding/go-stun v0.0.0-20180726100737-be486d185f3d h1:As4937T5NVbJ/DmZT9z33pyLEprMd6CUSfhbmMY57Io=
|
||||
github.com/ccding/go-stun v0.0.0-20180726100737-be486d185f3d/go.mod h1:3FK1bMar37f7jqVY7q/63k3OMX1c47pGCufzt3X0sYE=
|
||||
github.com/certifi/gocertifi v0.0.0-20190905060710-a5e0173ced67 h1:8k9FLYBLKT+9v2HQJ/a95ZemmTx+/ltJcAiRhVushG8=
|
||||
github.com/certifi/gocertifi v0.0.0-20190905060710-a5e0173ced67/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.1.0 h1:yTUvW7Vhb89inJ+8irsUqiWjh8iT6sQPZiQzI6ReGkA=
|
||||
github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM=
|
||||
github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
|
||||
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
|
||||
github.com/chmduquesne/rollinghash v0.0.0-20180912150627-a60f8e7142b5 h1:Wg96Dh0MLTanEaPO0OkGtUIaa2jOnShAIOVUIzRHUxo=
|
||||
github.com/chmduquesne/rollinghash v0.0.0-20180912150627-a60f8e7142b5/go.mod h1:Uc2I36RRfTAf7Dge82bi3RU0OQUmXT9iweIcPqvr8A0=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
|
||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/d4l3k/messagediff v1.2.1 h1:ZcAIMYsUg0EAp9X+tt8/enBE/Q8Yd5kzPynLyKptt9U=
|
||||
github.com/d4l3k/messagediff v1.2.1/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgraph-io/badger v1.6.1 h1:w9pSFNSdq/JPM1N12Fz/F/bzo993Is1W+Q7HjPzi7yg=
|
||||
github.com/dgraph-io/badger/v2 v2.0.3 h1:inzdf6VF/NZ+tJ8RwwYMjJMvsOALTHYdozn0qSl6XJI=
|
||||
github.com/dgraph-io/badger/v2 v2.0.3/go.mod h1:3KY8+bsP8wI0OEnQJAKpd4wIJW/Mm32yw2j/9FUVnIM=
|
||||
github.com/dgraph-io/ristretto v0.0.2-0.20200115201040-8f368f2f2ab3 h1:MQLRM35Pp0yAyBYksjbj1nZI/w6eyRY/mWoM1sFf4kU=
|
||||
github.com/dgraph-io/ristretto v0.0.2-0.20200115201040-8f368f2f2ab3/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA=
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BMXYYRWTLOJKlh+lOBt6nUQgXAfB7oVIQt5cNreqSLI=
|
||||
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:rZfgFAXFS/z/lEd6LJmf9HVZ1LkgYiHx5pHhV5DR16M=
|
||||
github.com/gobwas/glob v0.0.0-20170212200151-51eb1ee00b6d h1:IngNQgbqr5ZOU0exk395Szrvkzes9Ilk1fmJfkw7d+M=
|
||||
github.com/gobwas/glob v0.0.0-20170212200151-51eb1ee00b6d/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/gogo/protobuf v1.2.0 h1:xU6/SpYbvkNYiptHJYEDRseDLvYE7wSqhYYNy0QSUzI=
|
||||
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/golang/groupcache v0.0.0-20171101203131-84a468cf14b4 h1:6o8aP0LGMKzo3NzwhhX6EJsiJ3ejmj+9yA/3p8Fjjlw=
|
||||
github.com/golang/groupcache v0.0.0-20171101203131-84a468cf14b4/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
|
||||
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/getsentry/raven-go v0.2.0 h1:no+xWJRb5ZI7eE8TWgIq1jLulQiIoLG0IfYxv5JYMGs=
|
||||
github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||
github.com/go-asn1-ber/asn1-ber v1.3.1 h1:gvPdv/Hr++TRFCl0UbPFHC54P9N9jgsRPnmnr419Uck=
|
||||
github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-ldap/ldap/v3 v3.1.7 h1:aHjuWTgZsnxjMgqzx0JHwNqz4jBYZTcNarbPFkW1Oww=
|
||||
github.com/go-ldap/ldap/v3 v3.1.7/go.mod h1:5Zun81jBTabRaI8lzN7E1JjyEl1g6zI6u9pd8luAK4Q=
|
||||
github.com/go-ldap/ldap/v3 v3.1.10 h1:7WsKqasmPThNvdl0Q5GPpbTDD/ZD98CfuawrMIuh7qQ=
|
||||
github.com/go-ldap/ldap/v3 v3.1.10/go.mod h1:5Zun81jBTabRaI8lzN7E1JjyEl1g6zI6u9pd8luAK4Q=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E=
|
||||
github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
|
||||
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
|
||||
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
|
||||
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.4.0 h1:Rd1kQnQu0Hq3qvJppYSG0HtP+f5LPPUiDswTLiEegLg=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/snappy v0.0.0-20170215233205-553a64147049 h1:K9KHZbXKpGydfDN0aZrsoHpLJlZsBrGMFWbgLDGnPZk=
|
||||
github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/jackpal/gateway v0.0.0-20161225004348-5795ac81146e h1:lS8IitpqG4RkZbEDlZg5Z7FvBdWLVjSVfsPGOKafEkI=
|
||||
github.com/jackpal/gateway v0.0.0-20161225004348-5795ac81146e/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA=
|
||||
github.com/kballard/go-shellquote v0.0.0-20170619183022-cd60e84ee657 h1:vE7J1m7cCpiRVEIr1B5ccDxRpbPsWT5JU3if2Di5nE4=
|
||||
github.com/kballard/go-shellquote v0.0.0-20170619183022-cd60e84ee657/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
|
||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
||||
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/greatroar/blobloom v0.2.1 h1:Ie7+kTQFhcvfFHhzOYSJA2XE5sp8c9iB5iapu2tIig4=
|
||||
github.com/greatroar/blobloom v0.2.1/go.mod h1:we9vO6GNYMmsNvCWINtZnQbcGEHUT6hGBAznNHd6RlE=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jackpal/gateway v1.0.6 h1:/MJORKvJEwNVldtGVJC2p2cwCnsSoLn3hl3zxmZT7tk=
|
||||
github.com/jackpal/gateway v1.0.6/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA=
|
||||
github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus=
|
||||
github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
|
||||
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
|
||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lucas-clemente/quic-go v0.15.6 h1:2l7g4fjjjwqmM3NirLiBtszMaR7aDTSsiiXY9FrGroo=
|
||||
github.com/lucas-clemente/quic-go v0.15.6/go.mod h1:Myi1OyS0FOjL3not4BxT7KN29bRkcMUV5JVVFLKtDp8=
|
||||
github.com/lucas-clemente/quic-go v0.15.7 h1:Pu7To5/G9JoP1mwlrcIvfV8ByPBlCzif3MCl8+1W83I=
|
||||
github.com/lucas-clemente/quic-go v0.15.7/go.mod h1:Myi1OyS0FOjL3not4BxT7KN29bRkcMUV5JVVFLKtDp8=
|
||||
github.com/lucas-clemente/quic-go v0.16.0 h1:jJw36wfzGJhmOhAOaOC2lS36WgeqXQszH47A7spo1LI=
|
||||
github.com/lucas-clemente/quic-go v0.16.0/go.mod h1:I0+fcNTdb9eS1ZcjQZbDVPGchJ86chcIxPALn9lEJqE=
|
||||
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/marten-seemann/qpack v0.1.0/go.mod h1:LFt1NU/Ptjip0C2CPkhimBz5CGE3WGDAUWqna+CNTrI=
|
||||
github.com/marten-seemann/qtls v0.9.1 h1:O0YKQxNVPaiFgMng0suWEOY2Sb4LT2sRn9Qimq3Z1IQ=
|
||||
github.com/marten-seemann/qtls v0.9.1/go.mod h1:T1MmAdDPyISzxlK6kjRr0pcZFBVd1OZbBb/j3cvzHhk=
|
||||
github.com/maruel/panicparse v1.3.0 h1:1Ep/RaYoSL1r5rTILHQQbyzHG8T4UP5ZbQTYTo4bdDc=
|
||||
github.com/maruel/panicparse v1.3.0/go.mod h1:vszMjr5QQ4F5FSRfraldcIA/BCw5xrdLL+zEcU2nRBs=
|
||||
github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg=
|
||||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc=
|
||||
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
|
||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/minio/sha256-simd v0.0.0-20190117184323-cc1980cb0338 h1:USW1+zAUkUSvk097CAX/i8KR3r6f+DHNhk6Xe025Oyw=
|
||||
github.com/minio/sha256-simd v0.0.0-20190117184323-cc1980cb0338/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U=
|
||||
github.com/onsi/ginkgo v0.0.0-20171221013426-6c46eb8334b3 h1:ZN7kHmC0iunA+4UPmERwsuMQan4lUnntO6WX6H1jOO8=
|
||||
github.com/onsi/ginkgo v0.0.0-20171221013426-6c46eb8334b3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v0.0.0-20171227184521-ba3724c94e4d h1:r351oUAFgdsydkt/g+XR/iJWRwyxVpy6nkNdEl/QdAs=
|
||||
github.com/onsi/gomega v0.0.0-20171227184521-ba3724c94e4d/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||
github.com/oschwald/geoip2-golang v1.1.0 h1:ACVPz5YqH4/jZkQdsp/PZc9shQVZmreCzAVNss5y3bo=
|
||||
github.com/oschwald/geoip2-golang v1.1.0/go.mod h1:0LTTzix/Ao1uMvOhAV4iLU0Lz7eCrP94qZWBTDKf0iE=
|
||||
github.com/oschwald/maxminddb-golang v0.0.0-20170901134056-26fe5ace1c70 h1:XGLYUmodtNzThosQ8GkMvj9TiIB/uWsP8NfxKSa3aDc=
|
||||
github.com/oschwald/maxminddb-golang v0.0.0-20170901134056-26fe5ace1c70/go.mod h1:3jhIUymTJ5VREKyIhWm66LJiQt04F0UCDdodShpjWsY=
|
||||
github.com/petermattis/goid v0.0.0-20170816195418-3db12ebb2a59 h1:2pHcLyJYXivxVvpoCc29uo3GDU1qFfJ1ggXKGYMrM0E=
|
||||
github.com/petermattis/goid v0.0.0-20170816195418-3db12ebb2a59/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o=
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
||||
github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKUJU=
|
||||
github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
||||
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.11.0 h1:JAKSXpt1YjtLA7YpPiqO9ss6sNXEsPfSGdwN0UHqzrw=
|
||||
github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.8.1 h1:C5Dqfs/LeauYDX0jJXIe2SWmwCbGzx9yF8C8xy3Lh34=
|
||||
github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
|
||||
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
||||
github.com/oschwald/geoip2-golang v1.4.0 h1:5RlrjCgRyIGDz/mBmPfnAF4h8k0IAcRv9PvrpOfz+Ug=
|
||||
github.com/oschwald/geoip2-golang v1.4.0/go.mod h1:8QwxJvRImBH+Zl6Aa6MaIcs5YdlZSTKtzmPGzQqi9ng=
|
||||
github.com/oschwald/maxminddb-golang v1.6.0 h1:KAJSjdHQ8Kv45nFIbtoLGrGWqHFajOIm7skTyz/+Dls=
|
||||
github.com/oschwald/maxminddb-golang v1.6.0/go.mod h1:DUJFucBg2cvqx42YmDa/+xHvb0elJtOm3o4aFQ/nb/w=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ=
|
||||
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v0.9.2 h1:awm861/B8OKDd2I/6o1dy3ra4BamzKhYOiGItCeZ740=
|
||||
github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
|
||||
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.2.1 h1:JnMpQc6ppsNgw9QPAGF6Dod479itz7lvlsMzzNayLOI=
|
||||
github.com/prometheus/client_golang v1.2.1/go.mod h1:XMU6Z2MjaRKVu/dC1qupJI9SiNkDYzz3xecMgSW/F+U=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275 h1:PnBWHBf+6L0jOqq0gIVUe6Yk0/QMZ640k6NvkxcBf+8=
|
||||
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a h1:9a8MnZMP0X2nLJdBg+pBmGgkJlSaKC2KaQmTCk1XDtE=
|
||||
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20171128170426-e181e095bae9 h1:jmLW6izPBVlIbk4d+XgK9+sChGbVKxxOPmd9eqRHCjw=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20171128170426-e181e095bae9/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6Kw=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.7.0 h1:L+1lyG48J1zAQXA3RBX/nG/B3gjlHq0zTt2tlbJLyCY=
|
||||
github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
|
||||
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2 h1:6LJUbpNm42llc4HRCuvApCSWB/WfhuNo9K98Q9sNGfs=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8=
|
||||
github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563 h1:dY6ETXrvDG7Sa4vE8ZQG4yqWg6UnOcbqTAahkV813vQ=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sasha-s/go-deadlock v0.2.0 h1:lMqc+fUb7RrFS3gQLtoQsJ7/6TV/pAIFvBsqX73DK8Y=
|
||||
github.com/sasha-s/go-deadlock v0.2.0/go.mod h1:StQn567HiB1fF2yJ44N9au7wOhrPS3iZqiDbRupzT10=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shirou/gopsutil v0.0.0-20190714054239-47ef3260b6bf h1:c9SV5NzG4KOk448TUE7iqCmb4E4y79CZF4zDdc1Jx3Q=
|
||||
github.com/shirou/gopsutil v0.0.0-20190714054239-47ef3260b6bf/go.mod h1:WWnYX4lzhCH5h/3YBfyVA3VbLYjlMZZAQcW9ojMexNc=
|
||||
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
|
||||
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
|
||||
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
|
||||
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
|
||||
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
|
||||
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
|
||||
github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw=
|
||||
github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI=
|
||||
github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=
|
||||
github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=
|
||||
github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg=
|
||||
github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw=
|
||||
github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y=
|
||||
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
|
||||
github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=
|
||||
github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ=
|
||||
github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I=
|
||||
github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0=
|
||||
github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
|
||||
github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=
|
||||
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
|
||||
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
|
||||
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/syncthing/notify v0.0.0-20181107104724-4e389ea6c0d8 h1:ewsMW/a4xDpqHyIteoD29ayMn6GdkFZc2T0PX2K6PAg=
|
||||
github.com/syncthing/notify v0.0.0-20181107104724-4e389ea6c0d8/go.mod h1:Sn4ChoS7e4FxjCN1XHPVBT43AgnRLbuaB8pEc1Zcdjg=
|
||||
github.com/syndtr/goleveldb v0.0.0-20171214120811-34011bf325bc h1:yhWARKbbDg8UBRi/M5bVcVOBg2viFKcNJEAtHMYbRBo=
|
||||
github.com/syndtr/goleveldb v0.0.0-20171214120811-34011bf325bc/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/syncthing/notify v0.0.0-20190709140112-69c7a957d3e2 h1:6tuEEEpg+mxM82E0YingzoXzXXISYR/o/7I9n573LWI=
|
||||
github.com/syncthing/notify v0.0.0-20190709140112-69c7a957d3e2/go.mod h1:Sn4ChoS7e4FxjCN1XHPVBT43AgnRLbuaB8pEc1Zcdjg=
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d h1:gZZadD8H+fF+n9CmNhYL1Y0dJB+kLOmKd7FbPJLeGHs=
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d/go.mod h1:9OrXJhf154huy1nPWmuSrkgjPUtUNhA+Zmy+6AESzuA=
|
||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
||||
github.com/thejerf/suture v3.0.2+incompatible h1:GtMydYcnK4zBJ0KL6Lx9vLzl6Oozb65wh252FTBxrvM=
|
||||
github.com/thejerf/suture v3.0.2+incompatible/go.mod h1:ibKwrVj+Uzf3XZdAiNWUouPaAbSoemxOHLmJmwheEMc=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo=
|
||||
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
|
||||
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
|
||||
github.com/vitrun/qart v0.0.0-20160531060029-bf64b92db6b0 h1:okhMind4q9H1OxF44gNegWkiP4H/gsTFLalHFa4OOUI=
|
||||
github.com/vitrun/qart v0.0.0-20160531060029-bf64b92db6b0/go.mod h1:TTbGUfE+cXXceWtbTHq6lqcTvYPBKLNejBEbnUsQJtU=
|
||||
golang.org/x/crypto v0.0.0-20171231215028-0fcca4842a8d h1:GrqEEc3+MtHKTsZrdIGVoYDgLpbSRzW1EF+nLu0PcHE=
|
||||
golang.org/x/crypto v0.0.0-20171231215028-0fcca4842a8d/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
||||
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
||||
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc h1:a3CU5tJYVj92DY2LaA1kUkrsqD5/3mLDhx2NcNqyW+0=
|
||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d h1:1ZiEyfaQIg3Qh0EoqpwAakHVhecoE5wlSg5GjnafJGw=
|
||||
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5 h1:Q7tZBpemrlsc2I7IyODzhtallWRSm4Q0d09pL6XbQtU=
|
||||
golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190228165749-92fc7df08ae7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180926160741-c2ed4eda69e7/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181213200352-4d1cda033e06 h1:0oC8rFnE+74kEmuHZ46F6KHsMr5Gx2gUQPuNz28iQZM=
|
||||
golang.org/x/sys v0.0.0-20181213200352-4d1cda033e06/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.0.0-20171227012246-e19ae1496984 h1:ulYJn/BqO4fMRe1xAQzWjokgjsQLPpb21GltxXHI3fQ=
|
||||
golang.org/x/text v0.0.0-20171227012246-e19ae1496984/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/time v0.0.0-20170927054726-6dc17368e09b h1:3X+R0qq1+64izd8es+EttB6qcY+JDlVmAhpRXl7gpzU=
|
||||
golang.org/x/time v0.0.0-20170927054726-6dc17368e09b/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
gopkg.in/asn1-ber.v1 v1.0.0-20170511165959-379148ca0225 h1:JBwmEvLfCqgPcIq8MjVMQxsF3LVL4XG/HH0qiG0+IFY=
|
||||
gopkg.in/asn1-ber.v1 v1.0.0-20170511165959-379148ca0225/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY=
|
||||
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191224085550-c709ea063b76 h1:Dho5nD6R3PcW2SH1or8vS0dszDaXRxIw55lBX7XiE5g=
|
||||
golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
|
||||
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0 h1:qdOKuR/EIArgaWNjetjgTzgVTAZ+S/WXVrq9HW9zimw=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/ldap.v2 v2.5.1 h1:wiu0okdNfjlBzg6UWvd1Hn8Y+Ux17/u/4nlk4CQr6tU=
|
||||
gopkg.in/ldap.v2 v2.5.1/go.mod h1:oI0cpe/D7HRtBQl8aTg+ZmzFUAvu4lsv3eLXMLGFxWk=
|
||||
gopkg.in/yaml.v2 v2.0.0-20171116090243-287cf08546ab h1:yZ6iByf7GKeJ3gsd1Dr/xaj1DyJ//wxKX1Cdh8LhoAw=
|
||||
gopkg.in/yaml.v2 v2.0.0-20171116090243-287cf08546ab/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
|
||||
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
|
||||
|
||||
@@ -24,6 +24,27 @@ a:hover,a:focus,a.focus{
|
||||
border-width: 2px !important;
|
||||
}
|
||||
|
||||
|
||||
.nav-tabs > li.active > a,
|
||||
.nav-tabs > li.active > a:hover,
|
||||
.nav-tabs > li.active > a:focus {
|
||||
color: #3498db !important;
|
||||
background-color: #222222 !important;
|
||||
border: 1px solid #222222 !important;
|
||||
border-bottom-color: transparent !important;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.nav-tabs{
|
||||
border-bottom: 1px solid #333;
|
||||
}
|
||||
|
||||
.nav-tabs > li > a:hover,
|
||||
.nav-tabs > li > a:focus {
|
||||
background-color: #222222 !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.navbar-text, .dropdown>a, .dropdown-menu>li>a, .hidden-xs>a, .navbar-link {
|
||||
color: #aaa !important;
|
||||
}
|
||||
|
||||
@@ -28,6 +28,27 @@ a:hover,a:focus,a.focus{
|
||||
|
||||
}
|
||||
|
||||
.nav-tabs > li.active > a,
|
||||
.nav-tabs > li.active > a:hover,
|
||||
.nav-tabs > li.active > a:focus {
|
||||
color: #3498db !important;
|
||||
background-color: #424242 !important;
|
||||
border: 1px solid #424242 !important;
|
||||
border-bottom-color: transparent !important;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.nav-tabs{
|
||||
border-bottom: 1px solid #333;
|
||||
}
|
||||
|
||||
.nav-tabs > li > a:hover,
|
||||
.nav-tabs > li > a:focus {
|
||||
background-color: #424242 !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
|
||||
.navbar-text, .dropdown>a, .dropdown-menu>li>a, .hidden-xs>a, .navbar-link {
|
||||
color: #aaa !important;
|
||||
}
|
||||
|
||||
@@ -124,6 +124,23 @@ table.table-condensed td.no-overflow-ellipse {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
table.table-auto {
|
||||
table-layout: auto;
|
||||
}
|
||||
|
||||
table.table-auto th {
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
table.table-auto td {
|
||||
max-width: 0px;
|
||||
}
|
||||
|
||||
/* Wrap long file paths to prevent text overflow. See issue #6268. */
|
||||
.file-path {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.folder-advanced {
|
||||
padding: 1rem;
|
||||
margin-bottom: 15px;
|
||||
@@ -234,6 +251,14 @@ a.toggler:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Panel padding decrease
|
||||
*/
|
||||
|
||||
.panel-collapse .panel-body {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
/**
|
||||
* Progress bars with centered text
|
||||
*/
|
||||
@@ -336,6 +361,12 @@ ul.three-columns li, ul.two-columns li {
|
||||
* columns. */
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.two-columns {
|
||||
-webkit-column-count: 1;
|
||||
-moz-column-count: 1;
|
||||
column-count: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width:479px) {
|
||||
@@ -360,3 +391,32 @@ ul.three-columns li, ul.two-columns li {
|
||||
.fancytree-ext-table {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
@media (max-width: 419px) {
|
||||
/* the selectors are build to target only the content of folder and device
|
||||
panels as it would "destroy" e.g. out of sync or recent changes listings */
|
||||
div[id^='device-'].panel-collapse table,
|
||||
div[id^='folder-'].panel-collapse table,
|
||||
div[id^='device-'].panel-collapse tbody,
|
||||
div[id^='folder-'].panel-collapse tbody,
|
||||
div[id^='device-'].panel-collapse tr,
|
||||
div[id^='folder-'].panel-collapse tr {
|
||||
display: block;
|
||||
}
|
||||
div[id^='device-'].panel-collapse th,
|
||||
div[id^='folder-'].panel-collapse th,
|
||||
div[id^='device-'].panel-collapse td,
|
||||
div[id^='folder-'].panel-collapse td {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* all buttons, except panel headings, get bottom margin, as they won't fit
|
||||
beside each other anymore */
|
||||
.btn:not(.panel-heading),
|
||||
/* this "+"-selector is needed to override some bootstrap defaults */
|
||||
.btn:not(.panel-heading) + .btn:not(.panel-heading) {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,29 +7,5 @@
|
||||
|
||||
*/
|
||||
|
||||
.panel-progress {
|
||||
background: #3498db;
|
||||
}
|
||||
|
||||
.identicon rect {
|
||||
fill: #333;
|
||||
}
|
||||
|
||||
.panel-warning .identicon rect {
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
.li-column {
|
||||
background-color: rgb(236, 240, 241);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.panel-heading:hover, .panel-heading:focus {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.fancytree-ext-filter-hide tr.fancytree-submatch span.fancytree-title,
|
||||
.fancytree-ext-filter-hide span.fancytree-node.fancytree-submatch span.fancytree-title {
|
||||
color: black !important;
|
||||
font-weight: lighter !important;
|
||||
}
|
||||
@import "../../theme-assets/dark/assets/css/theme.css" screen and (prefers-color-scheme: dark);
|
||||
@import "../../theme-assets/light/assets/css/theme.css" (prefers-color-scheme: light), (prefers-color-scheme: no-preference);
|
||||
|
||||
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 3.9 KiB |
@@ -31,7 +31,9 @@
|
||||
"Are you sure you want to remove device {%name%}?": "Сигурни ли сте, че искате да премахнете устройството {{name}}?",
|
||||
"Are you sure you want to remove folder {%label%}?": "Сигурни ли сте, че искате да премахнете папката {{label}}?",
|
||||
"Are you sure you want to restore {%count%} files?": "Сигурни ли сте, че искате да възстановите файла {{count}}?",
|
||||
"Are you sure you want to upgrade?": "Are you sure you want to upgrade?",
|
||||
"Auto Accept": "Автоматично приемане",
|
||||
"Automatic Crash Reporting": "Automatic Crash Reporting",
|
||||
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Автоматичното обновяване вече предлага избор между стабилни версии и кандидат версии.",
|
||||
"Automatic upgrades": "Автоматично обновяване",
|
||||
"Automatic upgrades are always enabled for candidate releases.": "Automatic upgrades are always enabled for candidate releases.",
|
||||
@@ -48,6 +50,7 @@
|
||||
"Comment, when used at the start of a line": "Коментар, използван в началото на реда",
|
||||
"Compression": "Компресиране",
|
||||
"Configured": "Настроен",
|
||||
"Connected (Unused)": "Connected (Unused)",
|
||||
"Connection Error": "Грешка при свързването",
|
||||
"Connection Type": "Вид връзка",
|
||||
"Connections": "Връзки",
|
||||
@@ -58,11 +61,13 @@
|
||||
"Copyright © 2014-2017 the following Contributors:": "Всички правата запазени © 2014-2017. Сътрудници:",
|
||||
"Copyright © 2014-2019 the following Contributors:": "Copyright © 2014-2019 the following Contributors:",
|
||||
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Създаване на шаблони за игнориране, презаписване на съществуващ файл в {{path}}.",
|
||||
"Currently Shared With Devices": "Currently Shared With Devices",
|
||||
"Danger!": "Опасност!",
|
||||
"Debugging Facilities": "Дебъг функционалност",
|
||||
"Default Folder Path": "Път до папка по подразбиране",
|
||||
"Deleted": "Изтрито",
|
||||
"Deselect All": "Никое",
|
||||
"Deselect devices to stop sharing this folder with.": "Deselect devices to stop sharing this folder with.",
|
||||
"Device": "Устройство",
|
||||
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Устройство \"{{name}}\" ({{device}}) с адрес {{address}} желае да се свърже. Да бъде ли добавено?",
|
||||
"Device ID": "Идентификатор на устройство",
|
||||
@@ -71,12 +76,14 @@
|
||||
"Device rate limits": "Device rate limits",
|
||||
"Device that last modified the item": "Устройство, което последно промени обекта",
|
||||
"Devices": "Устройства",
|
||||
"Disable Crash Reporting": "Disable Crash Reporting",
|
||||
"Disabled": "Деактивирано",
|
||||
"Disabled periodic scanning and disabled watching for changes": "Периодичните сканирания и наблюденията за промяна са деактивирани.",
|
||||
"Disabled periodic scanning and enabled watching for changes": "Периодичните сканирания са деактивирани , а наблюденията за промяна са активирани.",
|
||||
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Периодичните сканирания са деактивирани и задаването на наблюдение за промени е неуспешно, ще опита пак след 1мин:",
|
||||
"Discard": "Discard",
|
||||
"Disconnected": "Не е свързано",
|
||||
"Disconnected (Unused)": "Disconnected (Unused)",
|
||||
"Discovered": "Открит",
|
||||
"Discovery": "Откриване",
|
||||
"Discovery Failures": "Грешка в откриването",
|
||||
@@ -92,6 +99,7 @@
|
||||
"Edit Folder": "Промяна на папката",
|
||||
"Editing": "Променяне",
|
||||
"Editing {%path%}.": "Променяне на {{path}}.",
|
||||
"Enable Crash Reporting": "Enable Crash Reporting",
|
||||
"Enable NAT traversal": "Разреши NAT traversal",
|
||||
"Enable Relaying": "Разреши препращане",
|
||||
"Enabled": "Активирано",
|
||||
@@ -100,6 +108,7 @@
|
||||
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Въведете адреси разделени със запетая (\"tcp://ip:port\", \"tcp://host:port\") или \"dynamic\", за автоматично откриване на наличните адреси.",
|
||||
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Въведете адреси разделени със запетая (\"tcp://ip:port\", \"tcp://host:port\") или \"dynamic\", за автоматично откриване на наличните адреси.",
|
||||
"Enter ignore patterns, one per line.": "Добавете шаблони за игнориране, по един на ред.",
|
||||
"Enter up to three octal digits.": "Enter up to three octal digits.",
|
||||
"Error": "Грешка",
|
||||
"External File Versioning": "Външно управление на версиите",
|
||||
"Failed Items": "Неуспешни",
|
||||
@@ -141,6 +150,7 @@
|
||||
"Global State": "Глобално състояние",
|
||||
"Help": "Помощ",
|
||||
"Home page": "Начална страница",
|
||||
"However, your current settings indicate you might not want it enabled. We have disabled automatic crash reporting for you.": "However, your current settings indicate you might not want it enabled. We have disabled automatic crash reporting for you.",
|
||||
"Ignore": "Игнорирай",
|
||||
"Ignore Patterns": "Шаблони за игнориране",
|
||||
"Ignore Permissions": "Игнорирай правата за достъп",
|
||||
@@ -153,6 +163,7 @@
|
||||
"Introducer": "Може да предлага други устройства",
|
||||
"Inversion of the given condition (i.e. do not exclude)": "Обратното на даденото условие (пр. не изключвай)",
|
||||
"Keep Versions": "Пази версии",
|
||||
"LDAP": "LDAP",
|
||||
"Largest First": " Първо най-големите",
|
||||
"Last File Received": "Последния получен файл",
|
||||
"Last Scan": "Последно сканирана",
|
||||
@@ -164,6 +175,7 @@
|
||||
"Listeners": "Синхронизиращи устройства",
|
||||
"Loading data...": "Зареждане на информация...",
|
||||
"Loading...": "Зареждане...",
|
||||
"Local Additions": "Local Additions",
|
||||
"Local Discovery": "Локално откриване",
|
||||
"Local State": "Локално състояние",
|
||||
"Local State (Total)": "Локално състояние (общо)",
|
||||
@@ -171,6 +183,7 @@
|
||||
"Log": "Доклад",
|
||||
"Log tailing paused. Click here to continue.": "Докладът е замразен. Натиснете, за да продължите.",
|
||||
"Log tailing paused. Scroll to bottom continue.": "Log tailing paused. Scroll to bottom continue.",
|
||||
"Log tailing paused. Scroll to the bottom to continue.": "Log tailing paused. Scroll to the bottom to continue.",
|
||||
"Logs": "Доклади",
|
||||
"Major Upgrade": "Основно Обновяване",
|
||||
"Mass actions": "Действия за всички",
|
||||
@@ -209,6 +222,7 @@
|
||||
"Pause": "Пауза",
|
||||
"Pause All": "Пауза на всички",
|
||||
"Paused": "На пауза",
|
||||
"Paused (Unused)": "Paused (Unused)",
|
||||
"Pending changes": "Pending changes",
|
||||
"Periodic scanning at given interval and disabled watching for changes": "Периодично сканиране, през определен интервал, без мониторинг за промени",
|
||||
"Periodic scanning at given interval and enabled watching for changes": "Периодично сканиране, през определен интервал, и мониторинг за промени",
|
||||
@@ -219,6 +233,7 @@
|
||||
"Please wait": "Моля изчакайте",
|
||||
"Prefix indicating that the file can be deleted if preventing directory removal": "Представка, която индикира, че файлът може да бъде изтрит ако пречи на премахването на папка",
|
||||
"Prefix indicating that the pattern should be matched without case sensitivity": "Представка, която индикира, че шаблона няма да прави разлика между главни/малки букви",
|
||||
"Preparing to Sync": "Preparing to Sync",
|
||||
"Preview": "Преглед",
|
||||
"Preview Usage Report": "Преглед на статистиката",
|
||||
"Quick guide to supported patterns": "Бърз наръчник към поддържаните шаблони",
|
||||
@@ -255,6 +270,7 @@
|
||||
"See external versioning help for supported templated command line parameters.": "Прегледайте външната документацията за поддържаните командни параметри. ",
|
||||
"Select All": "Всички",
|
||||
"Select a version": "Изберете версия",
|
||||
"Select additional devices to share this folder with.": "Select additional devices to share this folder with.",
|
||||
"Select latest version": "Избор на най-новата версия",
|
||||
"Select oldest version": "Избор на най-старата версия",
|
||||
"Select the devices to share this folder with.": "Изберете устройствата, с които да споделите папката.",
|
||||
@@ -295,8 +311,10 @@
|
||||
"Syncing": "Синхронизиране",
|
||||
"Syncthing has been shut down.": "Syncthing е спрян.",
|
||||
"Syncthing includes the following software or portions thereof:": "Syncthing уползотворява частично или изцяло следните софтуерни продукти:",
|
||||
"Syncthing is Free and Open Source Software licensed as MPL v2.0.": "Syncthing is Free and Open Source Software licensed as MPL v2.0.",
|
||||
"Syncthing is restarting.": "Syncthing се рестартира",
|
||||
"Syncthing is upgrading.": "Syncthing се обновява.",
|
||||
"Syncthing now supports automatically reporting crashes to the developers. This feature is enabled by default.": "Syncthing now supports automatically reporting crashes to the developers. This feature is enabled by default.",
|
||||
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Изглежда, че Syncthing не е включен, или има проблем с връзката с Интернет. Повторен опит...",
|
||||
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing има проблем при обработването на заявката. Моля, презаредете браузъра или рестартирайте Syncthing ако проблемът продължи.",
|
||||
"Take me back": "Take me back",
|
||||
@@ -325,6 +343,7 @@
|
||||
"The path cannot be blank.": "Пътят не може да бъде празен.",
|
||||
"The rate limit must be a non-negative number (0: no limit)": "Ограничението на скоростта трябва да бъде положително число (0: неограничено)",
|
||||
"The rescan interval must be a non-negative number of seconds.": "Интервала на сканиране трябва да бъде не отрицателно число в секунди.",
|
||||
"There are no devices to share this folder with.": "There are no devices to share this folder with.",
|
||||
"They are retried automatically and will be synced when the error is resolved.": "Ще бъдат спрени и автоматично синхронизирани, когато грешката бъде оправена.",
|
||||
"This Device": "Вашето устройство",
|
||||
"This can easily give hackers access to read and change any files on your computer.": "Така се предоставя изключително лесен достъп (четене, редактиране и изтриване) до всеки файл, на компютъра Ви.",
|
||||
@@ -334,12 +353,14 @@
|
||||
"Time the item was last modified": "Часът на последна промяна на елемента",
|
||||
"Trash Can File Versioning": "Само на файловете в кошчето",
|
||||
"Type": "Тип",
|
||||
"UNIX Permissions": "UNIX Permissions",
|
||||
"Unavailable": "Не е на разположение",
|
||||
"Unavailable/Disabled by administrator or maintainer": "Не е на разположение/Деактивриан от администраторът или поддръжника",
|
||||
"Undecided (will prompt)": "Неизбрано (ще попита)",
|
||||
"Unignore": "Unignore",
|
||||
"Unknown": "Неясно",
|
||||
"Unshared": "Несподелена",
|
||||
"Unshared Devices": "Unshared Devices",
|
||||
"Unused": "Неизползвано",
|
||||
"Up to Date": "Синхронизирано",
|
||||
"Updated": "Обновено",
|
||||
@@ -357,6 +378,8 @@
|
||||
"Versions": "Версии",
|
||||
"Versions Path": "Път до версиите",
|
||||
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Версиите биват изтривани автоматично, когато са по-стари от максималната възраст или надминават броя версии разрешени в даден интервал.",
|
||||
"Waiting to Scan": "Waiting to Scan",
|
||||
"Waiting to Sync": "Waiting to Sync",
|
||||
"Waiting to scan": "Waiting to scan",
|
||||
"Warning, this path is a parent directory of an existing folder \"{%otherFolder%}\".": "Предупреждение, този път е по-горна директория на съществуващата папка \"{{otherFolder}}\".",
|
||||
"Warning, this path is a parent directory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Предупреждение, този път е по-горна директория на съществуващата папка \"{{otherFolderLabel}}\" ({{otherFolder}}).",
|
||||
|
||||
@@ -31,10 +31,12 @@
|
||||
"Are you sure you want to remove device {%name%}?": "Estàs segur de que vols eliminar el dispositiu {{name}}?",
|
||||
"Are you sure you want to remove folder {%label%}?": "Estàs segur de que vols eliminar la carpeta {{label}}?",
|
||||
"Are you sure you want to restore {%count%} files?": "Estàs segur de que vols restaurar {{count}} fitxers?",
|
||||
"Are you sure you want to upgrade?": "Are you sure you want to upgrade?",
|
||||
"Auto Accept": "Auto Acceptar",
|
||||
"Automatic Crash Reporting": "Automatic Crash Reporting",
|
||||
"Automatic upgrade now offers the choice between stable releases and release candidates.": "L'actualització automàtica ara ofereix l'elecció entre les versions estables i les versions candidates.",
|
||||
"Automatic upgrades": "Actualitzacions automàtiques",
|
||||
"Automatic upgrades are always enabled for candidate releases.": "Automatic upgrades are always enabled for candidate releases.",
|
||||
"Automatic upgrades are always enabled for candidate releases.": "Les actualitzacions automàtiques sempre estàn activades per a les versions candidates.",
|
||||
"Automatically create or share folders that this device advertises at the default path.": "Crear o compartir automàticament les carpetes que aquest dispositiu anuncia en la ruta per defecte.",
|
||||
"Available debug logging facilities:": "Hi han disponibles les següents utilitats per a depurar el registre:",
|
||||
"Be careful!": "Tin precaució!",
|
||||
@@ -48,6 +50,7 @@
|
||||
"Comment, when used at the start of a line": "Comentar, quant s'utilitza al principi d'una línia",
|
||||
"Compression": "Compresió",
|
||||
"Configured": "Configurat",
|
||||
"Connected (Unused)": "Connected (Unused)",
|
||||
"Connection Error": "Error de connexió",
|
||||
"Connection Type": "Tipus de connexió",
|
||||
"Connections": "Connexions",
|
||||
@@ -56,13 +59,15 @@
|
||||
"Copied from original": "Copiat de l'original",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 els següents Col·laboradors:",
|
||||
"Copyright © 2014-2017 the following Contributors:": "Copyright © 2014-2017 els següents Col·laboradors:",
|
||||
"Copyright © 2014-2019 the following Contributors:": "Copyright © 2014-2019 the following Contributors:",
|
||||
"Copyright © 2014-2019 the following Contributors:": "Copyright © 2014-2019 els següents Col·laboradors:",
|
||||
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Creant patrons a ignorar, sobreescriguent un fitxer que ja existeix a {{path}}.",
|
||||
"Currently Shared With Devices": "Currently Shared With Devices",
|
||||
"Danger!": "Perill!",
|
||||
"Debugging Facilities": "Utilitats de Depuració",
|
||||
"Default Folder Path": "Carpeta de la Ruta per Defecte",
|
||||
"Deleted": "Esborrat",
|
||||
"Deselect All": "Deselect All",
|
||||
"Deselect All": "Anul·lar tota la selecció",
|
||||
"Deselect devices to stop sharing this folder with.": "Deselect devices to stop sharing this folder with.",
|
||||
"Device": "Dispositiu",
|
||||
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Dispositiu \"{{name}}\" ({{device}} a l'adreça {{address}}) vol connectar. Afegir nou dispositiu?",
|
||||
"Device ID": "ID del dispositiu",
|
||||
@@ -71,12 +76,14 @@
|
||||
"Device rate limits": "Límits de la tasa del dispositiu",
|
||||
"Device that last modified the item": "El dispositiu que va modificar el item per última vegada",
|
||||
"Devices": "Dispositius",
|
||||
"Disable Crash Reporting": "Disable Crash Reporting",
|
||||
"Disabled": "Desactivat",
|
||||
"Disabled periodic scanning and disabled watching for changes": "Desactivat l'escaneig periòdic i el rastreig continu de canvis",
|
||||
"Disabled periodic scanning and enabled watching for changes": "Desactivat l'escaneig periòdic i activat el rastreig continu de canvis",
|
||||
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Desactivat l'escaneig periòdic i errada al rastreig continu de canvis, es reintentarà cada 1 minut:",
|
||||
"Discard": "Discard",
|
||||
"Discard": "Descartar",
|
||||
"Disconnected": "Desconnectat",
|
||||
"Disconnected (Unused)": "Disconnected (Unused)",
|
||||
"Discovered": "Descobert",
|
||||
"Discovery": "Descobriment",
|
||||
"Discovery Failures": "Fallades al Descobriment",
|
||||
@@ -92,14 +99,16 @@
|
||||
"Edit Folder": "Editar Carpeta",
|
||||
"Editing": "Editant",
|
||||
"Editing {%path%}.": "Editant {{path}}.",
|
||||
"Enable Crash Reporting": "Enable Crash Reporting",
|
||||
"Enable NAT traversal": "Permetre NAT transversal",
|
||||
"Enable Relaying": "Permetre Transmissions",
|
||||
"Enabled": "Activat",
|
||||
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Introdueix un nombre no negatiu (per exemple, \"2.35\") i selecciona una unitat. Els percentatges són com a part del tamany total del disc.",
|
||||
"Enter a non-privileged port number (1024 - 65535).": "Introdueix un nombre de port sense privilegis (1024-65535).",
|
||||
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Introdueix adreces separades per coma (\"tcp://ip:port\", \"tcp://host:port\") o \"dynamic\" per a realitzar el descobriment automàtic de l'adreça.",
|
||||
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.",
|
||||
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Introduïr adreces separades per coma (\"tcp://ip:port\", \"tcp://host:port\") o dinàmiques per al descobriment automàtic de l'adreça.",
|
||||
"Enter ignore patterns, one per line.": "Introduïr patrons a ignorar, un per línia.",
|
||||
"Enter up to three octal digits.": "Enter up to three octal digits.",
|
||||
"Error": "Error",
|
||||
"External File Versioning": "Versionat extern de fitxers",
|
||||
"Failed Items": "Objectes fallits",
|
||||
@@ -141,18 +150,20 @@
|
||||
"Global State": "Estat global",
|
||||
"Help": "Ajuda",
|
||||
"Home page": "Pàgina inicial",
|
||||
"However, your current settings indicate you might not want it enabled. We have disabled automatic crash reporting for you.": "However, your current settings indicate you might not want it enabled. We have disabled automatic crash reporting for you.",
|
||||
"Ignore": "Ignorar",
|
||||
"Ignore Patterns": "Patrons a ignorar",
|
||||
"Ignore Permissions": "Permisos a ignorar",
|
||||
"Ignored Devices": "Ignored Devices",
|
||||
"Ignored Folders": "Ignored Folders",
|
||||
"Ignored at": "Ignored at",
|
||||
"Ignored Devices": "Dispositius Ignorats",
|
||||
"Ignored Folders": "Carpetes Ignorades",
|
||||
"Ignored at": "Ignorat en",
|
||||
"Incoming Rate Limit (KiB/s)": "Límit de descàrrega (KiB/s)",
|
||||
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "La configuración incorrecta pot danyar el contingut de la teua carpeta i deixar Syncthing inoperatiu.",
|
||||
"Introduced By": "Introduït Per",
|
||||
"Introducer": "Presentador",
|
||||
"Inversion of the given condition (i.e. do not exclude)": "Inversió de la condició donada (per exemple no excloure)",
|
||||
"Keep Versions": "Mantindre versions",
|
||||
"LDAP": "LDAP",
|
||||
"Largest First": "El més gran primer",
|
||||
"Last File Received": "Darrer fitxer rebut",
|
||||
"Last Scan": "Últim escaneig",
|
||||
@@ -160,17 +171,19 @@
|
||||
"Later": "Més tard",
|
||||
"Latest Change": "Últim Canvi",
|
||||
"Learn more": "Saber més",
|
||||
"Limit": "Limit",
|
||||
"Limit": "Límit",
|
||||
"Listeners": "Escoltants",
|
||||
"Loading data...": "Carregant dades...",
|
||||
"Loading...": "Carregant...",
|
||||
"Local Additions": "Local Additions",
|
||||
"Local Discovery": "Descobriment local",
|
||||
"Local State": "Estat local",
|
||||
"Local State (Total)": "Estat Local (Total)",
|
||||
"Locally Changed Items": "Locally Changed Items",
|
||||
"Locally Changed Items": "Dispositius Canviats Localment",
|
||||
"Log": "Registre",
|
||||
"Log tailing paused. Click here to continue.": "Pausada l'adició de dades al registre. Polsa ací per continuar.",
|
||||
"Log tailing paused. Scroll to bottom continue.": "Pausat el seguiment del registre. Es continua fins al final.",
|
||||
"Log tailing paused. Scroll to the bottom to continue.": "Pausada l'anotació al registre. Baixeu fins al final per continuar.",
|
||||
"Logs": "Registres",
|
||||
"Major Upgrade": "Actualització important",
|
||||
"Mass actions": "Accions en masa",
|
||||
@@ -209,7 +222,8 @@
|
||||
"Pause": "Pausa",
|
||||
"Pause All": "Pausa Tot",
|
||||
"Paused": "Pausat",
|
||||
"Pending changes": "Pending changes",
|
||||
"Paused (Unused)": "Paused (Unused)",
|
||||
"Pending changes": "Canvis pendents",
|
||||
"Periodic scanning at given interval and disabled watching for changes": "Escaneig periòdic a l'interval determinat i desactivat el rastreig continu de canvis",
|
||||
"Periodic scanning at given interval and enabled watching for changes": "Escaneig periòdic a l'interval determinat i activat el rastreig continu de canvis",
|
||||
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Escaneig periòdic a l'interval determinat i errada al activar el rastreig continu de canvis, reintentant cada 1 minut:",
|
||||
@@ -219,12 +233,13 @@
|
||||
"Please wait": "Per favor, espere",
|
||||
"Prefix indicating that the file can be deleted if preventing directory removal": "Prefix que indica que el fitxer pot ser eliminat encara que estiga restringida l'eliminació del directori",
|
||||
"Prefix indicating that the pattern should be matched without case sensitivity": "Prefix que indica que el patró deu coincidir sense tindre en compte les majúscules",
|
||||
"Preparing to Sync": "Preparing to Sync",
|
||||
"Preview": "Vista prèvia",
|
||||
"Preview Usage Report": "Informe d'ús de vista prèvia",
|
||||
"Quick guide to supported patterns": "Guía ràpida de patrons suportats",
|
||||
"RAM Utilization": "Utilització de la RAM",
|
||||
"Random": "Aleatori",
|
||||
"Receive Only": "Només recivir",
|
||||
"Receive Only": "Només rebre",
|
||||
"Recent Changes": "Canvis Recents",
|
||||
"Reduced by ignore patterns": "Reduït ignorant patrons",
|
||||
"Release Notes": "Notes de la versió",
|
||||
@@ -253,8 +268,9 @@
|
||||
"Scanning": "Rastrejant",
|
||||
"See external versioner help for supported templated command line parameters.": "Consulta l'ajuda externa sobre versions per a conéixer els paràmetres de la plantilla de la línia de comandaments.",
|
||||
"See external versioning help for supported templated command line parameters.": "Consulta l'ajuda externa sobre versions per a conéixer els paràmetres de la plantilla de la línia de comandaments.",
|
||||
"Select All": "Select All",
|
||||
"Select All": "Sel·leccionar Tot",
|
||||
"Select a version": "Seleccionar una versió",
|
||||
"Select additional devices to share this folder with.": "Select additional devices to share this folder with.",
|
||||
"Select latest version": "Seleccionar l'última versió",
|
||||
"Select oldest version": "Seleccionar la versió més antiga",
|
||||
"Select the devices to share this folder with.": "Selecciona els dispositius amb els que compartir aquesta carpeta.",
|
||||
@@ -290,17 +306,19 @@
|
||||
"Statistics": "Estadístiques",
|
||||
"Stopped": "Parat",
|
||||
"Support": "Suport",
|
||||
"Support Bundle": "Support Bundle",
|
||||
"Support Bundle": "Lot de Suport",
|
||||
"Sync Protocol Listen Addresses": "Direccions d'escolta del protocol de sincronització",
|
||||
"Syncing": "Sincronitzant",
|
||||
"Syncthing has been shut down.": "Syncthing s'ha apagat",
|
||||
"Syncthing includes the following software or portions thereof:": "Syncthing inclou el següent software o parts d'ell:",
|
||||
"Syncthing is Free and Open Source Software licensed as MPL v2.0.": "Syncthing és Software Gratuït i Open Source llicenciat com MPL v2.0.",
|
||||
"Syncthing is restarting.": "Syncthing està reiniciant.",
|
||||
"Syncthing is upgrading.": "Syncthing està actualitzant-se.",
|
||||
"Syncthing now supports automatically reporting crashes to the developers. This feature is enabled by default.": "Syncthing now supports automatically reporting crashes to the developers. This feature is enabled by default.",
|
||||
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing pareix apagat o hi ha un problema amb la connexió a Internet. Tornant a intentar...",
|
||||
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing pareix que té un problema processant la seua sol·licitud. Per favor, refresque la pàgina o reinicie Syncthing si el problema persistix.",
|
||||
"Take me back": "Take me back",
|
||||
"The GUI address is overridden by startup options. Changes here will not take effect while the override is in place.": "The GUI address is overridden by startup options. Changes here will not take effect while the override is in place.",
|
||||
"Take me back": "Porta'm enrere",
|
||||
"The GUI address is overridden by startup options. Changes here will not take effect while the override is in place.": "L'adreça del GUI és sobreescrita per les opcions d'inici. Els canvis ací no surtiràn efecte mentre la sobreescritura estiga en marxa.",
|
||||
"The Syncthing admin interface is configured to allow remote access without a password.": "L'interfície d'administració de Syncthing està configurat per a permetre l'accés remot sense una contrasenya.",
|
||||
"The aggregated statistics are publicly available at the URL below.": "Les estadístiques agregades estàn disponibles en la URL que figura a continuació.",
|
||||
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "La configuració ha sigut gravada però no activada. Syncthing deu reiniciar per tal d'activar la nova configuració.",
|
||||
@@ -314,7 +332,7 @@
|
||||
"The folder path cannot be blank.": "La ruta de la carpeta no pot estar buida.",
|
||||
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "S'utilitzen els següents intervals: per a la primera hora es guarda una versió cada 30 segons, per al primer dia es guarda una versió cada hora, per als primers 30 dies es guarda una versió diaria, fins l'edat màxima es guarda una versió cada setmana.",
|
||||
"The following items could not be synchronized.": "Els següents objectes no s'han pogut sincronitzar.",
|
||||
"The following items were changed locally.": "The following items were changed locally.",
|
||||
"The following items were changed locally.": "Els següents ítems es canviaren localment.",
|
||||
"The maximum age must be a number and cannot be blank.": "L'edat màxima deu ser un nombre i no pot estar buida.",
|
||||
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "El temps màxim per a guardar una versió (en dies, ficar 0 per a guardar les versions per a sempre).",
|
||||
"The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).": "El porcentatge d'espai mínim lliure en el disc deu ser un nombre no negatiu entre 0 i 100 (ambdós inclosos).",
|
||||
@@ -325,6 +343,7 @@
|
||||
"The path cannot be blank.": "La ruta no pot estar buida.",
|
||||
"The rate limit must be a non-negative number (0: no limit)": "El llímit del ritme deu ser un nombre no negatiu (0: sense llímit)",
|
||||
"The rescan interval must be a non-negative number of seconds.": "L'interval de reescaneig deu ser un nombre positiu de segons.",
|
||||
"There are no devices to share this folder with.": "There are no devices to share this folder with.",
|
||||
"They are retried automatically and will be synced when the error is resolved.": "Es reintenta automàticament i es sincronitzaràn quant el resolga l'error.",
|
||||
"This Device": "Aquest Dispositiu",
|
||||
"This can easily give hackers access to read and change any files on your computer.": "Açò pot donar accés fàcilment als hackers per a llegir i canviar qualsevol fitxer al teu ordinador.",
|
||||
@@ -334,12 +353,14 @@
|
||||
"Time the item was last modified": "Hora a la que l'ítem fou modificat per última vegada",
|
||||
"Trash Can File Versioning": "Versionat d'arxius de la paperera",
|
||||
"Type": "Tipus",
|
||||
"UNIX Permissions": "UNIX Permissions",
|
||||
"Unavailable": "No disponible",
|
||||
"Unavailable/Disabled by administrator or maintainer": "No disponible/Desactivar per l'administrador o mantenedor",
|
||||
"Undecided (will prompt)": "No decidit (es preguntarà)",
|
||||
"Unignore": "Unignore",
|
||||
"Unignore": "Designorar",
|
||||
"Unknown": "Desconegut",
|
||||
"Unshared": "No compartit",
|
||||
"Unshared Devices": "Unshared Devices",
|
||||
"Unused": "No utilitzat",
|
||||
"Up to Date": "Actualitzat",
|
||||
"Updated": "Actualitzat",
|
||||
@@ -350,14 +371,16 @@
|
||||
"Uptime": "Temps de funcionament",
|
||||
"Usage reporting is always enabled for candidate releases.": "Els informes d'ús sempre estan activats per a les versions candidates.",
|
||||
"Use HTTPS for GUI": "Utilitzar HTTPS per a l'Interfície Gràfica d'Usuari (GUI)",
|
||||
"Use notifications from the filesystem to detect changed items.": "Use notifications from the filesystem to detect changed items.",
|
||||
"Variable Size Blocks": "Variable Size Blocks",
|
||||
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "Variable size blocks (also \"large blocks\") are more efficient for large files.",
|
||||
"Use notifications from the filesystem to detect changed items.": "Usar notificacions del sistema de fitxers per a detectar els ítems canviats.",
|
||||
"Variable Size Blocks": "Blocs de Tamany Variable",
|
||||
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "Els blocs de tamany variable (també coneguts com \"blocs grans\") són més eficients per als fitxers grans.",
|
||||
"Version": "Versió",
|
||||
"Versions": "Versions",
|
||||
"Versions Path": "Ruta de les versions",
|
||||
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Les versions s'esborren automàticament si són més antigues que l'edat màxima o excedixen el nombre de fitxer permesos en un interval.",
|
||||
"Waiting to scan": "Waiting to scan",
|
||||
"Waiting to Scan": "Waiting to Scan",
|
||||
"Waiting to Sync": "Waiting to Sync",
|
||||
"Waiting to scan": "Esperant per a escanetjar",
|
||||
"Warning, this path is a parent directory of an existing folder \"{%otherFolder%}\".": "Perill! Esta ruta és un directori pare d'una carpeta ja existent \"{{otherFolder}}\".",
|
||||
"Warning, this path is a parent directory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Perill! Esta ruta és un directori pare d'una carpeta existent \"{{otherFolderLabel}}\" ({{otherFolder}}).",
|
||||
"Warning, this path is a subdirectory of an existing folder \"{%otherFolder%}\".": "Perill! Esta ruta és un subdirectori d'una carpeta que ja existeix nomenada \"{{otherFolder}}\".",
|
||||
@@ -365,16 +388,16 @@
|
||||
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "AVÍS: Si estàs utilitzant un observador extern com {{syncthingInotify}}, deus assegurar-te de que està desactivat.",
|
||||
"Watch for Changes": "Vigilar els Canvis",
|
||||
"Watching for Changes": "Vigilant els Canvis",
|
||||
"Watching for changes discovers most changes without periodic scanning.": "Watching for changes discovers most changes without periodic scanning.",
|
||||
"Watching for changes discovers most changes without periodic scanning.": "Vigil·lar els canvis detecta més canvis sense escanetjar periòdicament.",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "Quant s'afig un nou dispositiu, hi ha que tindre en compte que aquest dispositiu deu ser afegit també en l'altre costat.",
|
||||
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Quant s'afig una nova carpeta, hi ha que tindre en compte que l'ID de la carpeta s'utilitza per a juntar les carpetes entre dispositius. Són sensibles a les majúscules i deuen coincidir exactament entre tots els dispositius.",
|
||||
"Yes": "Sí",
|
||||
"You can also select one of these nearby devices:": "Pots seleccionar també un d'aquestos dispositius propers:",
|
||||
"You can change your choice at any time in the Settings dialog.": "Pots canviar la teua elecció en qualsevol moment en el dialog Ajustos",
|
||||
"You can read more about the two release channels at the link below.": "Pots llegir més sobre els dos canals de versions en l'enllaç de baix.",
|
||||
"You have no ignored devices.": "You have no ignored devices.",
|
||||
"You have no ignored folders.": "You have no ignored folders.",
|
||||
"You have unsaved changes. Do you really want to discard them?": "You have unsaved changes. Do you really want to discard them?",
|
||||
"You have no ignored devices.": "No tens dispositius ignorats.",
|
||||
"You have no ignored folders.": "No tens carpetes ignorades.",
|
||||
"You have unsaved changes. Do you really want to discard them?": "Tens canvis sense guardar. Realment vols descartar-los?",
|
||||
"You must keep at least one version.": "Es deu mantindre al menys una versió.",
|
||||
"days": "dies",
|
||||
"directories": "directoris",
|
||||
|
||||