Compare commits
569 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b09b9c290 | ||
|
|
c42865b3d9 | ||
|
|
836f021a87 | ||
|
|
26b049b361 | ||
|
|
e75627365d | ||
|
|
ae0334c930 | ||
|
|
920ad8b54b | ||
|
|
ac4a4f83fc | ||
|
|
a4652c2d32 | ||
|
|
c40d4e075e | ||
|
|
95967136d3 | ||
|
|
576c1ee0c5 | ||
|
|
5d4032edf4 | ||
|
|
ff3f84f9fd | ||
|
|
2a19b68b9a | ||
|
|
ed6c010aff | ||
|
|
783fb38e41 | ||
|
|
fcab4ae3c6 | ||
|
|
a69c456965 | ||
|
|
0e6db2f1c5 | ||
|
|
7aab18d197 | ||
|
|
d6b39e66d1 | ||
|
|
3f5c72d898 | ||
|
|
691ade794b | ||
|
|
1693c4ed8a | ||
|
|
ae9b3279c3 | ||
|
|
04956e45c7 | ||
|
|
027664af7b | ||
|
|
f8d5f76bdf | ||
|
|
114f9be47f | ||
|
|
c73369e11c | ||
|
|
5603e25542 | ||
|
|
0d8cb66d43 | ||
|
|
e7e4cfca4c | ||
|
|
fd23f1a29b | ||
|
|
57481e3dd7 | ||
|
|
53952b143f | ||
|
|
e7b0f4c6be | ||
|
|
ea143c0c9a | ||
|
|
a951110461 | ||
|
|
7a8f5f53d5 | ||
|
|
1b585159d1 | ||
|
|
f3692cd47f | ||
|
|
15800fd4ff | ||
|
|
9fb085f361 | ||
|
|
1e3f878470 | ||
|
|
bcf9bfa5d3 | ||
|
|
56bdc1f0ae | ||
|
|
9de6428585 | ||
|
|
55e609cbf4 | ||
|
|
f7319eb7a5 | ||
|
|
2cff64fd80 | ||
|
|
fdc0db07e0 | ||
|
|
779cb18590 | ||
|
|
a1a1128d6d | ||
|
|
7a50daca7c | ||
|
|
2ad23a09e8 | ||
|
|
7e2ea4d74d | ||
|
|
4bdce76041 | ||
|
|
6f5a78c22b | ||
|
|
d6d529278e | ||
|
|
a430d15ac5 | ||
|
|
b0c2d5f299 | ||
|
|
b32c01c11a | ||
|
|
05f42f0cb8 | ||
|
|
3efe3a524a | ||
|
|
8a788dfca5 | ||
|
|
d5f1589ea1 | ||
|
|
c40c8413b5 | ||
|
|
ae074dfb2b | ||
|
|
2c8a8f6cd5 | ||
|
|
4c7a09c228 | ||
|
|
ab39edc692 | ||
|
|
6132cd9df2 | ||
|
|
7d824a5179 | ||
|
|
46738b2934 | ||
|
|
ca82fcb48e | ||
|
|
cfa3bb3b64 | ||
|
|
b9a9319cb4 | ||
|
|
77e4317135 | ||
|
|
b10d97e53a | ||
|
|
648a4c04d7 | ||
|
|
3ca674dca7 | ||
|
|
fa97fd496e | ||
|
|
c76a7ee8da | ||
|
|
80f6b78332 | ||
|
|
8dc54a7c44 | ||
|
|
8f080c537b | ||
|
|
427cf86f44 | ||
|
|
2d244c08e7 | ||
|
|
82c0f523aa | ||
|
|
c07a0b0ada | ||
|
|
e4c306c0ee | ||
|
|
6ffb94f819 | ||
|
|
142238e8b7 | ||
|
|
678e23c7d6 | ||
|
|
0abcebe1d8 | ||
|
|
f398af1169 | ||
|
|
afbea415e3 | ||
|
|
225bd5d25b | ||
|
|
3651cc6161 | ||
|
|
dc674014ff | ||
|
|
0e0e03949d | ||
|
|
f5bf5c236a | ||
|
|
94480ecabb | ||
|
|
31ef9b1d45 | ||
|
|
bf76132fd4 | ||
|
|
8cc2983318 | ||
|
|
caeca18ed7 | ||
|
|
50febb41ff | ||
|
|
79293e067c | ||
|
|
f45be80591 | ||
|
|
d405ba8dca | ||
|
|
ca975b1c01 | ||
|
|
e0e3ca3832 | ||
|
|
e7c952cbf7 | ||
|
|
85ad2dd39a | ||
|
|
0c4f0ec17b | ||
|
|
5ad4136955 | ||
|
|
a432de95a9 | ||
|
|
1d25a0e18c | ||
|
|
29fd95685f | ||
|
|
62a6016882 | ||
|
|
18a4a79763 | ||
|
|
56ea722f93 | ||
|
|
d2ab974933 | ||
|
|
37d7a8b5fe | ||
|
|
e4dcadd825 | ||
|
|
fee99e9fe3 | ||
|
|
8ac4826126 | ||
|
|
7deb12e06d | ||
|
|
d6e218141b | ||
|
|
f44121b044 | ||
|
|
5d8d92462d | ||
|
|
985cf7b7dd | ||
|
|
dcbc10fd57 | ||
|
|
79f243e98d | ||
|
|
cf95692b93 | ||
|
|
d8e008606f | ||
|
|
3cd26a9f61 | ||
|
|
5d74320ee7 | ||
|
|
f9aadeef1c | ||
|
|
625de1c834 | ||
|
|
1c0a3ed1a4 | ||
|
|
03ba9169f4 | ||
|
|
c22e0e9db7 | ||
|
|
6bcbaf085d | ||
|
|
9a1d9c5d74 | ||
|
|
59a3a072e0 | ||
|
|
9f001bbc06 | ||
|
|
b8356b60a6 | ||
|
|
e2e218c74b | ||
|
|
3bf23cbae5 | ||
|
|
da562d8206 | ||
|
|
81bf83db13 | ||
|
|
7a25dcd130 | ||
|
|
877c7e1a9f | ||
|
|
77b2512745 | ||
|
|
749b73ef15 | ||
|
|
e499eca12c | ||
|
|
80f25c34e5 | ||
|
|
61677fbce2 | ||
|
|
dc6171185e | ||
|
|
f7e4331e93 | ||
|
|
1340511b64 | ||
|
|
c3078f84e8 | ||
|
|
9f65157a0d | ||
|
|
89166cdabf | ||
|
|
b872973e8b | ||
|
|
2000e72357 | ||
|
|
836e53642d | ||
|
|
af3f882bb8 | ||
|
|
2ab05b9350 | ||
|
|
1022eb8a6e | ||
|
|
15fe62de32 | ||
|
|
83d87f83f9 | ||
|
|
76a0c1f6c4 | ||
|
|
a1588302a7 | ||
|
|
91ce790b6b | ||
|
|
5d3982c2d2 | ||
|
|
2cf7f7b268 | ||
|
|
8645d978ba | ||
|
|
cc18b158f4 | ||
|
|
0730b6db6e | ||
|
|
3d2a360401 | ||
|
|
0c60dab384 | ||
|
|
f5f0ad7f28 | ||
|
|
f807f756eb | ||
|
|
11af6c10f1 | ||
|
|
40342619e7 | ||
|
|
19bf62c21f | ||
|
|
2ea00d149f | ||
|
|
cc677bde93 | ||
|
|
6627cda96c | ||
|
|
cade2732b0 | ||
|
|
541a4a3271 | ||
|
|
0eccd068e5 | ||
|
|
87f4b9e422 | ||
|
|
bcaf2e42fd | ||
|
|
d39201f9b3 | ||
|
|
8ac2a816c3 | ||
|
|
344f1bf9ee | ||
|
|
f0a006fc43 | ||
|
|
145da0b21d | ||
|
|
094de89a3e | ||
|
|
65ace12def | ||
|
|
9afe455635 | ||
|
|
45ce422a89 | ||
|
|
4a0738cd49 | ||
|
|
6b6caa435f | ||
|
|
f9cb71027c | ||
|
|
82ac568ee3 | ||
|
|
61afdce788 | ||
|
|
119cac5a67 | ||
|
|
c6fedd9214 | ||
|
|
da525e039d | ||
|
|
29d92fd307 | ||
|
|
3863cc439e | ||
|
|
b1cfc03fc5 | ||
|
|
f706071048 | ||
|
|
501ae2741b | ||
|
|
5b75635386 | ||
|
|
2901db3cf3 | ||
|
|
6c2a3e36b5 | ||
|
|
8b125e6e95 | ||
|
|
e1cc14e055 | ||
|
|
d6659552df | ||
|
|
67001fa958 | ||
|
|
ffeeae91ab | ||
|
|
04fad1b781 | ||
|
|
dcaf952986 | ||
|
|
ca3b9e892d | ||
|
|
9f12ffc069 | ||
|
|
0d6800a515 | ||
|
|
b3d8b78205 | ||
|
|
ee82a85543 | ||
|
|
7907146aaf | ||
|
|
1a677ce4f7 | ||
|
|
f1a6594474 | ||
|
|
f1a82d9d9c | ||
|
|
8b52af0d03 | ||
|
|
dbf5c569ea | ||
|
|
06a2d79cb4 | ||
|
|
eb6238ee52 | ||
|
|
f41fec57ed | ||
|
|
c348343b22 | ||
|
|
b69dcb62e3 | ||
|
|
e4a260f148 | ||
|
|
614eb10d67 | ||
|
|
0bfd58a3f5 | ||
|
|
ff56857fc8 | ||
|
|
8d258feff7 | ||
|
|
96ee1bbfb2 | ||
|
|
abaf688ad8 | ||
|
|
bec8ff27ae | ||
|
|
7191d4e911 | ||
|
|
6f59bc3037 | ||
|
|
5c2286f4e8 | ||
|
|
9218c7ef19 | ||
|
|
3d8e61900b | ||
|
|
105d81c018 | ||
|
|
d4ca5cf257 | ||
|
|
05018ec971 | ||
|
|
538bc72c3c | ||
|
|
0027a76c49 | ||
|
|
a0cb6fabfd | ||
|
|
9e5400f52d | ||
|
|
7a1d0ff3ec | ||
|
|
d9fbecaa01 | ||
|
|
ecdf9396a5 | ||
|
|
df51aa40f4 | ||
|
|
996942af47 | ||
|
|
f17a4eae3e | ||
|
|
c515603d2f | ||
|
|
14c3b6429b | ||
|
|
bd110b960b | ||
|
|
3ad4319163 | ||
|
|
97340ec70b | ||
|
|
5140a7b010 | ||
|
|
bd74879303 | ||
|
|
da30ae287f | ||
|
|
6a545aa088 | ||
|
|
384a4bae3a | ||
|
|
e65f924cd7 | ||
|
|
9105b33e9f | ||
|
|
cc2235653a | ||
|
|
a00de75f61 | ||
|
|
836412b032 | ||
|
|
ba16270059 | ||
|
|
2c73672e64 | ||
|
|
74b7c81195 | ||
|
|
a021ff6b22 | ||
|
|
6d1a90cac0 | ||
|
|
1f47c16102 | ||
|
|
abbcf60aed | ||
|
|
f339c882d7 | ||
|
|
982536e9e8 | ||
|
|
c17b351efb | ||
|
|
130bebf2c6 | ||
|
|
83c4ad2e59 | ||
|
|
0bcc6ed597 | ||
|
|
c61f854edc | ||
|
|
2998cf5e48 | ||
|
|
c777f8d97d | ||
|
|
7d4f5c8906 | ||
|
|
da39d07d48 | ||
|
|
b98f2456c0 | ||
|
|
564cc2b0bc | ||
|
|
49885c63c4 | ||
|
|
d7a6caa2ac | ||
|
|
73c383fd65 | ||
|
|
10b270f742 | ||
|
|
7a222923c7 | ||
|
|
430512dd27 | ||
|
|
d5ba15c23b | ||
|
|
037b43ee10 | ||
|
|
ab910403c6 | ||
|
|
8105437815 | ||
|
|
7b20cec035 | ||
|
|
8d512852a4 | ||
|
|
c8ad9b942a | ||
|
|
8153d4bb2a | ||
|
|
849dfee200 | ||
|
|
85540d96b6 | ||
|
|
7479974d79 | ||
|
|
3f1fb52fcb | ||
|
|
7e343d7006 | ||
|
|
72a5e1f695 | ||
|
|
253310bd1a | ||
|
|
fa6ccb08bd | ||
|
|
762507855e | ||
|
|
54610866f2 | ||
|
|
c39ff5c233 | ||
|
|
2ddc784965 | ||
|
|
10aabe8375 | ||
|
|
122647b39d | ||
|
|
02492c34a7 | ||
|
|
9436f604ba | ||
|
|
d9ca0deb08 | ||
|
|
0b985e8c35 | ||
|
|
c5d92ae02c | ||
|
|
94b60d9f70 | ||
|
|
b23eb8f29d | ||
|
|
3c44214d01 | ||
|
|
21f4cf7ab5 | ||
|
|
e94684aa39 | ||
|
|
a34cc48197 | ||
|
|
b262d91ccc | ||
|
|
39aa983771 | ||
|
|
5b9887dade | ||
|
|
c33402ce66 | ||
|
|
6f58f84151 | ||
|
|
6acb7caf5b | ||
|
|
8beff34cca | ||
|
|
478e0f74f7 | ||
|
|
b7bd23ab60 | ||
|
|
82533c1453 | ||
|
|
e0735b57ce | ||
|
|
1e0bfc3b0c | ||
|
|
cb0e89a38e | ||
|
|
da4d528463 | ||
|
|
394abbbe35 | ||
|
|
fd39bc8518 | ||
|
|
2663e8fba7 | ||
|
|
faebac6a77 | ||
|
|
bc1b09e997 | ||
|
|
af358f777b | ||
|
|
c0d27b4bfc | ||
|
|
7e50c95823 | ||
|
|
39068dda17 | ||
|
|
8185979ca4 | ||
|
|
7c44188130 | ||
|
|
c2d527bbd3 | ||
|
|
ac3ff095a1 | ||
|
|
0ed738cd61 | ||
|
|
6bbb7c8f7d | ||
|
|
d29429808c | ||
|
|
09eccd7cd9 | ||
|
|
bb2b8b4514 | ||
|
|
e20b9c5774 | ||
|
|
3badfa197a | ||
|
|
dee372e71b | ||
|
|
679be6e1bd | ||
|
|
92212fdd11 | ||
|
|
a6fb1ad9ef | ||
|
|
87d712fbd7 | ||
|
|
023809f099 | ||
|
|
ace37370d1 | ||
|
|
8efbd4fac1 | ||
|
|
06c8792887 | ||
|
|
3ea376a1b2 | ||
|
|
9667d30907 | ||
|
|
3f7ccc6c49 | ||
|
|
dd97ff5895 | ||
|
|
2e4d80d9bc | ||
|
|
1227dc5a2b | ||
|
|
ed828bc733 | ||
|
|
c25a018c05 | ||
|
|
266596af68 | ||
|
|
2c77b73ebc | ||
|
|
d81d4bbda3 | ||
|
|
400affe429 | ||
|
|
d3c63680e8 | ||
|
|
28de8a834c | ||
|
|
208374fc54 | ||
|
|
535a136a27 | ||
|
|
ba4c3e30a4 | ||
|
|
16d8a388cb | ||
|
|
5ea31b0b64 | ||
|
|
582c6d1c43 | ||
|
|
c63ae3f3af | ||
|
|
4c0df96a95 | ||
|
|
05c6b9379a | ||
|
|
fb7fdcd925 | ||
|
|
1774e2ad88 | ||
|
|
a402d9135e | ||
|
|
3d2c56d9ee | ||
|
|
f9308e6fed | ||
|
|
6710468020 | ||
|
|
ad1981fff6 | ||
|
|
01f9e71912 | ||
|
|
d41b75ee35 | ||
|
|
b829490aac | ||
|
|
7002bf8e34 | ||
|
|
625ea493fb | ||
|
|
79b3b2823b | ||
|
|
9be912e9fd | ||
|
|
3c3cd431cd | ||
|
|
8b8bab5c58 | ||
|
|
835fa6c41f | ||
|
|
8a6cf221a9 | ||
|
|
876563c492 | ||
|
|
be22c8547f | ||
|
|
82d98c4859 | ||
|
|
f1b5341f33 | ||
|
|
b3829493ea | ||
|
|
7db1253967 | ||
|
|
449db40d5f | ||
|
|
d5d0311bc6 | ||
|
|
0c4f01a892 | ||
|
|
bc7246f882 | ||
|
|
da65f3b016 | ||
|
|
a8c574219d | ||
|
|
a3751a77aa | ||
|
|
4f521e4dcb | ||
|
|
a9589d8d5b | ||
|
|
13e75aaf20 | ||
|
|
0c9bd8aaa0 | ||
|
|
5dba91c9ab | ||
|
|
7d6763cde7 | ||
|
|
dd1b23773e | ||
|
|
33253c0cfc | ||
|
|
0099c06056 | ||
|
|
1540660cc3 | ||
|
|
cff5e693d2 | ||
|
|
5159c1dc83 | ||
|
|
ccc7ad7cbd | ||
|
|
c8a61ca687 | ||
|
|
61e36d6aff | ||
|
|
e8c8742bae | ||
|
|
1cb93d76ed | ||
|
|
dadc939aab | ||
|
|
c59ea781e3 | ||
|
|
810ff62c26 | ||
|
|
5a0418bba6 | ||
|
|
baf506ae27 | ||
|
|
52ff03ae41 | ||
|
|
2d95edf8ab | ||
|
|
95b0fb81d6 | ||
|
|
eff65dce00 | ||
|
|
6c1c069261 | ||
|
|
4fe1e062f2 | ||
|
|
1fb0840e72 | ||
|
|
689ddf8bf0 | ||
|
|
d243ac49f3 | ||
|
|
de8f018b14 | ||
|
|
8407542600 | ||
|
|
a7a5cca8dd | ||
|
|
d9a70fd094 | ||
|
|
248cc0d3d3 | ||
|
|
2924d711cb | ||
|
|
d7db105a2f | ||
|
|
2ec2f45c82 | ||
|
|
a34769ae02 | ||
|
|
c0e4d805b1 | ||
|
|
6770336274 | ||
|
|
8d431dbb34 | ||
|
|
e8e7b83297 | ||
|
|
442340dcf2 | ||
|
|
91b037a335 | ||
|
|
d5ef3de64c | ||
|
|
167a0b0b29 | ||
|
|
954e3c70b2 | ||
|
|
b02a80abbd | ||
|
|
04313d3c3b | ||
|
|
fb8279f8f0 | ||
|
|
e0e56595c6 | ||
|
|
44d8cf9d4e | ||
|
|
282c1e53ec | ||
|
|
7ba98af1cc | ||
|
|
d3df5aaa52 | ||
|
|
1c83dcab5e | ||
|
|
6208081788 | ||
|
|
3795e92a82 | ||
|
|
0636123e7a | ||
|
|
69f9461bcd | ||
|
|
d1558a3472 | ||
|
|
8230596f98 | ||
|
|
cc4117e054 | ||
|
|
a0ddbc037f | ||
|
|
de82d4e616 | ||
|
|
fa220f9e93 | ||
|
|
aca112fa42 | ||
|
|
9f4077d35d | ||
|
|
9509b855f1 | ||
|
|
be72f4a046 | ||
|
|
8b36ce198f | ||
|
|
71de05dc68 | ||
|
|
83b5a9457a | ||
|
|
0b7ada9fd9 | ||
|
|
92bcef0b1c | ||
|
|
a10c4056d0 | ||
|
|
1fd3ee7149 | ||
|
|
e3a157bfe1 | ||
|
|
b446aa6590 | ||
|
|
c54ee71e1d | ||
|
|
1748a2ae12 | ||
|
|
eff46aa97a | ||
|
|
9fb186af75 | ||
|
|
f1b1001863 | ||
|
|
c5af536299 | ||
|
|
b9b2f691a5 | ||
|
|
bdc8817672 | ||
|
|
a55acb2816 | ||
|
|
d686c76db3 | ||
|
|
30c1ae651e | ||
|
|
adaad62fbd | ||
|
|
fe5ec205fc | ||
|
|
576400e0d9 | ||
|
|
f08a03106f | ||
|
|
f852b7789e | ||
|
|
b0bd06bdc5 | ||
|
|
84787f0ea2 | ||
|
|
f69b3dbbe6 | ||
|
|
ec5ec6f02c | ||
|
|
5d681e635b | ||
|
|
3deb65b529 | ||
|
|
3e527fee8b | ||
|
|
b1f1f94a76 | ||
|
|
43e140e6cc | ||
|
|
7ca9d445f1 | ||
|
|
90aaf71270 | ||
|
|
4f2570865c | ||
|
|
81556ec2e1 | ||
|
|
dd5a9c6067 | ||
|
|
982c50c756 | ||
|
|
3c7dc9b9ad | ||
|
|
8f4354936c | ||
|
|
e50941277f | ||
|
|
a72647b925 | ||
|
|
e254849009 | ||
|
|
5757f0e201 | ||
|
|
75fdf7adab | ||
|
|
702eafae4c | ||
|
|
4abb8cd87f | ||
|
|
5194361f3b | ||
|
|
bc83b75634 | ||
|
|
b1ff95affa | ||
|
|
a243d48fb1 |
@@ -1 +1,2 @@
|
|||||||
FROM qmcgaw/godevcontainer
|
FROM qmcgaw/godevcontainer
|
||||||
|
RUN apk add wireguard-tools
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ It works on Linux, Windows and OSX.
|
|||||||
|
|
||||||
1. Open the command palette in Visual Studio Code (CTRL+SHIFT+P).
|
1. Open the command palette in Visual Studio Code (CTRL+SHIFT+P).
|
||||||
1. Select `Remote-Containers: Open Folder in Container...` and choose the project directory.
|
1. Select `Remote-Containers: Open Folder in Container...` and choose the project directory.
|
||||||
|
1. For Docker running on Windows HyperV, if you want to use SSH keys, bind mount them at `/tmp/.ssh` by changing the `volumes` section in the [docker-compose.yml](docker-compose.yml).
|
||||||
|
|
||||||
## Customization
|
## Customization
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
"vscode"
|
"vscode"
|
||||||
],
|
],
|
||||||
"shutdownAction": "stopCompose",
|
"shutdownAction": "stopCompose",
|
||||||
"postCreateCommand": "go mod download",
|
"postCreateCommand": "~/.windows.sh && go mod download && go mod tidy",
|
||||||
"workspaceFolder": "/workspace",
|
"workspaceFolder": "/workspace",
|
||||||
"extensions": [
|
"extensions": [
|
||||||
"golang.go",
|
"golang.go",
|
||||||
|
|||||||
@@ -4,21 +4,29 @@ services:
|
|||||||
vscode:
|
vscode:
|
||||||
build: .
|
build: .
|
||||||
image: godevcontainer
|
image: godevcontainer
|
||||||
|
devices:
|
||||||
|
- /dev/net/tun:/dev/net/tun
|
||||||
volumes:
|
volumes:
|
||||||
- ../:/workspace
|
- ../:/workspace
|
||||||
# Docker socket to access Docker server
|
# Docker socket to access Docker server
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
# SSH directory
|
# Docker configuration
|
||||||
- ~/.ssh:/home/vscode/.ssh
|
- ~/.docker:/root/.docker:z
|
||||||
- ~/.ssh:/root/.ssh
|
# SSH directory for Linux, OSX and WSL
|
||||||
|
- ~/.ssh:/root/.ssh:z
|
||||||
|
# For Windows without WSL, a copy will be made
|
||||||
|
# from /tmp/.ssh to ~/.ssh to fix permissions
|
||||||
|
#- ~/.ssh:/tmp/.ssh:ro
|
||||||
|
# Shell history persistence
|
||||||
|
- ~/.zsh_history:/root/.zsh_history:z
|
||||||
# Git config
|
# Git config
|
||||||
- ~/.gitconfig:/home/districter/.gitconfig
|
- ~/.gitconfig:/root/.gitconfig:z
|
||||||
- ~/.gitconfig:/root/.gitconfig
|
|
||||||
environment:
|
environment:
|
||||||
- TZ=
|
- TZ=
|
||||||
cap_add:
|
cap_add:
|
||||||
# For debugging with dlv
|
# For debugging with dlv
|
||||||
- SYS_PTRACE
|
# - SYS_PTRACE
|
||||||
|
- NET_ADMIN
|
||||||
security_opt:
|
security_opt:
|
||||||
# For debugging with dlv
|
# For debugging with dlv
|
||||||
- seccomp:unconfined
|
- seccomp:unconfined
|
||||||
|
|||||||
37
.github/ISSUE_TEMPLATE/bug.md
vendored
37
.github/ISSUE_TEMPLATE/bug.md
vendored
@@ -1,37 +0,0 @@
|
|||||||
---
|
|
||||||
name: Bug
|
|
||||||
about: Report a bug
|
|
||||||
title: 'Bug: FILL THIS TEXT!'
|
|
||||||
labels: ":bug: bug"
|
|
||||||
assignees: qdm12
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Host OS** (approximate answer is fine too): Ubuntu 18
|
|
||||||
|
|
||||||
**Is this urgent?**: No
|
|
||||||
|
|
||||||
**What VPN provider are you using**:
|
|
||||||
|
|
||||||
**What are you using to run your container?**: Docker Compose
|
|
||||||
|
|
||||||
**What is the version of the program** (See the line at the top of your logs)
|
|
||||||
|
|
||||||
```
|
|
||||||
Running version latest built on 2020-03-13T01:30:06Z (commit d0f678c)
|
|
||||||
```
|
|
||||||
|
|
||||||
**What's the problem** 🤔
|
|
||||||
|
|
||||||
That feature doesn't work
|
|
||||||
|
|
||||||
**Share your logs...**
|
|
||||||
|
|
||||||
...*careful to remove i.e. token information with PIA port forwarding*
|
|
||||||
|
|
||||||
```log
|
|
||||||
|
|
||||||
PASTE YOUR LOGS
|
|
||||||
IN THERE
|
|
||||||
|
|
||||||
```
|
|
||||||
107
.github/ISSUE_TEMPLATE/bug.yml
vendored
Normal file
107
.github/ISSUE_TEMPLATE/bug.yml
vendored
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
name: Bug
|
||||||
|
description: Report a bug
|
||||||
|
title: "Bug: "
|
||||||
|
labels: [":bug: bug"]
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Thanks for taking the time to fill out this bug report!
|
||||||
|
- type: dropdown
|
||||||
|
id: urgent
|
||||||
|
attributes:
|
||||||
|
label: Is this urgent?
|
||||||
|
description: |
|
||||||
|
Is this a critical bug, or do you need this fixed urgently?
|
||||||
|
If this is urgent, note you can use one of the [image tags available](https://github.com/qdm12/gluetun/wiki/Docker-image-tags) if that can help.
|
||||||
|
options:
|
||||||
|
- "No"
|
||||||
|
- "Yes"
|
||||||
|
- type: input
|
||||||
|
id: host-os
|
||||||
|
attributes:
|
||||||
|
label: Host OS
|
||||||
|
description: What is your host OS?
|
||||||
|
placeholder: "Debian Buster"
|
||||||
|
- type: dropdown
|
||||||
|
id: cpu-arch
|
||||||
|
attributes:
|
||||||
|
label: CPU arch
|
||||||
|
description: You can find it on Linux with `uname -m`.
|
||||||
|
options:
|
||||||
|
- x86_64
|
||||||
|
- aarch64
|
||||||
|
- armv7l
|
||||||
|
- "386"
|
||||||
|
- s390x
|
||||||
|
- ppc64le
|
||||||
|
- type: dropdown
|
||||||
|
id: vpn-service-provider
|
||||||
|
attributes:
|
||||||
|
label: VPN service provider
|
||||||
|
options:
|
||||||
|
- Custom
|
||||||
|
- Cyberghost
|
||||||
|
- ExpressVPN
|
||||||
|
- FastestVPN
|
||||||
|
- HideMyAss
|
||||||
|
- IPVanish
|
||||||
|
- IVPN
|
||||||
|
- Mullvad
|
||||||
|
- NordVPN
|
||||||
|
- Privado
|
||||||
|
- Private Internet Access
|
||||||
|
- PrivateVPN
|
||||||
|
- ProtonVPN
|
||||||
|
- PureVPN
|
||||||
|
- Surfshark
|
||||||
|
- TorGuard
|
||||||
|
- VPNUnlimited
|
||||||
|
- VyprVPN
|
||||||
|
- WeVPN
|
||||||
|
- Windscribe
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
id: docker
|
||||||
|
attributes:
|
||||||
|
label: What are you using to run the container
|
||||||
|
options:
|
||||||
|
- docker run
|
||||||
|
- docker-compose
|
||||||
|
- Portainer
|
||||||
|
- Kubernetes
|
||||||
|
- Podman
|
||||||
|
- Other
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: version
|
||||||
|
attributes:
|
||||||
|
label: What is the version of Gluetun
|
||||||
|
description: |
|
||||||
|
Copy paste the version line at the top of your logs.
|
||||||
|
It should be in the form `Running version latest built on 2020-03-13T01:30:06Z (commit d0f678c)`.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: problem
|
||||||
|
attributes:
|
||||||
|
label: "What's the problem 🤔"
|
||||||
|
placeholder: "That feature does not work..."
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: logs
|
||||||
|
attributes:
|
||||||
|
label: Share your logs
|
||||||
|
description: No sensitive information is logged out except when running with `LOG_LEVEL=debug`.
|
||||||
|
render: log
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: config
|
||||||
|
attributes:
|
||||||
|
label: Share your configuration
|
||||||
|
description: Share your configuration such as `docker-compose.yml`. Ensure to remove credentials.
|
||||||
|
render: yml
|
||||||
7
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
7
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
contact_links:
|
||||||
|
- name: Configuration help?
|
||||||
|
url: https://github.com/qdm12/gluetun/discussions/new
|
||||||
|
about: Please create a Github discussion.
|
||||||
|
- name: Unraid template issue
|
||||||
|
url: https://github.com/qdm12/gluetun/discussions/550
|
||||||
|
about: Please read the relevant Github discussion.
|
||||||
17
.github/ISSUE_TEMPLATE/feature_request.md
vendored
17
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,17 +0,0 @@
|
|||||||
---
|
|
||||||
name: Feature request
|
|
||||||
about: Suggest a feature to add to this project
|
|
||||||
title: 'Feature request: FILL THIS TEXT!'
|
|
||||||
labels: ":bulb: feature request"
|
|
||||||
assignees: qdm12
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**What's the feature?** 🧐
|
|
||||||
|
|
||||||
- Support this new feature because that and that
|
|
||||||
|
|
||||||
**Optional extra information** 🚀
|
|
||||||
|
|
||||||
- I tried `docker run something` and it doesn't work
|
|
||||||
- That [url](https://github.com/qdm12/gluetun) is interesting
|
|
||||||
19
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
19
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
name: Feature request
|
||||||
|
description: Suggest a feature to add to Gluetun
|
||||||
|
title: "Feature request: "
|
||||||
|
labels: [":bulb: feature request"]
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
id: description
|
||||||
|
attributes:
|
||||||
|
label: "What's the feature 🧐"
|
||||||
|
placeholder: "Make the tunnel resistant to earth quakes"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: extra
|
||||||
|
attributes:
|
||||||
|
label: "Extra information and references"
|
||||||
|
placeholder: |
|
||||||
|
- I tried `docker run something` and it doesn't work
|
||||||
|
- That [url](https://github.com/qdm12/gluetun) is interesting
|
||||||
53
.github/ISSUE_TEMPLATE/help.md
vendored
53
.github/ISSUE_TEMPLATE/help.md
vendored
@@ -1,53 +0,0 @@
|
|||||||
---
|
|
||||||
name: Help
|
|
||||||
about: Ask for help
|
|
||||||
title: 'Help: FILL THIS TEXT!'
|
|
||||||
labels: ":pray: help wanted"
|
|
||||||
assignees:
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Host OS** (approximate answer is fine too): Ubuntu 18
|
|
||||||
|
|
||||||
**Is this urgent?**: No
|
|
||||||
|
|
||||||
**What VPN provider are you using**:
|
|
||||||
|
|
||||||
**What is the version of the program** (See the line at the top of your logs)
|
|
||||||
|
|
||||||
```
|
|
||||||
Running version latest built on 2020-03-13T01:30:06Z (commit d0f678c)
|
|
||||||
```
|
|
||||||
|
|
||||||
**What's the problem** 🤔
|
|
||||||
|
|
||||||
That feature doesn't work
|
|
||||||
|
|
||||||
**Share your logs...**
|
|
||||||
|
|
||||||
...*careful to remove i.e. token information with PIA port forwarding*
|
|
||||||
|
|
||||||
```log
|
|
||||||
|
|
||||||
PASTE YOUR LOGS
|
|
||||||
IN THERE
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
**What are you using to run your container?**: Docker Compose
|
|
||||||
|
|
||||||
Please also share your configuration file:
|
|
||||||
|
|
||||||
```yml
|
|
||||||
your .yml
|
|
||||||
content
|
|
||||||
in here
|
|
||||||
```
|
|
||||||
|
|
||||||
or
|
|
||||||
|
|
||||||
```sh
|
|
||||||
# your docker
|
|
||||||
# run command
|
|
||||||
# in here
|
|
||||||
```
|
|
||||||
17
.github/ISSUE_TEMPLATE/provider.md
vendored
Normal file
17
.github/ISSUE_TEMPLATE/provider.md
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
name: Support a VPN provider
|
||||||
|
about: Suggest a VPN provider to be supported
|
||||||
|
title: 'VPN provider support: NAME OF THE PROVIDER'
|
||||||
|
labels: ":bulb: New provider"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
One of the following is required:
|
||||||
|
|
||||||
|
- Publicly accessible URL to a zip file containing the Openvpn configuration files
|
||||||
|
- Publicly accessible URL to a structured (JSON etc.) list of servers **and attach** an example Openvpn configuration file for both TCP and UDP
|
||||||
|
- Publicly accessible URL to the list of servers **and attach** an example Openvpn configuration file for both TCP and UDP
|
||||||
|
|
||||||
|
If the list of servers requires to login **or** is hidden behind an interactive configurator,
|
||||||
|
you can only use a custom Openvpn configuration file.
|
||||||
|
[The Wiki](https://github.com/qdm12/gluetun/wiki/Openvpn-file) describes how to do so.
|
||||||
18
.github/ISSUE_TEMPLATE/wiki issue.yml
vendored
Normal file
18
.github/ISSUE_TEMPLATE/wiki issue.yml
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
name: Wiki issue
|
||||||
|
description: Report a Wiki issue
|
||||||
|
title: "Wiki issue: "
|
||||||
|
labels: ["📄 Wiki issue"]
|
||||||
|
body:
|
||||||
|
- type: input
|
||||||
|
id: url
|
||||||
|
attributes:
|
||||||
|
label: "URL to the Wiki page"
|
||||||
|
placeholder: "https://github.com/qdm12/gluetun/wiki/OpenVPN-options"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: description
|
||||||
|
attributes:
|
||||||
|
label: "What's the issue?"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
43
.github/labels.yml
vendored
43
.github/labels.yml
vendored
@@ -14,31 +14,71 @@
|
|||||||
color: "795548"
|
color: "795548"
|
||||||
description: ""
|
description: ""
|
||||||
|
|
||||||
|
# Priority
|
||||||
|
- name: "🚨 Urgent"
|
||||||
|
color: "d5232f"
|
||||||
|
description: ""
|
||||||
|
- name: "💤 Low priority"
|
||||||
|
color: "4285f4"
|
||||||
|
description: ""
|
||||||
|
|
||||||
# VPN providers
|
# VPN providers
|
||||||
- name: ":cloud: Cyberghost"
|
- name: ":cloud: Cyberghost"
|
||||||
color: "cfe8d4"
|
color: "cfe8d4"
|
||||||
description: ""
|
description: ""
|
||||||
|
- name: ":cloud: HideMyAss"
|
||||||
|
color: "cfe8d4"
|
||||||
|
description: ""
|
||||||
|
- name: ":cloud: IPVanish"
|
||||||
|
color: "cfe8d4"
|
||||||
|
description: ""
|
||||||
|
- name: ":cloud: IVPN"
|
||||||
|
color: "cfe8d4"
|
||||||
|
description: ""
|
||||||
|
- name: ":cloud: ExpressVPN"
|
||||||
|
color: "cfe8d4"
|
||||||
|
description: ""
|
||||||
|
- name: ":cloud: FastestVPN"
|
||||||
|
color: "cfe8d4"
|
||||||
|
description: ""
|
||||||
- name: ":cloud: Mullvad"
|
- name: ":cloud: Mullvad"
|
||||||
color: "cfe8d4"
|
color: "cfe8d4"
|
||||||
description: ""
|
description: ""
|
||||||
- name: ":cloud: NordVPN"
|
- name: ":cloud: NordVPN"
|
||||||
color: "cfe8d4"
|
color: "cfe8d4"
|
||||||
description: ""
|
description: ""
|
||||||
|
- name: ":cloud: Perfect Privacy"
|
||||||
|
color: "cfe8d4"
|
||||||
|
description: ""
|
||||||
- name: ":cloud: PIA"
|
- name: ":cloud: PIA"
|
||||||
color: "cfe8d4"
|
color: "cfe8d4"
|
||||||
description: ""
|
description: ""
|
||||||
- name: ":cloud: Privado"
|
- name: ":cloud: Privado"
|
||||||
color: "cfe8d4"
|
color: "cfe8d4"
|
||||||
description: ""
|
description: ""
|
||||||
|
- name: ":cloud: PrivateVPN"
|
||||||
|
color: "cfe8d4"
|
||||||
|
description: ""
|
||||||
|
- name: ":cloud: ProtonVPN"
|
||||||
|
color: "cfe8d4"
|
||||||
- name: ":cloud: PureVPN"
|
- name: ":cloud: PureVPN"
|
||||||
color: "cfe8d4"
|
color: "cfe8d4"
|
||||||
description: ""
|
description: ""
|
||||||
- name: ":cloud: Surfshark"
|
- name: ":cloud: Surfshark"
|
||||||
color: "cfe8d4"
|
color: "cfe8d4"
|
||||||
description: ""
|
description: ""
|
||||||
|
- name: ":cloud: Torguard"
|
||||||
|
color: "cfe8d4"
|
||||||
|
description: ""
|
||||||
|
- name: ":cloud: VPNUnlimited"
|
||||||
|
color: "cfe8d4"
|
||||||
|
description: ""
|
||||||
- name: ":cloud: Vyprvpn"
|
- name: ":cloud: Vyprvpn"
|
||||||
color: "cfe8d4"
|
color: "cfe8d4"
|
||||||
description: ""
|
description: ""
|
||||||
|
- name: ":cloud: WeVPN"
|
||||||
|
color: "cfe8d4"
|
||||||
|
description: ""
|
||||||
- name: ":cloud: Windscribe"
|
- name: ":cloud: Windscribe"
|
||||||
color: "cfe8d4"
|
color: "cfe8d4"
|
||||||
description: ""
|
description: ""
|
||||||
@@ -47,6 +87,9 @@
|
|||||||
- name: "Openvpn"
|
- name: "Openvpn"
|
||||||
color: "ffc7ea"
|
color: "ffc7ea"
|
||||||
description: ""
|
description: ""
|
||||||
|
- name: "Wireguard"
|
||||||
|
color: "ffc7ea"
|
||||||
|
description: ""
|
||||||
- name: "Unbound (DNS over TLS)"
|
- name: "Unbound (DNS over TLS)"
|
||||||
color: "ffc7ea"
|
color: "ffc7ea"
|
||||||
description: ""
|
description: ""
|
||||||
|
|||||||
100
.github/workflows/build.yml
vendored
100
.github/workflows/build.yml
vendored
@@ -1,100 +0,0 @@
|
|||||||
name: CI
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
paths:
|
|
||||||
- .github/workflows/build.yml
|
|
||||||
- cmd/**
|
|
||||||
- internal/**
|
|
||||||
- pkg/**
|
|
||||||
- .dockerignore
|
|
||||||
- .golangci.yml
|
|
||||||
- Dockerfile
|
|
||||||
- go.mod
|
|
||||||
- go.sum
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
verify:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
env:
|
|
||||||
DOCKER_BUILDKIT: "1"
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
|
|
||||||
- name: Linting
|
|
||||||
run: docker build --target lint .
|
|
||||||
|
|
||||||
- name: Go mod tidy check
|
|
||||||
run: docker build --target tidy .
|
|
||||||
|
|
||||||
- name: Build test image
|
|
||||||
run: docker build --target test -t test-container .
|
|
||||||
|
|
||||||
- name: Run tests in test container
|
|
||||||
run: |
|
|
||||||
touch coverage.txt
|
|
||||||
docker run --rm \
|
|
||||||
-v "$(pwd)/coverage.txt:/tmp/gobuild/coverage.txt" \
|
|
||||||
test-container \
|
|
||||||
go test \
|
|
||||||
-race \
|
|
||||||
-coverpkg=./... \
|
|
||||||
-coverprofile=coverage.txt \
|
|
||||||
-covermode=atomic \
|
|
||||||
./...
|
|
||||||
|
|
||||||
# We run this here to use the caching of the previous steps
|
|
||||||
- if: github.event_name == 'push'
|
|
||||||
name: Build final image
|
|
||||||
run: docker build .
|
|
||||||
|
|
||||||
publish:
|
|
||||||
needs: [verify]
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
|
|
||||||
- uses: docker/setup-qemu-action@v1
|
|
||||||
- uses: docker/setup-buildx-action@v1
|
|
||||||
|
|
||||||
- uses: docker/login-action@v1
|
|
||||||
with:
|
|
||||||
username: qmcgaw
|
|
||||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
|
||||||
|
|
||||||
- name: Set variables
|
|
||||||
id: vars
|
|
||||||
env:
|
|
||||||
EVENT_NAME: ${{ github.event_name }}
|
|
||||||
run: |
|
|
||||||
BRANCH=${GITHUB_REF#refs/heads/}
|
|
||||||
TAG=${GITHUB_REF#refs/tags/}
|
|
||||||
echo ::set-output name=commit::$(git rev-parse --short HEAD)
|
|
||||||
echo ::set-output name=build_date::$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
||||||
if [ "$TAG" != "$GITHUB_REF" ]; then
|
|
||||||
echo ::set-output name=version::$TAG
|
|
||||||
echo ::set-output name=platforms::linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/s390x,linux/ppc64le
|
|
||||||
elif [ "$BRANCH" = "master" ]; then
|
|
||||||
echo ::set-output name=version::latest
|
|
||||||
echo ::set-output name=platforms::linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/s390x,linux/ppc64le
|
|
||||||
else
|
|
||||||
echo ::set-output name=version::$BRANCH
|
|
||||||
echo ::set-output name=platforms::linux/amd64
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Build and push final image
|
|
||||||
uses: docker/build-push-action@v2
|
|
||||||
with:
|
|
||||||
platforms: ${{ steps.vars.outputs.platforms }}
|
|
||||||
build-args: |
|
|
||||||
BUILD_DATE=${{ steps.vars.outputs.build_date }}
|
|
||||||
COMMIT=${{ steps.vars.outputs.commit }}
|
|
||||||
VERSION=${{ steps.vars.outputs.version }}
|
|
||||||
tags: |
|
|
||||||
qmcgaw/gluetun:${{ steps.vars.outputs.version }}
|
|
||||||
qmcgaw/private-internet-access:${{ steps.vars.outputs.version }}
|
|
||||||
push: true
|
|
||||||
|
|
||||||
- if: github.event_name == 'push' && github.event.ref == 'refs/heads/master'
|
|
||||||
name: Microbadger hook
|
|
||||||
run: curl -X POST https://hooks.microbadger.com/images/qmcgaw/gluetun/l-keGI7p4IhX4QuIDMFYKhsZ1L0=
|
|
||||||
continue-on-error: true
|
|
||||||
135
.github/workflows/ci.yml
vendored
Normal file
135
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
name: CI
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types:
|
||||||
|
- published
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
paths:
|
||||||
|
- .github/workflows/ci.yml
|
||||||
|
- cmd/**
|
||||||
|
- internal/**
|
||||||
|
- pkg/**
|
||||||
|
- .dockerignore
|
||||||
|
- .golangci.yml
|
||||||
|
- Dockerfile
|
||||||
|
- go.mod
|
||||||
|
- go.sum
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
paths:
|
||||||
|
- .github/workflows/ci.yml
|
||||||
|
- cmd/**
|
||||||
|
- internal/**
|
||||||
|
- pkg/**
|
||||||
|
- .dockerignore
|
||||||
|
- .golangci.yml
|
||||||
|
- Dockerfile
|
||||||
|
- go.mod
|
||||||
|
- go.sum
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
verify:
|
||||||
|
# Only run if it's a push event or if it's a PR from this repository, and it is not dependabot.
|
||||||
|
if: |
|
||||||
|
github.actor != 'dependabot[bot]' &&
|
||||||
|
(github.event_name == 'push' ||
|
||||||
|
github.event_name == 'release' ||
|
||||||
|
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository))
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
DOCKER_BUILDKIT: "1"
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2.4.0
|
||||||
|
|
||||||
|
- uses: reviewdog/action-misspell@v1
|
||||||
|
with:
|
||||||
|
locale: "US"
|
||||||
|
level: error
|
||||||
|
|
||||||
|
- name: Linting
|
||||||
|
run: docker build --target lint .
|
||||||
|
|
||||||
|
- name: Go mod tidy check
|
||||||
|
run: docker build --target tidy .
|
||||||
|
|
||||||
|
- name: Build test image
|
||||||
|
run: docker build --target test -t test-container .
|
||||||
|
|
||||||
|
- name: Run tests in test container
|
||||||
|
run: |
|
||||||
|
touch coverage.txt
|
||||||
|
docker run --rm \
|
||||||
|
-v "$(pwd)/coverage.txt:/tmp/gobuild/coverage.txt" \
|
||||||
|
test-container
|
||||||
|
|
||||||
|
- name: Code security analysis
|
||||||
|
uses: snyk/actions/golang@master
|
||||||
|
env:
|
||||||
|
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build final image
|
||||||
|
run: docker build -t final-image .
|
||||||
|
|
||||||
|
# - name: Image security analysis
|
||||||
|
# uses: snyk/actions/docker@master
|
||||||
|
# env:
|
||||||
|
# SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
|
||||||
|
# with:
|
||||||
|
# image: final-image
|
||||||
|
|
||||||
|
publish:
|
||||||
|
# Only run if it's a push event or if it's a PR from this repository
|
||||||
|
if: |
|
||||||
|
github.event_name == 'push' ||
|
||||||
|
github.event_name == 'release' ||
|
||||||
|
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository)
|
||||||
|
needs: [verify]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2.4.0
|
||||||
|
|
||||||
|
# extract metadata (tags, labels) for Docker
|
||||||
|
# https://github.com/docker/metadata-action
|
||||||
|
- name: Extract Docker metadata
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v3
|
||||||
|
with:
|
||||||
|
flavor: |
|
||||||
|
latest=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}
|
||||||
|
images: |
|
||||||
|
qmcgaw/gluetun
|
||||||
|
qmcgaw/private-internet-access
|
||||||
|
tags: |
|
||||||
|
type=ref,event=branch,enable=${{ github.ref != format('refs/heads/{0}', github.event.repository.default_branch) }}
|
||||||
|
type=ref,event=pr
|
||||||
|
type=semver,pattern=v{{major}}.{{minor}}.{{patch}}
|
||||||
|
type=semver,pattern=v{{major}}.{{minor}}
|
||||||
|
type=semver,pattern=v{{major}},enable=${{ !startsWith(github.ref, 'refs/tags/v0.') }}
|
||||||
|
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}
|
||||||
|
|
||||||
|
- uses: docker/setup-qemu-action@v1
|
||||||
|
- uses: docker/setup-buildx-action@v1
|
||||||
|
|
||||||
|
- uses: docker/login-action@v1
|
||||||
|
with:
|
||||||
|
username: qmcgaw
|
||||||
|
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Short commit
|
||||||
|
id: shortcommit
|
||||||
|
run: echo "::set-output name=value::$(git rev-parse --short HEAD)"
|
||||||
|
|
||||||
|
- name: Build and push final image
|
||||||
|
uses: docker/build-push-action@v2.8.0
|
||||||
|
with:
|
||||||
|
platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/ppc64le
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
build-args: |
|
||||||
|
CREATED=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }}
|
||||||
|
COMMIT=${{ steps.shortcommit.outputs.value }}
|
||||||
|
VERSION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }}
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
push: true
|
||||||
37
.github/workflows/dependabot.yml
vendored
Normal file
37
.github/workflows/dependabot.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
name: Dependabot
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
paths:
|
||||||
|
- .github/workflows/dependabot.yml
|
||||||
|
- cmd/**
|
||||||
|
- internal/**
|
||||||
|
- pkg/**
|
||||||
|
- .dockerignore
|
||||||
|
- .golangci.yml
|
||||||
|
- Dockerfile
|
||||||
|
- go.mod
|
||||||
|
- go.sum
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
verify:
|
||||||
|
if: ${{ github.actor == 'dependabot[bot]' }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
DOCKER_BUILDKIT: "1"
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2.4.0
|
||||||
|
|
||||||
|
- name: Build test image
|
||||||
|
run: docker build --target test -t test-container .
|
||||||
|
|
||||||
|
- name: Run tests in test container
|
||||||
|
run: |
|
||||||
|
touch coverage.txt
|
||||||
|
docker run --rm \
|
||||||
|
-v "$(pwd)/coverage.txt:/tmp/gobuild/coverage.txt" \
|
||||||
|
test-container
|
||||||
|
|
||||||
|
- name: Build final image
|
||||||
|
run: docker build -t final-image .
|
||||||
2
.github/workflows/dockerhub-description.yml
vendored
2
.github/workflows/dockerhub-description.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2.4.0
|
||||||
- name: Docker Hub Description
|
- name: Docker Hub Description
|
||||||
uses: peter-evans/dockerhub-description@v2
|
uses: peter-evans/dockerhub-description@v2
|
||||||
with:
|
with:
|
||||||
|
|||||||
40
.github/workflows/fork.yml
vendored
Normal file
40
.github/workflows/fork.yml
vendored
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
name: Fork
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
paths:
|
||||||
|
- .github/workflows/fork.yml
|
||||||
|
- cmd/**
|
||||||
|
- internal/**
|
||||||
|
- pkg/**
|
||||||
|
- .dockerignore
|
||||||
|
- .golangci.yml
|
||||||
|
- Dockerfile
|
||||||
|
- go.mod
|
||||||
|
- go.sum
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
verify:
|
||||||
|
if: github.event.pull_request.head.repo.full_name != github.repository && github.actor != 'dependabot[bot]'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
DOCKER_BUILDKIT: "1"
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2.4.0
|
||||||
|
|
||||||
|
- name: Linting
|
||||||
|
run: docker build --target lint .
|
||||||
|
|
||||||
|
- name: Build test image
|
||||||
|
run: docker build --target test -t test-container .
|
||||||
|
|
||||||
|
- name: Run tests in test container
|
||||||
|
run: |
|
||||||
|
touch coverage.txt
|
||||||
|
docker run --rm \
|
||||||
|
-v "$(pwd)/coverage.txt:/tmp/gobuild/coverage.txt" \
|
||||||
|
test-container
|
||||||
|
|
||||||
|
- name: Build final image
|
||||||
|
run: docker build -t final-image .
|
||||||
2
.github/workflows/labels.yml
vendored
2
.github/workflows/labels.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
|||||||
labeler:
|
labeler:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2.4.0
|
||||||
- uses: crazy-max/ghaction-github-labeler@v3
|
- uses: crazy-max/ghaction-github-labeler@v3
|
||||||
with:
|
with:
|
||||||
yaml-file: .github/labels.yml
|
yaml-file: .github/labels.yml
|
||||||
|
|||||||
15
.github/workflows/misspell.yml
vendored
15
.github/workflows/misspell.yml
vendored
@@ -1,15 +0,0 @@
|
|||||||
name: Misspells
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
branches: [master]
|
|
||||||
push:
|
|
||||||
branches: [master]
|
|
||||||
jobs:
|
|
||||||
misspell:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- uses: reviewdog/action-misspell@v1
|
|
||||||
with:
|
|
||||||
locale: "US"
|
|
||||||
level: error
|
|
||||||
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
scratch.txt
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
linters-settings:
|
linters-settings:
|
||||||
maligned:
|
|
||||||
suggest-new: true
|
|
||||||
misspell:
|
misspell:
|
||||||
locale: US
|
locale: US
|
||||||
|
|
||||||
@@ -10,21 +8,58 @@ issues:
|
|||||||
linters:
|
linters:
|
||||||
- dupl
|
- dupl
|
||||||
- maligned
|
- maligned
|
||||||
- path: internal/unix/constants\.go
|
- goerr113
|
||||||
|
- containedctx
|
||||||
|
- path: internal/server/
|
||||||
linters:
|
linters:
|
||||||
- golint
|
- dupl
|
||||||
text: don't use ALL_CAPS in Go names; use CamelCase
|
- path: internal/configuration/
|
||||||
|
linters:
|
||||||
|
- dupl
|
||||||
|
- path: internal/constants/
|
||||||
|
linters:
|
||||||
|
- dupl
|
||||||
|
- text: "exported: exported var Err*"
|
||||||
|
linters:
|
||||||
|
- revive
|
||||||
|
- text: "mnd: Magic number: 0644*"
|
||||||
|
linters:
|
||||||
|
- gomnd
|
||||||
|
- text: "mnd: Magic number: 0400*"
|
||||||
|
linters:
|
||||||
|
- gomnd
|
||||||
|
- text: "variable 'mssFix' is only used in the if-statement*"
|
||||||
|
path: "openvpnconf.go"
|
||||||
|
linters:
|
||||||
|
- ifshort
|
||||||
|
- text: "variable 'auth' is only used in the if-statement*"
|
||||||
|
path: "openvpnconf.go"
|
||||||
|
linters:
|
||||||
|
- ifshort
|
||||||
|
- linters:
|
||||||
|
- lll
|
||||||
|
source: "^//go:generate "
|
||||||
|
|
||||||
linters:
|
linters:
|
||||||
disable-all: true
|
|
||||||
enable:
|
enable:
|
||||||
|
# - cyclop
|
||||||
|
# - errorlint
|
||||||
|
# - ireturn
|
||||||
|
# - varnamelen
|
||||||
|
# - wrapcheck
|
||||||
- asciicheck
|
- asciicheck
|
||||||
|
- bidichk
|
||||||
- bodyclose
|
- bodyclose
|
||||||
- deadcode
|
- containedctx
|
||||||
|
- decorder
|
||||||
- dogsled
|
- dogsled
|
||||||
- dupl
|
- dupl
|
||||||
- errcheck
|
- durationcheck
|
||||||
|
- errchkjson
|
||||||
|
- errname
|
||||||
- exhaustive
|
- exhaustive
|
||||||
- exportloopref
|
- exportloopref
|
||||||
|
- forcetypeassert
|
||||||
- gci
|
- gci
|
||||||
- gochecknoglobals
|
- gochecknoglobals
|
||||||
- gochecknoinits
|
- gochecknoinits
|
||||||
@@ -33,34 +68,39 @@ linters:
|
|||||||
- gocritic
|
- gocritic
|
||||||
- gocyclo
|
- gocyclo
|
||||||
- godot
|
- godot
|
||||||
|
- goerr113
|
||||||
- goheader
|
- goheader
|
||||||
- goimports
|
- goimports
|
||||||
- golint
|
|
||||||
- gomnd
|
- gomnd
|
||||||
|
- gomoddirectives
|
||||||
- goprintffuncname
|
- goprintffuncname
|
||||||
- gosec
|
- gosec
|
||||||
- gosimple
|
- grouper
|
||||||
- govet
|
- ifshort
|
||||||
- ineffassign
|
- importas
|
||||||
- interfacer
|
|
||||||
- lll
|
- lll
|
||||||
- maligned
|
- maintidx
|
||||||
|
- makezero
|
||||||
- misspell
|
- misspell
|
||||||
- nakedret
|
- nakedret
|
||||||
- nestif
|
- nestif
|
||||||
|
- nilerr
|
||||||
|
- nilnil
|
||||||
- noctx
|
- noctx
|
||||||
- nolintlint
|
- nolintlint
|
||||||
- prealloc
|
- prealloc
|
||||||
|
- predeclared
|
||||||
|
- predeclared
|
||||||
|
- promlinter
|
||||||
|
- revive
|
||||||
- rowserrcheck
|
- rowserrcheck
|
||||||
- scopelint
|
|
||||||
- sqlclosecheck
|
- sqlclosecheck
|
||||||
- staticcheck
|
- tenv
|
||||||
- structcheck
|
- thelper
|
||||||
- typecheck
|
- tparallel
|
||||||
- unconvert
|
- unconvert
|
||||||
- unparam
|
- unparam
|
||||||
- unused
|
- wastedassign
|
||||||
- varcheck
|
|
||||||
- whitespace
|
- whitespace
|
||||||
|
|
||||||
run:
|
run:
|
||||||
|
|||||||
189
Dockerfile
189
Dockerfile
@@ -1,31 +1,36 @@
|
|||||||
ARG ALPINE_VERSION=3.13
|
ARG ALPINE_VERSION=3.15
|
||||||
ARG GO_VERSION=1.15
|
ARG GO_ALPINE_VERSION=3.15
|
||||||
|
ARG GO_VERSION=1.17
|
||||||
|
ARG XCPUTRANSLATE_VERSION=v0.6.0
|
||||||
|
ARG GOLANGCI_LINT_VERSION=v1.44.2
|
||||||
ARG BUILDPLATFORM=linux/amd64
|
ARG BUILDPLATFORM=linux/amd64
|
||||||
|
|
||||||
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS base
|
FROM --platform=${BUILDPLATFORM} qmcgaw/xcputranslate:${XCPUTRANSLATE_VERSION} AS xcputranslate
|
||||||
RUN apk --update add git
|
FROM --platform=${BUILDPLATFORM} qmcgaw/binpot:golangci-lint-${GOLANGCI_LINT_VERSION} AS golangci-lint
|
||||||
|
|
||||||
|
FROM --platform=${BUILDPLATFORM} golang:${GO_VERSION}-alpine${GO_ALPINE_VERSION} AS base
|
||||||
|
COPY --from=xcputranslate /xcputranslate /usr/local/bin/xcputranslate
|
||||||
|
RUN apk --update add git g++
|
||||||
ENV CGO_ENABLED=0
|
ENV CGO_ENABLED=0
|
||||||
|
COPY --from=golangci-lint /bin /go/bin/golangci-lint
|
||||||
WORKDIR /tmp/gobuild
|
WORKDIR /tmp/gobuild
|
||||||
COPY go.mod go.sum ./
|
COPY go.mod go.sum ./
|
||||||
RUN go mod download
|
RUN go mod download
|
||||||
COPY cmd/ ./cmd/
|
COPY cmd/ ./cmd/
|
||||||
COPY internal/ ./internal/
|
COPY internal/ ./internal/
|
||||||
|
|
||||||
FROM --platform=$BUILDPLATFORM base AS test
|
FROM --platform=${BUILDPLATFORM} base AS test
|
||||||
# Note on the go race detector:
|
# Note on the go race detector:
|
||||||
# - we set CGO_ENABLED=1 to have it enabled
|
# - we set CGO_ENABLED=1 to have it enabled
|
||||||
# - we install g++ to support the race detector
|
# - we installed g++ to support the race detector
|
||||||
ENV CGO_ENABLED=1
|
ENV CGO_ENABLED=1
|
||||||
RUN apk --update --no-cache add g++
|
ENTRYPOINT go test -race -coverpkg=./... -coverprofile=coverage.txt -covermode=atomic ./...
|
||||||
|
|
||||||
FROM --platform=$BUILDPLATFORM base AS lint
|
FROM --platform=${BUILDPLATFORM} base AS lint
|
||||||
ARG GOLANGCI_LINT_VERSION=v1.35.2
|
|
||||||
RUN wget -O- -nv https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | \
|
|
||||||
sh -s -- -b /usr/local/bin ${GOLANGCI_LINT_VERSION}
|
|
||||||
COPY .golangci.yml ./
|
COPY .golangci.yml ./
|
||||||
RUN golangci-lint run --timeout=10m
|
RUN golangci-lint run --timeout=10m
|
||||||
|
|
||||||
FROM --platform=$BUILDPLATFORM base AS tidy
|
FROM --platform=${BUILDPLATFORM} base AS tidy
|
||||||
RUN git init && \
|
RUN git init && \
|
||||||
git config user.email ci@localhost && \
|
git config user.email ci@localhost && \
|
||||||
git config user.name ci && \
|
git config user.name ci && \
|
||||||
@@ -34,27 +39,26 @@ RUN git init && \
|
|||||||
go mod tidy && \
|
go mod tidy && \
|
||||||
git diff --exit-code -- go.mod
|
git diff --exit-code -- go.mod
|
||||||
|
|
||||||
FROM --platform=$BUILDPLATFORM base AS build
|
FROM --platform=${BUILDPLATFORM} base AS build
|
||||||
COPY --from=qmcgaw/xcputranslate:v0.4.0 /xcputranslate /usr/local/bin/xcputranslate
|
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
ARG VERSION=unknown
|
ARG VERSION=unknown
|
||||||
ARG BUILD_DATE="an unknown date"
|
ARG CREATED="an unknown date"
|
||||||
ARG COMMIT=unknown
|
ARG COMMIT=unknown
|
||||||
RUN GOARCH="$(xcputranslate -field arch -targetplatform ${TARGETPLATFORM})" \
|
RUN GOARCH="$(xcputranslate translate -field arch -targetplatform ${TARGETPLATFORM})" \
|
||||||
GOARM="$(xcputranslate -field arm -targetplatform ${TARGETPLATFORM})" \
|
GOARM="$(xcputranslate translate -field arm -targetplatform ${TARGETPLATFORM})" \
|
||||||
go build -trimpath -ldflags="-s -w \
|
go build -trimpath -ldflags="-s -w \
|
||||||
-X 'main.version=$VERSION' \
|
-X 'main.version=$VERSION' \
|
||||||
-X 'main.buildDate=$BUILD_DATE' \
|
-X 'main.created=$CREATED' \
|
||||||
-X 'main.commit=$COMMIT' \
|
-X 'main.commit=$COMMIT' \
|
||||||
" -o entrypoint cmd/gluetun/main.go
|
" -o entrypoint cmd/gluetun/main.go
|
||||||
|
|
||||||
FROM alpine:${ALPINE_VERSION}
|
FROM alpine:${ALPINE_VERSION}
|
||||||
ARG VERSION=unknown
|
ARG VERSION=unknown
|
||||||
ARG BUILD_DATE="an unknown date"
|
ARG CREATED="an unknown date"
|
||||||
ARG COMMIT=unknown
|
ARG COMMIT=unknown
|
||||||
LABEL \
|
LABEL \
|
||||||
org.opencontainers.image.authors="quentin.mcgaw@gmail.com" \
|
org.opencontainers.image.authors="quentin.mcgaw@gmail.com" \
|
||||||
org.opencontainers.image.created=$BUILD_DATE \
|
org.opencontainers.image.created=$CREATED \
|
||||||
org.opencontainers.image.version=$VERSION \
|
org.opencontainers.image.version=$VERSION \
|
||||||
org.opencontainers.image.revision=$COMMIT \
|
org.opencontainers.image.revision=$COMMIT \
|
||||||
org.opencontainers.image.url="https://github.com/qdm12/gluetun" \
|
org.opencontainers.image.url="https://github.com/qdm12/gluetun" \
|
||||||
@@ -62,51 +66,71 @@ LABEL \
|
|||||||
org.opencontainers.image.source="https://github.com/qdm12/gluetun" \
|
org.opencontainers.image.source="https://github.com/qdm12/gluetun" \
|
||||||
org.opencontainers.image.title="VPN swiss-knife like client for multiple VPN providers" \
|
org.opencontainers.image.title="VPN swiss-knife like client for multiple VPN providers" \
|
||||||
org.opencontainers.image.description="VPN swiss-knife like client to tunnel to multiple VPN servers using OpenVPN, IPtables, DNS over TLS, Shadowsocks, an HTTP proxy and Alpine Linux"
|
org.opencontainers.image.description="VPN swiss-knife like client to tunnel to multiple VPN servers using OpenVPN, IPtables, DNS over TLS, Shadowsocks, an HTTP proxy and Alpine Linux"
|
||||||
ENV VPNSP=pia \
|
ENV VPN_SERVICE_PROVIDER=pia \
|
||||||
VERSION_INFORMATION=on \
|
VPN_TYPE=openvpn \
|
||||||
PROTOCOL=udp \
|
# Common VPN options
|
||||||
OPENVPN_VERBOSITY=1 \
|
VPN_ENDPOINT_IP= \
|
||||||
OPENVPN_ROOT=no \
|
VPN_ENDPOINT_PORT= \
|
||||||
OPENVPN_TARGET_IP= \
|
VPN_INTERFACE=tun0 \
|
||||||
OPENVPN_IPV6=off \
|
# OpenVPN
|
||||||
TZ= \
|
OPENVPN_PROTOCOL=udp \
|
||||||
PUID= \
|
|
||||||
PGID= \
|
|
||||||
PUBLICIP_FILE="/tmp/gluetun/ip" \
|
|
||||||
# PIA, Windscribe, Surfshark, Cyberghost, Vyprvpn, NordVPN, PureVPN only
|
|
||||||
OPENVPN_USER= \
|
OPENVPN_USER= \
|
||||||
OPENVPN_PASSWORD= \
|
OPENVPN_PASSWORD= \
|
||||||
USER_SECRETFILE=/run/secrets/openvpn_user \
|
OPENVPN_USER_SECRETFILE=/run/secrets/openvpn_user \
|
||||||
PASSWORD_SECRETFILE=/run/secrets/openvpn_password \
|
OPENVPN_PASSWORD_SECRETFILE=/run/secrets/openvpn_password \
|
||||||
REGION= \
|
OPENVPN_VERSION=2.5 \
|
||||||
# PIA only
|
OPENVPN_VERBOSITY=1 \
|
||||||
PIA_ENCRYPTION=strong \
|
OPENVPN_FLAGS= \
|
||||||
PORT_FORWARDING=off \
|
OPENVPN_CIPHERS= \
|
||||||
PORT_FORWARDING_STATUS_FILE="/tmp/gluetun/forwarded_port" \
|
OPENVPN_AUTH= \
|
||||||
# Mullvad and PureVPN only
|
OPENVPN_PROCESS_USER= \
|
||||||
COUNTRY= \
|
OPENVPN_IPV6=off \
|
||||||
# Mullvad, PureVPN, Windscribe only
|
OPENVPN_CUSTOM_CONFIG= \
|
||||||
CITY= \
|
# Wireguard
|
||||||
# Windscribe only
|
WIREGUARD_PRIVATE_KEY= \
|
||||||
SERVER_HOSTNAME= \
|
WIREGUARD_PRESHARED_KEY= \
|
||||||
# Mullvad only
|
WIREGUARD_PUBLIC_KEY= \
|
||||||
|
WIREGUARD_ADDRESSES= \
|
||||||
|
# VPN server filtering
|
||||||
|
SERVER_REGIONS= \
|
||||||
|
SERVER_COUNTRIES= \
|
||||||
|
SERVER_CITIES= \
|
||||||
|
SERVER_HOSTNAMES= \
|
||||||
|
# # Mullvad only:
|
||||||
ISP= \
|
ISP= \
|
||||||
OWNED=no \
|
OWNED_ONLY=no \
|
||||||
# Mullvad and Windscribe only
|
# # Private Internet Access only:
|
||||||
PORT= \
|
PRIVATE_INTERNET_ACCESS_OPENVPN_ENCRYPTION_PRESET= \
|
||||||
# Cyberghost only
|
PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING=off \
|
||||||
CYBERGHOST_GROUP="Premium UDP Europe" \
|
PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING_STATUS_FILE="/tmp/gluetun/forwarded_port" \
|
||||||
|
# # Cyberghost only:
|
||||||
OPENVPN_CLIENTCRT_SECRETFILE=/run/secrets/openvpn_clientcrt \
|
OPENVPN_CLIENTCRT_SECRETFILE=/run/secrets/openvpn_clientcrt \
|
||||||
OPENVPN_CLIENTKEY_SECRETFILE=/run/secrets/openvpn_clientkey \
|
OPENVPN_CLIENTKEY_SECRETFILE=/run/secrets/openvpn_clientkey \
|
||||||
# NordVPN only
|
# # Nordvpn only:
|
||||||
SERVER_NUMBER= \
|
SERVER_NUMBER= \
|
||||||
# Openvpn
|
# # PIA and ProtonVPN only:
|
||||||
OPENVPN_CIPHER= \
|
SERVER_NAMES= \
|
||||||
OPENVPN_AUTH= \
|
# # ProtonVPN only:
|
||||||
|
FREE_ONLY= \
|
||||||
|
# # Surfshark only:
|
||||||
|
MULTIHOP_ONLY= \
|
||||||
|
# Firewall
|
||||||
|
FIREWALL=on \
|
||||||
|
FIREWALL_VPN_INPUT_PORTS= \
|
||||||
|
FIREWALL_INPUT_PORTS= \
|
||||||
|
FIREWALL_OUTBOUND_SUBNETS= \
|
||||||
|
FIREWALL_DEBUG=off \
|
||||||
|
# Logging
|
||||||
|
LOG_LEVEL=info \
|
||||||
|
# Health
|
||||||
|
HEALTH_SERVER_ADDRESS=127.0.0.1:9999 \
|
||||||
|
HEALTH_ADDRESS_TO_PING=github.com \
|
||||||
|
HEALTH_VPN_DURATION_INITIAL=6s \
|
||||||
|
HEALTH_VPN_DURATION_ADDITION=5s \
|
||||||
# DNS over TLS
|
# DNS over TLS
|
||||||
DOT=on \
|
DOT=on \
|
||||||
DOT_PROVIDERS=cloudflare \
|
DOT_PROVIDERS=cloudflare \
|
||||||
DOT_PRIVATE_ADDRESS=127.0.0.1/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,169.254.0.0/16,::1/128,fc00::/7,fe80::/10,::ffff:0:0/96 \
|
DOT_PRIVATE_ADDRESS=127.0.0.1/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,169.254.0.0/16,::1/128,fc00::/7,fe80::/10,::ffff:7f00:1/104,::ffff:a00:0/104,::ffff:a9fe:0/112,::ffff:ac10:0/108,::ffff:c0a8:0/112 \
|
||||||
DOT_VERBOSITY=1 \
|
DOT_VERBOSITY=1 \
|
||||||
DOT_VERBOSITY_DETAILS=0 \
|
DOT_VERBOSITY_DETAILS=0 \
|
||||||
DOT_VALIDATION_LOGLEVEL=0 \
|
DOT_VALIDATION_LOGLEVEL=0 \
|
||||||
@@ -117,18 +141,12 @@ ENV VPNSP=pia \
|
|||||||
BLOCK_ADS=off \
|
BLOCK_ADS=off \
|
||||||
UNBLOCK= \
|
UNBLOCK= \
|
||||||
DNS_UPDATE_PERIOD=24h \
|
DNS_UPDATE_PERIOD=24h \
|
||||||
DNS_PLAINTEXT_ADDRESS=1.1.1.1 \
|
DNS_ADDRESS=127.0.0.1 \
|
||||||
DNS_KEEP_NAMESERVER=off \
|
DNS_KEEP_NAMESERVER=off \
|
||||||
# Firewall
|
|
||||||
FIREWALL=on \
|
|
||||||
FIREWALL_VPN_INPUT_PORTS= \
|
|
||||||
FIREWALL_INPUT_PORTS= \
|
|
||||||
FIREWALL_OUTBOUND_SUBNETS= \
|
|
||||||
FIREWALL_DEBUG=off \
|
|
||||||
# HTTP proxy
|
# HTTP proxy
|
||||||
HTTPPROXY= \
|
HTTPPROXY= \
|
||||||
HTTPPROXY_LOG=off \
|
HTTPPROXY_LOG=off \
|
||||||
HTTPPROXY_PORT=8888 \
|
HTTPPROXY_LISTENING_ADDRESS=":8888" \
|
||||||
HTTPPROXY_USER= \
|
HTTPPROXY_USER= \
|
||||||
HTTPPROXY_PASSWORD= \
|
HTTPPROXY_PASSWORD= \
|
||||||
HTTPPROXY_USER_SECRETFILE=/run/secrets/httpproxy_user \
|
HTTPPROXY_USER_SECRETFILE=/run/secrets/httpproxy_user \
|
||||||
@@ -136,19 +154,42 @@ ENV VPNSP=pia \
|
|||||||
# Shadowsocks
|
# Shadowsocks
|
||||||
SHADOWSOCKS=off \
|
SHADOWSOCKS=off \
|
||||||
SHADOWSOCKS_LOG=off \
|
SHADOWSOCKS_LOG=off \
|
||||||
SHADOWSOCKS_PORT=8388 \
|
SHADOWSOCKS_LISTENING_ADDRESS=":8388" \
|
||||||
SHADOWSOCKS_PASSWORD= \
|
SHADOWSOCKS_PASSWORD= \
|
||||||
SHADOWSOCKS_PASSWORD_SECRETFILE=/run/secrets/shadowsocks_password \
|
SHADOWSOCKS_PASSWORD_SECRETFILE=/run/secrets/shadowsocks_password \
|
||||||
SHADOWSOCKS_METHOD=chacha20-ietf-poly1305 \
|
SHADOWSOCKS_CIPHER=chacha20-ietf-poly1305 \
|
||||||
UPDATER_PERIOD=0
|
# Control server
|
||||||
ENTRYPOINT ["/entrypoint"]
|
HTTP_CONTROL_SERVER_ADDRESS=":8000" \
|
||||||
|
# Server data updater
|
||||||
|
UPDATER_PERIOD=0 \
|
||||||
|
UPDATER_VPN_SERVICE_PROVIDERS= \
|
||||||
|
# Public IP
|
||||||
|
PUBLICIP_FILE="/tmp/gluetun/ip" \
|
||||||
|
PUBLICIP_PERIOD=12h \
|
||||||
|
# Pprof
|
||||||
|
PPROF_ENABLED=no \
|
||||||
|
PPROF_BLOCK_PROFILE_RATE=0 \
|
||||||
|
PPROF_MUTEX_PROFILE_RATE=0 \
|
||||||
|
PPROF_HTTP_SERVER_ADDRESS=":6060" \
|
||||||
|
# Extras
|
||||||
|
VERSION_INFORMATION=on \
|
||||||
|
TZ= \
|
||||||
|
PUID= \
|
||||||
|
PGID=
|
||||||
|
ENTRYPOINT ["/gluetun-entrypoint"]
|
||||||
EXPOSE 8000/tcp 8888/tcp 8388/tcp 8388/udp
|
EXPOSE 8000/tcp 8888/tcp 8388/tcp 8388/udp
|
||||||
HEALTHCHECK --interval=5s --timeout=5s --start-period=10s --retries=1 CMD /entrypoint healthcheck
|
HEALTHCHECK --interval=5s --timeout=5s --start-period=10s --retries=1 CMD /gluetun-entrypoint healthcheck
|
||||||
RUN apk add -q --progress --no-cache --update openvpn ca-certificates iptables ip6tables unbound tzdata && \
|
ARG TARGETPLATFORM
|
||||||
rm -rf /var/cache/apk/* /etc/unbound/* /usr/sbin/unbound-* && \
|
RUN apk add --no-cache --update -l apk-tools && \
|
||||||
|
apk add --no-cache --update -X "https://dl-cdn.alpinelinux.org/alpine/v3.12/main" openvpn==2.4.11-r0 && \
|
||||||
|
mv /usr/sbin/openvpn /usr/sbin/openvpn2.4 && \
|
||||||
|
apk del openvpn && \
|
||||||
|
apk add --no-cache --update openvpn ca-certificates iptables ip6tables unbound tzdata && \
|
||||||
|
mv /usr/sbin/openvpn /usr/sbin/openvpn2.5 && \
|
||||||
|
# Fix vulnerability issue
|
||||||
|
apk add --no-cache --update busybox && \
|
||||||
|
rm -rf /var/cache/apk/* /etc/unbound/* /usr/sbin/unbound-* /etc/openvpn/*.sh /usr/lib/openvpn/plugins/openvpn-plugin-down-root.so && \
|
||||||
deluser openvpn && \
|
deluser openvpn && \
|
||||||
deluser unbound && \
|
deluser unbound && \
|
||||||
mkdir /gluetun
|
mkdir /gluetun
|
||||||
# TODO remove once SAN is added to PIA servers certificates, see https://github.com/pia-foss/manual-connections/issues/10
|
COPY --from=build /tmp/gobuild/entrypoint /gluetun-entrypoint
|
||||||
ENV GODEBUG=x509ignoreCN=0
|
|
||||||
COPY --from=build /tmp/gobuild/entrypoint /entrypoint
|
|
||||||
|
|||||||
157
README.md
157
README.md
@@ -1,116 +1,127 @@
|
|||||||
# Gluetun VPN client
|
# Gluetun VPN client
|
||||||
|
|
||||||
*Lightweight swiss-knife-like VPN client to tunnel to Private Internet Access,
|
*Lightweight swiss-knife-like VPN client to tunnel to Cyberghost, ExpressVPN, FastestVPN,
|
||||||
Mullvad, Windscribe, Surfshark Cyberghost, VyprVPN, NordVPN, PureVPN and Privado VPN servers, using Go, OpenVPN, iptables, DNS over TLS, ShadowSocks and an HTTP proxy*
|
HideMyAss, IPVanish, IVPN, Mullvad, NordVPN, Perfect Privacy, Privado, Private Internet Access, PrivateVPN,
|
||||||
|
ProtonVPN, PureVPN, Surfshark, TorGuard, VPNUnlimited, VyprVPN, WeVPN and Windscribe VPN servers
|
||||||
|
using Go, OpenVPN or Wireguard, iptables, DNS over TLS, ShadowSocks and an HTTP proxy*
|
||||||
|
|
||||||
**ANNOUNCEMENT**: *New Docker image name `qmcgaw/gluetun`*
|
**ANNOUNCEMENT**: Large settings refactor merged on 2022-06-01, please file issues if you find any problem!
|
||||||
|
|
||||||
<img height="250" src="https://raw.githubusercontent.com/qdm12/gluetun/master/title.svg?sanitize=true">
|

|
||||||
|
|
||||||
[](https://hub.docker.com/r/qmcgaw/gluetun/tags?page=1&ordering=last_updated)
|
[](https://github.com/qdm12/gluetun/actions/workflows/ci.yml)
|
||||||
[](https://hub.docker.com/r/qmcgaw/gluetun/tags)
|
|
||||||
|
|
||||||
[](https://hub.docker.com/r/qmcgaw/private-internet-access)
|
[](https://hub.docker.com/r/qmcgaw/gluetun)
|
||||||
[](https://hub.docker.com/r/qmcgaw/gluetun)
|
[](https://hub.docker.com/r/qmcgaw/gluetun)
|
||||||
|
|
||||||
|
[](https://hub.docker.com/r/qmcgaw/gluetun)
|
||||||
|
[](https://hub.docker.com/r/qmcgaw/gluetun)
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||

|
[](https://hub.docker.com/r/qmcgaw/gluetun/tags?page=1&ordering=last_updated)
|
||||||
|

|
||||||

|

|
||||||
[](https://github.com/qdm12/gluetun/commits)
|
|
||||||
|
[](https://hub.docker.com/r/qmcgaw/gluetun/tags)
|
||||||
|
|
||||||
|
[](https://github.com/qdm12/gluetun/commits/master)
|
||||||
|
[](https://github.com/qdm12/gluetun/graphs/contributors)
|
||||||
|
[](https://github.com/qdm12/gluetun/pulls?q=is%3Apr+is%3Aclosed)
|
||||||
|
[](https://github.com/qdm12/gluetun/issues)
|
||||||
|
[](https://github.com/qdm12/gluetun/issues?q=is%3Aissue+is%3Aclosed)
|
||||||
|
|
||||||
[](https://github.com/qdm12/gluetun)
|
[](https://github.com/qdm12/gluetun)
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
## Quick links
|
## Quick links
|
||||||
|
|
||||||
- Problem or suggestion?
|
- [Setup](#Setup)
|
||||||
- [Start a discussion](https://github.com/qdm12/gluetun/discussions)
|
- [Features](#Features)
|
||||||
- [Create an issue](https://github.com/qdm12/gluetun/issues)
|
- Problem?
|
||||||
- [Check the Wiki](https://github.com/qdm12/gluetun/wiki)
|
- [Check the Wiki](https://github.com/qdm12/gluetun/wiki)
|
||||||
|
- [Start a discussion](https://github.com/qdm12/gluetun/discussions)
|
||||||
|
- [Fix the Unraid template](https://github.com/qdm12/gluetun/discussions/550)
|
||||||
|
- Suggestion?
|
||||||
|
- [Create an issue](https://github.com/qdm12/gluetun/issues)
|
||||||
- [Join the Slack channel](https://join.slack.com/t/qdm12/shared_invite/enQtOTE0NjcxNTM1ODc5LTYyZmVlOTM3MGI4ZWU0YmJkMjUxNmQ4ODQ2OTAwYzMxMTlhY2Q1MWQyOWUyNjc2ODliNjFjMDUxNWNmNzk5MDk)
|
- [Join the Slack channel](https://join.slack.com/t/qdm12/shared_invite/enQtOTE0NjcxNTM1ODc5LTYyZmVlOTM3MGI4ZWU0YmJkMjUxNmQ4ODQ2OTAwYzMxMTlhY2Q1MWQyOWUyNjc2ODliNjFjMDUxNWNmNzk5MDk)
|
||||||
- Happy?
|
- Happy?
|
||||||
- Sponsor me on [github.com/sponsors/qdm12](https://github.com/sponsors/qdm12)
|
- Sponsor me on [github.com/sponsors/qdm12](https://github.com/sponsors/qdm12)
|
||||||
- Donate to [paypal.me/qmcgaw](https://www.paypal.me/qmcgaw)
|
- Donate to [paypal.me/qmcgaw](https://www.paypal.me/qmcgaw)
|
||||||
- Drop me [an email](mailto:quentin.mcgaw@gmail.com)
|
- Drop me [an email](mailto:quentin.mcgaw@gmail.com)
|
||||||
|
- Video:
|
||||||
|
|
||||||
|
[](https://youtu.be/0F6I03LQcI4)
|
||||||
|
|
||||||
|
- [Substack Console interview](https://console.substack.com/p/console-72)
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Based on Alpine 3.12 for a small Docker image of 52MB
|
- Based on Alpine 3.15 for a small Docker image of 29MB
|
||||||
- Supports **Private Internet Access**, **Mullvad**, **Windscribe**, **Surfshark**, **Cyberghost**, **Vyprvpn**, **NordVPN**, **PureVPN** and **Privado** servers
|
- Supports: **Cyberghost**, **ExpressVPN**, **FastestVPN**, **HideMyAss**, **IPVanish**, **IVPN**, **Mullvad**, **NordVPN**, **Perfect Privacy**, **Privado**, **Private Internet Access**, **PrivateVPN**, **ProtonVPN**, **PureVPN**, **Surfshark**, **TorGuard**, **VPNUnlimited**, **Vyprvpn**, **WeVPN**, **Windscribe** servers
|
||||||
- Supports Openvpn only for now
|
- Supports OpenVPN for all providers listed
|
||||||
|
- Supports Wireguard both kernelspace and userspace
|
||||||
|
- For **Mullvad**, **Ivpn** and **Windscribe**
|
||||||
|
- For **Torguard**, **VPN Unlimited** and **WeVPN** using [the custom provider](https://github.com/qdm12/gluetun/wiki/Custom-provider)
|
||||||
|
- For custom Wireguard configurations using [the custom provider](https://github.com/qdm12/gluetun/wiki/Custom-provider)
|
||||||
|
- More in progress, see [#134](https://github.com/qdm12/gluetun/issues/134)
|
||||||
- DNS over TLS baked in with service provider(s) of your choice
|
- DNS over TLS baked in with service provider(s) of your choice
|
||||||
- DNS fine blocking of malicious/ads/surveillance hostnames and IP addresses, with live update every 24 hours
|
- DNS fine blocking of malicious/ads/surveillance hostnames and IP addresses, with live update every 24 hours
|
||||||
- Choose the vpn network protocol, `udp` or `tcp`
|
- Choose the vpn network protocol, `udp` or `tcp`
|
||||||
- Built in firewall kill switch to allow traffic only with needed the VPN servers and LAN devices
|
- Built in firewall kill switch to allow traffic only with needed the VPN servers and LAN devices
|
||||||
- Built in Shadowsocks proxy (protocol based on SOCKS5 with an encryption layer, tunnels TCP+UDP)
|
- Built in Shadowsocks proxy (protocol based on SOCKS5 with an encryption layer, tunnels TCP+UDP)
|
||||||
- Built in HTTP proxy (tunnels HTTP and HTTPS through TCP)
|
- Built in HTTP proxy (tunnels HTTP and HTTPS through TCP)
|
||||||
- [Connect other containers to it](https://github.com/qdm12/gluetun/wiki/Connect-to-gluetun)
|
- [Connect other containers to it](https://github.com/qdm12/gluetun/wiki/Connect-a-container-to-gluetun)
|
||||||
- [Connect LAN devices to it](https://github.com/qdm12/gluetun/wiki/Connect-to-gluetun)
|
- [Connect LAN devices to it](https://github.com/qdm12/gluetun/wiki/Connect-a-LAN-device-to-gluetun)
|
||||||
- Compatible with amd64, i686 (32 bit), **ARM** 64 bit, ARM 32 bit v6 and v7, and even s390x as well as ppc64le 🎆
|
- Compatible with amd64, i686 (32 bit), **ARM** 64 bit, ARM 32 bit v6 and v7, and even ppc64le 🎆
|
||||||
- VPN server side port forwarding for Private Internet Access and Vyprvpn
|
- [Custom VPN server side port forwarding for Private Internet Access](https://github.com/qdm12/gluetun/wiki/Private-internet-access#vpn-server-port-forwarding)
|
||||||
- Possibility of split horizon DNS by selecting multiple DNS over TLS providers
|
- Possibility of split horizon DNS by selecting multiple DNS over TLS providers
|
||||||
- Subprograms all drop root privileges once launched
|
- Unbound subprogram drops root privileges once launched
|
||||||
- Subprograms output streams are all merged together
|
|
||||||
- Can work as a Kubernetes sidecar container, thanks @rorph
|
- Can work as a Kubernetes sidecar container, thanks @rorph
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
1. On some devices you may need to setup your tunnel kernel module on your host with `insmod /lib/modules/tun.ko` or `modprobe tun`
|
🎉 There are now instructions specific to each VPN provider with examples to help you get started as quickly as possible!
|
||||||
- [Synology users Wiki page](https://github.com/qdm12/gluetun/wiki/Synology-setup)
|
|
||||||
1. Launch the container with:
|
|
||||||
|
|
||||||
```bash
|
Go to the [Wiki](https://github.com/qdm12/gluetun/wiki)!
|
||||||
docker run -d --name gluetun --cap-add=NET_ADMIN \
|
|
||||||
-e VPNSP="private internet access" -e REGION="CA Montreal" \
|
|
||||||
-e OPENVPN_USER=js89ds7 -e OPENVPN_PASSWORD=8fd9s239G \
|
|
||||||
-v /yourpath:/gluetun \
|
|
||||||
qmcgaw/gluetun
|
|
||||||
```
|
|
||||||
|
|
||||||
or use [docker-compose.yml](https://github.com/qdm12/gluetun/blob/master/docker-compose.yml) with:
|
[🐛 Found a bug in the Wiki?!](https://github.com/qdm12/gluetun/issues/new?assignees=&labels=%F0%9F%93%84+Wiki+issue&template=wiki+issue.yml&title=Wiki+issue%3A+)
|
||||||
|
|
||||||
```bash
|
Here's a docker-compose.yml for the laziest:
|
||||||
echo "your openvpn username" > openvpn_user
|
|
||||||
echo "your openvpn password" > openvpn_password
|
|
||||||
docker-compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
You should probably check the many [environment variables](https://github.com/qdm12/gluetun/wiki/Environment-variables) available to adapt the container to your needs.
|
```yml
|
||||||
|
version: "3"
|
||||||
## Further setup
|
services:
|
||||||
|
gluetun:
|
||||||
The following points are all optional but should give you insights on all the possibilities with this container.
|
image: qmcgaw/gluetun
|
||||||
|
# container_name: gluetun
|
||||||
- Use [Docker secrets](https://github.com/qdm12/gluetun/wiki/Docker-secrets) to read your credentials instead of environment variables
|
# line above must be uncommented to allow external containers to connect. See https://github.com/qdm12/gluetun/wiki/Connect-a-container-to-gluetun#external-container-to-gluetun
|
||||||
- [Test your setup](https://github.com/qdm12/gluetun/wiki/Test-your-setup)
|
cap_add:
|
||||||
- [How to connect other containers and devices to Gluetun](https://github.com/qdm12/gluetun/wiki/Connect-to-gluetun)
|
- NET_ADMIN
|
||||||
- [VPN server side port forwarding](https://github.com/qdm12/gluetun/wiki/Port-forwarding)
|
ports:
|
||||||
- [HTTP control server](https://github.com/qdm12/gluetun/wiki/HTTP-Control-server) to automate things, restart Openvpn etc.
|
- 8888:8888/tcp # HTTP proxy
|
||||||
- Update the image with `docker pull qmcgaw/gluetun:latest`. See this [Wiki document](https://github.com/qdm12/gluetun/wiki/Docker-image-tags) for Docker tags available.
|
- 8388:8388/tcp # Shadowsocks
|
||||||
|
- 8388:8388/udp # Shadowsocks
|
||||||
|
volumes:
|
||||||
|
- /yourpath:/gluetun
|
||||||
|
environment:
|
||||||
|
# See https://github.com/qdm12/gluetun/wiki
|
||||||
|
- VPN_SERVICE_PROVIDER=ivpn
|
||||||
|
- VPN_TYPE=openvpn
|
||||||
|
# OpenVPN:
|
||||||
|
- OPENVPN_USER=
|
||||||
|
- OPENVPN_PASSWORD=
|
||||||
|
# Wireguard:
|
||||||
|
# - WIREGUARD_PRIVATE_KEY=wOEI9rqqbDwnN8/Bpp22sVz48T71vJ4fYmFWujulwUU=
|
||||||
|
# - WIREGUARD_ADDRESSES=10.64.222.21/32
|
||||||
|
# Timezone for accurate log times
|
||||||
|
- TZ=
|
||||||
|
```
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
[](https://github.com/qdm12/gluetun/master/LICENSE)
|
[](https://github.com/qdm12/gluetun/master/LICENSE)
|
||||||
|
|
||||||
## Metadata
|
|
||||||
|
|
||||||
[](https://github.com/qdm12/gluetun/commits)
|
|
||||||
[](https://github.com/qdm12/gluetun/pulls?q=is%3Apr+is%3Aclosed)
|
|
||||||
|
|
||||||
[](https://github.com/qdm12/gluetun/issues)
|
|
||||||
[](https://github.com/qdm12/gluetun/issues?q=is%3Aissue+is%3Aclosed)
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
[](https://hub.docker.com/r/qmcgaw/gluetun)
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||
|
|||||||
@@ -2,98 +2,107 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
nativeos "os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
_ "time/tzdata"
|
||||||
|
|
||||||
|
_ "github.com/breml/rootcerts"
|
||||||
"github.com/qdm12/dns/pkg/unbound"
|
"github.com/qdm12/dns/pkg/unbound"
|
||||||
"github.com/qdm12/gluetun/internal/alpine"
|
"github.com/qdm12/gluetun/internal/alpine"
|
||||||
"github.com/qdm12/gluetun/internal/cli"
|
"github.com/qdm12/gluetun/internal/cli"
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/sources"
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/sources/env"
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/sources/files"
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/sources/mux"
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/sources/secrets"
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
"github.com/qdm12/gluetun/internal/dns"
|
"github.com/qdm12/gluetun/internal/dns"
|
||||||
"github.com/qdm12/gluetun/internal/firewall"
|
"github.com/qdm12/gluetun/internal/firewall"
|
||||||
"github.com/qdm12/gluetun/internal/healthcheck"
|
"github.com/qdm12/gluetun/internal/healthcheck"
|
||||||
"github.com/qdm12/gluetun/internal/httpproxy"
|
"github.com/qdm12/gluetun/internal/httpproxy"
|
||||||
gluetunLogging "github.com/qdm12/gluetun/internal/logging"
|
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/netlink"
|
||||||
"github.com/qdm12/gluetun/internal/openvpn"
|
"github.com/qdm12/gluetun/internal/openvpn"
|
||||||
"github.com/qdm12/gluetun/internal/params"
|
"github.com/qdm12/gluetun/internal/portforward"
|
||||||
|
"github.com/qdm12/gluetun/internal/pprof"
|
||||||
"github.com/qdm12/gluetun/internal/publicip"
|
"github.com/qdm12/gluetun/internal/publicip"
|
||||||
"github.com/qdm12/gluetun/internal/routing"
|
"github.com/qdm12/gluetun/internal/routing"
|
||||||
"github.com/qdm12/gluetun/internal/server"
|
"github.com/qdm12/gluetun/internal/server"
|
||||||
"github.com/qdm12/gluetun/internal/settings"
|
|
||||||
"github.com/qdm12/gluetun/internal/shadowsocks"
|
"github.com/qdm12/gluetun/internal/shadowsocks"
|
||||||
"github.com/qdm12/gluetun/internal/storage"
|
"github.com/qdm12/gluetun/internal/storage"
|
||||||
"github.com/qdm12/gluetun/internal/unix"
|
"github.com/qdm12/gluetun/internal/tun"
|
||||||
"github.com/qdm12/gluetun/internal/updater"
|
"github.com/qdm12/gluetun/internal/updater"
|
||||||
versionpkg "github.com/qdm12/gluetun/internal/version"
|
"github.com/qdm12/gluetun/internal/vpn"
|
||||||
"github.com/qdm12/golibs/command"
|
"github.com/qdm12/golibs/command"
|
||||||
"github.com/qdm12/golibs/logging"
|
"github.com/qdm12/golibs/logging"
|
||||||
"github.com/qdm12/golibs/os"
|
"github.com/qdm12/goshutdown"
|
||||||
"github.com/qdm12/golibs/os/user"
|
"github.com/qdm12/goshutdown/goroutine"
|
||||||
|
"github.com/qdm12/goshutdown/group"
|
||||||
|
"github.com/qdm12/goshutdown/order"
|
||||||
|
"github.com/qdm12/gosplash"
|
||||||
"github.com/qdm12/updated/pkg/dnscrypto"
|
"github.com/qdm12/updated/pkg/dnscrypto"
|
||||||
)
|
)
|
||||||
|
|
||||||
//nolint:gochecknoglobals
|
//nolint:gochecknoglobals
|
||||||
var (
|
var (
|
||||||
version = "unknown"
|
version = "unknown"
|
||||||
commit = "unknown"
|
commit = "unknown"
|
||||||
buildDate = "an unknown date"
|
created = "an unknown date"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
buildInfo := models.BuildInformation{
|
buildInfo := models.BuildInformation{
|
||||||
Version: version,
|
Version: version,
|
||||||
Commit: commit,
|
Commit: commit,
|
||||||
BuildDate: buildDate,
|
Created: created,
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := context.Background()
|
background := context.Background()
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
signalCtx, stop := signal.NotifyContext(background, syscall.SIGINT, syscall.SIGTERM, os.Interrupt)
|
||||||
|
ctx, cancel := context.WithCancel(background)
|
||||||
|
|
||||||
logger, err := logging.NewLogger(logging.ConsoleEncoding, logging.InfoLevel)
|
logger := logging.New(logging.Settings{
|
||||||
if err != nil {
|
Level: logging.LevelInfo,
|
||||||
fmt.Println(err)
|
})
|
||||||
nativeos.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
args := nativeos.Args
|
args := os.Args
|
||||||
os := os.New()
|
tun := tun.New()
|
||||||
osUser := user.New()
|
netLinker := netlink.New()
|
||||||
unix := unix.New()
|
|
||||||
cli := cli.New()
|
cli := cli.New()
|
||||||
|
cmder := command.NewCmder()
|
||||||
|
|
||||||
|
envReader := env.New(logger)
|
||||||
|
filesReader := files.New()
|
||||||
|
secretsReader := secrets.New()
|
||||||
|
muxReader := mux.New(envReader, filesReader, secretsReader)
|
||||||
|
|
||||||
errorCh := make(chan error)
|
errorCh := make(chan error)
|
||||||
go func() {
|
go func() {
|
||||||
errorCh <- _main(ctx, buildInfo, args, logger, os, osUser, unix, cli)
|
errorCh <- _main(ctx, buildInfo, args, logger, muxReader, tun, netLinker, cmder, cli)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
signalsCh := make(chan nativeos.Signal, 1)
|
|
||||||
signal.Notify(signalsCh,
|
|
||||||
syscall.SIGINT,
|
|
||||||
syscall.SIGTERM,
|
|
||||||
nativeos.Interrupt,
|
|
||||||
)
|
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case signal := <-signalsCh:
|
case <-signalCtx.Done():
|
||||||
logger.Warn("Caught OS signal %s, shutting down", signal)
|
stop()
|
||||||
|
fmt.Println("")
|
||||||
|
logger.Warn("Caught OS signal, shutting down")
|
||||||
|
cancel()
|
||||||
case err := <-errorCh:
|
case err := <-errorCh:
|
||||||
|
stop()
|
||||||
close(errorCh)
|
close(errorCh)
|
||||||
if err == nil { // expected exit such as healthcheck
|
if err == nil { // expected exit such as healthcheck
|
||||||
nativeos.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
logger.Error(err)
|
logger.Error(err.Error())
|
||||||
|
cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
cancel()
|
|
||||||
|
|
||||||
const shutdownGracePeriod = 5 * time.Second
|
const shutdownGracePeriod = 5 * time.Second
|
||||||
timer := time.NewTimer(shutdownGracePeriod)
|
timer := time.NewTimer(shutdownGracePeriod)
|
||||||
select {
|
select {
|
||||||
@@ -106,102 +115,81 @@ func main() {
|
|||||||
logger.Warn("Shutdown timed out")
|
logger.Warn("Shutdown timed out")
|
||||||
}
|
}
|
||||||
|
|
||||||
nativeos.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:gocognit,gocyclo
|
var (
|
||||||
func _main(background context.Context, buildInfo models.BuildInformation,
|
errCommandUnknown = errors.New("command is unknown")
|
||||||
args []string, logger logging.Logger, os os.OS, osUser user.OSUser, unix unix.Unix,
|
)
|
||||||
cli cli.CLI) error {
|
|
||||||
|
//nolint:gocognit,gocyclo,maintidx
|
||||||
|
func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||||
|
args []string, logger logging.ParentLogger, source sources.Source,
|
||||||
|
tun tun.Interface, netLinker netlink.NetLinker, cmder command.RunStarter,
|
||||||
|
cli cli.CLIer) error {
|
||||||
if len(args) > 1 { // cli operation
|
if len(args) > 1 { // cli operation
|
||||||
switch args[1] {
|
switch args[1] {
|
||||||
case "healthcheck":
|
case "healthcheck":
|
||||||
return cli.HealthCheck(background)
|
return cli.HealthCheck(ctx, source, logger)
|
||||||
case "clientkey":
|
case "clientkey":
|
||||||
return cli.ClientKey(args[2:], os.OpenFile)
|
return cli.ClientKey(args[2:])
|
||||||
case "openvpnconfig":
|
case "openvpnconfig":
|
||||||
return cli.OpenvpnConfig(os)
|
return cli.OpenvpnConfig(logger, source)
|
||||||
case "update":
|
case "update":
|
||||||
return cli.Update(args[2:], os)
|
return cli.Update(ctx, args[2:], logger)
|
||||||
|
case "format-servers":
|
||||||
|
return cli.FormatServers(args[2:])
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("command %q is unknown", args[1])
|
return fmt.Errorf("%w: %s", errCommandUnknown, args[1])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ctx, cancel := context.WithCancel(background)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
const clientTimeout = 15 * time.Second
|
announcementExp, err := time.Parse(time.RFC3339, "2021-02-15T00:00:00Z")
|
||||||
httpClient := &http.Client{Timeout: clientTimeout}
|
if err != nil {
|
||||||
// Create configurators
|
return err
|
||||||
alpineConf := alpine.NewConfigurator(os.OpenFile, osUser)
|
}
|
||||||
ovpnConf := openvpn.NewConfigurator(logger, os, unix)
|
splashSettings := gosplash.Settings{
|
||||||
dnsCrypto := dnscrypto.New(httpClient, "", "")
|
User: "qdm12",
|
||||||
const cacertsPath = "/etc/ssl/certs/ca-certificates.crt"
|
Repository: "gluetun",
|
||||||
dnsConf := unbound.NewConfigurator(logger, os.OpenFile, dnsCrypto,
|
Emails: []string{"quentin.mcgaw@gmail.com"},
|
||||||
"/etc/unbound", "/usr/sbin/unbound", cacertsPath)
|
Version: buildInfo.Version,
|
||||||
routingConf := routing.NewRouting(logger)
|
Commit: buildInfo.Commit,
|
||||||
firewallConf := firewall.NewConfigurator(logger, routingConf, os.OpenFile)
|
BuildDate: buildInfo.Created,
|
||||||
streamMerger := command.NewStreamMerger()
|
Announcement: "Large settings parsing refactoring merged on 2022-01-06, please report any issue!",
|
||||||
|
AnnounceExp: announcementExp,
|
||||||
|
// Sponsor information
|
||||||
|
PaypalUser: "qmcgaw",
|
||||||
|
GithubSponsor: "qdm12",
|
||||||
|
}
|
||||||
|
for _, line := range gosplash.MakeLines(splashSettings) {
|
||||||
|
fmt.Println(line)
|
||||||
|
}
|
||||||
|
|
||||||
paramsReader := params.NewReader(logger, os)
|
allSettings, err := source.Read()
|
||||||
fmt.Println(gluetunLogging.Splash(buildInfo))
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
printVersions(ctx, logger, map[string]func(ctx context.Context) (string, error){
|
// Note: no need to validate minimal settings for the firewall:
|
||||||
"OpenVPN": ovpnConf.Version,
|
// - global log level is parsed from source
|
||||||
"Unbound": dnsConf.Version,
|
// - firewall Debug and Enabled are booleans parsed from source
|
||||||
"IPtables": firewallConf.Version,
|
|
||||||
|
logger.PatchLevel(*allSettings.Log.Level)
|
||||||
|
|
||||||
|
routingLogger := logger.NewChild(logging.Settings{
|
||||||
|
Prefix: "routing: ",
|
||||||
})
|
})
|
||||||
|
if *allSettings.Firewall.Debug { // To remove in v4
|
||||||
allSettings, warnings, err := settings.GetAllSettings(paramsReader)
|
routingLogger.PatchLevel(logging.LevelDebug)
|
||||||
for _, warning := range warnings {
|
|
||||||
logger.Warn(warning)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
logger.Info(allSettings.String())
|
|
||||||
|
|
||||||
if err := os.MkdirAll("/tmp/gluetun", 0644); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := os.MkdirAll("/gluetun", 0644); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO run this in a loop or in openvpn to reload from file without restarting
|
|
||||||
storage := storage.New(logger, os, constants.ServersData)
|
|
||||||
allServers, err := storage.SyncServers(constants.GetAllServers())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Should never change
|
|
||||||
puid, pgid := allSettings.System.PUID, allSettings.System.PGID
|
|
||||||
|
|
||||||
const defaultUsername = "nonrootuser"
|
|
||||||
nonRootUsername, err := alpineConf.CreateUser(defaultUsername, puid)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if nonRootUsername != defaultUsername {
|
|
||||||
logger.Info("using existing username %s corresponding to user id %d", nonRootUsername, puid)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := os.Chown("/etc/unbound", puid, pgid); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if allSettings.Firewall.Debug {
|
|
||||||
firewallConf.SetDebug()
|
|
||||||
routingConf.SetDebug()
|
|
||||||
}
|
}
|
||||||
|
routingConf := routing.New(netLinker, routingLogger)
|
||||||
|
|
||||||
defaultInterface, defaultGateway, err := routingConf.DefaultRoute()
|
defaultInterface, defaultGateway, err := routingConf.DefaultRoute()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
localSubnet, err := routingConf.LocalSubnet()
|
localNetworks, err := routingConf.LocalNetworks()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -211,15 +199,104 @@ func _main(background context.Context, buildInfo models.BuildInformation,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
firewallConf.SetNetworkInformation(defaultInterface, defaultGateway, localSubnet, defaultIP)
|
firewallLogger := logger.NewChild(logging.Settings{
|
||||||
|
Prefix: "firewall: ",
|
||||||
|
})
|
||||||
|
if *allSettings.Firewall.Debug { // To remove in v4
|
||||||
|
firewallLogger.PatchLevel(logging.LevelDebug)
|
||||||
|
}
|
||||||
|
firewallConf := firewall.NewConfig(firewallLogger, cmder,
|
||||||
|
defaultInterface, defaultGateway, localNetworks, defaultIP)
|
||||||
|
if *allSettings.Firewall.Enabled {
|
||||||
|
err = firewallConf.SetEnabled(ctx, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err := routingConf.Setup(); err != nil {
|
// TODO run this in a loop or in openvpn to reload from file without restarting
|
||||||
|
storageLogger := logger.NewChild(logging.Settings{Prefix: "storage: "})
|
||||||
|
storage, err := storage.New(storageLogger, constants.ServersData)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
allServers := storage.GetServers()
|
||||||
|
|
||||||
|
err = allSettings.Validate(allServers)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
allSettings.Pprof.HTTPServer.Logger = logger
|
||||||
|
pprofServer, err := pprof.New(allSettings.Pprof)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot create Pprof server: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
puid, pgid := int(*allSettings.System.PUID), int(*allSettings.System.PGID)
|
||||||
|
|
||||||
|
const clientTimeout = 15 * time.Second
|
||||||
|
httpClient := &http.Client{Timeout: clientTimeout}
|
||||||
|
// Create configurators
|
||||||
|
alpineConf := alpine.New()
|
||||||
|
ovpnConf := openvpn.New(
|
||||||
|
logger.NewChild(logging.Settings{Prefix: "openvpn configurator: "}),
|
||||||
|
cmder, puid, pgid)
|
||||||
|
dnsCrypto := dnscrypto.New(httpClient, "", "")
|
||||||
|
const cacertsPath = "/etc/ssl/certs/ca-certificates.crt"
|
||||||
|
dnsConf := unbound.NewConfigurator(nil, cmder, dnsCrypto,
|
||||||
|
"/etc/unbound", "/usr/sbin/unbound", cacertsPath)
|
||||||
|
|
||||||
|
err = printVersions(ctx, logger, []printVersionElement{
|
||||||
|
{name: "Alpine", getVersion: alpineConf.Version},
|
||||||
|
{name: "OpenVPN 2.4", getVersion: ovpnConf.Version24},
|
||||||
|
{name: "OpenVPN 2.5", getVersion: ovpnConf.Version25},
|
||||||
|
{name: "Unbound", getVersion: dnsConf.Version},
|
||||||
|
{name: "IPtables", getVersion: func(ctx context.Context) (version string, err error) {
|
||||||
|
return firewall.Version(ctx, cmder)
|
||||||
|
}},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info(allSettings.String())
|
||||||
|
|
||||||
|
if err := os.MkdirAll("/tmp/gluetun", 0644); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := os.MkdirAll("/gluetun", 0644); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultUsername = "nonrootuser"
|
||||||
|
nonRootUsername, err := alpineConf.CreateUser(defaultUsername, puid)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot create user: %w", err)
|
||||||
|
}
|
||||||
|
if nonRootUsername != defaultUsername {
|
||||||
|
logger.Info("using existing username " + nonRootUsername + " corresponding to user id " + fmt.Sprint(puid))
|
||||||
|
}
|
||||||
|
// set it for Unbound
|
||||||
|
// TODO remove this when migrating to qdm12/dns v2
|
||||||
|
allSettings.DNS.DoT.Unbound.Username = nonRootUsername
|
||||||
|
allSettings.VPN.OpenVPN.ProcessUser = nonRootUsername
|
||||||
|
|
||||||
|
if err := os.Chown("/etc/unbound", puid, pgid); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := routingConf.Setup(); err != nil {
|
||||||
|
if strings.Contains(err.Error(), "operation not permitted") {
|
||||||
|
logger.Warn("💡 Tip: Are you passing NET_ADMIN capability to gluetun?")
|
||||||
|
}
|
||||||
|
return fmt.Errorf("cannot setup routing: %w", err)
|
||||||
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
routingConf.SetVerbose(false)
|
logger.Info("routing cleanup...")
|
||||||
if err := routingConf.TearDown(); err != nil {
|
if err := routingConf.TearDown(); err != nil {
|
||||||
logger.Error(err)
|
logger.Error("cannot teardown routing: " + err.Error())
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -230,28 +307,10 @@ func _main(background context.Context, buildInfo models.BuildInformation,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ovpnConf.CheckTUN(); err != nil {
|
const tunDevice = "/dev/net/tun"
|
||||||
logger.Warn(err)
|
if err := tun.Check(tunDevice); err != nil {
|
||||||
err = ovpnConf.CreateTUN()
|
logger.Info(err.Error() + "; creating it...")
|
||||||
if err != nil {
|
err = tun.Create(tunDevice)
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tunnelReadyCh := make(chan struct{})
|
|
||||||
dnsReadyCh := make(chan struct{})
|
|
||||||
defer close(tunnelReadyCh)
|
|
||||||
defer close(dnsReadyCh)
|
|
||||||
|
|
||||||
if allSettings.Firewall.Enabled {
|
|
||||||
err := firewallConf.SetEnabled(ctx, true) // disabled by default
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, vpnPort := range allSettings.Firewall.VPNInputPorts {
|
|
||||||
err = firewallConf.SetAllowedPort(ctx, vpnPort, string(constants.TUN))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -264,184 +323,155 @@ func _main(background context.Context, buildInfo models.BuildInformation,
|
|||||||
}
|
}
|
||||||
} // TODO move inside firewall?
|
} // TODO move inside firewall?
|
||||||
|
|
||||||
wg := &sync.WaitGroup{}
|
// Shutdown settings
|
||||||
|
const totalShutdownTimeout = 3 * time.Second
|
||||||
|
const defaultShutdownTimeout = 400 * time.Millisecond
|
||||||
|
defaultShutdownOnSuccess := func(goRoutineName string) {
|
||||||
|
logger.Info(goRoutineName + ": terminated ✔️")
|
||||||
|
}
|
||||||
|
defaultShutdownOnFailure := func(goRoutineName string, err error) {
|
||||||
|
logger.Warn(goRoutineName + ": " + err.Error() + " ⚠️")
|
||||||
|
}
|
||||||
|
defaultGroupOptions := []group.Option{
|
||||||
|
group.OptionTimeout(defaultShutdownTimeout),
|
||||||
|
group.OptionOnSuccess(defaultShutdownOnSuccess)}
|
||||||
|
|
||||||
wg.Add(1)
|
controlGroupHandler := goshutdown.NewGroupHandler("control", defaultGroupOptions...)
|
||||||
go collectStreamLines(ctx, wg, streamMerger, logger, tunnelReadyCh)
|
tickersGroupHandler := goshutdown.NewGroupHandler("tickers", defaultGroupOptions...)
|
||||||
|
otherGroupHandler := goshutdown.NewGroupHandler("other", defaultGroupOptions...)
|
||||||
|
|
||||||
openvpnLooper := openvpn.NewLooper(allSettings.OpenVPN, nonRootUsername, puid, pgid, allServers,
|
pprofReady := make(chan struct{})
|
||||||
ovpnConf, firewallConf, routingConf, logger, httpClient, os.OpenFile, streamMerger, cancel)
|
pprofHandler, pprofCtx, pprofDone := goshutdown.NewGoRoutineHandler("pprof server")
|
||||||
wg.Add(1)
|
go pprofServer.Run(pprofCtx, pprofReady, pprofDone)
|
||||||
// wait for restartOpenvpn
|
otherGroupHandler.Add(pprofHandler)
|
||||||
go openvpnLooper.Run(ctx, wg)
|
<-pprofReady
|
||||||
|
|
||||||
|
portForwardLogger := logger.NewChild(logging.Settings{Prefix: "port forwarding: "})
|
||||||
|
portForwardLooper := portforward.NewLoop(allSettings.VPN.Provider.PortForwarding,
|
||||||
|
httpClient, firewallConf, portForwardLogger)
|
||||||
|
portForwardHandler, portForwardCtx, portForwardDone := goshutdown.NewGoRoutineHandler(
|
||||||
|
"port forwarding", goroutine.OptionTimeout(time.Second))
|
||||||
|
go portForwardLooper.Run(portForwardCtx, portForwardDone)
|
||||||
|
|
||||||
|
unboundLogger := logger.NewChild(logging.Settings{Prefix: "dns over tls: "})
|
||||||
|
unboundLooper := dns.NewLoop(dnsConf, allSettings.DNS, httpClient,
|
||||||
|
unboundLogger)
|
||||||
|
dnsHandler, dnsCtx, dnsDone := goshutdown.NewGoRoutineHandler(
|
||||||
|
"unbound", goroutine.OptionTimeout(defaultShutdownTimeout))
|
||||||
|
// wait for unboundLooper.Restart or its ticker launched with RunRestartTicker
|
||||||
|
go unboundLooper.Run(dnsCtx, dnsDone)
|
||||||
|
otherGroupHandler.Add(dnsHandler)
|
||||||
|
|
||||||
|
dnsTickerHandler, dnsTickerCtx, dnsTickerDone := goshutdown.NewGoRoutineHandler(
|
||||||
|
"dns ticker", goroutine.OptionTimeout(defaultShutdownTimeout))
|
||||||
|
go unboundLooper.RunRestartTicker(dnsTickerCtx, dnsTickerDone)
|
||||||
|
controlGroupHandler.Add(dnsTickerHandler)
|
||||||
|
|
||||||
|
publicIPLooper := publicip.NewLoop(httpClient,
|
||||||
|
logger.NewChild(logging.Settings{Prefix: "ip getter: "}),
|
||||||
|
allSettings.PublicIP, puid, pgid)
|
||||||
|
pubIPHandler, pubIPCtx, pubIPDone := goshutdown.NewGoRoutineHandler(
|
||||||
|
"public IP", goroutine.OptionTimeout(defaultShutdownTimeout))
|
||||||
|
go publicIPLooper.Run(pubIPCtx, pubIPDone)
|
||||||
|
otherGroupHandler.Add(pubIPHandler)
|
||||||
|
|
||||||
|
pubIPTickerHandler, pubIPTickerCtx, pubIPTickerDone := goshutdown.NewGoRoutineHandler(
|
||||||
|
"public IP", goroutine.OptionTimeout(defaultShutdownTimeout))
|
||||||
|
go publicIPLooper.RunRestartTicker(pubIPTickerCtx, pubIPTickerDone)
|
||||||
|
tickersGroupHandler.Add(pubIPTickerHandler)
|
||||||
|
|
||||||
|
vpnLogger := logger.NewChild(logging.Settings{Prefix: "vpn: "})
|
||||||
|
vpnLooper := vpn.NewLoop(allSettings.VPN, allSettings.Firewall.VPNInputPorts,
|
||||||
|
allServers, ovpnConf, netLinker, firewallConf, routingConf, portForwardLooper,
|
||||||
|
cmder, publicIPLooper, unboundLooper, vpnLogger, httpClient,
|
||||||
|
buildInfo, *allSettings.Version.Enabled)
|
||||||
|
vpnHandler, vpnCtx, vpnDone := goshutdown.NewGoRoutineHandler(
|
||||||
|
"vpn", goroutine.OptionTimeout(time.Second))
|
||||||
|
go vpnLooper.Run(vpnCtx, vpnDone)
|
||||||
|
|
||||||
updaterLooper := updater.NewLooper(allSettings.Updater,
|
updaterLooper := updater.NewLooper(allSettings.Updater,
|
||||||
allServers, storage, openvpnLooper.SetServers, httpClient, logger)
|
allServers, storage, vpnLooper.SetServers, httpClient,
|
||||||
wg.Add(1)
|
logger.NewChild(logging.Settings{Prefix: "updater: "}))
|
||||||
|
updaterHandler, updaterCtx, updaterDone := goshutdown.NewGoRoutineHandler(
|
||||||
|
"updater", goroutine.OptionTimeout(defaultShutdownTimeout))
|
||||||
// wait for updaterLooper.Restart() or its ticket launched with RunRestartTicker
|
// wait for updaterLooper.Restart() or its ticket launched with RunRestartTicker
|
||||||
go updaterLooper.Run(ctx, wg)
|
go updaterLooper.Run(updaterCtx, updaterDone)
|
||||||
|
tickersGroupHandler.Add(updaterHandler)
|
||||||
|
|
||||||
unboundLooper := dns.NewLooper(dnsConf, allSettings.DNS, httpClient,
|
updaterTickerHandler, updaterTickerCtx, updaterTickerDone := goshutdown.NewGoRoutineHandler(
|
||||||
logger, streamMerger, nonRootUsername, puid, pgid)
|
"updater ticker", goroutine.OptionTimeout(defaultShutdownTimeout))
|
||||||
wg.Add(1)
|
go updaterLooper.RunRestartTicker(updaterTickerCtx, updaterTickerDone)
|
||||||
// wait for unboundLooper.Restart or its ticker launched with RunRestartTicker
|
controlGroupHandler.Add(updaterTickerHandler)
|
||||||
go unboundLooper.Run(ctx, wg, dnsReadyCh)
|
|
||||||
|
|
||||||
publicIPLooper := publicip.NewLooper(
|
httpProxyLooper := httpproxy.NewLoop(
|
||||||
httpClient, logger, allSettings.PublicIP, puid, pgid, os)
|
logger.NewChild(logging.Settings{Prefix: "http proxy: "}),
|
||||||
wg.Add(1)
|
allSettings.HTTPProxy)
|
||||||
go publicIPLooper.Run(ctx, wg)
|
httpProxyHandler, httpProxyCtx, httpProxyDone := goshutdown.NewGoRoutineHandler(
|
||||||
wg.Add(1)
|
"http proxy", goroutine.OptionTimeout(defaultShutdownTimeout))
|
||||||
go publicIPLooper.RunRestartTicker(ctx, wg)
|
go httpProxyLooper.Run(httpProxyCtx, httpProxyDone)
|
||||||
|
otherGroupHandler.Add(httpProxyHandler)
|
||||||
|
|
||||||
httpProxyLooper := httpproxy.NewLooper(logger, allSettings.HTTPProxy)
|
shadowsocksLooper := shadowsocks.NewLooper(allSettings.Shadowsocks,
|
||||||
wg.Add(1)
|
logger.NewChild(logging.Settings{Prefix: "shadowsocks: "}))
|
||||||
go httpProxyLooper.Run(ctx, wg)
|
shadowsocksHandler, shadowsocksCtx, shadowsocksDone := goshutdown.NewGoRoutineHandler(
|
||||||
|
"shadowsocks proxy", goroutine.OptionTimeout(defaultShutdownTimeout))
|
||||||
|
go shadowsocksLooper.Run(shadowsocksCtx, shadowsocksDone)
|
||||||
|
otherGroupHandler.Add(shadowsocksHandler)
|
||||||
|
|
||||||
shadowsocksLooper := shadowsocks.NewLooper(allSettings.ShadowSocks, logger)
|
controlServerAddress := *allSettings.ControlServer.Address
|
||||||
wg.Add(1)
|
controlServerLogging := *allSettings.ControlServer.Log
|
||||||
go shadowsocksLooper.Run(ctx, wg)
|
httpServerHandler, httpServerCtx, httpServerDone := goshutdown.NewGoRoutineHandler(
|
||||||
|
"http server", goroutine.OptionTimeout(defaultShutdownTimeout))
|
||||||
|
httpServer := server.New(httpServerCtx, controlServerAddress, controlServerLogging,
|
||||||
|
logger.NewChild(logging.Settings{Prefix: "http server: "}),
|
||||||
|
buildInfo, vpnLooper, portForwardLooper, unboundLooper, updaterLooper, publicIPLooper)
|
||||||
|
go httpServer.Run(httpServerCtx, httpServerDone)
|
||||||
|
controlGroupHandler.Add(httpServerHandler)
|
||||||
|
|
||||||
wg.Add(1)
|
healthLogger := logger.NewChild(logging.Settings{Prefix: "healthcheck: "})
|
||||||
go routeReadyEvents(ctx, wg, buildInfo, tunnelReadyCh, dnsReadyCh,
|
healthcheckServer := healthcheck.NewServer(allSettings.Health, healthLogger, vpnLooper)
|
||||||
unboundLooper, updaterLooper, publicIPLooper, routingConf, logger, httpClient,
|
healthServerHandler, healthServerCtx, healthServerDone := goshutdown.NewGoRoutineHandler(
|
||||||
allSettings.VersionInformation, allSettings.OpenVPN.Provider.PortForwarding.Enabled, openvpnLooper.PortForward,
|
"HTTP health server", goroutine.OptionTimeout(defaultShutdownTimeout))
|
||||||
)
|
go healthcheckServer.Run(healthServerCtx, healthServerDone)
|
||||||
controlServerAddress := fmt.Sprintf("0.0.0.0:%d", allSettings.ControlServer.Port)
|
|
||||||
controlServerLogging := allSettings.ControlServer.Log
|
|
||||||
httpServer := server.New(controlServerAddress, controlServerLogging,
|
|
||||||
logger, buildInfo, openvpnLooper, unboundLooper, updaterLooper, publicIPLooper)
|
|
||||||
wg.Add(1)
|
|
||||||
go httpServer.Run(ctx, wg)
|
|
||||||
|
|
||||||
healthcheckServer := healthcheck.NewServer(
|
orderHandler := goshutdown.NewOrderHandler("gluetun",
|
||||||
constants.HealthcheckAddress, logger)
|
order.OptionTimeout(totalShutdownTimeout),
|
||||||
wg.Add(1)
|
order.OptionOnSuccess(defaultShutdownOnSuccess),
|
||||||
go healthcheckServer.Run(ctx, wg)
|
order.OptionOnFailure(defaultShutdownOnFailure))
|
||||||
|
orderHandler.Append(controlGroupHandler, tickersGroupHandler, healthServerHandler,
|
||||||
|
vpnHandler, portForwardHandler, otherGroupHandler)
|
||||||
|
|
||||||
// Start openvpn for the first time in a blocking call
|
// Start VPN for the first time in a blocking call
|
||||||
// until openvpn is launched
|
// until the VPN is launched
|
||||||
_, _ = openvpnLooper.SetStatus(constants.Running) // TODO option to disable with variable
|
_, _ = vpnLooper.ApplyStatus(ctx, constants.Running) // TODO option to disable with variable
|
||||||
|
|
||||||
<-ctx.Done()
|
<-ctx.Done()
|
||||||
|
|
||||||
if allSettings.OpenVPN.Provider.PortForwarding.Enabled {
|
return orderHandler.Shutdown(context.Background())
|
||||||
logger.Info("Clearing forwarded port status file %s", allSettings.OpenVPN.Provider.PortForwarding.Filepath)
|
|
||||||
if err := os.Remove(string(allSettings.OpenVPN.Provider.PortForwarding.Filepath)); err != nil {
|
|
||||||
logger.Error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func printVersions(ctx context.Context, logger logging.Logger,
|
type printVersionElement struct {
|
||||||
versionFunctions map[string]func(ctx context.Context) (string, error)) {
|
name string
|
||||||
|
getVersion func(ctx context.Context) (version string, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type infoer interface {
|
||||||
|
Info(s string)
|
||||||
|
}
|
||||||
|
|
||||||
|
func printVersions(ctx context.Context, logger infoer,
|
||||||
|
elements []printVersionElement) (err error) {
|
||||||
const timeout = 5 * time.Second
|
const timeout = 5 * time.Second
|
||||||
ctx, cancel := context.WithTimeout(ctx, timeout)
|
ctx, cancel := context.WithTimeout(ctx, timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
for name, f := range versionFunctions {
|
|
||||||
version, err := f(ctx)
|
for _, element := range elements {
|
||||||
|
version, err := element.getVersion(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(err)
|
return err
|
||||||
} else {
|
|
||||||
logger.Info("%s version: %s", name, version)
|
|
||||||
}
|
}
|
||||||
|
logger.Info(element.name + " version: " + version)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func collectStreamLines(ctx context.Context, wg *sync.WaitGroup,
|
return nil
|
||||||
streamMerger command.StreamMerger,
|
|
||||||
logger logging.Logger, tunnelReadyCh chan<- struct{}) {
|
|
||||||
defer wg.Done()
|
|
||||||
// Blocking line merging paramsReader for openvpn and unbound
|
|
||||||
logger.Info("Launching standard output merger")
|
|
||||||
streamMerger.CollectLines(ctx, func(line string) {
|
|
||||||
line, level := gluetunLogging.PostProcessLine(line)
|
|
||||||
if line == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switch level {
|
|
||||||
case logging.DebugLevel:
|
|
||||||
logger.Debug(line)
|
|
||||||
case logging.InfoLevel:
|
|
||||||
logger.Info(line)
|
|
||||||
case logging.WarnLevel:
|
|
||||||
logger.Warn(line)
|
|
||||||
case logging.ErrorLevel:
|
|
||||||
logger.Error(line)
|
|
||||||
}
|
|
||||||
switch {
|
|
||||||
case strings.Contains(line, "Initialization Sequence Completed"):
|
|
||||||
tunnelReadyCh <- struct{}{}
|
|
||||||
case strings.Contains(line, "TLS Error: TLS key negotiation failed to occur within 60 seconds (check your network connectivity)"): //nolint:lll
|
|
||||||
logger.Warn("This means that either...")
|
|
||||||
logger.Warn("1. The VPN server IP address you are trying to connect to is no longer valid, see https://github.com/qdm12/gluetun/wiki/Update-servers-information") //nolint:lll
|
|
||||||
logger.Warn("2. The VPN server crashed, try changing region")
|
|
||||||
logger.Warn("3. Your Internet connection is not working, ensure it works")
|
|
||||||
logger.Warn("Feel free to create an issue at https://github.com/qdm12/gluetun/issues/new/choose")
|
|
||||||
}
|
|
||||||
}, func(err error) {
|
|
||||||
logger.Warn(err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func routeReadyEvents(ctx context.Context, wg *sync.WaitGroup, buildInfo models.BuildInformation,
|
|
||||||
tunnelReadyCh, dnsReadyCh <-chan struct{},
|
|
||||||
unboundLooper dns.Looper, updaterLooper updater.Looper, publicIPLooper publicip.Looper,
|
|
||||||
routing routing.Routing, logger logging.Logger, httpClient *http.Client,
|
|
||||||
versionInformation, portForwardingEnabled bool, startPortForward func(vpnGateway net.IP)) {
|
|
||||||
defer wg.Done()
|
|
||||||
tickerWg := &sync.WaitGroup{}
|
|
||||||
// for linters only
|
|
||||||
var restartTickerContext context.Context
|
|
||||||
var restartTickerCancel context.CancelFunc = func() {}
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
restartTickerCancel() // for linters only
|
|
||||||
tickerWg.Wait()
|
|
||||||
return
|
|
||||||
case <-tunnelReadyCh: // blocks until openvpn is connected
|
|
||||||
if unboundLooper.GetSettings().Enabled {
|
|
||||||
_, _ = unboundLooper.SetStatus(constants.Running)
|
|
||||||
}
|
|
||||||
restartTickerCancel() // stop previous restart tickers
|
|
||||||
tickerWg.Wait()
|
|
||||||
restartTickerContext, restartTickerCancel = context.WithCancel(ctx)
|
|
||||||
//nolint:gomnd
|
|
||||||
tickerWg.Add(2)
|
|
||||||
go unboundLooper.RunRestartTicker(restartTickerContext, tickerWg)
|
|
||||||
go updaterLooper.RunRestartTicker(restartTickerContext, tickerWg)
|
|
||||||
vpnDestination, err := routing.VPNDestinationIP()
|
|
||||||
if err != nil {
|
|
||||||
logger.Warn(err)
|
|
||||||
} else {
|
|
||||||
logger.Info("VPN routing IP address: %s", vpnDestination)
|
|
||||||
}
|
|
||||||
if portForwardingEnabled {
|
|
||||||
// vpnGateway required only for PIA
|
|
||||||
vpnGateway, err := routing.VPNLocalGatewayIP()
|
|
||||||
if err != nil {
|
|
||||||
logger.Error(err)
|
|
||||||
}
|
|
||||||
logger.Info("VPN gateway IP address: %s", vpnGateway)
|
|
||||||
startPortForward(vpnGateway)
|
|
||||||
}
|
|
||||||
case <-dnsReadyCh:
|
|
||||||
// Runs the Public IP getter job once
|
|
||||||
_, _ = publicIPLooper.SetStatus(constants.Running)
|
|
||||||
if !versionInformation {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
message, err := versionpkg.GetMessage(ctx, buildInfo, httpClient)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error(err)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
logger.Info(message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
version: "3.7"
|
|
||||||
services:
|
|
||||||
gluetun:
|
|
||||||
image: qmcgaw/gluetun
|
|
||||||
container_name: gluetun
|
|
||||||
cap_add:
|
|
||||||
- NET_ADMIN
|
|
||||||
network_mode: bridge
|
|
||||||
ports:
|
|
||||||
- 8888:8888/tcp # HTTP proxy
|
|
||||||
- 8388:8388/tcp # Shadowsocks
|
|
||||||
- 8388:8388/udp # Shadowsocks
|
|
||||||
- 8000:8000/tcp # Built-in HTTP control server
|
|
||||||
# command:
|
|
||||||
volumes:
|
|
||||||
- /yourpath:/gluetun
|
|
||||||
secrets:
|
|
||||||
- openvpn_user
|
|
||||||
- openvpn_password
|
|
||||||
environment:
|
|
||||||
# More variables are available, see the readme table
|
|
||||||
- VPNSP=private internet access
|
|
||||||
# Timezone for accurate logs times
|
|
||||||
- TZ=
|
|
||||||
restart: always
|
|
||||||
|
|
||||||
secrets:
|
|
||||||
openvpn_user:
|
|
||||||
file: ./openvpn_user
|
|
||||||
openvpn_password:
|
|
||||||
file: ./openvpn_password
|
|
||||||
50
go.mod
50
go.mod
@@ -1,16 +1,46 @@
|
|||||||
module github.com/qdm12/gluetun
|
module github.com/qdm12/gluetun
|
||||||
|
|
||||||
go 1.15
|
go 1.17
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/fatih/color v1.10.0
|
github.com/breml/rootcerts v0.2.2
|
||||||
github.com/golang/mock v1.4.4
|
github.com/fatih/color v1.13.0
|
||||||
github.com/kyokomi/emoji v2.2.4+incompatible
|
github.com/go-ping/ping v0.0.0-20210911151512-381826476871
|
||||||
github.com/qdm12/dns v1.4.0-rc4
|
github.com/golang/mock v1.6.0
|
||||||
github.com/qdm12/golibs v0.0.0-20210110211000-0a3a4541ae09
|
github.com/qdm12/dns v1.11.0
|
||||||
github.com/qdm12/ss-server v0.1.0
|
github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6
|
||||||
github.com/qdm12/updated v0.0.0-20210102005021-dd457d77f94a
|
github.com/qdm12/goshutdown v0.3.0
|
||||||
|
github.com/qdm12/gosplash v0.1.0
|
||||||
|
github.com/qdm12/gotree v0.2.0
|
||||||
|
github.com/qdm12/govalid v0.1.0
|
||||||
|
github.com/qdm12/ss-server v0.4.0
|
||||||
|
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e
|
||||||
github.com/stretchr/testify v1.7.0
|
github.com/stretchr/testify v1.7.0
|
||||||
github.com/vishvananda/netlink v1.1.0
|
github.com/vishvananda/netlink v1.1.1-0.20211129163951-9ada19101fc5
|
||||||
golang.org/x/sys v0.0.0-20201223074533-0d417f636930
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
|
||||||
|
golang.zx2c4.com/wireguard v0.0.0-20210805125648-3957e9b9dd19
|
||||||
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c
|
||||||
|
inet.af/netaddr v0.0.0-20210718074554-06ca8145d722
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/google/go-cmp v0.5.5 // indirect
|
||||||
|
github.com/google/uuid v1.2.0 // indirect
|
||||||
|
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.9 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||||
|
github.com/mdlayher/genetlink v1.0.0 // indirect
|
||||||
|
github.com/mdlayher/netlink v1.4.0 // indirect
|
||||||
|
github.com/miekg/dns v1.1.40 // indirect
|
||||||
|
github.com/mr-tron/base58 v1.2.0 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
||||||
|
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae // indirect
|
||||||
|
go4.org/intern v0.0.0-20210108033219-3eb7198706b2 // indirect
|
||||||
|
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063 // indirect
|
||||||
|
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
|
||||||
|
golang.org/x/net v0.0.0-20210504132125-bbd867fde50d // indirect
|
||||||
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
251
go.sum
251
go.sum
@@ -1,183 +1,249 @@
|
|||||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
|
||||||
github.com/PuerkitoBio/purell v1.1.0 h1:rmGxhojJlM0tuKtfdvliR84CFHljx9ag64t2xmVkjK4=
|
|
||||||
github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
|
||||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||||
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
|
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||||
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf h1:eg0MeVzsP1G42dRafH3vf+al2vQIJU0YHX+1Tw87oco=
|
|
||||||
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||||
|
github.com/breml/rootcerts v0.2.2 h1:hkHEpbTdYaNvDoYeq+mwRvCeg/YTTl23DjQ1Tnj71Zs=
|
||||||
|
github.com/breml/rootcerts v0.2.2/go.mod h1:24FDtzYMpqIeYC7QzaE8VPRQaFZU5TIUDlyk8qwjD88=
|
||||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
|
github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
|
||||||
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
|
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
|
||||||
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
|
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||||
github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
|
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||||
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
|
||||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||||
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||||
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb h1:D4uzjWwKYQ5XnAvUbuvHW93esHg7F8N/OYeBBcJoTr0=
|
|
||||||
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||||
github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI=
|
github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI=
|
||||||
github.com/go-openapi/analysis v0.17.0 h1:8JV+dzJJiK46XqGLqqLav8ZfEiJECp8jlOFhpiCdZ+0=
|
|
||||||
github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
|
github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
|
||||||
github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
|
github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
|
||||||
github.com/go-openapi/errors v0.17.2 h1:azEQ8Fnx0jmtFF2fxsnmd6I0x6rsweUF63qqSO1NmKk=
|
|
||||||
github.com/go-openapi/errors v0.17.2/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
|
github.com/go-openapi/errors v0.17.2/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
|
||||||
github.com/go-openapi/jsonpointer v0.17.0 h1:nH6xp8XdXHx8dqveo0ZuJBluCO2qGrPbDNZ0dwoRHP0=
|
|
||||||
github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
|
github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
|
||||||
github.com/go-openapi/jsonreference v0.17.0 h1:yJW3HCkTHg7NOA+gZ83IPHzUSnUzGXhGmsdiCcMexbA=
|
|
||||||
github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
|
github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
|
||||||
github.com/go-openapi/loads v0.17.0 h1:H22nMs3GDQk4SwAaFQ+jLNw+0xoFeCueawhZlv8MBYs=
|
|
||||||
github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
|
github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
|
||||||
github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA=
|
github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA=
|
||||||
github.com/go-openapi/runtime v0.17.2 h1:/ZK67ikFhQAMFFH/aPu2MaGH7QjP4wHBvHYOVIzDAw0=
|
|
||||||
github.com/go-openapi/runtime v0.17.2/go.mod h1:QO936ZXeisByFmZEO1IS1Dqhtf4QV1sYYFtIq6Ld86Q=
|
github.com/go-openapi/runtime v0.17.2/go.mod h1:QO936ZXeisByFmZEO1IS1Dqhtf4QV1sYYFtIq6Ld86Q=
|
||||||
github.com/go-openapi/spec v0.17.0 h1:XNvrt8FlSVP8T1WuhbAFF6QDhJc0zsoWzX4wXARhhpE=
|
|
||||||
github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
|
github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
|
||||||
github.com/go-openapi/strfmt v0.17.0 h1:1isAxYf//QDTnVzbLAMrUK++0k1EjeLJU/gTOR0o3Mc=
|
|
||||||
github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
|
github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
|
||||||
github.com/go-openapi/swag v0.17.0 h1:iqrgMg7Q7SvtbWLlltPrkMs0UBJI6oTSs79JFRUi880=
|
|
||||||
github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
|
github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
|
||||||
github.com/go-openapi/validate v0.17.0 h1:pqoViQz3YLOGIhAmD0N4Lt6pa/3Gnj3ymKqQwq8iS6U=
|
|
||||||
github.com/go-openapi/validate v0.17.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=
|
github.com/go-openapi/validate v0.17.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=
|
||||||
github.com/golang/mock v1.4.3 h1:GV+pQPG/EUUbkh47niozDcADz6go/dUwhVzdUQHIVRw=
|
github.com/go-ping/ping v0.0.0-20210911151512-381826476871 h1:wtjTfjwAR/BYYMJ+QOLI/3J/qGEI0fgrkZvgsEWK2/Q=
|
||||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
github.com/go-ping/ping v0.0.0-20210911151512-381826476871/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk=
|
||||||
github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
|
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
|
||||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||||
|
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||||
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
||||||
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||||
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gotify/go-api-client/v2 v2.0.4 h1:0w8skCr8aLBDKaQDg31LKKHUGF7rt7zdRpR+6cqIAlE=
|
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
|
||||||
|
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gotify/go-api-client/v2 v2.0.4/go.mod h1:VKiah/UK20bXsr0JObE1eBVLW44zbBouzjuri9iwjFU=
|
github.com/gotify/go-api-client/v2 v2.0.4/go.mod h1:VKiah/UK20bXsr0JObE1eBVLW44zbBouzjuri9iwjFU=
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
|
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 h1:uhL5Gw7BINiiPAo24A2sxkcDI0Jt/sqp1v5xQCniEFA=
|
||||||
|
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||||
|
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
|
||||||
|
github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ=
|
||||||
|
github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok=
|
||||||
|
github.com/jsimonetti/rtnetlink v0.0.0-20201216134343-bde56ed16391/go.mod h1:cR77jAZG3Y3bsb8hF6fHJbFoyFukLFOkQ98S0pQz3xw=
|
||||||
|
github.com/jsimonetti/rtnetlink v0.0.0-20201220180245-69540ac93943/go.mod h1:z4c53zj6Eex712ROyh8WI0ihysb5j2ROyV42iNogmAs=
|
||||||
|
github.com/jsimonetti/rtnetlink v0.0.0-20210122163228-8d122574c736/go.mod h1:ZXpIyOK59ZnN7J0BV99cZUPmsqDRZ3eq5X+st7u/oSA=
|
||||||
|
github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b h1:c3NTyLNozICy8B4mlMXemD3z/gXgQzVXZS/HqT+i3do=
|
||||||
|
github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b/go.mod h1:8w9Rh8m+aHZIG69YPGGem1i5VzoyRC8nw2kA8B+ik5U=
|
||||||
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
|
||||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
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/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kyokomi/emoji v2.2.4+incompatible h1:np0woGKwx9LiHAQmwZx79Oc0rHpNw3o+3evou4BEPv4=
|
|
||||||
github.com/kyokomi/emoji v2.2.4+incompatible/go.mod h1:mZ6aGCD7yk8j6QY6KICwnZ2pxoszVseX1DNoGtU2tBA=
|
github.com/kyokomi/emoji v2.2.4+incompatible/go.mod h1:mZ6aGCD7yk8j6QY6KICwnZ2pxoszVseX1DNoGtU2tBA=
|
||||||
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329 h1:2gxZ0XQIU/5z3Z3bUBu+FXuk2pFbkN6tcwi/pjyaDic=
|
|
||||||
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
|
|
||||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
|
||||||
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
|
|
||||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U=
|
||||||
github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
|
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
|
||||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
|
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||||
|
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||||
|
github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43 h1:WgyLFv10Ov49JAQI/ZLUkCZ7VJS3r74hwFIGXJsgZlY=
|
||||||
|
github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43/go.mod h1:+t7E0lkKfbBsebllff1xdTmyJt8lH37niI6kwFk9OTo=
|
||||||
|
github.com/mdlayher/genetlink v1.0.0 h1:OoHN1OdyEIkScEmRgxLEe2M9U8ClMytqA5niynLtfj0=
|
||||||
|
github.com/mdlayher/genetlink v1.0.0/go.mod h1:0rJ0h4itni50A86M2kHcgS85ttZazNt7a8H2a2cw0Gc=
|
||||||
|
github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
|
||||||
|
github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M=
|
||||||
|
github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY=
|
||||||
|
github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o=
|
||||||
|
github.com/mdlayher/netlink v1.2.0/go.mod h1:kwVW1io0AZy9A1E2YYgaD4Cj+C+GPkU6klXCMzIJ9p8=
|
||||||
|
github.com/mdlayher/netlink v1.2.1/go.mod h1:bacnNlfhqHqqLo4WsYeXSqfyXkInQ9JneWI68v1KwSU=
|
||||||
|
github.com/mdlayher/netlink v1.2.2-0.20210123213345-5cc92139ae3e/go.mod h1:bacnNlfhqHqqLo4WsYeXSqfyXkInQ9JneWI68v1KwSU=
|
||||||
|
github.com/mdlayher/netlink v1.3.0/go.mod h1:xK/BssKuwcRXHrtN04UBkwQ6dY9VviGGuriDdoPSWys=
|
||||||
|
github.com/mdlayher/netlink v1.4.0 h1:n3ARR+Fm0dDv37dj5wSWZXDKcy+U0zwcXS3zKMnSiT0=
|
||||||
|
github.com/mdlayher/netlink v1.4.0/go.mod h1:dRJi5IABcZpBD2A3D0Mv/AiX8I9uDEu5oGkAVrekmf8=
|
||||||
|
github.com/miekg/dns v1.1.40 h1:pyyPFfGMnciYUk/mXpKkVmeMQjfXqt3FAJ2hy7tPiLA=
|
||||||
|
github.com/miekg/dns v1.1.40/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||||
|
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws=
|
||||||
|
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc=
|
||||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
|
||||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
github.com/mr-tron/base58 v1.1.3 h1:v+sk57XuaCKGXpWtVBX8YJzO7hMGx4Aajh4TQbdEFdc=
|
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
|
||||||
github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
|
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
|
||||||
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
|
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
|
||||||
github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo=
|
github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo=
|
||||||
github.com/phayes/permbits v0.0.0-20190612203442-39d7c581d2ee h1:P6U24L02WMfj9ymZTxl7CxS73JC99x3ukk+DBkgQGQs=
|
|
||||||
github.com/phayes/permbits v0.0.0-20190612203442-39d7c581d2ee/go.mod h1:3uODdxMgOaPYeWU7RzZLxVtJHZ/x1f/iHkBZuKJDzuY=
|
github.com/phayes/permbits v0.0.0-20190612203442-39d7c581d2ee/go.mod h1:3uODdxMgOaPYeWU7RzZLxVtJHZ/x1f/iHkBZuKJDzuY=
|
||||||
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.8.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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/qdm12/dns v1.4.0-rc4 h1:pCcFMqismbktPQX7yrtdmJZq30Y05JBfWRTXY1ZLVFw=
|
github.com/qdm12/dns v1.11.0 h1:jpcD5DZXXQSQe5a263PL09ghukiIdptvXFOZvyKEm6Q=
|
||||||
github.com/qdm12/dns v1.4.0-rc4/go.mod h1:JhUKBhuDRYBUQ2XwW/jbeWx/qS0sSJjIFjGTCFGP5I8=
|
github.com/qdm12/dns v1.11.0/go.mod h1:FmQsNOUcrrZ4UFzWAiED56AKXeNgaX3ySbmPwEfNjjE=
|
||||||
github.com/qdm12/golibs v0.0.0-20201227203847-2fd99ffdfdba/go.mod h1:pikkTN7g7zRuuAnERwqW1yAFq6pYmxrxpjiwGvb0Ysc=
|
github.com/qdm12/golibs v0.0.0-20210603202746-e5494e9c2ebb/go.mod h1:15RBzkun0i8XB7ADIoLJWp9ITRgsz3LroEI2FiOXLRg=
|
||||||
github.com/qdm12/golibs v0.0.0-20210102020307-17bc97def973 h1:5YeJALmDjvg2wSi6XB8MpQQekbT/eBnwGahJrh01HHQ=
|
github.com/qdm12/golibs v0.0.0-20210723175634-a75ca7fd74c2/go.mod h1:6aRbg4Z/bTbm9JfxsGXfWKHi7zsOvPfUTK1S5HuAFKg=
|
||||||
github.com/qdm12/golibs v0.0.0-20210102020307-17bc97def973/go.mod h1:pikkTN7g7zRuuAnERwqW1yAFq6pYmxrxpjiwGvb0Ysc=
|
github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6 h1:bge5AL7cjHJMPz+5IOz5yF01q/l8No6+lIEBieA8gMg=
|
||||||
github.com/qdm12/golibs v0.0.0-20210110211000-0a3a4541ae09 h1:zP+ZRwV3GldgTWFgKNBQ2zoFA8mIczb+fvTvrX8LZRo=
|
github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6/go.mod h1:6aRbg4Z/bTbm9JfxsGXfWKHi7zsOvPfUTK1S5HuAFKg=
|
||||||
github.com/qdm12/golibs v0.0.0-20210110211000-0a3a4541ae09/go.mod h1:pikkTN7g7zRuuAnERwqW1yAFq6pYmxrxpjiwGvb0Ysc=
|
github.com/qdm12/goshutdown v0.3.0 h1:pqBpJkdwlZlfTEx4QHtS8u8CXx6pG0fVo6S1N0MpSEM=
|
||||||
github.com/qdm12/ss-server v0.1.0 h1:WV9MkHCDEWRwe4WpnYFeR/zcZAxYoTbfntLDnw9AQ50=
|
github.com/qdm12/goshutdown v0.3.0/go.mod h1:EqZ46No00kCTZ5qzdd3qIzY6ayhMt24QI8Mh8LVQYmM=
|
||||||
github.com/qdm12/ss-server v0.1.0/go.mod h1:ABVUkxubboL3vqBkOwDV9glX1/x7SnYrckBe5d+M/zw=
|
github.com/qdm12/gosplash v0.1.0 h1:Sfl+zIjFZFP7b0iqf2l5UkmEY97XBnaKkH3FNY6Gf7g=
|
||||||
github.com/qdm12/updated v0.0.0-20210102005021-dd457d77f94a h1:gkyP+gMEeBgMgyRYGrVNcoy6cL1065IvXsyfB6xboIc=
|
github.com/qdm12/gosplash v0.1.0/go.mod h1:+A3fWW4/rUeDXhY3ieBzwghKdnIPFJgD8K3qQkenJlw=
|
||||||
github.com/qdm12/updated v0.0.0-20210102005021-dd457d77f94a/go.mod h1:bbJGxEYCnsA8WU4vBcXYU6mOoHyzdP458FIKP4mfLJM=
|
github.com/qdm12/gotree v0.2.0 h1:+58ltxkNLUyHtATFereAcOjBVfY6ETqRex8XK90Fb/c=
|
||||||
|
github.com/qdm12/gotree v0.2.0/go.mod h1:1SdFaqKZuI46U1apbXIf25pDMNnrPuYLEqMF/qL4lY4=
|
||||||
|
github.com/qdm12/govalid v0.1.0 h1:UIFVmuaAg0Q+h0GeyfcFEZ5sQ5KJPvRQwycC1/cqDN8=
|
||||||
|
github.com/qdm12/govalid v0.1.0/go.mod h1:CyS/OEQdOvunBgrtIsW93fjd4jBkwZPBjGSpxq3NwA4=
|
||||||
|
github.com/qdm12/ss-server v0.4.0 h1:lMMYfDGc9P86Lyvd3+p8lK4hhgHUKDzjZC91FqJYkDU=
|
||||||
|
github.com/qdm12/ss-server v0.4.0/go.mod h1:AY0p4huvPUPW+/CiWsJcDgT6sneDryk26VXSccPNCxY=
|
||||||
|
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e h1:4q+uFLawkaQRq3yARYLsjJPZd2wYwxn4g6G/5v0xW1g=
|
||||||
|
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e/go.mod h1:UvJRGkZ9XL3/D7e7JiTTVLm1F3Cymd3/gFpD6frEpBo=
|
||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
|
||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
|
||||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||||
github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI=
|
github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI=
|
||||||
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0=
|
github.com/vishvananda/netlink v1.1.1-0.20211129163951-9ada19101fc5 h1:b/k/BVWzWRS5v6AB0gf2ckFSbFsHN5jR0HoNso1pN+w=
|
||||||
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
|
github.com/vishvananda/netlink v1.1.1-0.20211129163951-9ada19101fc5/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
|
||||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df h1:OviZH7qLw/7ZovXvuNyL3XQl8UFofeikI1NW1Gypu7k=
|
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae h1:4hwBBUfQCFe3Cym0ZtKyq7L16eZUtYKs+BaHDN6mAns=
|
||||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
|
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||||
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
|
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
|
||||||
github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g=
|
github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g=
|
||||||
go.uber.org/atomic v1.5.0 h1:OI5t8sDa1Or+q8AeE+yKeB/SDYioSHAgcVljj9JIETY=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
go.uber.org/multierr v1.3.0 h1:sFPn2GLc3poCkfrpIXGhBD2X0CMIo4Q/zSULXrj/+uc=
|
go4.org/intern v0.0.0-20210108033219-3eb7198706b2 h1:VFTf+jjIgsldaz/Mr00VaCSswHJrI2hIjQygE/W4IMg=
|
||||||
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
go4.org/intern v0.0.0-20210108033219-3eb7198706b2/go.mod h1:vLqJ+12kCw61iCWsPto0EOHhBS+o4rO5VIucbc9g2Cc=
|
||||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
|
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222175341-b30ae309168e/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
|
||||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063 h1:1tk03FUNpulq2cuWpXZWj649rwJpk0d20rxWiopKRmc=
|
||||||
go.uber.org/zap v1.13.0 h1:nR6NoDBgAf67s68NhaXbsojM+2gxp3S1hWkHDl27pVU=
|
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
|
||||||
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
|
||||||
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200117160349-530e935923ad/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
|
golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
|
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
|
||||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20181005035420-146acd28ed58/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-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
golang.org/x/net v0.0.0-20201216054612-986b41b23924/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
||||||
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
|
golang.org/x/net v0.0.0-20210504132125-bbd867fde50d h1:nTDGCTeAu2LhcsHTRzjyIUbZHCJ4QePArsm27Hka0UM=
|
||||||
|
golang.org/x/net v0.0.0-20210504132125-bbd867fde50d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||||
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201223074533-0d417f636930 h1:vRgIt+nup/B/BwIS0g2oC0haq0iqbV3ZA+u6+0TlNCo=
|
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201223074533-0d417f636930/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201118182958-a01c418693c7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201218084310-7d0127a74742/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210110051926-789bb1bd4061/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210123111255-9b0068b26619/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210216163648-f7da38b97c65/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210309040221-94ec62e08169/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210503173754-0981d6026fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
|
||||||
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
|
||||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
|
||||||
golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
|
golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
|
||||||
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs=
|
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||||
|
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.zx2c4.com/wireguard v0.0.0-20210427022245-097af6e1351b/go.mod h1:a057zjmoc00UN7gVkaJt2sXVK523kMJcogDTEvPIasg=
|
||||||
|
golang.zx2c4.com/wireguard v0.0.0-20210805125648-3957e9b9dd19 h1:ab2jcw2W91Rz07eHAb8Lic7sFQKO0NhBftjv6m/gL/0=
|
||||||
|
golang.zx2c4.com/wireguard v0.0.0-20210805125648-3957e9b9dd19/go.mod h1:laHzsbfMhGSobUmruXWAyMKKHSqvIcrqZJMyHD+/3O8=
|
||||||
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c h1:ADNrRDI5NR23/TUCnEmlLZLt4u9DnZ2nwRkPrAcFvto=
|
||||||
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c/go.mod h1:+1XihzyZUBJcSc5WO9SwNA7v26puQwOEDwanaxfNXPQ=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
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/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
|
||||||
gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98=
|
gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98=
|
||||||
gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g=
|
gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g=
|
||||||
gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8=
|
gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8=
|
||||||
@@ -186,7 +252,6 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
|
inet.af/netaddr v0.0.0-20210511181906-37180328850c/go.mod h1:z0nx+Dh+7N7CC8V5ayHtHGpZpxLQZZxkIaaz6HN65Ls=
|
||||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
inet.af/netaddr v0.0.0-20210718074554-06ca8145d722 h1:Qws2rZnQudC58cIagVucPQDLmMi3kAXgxscsgD0v6DU=
|
||||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
inet.af/netaddr v0.0.0-20210718074554-06ca8145d722/go.mod h1:z0nx+Dh+7N7CC8V5ayHtHGpZpxLQZZxkIaaz6HN65Ls=
|
||||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
|
||||||
|
|||||||
@@ -1,22 +1,29 @@
|
|||||||
|
// Package alpine defines a configurator to interact with the Alpine operating system.
|
||||||
package alpine
|
package alpine
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/qdm12/golibs/os"
|
"os/user"
|
||||||
"github.com/qdm12/golibs/os/user"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Configurator interface {
|
var _ Alpiner = (*Alpine)(nil)
|
||||||
CreateUser(username string, uid int) (createdUsername string, err error)
|
|
||||||
|
type Alpiner interface {
|
||||||
|
UserCreater
|
||||||
|
VersionGetter
|
||||||
}
|
}
|
||||||
|
|
||||||
type configurator struct {
|
type Alpine struct {
|
||||||
openFile os.OpenFileFunc
|
alpineReleasePath string
|
||||||
osUser user.OSUser
|
passwdPath string
|
||||||
|
lookupID func(uid string) (*user.User, error)
|
||||||
|
lookup func(username string) (*user.User, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConfigurator(openFile os.OpenFileFunc, osUser user.OSUser) Configurator {
|
func New() *Alpine {
|
||||||
return &configurator{
|
return &Alpine{
|
||||||
openFile: openFile,
|
alpineReleasePath: "/etc/alpine-release",
|
||||||
osUser: osUser,
|
passwdPath: "/etc/passwd",
|
||||||
|
lookupID: user.LookupId,
|
||||||
|
lookup: user.Lookup,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,35 +1,51 @@
|
|||||||
package alpine
|
package alpine
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/user"
|
"os/user"
|
||||||
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrUserAlreadyExists = errors.New("user already exists")
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserCreater interface {
|
||||||
|
CreateUser(username string, uid int) (createdUsername string, err error)
|
||||||
|
}
|
||||||
|
|
||||||
// CreateUser creates a user in Alpine with the given UID.
|
// CreateUser creates a user in Alpine with the given UID.
|
||||||
func (c *configurator) CreateUser(username string, uid int) (createdUsername string, err error) {
|
func (a *Alpine) CreateUser(username string, uid int) (createdUsername string, err error) {
|
||||||
UIDStr := fmt.Sprintf("%d", uid)
|
UIDStr := strconv.Itoa(uid)
|
||||||
u, err := c.osUser.LookupID(UIDStr)
|
u, err := a.lookupID(UIDStr)
|
||||||
_, unknownUID := err.(user.UnknownUserIdError)
|
_, unknownUID := err.(user.UnknownUserIdError)
|
||||||
if err != nil && !unknownUID {
|
if err != nil && !unknownUID {
|
||||||
return "", fmt.Errorf("cannot create user: %w", err)
|
return "", err
|
||||||
} else if u != nil {
|
}
|
||||||
|
|
||||||
|
if u != nil {
|
||||||
if u.Username == username {
|
if u.Username == username {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
return u.Username, nil
|
return u.Username, nil
|
||||||
}
|
}
|
||||||
u, err = c.osUser.Lookup(username)
|
|
||||||
|
u, err = a.lookup(username)
|
||||||
_, unknownUsername := err.(user.UnknownUserError)
|
_, unknownUsername := err.(user.UnknownUserError)
|
||||||
if err != nil && !unknownUsername {
|
if err != nil && !unknownUsername {
|
||||||
return "", fmt.Errorf("cannot create user: %w", err)
|
return "", err
|
||||||
} else if u != nil {
|
|
||||||
return "", fmt.Errorf("cannot create user: user with name %s already exists for ID %s instead of %d",
|
|
||||||
username, u.Uid, uid)
|
|
||||||
}
|
}
|
||||||
file, err := c.openFile("/etc/passwd", os.O_APPEND|os.O_WRONLY, 0644)
|
|
||||||
|
if u != nil {
|
||||||
|
return "", fmt.Errorf("%w: with name %s for ID %s instead of %d",
|
||||||
|
ErrUserAlreadyExists, username, u.Uid, uid)
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.OpenFile(a.passwdPath, os.O_APPEND|os.O_WRONLY, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("cannot create user: %w", err)
|
return "", err
|
||||||
}
|
}
|
||||||
s := fmt.Sprintf("%s:x:%d:::/dev/null:/sbin/nologin\n", username, uid)
|
s := fmt.Sprintf("%s:x:%d:::/dev/null:/sbin/nologin\n", username, uid)
|
||||||
_, err = file.WriteString(s)
|
_, err = file.WriteString(s)
|
||||||
@@ -37,5 +53,6 @@ func (c *configurator) CreateUser(username string, uid int) (createdUsername str
|
|||||||
_ = file.Close()
|
_ = file.Close()
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return username, file.Close()
|
return username, file.Close()
|
||||||
}
|
}
|
||||||
|
|||||||
31
internal/alpine/version.go
Normal file
31
internal/alpine/version.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package alpine
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VersionGetter interface {
|
||||||
|
Version(ctx context.Context) (version string, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Alpine) Version(ctx context.Context) (version string, err error) {
|
||||||
|
file, err := os.OpenFile(a.alpineReleasePath, os.O_RDONLY, 0)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := io.ReadAll(file)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := file.Close(); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
version = strings.ReplaceAll(string(b), "\n", "")
|
||||||
|
return version, nil
|
||||||
|
}
|
||||||
@@ -2,6 +2,6 @@ package cli
|
|||||||
|
|
||||||
import "context"
|
import "context"
|
||||||
|
|
||||||
func (c *cli) CI(context context.Context) error {
|
func (c *CLI) CI(context context.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,22 @@
|
|||||||
|
// Package cli defines an interface CLI to run command line operations.
|
||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
var _ CLIer = (*CLI)(nil)
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/qdm12/golibs/os"
|
type CLIer interface {
|
||||||
)
|
ClientKeyFormatter
|
||||||
|
HealthChecker
|
||||||
type CLI interface {
|
OpenvpnConfigMaker
|
||||||
ClientKey(args []string, openFile os.OpenFileFunc) error
|
Updater
|
||||||
HealthCheck(ctx context.Context) error
|
ServersFormatter
|
||||||
OpenvpnConfig(os os.OS) error
|
|
||||||
Update(args []string, os os.OS) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type cli struct{}
|
type CLI struct {
|
||||||
|
repoServersPath string
|
||||||
func New() CLI {
|
}
|
||||||
return &cli{}
|
|
||||||
|
func New() *CLI {
|
||||||
|
return &CLI{
|
||||||
|
repoServersPath: "./internal/storage/servers.json",
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,24 +3,28 @@ package cli
|
|||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
"github.com/qdm12/gluetun/internal/configuration/sources/files"
|
||||||
"github.com/qdm12/golibs/os"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *cli) ClientKey(args []string, openFile os.OpenFileFunc) error {
|
type ClientKeyFormatter interface {
|
||||||
|
ClientKey(args []string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CLI) ClientKey(args []string) error {
|
||||||
flagSet := flag.NewFlagSet("clientkey", flag.ExitOnError)
|
flagSet := flag.NewFlagSet("clientkey", flag.ExitOnError)
|
||||||
filepath := flagSet.String("path", string(constants.ClientKey), "file path to the client.key file")
|
filepath := flagSet.String("path", files.OpenVPNClientKeyPath, "file path to the client.key file")
|
||||||
if err := flagSet.Parse(args); err != nil {
|
if err := flagSet.Parse(args); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
file, err := openFile(*filepath, os.O_RDONLY, 0)
|
file, err := os.OpenFile(*filepath, os.O_RDONLY, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
data, err := ioutil.ReadAll(file)
|
data, err := io.ReadAll(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = file.Close()
|
_ = file.Close()
|
||||||
return err
|
return err
|
||||||
|
|||||||
130
internal/cli/formatservers.go
Normal file
130
internal/cli/formatservers.go
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gluetun/internal/storage"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ServersFormatter interface {
|
||||||
|
FormatServers(args []string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrFormatNotRecognized = errors.New("format is not recognized")
|
||||||
|
ErrProviderUnspecified = errors.New("VPN provider to format was not specified")
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *CLI) FormatServers(args []string) error {
|
||||||
|
var format, output string
|
||||||
|
var cyberghost, expressvpn, fastestvpn, hideMyAss, ipvanish, ivpn, mullvad,
|
||||||
|
nordvpn, perfectPrivacy, pia, privado, privatevpn, protonvpn, purevpn, surfshark,
|
||||||
|
torguard, vpnUnlimited, vyprvpn, wevpn, windscribe bool
|
||||||
|
flagSet := flag.NewFlagSet("markdown", flag.ExitOnError)
|
||||||
|
flagSet.StringVar(&format, "format", "markdown", "Format to use which can be: 'markdown'")
|
||||||
|
flagSet.StringVar(&output, "output", "/dev/stdout", "Output file to write the formatted data to")
|
||||||
|
flagSet.BoolVar(&cyberghost, "cyberghost", false, "Format Cyberghost servers")
|
||||||
|
flagSet.BoolVar(&expressvpn, "expressvpn", false, "Format ExpressVPN servers")
|
||||||
|
flagSet.BoolVar(&fastestvpn, "fastestvpn", false, "Format FastestVPN servers")
|
||||||
|
flagSet.BoolVar(&hideMyAss, "hidemyass", false, "Format HideMyAss servers")
|
||||||
|
flagSet.BoolVar(&ipvanish, "ipvanish", false, "Format IpVanish servers")
|
||||||
|
flagSet.BoolVar(&ivpn, "ivpn", false, "Format IVPN servers")
|
||||||
|
flagSet.BoolVar(&mullvad, "mullvad", false, "Format Mullvad servers")
|
||||||
|
flagSet.BoolVar(&nordvpn, "nordvpn", false, "Format Nordvpn servers")
|
||||||
|
flagSet.BoolVar(&perfectPrivacy, "perfectprivacy", false, "Format Perfect Privacy servers")
|
||||||
|
flagSet.BoolVar(&pia, "pia", false, "Format Private Internet Access servers")
|
||||||
|
flagSet.BoolVar(&privado, "privado", false, "Format Privado servers")
|
||||||
|
flagSet.BoolVar(&privatevpn, "privatevpn", false, "Format Private VPN servers")
|
||||||
|
flagSet.BoolVar(&protonvpn, "protonvpn", false, "Format Protonvpn servers")
|
||||||
|
flagSet.BoolVar(&purevpn, "purevpn", false, "Format Purevpn servers")
|
||||||
|
flagSet.BoolVar(&surfshark, "surfshark", false, "Format Surfshark servers")
|
||||||
|
flagSet.BoolVar(&torguard, "torguard", false, "Format Torguard servers")
|
||||||
|
flagSet.BoolVar(&vpnUnlimited, "vpnunlimited", false, "Format VPN Unlimited servers")
|
||||||
|
flagSet.BoolVar(&vyprvpn, "vyprvpn", false, "Format Vyprvpn servers")
|
||||||
|
flagSet.BoolVar(&wevpn, "wevpn", false, "Format WeVPN servers")
|
||||||
|
flagSet.BoolVar(&windscribe, "windscribe", false, "Format Windscribe servers")
|
||||||
|
if err := flagSet.Parse(args); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if format != "markdown" {
|
||||||
|
return fmt.Errorf("%w: %s", ErrFormatNotRecognized, format)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger := newNoopLogger()
|
||||||
|
storage, err := storage.New(logger, constants.ServersData)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot create servers storage: %w", err)
|
||||||
|
}
|
||||||
|
currentServers := storage.GetServers()
|
||||||
|
|
||||||
|
var formatted string
|
||||||
|
switch {
|
||||||
|
case cyberghost:
|
||||||
|
formatted = currentServers.Cyberghost.ToMarkdown()
|
||||||
|
case expressvpn:
|
||||||
|
formatted = currentServers.Expressvpn.ToMarkdown()
|
||||||
|
case fastestvpn:
|
||||||
|
formatted = currentServers.Fastestvpn.ToMarkdown()
|
||||||
|
case hideMyAss:
|
||||||
|
formatted = currentServers.HideMyAss.ToMarkdown()
|
||||||
|
case ipvanish:
|
||||||
|
formatted = currentServers.Ipvanish.ToMarkdown()
|
||||||
|
case ivpn:
|
||||||
|
formatted = currentServers.Ivpn.ToMarkdown()
|
||||||
|
case mullvad:
|
||||||
|
formatted = currentServers.Mullvad.ToMarkdown()
|
||||||
|
case nordvpn:
|
||||||
|
formatted = currentServers.Nordvpn.ToMarkdown()
|
||||||
|
case perfectPrivacy:
|
||||||
|
formatted = currentServers.Perfectprivacy.ToMarkdown()
|
||||||
|
case pia:
|
||||||
|
formatted = currentServers.Pia.ToMarkdown()
|
||||||
|
case privado:
|
||||||
|
formatted = currentServers.Privado.ToMarkdown()
|
||||||
|
case privatevpn:
|
||||||
|
formatted = currentServers.Privatevpn.ToMarkdown()
|
||||||
|
case protonvpn:
|
||||||
|
formatted = currentServers.Protonvpn.ToMarkdown()
|
||||||
|
case purevpn:
|
||||||
|
formatted = currentServers.Purevpn.ToMarkdown()
|
||||||
|
case surfshark:
|
||||||
|
formatted = currentServers.Surfshark.ToMarkdown()
|
||||||
|
case torguard:
|
||||||
|
formatted = currentServers.Torguard.ToMarkdown()
|
||||||
|
case vpnUnlimited:
|
||||||
|
formatted = currentServers.VPNUnlimited.ToMarkdown()
|
||||||
|
case vyprvpn:
|
||||||
|
formatted = currentServers.Vyprvpn.ToMarkdown()
|
||||||
|
case wevpn:
|
||||||
|
formatted = currentServers.Wevpn.ToMarkdown()
|
||||||
|
case windscribe:
|
||||||
|
formatted = currentServers.Windscribe.ToMarkdown()
|
||||||
|
default:
|
||||||
|
return ErrProviderUnspecified
|
||||||
|
}
|
||||||
|
|
||||||
|
output = filepath.Clean(output)
|
||||||
|
file, err := os.OpenFile(output, os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot open output file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = fmt.Fprint(file, formatted)
|
||||||
|
if err != nil {
|
||||||
|
_ = file.Close()
|
||||||
|
return fmt.Errorf("cannot write to output file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = file.Close()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot close output file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -2,19 +2,41 @@ package cli
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
"github.com/qdm12/gluetun/internal/configuration/sources"
|
||||||
"github.com/qdm12/gluetun/internal/healthcheck"
|
"github.com/qdm12/gluetun/internal/healthcheck"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *cli) HealthCheck(ctx context.Context) error {
|
type HealthChecker interface {
|
||||||
|
HealthCheck(ctx context.Context, source sources.Source, warner Warner) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CLI) HealthCheck(ctx context.Context, source sources.Source, warner Warner) error {
|
||||||
|
// Extract the health server port from the configuration.
|
||||||
|
config, err := source.ReadHealth()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = config.Validate()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, port, err := net.SplitHostPort(config.ServerAddress)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
const timeout = 10 * time.Second
|
const timeout = 10 * time.Second
|
||||||
httpClient := &http.Client{Timeout: timeout}
|
httpClient := &http.Client{Timeout: timeout}
|
||||||
healthchecker := healthcheck.NewChecker(httpClient)
|
client := healthcheck.NewClient(httpClient)
|
||||||
ctx, cancel := context.WithTimeout(ctx, timeout)
|
ctx, cancel := context.WithTimeout(ctx, timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
const url = "http://" + constants.HealthcheckAddress
|
|
||||||
return healthchecker.Check(ctx, url)
|
url := "http://127.0.0.1:" + port
|
||||||
|
return client.Check(ctx, url)
|
||||||
}
|
}
|
||||||
|
|||||||
16
internal/cli/nooplogger.go
Normal file
16
internal/cli/nooplogger.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import "github.com/qdm12/golibs/logging"
|
||||||
|
|
||||||
|
type noopLogger struct{}
|
||||||
|
|
||||||
|
func newNoopLogger() *noopLogger {
|
||||||
|
return new(noopLogger)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *noopLogger) Debug(s string) {}
|
||||||
|
func (l *noopLogger) Info(s string) {}
|
||||||
|
func (l *noopLogger) Warn(s string) {}
|
||||||
|
func (l *noopLogger) Error(s string) {}
|
||||||
|
func (l *noopLogger) PatchLevel(level logging.Level) {}
|
||||||
|
func (l *noopLogger) PatchPrefix(prefix string) {}
|
||||||
@@ -5,36 +5,47 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/sources"
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
"github.com/qdm12/gluetun/internal/params"
|
|
||||||
"github.com/qdm12/gluetun/internal/provider"
|
"github.com/qdm12/gluetun/internal/provider"
|
||||||
"github.com/qdm12/gluetun/internal/settings"
|
|
||||||
"github.com/qdm12/gluetun/internal/storage"
|
"github.com/qdm12/gluetun/internal/storage"
|
||||||
"github.com/qdm12/golibs/logging"
|
|
||||||
"github.com/qdm12/golibs/os"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *cli) OpenvpnConfig(os os.OS) error {
|
type OpenvpnConfigMaker interface {
|
||||||
logger, err := logging.NewLogger(logging.ConsoleEncoding, logging.InfoLevel)
|
OpenvpnConfig(logger OpenvpnConfigLogger, source sources.Source) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type OpenvpnConfigLogger interface {
|
||||||
|
Info(s string)
|
||||||
|
Warn(s string)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CLI) OpenvpnConfig(logger OpenvpnConfigLogger, source sources.Source) error {
|
||||||
|
storage, err := storage.New(logger, constants.ServersData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
paramsReader := params.NewReader(logger, os)
|
allServers := storage.GetServers()
|
||||||
allSettings, _, err := settings.GetAllSettings(paramsReader)
|
|
||||||
|
allSettings, err := source.Read()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
allServers, err := storage.New(logger, os, constants.ServersData).
|
|
||||||
SyncServers(constants.GetAllServers())
|
if err = allSettings.Validate(allServers); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
providerConf := provider.New(*allSettings.VPN.Provider.Name, allServers, time.Now)
|
||||||
|
connection, err := providerConf.GetConnection(allSettings.VPN.Provider.ServerSelection)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
providerConf := provider.New(allSettings.OpenVPN.Provider.Name, allServers, time.Now)
|
lines, err := providerConf.BuildConf(connection, allSettings.VPN.OpenVPN)
|
||||||
connection, err := providerConf.GetOpenVPNConnection(allSettings.OpenVPN.Provider.ServerSelection)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
lines := providerConf.BuildConf(connection, "nonroortuser", allSettings.OpenVPN)
|
|
||||||
fmt.Println(strings.Join(lines, "\n"))
|
fmt.Println(strings.Join(lines, "\n"))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,63 +2,128 @@ package cli
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
"github.com/qdm12/gluetun/internal/settings"
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
"github.com/qdm12/gluetun/internal/storage"
|
"github.com/qdm12/gluetun/internal/storage"
|
||||||
"github.com/qdm12/gluetun/internal/updater"
|
"github.com/qdm12/gluetun/internal/updater"
|
||||||
"github.com/qdm12/golibs/logging"
|
|
||||||
"github.com/qdm12/golibs/os"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *cli) Update(args []string, os os.OS) error {
|
var (
|
||||||
options := settings.Updater{CLI: true}
|
ErrModeUnspecified = errors.New("at least one of -enduser or -maintainer must be specified")
|
||||||
var flushToFile bool
|
ErrDNSAddress = errors.New("DNS address is not valid")
|
||||||
|
ErrNoProviderSpecified = errors.New("no provider was specified")
|
||||||
|
)
|
||||||
|
|
||||||
|
type Updater interface {
|
||||||
|
Update(ctx context.Context, args []string, logger UpdaterLogger) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdaterLogger interface {
|
||||||
|
Info(s string)
|
||||||
|
Warn(s string)
|
||||||
|
Error(s string)
|
||||||
|
}
|
||||||
|
|
||||||
|
func boolPtr(b bool) *bool { return &b }
|
||||||
|
|
||||||
|
func (c *CLI) Update(ctx context.Context, args []string, logger UpdaterLogger) error {
|
||||||
|
options := settings.Updater{CLI: boolPtr(true)}
|
||||||
|
var endUserMode, maintainerMode, updateAll bool
|
||||||
|
var dnsAddress, csvProviders string
|
||||||
flagSet := flag.NewFlagSet("update", flag.ExitOnError)
|
flagSet := flag.NewFlagSet("update", flag.ExitOnError)
|
||||||
flagSet.BoolVar(&flushToFile, "file", false, "Write results to /gluetun/servers.json (for end users)")
|
flagSet.BoolVar(&endUserMode, "enduser", false, "Write results to /gluetun/servers.json (for end users)")
|
||||||
flagSet.BoolVar(&options.Stdout, "stdout", false, "Write results to console to modify the program (for maintainers)")
|
flagSet.BoolVar(&maintainerMode, "maintainer", false,
|
||||||
flagSet.StringVar(&options.DNSAddress, "dns", "1.1.1.1", "DNS resolver address to use")
|
"Write results to ./internal/storage/servers.json to modify the program (for maintainers)")
|
||||||
flagSet.BoolVar(&options.Cyberghost, "cyberghost", false, "Update Cyberghost servers")
|
flagSet.StringVar(&dnsAddress, "dns", "8.8.8.8", "DNS resolver address to use")
|
||||||
flagSet.BoolVar(&options.Mullvad, "mullvad", false, "Update Mullvad servers")
|
flagSet.BoolVar(&updateAll, "all", false, "Update servers for all VPN providers")
|
||||||
flagSet.BoolVar(&options.Nordvpn, "nordvpn", false, "Update Nordvpn servers")
|
flagSet.StringVar(&csvProviders, "providers", "", "CSV string of VPN providers to update server data for")
|
||||||
flagSet.BoolVar(&options.PIA, "pia", false, "Update Private Internet Access post-summer 2020 servers")
|
|
||||||
flagSet.BoolVar(&options.Privado, "privado", false, "Update Privado servers")
|
|
||||||
flagSet.BoolVar(&options.Purevpn, "purevpn", false, "Update Purevpn servers")
|
|
||||||
flagSet.BoolVar(&options.Surfshark, "surfshark", false, "Update Surfshark servers")
|
|
||||||
flagSet.BoolVar(&options.Vyprvpn, "vyprvpn", false, "Update Vyprvpn servers")
|
|
||||||
flagSet.BoolVar(&options.Windscribe, "windscribe", false, "Update Windscribe servers")
|
|
||||||
if err := flagSet.Parse(args); err != nil {
|
if err := flagSet.Parse(args); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
logger, err := logging.NewLogger(logging.ConsoleEncoding, logging.InfoLevel)
|
|
||||||
|
if !endUserMode && !maintainerMode {
|
||||||
|
return ErrModeUnspecified
|
||||||
|
}
|
||||||
|
|
||||||
|
options.DNSAddress = net.ParseIP(dnsAddress)
|
||||||
|
if options.DNSAddress == nil {
|
||||||
|
return fmt.Errorf("%w: %s", ErrDNSAddress, dnsAddress)
|
||||||
|
}
|
||||||
|
|
||||||
|
if updateAll {
|
||||||
|
for _, provider := range constants.AllProviders() {
|
||||||
|
if provider == constants.Custom {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
options.Providers = append(options.Providers, provider)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if csvProviders == "" {
|
||||||
|
return ErrNoProviderSpecified
|
||||||
|
}
|
||||||
|
options.Providers = strings.Split(csvProviders, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
options.SetDefaults(options.Providers[0])
|
||||||
|
|
||||||
|
err := options.Validate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("options validation failed: %w", err)
|
||||||
}
|
}
|
||||||
if !flushToFile && !options.Stdout {
|
|
||||||
return fmt.Errorf("at least one of -file or -stdout must be specified")
|
|
||||||
}
|
|
||||||
ctx := context.Background()
|
|
||||||
const clientTimeout = 10 * time.Second
|
const clientTimeout = 10 * time.Second
|
||||||
httpClient := &http.Client{Timeout: clientTimeout}
|
httpClient := &http.Client{Timeout: clientTimeout}
|
||||||
storage := storage.New(logger, os, constants.ServersData)
|
|
||||||
currentServers, err := storage.SyncServers(constants.GetAllServers())
|
storage, err := storage.New(logger, constants.ServersData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot update servers: %w", err)
|
return fmt.Errorf("cannot create servers storage: %w", err)
|
||||||
}
|
}
|
||||||
|
currentServers := storage.GetServers()
|
||||||
|
|
||||||
updater := updater.New(options, httpClient, currentServers, logger)
|
updater := updater.New(options, httpClient, currentServers, logger)
|
||||||
allServers, err := updater.UpdateServers(ctx)
|
allServers, err := updater.UpdateServers(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("cannot update server information: %w", err)
|
||||||
}
|
}
|
||||||
if flushToFile {
|
|
||||||
|
if endUserMode {
|
||||||
if err := storage.FlushToFile(allServers); err != nil {
|
if err := storage.FlushToFile(allServers); err != nil {
|
||||||
return fmt.Errorf("cannot update servers: %w", err)
|
return fmt.Errorf("cannot write updated information to file: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if maintainerMode {
|
||||||
|
if err := writeToEmbeddedJSON(c.repoServersPath, allServers); err != nil {
|
||||||
|
return fmt.Errorf("cannot write updated information to file: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func writeToEmbeddedJSON(repoServersPath string,
|
||||||
|
allServers models.AllServers) error {
|
||||||
|
const perms = 0600
|
||||||
|
f, err := os.OpenFile(repoServersPath,
|
||||||
|
os.O_TRUNC|os.O_WRONLY|os.O_CREATE, perms)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
encoder := json.NewEncoder(f)
|
||||||
|
encoder.SetIndent("", " ")
|
||||||
|
return encoder.Encode(allServers)
|
||||||
|
}
|
||||||
|
|||||||
5
internal/cli/warner.go
Normal file
5
internal/cli/warner.go
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
type Warner interface {
|
||||||
|
Warn(s string)
|
||||||
|
}
|
||||||
82
internal/configuration/settings/dns.go
Normal file
82
internal/configuration/settings/dns.go
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
|
"github.com/qdm12/gotree"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DNS contains settings to configure DNS.
|
||||||
|
type DNS struct {
|
||||||
|
// ServerAddress is the DNS server to use inside
|
||||||
|
// the Go program and for the system.
|
||||||
|
// It defaults to '127.0.0.1' to be used with the
|
||||||
|
// DoT server. It cannot be nil in the internal
|
||||||
|
// state.
|
||||||
|
ServerAddress net.IP
|
||||||
|
// KeepNameserver is true if the Docker DNS server
|
||||||
|
// found in /etc/resolv.conf should be kept.
|
||||||
|
// Note settings this to true will go around the
|
||||||
|
// DoT server blocking.
|
||||||
|
// It defaults to false and cannot be nil in the
|
||||||
|
// internal state.
|
||||||
|
KeepNameserver *bool
|
||||||
|
// DOT contains settings to configure the DoT
|
||||||
|
// server.
|
||||||
|
DoT DoT
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d DNS) validate() (err error) {
|
||||||
|
err = d.DoT.validate()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed validating DoT settings: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DNS) Copy() (copied DNS) {
|
||||||
|
return DNS{
|
||||||
|
ServerAddress: helpers.CopyIP(d.ServerAddress),
|
||||||
|
KeepNameserver: helpers.CopyBoolPtr(d.KeepNameserver),
|
||||||
|
DoT: d.DoT.copy(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// mergeWith merges the other settings into any
|
||||||
|
// unset field of the receiver settings object.
|
||||||
|
func (d *DNS) mergeWith(other DNS) {
|
||||||
|
d.ServerAddress = helpers.MergeWithIP(d.ServerAddress, other.ServerAddress)
|
||||||
|
d.KeepNameserver = helpers.MergeWithBool(d.KeepNameserver, other.KeepNameserver)
|
||||||
|
d.DoT.mergeWith(other.DoT)
|
||||||
|
}
|
||||||
|
|
||||||
|
// overrideWith overrides fields of the receiver
|
||||||
|
// settings object with any field set in the other
|
||||||
|
// settings.
|
||||||
|
func (d *DNS) overrideWith(other DNS) {
|
||||||
|
d.ServerAddress = helpers.OverrideWithIP(d.ServerAddress, other.ServerAddress)
|
||||||
|
d.KeepNameserver = helpers.OverrideWithBool(d.KeepNameserver, other.KeepNameserver)
|
||||||
|
d.DoT.overrideWith(other.DoT)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DNS) setDefaults() {
|
||||||
|
localhost := net.IPv4(127, 0, 0, 1) //nolint:gomnd
|
||||||
|
d.ServerAddress = helpers.DefaultIP(d.ServerAddress, localhost)
|
||||||
|
d.KeepNameserver = helpers.DefaultBool(d.KeepNameserver, false)
|
||||||
|
d.DoT.setDefaults()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d DNS) String() string {
|
||||||
|
return d.toLinesNode().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d DNS) toLinesNode() (node *gotree.Node) {
|
||||||
|
node = gotree.New("DNS settings:")
|
||||||
|
node.Appendf("DNS server address to use: %s", d.ServerAddress)
|
||||||
|
node.Appendf("Keep existing nameserver(s): %s", helpers.BoolPtrToYesNo(d.KeepNameserver))
|
||||||
|
node.AppendNode(d.DoT.toLinesNode())
|
||||||
|
return node
|
||||||
|
}
|
||||||
138
internal/configuration/settings/dnsblacklist.go
Normal file
138
internal/configuration/settings/dnsblacklist.go
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
"github.com/qdm12/dns/pkg/blacklist"
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
|
"github.com/qdm12/gotree"
|
||||||
|
"inet.af/netaddr"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DNSBlacklist is settings for the DNS blacklist building.
|
||||||
|
type DNSBlacklist struct {
|
||||||
|
BlockMalicious *bool
|
||||||
|
BlockAds *bool
|
||||||
|
BlockSurveillance *bool
|
||||||
|
AllowedHosts []string
|
||||||
|
AddBlockedHosts []string
|
||||||
|
AddBlockedIPs []netaddr.IP
|
||||||
|
AddBlockedIPPrefixes []netaddr.IPPrefix
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *DNSBlacklist) setDefaults() {
|
||||||
|
b.BlockMalicious = helpers.DefaultBool(b.BlockMalicious, true)
|
||||||
|
b.BlockAds = helpers.DefaultBool(b.BlockAds, false)
|
||||||
|
b.BlockSurveillance = helpers.DefaultBool(b.BlockSurveillance, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
var hostRegex = regexp.MustCompile(`^([a-zA-Z0-9]|[a-zA-Z0-9_][a-zA-Z0-9\-_]{0,61}[a-zA-Z0-9_])(\.([a-zA-Z0-9]|[a-zA-Z0-9_][a-zA-Z0-9\-_]{0,61}[a-zA-Z0-9]))*$`) //nolint:lll
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrAllowedHostNotValid = errors.New("allowed host is not valid")
|
||||||
|
ErrBlockedHostNotValid = errors.New("blocked host is not valid")
|
||||||
|
)
|
||||||
|
|
||||||
|
func (b DNSBlacklist) validate() (err error) {
|
||||||
|
for _, host := range b.AllowedHosts {
|
||||||
|
if !hostRegex.MatchString(host) {
|
||||||
|
return fmt.Errorf("%w: %s", ErrAllowedHostNotValid, host)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, host := range b.AddBlockedHosts {
|
||||||
|
if !hostRegex.MatchString(host) {
|
||||||
|
return fmt.Errorf("%w: %s", ErrBlockedHostNotValid, host)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b DNSBlacklist) copy() (copied DNSBlacklist) {
|
||||||
|
return DNSBlacklist{
|
||||||
|
BlockMalicious: helpers.CopyBoolPtr(b.BlockMalicious),
|
||||||
|
BlockAds: helpers.CopyBoolPtr(b.BlockAds),
|
||||||
|
BlockSurveillance: helpers.CopyBoolPtr(b.BlockSurveillance),
|
||||||
|
AllowedHosts: helpers.CopyStringSlice(b.AllowedHosts),
|
||||||
|
AddBlockedHosts: helpers.CopyStringSlice(b.AddBlockedHosts),
|
||||||
|
AddBlockedIPs: helpers.CopyNetaddrIPsSlice(b.AddBlockedIPs),
|
||||||
|
AddBlockedIPPrefixes: helpers.CopyIPPrefixSlice(b.AddBlockedIPPrefixes),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *DNSBlacklist) mergeWith(other DNSBlacklist) {
|
||||||
|
b.BlockMalicious = helpers.MergeWithBool(b.BlockMalicious, other.BlockMalicious)
|
||||||
|
b.BlockAds = helpers.MergeWithBool(b.BlockAds, other.BlockAds)
|
||||||
|
b.BlockSurveillance = helpers.MergeWithBool(b.BlockSurveillance, other.BlockSurveillance)
|
||||||
|
b.AllowedHosts = helpers.MergeStringSlices(b.AllowedHosts, other.AllowedHosts)
|
||||||
|
b.AddBlockedHosts = helpers.MergeStringSlices(b.AddBlockedHosts, other.AddBlockedHosts)
|
||||||
|
b.AddBlockedIPs = helpers.MergeNetaddrIPsSlices(b.AddBlockedIPs, other.AddBlockedIPs)
|
||||||
|
b.AddBlockedIPPrefixes = helpers.MergeIPPrefixesSlices(b.AddBlockedIPPrefixes, other.AddBlockedIPPrefixes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *DNSBlacklist) overrideWith(other DNSBlacklist) {
|
||||||
|
b.BlockMalicious = helpers.OverrideWithBool(b.BlockMalicious, other.BlockMalicious)
|
||||||
|
b.BlockAds = helpers.OverrideWithBool(b.BlockAds, other.BlockAds)
|
||||||
|
b.BlockSurveillance = helpers.OverrideWithBool(b.BlockSurveillance, other.BlockSurveillance)
|
||||||
|
b.AllowedHosts = helpers.OverrideWithStringSlice(b.AllowedHosts, other.AllowedHosts)
|
||||||
|
b.AddBlockedHosts = helpers.OverrideWithStringSlice(b.AddBlockedHosts, other.AddBlockedHosts)
|
||||||
|
b.AddBlockedIPs = helpers.OverrideWithNetaddrIPsSlice(b.AddBlockedIPs, other.AddBlockedIPs)
|
||||||
|
b.AddBlockedIPPrefixes = helpers.OverrideWithIPPrefixesSlice(b.AddBlockedIPPrefixes, other.AddBlockedIPPrefixes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b DNSBlacklist) ToBlacklistFormat() (settings blacklist.BuilderSettings, err error) {
|
||||||
|
return blacklist.BuilderSettings{
|
||||||
|
BlockMalicious: *b.BlockMalicious,
|
||||||
|
BlockAds: *b.BlockAds,
|
||||||
|
BlockSurveillance: *b.BlockSurveillance,
|
||||||
|
AllowedHosts: b.AllowedHosts,
|
||||||
|
AddBlockedHosts: b.AddBlockedHosts,
|
||||||
|
AddBlockedIPs: b.AddBlockedIPs,
|
||||||
|
AddBlockedIPPrefixes: b.AddBlockedIPPrefixes,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b DNSBlacklist) String() string {
|
||||||
|
return b.toLinesNode().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b DNSBlacklist) toLinesNode() (node *gotree.Node) {
|
||||||
|
node = gotree.New("DNS filtering settings:")
|
||||||
|
|
||||||
|
node.Appendf("Block malicious: %s", helpers.BoolPtrToYesNo(b.BlockMalicious))
|
||||||
|
node.Appendf("Block ads: %s", helpers.BoolPtrToYesNo(b.BlockAds))
|
||||||
|
node.Appendf("Block surveillance: %s", helpers.BoolPtrToYesNo(b.BlockSurveillance))
|
||||||
|
|
||||||
|
if len(b.AllowedHosts) > 0 {
|
||||||
|
allowedHostsNode := node.Appendf("Allowed hosts:")
|
||||||
|
for _, host := range b.AllowedHosts {
|
||||||
|
allowedHostsNode.Appendf(host)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(b.AddBlockedHosts) > 0 {
|
||||||
|
blockedHostsNode := node.Appendf("Blocked hosts:")
|
||||||
|
for _, host := range b.AddBlockedHosts {
|
||||||
|
blockedHostsNode.Appendf(host)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(b.AddBlockedIPs) > 0 {
|
||||||
|
blockedIPsNode := node.Appendf("Blocked IP addresses:")
|
||||||
|
for _, ip := range b.AddBlockedIPs {
|
||||||
|
blockedIPsNode.Appendf(ip.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(b.AddBlockedIPPrefixes) > 0 {
|
||||||
|
blockedIPPrefixesNode := node.Appendf("Blocked IP networks:")
|
||||||
|
for _, ipNetwork := range b.AddBlockedIPPrefixes {
|
||||||
|
blockedIPPrefixesNode.Appendf(ipNetwork.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return node
|
||||||
|
}
|
||||||
113
internal/configuration/settings/dot.go
Normal file
113
internal/configuration/settings/dot.go
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
|
"github.com/qdm12/gotree"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DoT contains settings to configure the DoT server.
|
||||||
|
type DoT struct {
|
||||||
|
// Enabled is true if the DoT server should be running
|
||||||
|
// and used. It defaults to true, and cannot be nil
|
||||||
|
// in the internal state.
|
||||||
|
Enabled *bool
|
||||||
|
// UpdatePeriod is the period to update DNS block
|
||||||
|
// lists and cryptographic files for DNSSEC validation.
|
||||||
|
// It can be set to 0 to disable the update.
|
||||||
|
// It defaults to 24h and cannot be nil in
|
||||||
|
// the internal state.
|
||||||
|
UpdatePeriod *time.Duration
|
||||||
|
// Unbound contains settings to configure Unbound.
|
||||||
|
Unbound Unbound
|
||||||
|
// Blacklist contains settings to configure the filter
|
||||||
|
// block lists.
|
||||||
|
Blacklist DNSBlacklist
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrDoTUpdatePeriodTooShort = errors.New("update period is too short")
|
||||||
|
)
|
||||||
|
|
||||||
|
func (d DoT) validate() (err error) {
|
||||||
|
const minUpdatePeriod = 30 * time.Second
|
||||||
|
if *d.UpdatePeriod != 0 && *d.UpdatePeriod < minUpdatePeriod {
|
||||||
|
return fmt.Errorf("%w: %s must be bigger than %s",
|
||||||
|
ErrDoTUpdatePeriodTooShort, *d.UpdatePeriod, minUpdatePeriod)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = d.Unbound.validate()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = d.Blacklist.validate()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DoT) copy() (copied DoT) {
|
||||||
|
return DoT{
|
||||||
|
Enabled: helpers.CopyBoolPtr(d.Enabled),
|
||||||
|
UpdatePeriod: helpers.CopyDurationPtr(d.UpdatePeriod),
|
||||||
|
Unbound: d.Unbound.copy(),
|
||||||
|
Blacklist: d.Blacklist.copy(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// mergeWith merges the other settings into any
|
||||||
|
// unset field of the receiver settings object.
|
||||||
|
func (d *DoT) mergeWith(other DoT) {
|
||||||
|
d.Enabled = helpers.MergeWithBool(d.Enabled, other.Enabled)
|
||||||
|
d.UpdatePeriod = helpers.MergeWithDuration(d.UpdatePeriod, other.UpdatePeriod)
|
||||||
|
d.Unbound.mergeWith(other.Unbound)
|
||||||
|
d.Blacklist.mergeWith(other.Blacklist)
|
||||||
|
}
|
||||||
|
|
||||||
|
// overrideWith overrides fields of the receiver
|
||||||
|
// settings object with any field set in the other
|
||||||
|
// settings.
|
||||||
|
func (d *DoT) overrideWith(other DoT) {
|
||||||
|
d.Enabled = helpers.OverrideWithBool(d.Enabled, other.Enabled)
|
||||||
|
d.UpdatePeriod = helpers.OverrideWithDuration(d.UpdatePeriod, other.UpdatePeriod)
|
||||||
|
d.Unbound.overrideWith(other.Unbound)
|
||||||
|
d.Blacklist.overrideWith(other.Blacklist)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DoT) setDefaults() {
|
||||||
|
d.Enabled = helpers.DefaultBool(d.Enabled, true)
|
||||||
|
const defaultUpdatePeriod = 24 * time.Hour
|
||||||
|
d.UpdatePeriod = helpers.DefaultDuration(d.UpdatePeriod, defaultUpdatePeriod)
|
||||||
|
d.Unbound.setDefaults()
|
||||||
|
d.Blacklist.setDefaults()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d DoT) String() string {
|
||||||
|
return d.toLinesNode().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d DoT) toLinesNode() (node *gotree.Node) {
|
||||||
|
node = gotree.New("DNS over TLS settings:")
|
||||||
|
|
||||||
|
node.Appendf("Enabled: %s", helpers.BoolPtrToYesNo(d.Enabled))
|
||||||
|
if !*d.Enabled {
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
update := "disabled"
|
||||||
|
if *d.UpdatePeriod > 0 {
|
||||||
|
update = "every " + d.UpdatePeriod.String()
|
||||||
|
}
|
||||||
|
node.Appendf("Update period: %s", update)
|
||||||
|
|
||||||
|
node.AppendNode(d.Unbound.toLinesNode())
|
||||||
|
node.AppendNode(d.Blacklist.toLinesNode())
|
||||||
|
|
||||||
|
return node
|
||||||
|
}
|
||||||
44
internal/configuration/settings/errors.go
Normal file
44
internal/configuration/settings/errors.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrCityNotValid = errors.New("the city specified is not valid")
|
||||||
|
ErrControlServerPrivilegedPort = errors.New("cannot use privileged port without running as root")
|
||||||
|
ErrCountryNotValid = errors.New("the country specified is not valid")
|
||||||
|
ErrFilepathMissing = errors.New("filepath is missing")
|
||||||
|
ErrFirewallZeroPort = errors.New("cannot have a zero port to block")
|
||||||
|
ErrHostnameNotValid = errors.New("the hostname specified is not valid")
|
||||||
|
ErrISPNotValid = errors.New("the ISP specified is not valid")
|
||||||
|
ErrMissingValue = errors.New("missing value")
|
||||||
|
ErrNameNotValid = errors.New("the server name specified is not valid")
|
||||||
|
ErrOpenVPNClientKeyMissing = errors.New("client key is missing")
|
||||||
|
ErrOpenVPNCustomPortNotAllowed = errors.New("custom endpoint port is not allowed")
|
||||||
|
ErrOpenVPNEncryptionPresetNotValid = errors.New("PIA encryption preset is not valid")
|
||||||
|
ErrOpenVPNInterfaceNotValid = errors.New("interface name is not valid")
|
||||||
|
ErrOpenVPNMSSFixIsTooHigh = errors.New("mssfix option value is too high")
|
||||||
|
ErrOpenVPNPasswordIsEmpty = errors.New("password is empty")
|
||||||
|
ErrOpenVPNTCPNotSupported = errors.New("TCP protocol is not supported")
|
||||||
|
ErrOpenVPNUserIsEmpty = errors.New("user is empty")
|
||||||
|
ErrOpenVPNVerbosityIsOutOfBounds = errors.New("verbosity value is out of bounds")
|
||||||
|
ErrOpenVPNVersionIsNotValid = errors.New("version is not valid")
|
||||||
|
ErrPortForwardingEnabled = errors.New("port forwarding cannot be enabled")
|
||||||
|
ErrPublicIPPeriodTooShort = errors.New("public IP address check period is too short")
|
||||||
|
ErrRegionNotValid = errors.New("the region specified is not valid")
|
||||||
|
ErrServerAddressNotValid = errors.New("server listening address is not valid")
|
||||||
|
ErrSystemPGIDNotValid = errors.New("process group id is not valid")
|
||||||
|
ErrSystemPUIDNotValid = errors.New("process user id is not valid")
|
||||||
|
ErrSystemTimezoneNotValid = errors.New("timezone is not valid")
|
||||||
|
ErrUpdaterPeriodTooSmall = errors.New("VPN server data updater period is too small")
|
||||||
|
ErrVPNProviderNameNotValid = errors.New("VPN provider name is not valid")
|
||||||
|
ErrVPNTypeNotValid = errors.New("VPN type is not valid")
|
||||||
|
ErrWireguardEndpointIPNotSet = errors.New("endpoint IP is not set")
|
||||||
|
ErrWireguardEndpointPortNotAllowed = errors.New("endpoint port is not allowed")
|
||||||
|
ErrWireguardEndpointPortNotSet = errors.New("endpoint port is not set")
|
||||||
|
ErrWireguardInterfaceAddressNotSet = errors.New("interface address is not set")
|
||||||
|
ErrWireguardInterfaceNotValid = errors.New("interface name is not valid")
|
||||||
|
ErrWireguardPreSharedKeyNotSet = errors.New("pre-shared key is not set")
|
||||||
|
ErrWireguardPrivateKeyNotSet = errors.New("private key is not set")
|
||||||
|
ErrWireguardPublicKeyNotSet = errors.New("public key is not set")
|
||||||
|
ErrWireguardPublicKeyNotValid = errors.New("public key is not valid")
|
||||||
|
)
|
||||||
117
internal/configuration/settings/firewall.go
Normal file
117
internal/configuration/settings/firewall.go
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
|
"github.com/qdm12/gotree"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Firewall contains settings to customize the firewall operation.
|
||||||
|
type Firewall struct {
|
||||||
|
VPNInputPorts []uint16
|
||||||
|
InputPorts []uint16
|
||||||
|
OutboundSubnets []net.IPNet
|
||||||
|
Enabled *bool
|
||||||
|
Debug *bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Firewall) validate() (err error) {
|
||||||
|
if hasZeroPort(f.VPNInputPorts) {
|
||||||
|
return fmt.Errorf("VPN input ports: %w", ErrFirewallZeroPort)
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasZeroPort(f.InputPorts) {
|
||||||
|
return fmt.Errorf("input ports: %w", ErrFirewallZeroPort)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasZeroPort(ports []uint16) (has bool) {
|
||||||
|
for _, port := range ports {
|
||||||
|
if port == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Firewall) copy() (copied Firewall) {
|
||||||
|
return Firewall{
|
||||||
|
VPNInputPorts: helpers.CopyUint16Slice(f.VPNInputPorts),
|
||||||
|
InputPorts: helpers.CopyUint16Slice(f.InputPorts),
|
||||||
|
OutboundSubnets: helpers.CopyIPNetSlice(f.OutboundSubnets),
|
||||||
|
Enabled: helpers.CopyBoolPtr(f.Enabled),
|
||||||
|
Debug: helpers.CopyBoolPtr(f.Debug),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// mergeWith merges the other settings into any
|
||||||
|
// unset field of the receiver settings object.
|
||||||
|
// It merges values of slices together, even if they
|
||||||
|
// are set in the receiver settings.
|
||||||
|
func (f *Firewall) mergeWith(other Firewall) {
|
||||||
|
f.VPNInputPorts = helpers.MergeUint16Slices(f.VPNInputPorts, other.VPNInputPorts)
|
||||||
|
f.InputPorts = helpers.MergeUint16Slices(f.InputPorts, other.InputPorts)
|
||||||
|
f.OutboundSubnets = helpers.MergeIPNetsSlices(f.OutboundSubnets, other.OutboundSubnets)
|
||||||
|
f.Enabled = helpers.MergeWithBool(f.Enabled, other.Enabled)
|
||||||
|
f.Debug = helpers.MergeWithBool(f.Debug, other.Debug)
|
||||||
|
}
|
||||||
|
|
||||||
|
// overrideWith overrides fields of the receiver
|
||||||
|
// settings object with any field set in the other
|
||||||
|
// settings.
|
||||||
|
func (f *Firewall) overrideWith(other Firewall) {
|
||||||
|
f.VPNInputPorts = helpers.OverrideWithUint16Slice(f.VPNInputPorts, other.VPNInputPorts)
|
||||||
|
f.InputPorts = helpers.OverrideWithUint16Slice(f.InputPorts, other.InputPorts)
|
||||||
|
f.OutboundSubnets = helpers.OverrideWithIPNetsSlice(f.OutboundSubnets, other.OutboundSubnets)
|
||||||
|
f.Enabled = helpers.OverrideWithBool(f.Enabled, other.Enabled)
|
||||||
|
f.Debug = helpers.OverrideWithBool(f.Debug, other.Debug)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Firewall) setDefaults() {
|
||||||
|
f.Enabled = helpers.DefaultBool(f.Enabled, true)
|
||||||
|
f.Debug = helpers.DefaultBool(f.Debug, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Firewall) String() string {
|
||||||
|
return f.toLinesNode().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Firewall) toLinesNode() (node *gotree.Node) {
|
||||||
|
node = gotree.New("Firewall settings:")
|
||||||
|
|
||||||
|
node.Appendf("Enabled: %s", helpers.BoolPtrToYesNo(f.Enabled))
|
||||||
|
if !*f.Enabled {
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
if *f.Debug {
|
||||||
|
node.Appendf("Debug mode: on")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(f.VPNInputPorts) > 0 {
|
||||||
|
vpnInputPortsNode := node.Appendf("VPN input ports:")
|
||||||
|
for _, port := range f.VPNInputPorts {
|
||||||
|
vpnInputPortsNode.Appendf("%d", port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(f.InputPorts) > 0 {
|
||||||
|
inputPortsNode := node.Appendf("Input ports:")
|
||||||
|
for _, port := range f.InputPorts {
|
||||||
|
inputPortsNode.Appendf("%d", port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(f.OutboundSubnets) > 0 {
|
||||||
|
outboundSubnets := node.Appendf("Outbound subnets:")
|
||||||
|
for _, subnet := range f.OutboundSubnets {
|
||||||
|
outboundSubnets.Appendf("%s", subnet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return node
|
||||||
|
}
|
||||||
82
internal/configuration/settings/health.go
Normal file
82
internal/configuration/settings/health.go
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
|
"github.com/qdm12/gotree"
|
||||||
|
"github.com/qdm12/govalid/address"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Health contains settings for the healthcheck and health server.
|
||||||
|
type Health struct {
|
||||||
|
// ServerAddress is the listening address
|
||||||
|
// for the health check server.
|
||||||
|
// It cannot be the empty string in the internal state.
|
||||||
|
ServerAddress string
|
||||||
|
// AddressToPing is the IP address or domain name to
|
||||||
|
// ping periodically for the health check.
|
||||||
|
// It cannot be the empty string in the internal state.
|
||||||
|
AddressToPing string
|
||||||
|
VPN HealthyWait
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h Health) Validate() (err error) {
|
||||||
|
uid := os.Getuid()
|
||||||
|
_, err = address.Validate(h.ServerAddress,
|
||||||
|
address.OptionListening(uid))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("server listening address is not valid: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.VPN.validate()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("health VPN settings: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Health) copy() (copied Health) {
|
||||||
|
return Health{
|
||||||
|
ServerAddress: h.ServerAddress,
|
||||||
|
AddressToPing: h.AddressToPing,
|
||||||
|
VPN: h.VPN.copy(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MergeWith merges the other settings into any
|
||||||
|
// unset field of the receiver settings object.
|
||||||
|
func (h *Health) MergeWith(other Health) {
|
||||||
|
h.ServerAddress = helpers.MergeWithString(h.ServerAddress, other.ServerAddress)
|
||||||
|
h.AddressToPing = helpers.MergeWithString(h.AddressToPing, other.AddressToPing)
|
||||||
|
h.VPN.mergeWith(other.VPN)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OverrideWith overrides fields of the receiver
|
||||||
|
// settings object with any field set in the other
|
||||||
|
// settings.
|
||||||
|
func (h *Health) OverrideWith(other Health) {
|
||||||
|
h.ServerAddress = helpers.OverrideWithString(h.ServerAddress, other.ServerAddress)
|
||||||
|
h.AddressToPing = helpers.OverrideWithString(h.AddressToPing, other.AddressToPing)
|
||||||
|
h.VPN.overrideWith(other.VPN)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Health) SetDefaults() {
|
||||||
|
h.ServerAddress = helpers.DefaultString(h.ServerAddress, "127.0.0.1:9999")
|
||||||
|
h.AddressToPing = helpers.DefaultString(h.AddressToPing, "github.com")
|
||||||
|
h.VPN.setDefaults()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h Health) String() string {
|
||||||
|
return h.toLinesNode().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h Health) toLinesNode() (node *gotree.Node) {
|
||||||
|
node = gotree.New("Health settings:")
|
||||||
|
node.Appendf("Server listening address: %s", h.ServerAddress)
|
||||||
|
node.Appendf("Address to ping: %s", h.AddressToPing)
|
||||||
|
node.AppendNode(h.VPN.toLinesNode("VPN"))
|
||||||
|
return node
|
||||||
|
}
|
||||||
66
internal/configuration/settings/healthywait.go
Normal file
66
internal/configuration/settings/healthywait.go
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
|
"github.com/qdm12/gotree"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HealthyWait struct {
|
||||||
|
// Initial is the initial duration to wait for the program
|
||||||
|
// to be healthy before taking action.
|
||||||
|
// It cannot be nil in the internal state.
|
||||||
|
Initial *time.Duration
|
||||||
|
// Addition is the duration to add to the Initial duration
|
||||||
|
// after Initial has expired to wait longer for the program
|
||||||
|
// to be healthy.
|
||||||
|
// It cannot be nil in the internal state.
|
||||||
|
Addition *time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h HealthyWait) validate() (err error) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// mergeWith merges the other settings into any
|
||||||
|
// unset field of the receiver settings object.
|
||||||
|
func (h *HealthyWait) copy() (copied HealthyWait) {
|
||||||
|
return HealthyWait{
|
||||||
|
Initial: helpers.CopyDurationPtr(h.Initial),
|
||||||
|
Addition: helpers.CopyDurationPtr(h.Addition),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// mergeWith merges the other settings into any
|
||||||
|
// unset field of the receiver settings object.
|
||||||
|
func (h *HealthyWait) mergeWith(other HealthyWait) {
|
||||||
|
h.Initial = helpers.MergeWithDuration(h.Initial, other.Initial)
|
||||||
|
h.Addition = helpers.MergeWithDuration(h.Addition, other.Addition)
|
||||||
|
}
|
||||||
|
|
||||||
|
// overrideWith overrides fields of the receiver
|
||||||
|
// settings object with any field set in the other
|
||||||
|
// settings.
|
||||||
|
func (h *HealthyWait) overrideWith(other HealthyWait) {
|
||||||
|
h.Initial = helpers.OverrideWithDuration(h.Initial, other.Initial)
|
||||||
|
h.Addition = helpers.OverrideWithDuration(h.Addition, other.Addition)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HealthyWait) setDefaults() {
|
||||||
|
const initialDurationDefault = 6 * time.Second
|
||||||
|
const additionDurationDefault = 5 * time.Second
|
||||||
|
h.Initial = helpers.DefaultDuration(h.Initial, initialDurationDefault)
|
||||||
|
h.Addition = helpers.DefaultDuration(h.Addition, additionDurationDefault)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h HealthyWait) String() string {
|
||||||
|
return h.toLinesNode("Health").String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h HealthyWait) toLinesNode(kind string) (node *gotree.Node) {
|
||||||
|
node = gotree.New(kind + " wait durations:")
|
||||||
|
node.Appendf("Initial duration: %s", *h.Initial)
|
||||||
|
node.Appendf("Additional duration: %s", *h.Addition)
|
||||||
|
return node
|
||||||
|
}
|
||||||
52
internal/configuration/settings/helpers/belong.go
Normal file
52
internal/configuration/settings/helpers/belong.go
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package helpers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func IsOneOf(value string, choices ...string) (ok bool) {
|
||||||
|
for _, choice := range choices {
|
||||||
|
if value == choice {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNoChoice = errors.New("one or more values is set but there is no possible value available")
|
||||||
|
ErrValueNotOneOf = errors.New("value is not one of the possible choices")
|
||||||
|
)
|
||||||
|
|
||||||
|
func AreAllOneOf(values, choices []string) (err error) {
|
||||||
|
if len(values) > 0 && len(choices) == 0 {
|
||||||
|
return ErrNoChoice
|
||||||
|
}
|
||||||
|
|
||||||
|
set := make(map[string]struct{}, len(choices))
|
||||||
|
for _, choice := range choices {
|
||||||
|
choice = strings.ToLower(choice)
|
||||||
|
set[choice] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, value := range values {
|
||||||
|
_, ok := set[value]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("%w: value %q, choices available are %s",
|
||||||
|
ErrValueNotOneOf, value, strings.Join(choices, ", "))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Uint16IsOneOf(port uint16, choices []uint16) (ok bool) {
|
||||||
|
for _, choice := range choices {
|
||||||
|
if port == choice {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
190
internal/configuration/settings/helpers/copy.go
Normal file
190
internal/configuration/settings/helpers/copy.go
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
package helpers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/qdm12/golibs/logging"
|
||||||
|
"inet.af/netaddr"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CopyStringPtr(original *string) (copied *string) {
|
||||||
|
if original == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
copied = new(string)
|
||||||
|
*copied = *original
|
||||||
|
return copied
|
||||||
|
}
|
||||||
|
|
||||||
|
func CopyBoolPtr(original *bool) (copied *bool) {
|
||||||
|
if original == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
copied = new(bool)
|
||||||
|
*copied = *original
|
||||||
|
return copied
|
||||||
|
}
|
||||||
|
|
||||||
|
func CopyUint8Ptr(original *uint8) (copied *uint8) {
|
||||||
|
if original == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
copied = new(uint8)
|
||||||
|
*copied = *original
|
||||||
|
return copied
|
||||||
|
}
|
||||||
|
|
||||||
|
func CopyUint16Ptr(original *uint16) (copied *uint16) {
|
||||||
|
if original == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
copied = new(uint16)
|
||||||
|
*copied = *original
|
||||||
|
return copied
|
||||||
|
}
|
||||||
|
|
||||||
|
func CopyIntPtr(original *int) (copied *int) {
|
||||||
|
if original == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
copied = new(int)
|
||||||
|
*copied = *original
|
||||||
|
return copied
|
||||||
|
}
|
||||||
|
|
||||||
|
func CopyDurationPtr(original *time.Duration) (copied *time.Duration) {
|
||||||
|
if original == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
copied = new(time.Duration)
|
||||||
|
*copied = *original
|
||||||
|
return copied
|
||||||
|
}
|
||||||
|
|
||||||
|
func CopyLogLevelPtr(original *logging.Level) (copied *logging.Level) {
|
||||||
|
if original == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
copied = new(logging.Level)
|
||||||
|
*copied = *original
|
||||||
|
return copied
|
||||||
|
}
|
||||||
|
|
||||||
|
func CopyIP(original net.IP) (copied net.IP) {
|
||||||
|
if original == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
copied = make(net.IP, len(original))
|
||||||
|
copy(copied, original)
|
||||||
|
return copied
|
||||||
|
}
|
||||||
|
|
||||||
|
func CopyIPNet(original net.IPNet) (copied net.IPNet) {
|
||||||
|
if original.IP != nil {
|
||||||
|
copied.IP = make(net.IP, len(original.IP))
|
||||||
|
copy(copied.IP, original.IP)
|
||||||
|
}
|
||||||
|
|
||||||
|
if original.Mask != nil {
|
||||||
|
copied.Mask = make(net.IPMask, len(original.Mask))
|
||||||
|
copy(copied.Mask, original.Mask)
|
||||||
|
}
|
||||||
|
|
||||||
|
return copied
|
||||||
|
}
|
||||||
|
|
||||||
|
func CopyIPNetPtr(original *net.IPNet) (copied *net.IPNet) {
|
||||||
|
if original == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
copied = new(net.IPNet)
|
||||||
|
*copied = CopyIPNet(*original)
|
||||||
|
return copied
|
||||||
|
}
|
||||||
|
|
||||||
|
func CopyNetaddrIP(original netaddr.IP) (copied netaddr.IP) {
|
||||||
|
b, err := original.MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = copied.UnmarshalBinary(b)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return copied
|
||||||
|
}
|
||||||
|
|
||||||
|
func CopyIPPrefix(original netaddr.IPPrefix) (copied netaddr.IPPrefix) {
|
||||||
|
b, err := original.MarshalText()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = copied.UnmarshalText(b)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return copied
|
||||||
|
}
|
||||||
|
|
||||||
|
func CopyStringSlice(original []string) (copied []string) {
|
||||||
|
if original == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
copied = make([]string, len(original))
|
||||||
|
copy(copied, original)
|
||||||
|
return copied
|
||||||
|
}
|
||||||
|
|
||||||
|
func CopyUint16Slice(original []uint16) (copied []uint16) {
|
||||||
|
if original == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
copied = make([]uint16, len(original))
|
||||||
|
copy(copied, original)
|
||||||
|
return copied
|
||||||
|
}
|
||||||
|
|
||||||
|
func CopyIPNetSlice(original []net.IPNet) (copied []net.IPNet) {
|
||||||
|
if original == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
copied = make([]net.IPNet, len(original))
|
||||||
|
for i := range original {
|
||||||
|
copied[i] = CopyIPNet(original[i])
|
||||||
|
}
|
||||||
|
return copied
|
||||||
|
}
|
||||||
|
|
||||||
|
func CopyIPPrefixSlice(original []netaddr.IPPrefix) (copied []netaddr.IPPrefix) {
|
||||||
|
if original == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
copied = make([]netaddr.IPPrefix, len(original))
|
||||||
|
for i := range original {
|
||||||
|
copied[i] = CopyIPPrefix(original[i])
|
||||||
|
}
|
||||||
|
return copied
|
||||||
|
}
|
||||||
|
|
||||||
|
func CopyNetaddrIPsSlice(original []netaddr.IP) (copied []netaddr.IP) {
|
||||||
|
if original == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
copied = make([]netaddr.IP, len(original))
|
||||||
|
for i := range original {
|
||||||
|
copied[i] = CopyNetaddrIP(original[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
return copied
|
||||||
|
}
|
||||||
93
internal/configuration/settings/helpers/default.go
Normal file
93
internal/configuration/settings/helpers/default.go
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
package helpers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/qdm12/golibs/logging"
|
||||||
|
)
|
||||||
|
|
||||||
|
func DefaultInt(existing *int, defaultValue int) (
|
||||||
|
result *int) {
|
||||||
|
if existing != nil {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
result = new(int)
|
||||||
|
*result = defaultValue
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func DefaultUint8(existing *uint8, defaultValue uint8) (
|
||||||
|
result *uint8) {
|
||||||
|
if existing != nil {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
result = new(uint8)
|
||||||
|
*result = defaultValue
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func DefaultUint16(existing *uint16, defaultValue uint16) (
|
||||||
|
result *uint16) {
|
||||||
|
if existing != nil {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
result = new(uint16)
|
||||||
|
*result = defaultValue
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func DefaultBool(existing *bool, defaultValue bool) (
|
||||||
|
result *bool) {
|
||||||
|
if existing != nil {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
result = new(bool)
|
||||||
|
*result = defaultValue
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func DefaultString(existing string, defaultValue string) (
|
||||||
|
result string) {
|
||||||
|
if existing != "" {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func DefaultStringPtr(existing *string, defaultValue string) (result *string) {
|
||||||
|
if existing != nil {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
result = new(string)
|
||||||
|
*result = defaultValue
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func DefaultDuration(existing *time.Duration,
|
||||||
|
defaultValue time.Duration) (result *time.Duration) {
|
||||||
|
if existing != nil {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
result = new(time.Duration)
|
||||||
|
*result = defaultValue
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func DefaultLogLevel(existing *logging.Level,
|
||||||
|
defaultValue logging.Level) (result *logging.Level) {
|
||||||
|
if existing != nil {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
result = new(logging.Level)
|
||||||
|
*result = defaultValue
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func DefaultIP(existing net.IP, defaultValue net.IP) (
|
||||||
|
result net.IP) {
|
||||||
|
if existing != nil {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
31
internal/configuration/settings/helpers/files.go
Normal file
31
internal/configuration/settings/helpers/files.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package helpers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrFileDoesNotExist = errors.New("file does not exist")
|
||||||
|
ErrFileRead = errors.New("cannot read file")
|
||||||
|
ErrFileClose = errors.New("cannot close file")
|
||||||
|
)
|
||||||
|
|
||||||
|
func FileExists(path string) (err error) {
|
||||||
|
path = filepath.Clean(path)
|
||||||
|
|
||||||
|
f, err := os.Open(path)
|
||||||
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
return fmt.Errorf("%w: %s", ErrFileDoesNotExist, path)
|
||||||
|
} else if err != nil {
|
||||||
|
return fmt.Errorf("%w: %s", ErrFileRead, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := f.Close(); err != nil {
|
||||||
|
return fmt.Errorf("%w: %s", ErrFileClose, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
241
internal/configuration/settings/helpers/merge.go
Normal file
241
internal/configuration/settings/helpers/merge.go
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
package helpers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/qdm12/golibs/logging"
|
||||||
|
"inet.af/netaddr"
|
||||||
|
)
|
||||||
|
|
||||||
|
func MergeWithBool(existing, other *bool) (result *bool) {
|
||||||
|
if existing != nil {
|
||||||
|
return existing
|
||||||
|
} else if other == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
result = new(bool)
|
||||||
|
*result = *other
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func MergeWithString(existing, other string) (result string) {
|
||||||
|
if existing != "" {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
return other
|
||||||
|
}
|
||||||
|
|
||||||
|
func MergeWithInt(existing, other int) (result int) {
|
||||||
|
if existing != 0 {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
return other
|
||||||
|
}
|
||||||
|
|
||||||
|
func MergeWithStringPtr(existing, other *string) (result *string) {
|
||||||
|
if existing != nil {
|
||||||
|
return existing
|
||||||
|
} else if other == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
result = new(string)
|
||||||
|
*result = *other
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func MergeWithIntPtr(existing, other *int) (result *int) {
|
||||||
|
if existing != nil {
|
||||||
|
return existing
|
||||||
|
} else if other == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
result = new(int)
|
||||||
|
*result = *other
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func MergeWithUint8(existing, other *uint8) (result *uint8) {
|
||||||
|
if existing != nil {
|
||||||
|
return existing
|
||||||
|
} else if other == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
result = new(uint8)
|
||||||
|
*result = *other
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func MergeWithUint16(existing, other *uint16) (result *uint16) {
|
||||||
|
if existing != nil {
|
||||||
|
return existing
|
||||||
|
} else if other == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
result = new(uint16)
|
||||||
|
*result = *other
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func MergeWithIP(existing, other net.IP) (result net.IP) {
|
||||||
|
if existing != nil {
|
||||||
|
return existing
|
||||||
|
} else if other == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
result = make(net.IP, len(other))
|
||||||
|
copy(result, other)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func MergeWithDuration(existing, other *time.Duration) (result *time.Duration) {
|
||||||
|
if existing != nil {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
return other
|
||||||
|
}
|
||||||
|
|
||||||
|
func MergeWithLogLevel(existing, other *logging.Level) (result *logging.Level) {
|
||||||
|
if existing != nil {
|
||||||
|
return existing
|
||||||
|
} else if other == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
result = new(logging.Level)
|
||||||
|
*result = *other
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func MergeWithHTTPHandler(existing, other http.Handler) (result http.Handler) {
|
||||||
|
if existing != nil {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
return other
|
||||||
|
}
|
||||||
|
|
||||||
|
func MergeStringSlices(a, b []string) (result []string) {
|
||||||
|
if a == nil && b == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
seen := make(map[string]struct{}, len(a)+len(b))
|
||||||
|
result = make([]string, 0, len(a)+len(b))
|
||||||
|
for _, s := range a {
|
||||||
|
if _, ok := seen[s]; ok {
|
||||||
|
continue // duplicate
|
||||||
|
}
|
||||||
|
result = append(result, s)
|
||||||
|
seen[s] = struct{}{}
|
||||||
|
}
|
||||||
|
for _, s := range b {
|
||||||
|
if _, ok := seen[s]; ok {
|
||||||
|
continue // duplicate
|
||||||
|
}
|
||||||
|
result = append(result, s)
|
||||||
|
seen[s] = struct{}{}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func MergeUint16Slices(a, b []uint16) (result []uint16) {
|
||||||
|
if a == nil && b == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
seen := make(map[uint16]struct{}, len(a)+len(b))
|
||||||
|
result = make([]uint16, 0, len(a)+len(b))
|
||||||
|
for _, n := range a {
|
||||||
|
if _, ok := seen[n]; ok {
|
||||||
|
continue // duplicate
|
||||||
|
}
|
||||||
|
result = append(result, n)
|
||||||
|
seen[n] = struct{}{}
|
||||||
|
}
|
||||||
|
for _, n := range b {
|
||||||
|
if _, ok := seen[n]; ok {
|
||||||
|
continue // duplicate
|
||||||
|
}
|
||||||
|
result = append(result, n)
|
||||||
|
seen[n] = struct{}{}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func MergeIPNetsSlices(a, b []net.IPNet) (result []net.IPNet) {
|
||||||
|
if a == nil && b == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
seen := make(map[string]struct{}, len(a)+len(b))
|
||||||
|
result = make([]net.IPNet, 0, len(a)+len(b))
|
||||||
|
for _, ipNet := range a {
|
||||||
|
key := ipNet.String()
|
||||||
|
if _, ok := seen[key]; ok {
|
||||||
|
continue // duplicate
|
||||||
|
}
|
||||||
|
result = append(result, ipNet)
|
||||||
|
seen[key] = struct{}{}
|
||||||
|
}
|
||||||
|
for _, ipNet := range b {
|
||||||
|
key := ipNet.String()
|
||||||
|
if _, ok := seen[key]; ok {
|
||||||
|
continue // duplicate
|
||||||
|
}
|
||||||
|
result = append(result, ipNet)
|
||||||
|
seen[key] = struct{}{}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func MergeNetaddrIPsSlices(a, b []netaddr.IP) (result []netaddr.IP) {
|
||||||
|
if a == nil && b == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
seen := make(map[string]struct{}, len(a)+len(b))
|
||||||
|
result = make([]netaddr.IP, 0, len(a)+len(b))
|
||||||
|
for _, ip := range a {
|
||||||
|
key := ip.String()
|
||||||
|
if _, ok := seen[key]; ok {
|
||||||
|
continue // duplicate
|
||||||
|
}
|
||||||
|
result = append(result, ip)
|
||||||
|
seen[key] = struct{}{}
|
||||||
|
}
|
||||||
|
for _, ip := range b {
|
||||||
|
key := ip.String()
|
||||||
|
if _, ok := seen[key]; ok {
|
||||||
|
continue // duplicate
|
||||||
|
}
|
||||||
|
result = append(result, ip)
|
||||||
|
seen[key] = struct{}{}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func MergeIPPrefixesSlices(a, b []netaddr.IPPrefix) (result []netaddr.IPPrefix) {
|
||||||
|
if a == nil && b == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
seen := make(map[string]struct{}, len(a)+len(b))
|
||||||
|
result = make([]netaddr.IPPrefix, 0, len(a)+len(b))
|
||||||
|
for _, ipPrefix := range a {
|
||||||
|
key := ipPrefix.String()
|
||||||
|
if _, ok := seen[key]; ok {
|
||||||
|
continue // duplicate
|
||||||
|
}
|
||||||
|
result = append(result, ipPrefix)
|
||||||
|
seen[key] = struct{}{}
|
||||||
|
}
|
||||||
|
for _, ipPrefix := range b {
|
||||||
|
key := ipPrefix.String()
|
||||||
|
if _, ok := seen[key]; ok {
|
||||||
|
continue // duplicate
|
||||||
|
}
|
||||||
|
result = append(result, ipPrefix)
|
||||||
|
seen[key] = struct{}{}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
29
internal/configuration/settings/helpers/messages.go
Normal file
29
internal/configuration/settings/helpers/messages.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package helpers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ChoicesOrString(choices []string) string {
|
||||||
|
return strings.Join(
|
||||||
|
choices[:len(choices)-1], ", ") +
|
||||||
|
" or " + choices[len(choices)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func PortChoicesOrString(ports []uint16) (s string) {
|
||||||
|
switch len(ports) {
|
||||||
|
case 0:
|
||||||
|
return "there is no allowed port"
|
||||||
|
case 1:
|
||||||
|
return "allowed port is " + fmt.Sprint(ports[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
s = "allowed ports are "
|
||||||
|
portStrings := make([]string, len(ports))
|
||||||
|
for i := range ports {
|
||||||
|
portStrings[i] = fmt.Sprint(ports[i])
|
||||||
|
}
|
||||||
|
s += ChoicesOrString(portStrings)
|
||||||
|
return s
|
||||||
|
}
|
||||||
25
internal/configuration/settings/helpers/obfuscate.go
Normal file
25
internal/configuration/settings/helpers/obfuscate.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package helpers
|
||||||
|
|
||||||
|
func ObfuscateWireguardKey(fullKey string) (obfuscatedKey string) {
|
||||||
|
const minKeyLength = 10
|
||||||
|
if len(fullKey) < minKeyLength {
|
||||||
|
return "(too short)"
|
||||||
|
}
|
||||||
|
|
||||||
|
lastIndex := len(fullKey) - 1
|
||||||
|
return fullKey[0:2] + "..." + fullKey[lastIndex-2:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func ObfuscatePassword(password string) (obfuscatedPassword string) {
|
||||||
|
if password != "" {
|
||||||
|
return "[set]"
|
||||||
|
}
|
||||||
|
return "[not set]"
|
||||||
|
}
|
||||||
|
|
||||||
|
func ObfuscateData(data string) (obfuscated string) {
|
||||||
|
if data != "" {
|
||||||
|
return "[set]"
|
||||||
|
}
|
||||||
|
return "[not set]"
|
||||||
|
}
|
||||||
148
internal/configuration/settings/helpers/override.go
Normal file
148
internal/configuration/settings/helpers/override.go
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
package helpers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/qdm12/golibs/logging"
|
||||||
|
"inet.af/netaddr"
|
||||||
|
)
|
||||||
|
|
||||||
|
func OverrideWithBool(existing, other *bool) (result *bool) {
|
||||||
|
if other == nil {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
result = new(bool)
|
||||||
|
*result = *other
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func OverrideWithString(existing, other string) (result string) {
|
||||||
|
if other == "" {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
return other
|
||||||
|
}
|
||||||
|
|
||||||
|
func OverrideWithInt(existing, other int) (result int) {
|
||||||
|
if other == 0 {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
return other
|
||||||
|
}
|
||||||
|
|
||||||
|
func OverrideWithStringPtr(existing, other *string) (result *string) {
|
||||||
|
if other == nil {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
result = new(string)
|
||||||
|
*result = *other
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func OverrideWithIntPtr(existing, other *int) (result *int) {
|
||||||
|
if other == nil {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
result = new(int)
|
||||||
|
*result = *other
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func OverrideWithUint8(existing, other *uint8) (result *uint8) {
|
||||||
|
if other == nil {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
result = new(uint8)
|
||||||
|
*result = *other
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func OverrideWithUint16(existing, other *uint16) (result *uint16) {
|
||||||
|
if other == nil {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
result = new(uint16)
|
||||||
|
*result = *other
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func OverrideWithIP(existing, other net.IP) (result net.IP) {
|
||||||
|
if other == nil {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
result = make(net.IP, len(other))
|
||||||
|
copy(result, other)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func OverrideWithDuration(existing, other *time.Duration) (result *time.Duration) {
|
||||||
|
if other == nil {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
result = new(time.Duration)
|
||||||
|
*result = *other
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func OverrideWithLogLevel(existing, other *logging.Level) (result *logging.Level) {
|
||||||
|
if other == nil {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
result = new(logging.Level)
|
||||||
|
*result = *other
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func OverrideWithHTTPHandler(existing, other http.Handler) (result http.Handler) {
|
||||||
|
if other != nil {
|
||||||
|
return other
|
||||||
|
}
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
|
||||||
|
func OverrideWithStringSlice(existing, other []string) (result []string) {
|
||||||
|
if other == nil {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
result = make([]string, len(other))
|
||||||
|
copy(result, other)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func OverrideWithUint16Slice(existing, other []uint16) (result []uint16) {
|
||||||
|
if other == nil {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
result = make([]uint16, len(other))
|
||||||
|
copy(result, other)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func OverrideWithIPNetsSlice(existing, other []net.IPNet) (result []net.IPNet) {
|
||||||
|
if other == nil {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
result = make([]net.IPNet, len(other))
|
||||||
|
copy(result, other)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func OverrideWithNetaddrIPsSlice(existing, other []netaddr.IP) (result []netaddr.IP) {
|
||||||
|
if other == nil {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
result = make([]netaddr.IP, len(other))
|
||||||
|
copy(result, other)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func OverrideWithIPPrefixesSlice(existing, other []netaddr.IPPrefix) (result []netaddr.IPPrefix) {
|
||||||
|
if other == nil {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
result = make([]netaddr.IPPrefix, len(other))
|
||||||
|
copy(result, other)
|
||||||
|
return result
|
||||||
|
}
|
||||||
11
internal/configuration/settings/helpers/pointers.go
Normal file
11
internal/configuration/settings/helpers/pointers.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package helpers
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// StringPtr returns a pointer to the string value
|
||||||
|
// passed as argument.
|
||||||
|
func StringPtr(s string) *string { return &s }
|
||||||
|
|
||||||
|
// DurationPtr returns a pointer to the duration value
|
||||||
|
// passed as argument.
|
||||||
|
func DurationPtr(d time.Duration) *time.Duration { return &d }
|
||||||
15
internal/configuration/settings/helpers/string.go
Normal file
15
internal/configuration/settings/helpers/string.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package helpers
|
||||||
|
|
||||||
|
func BoolPtrToYesNo(b *bool) string {
|
||||||
|
if *b {
|
||||||
|
return "yes"
|
||||||
|
}
|
||||||
|
return "no"
|
||||||
|
}
|
||||||
|
|
||||||
|
func TCPPtrToString(tcp *bool) string {
|
||||||
|
if *tcp {
|
||||||
|
return "TCP"
|
||||||
|
}
|
||||||
|
return "UDP"
|
||||||
|
}
|
||||||
4
internal/configuration/settings/helpers_test.go
Normal file
4
internal/configuration/settings/helpers_test.go
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
func boolPtr(b bool) *bool { return &b }
|
||||||
|
func uint8Ptr(n uint8) *uint8 { return &n }
|
||||||
111
internal/configuration/settings/httpproxy.go
Normal file
111
internal/configuration/settings/httpproxy.go
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
|
"github.com/qdm12/gotree"
|
||||||
|
"github.com/qdm12/govalid/address"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HTTPProxy contains settings to configure the HTTP proxy.
|
||||||
|
type HTTPProxy struct {
|
||||||
|
// User is the username to use for the HTTP proxy.
|
||||||
|
// It cannot be nil in the internal state.
|
||||||
|
User *string
|
||||||
|
// Password is the password to use for the HTTP proxy.
|
||||||
|
// It cannot be nil in the internal state.
|
||||||
|
Password *string
|
||||||
|
// ListeningAddress is the listening address
|
||||||
|
// of the HTTP proxy server.
|
||||||
|
// It cannot be the empty string in the internal state.
|
||||||
|
ListeningAddress string
|
||||||
|
// Enabled is true if the HTTP proxy server should run,
|
||||||
|
// and false otherwise. It cannot be nil in the
|
||||||
|
// internal state.
|
||||||
|
Enabled *bool
|
||||||
|
// Stealth is true if the HTTP proxy server should hide
|
||||||
|
// each request has been proxied to the destination.
|
||||||
|
// It cannot be nil in the internal state.
|
||||||
|
Stealth *bool
|
||||||
|
// Log is true if the HTTP proxy server should log
|
||||||
|
// each request/response. It cannot be nil in the
|
||||||
|
// internal state.
|
||||||
|
Log *bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h HTTPProxy) validate() (err error) {
|
||||||
|
// Do not validate user and password
|
||||||
|
|
||||||
|
uid := os.Getuid()
|
||||||
|
_, err = address.Validate(h.ListeningAddress, address.OptionListening(uid))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%w: %s", ErrServerAddressNotValid, h.ListeningAddress)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HTTPProxy) copy() (copied HTTPProxy) {
|
||||||
|
return HTTPProxy{
|
||||||
|
User: helpers.CopyStringPtr(h.User),
|
||||||
|
Password: helpers.CopyStringPtr(h.Password),
|
||||||
|
ListeningAddress: h.ListeningAddress,
|
||||||
|
Enabled: helpers.CopyBoolPtr(h.Enabled),
|
||||||
|
Stealth: helpers.CopyBoolPtr(h.Stealth),
|
||||||
|
Log: helpers.CopyBoolPtr(h.Log),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// mergeWith merges the other settings into any
|
||||||
|
// unset field of the receiver settings object.
|
||||||
|
func (h *HTTPProxy) mergeWith(other HTTPProxy) {
|
||||||
|
h.User = helpers.MergeWithStringPtr(h.User, other.User)
|
||||||
|
h.Password = helpers.MergeWithStringPtr(h.Password, other.Password)
|
||||||
|
h.ListeningAddress = helpers.MergeWithString(h.ListeningAddress, other.ListeningAddress)
|
||||||
|
h.Enabled = helpers.MergeWithBool(h.Enabled, other.Enabled)
|
||||||
|
h.Stealth = helpers.MergeWithBool(h.Stealth, other.Stealth)
|
||||||
|
h.Log = helpers.MergeWithBool(h.Log, other.Log)
|
||||||
|
}
|
||||||
|
|
||||||
|
// overrideWith overrides fields of the receiver
|
||||||
|
// settings object with any field set in the other
|
||||||
|
// settings.
|
||||||
|
func (h *HTTPProxy) overrideWith(other HTTPProxy) {
|
||||||
|
h.User = helpers.OverrideWithStringPtr(h.User, other.User)
|
||||||
|
h.Password = helpers.OverrideWithStringPtr(h.Password, other.Password)
|
||||||
|
h.ListeningAddress = helpers.OverrideWithString(h.ListeningAddress, other.ListeningAddress)
|
||||||
|
h.Enabled = helpers.OverrideWithBool(h.Enabled, other.Enabled)
|
||||||
|
h.Stealth = helpers.OverrideWithBool(h.Stealth, other.Stealth)
|
||||||
|
h.Log = helpers.OverrideWithBool(h.Log, other.Log)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HTTPProxy) setDefaults() {
|
||||||
|
h.User = helpers.DefaultStringPtr(h.User, "")
|
||||||
|
h.Password = helpers.DefaultStringPtr(h.Password, "")
|
||||||
|
h.ListeningAddress = helpers.DefaultString(h.ListeningAddress, ":8888")
|
||||||
|
h.Enabled = helpers.DefaultBool(h.Enabled, false)
|
||||||
|
h.Stealth = helpers.DefaultBool(h.Stealth, false)
|
||||||
|
h.Log = helpers.DefaultBool(h.Log, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h HTTPProxy) String() string {
|
||||||
|
return h.toLinesNode().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h HTTPProxy) toLinesNode() (node *gotree.Node) {
|
||||||
|
node = gotree.New("HTTP proxy settings:")
|
||||||
|
node.Appendf("Enabled: %s", helpers.BoolPtrToYesNo(h.Enabled))
|
||||||
|
if !*h.Enabled {
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
node.Appendf("Listening address: %s", h.ListeningAddress)
|
||||||
|
node.Appendf("User: %s", *h.User)
|
||||||
|
node.Appendf("Password: %s", helpers.ObfuscatePassword(*h.Password))
|
||||||
|
node.Appendf("Stealth mode: %s", helpers.BoolPtrToYesNo(h.Stealth))
|
||||||
|
node.Appendf("Log: %s", helpers.BoolPtrToYesNo(h.Log))
|
||||||
|
|
||||||
|
return node
|
||||||
|
}
|
||||||
51
internal/configuration/settings/log.go
Normal file
51
internal/configuration/settings/log.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
|
"github.com/qdm12/golibs/logging"
|
||||||
|
"github.com/qdm12/gotree"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Log contains settings to configure the logger.
|
||||||
|
type Log struct {
|
||||||
|
// Level is the log level of the logger.
|
||||||
|
// It cannot be nil in the internal state.
|
||||||
|
Level *logging.Level
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Log) validate() (err error) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Log) copy() (copied Log) {
|
||||||
|
return Log{
|
||||||
|
Level: helpers.CopyLogLevelPtr(l.Level),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// mergeWith merges the other settings into any
|
||||||
|
// unset field of the receiver settings object.
|
||||||
|
func (l *Log) mergeWith(other Log) {
|
||||||
|
l.Level = helpers.MergeWithLogLevel(l.Level, other.Level)
|
||||||
|
}
|
||||||
|
|
||||||
|
// overrideWith overrides fields of the receiver
|
||||||
|
// settings object with any field set in the other
|
||||||
|
// settings.
|
||||||
|
func (l *Log) overrideWith(other Log) {
|
||||||
|
l.Level = helpers.OverrideWithLogLevel(l.Level, other.Level)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Log) setDefaults() {
|
||||||
|
l.Level = helpers.DefaultLogLevel(l.Level, logging.LevelInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Log) String() string {
|
||||||
|
return l.toLinesNode().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Log) toLinesNode() (node *gotree.Node) {
|
||||||
|
node = gotree.New("Log settings:")
|
||||||
|
node.Appendf("Log level: %s", l.Level.String())
|
||||||
|
return node
|
||||||
|
}
|
||||||
335
internal/configuration/settings/openvpn.go
Normal file
335
internal/configuration/settings/openvpn.go
Normal file
@@ -0,0 +1,335 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gluetun/internal/openvpn/parse"
|
||||||
|
"github.com/qdm12/gotree"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OpenVPN contains settings to configure the OpenVPN client.
|
||||||
|
type OpenVPN struct {
|
||||||
|
// Version is the OpenVPN version to run.
|
||||||
|
// It can only be "2.4" or "2.5".
|
||||||
|
Version string
|
||||||
|
// User is the OpenVPN authentication username.
|
||||||
|
// It cannot be an empty string in the internal state
|
||||||
|
// if OpenVPN is used.
|
||||||
|
User string
|
||||||
|
// Password is the OpenVPN authentication password.
|
||||||
|
// It cannot be an empty string in the internal state
|
||||||
|
// if OpenVPN is used.
|
||||||
|
Password string
|
||||||
|
// ConfFile is a custom OpenVPN configuration file path.
|
||||||
|
// It can be set to the empty string for it to be ignored.
|
||||||
|
// It cannot be nil in the internal state.
|
||||||
|
ConfFile *string
|
||||||
|
// Ciphers is a list of ciphers to use for OpenVPN,
|
||||||
|
// different from the ones specified by the VPN
|
||||||
|
// service provider configuration files.
|
||||||
|
Ciphers []string
|
||||||
|
// Auth is an auth algorithm to use in OpenVPN instead
|
||||||
|
// of the one specified by the VPN service provider.
|
||||||
|
// It cannot be nil in the internal state.
|
||||||
|
// It is ignored if it is set to the empty string.
|
||||||
|
Auth *string
|
||||||
|
// ClientCrt is the OpenVPN client certificate.
|
||||||
|
// This is notably used by Cyberghost.
|
||||||
|
// It can be set to the empty string to be ignored.
|
||||||
|
// It cannot be nil in the internal state.
|
||||||
|
ClientCrt *string
|
||||||
|
// ClientKey is the OpenVPN client key.
|
||||||
|
// This is used by Cyberghost and VPN Unlimited.
|
||||||
|
// It can be set to the empty string to be ignored.
|
||||||
|
// It cannot be nil in the internal state.
|
||||||
|
ClientKey *string
|
||||||
|
// PIAEncPreset is the encryption preset for
|
||||||
|
// Private Internet Access. It can be set to an
|
||||||
|
// empty string for other providers.
|
||||||
|
PIAEncPreset *string
|
||||||
|
// IPv6 is set to true if IPv6 routing should be
|
||||||
|
// set to be tunnel in OpenVPN, and false otherwise.
|
||||||
|
// It cannot be nil in the internal state.
|
||||||
|
IPv6 *bool // TODO automate like with Wireguard
|
||||||
|
// MSSFix is the value (1 to 10000) to set for the
|
||||||
|
// mssfix option for OpenVPN. It is ignored if set to 0.
|
||||||
|
// It cannot be nil in the internal state.
|
||||||
|
MSSFix *uint16
|
||||||
|
// Interface is the OpenVPN device interface name.
|
||||||
|
// It cannot be an empty string in the internal state.
|
||||||
|
Interface string
|
||||||
|
// ProcessUser is the OpenVPN process OS username
|
||||||
|
// to use. It cannot be empty in the internal state.
|
||||||
|
// It defaults to 'root'.
|
||||||
|
ProcessUser string
|
||||||
|
// Verbosity is the OpenVPN verbosity level from 0 to 6.
|
||||||
|
// It cannot be nil in the internal state.
|
||||||
|
Verbosity *int
|
||||||
|
// Flags is a slice of additional flags to be passed
|
||||||
|
// to the OpenVPN program.
|
||||||
|
Flags []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o OpenVPN) validate(vpnProvider string) (err error) {
|
||||||
|
// Validate version
|
||||||
|
validVersions := []string{constants.Openvpn24, constants.Openvpn25}
|
||||||
|
if !helpers.IsOneOf(o.Version, validVersions...) {
|
||||||
|
return fmt.Errorf("%w: %q can only be one of %s",
|
||||||
|
ErrOpenVPNVersionIsNotValid, o.Version, strings.Join(validVersions, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
isCustom := vpnProvider == constants.Custom
|
||||||
|
|
||||||
|
if !isCustom && o.User == "" {
|
||||||
|
return ErrOpenVPNUserIsEmpty
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isCustom && o.Password == "" {
|
||||||
|
return ErrOpenVPNPasswordIsEmpty
|
||||||
|
}
|
||||||
|
|
||||||
|
err = validateOpenVPNConfigFilepath(isCustom, *o.ConfFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("custom configuration file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = validateOpenVPNClientCertificate(vpnProvider, *o.ClientCrt)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("client certificate: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = validateOpenVPNClientKey(vpnProvider, *o.ClientKey)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("client key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxMSSFix = 10000
|
||||||
|
if *o.MSSFix > maxMSSFix {
|
||||||
|
return fmt.Errorf("%w: %d is over the maximum value of %d",
|
||||||
|
ErrOpenVPNMSSFixIsTooHigh, *o.MSSFix, maxMSSFix)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !regexpInterfaceName.MatchString(o.Interface) {
|
||||||
|
return fmt.Errorf("%w: '%s' does not match regex '%s'",
|
||||||
|
ErrOpenVPNInterfaceNotValid, o.Interface, regexpInterfaceName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *o.Verbosity < 0 || *o.Verbosity > 6 {
|
||||||
|
return fmt.Errorf("%w: %d can only be between 0 and 5",
|
||||||
|
ErrOpenVPNVerbosityIsOutOfBounds, o.Verbosity)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateOpenVPNConfigFilepath(isCustom bool,
|
||||||
|
confFile string) (err error) {
|
||||||
|
if !isCustom {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if confFile == "" {
|
||||||
|
return ErrFilepathMissing
|
||||||
|
}
|
||||||
|
|
||||||
|
err = helpers.FileExists(confFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateOpenVPNClientCertificate(vpnProvider,
|
||||||
|
clientCert string) (err error) {
|
||||||
|
switch vpnProvider {
|
||||||
|
case
|
||||||
|
constants.Cyberghost,
|
||||||
|
constants.VPNUnlimited:
|
||||||
|
if clientCert == "" {
|
||||||
|
return ErrMissingValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if clientCert == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = parse.ExtractCert([]byte(clientCert))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateOpenVPNClientKey(vpnProvider, clientKey string) (err error) {
|
||||||
|
switch vpnProvider {
|
||||||
|
case
|
||||||
|
constants.Cyberghost,
|
||||||
|
constants.VPNUnlimited,
|
||||||
|
constants.Wevpn:
|
||||||
|
if clientKey == "" {
|
||||||
|
return ErrMissingValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if clientKey == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = parse.ExtractPrivateKey([]byte(clientKey))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *OpenVPN) copy() (copied OpenVPN) {
|
||||||
|
return OpenVPN{
|
||||||
|
Version: o.Version,
|
||||||
|
User: o.User,
|
||||||
|
Password: o.Password,
|
||||||
|
ConfFile: helpers.CopyStringPtr(o.ConfFile),
|
||||||
|
Ciphers: helpers.CopyStringSlice(o.Ciphers),
|
||||||
|
Auth: helpers.CopyStringPtr(o.Auth),
|
||||||
|
ClientCrt: helpers.CopyStringPtr(o.ClientCrt),
|
||||||
|
ClientKey: helpers.CopyStringPtr(o.ClientKey),
|
||||||
|
PIAEncPreset: helpers.CopyStringPtr(o.PIAEncPreset),
|
||||||
|
IPv6: helpers.CopyBoolPtr(o.IPv6),
|
||||||
|
MSSFix: helpers.CopyUint16Ptr(o.MSSFix),
|
||||||
|
Interface: o.Interface,
|
||||||
|
ProcessUser: o.ProcessUser,
|
||||||
|
Verbosity: helpers.CopyIntPtr(o.Verbosity),
|
||||||
|
Flags: helpers.CopyStringSlice(o.Flags),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// mergeWith merges the other settings into any
|
||||||
|
// unset field of the receiver settings object.
|
||||||
|
func (o *OpenVPN) mergeWith(other OpenVPN) {
|
||||||
|
o.Version = helpers.MergeWithString(o.Version, other.Version)
|
||||||
|
o.User = helpers.MergeWithString(o.User, other.User)
|
||||||
|
o.Password = helpers.MergeWithString(o.Password, other.Password)
|
||||||
|
o.ConfFile = helpers.MergeWithStringPtr(o.ConfFile, other.ConfFile)
|
||||||
|
o.Ciphers = helpers.MergeStringSlices(o.Ciphers, other.Ciphers)
|
||||||
|
o.Auth = helpers.MergeWithStringPtr(o.Auth, other.Auth)
|
||||||
|
o.ClientCrt = helpers.MergeWithStringPtr(o.ClientCrt, other.ClientCrt)
|
||||||
|
o.ClientKey = helpers.MergeWithStringPtr(o.ClientKey, other.ClientKey)
|
||||||
|
o.PIAEncPreset = helpers.MergeWithStringPtr(o.PIAEncPreset, other.PIAEncPreset)
|
||||||
|
o.IPv6 = helpers.MergeWithBool(o.IPv6, other.IPv6)
|
||||||
|
o.MSSFix = helpers.MergeWithUint16(o.MSSFix, other.MSSFix)
|
||||||
|
o.Interface = helpers.MergeWithString(o.Interface, other.Interface)
|
||||||
|
o.ProcessUser = helpers.MergeWithString(o.ProcessUser, other.ProcessUser)
|
||||||
|
o.Verbosity = helpers.MergeWithIntPtr(o.Verbosity, other.Verbosity)
|
||||||
|
o.Flags = helpers.MergeStringSlices(o.Flags, other.Flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
// overrideWith overrides fields of the receiver
|
||||||
|
// settings object with any field set in the other
|
||||||
|
// settings.
|
||||||
|
func (o *OpenVPN) overrideWith(other OpenVPN) {
|
||||||
|
o.Version = helpers.OverrideWithString(o.Version, other.Version)
|
||||||
|
o.User = helpers.OverrideWithString(o.User, other.User)
|
||||||
|
o.Password = helpers.OverrideWithString(o.Password, other.Password)
|
||||||
|
o.ConfFile = helpers.OverrideWithStringPtr(o.ConfFile, other.ConfFile)
|
||||||
|
o.Ciphers = helpers.OverrideWithStringSlice(o.Ciphers, other.Ciphers)
|
||||||
|
o.Auth = helpers.OverrideWithStringPtr(o.Auth, other.Auth)
|
||||||
|
o.ClientCrt = helpers.OverrideWithStringPtr(o.ClientCrt, other.ClientCrt)
|
||||||
|
o.ClientKey = helpers.OverrideWithStringPtr(o.ClientKey, other.ClientKey)
|
||||||
|
o.PIAEncPreset = helpers.OverrideWithStringPtr(o.PIAEncPreset, other.PIAEncPreset)
|
||||||
|
o.IPv6 = helpers.OverrideWithBool(o.IPv6, other.IPv6)
|
||||||
|
o.MSSFix = helpers.OverrideWithUint16(o.MSSFix, other.MSSFix)
|
||||||
|
o.Interface = helpers.OverrideWithString(o.Interface, other.Interface)
|
||||||
|
o.ProcessUser = helpers.OverrideWithString(o.ProcessUser, other.ProcessUser)
|
||||||
|
o.Verbosity = helpers.OverrideWithIntPtr(o.Verbosity, other.Verbosity)
|
||||||
|
o.Flags = helpers.OverrideWithStringSlice(o.Flags, other.Flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *OpenVPN) setDefaults(vpnProvider string) {
|
||||||
|
o.Version = helpers.DefaultString(o.Version, constants.Openvpn25)
|
||||||
|
if vpnProvider == constants.Mullvad {
|
||||||
|
o.Password = "m"
|
||||||
|
}
|
||||||
|
|
||||||
|
o.ConfFile = helpers.DefaultStringPtr(o.ConfFile, "")
|
||||||
|
o.Auth = helpers.DefaultStringPtr(o.Auth, "")
|
||||||
|
o.ClientCrt = helpers.DefaultStringPtr(o.ClientCrt, "")
|
||||||
|
o.ClientKey = helpers.DefaultStringPtr(o.ClientKey, "")
|
||||||
|
|
||||||
|
var defaultEncPreset string
|
||||||
|
if vpnProvider == constants.PrivateInternetAccess {
|
||||||
|
defaultEncPreset = constants.PIAEncryptionPresetStrong
|
||||||
|
}
|
||||||
|
o.PIAEncPreset = helpers.DefaultStringPtr(o.PIAEncPreset, defaultEncPreset)
|
||||||
|
|
||||||
|
o.IPv6 = helpers.DefaultBool(o.IPv6, false)
|
||||||
|
o.MSSFix = helpers.DefaultUint16(o.MSSFix, 0)
|
||||||
|
o.Interface = helpers.DefaultString(o.Interface, "tun0")
|
||||||
|
o.ProcessUser = helpers.DefaultString(o.ProcessUser, "root")
|
||||||
|
o.Verbosity = helpers.DefaultInt(o.Verbosity, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o OpenVPN) String() string {
|
||||||
|
return o.toLinesNode().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o OpenVPN) toLinesNode() (node *gotree.Node) {
|
||||||
|
node = gotree.New("OpenVPN settings:")
|
||||||
|
node.Appendf("OpenVPN version: %s", o.Version)
|
||||||
|
node.Appendf("User: %s", helpers.ObfuscatePassword(o.User))
|
||||||
|
node.Appendf("Password: %s", helpers.ObfuscatePassword(o.Password))
|
||||||
|
|
||||||
|
if *o.ConfFile != "" {
|
||||||
|
node.Appendf("Custom configuration file: %s", *o.ConfFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(o.Ciphers) > 0 {
|
||||||
|
node.Appendf("Ciphers: %s", o.Ciphers)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *o.Auth != "" {
|
||||||
|
node.Appendf("Auth: %s", *o.Auth)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *o.ClientCrt != "" {
|
||||||
|
node.Appendf("Client crt: %s", helpers.ObfuscateData(*o.ClientCrt))
|
||||||
|
}
|
||||||
|
|
||||||
|
if *o.ClientKey != "" {
|
||||||
|
node.Appendf("Client key: %s", helpers.ObfuscateData(*o.ClientKey))
|
||||||
|
}
|
||||||
|
|
||||||
|
if *o.PIAEncPreset != "" {
|
||||||
|
node.Appendf("Private Internet Access encryption preset: %s", *o.PIAEncPreset)
|
||||||
|
}
|
||||||
|
|
||||||
|
node.Appendf("Tunnel IPv6: %s", helpers.BoolPtrToYesNo(o.IPv6))
|
||||||
|
|
||||||
|
if *o.MSSFix > 0 {
|
||||||
|
node.Appendf("MSS Fix: %d", *o.MSSFix)
|
||||||
|
}
|
||||||
|
|
||||||
|
if o.Interface != "" {
|
||||||
|
node.Appendf("Network interface: %s", o.Interface)
|
||||||
|
}
|
||||||
|
|
||||||
|
node.Appendf("Run OpenVPN as: %s", o.ProcessUser)
|
||||||
|
|
||||||
|
node.Appendf("Verbosity level: %d", *o.Verbosity)
|
||||||
|
|
||||||
|
if len(o.Flags) > 0 {
|
||||||
|
node.Appendf("Flags: %s", o.Flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDefaults is a shorthand using setDefaults.
|
||||||
|
// It's used in unit tests in other packages.
|
||||||
|
func (o OpenVPN) WithDefaults(provider string) OpenVPN {
|
||||||
|
o.setDefaults(provider)
|
||||||
|
return o
|
||||||
|
}
|
||||||
172
internal/configuration/settings/openvpnselection.go
Normal file
172
internal/configuration/settings/openvpnselection.go
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gotree"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OpenVPNSelection struct {
|
||||||
|
// ConfFile is the custom configuration file path.
|
||||||
|
// It can be set to an empty string to indicate to
|
||||||
|
// NOT use a custom configuration file.
|
||||||
|
// It cannot be nil in the internal state.
|
||||||
|
ConfFile *string
|
||||||
|
// TCP is true if the OpenVPN protocol is TCP,
|
||||||
|
// and false for UDP.
|
||||||
|
// It cannot be nil in the internal state.
|
||||||
|
TCP *bool
|
||||||
|
// CustomPort is the OpenVPN server endpoint port.
|
||||||
|
// It can be set to 0 to indicate no custom port should
|
||||||
|
// be used. It cannot be nil in the internal state.
|
||||||
|
CustomPort *uint16 // HideMyAss, Mullvad, PIA, ProtonVPN, WeVPN, Windscribe
|
||||||
|
// PIAEncPreset is the encryption preset for
|
||||||
|
// Private Internet Access. It can be set to an
|
||||||
|
// empty string for other providers.
|
||||||
|
PIAEncPreset *string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
|
||||||
|
// Validate ConfFile
|
||||||
|
if confFile := *o.ConfFile; confFile != "" {
|
||||||
|
err := helpers.FileExists(confFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("configuration file: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate TCP
|
||||||
|
if *o.TCP && helpers.IsOneOf(vpnProvider,
|
||||||
|
constants.Ipvanish,
|
||||||
|
constants.Perfectprivacy,
|
||||||
|
constants.Privado,
|
||||||
|
constants.VPNUnlimited,
|
||||||
|
constants.Vyprvpn,
|
||||||
|
) {
|
||||||
|
return fmt.Errorf("%w: for VPN service provider %s",
|
||||||
|
ErrOpenVPNTCPNotSupported, vpnProvider)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate CustomPort
|
||||||
|
if *o.CustomPort != 0 {
|
||||||
|
switch vpnProvider {
|
||||||
|
// no restriction on port
|
||||||
|
case constants.Cyberghost, constants.HideMyAss,
|
||||||
|
constants.PrivateInternetAccess, constants.Privatevpn,
|
||||||
|
constants.Protonvpn, constants.Torguard:
|
||||||
|
// no custom port allowed
|
||||||
|
case constants.Expressvpn, constants.Fastestvpn,
|
||||||
|
constants.Ipvanish, constants.Nordvpn,
|
||||||
|
constants.Privado, constants.Purevpn,
|
||||||
|
constants.Surfshark, constants.VPNUnlimited,
|
||||||
|
constants.Vyprvpn:
|
||||||
|
return fmt.Errorf("%w: for VPN service provider %s",
|
||||||
|
ErrOpenVPNCustomPortNotAllowed, vpnProvider)
|
||||||
|
default:
|
||||||
|
var allowedTCP, allowedUDP []uint16
|
||||||
|
switch vpnProvider {
|
||||||
|
case constants.Ivpn:
|
||||||
|
allowedTCP = []uint16{80, 443, 1143}
|
||||||
|
allowedUDP = []uint16{53, 1194, 2049, 2050}
|
||||||
|
case constants.Mullvad:
|
||||||
|
allowedTCP = []uint16{80, 443, 1401}
|
||||||
|
allowedUDP = []uint16{53, 1194, 1195, 1196, 1197, 1300, 1301, 1302, 1303, 1400}
|
||||||
|
case constants.Perfectprivacy:
|
||||||
|
allowedTCP = []uint16{44, 443, 4433}
|
||||||
|
allowedUDP = []uint16{44, 443, 4433}
|
||||||
|
case constants.Wevpn:
|
||||||
|
allowedTCP = []uint16{53, 1195, 1199, 2018}
|
||||||
|
allowedUDP = []uint16{80, 1194, 1198}
|
||||||
|
case constants.Windscribe:
|
||||||
|
allowedTCP = []uint16{21, 22, 80, 123, 143, 443, 587, 1194, 3306, 8080, 54783}
|
||||||
|
allowedUDP = []uint16{53, 80, 123, 443, 1194, 54783}
|
||||||
|
}
|
||||||
|
|
||||||
|
if *o.TCP && !helpers.Uint16IsOneOf(*o.CustomPort, allowedTCP) {
|
||||||
|
return fmt.Errorf("%w: %d for VPN service provider %s; %s",
|
||||||
|
ErrOpenVPNCustomPortNotAllowed, o.CustomPort, vpnProvider,
|
||||||
|
helpers.PortChoicesOrString(allowedTCP))
|
||||||
|
} else if !*o.TCP && !helpers.Uint16IsOneOf(*o.CustomPort, allowedUDP) {
|
||||||
|
return fmt.Errorf("%w: %d for VPN service provider %s; %s",
|
||||||
|
ErrOpenVPNCustomPortNotAllowed, o.CustomPort, vpnProvider,
|
||||||
|
helpers.PortChoicesOrString(allowedUDP))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate EncPreset
|
||||||
|
if vpnProvider == constants.PrivateInternetAccess {
|
||||||
|
validEncryptionPresets := []string{
|
||||||
|
constants.PIAEncryptionPresetNone,
|
||||||
|
constants.PIAEncryptionPresetNormal,
|
||||||
|
constants.PIAEncryptionPresetStrong,
|
||||||
|
}
|
||||||
|
if !helpers.IsOneOf(*o.PIAEncPreset, validEncryptionPresets...) {
|
||||||
|
return fmt.Errorf("%w: %s; valid presets are %s",
|
||||||
|
ErrOpenVPNEncryptionPresetNotValid, *o.PIAEncPreset,
|
||||||
|
helpers.ChoicesOrString(validEncryptionPresets))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *OpenVPNSelection) copy() (copied OpenVPNSelection) {
|
||||||
|
return OpenVPNSelection{
|
||||||
|
ConfFile: helpers.CopyStringPtr(o.ConfFile),
|
||||||
|
TCP: helpers.CopyBoolPtr(o.TCP),
|
||||||
|
CustomPort: helpers.CopyUint16Ptr(o.CustomPort),
|
||||||
|
PIAEncPreset: helpers.CopyStringPtr(o.PIAEncPreset),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *OpenVPNSelection) mergeWith(other OpenVPNSelection) {
|
||||||
|
o.ConfFile = helpers.MergeWithStringPtr(o.ConfFile, other.ConfFile)
|
||||||
|
o.TCP = helpers.MergeWithBool(o.TCP, other.TCP)
|
||||||
|
o.CustomPort = helpers.MergeWithUint16(o.CustomPort, other.CustomPort)
|
||||||
|
o.PIAEncPreset = helpers.MergeWithStringPtr(o.PIAEncPreset, other.PIAEncPreset)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *OpenVPNSelection) overrideWith(other OpenVPNSelection) {
|
||||||
|
o.ConfFile = helpers.OverrideWithStringPtr(o.ConfFile, other.ConfFile)
|
||||||
|
o.TCP = helpers.OverrideWithBool(o.TCP, other.TCP)
|
||||||
|
o.CustomPort = helpers.OverrideWithUint16(o.CustomPort, other.CustomPort)
|
||||||
|
o.PIAEncPreset = helpers.OverrideWithStringPtr(o.PIAEncPreset, other.PIAEncPreset)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *OpenVPNSelection) setDefaults(vpnProvider string) {
|
||||||
|
o.ConfFile = helpers.DefaultStringPtr(o.ConfFile, "")
|
||||||
|
o.TCP = helpers.DefaultBool(o.TCP, false)
|
||||||
|
o.CustomPort = helpers.DefaultUint16(o.CustomPort, 0)
|
||||||
|
|
||||||
|
var defaultEncPreset string
|
||||||
|
if vpnProvider == constants.PrivateInternetAccess {
|
||||||
|
defaultEncPreset = constants.PIAEncryptionPresetStrong
|
||||||
|
}
|
||||||
|
o.PIAEncPreset = helpers.DefaultStringPtr(o.PIAEncPreset, defaultEncPreset)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o OpenVPNSelection) String() string {
|
||||||
|
return o.toLinesNode().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o OpenVPNSelection) toLinesNode() (node *gotree.Node) {
|
||||||
|
node = gotree.New("OpenVPN server selection settings:")
|
||||||
|
node.Appendf("Protocol: %s", helpers.TCPPtrToString(o.TCP))
|
||||||
|
|
||||||
|
if *o.CustomPort != 0 {
|
||||||
|
node.Appendf("Custom port: %d", *o.CustomPort)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *o.PIAEncPreset != "" {
|
||||||
|
node.Appendf("Private Internet Access encryption preset: %s", *o.PIAEncPreset)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *o.ConfFile != "" {
|
||||||
|
node.Appendf("Custom configuration file: %s", *o.ConfFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
return node
|
||||||
|
}
|
||||||
89
internal/configuration/settings/portforward.go
Normal file
89
internal/configuration/settings/portforward.go
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gotree"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PortForwarding contains settings for port forwarding.
|
||||||
|
type PortForwarding struct {
|
||||||
|
// Enabled is true if port forwarding should be activated.
|
||||||
|
// It cannot be nil for the internal state.
|
||||||
|
Enabled *bool
|
||||||
|
// Filepath is the port forwarding status file path
|
||||||
|
// to use. It can be the empty string to indicate not
|
||||||
|
// to write to a file. It cannot be nil for the
|
||||||
|
// internal state
|
||||||
|
Filepath *string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p PortForwarding) validate(vpnProvider string) (err error) {
|
||||||
|
if !*p.Enabled {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate Enabled
|
||||||
|
validProviders := []string{constants.PrivateInternetAccess}
|
||||||
|
if !helpers.IsOneOf(vpnProvider, validProviders...) {
|
||||||
|
return fmt.Errorf("%w: for provider %s, it is only available for %s",
|
||||||
|
ErrPortForwardingEnabled, vpnProvider, strings.Join(validProviders, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate Filepath
|
||||||
|
if *p.Filepath != "" { // optional
|
||||||
|
_, err := filepath.Abs(*p.Filepath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("filepath is not valid: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PortForwarding) copy() (copied PortForwarding) {
|
||||||
|
return PortForwarding{
|
||||||
|
Enabled: helpers.CopyBoolPtr(p.Enabled),
|
||||||
|
Filepath: helpers.CopyStringPtr(p.Filepath),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PortForwarding) mergeWith(other PortForwarding) {
|
||||||
|
p.Enabled = helpers.MergeWithBool(p.Enabled, other.Enabled)
|
||||||
|
p.Filepath = helpers.MergeWithStringPtr(p.Filepath, other.Filepath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PortForwarding) overrideWith(other PortForwarding) {
|
||||||
|
p.Enabled = helpers.OverrideWithBool(p.Enabled, other.Enabled)
|
||||||
|
p.Filepath = helpers.OverrideWithStringPtr(p.Filepath, other.Filepath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PortForwarding) setDefaults() {
|
||||||
|
p.Enabled = helpers.DefaultBool(p.Enabled, false)
|
||||||
|
p.Filepath = helpers.DefaultStringPtr(p.Filepath, "/tmp/gluetun/forwarded_port")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p PortForwarding) String() string {
|
||||||
|
return p.toLinesNode().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p PortForwarding) toLinesNode() (node *gotree.Node) {
|
||||||
|
if !*p.Enabled {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
node = gotree.New("Automatic port forwarding settings:")
|
||||||
|
node.Appendf("Enabled: yes")
|
||||||
|
|
||||||
|
filepath := *p.Filepath
|
||||||
|
if filepath == "" {
|
||||||
|
filepath = "[not set]"
|
||||||
|
}
|
||||||
|
node.Appendf("Forwarded port file path: %s", filepath)
|
||||||
|
|
||||||
|
return node
|
||||||
|
}
|
||||||
19
internal/configuration/settings/portforward_test.go
Normal file
19
internal/configuration/settings/portforward_test.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_PortForwarding_String(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
settings := PortForwarding{
|
||||||
|
Enabled: boolPtr(false),
|
||||||
|
}
|
||||||
|
|
||||||
|
s := settings.String()
|
||||||
|
|
||||||
|
assert.Empty(t, s)
|
||||||
|
}
|
||||||
93
internal/configuration/settings/provider.go
Normal file
93
internal/configuration/settings/provider.go
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gotree"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Provider contains settings specific to a VPN provider.
|
||||||
|
type Provider struct {
|
||||||
|
// Name is the VPN service provider name.
|
||||||
|
// It cannot be nil in the internal state.
|
||||||
|
Name *string
|
||||||
|
// ServerSelection is the settings to
|
||||||
|
// select the VPN server.
|
||||||
|
ServerSelection ServerSelection
|
||||||
|
// PortForwarding is the settings about port forwarding.
|
||||||
|
PortForwarding PortForwarding
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO v4 remove pointer for receiver (because of Surfshark).
|
||||||
|
func (p *Provider) validate(vpnType string, allServers models.AllServers) (err error) {
|
||||||
|
// Validate Name
|
||||||
|
var validNames []string
|
||||||
|
if vpnType == constants.OpenVPN {
|
||||||
|
validNames = constants.AllProviders()
|
||||||
|
validNames = append(validNames, "pia") // Retro-compatibility
|
||||||
|
} else { // Wireguard
|
||||||
|
validNames = []string{
|
||||||
|
constants.Custom,
|
||||||
|
constants.Ivpn,
|
||||||
|
constants.Mullvad,
|
||||||
|
constants.Windscribe,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !helpers.IsOneOf(*p.Name, validNames...) {
|
||||||
|
return fmt.Errorf("%w: %q can only be one of %s",
|
||||||
|
ErrVPNProviderNameNotValid, *p.Name, helpers.ChoicesOrString(validNames))
|
||||||
|
}
|
||||||
|
|
||||||
|
err = p.ServerSelection.validate(*p.Name, allServers)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("server selection: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = p.PortForwarding.validate(*p.Name)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("port forwarding: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) copy() (copied Provider) {
|
||||||
|
return Provider{
|
||||||
|
Name: helpers.CopyStringPtr(p.Name),
|
||||||
|
ServerSelection: p.ServerSelection.copy(),
|
||||||
|
PortForwarding: p.PortForwarding.copy(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) mergeWith(other Provider) {
|
||||||
|
p.Name = helpers.MergeWithStringPtr(p.Name, other.Name)
|
||||||
|
p.ServerSelection.mergeWith(other.ServerSelection)
|
||||||
|
p.PortForwarding.mergeWith(other.PortForwarding)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) overrideWith(other Provider) {
|
||||||
|
p.Name = helpers.OverrideWithStringPtr(p.Name, other.Name)
|
||||||
|
p.ServerSelection.overrideWith(other.ServerSelection)
|
||||||
|
p.PortForwarding.overrideWith(other.PortForwarding)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) setDefaults() {
|
||||||
|
p.Name = helpers.DefaultStringPtr(p.Name, constants.PrivateInternetAccess)
|
||||||
|
p.ServerSelection.setDefaults(*p.Name)
|
||||||
|
p.PortForwarding.setDefaults()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Provider) String() string {
|
||||||
|
return p.toLinesNode().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Provider) toLinesNode() (node *gotree.Node) {
|
||||||
|
node = gotree.New("VPN provider settings:")
|
||||||
|
node.Appendf("Name: %s", *p.Name)
|
||||||
|
node.AppendNode(p.ServerSelection.toLinesNode())
|
||||||
|
node.AppendNode(p.PortForwarding.toLinesNode())
|
||||||
|
return node
|
||||||
|
}
|
||||||
89
internal/configuration/settings/publicip.go
Normal file
89
internal/configuration/settings/publicip.go
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
|
"github.com/qdm12/gotree"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PublicIP contains settings for port forwarding.
|
||||||
|
type PublicIP struct {
|
||||||
|
// Period is the period to get the public IP address.
|
||||||
|
// It can be set to 0 to disable periodic checking.
|
||||||
|
// It cannot be nil for the internal state.
|
||||||
|
// TODO change to value and add enabled field
|
||||||
|
Period *time.Duration
|
||||||
|
// IPFilepath is the public IP address status file path
|
||||||
|
// to use. It can be the empty string to indicate not
|
||||||
|
// to write to a file. It cannot be nil for the
|
||||||
|
// internal state
|
||||||
|
IPFilepath *string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p PublicIP) validate() (err error) {
|
||||||
|
const minPeriod = 5 * time.Second
|
||||||
|
if *p.Period < minPeriod {
|
||||||
|
return fmt.Errorf("%w: %s must be at least %s",
|
||||||
|
ErrPublicIPPeriodTooShort, p.Period, minPeriod)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *p.IPFilepath != "" { // optional
|
||||||
|
_, err := filepath.Abs(*p.IPFilepath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("filepath is not valid: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PublicIP) copy() (copied PublicIP) {
|
||||||
|
return PublicIP{
|
||||||
|
Period: helpers.CopyDurationPtr(p.Period),
|
||||||
|
IPFilepath: helpers.CopyStringPtr(p.IPFilepath),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PublicIP) mergeWith(other PublicIP) {
|
||||||
|
p.Period = helpers.MergeWithDuration(p.Period, other.Period)
|
||||||
|
p.IPFilepath = helpers.MergeWithStringPtr(p.IPFilepath, other.IPFilepath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PublicIP) overrideWith(other PublicIP) {
|
||||||
|
p.Period = helpers.OverrideWithDuration(p.Period, other.Period)
|
||||||
|
p.IPFilepath = helpers.OverrideWithStringPtr(p.IPFilepath, other.IPFilepath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PublicIP) setDefaults() {
|
||||||
|
const defaultPeriod = 12 * time.Hour
|
||||||
|
p.Period = helpers.DefaultDuration(p.Period, defaultPeriod)
|
||||||
|
p.IPFilepath = helpers.DefaultStringPtr(p.IPFilepath, "/tmp/gluetun/ip")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p PublicIP) String() string {
|
||||||
|
return p.toLinesNode().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p PublicIP) toLinesNode() (node *gotree.Node) {
|
||||||
|
node = gotree.New("Public IP settings:")
|
||||||
|
|
||||||
|
if *p.Period == 0 {
|
||||||
|
node.Appendf("Enabled: no")
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePeriod := "disabled"
|
||||||
|
if *p.Period > 0 {
|
||||||
|
updatePeriod = "every " + p.Period.String()
|
||||||
|
}
|
||||||
|
node.Appendf("Fetching: %s", updatePeriod)
|
||||||
|
|
||||||
|
if *p.IPFilepath != "" {
|
||||||
|
node.Appendf("IP file path: %s", *p.IPFilepath)
|
||||||
|
}
|
||||||
|
|
||||||
|
return node
|
||||||
|
}
|
||||||
80
internal/configuration/settings/server.go
Normal file
80
internal/configuration/settings/server.go
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
|
"github.com/qdm12/gotree"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ControlServer contains settings to customize the control server operation.
|
||||||
|
type ControlServer struct {
|
||||||
|
// Address is the listening address to use.
|
||||||
|
// It cannot be nil in the internal state.
|
||||||
|
Address *string
|
||||||
|
// Log can be true or false to enable logging on requests.
|
||||||
|
// It cannot be nil in the internal state.
|
||||||
|
Log *bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c ControlServer) validate() (err error) {
|
||||||
|
_, portStr, err := net.SplitHostPort(*c.Address)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("listening address is not valid: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
port, err := strconv.Atoi(portStr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("listening port it not valid: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
uid := os.Getuid()
|
||||||
|
const maxPrivilegedPort = 1023
|
||||||
|
if uid != 0 && port != 0 && port <= maxPrivilegedPort {
|
||||||
|
return fmt.Errorf("%w: %d when running with user ID %d",
|
||||||
|
ErrControlServerPrivilegedPort, port, uid)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ControlServer) copy() (copied ControlServer) {
|
||||||
|
return ControlServer{
|
||||||
|
Address: helpers.CopyStringPtr(c.Address),
|
||||||
|
Log: helpers.CopyBoolPtr(c.Log),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// mergeWith merges the other settings into any
|
||||||
|
// unset field of the receiver settings object.
|
||||||
|
func (c *ControlServer) mergeWith(other ControlServer) {
|
||||||
|
c.Address = helpers.MergeWithStringPtr(c.Address, other.Address)
|
||||||
|
c.Log = helpers.MergeWithBool(c.Log, other.Log)
|
||||||
|
}
|
||||||
|
|
||||||
|
// overrideWith overrides fields of the receiver
|
||||||
|
// settings object with any field set in the other
|
||||||
|
// settings.
|
||||||
|
func (c *ControlServer) overrideWith(other ControlServer) {
|
||||||
|
c.Address = helpers.OverrideWithStringPtr(c.Address, other.Address)
|
||||||
|
c.Log = helpers.OverrideWithBool(c.Log, other.Log)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ControlServer) setDefaults() {
|
||||||
|
c.Address = helpers.DefaultStringPtr(c.Address, ":8000")
|
||||||
|
c.Log = helpers.DefaultBool(c.Log, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c ControlServer) String() string {
|
||||||
|
return c.toLinesNode().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c ControlServer) toLinesNode() (node *gotree.Node) {
|
||||||
|
node = gotree.New("Control server settings:")
|
||||||
|
node.Appendf("Listening address: %s", *c.Address)
|
||||||
|
node.Appendf("Logging: %s", helpers.BoolPtrToYesNo(c.Log))
|
||||||
|
return node
|
||||||
|
}
|
||||||
432
internal/configuration/settings/serverselection.go
Normal file
432
internal/configuration/settings/serverselection.go
Normal file
@@ -0,0 +1,432 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/settings/validation"
|
||||||
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gotree"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ServerSelection struct { //nolint:maligned
|
||||||
|
// VPN is the VPN type which can be 'openvpn'
|
||||||
|
// or 'wireguard'. It cannot be the empty string
|
||||||
|
// in the internal state.
|
||||||
|
VPN string
|
||||||
|
// TargetIP is the server endpoint IP address to use.
|
||||||
|
// It will override any IP address from the picked
|
||||||
|
// built-in server. It cannot be nil in the internal
|
||||||
|
// state, and can be set to an empty net.IP{} to indicate
|
||||||
|
// there is not target IP address to use.
|
||||||
|
TargetIP net.IP
|
||||||
|
// Counties is the list of countries to filter VPN servers with.
|
||||||
|
Countries []string
|
||||||
|
// Regions is the list of regions to filter VPN servers with.
|
||||||
|
Regions []string
|
||||||
|
// Cities is the list of cities to filter VPN servers with.
|
||||||
|
Cities []string
|
||||||
|
// ISPs is the list of ISP names to filter VPN servers with.
|
||||||
|
ISPs []string
|
||||||
|
// Names is the list of server names to filter VPN servers with.
|
||||||
|
Names []string
|
||||||
|
// Numbers is the list of server numbers to filter VPN servers with.
|
||||||
|
Numbers []uint16
|
||||||
|
// Hostnames is the list of hostnames to filter VPN servers with.
|
||||||
|
Hostnames []string
|
||||||
|
// OwnedOnly is true if VPN provider servers that are not owned
|
||||||
|
// should be filtered. This is used with Mullvad.
|
||||||
|
OwnedOnly *bool
|
||||||
|
// FreeOnly is true if VPN servers that are not free should
|
||||||
|
// be filtered. This is used with ProtonVPN and VPN Unlimited.
|
||||||
|
FreeOnly *bool
|
||||||
|
// StreamOnly is true if VPN servers not for streaming should
|
||||||
|
// be filtered. This is used with VPNUnlimited.
|
||||||
|
StreamOnly *bool
|
||||||
|
// MultiHopOnly is true if VPN servers that are not multihop
|
||||||
|
// should be filtered. This is used with Surfshark.
|
||||||
|
MultiHopOnly *bool
|
||||||
|
|
||||||
|
// OpenVPN contains settings to select OpenVPN servers
|
||||||
|
// and the final connection.
|
||||||
|
OpenVPN OpenVPNSelection
|
||||||
|
// Wireguard contains settings to select Wireguard servers
|
||||||
|
// and the final connection.
|
||||||
|
Wireguard WireguardSelection
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrOwnedOnlyNotSupported = errors.New("owned only filter is not supported")
|
||||||
|
ErrFreeOnlyNotSupported = errors.New("free only filter is not supported")
|
||||||
|
ErrStreamOnlyNotSupported = errors.New("stream only filter is not supported")
|
||||||
|
ErrMultiHopOnlyNotSupported = errors.New("multi hop only filter is not supported")
|
||||||
|
)
|
||||||
|
|
||||||
|
func (ss *ServerSelection) validate(vpnServiceProvider string,
|
||||||
|
allServers models.AllServers) (err error) {
|
||||||
|
switch ss.VPN {
|
||||||
|
case constants.OpenVPN, constants.Wireguard:
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("%w: %s", ErrVPNTypeNotValid, ss.VPN)
|
||||||
|
}
|
||||||
|
|
||||||
|
countryChoices, regionChoices, cityChoices,
|
||||||
|
ispChoices, nameChoices, hostnameChoices, err := getLocationFilterChoices(vpnServiceProvider, ss, allServers)
|
||||||
|
if err != nil {
|
||||||
|
return err // already wrapped error
|
||||||
|
}
|
||||||
|
|
||||||
|
err = validateServerFilters(*ss, countryChoices, regionChoices, cityChoices,
|
||||||
|
ispChoices, nameChoices, hostnameChoices)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, helpers.ErrNoChoice) {
|
||||||
|
return fmt.Errorf("for VPN service provider %s: %w", vpnServiceProvider, err)
|
||||||
|
}
|
||||||
|
return err // already wrapped error
|
||||||
|
}
|
||||||
|
|
||||||
|
if *ss.OwnedOnly &&
|
||||||
|
vpnServiceProvider != constants.Mullvad {
|
||||||
|
return fmt.Errorf("%w: for VPN service provider %s",
|
||||||
|
ErrOwnedOnlyNotSupported, vpnServiceProvider)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *ss.FreeOnly &&
|
||||||
|
!helpers.IsOneOf(vpnServiceProvider,
|
||||||
|
constants.Protonvpn,
|
||||||
|
constants.VPNUnlimited,
|
||||||
|
) {
|
||||||
|
return fmt.Errorf("%w: for VPN service provider %s",
|
||||||
|
ErrFreeOnlyNotSupported, vpnServiceProvider)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *ss.StreamOnly &&
|
||||||
|
!helpers.IsOneOf(vpnServiceProvider,
|
||||||
|
constants.Protonvpn,
|
||||||
|
constants.VPNUnlimited,
|
||||||
|
) {
|
||||||
|
return fmt.Errorf("%w: for VPN service provider %s",
|
||||||
|
ErrStreamOnlyNotSupported, vpnServiceProvider)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *ss.MultiHopOnly &&
|
||||||
|
vpnServiceProvider != constants.Surfshark {
|
||||||
|
return fmt.Errorf("%w: for VPN service provider %s",
|
||||||
|
ErrMultiHopOnlyNotSupported, vpnServiceProvider)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ss.VPN == constants.OpenVPN {
|
||||||
|
err = ss.OpenVPN.validate(vpnServiceProvider)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("OpenVPN server selection settings: %w", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = ss.Wireguard.validate(vpnServiceProvider)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Wireguard server selection settings: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getLocationFilterChoices(vpnServiceProvider string, ss *ServerSelection,
|
||||||
|
allServers models.AllServers) (
|
||||||
|
countryChoices, regionChoices, cityChoices,
|
||||||
|
ispChoices, nameChoices, hostnameChoices []string,
|
||||||
|
err error) {
|
||||||
|
switch vpnServiceProvider {
|
||||||
|
case constants.Custom:
|
||||||
|
case constants.Cyberghost:
|
||||||
|
servers := allServers.GetCyberghost()
|
||||||
|
countryChoices = validation.CyberghostCountryChoices(servers)
|
||||||
|
hostnameChoices = validation.CyberghostHostnameChoices(servers)
|
||||||
|
case constants.Expressvpn:
|
||||||
|
servers := allServers.GetExpressvpn()
|
||||||
|
countryChoices = validation.ExpressvpnCountriesChoices(servers)
|
||||||
|
cityChoices = validation.ExpressvpnCityChoices(servers)
|
||||||
|
hostnameChoices = validation.ExpressvpnHostnameChoices(servers)
|
||||||
|
case constants.Fastestvpn:
|
||||||
|
servers := allServers.GetFastestvpn()
|
||||||
|
countryChoices = validation.FastestvpnCountriesChoices(servers)
|
||||||
|
hostnameChoices = validation.FastestvpnHostnameChoices(servers)
|
||||||
|
case constants.HideMyAss:
|
||||||
|
servers := allServers.GetHideMyAss()
|
||||||
|
countryChoices = validation.HideMyAssCountryChoices(servers)
|
||||||
|
regionChoices = validation.HideMyAssRegionChoices(servers)
|
||||||
|
cityChoices = validation.HideMyAssCityChoices(servers)
|
||||||
|
hostnameChoices = validation.HideMyAssHostnameChoices(servers)
|
||||||
|
case constants.Ipvanish:
|
||||||
|
servers := allServers.GetIpvanish()
|
||||||
|
countryChoices = validation.IpvanishCountryChoices(servers)
|
||||||
|
cityChoices = validation.IpvanishCityChoices(servers)
|
||||||
|
hostnameChoices = validation.IpvanishHostnameChoices(servers)
|
||||||
|
case constants.Ivpn:
|
||||||
|
servers := allServers.GetIvpn()
|
||||||
|
countryChoices = validation.IvpnCountryChoices(servers)
|
||||||
|
cityChoices = validation.IvpnCityChoices(servers)
|
||||||
|
ispChoices = validation.IvpnISPChoices(servers)
|
||||||
|
hostnameChoices = validation.IvpnHostnameChoices(servers)
|
||||||
|
case constants.Mullvad:
|
||||||
|
servers := allServers.GetMullvad()
|
||||||
|
countryChoices = validation.MullvadCountryChoices(servers)
|
||||||
|
cityChoices = validation.MullvadCityChoices(servers)
|
||||||
|
ispChoices = validation.MullvadISPChoices(servers)
|
||||||
|
hostnameChoices = validation.MullvadHostnameChoices(servers)
|
||||||
|
case constants.Nordvpn:
|
||||||
|
servers := allServers.GetNordvpn()
|
||||||
|
regionChoices = validation.NordvpnRegionChoices(servers)
|
||||||
|
hostnameChoices = validation.NordvpnHostnameChoices(servers)
|
||||||
|
case constants.Perfectprivacy:
|
||||||
|
servers := allServers.GetPerfectprivacy()
|
||||||
|
cityChoices = validation.PerfectprivacyCityChoices(servers)
|
||||||
|
case constants.Privado:
|
||||||
|
servers := allServers.GetPrivado()
|
||||||
|
countryChoices = validation.PrivadoCountryChoices(servers)
|
||||||
|
regionChoices = validation.PrivadoRegionChoices(servers)
|
||||||
|
cityChoices = validation.PrivadoCityChoices(servers)
|
||||||
|
hostnameChoices = validation.PrivadoHostnameChoices(servers)
|
||||||
|
case constants.PrivateInternetAccess:
|
||||||
|
servers := allServers.GetPia()
|
||||||
|
regionChoices = validation.PIAGeoChoices(servers)
|
||||||
|
hostnameChoices = validation.PIAHostnameChoices(servers)
|
||||||
|
nameChoices = validation.PIANameChoices(servers)
|
||||||
|
case constants.Privatevpn:
|
||||||
|
servers := allServers.GetPrivatevpn()
|
||||||
|
countryChoices = validation.PrivatevpnCountryChoices(servers)
|
||||||
|
cityChoices = validation.PrivatevpnCityChoices(servers)
|
||||||
|
hostnameChoices = validation.PrivatevpnHostnameChoices(servers)
|
||||||
|
case constants.Protonvpn:
|
||||||
|
servers := allServers.GetProtonvpn()
|
||||||
|
countryChoices = validation.ProtonvpnCountryChoices(servers)
|
||||||
|
regionChoices = validation.ProtonvpnRegionChoices(servers)
|
||||||
|
cityChoices = validation.ProtonvpnCityChoices(servers)
|
||||||
|
nameChoices = validation.ProtonvpnNameChoices(servers)
|
||||||
|
hostnameChoices = validation.ProtonvpnHostnameChoices(servers)
|
||||||
|
case constants.Purevpn:
|
||||||
|
servers := allServers.GetPurevpn()
|
||||||
|
countryChoices = validation.PurevpnCountryChoices(servers)
|
||||||
|
regionChoices = validation.PurevpnRegionChoices(servers)
|
||||||
|
cityChoices = validation.PurevpnCityChoices(servers)
|
||||||
|
hostnameChoices = validation.PurevpnHostnameChoices(servers)
|
||||||
|
case constants.Surfshark:
|
||||||
|
servers := allServers.GetSurfshark()
|
||||||
|
countryChoices = validation.SurfsharkCountryChoices(servers)
|
||||||
|
cityChoices = validation.SurfsharkCityChoices(servers)
|
||||||
|
hostnameChoices = validation.SurfsharkHostnameChoices(servers)
|
||||||
|
regionChoices = validation.SurfsharkRegionChoices(servers)
|
||||||
|
// TODO v4 remove
|
||||||
|
regionChoices = append(regionChoices, validation.SurfsharkRetroLocChoices()...)
|
||||||
|
if err := helpers.AreAllOneOf(ss.Regions, regionChoices); err != nil {
|
||||||
|
return nil, nil, nil, nil, nil, nil, fmt.Errorf("%w: %s", ErrRegionNotValid, err)
|
||||||
|
}
|
||||||
|
// Retro compatibility
|
||||||
|
// TODO remove in v4
|
||||||
|
*ss = surfsharkRetroRegion(*ss)
|
||||||
|
case constants.Torguard:
|
||||||
|
servers := allServers.GetTorguard()
|
||||||
|
countryChoices = validation.TorguardCountryChoices(servers)
|
||||||
|
cityChoices = validation.TorguardCityChoices(servers)
|
||||||
|
hostnameChoices = validation.TorguardHostnameChoices(servers)
|
||||||
|
case constants.VPNUnlimited:
|
||||||
|
servers := allServers.GetVPNUnlimited()
|
||||||
|
countryChoices = validation.VPNUnlimitedCountryChoices(servers)
|
||||||
|
cityChoices = validation.VPNUnlimitedCityChoices(servers)
|
||||||
|
hostnameChoices = validation.VPNUnlimitedHostnameChoices(servers)
|
||||||
|
case constants.Vyprvpn:
|
||||||
|
servers := allServers.GetVyprvpn()
|
||||||
|
regionChoices = validation.VyprvpnRegionChoices(servers)
|
||||||
|
case constants.Wevpn:
|
||||||
|
servers := allServers.GetWevpn()
|
||||||
|
cityChoices = validation.WevpnCityChoices(servers)
|
||||||
|
hostnameChoices = validation.WevpnHostnameChoices(servers)
|
||||||
|
case constants.Windscribe:
|
||||||
|
servers := allServers.GetWindscribe()
|
||||||
|
regionChoices = validation.WindscribeRegionChoices(servers)
|
||||||
|
cityChoices = validation.WindscribeCityChoices(servers)
|
||||||
|
hostnameChoices = validation.WindscribeHostnameChoices(servers)
|
||||||
|
default:
|
||||||
|
return nil, nil, nil, nil, nil, nil, fmt.Errorf("%w: %s", ErrVPNProviderNameNotValid, vpnServiceProvider)
|
||||||
|
}
|
||||||
|
|
||||||
|
return countryChoices, regionChoices, cityChoices,
|
||||||
|
ispChoices, nameChoices, hostnameChoices, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateServerFilters validates filters against the choices given as arguments.
|
||||||
|
// Set an argument to nil to pass the check for a particular filter.
|
||||||
|
func validateServerFilters(settings ServerSelection,
|
||||||
|
countryChoices, regionChoices, cityChoices, ispChoices,
|
||||||
|
nameChoices, hostnameChoices []string) (err error) {
|
||||||
|
if err := helpers.AreAllOneOf(settings.Countries, countryChoices); err != nil {
|
||||||
|
return fmt.Errorf("%w: %s", ErrCountryNotValid, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := helpers.AreAllOneOf(settings.Regions, regionChoices); err != nil {
|
||||||
|
return fmt.Errorf("%w: %s", ErrRegionNotValid, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := helpers.AreAllOneOf(settings.Cities, cityChoices); err != nil {
|
||||||
|
return fmt.Errorf("%w: %s", ErrCityNotValid, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := helpers.AreAllOneOf(settings.ISPs, ispChoices); err != nil {
|
||||||
|
return fmt.Errorf("%w: %s", ErrISPNotValid, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := helpers.AreAllOneOf(settings.Hostnames, hostnameChoices); err != nil {
|
||||||
|
return fmt.Errorf("%w: %s", ErrHostnameNotValid, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := helpers.AreAllOneOf(settings.Names, nameChoices); err != nil {
|
||||||
|
return fmt.Errorf("%w: %s", ErrNameNotValid, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ss *ServerSelection) copy() (copied ServerSelection) {
|
||||||
|
return ServerSelection{
|
||||||
|
VPN: ss.VPN,
|
||||||
|
TargetIP: helpers.CopyIP(ss.TargetIP),
|
||||||
|
Countries: helpers.CopyStringSlice(ss.Countries),
|
||||||
|
Regions: helpers.CopyStringSlice(ss.Regions),
|
||||||
|
Cities: helpers.CopyStringSlice(ss.Cities),
|
||||||
|
ISPs: helpers.CopyStringSlice(ss.ISPs),
|
||||||
|
Hostnames: helpers.CopyStringSlice(ss.Hostnames),
|
||||||
|
Names: helpers.CopyStringSlice(ss.Names),
|
||||||
|
Numbers: helpers.CopyUint16Slice(ss.Numbers),
|
||||||
|
OwnedOnly: helpers.CopyBoolPtr(ss.OwnedOnly),
|
||||||
|
FreeOnly: helpers.CopyBoolPtr(ss.FreeOnly),
|
||||||
|
StreamOnly: helpers.CopyBoolPtr(ss.StreamOnly),
|
||||||
|
MultiHopOnly: helpers.CopyBoolPtr(ss.MultiHopOnly),
|
||||||
|
OpenVPN: ss.OpenVPN.copy(),
|
||||||
|
Wireguard: ss.Wireguard.copy(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ss *ServerSelection) mergeWith(other ServerSelection) {
|
||||||
|
ss.VPN = helpers.MergeWithString(ss.VPN, other.VPN)
|
||||||
|
ss.TargetIP = helpers.MergeWithIP(ss.TargetIP, other.TargetIP)
|
||||||
|
ss.Countries = helpers.MergeStringSlices(ss.Countries, other.Countries)
|
||||||
|
ss.Regions = helpers.MergeStringSlices(ss.Regions, other.Regions)
|
||||||
|
ss.Cities = helpers.MergeStringSlices(ss.Cities, other.Cities)
|
||||||
|
ss.ISPs = helpers.MergeStringSlices(ss.ISPs, other.ISPs)
|
||||||
|
ss.Hostnames = helpers.MergeStringSlices(ss.Hostnames, other.Hostnames)
|
||||||
|
ss.Names = helpers.MergeStringSlices(ss.Names, other.Names)
|
||||||
|
ss.Numbers = helpers.MergeUint16Slices(ss.Numbers, other.Numbers)
|
||||||
|
ss.OwnedOnly = helpers.MergeWithBool(ss.OwnedOnly, other.OwnedOnly)
|
||||||
|
ss.FreeOnly = helpers.MergeWithBool(ss.FreeOnly, other.FreeOnly)
|
||||||
|
ss.StreamOnly = helpers.MergeWithBool(ss.StreamOnly, other.StreamOnly)
|
||||||
|
ss.MultiHopOnly = helpers.MergeWithBool(ss.MultiHopOnly, other.MultiHopOnly)
|
||||||
|
|
||||||
|
ss.OpenVPN.mergeWith(other.OpenVPN)
|
||||||
|
ss.Wireguard.mergeWith(other.Wireguard)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ss *ServerSelection) overrideWith(other ServerSelection) {
|
||||||
|
ss.VPN = helpers.OverrideWithString(ss.VPN, other.VPN)
|
||||||
|
ss.TargetIP = helpers.OverrideWithIP(ss.TargetIP, other.TargetIP)
|
||||||
|
ss.Countries = helpers.OverrideWithStringSlice(ss.Countries, other.Countries)
|
||||||
|
ss.Regions = helpers.OverrideWithStringSlice(ss.Regions, other.Regions)
|
||||||
|
ss.Cities = helpers.OverrideWithStringSlice(ss.Cities, other.Cities)
|
||||||
|
ss.ISPs = helpers.OverrideWithStringSlice(ss.ISPs, other.ISPs)
|
||||||
|
ss.Hostnames = helpers.OverrideWithStringSlice(ss.Hostnames, other.Hostnames)
|
||||||
|
ss.Names = helpers.OverrideWithStringSlice(ss.Names, other.Names)
|
||||||
|
ss.Numbers = helpers.OverrideWithUint16Slice(ss.Numbers, other.Numbers)
|
||||||
|
ss.OwnedOnly = helpers.OverrideWithBool(ss.OwnedOnly, other.OwnedOnly)
|
||||||
|
ss.FreeOnly = helpers.OverrideWithBool(ss.FreeOnly, other.FreeOnly)
|
||||||
|
ss.StreamOnly = helpers.OverrideWithBool(ss.StreamOnly, other.StreamOnly)
|
||||||
|
ss.MultiHopOnly = helpers.OverrideWithBool(ss.MultiHopOnly, other.MultiHopOnly)
|
||||||
|
ss.OpenVPN.overrideWith(other.OpenVPN)
|
||||||
|
ss.Wireguard.overrideWith(other.Wireguard)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ss *ServerSelection) setDefaults(vpnProvider string) {
|
||||||
|
ss.VPN = helpers.DefaultString(ss.VPN, constants.OpenVPN)
|
||||||
|
ss.TargetIP = helpers.DefaultIP(ss.TargetIP, net.IP{})
|
||||||
|
ss.OwnedOnly = helpers.DefaultBool(ss.OwnedOnly, false)
|
||||||
|
ss.FreeOnly = helpers.DefaultBool(ss.FreeOnly, false)
|
||||||
|
ss.StreamOnly = helpers.DefaultBool(ss.StreamOnly, false)
|
||||||
|
ss.MultiHopOnly = helpers.DefaultBool(ss.MultiHopOnly, false)
|
||||||
|
ss.OpenVPN.setDefaults(vpnProvider)
|
||||||
|
ss.Wireguard.setDefaults()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ss ServerSelection) String() string {
|
||||||
|
return ss.toLinesNode().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ss ServerSelection) toLinesNode() (node *gotree.Node) {
|
||||||
|
node = gotree.New("Server selection settings:")
|
||||||
|
node.Appendf("VPN type: %s", ss.VPN)
|
||||||
|
if len(ss.TargetIP) > 0 {
|
||||||
|
node.Appendf("Target IP address: %s", ss.TargetIP)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ss.Countries) > 0 {
|
||||||
|
node.Appendf("Countries: %s", strings.Join(ss.Countries, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ss.Regions) > 0 {
|
||||||
|
node.Appendf("Regions: %s", strings.Join(ss.Regions, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ss.Cities) > 0 {
|
||||||
|
node.Appendf("Cities: %s", strings.Join(ss.Cities, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ss.ISPs) > 0 {
|
||||||
|
node.Appendf("ISPs: %s", strings.Join(ss.ISPs, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ss.Names) > 0 {
|
||||||
|
node.Appendf("Server names: %s", strings.Join(ss.Names, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ss.Numbers) > 0 {
|
||||||
|
numbersNode := node.Appendf("Server numbers:")
|
||||||
|
for _, number := range ss.Numbers {
|
||||||
|
numbersNode.Appendf("%d", number)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ss.Hostnames) > 0 {
|
||||||
|
node.Appendf("Hostnames: %s", strings.Join(ss.Hostnames, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
if *ss.OwnedOnly {
|
||||||
|
node.Appendf("Owned only servers: yes")
|
||||||
|
}
|
||||||
|
|
||||||
|
if *ss.FreeOnly {
|
||||||
|
node.Appendf("Free only servers: yes")
|
||||||
|
}
|
||||||
|
|
||||||
|
if *ss.StreamOnly {
|
||||||
|
node.Appendf("Stream only servers: yes")
|
||||||
|
}
|
||||||
|
|
||||||
|
if *ss.MultiHopOnly {
|
||||||
|
node.Appendf("Multi-hop only servers: yes")
|
||||||
|
}
|
||||||
|
|
||||||
|
if ss.VPN == constants.OpenVPN {
|
||||||
|
node.AppendNode(ss.OpenVPN.toLinesNode())
|
||||||
|
} else {
|
||||||
|
node.AppendNode(ss.Wireguard.toLinesNode())
|
||||||
|
}
|
||||||
|
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDefaults is a shorthand using setDefaults.
|
||||||
|
// It's used in unit tests in other packages.
|
||||||
|
func (ss ServerSelection) WithDefaults(provider string) ServerSelection {
|
||||||
|
ss.setDefaults(provider)
|
||||||
|
return ss
|
||||||
|
}
|
||||||
155
internal/configuration/settings/settings.go
Normal file
155
internal/configuration/settings/settings.go
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/pprof"
|
||||||
|
"github.com/qdm12/gotree"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Settings struct {
|
||||||
|
ControlServer ControlServer
|
||||||
|
DNS DNS
|
||||||
|
Firewall Firewall
|
||||||
|
Health Health
|
||||||
|
HTTPProxy HTTPProxy
|
||||||
|
Log Log
|
||||||
|
PublicIP PublicIP
|
||||||
|
Shadowsocks Shadowsocks
|
||||||
|
System System
|
||||||
|
Updater Updater
|
||||||
|
Version Version
|
||||||
|
VPN VPN
|
||||||
|
Pprof pprof.Settings
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate validates all the settings and returns an error
|
||||||
|
// if one of them is not valid.
|
||||||
|
// TODO v4 remove pointer for receiver (because of Surfshark).
|
||||||
|
func (s *Settings) Validate(allServers models.AllServers) (err error) {
|
||||||
|
nameToValidation := map[string]func() error{
|
||||||
|
"control server": s.ControlServer.validate,
|
||||||
|
"dns": s.DNS.validate,
|
||||||
|
"firewall": s.Firewall.validate,
|
||||||
|
"health": s.Health.Validate,
|
||||||
|
"http proxy": s.HTTPProxy.validate,
|
||||||
|
"log": s.Log.validate,
|
||||||
|
"public ip check": s.PublicIP.validate,
|
||||||
|
"shadowsocks": s.Shadowsocks.validate,
|
||||||
|
"system": s.System.validate,
|
||||||
|
"updater": s.Updater.Validate,
|
||||||
|
"version": s.Version.validate,
|
||||||
|
// Pprof validation done in pprof constructor
|
||||||
|
"VPN": func() error {
|
||||||
|
return s.VPN.validate(allServers)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, validation := range nameToValidation {
|
||||||
|
err = validation()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%s settings: %w", name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Settings) copy() (copied Settings) {
|
||||||
|
return Settings{
|
||||||
|
ControlServer: s.ControlServer.copy(),
|
||||||
|
DNS: s.DNS.Copy(),
|
||||||
|
Firewall: s.Firewall.copy(),
|
||||||
|
Health: s.Health.copy(),
|
||||||
|
HTTPProxy: s.HTTPProxy.copy(),
|
||||||
|
Log: s.Log.copy(),
|
||||||
|
PublicIP: s.PublicIP.copy(),
|
||||||
|
Shadowsocks: s.Shadowsocks.copy(),
|
||||||
|
System: s.System.copy(),
|
||||||
|
Updater: s.Updater.copy(),
|
||||||
|
Version: s.Version.copy(),
|
||||||
|
VPN: s.VPN.copy(),
|
||||||
|
Pprof: s.Pprof.Copy(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Settings) MergeWith(other Settings) {
|
||||||
|
s.ControlServer.mergeWith(other.ControlServer)
|
||||||
|
s.DNS.mergeWith(other.DNS)
|
||||||
|
s.Firewall.mergeWith(other.Firewall)
|
||||||
|
s.Health.MergeWith(other.Health)
|
||||||
|
s.HTTPProxy.mergeWith(other.HTTPProxy)
|
||||||
|
s.Log.mergeWith(other.Log)
|
||||||
|
s.PublicIP.mergeWith(other.PublicIP)
|
||||||
|
s.Shadowsocks.mergeWith(other.Shadowsocks)
|
||||||
|
s.System.mergeWith(other.System)
|
||||||
|
s.Updater.mergeWith(other.Updater)
|
||||||
|
s.Version.mergeWith(other.Version)
|
||||||
|
s.VPN.mergeWith(other.VPN)
|
||||||
|
s.Pprof.MergeWith(other.Pprof)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Settings) OverrideWith(other Settings,
|
||||||
|
allServers models.AllServers) (err error) {
|
||||||
|
patchedSettings := s.copy()
|
||||||
|
patchedSettings.ControlServer.overrideWith(other.ControlServer)
|
||||||
|
patchedSettings.DNS.overrideWith(other.DNS)
|
||||||
|
patchedSettings.Firewall.overrideWith(other.Firewall)
|
||||||
|
patchedSettings.Health.OverrideWith(other.Health)
|
||||||
|
patchedSettings.HTTPProxy.overrideWith(other.HTTPProxy)
|
||||||
|
patchedSettings.Log.overrideWith(other.Log)
|
||||||
|
patchedSettings.PublicIP.overrideWith(other.PublicIP)
|
||||||
|
patchedSettings.Shadowsocks.overrideWith(other.Shadowsocks)
|
||||||
|
patchedSettings.System.overrideWith(other.System)
|
||||||
|
patchedSettings.Updater.overrideWith(other.Updater)
|
||||||
|
patchedSettings.Version.overrideWith(other.Version)
|
||||||
|
patchedSettings.VPN.overrideWith(other.VPN)
|
||||||
|
patchedSettings.Pprof.MergeWith(other.Pprof)
|
||||||
|
err = patchedSettings.Validate(allServers)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*s = patchedSettings
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Settings) SetDefaults() {
|
||||||
|
s.ControlServer.setDefaults()
|
||||||
|
s.DNS.setDefaults()
|
||||||
|
s.Firewall.setDefaults()
|
||||||
|
s.Health.SetDefaults()
|
||||||
|
s.HTTPProxy.setDefaults()
|
||||||
|
s.Log.setDefaults()
|
||||||
|
s.PublicIP.setDefaults()
|
||||||
|
s.Shadowsocks.setDefaults()
|
||||||
|
s.System.setDefaults()
|
||||||
|
s.Version.setDefaults()
|
||||||
|
s.VPN.setDefaults()
|
||||||
|
s.Updater.SetDefaults(*s.VPN.Provider.Name)
|
||||||
|
s.Pprof.SetDefaults()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Settings) String() string {
|
||||||
|
return s.toLinesNode().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Settings) toLinesNode() (node *gotree.Node) {
|
||||||
|
node = gotree.New("Settings summary:")
|
||||||
|
|
||||||
|
node.AppendNode(s.VPN.toLinesNode())
|
||||||
|
node.AppendNode(s.DNS.toLinesNode())
|
||||||
|
node.AppendNode(s.Firewall.toLinesNode())
|
||||||
|
node.AppendNode(s.Log.toLinesNode())
|
||||||
|
node.AppendNode(s.Health.toLinesNode())
|
||||||
|
node.AppendNode(s.Shadowsocks.toLinesNode())
|
||||||
|
node.AppendNode(s.HTTPProxy.toLinesNode())
|
||||||
|
node.AppendNode(s.ControlServer.toLinesNode())
|
||||||
|
node.AppendNode(s.System.toLinesNode())
|
||||||
|
node.AppendNode(s.PublicIP.toLinesNode())
|
||||||
|
node.AppendNode(s.Updater.toLinesNode())
|
||||||
|
node.AppendNode(s.Version.toLinesNode())
|
||||||
|
node.AppendNode(s.Pprof.ToLinesNode())
|
||||||
|
|
||||||
|
return node
|
||||||
|
}
|
||||||
101
internal/configuration/settings/settings_test.go
Normal file
101
internal/configuration/settings/settings_test.go
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_Settings_String(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
withDefaults := func(s Settings) Settings {
|
||||||
|
s.SetDefaults()
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
settings Settings
|
||||||
|
s string
|
||||||
|
}{
|
||||||
|
"default settings": {
|
||||||
|
settings: withDefaults(Settings{}),
|
||||||
|
s: `Settings summary:
|
||||||
|
├── VPN settings:
|
||||||
|
| ├── VPN provider settings:
|
||||||
|
| | ├── Name: private internet access
|
||||||
|
| | └── Server selection settings:
|
||||||
|
| | ├── VPN type: openvpn
|
||||||
|
| | └── OpenVPN server selection settings:
|
||||||
|
| | ├── Protocol: UDP
|
||||||
|
| | └── Private Internet Access encryption preset: strong
|
||||||
|
| └── OpenVPN settings:
|
||||||
|
| ├── OpenVPN version: 2.5
|
||||||
|
| ├── User: [not set]
|
||||||
|
| ├── Password: [not set]
|
||||||
|
| ├── Private Internet Access encryption preset: strong
|
||||||
|
| ├── Tunnel IPv6: no
|
||||||
|
| ├── Network interface: tun0
|
||||||
|
| ├── Run OpenVPN as: root
|
||||||
|
| └── Verbosity level: 1
|
||||||
|
├── DNS settings:
|
||||||
|
| ├── DNS server address to use: 127.0.0.1
|
||||||
|
| ├── Keep existing nameserver(s): no
|
||||||
|
| └── DNS over TLS settings:
|
||||||
|
| ├── Enabled: yes
|
||||||
|
| ├── Update period: every 24h0m0s
|
||||||
|
| ├── Unbound settings:
|
||||||
|
| | ├── Authoritative servers:
|
||||||
|
| | | └── Cloudflare
|
||||||
|
| | ├── Caching: yes
|
||||||
|
| | ├── IPv6: no
|
||||||
|
| | ├── Verbosity level: 1
|
||||||
|
| | ├── Verbosity details level: 0
|
||||||
|
| | ├── Validation log level: 0
|
||||||
|
| | ├── System user: root
|
||||||
|
| | └── Allowed networks:
|
||||||
|
| | ├── 0.0.0.0/0
|
||||||
|
| | └── ::/0
|
||||||
|
| └── DNS filtering settings:
|
||||||
|
| ├── Block malicious: yes
|
||||||
|
| ├── Block ads: no
|
||||||
|
| └── Block surveillance: yes
|
||||||
|
├── Firewall settings:
|
||||||
|
| └── Enabled: yes
|
||||||
|
├── Log settings:
|
||||||
|
| └── Log level: INFO
|
||||||
|
├── Health settings:
|
||||||
|
| ├── Server listening address: 127.0.0.1:9999
|
||||||
|
| ├── Address to ping: github.com
|
||||||
|
| └── VPN wait durations:
|
||||||
|
| ├── Initial duration: 6s
|
||||||
|
| └── Additional duration: 5s
|
||||||
|
├── Shadowsocks server settings:
|
||||||
|
| └── Enabled: no
|
||||||
|
├── HTTP proxy settings:
|
||||||
|
| └── Enabled: no
|
||||||
|
├── Control server settings:
|
||||||
|
| ├── Listening address: :8000
|
||||||
|
| └── Logging: yes
|
||||||
|
├── OS Alpine settings:
|
||||||
|
| ├── Process UID: 1000
|
||||||
|
| └── Process GID: 1000
|
||||||
|
├── Public IP settings:
|
||||||
|
| ├── Fetching: every 12h0m0s
|
||||||
|
| └── IP file path: /tmp/gluetun/ip
|
||||||
|
└── Version settings:
|
||||||
|
└── Enabled: yes`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, testCase := range testCases {
|
||||||
|
testCase := testCase
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
s := testCase.settings.String()
|
||||||
|
|
||||||
|
assert.Equal(t, testCase.s, s)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
68
internal/configuration/settings/shadowsocks.go
Normal file
68
internal/configuration/settings/shadowsocks.go
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
|
"github.com/qdm12/gotree"
|
||||||
|
"github.com/qdm12/ss-server/pkg/tcpudp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Shadowsocks contains settings to configure the Shadowsocks server.
|
||||||
|
type Shadowsocks struct {
|
||||||
|
// Enabled is true if the server should be running.
|
||||||
|
// It defaults to false, and cannot be nil in the internal state.
|
||||||
|
Enabled *bool
|
||||||
|
// Settings are settings for the TCP+UDP server.
|
||||||
|
tcpudp.Settings
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Shadowsocks) validate() (err error) {
|
||||||
|
return s.Settings.Validate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Shadowsocks) copy() (copied Shadowsocks) {
|
||||||
|
return Shadowsocks{
|
||||||
|
Enabled: helpers.CopyBoolPtr(s.Enabled),
|
||||||
|
Settings: s.Settings.Copy(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// mergeWith merges the other settings into any
|
||||||
|
// unset field of the receiver settings object.
|
||||||
|
func (s *Shadowsocks) mergeWith(other Shadowsocks) {
|
||||||
|
s.Enabled = helpers.MergeWithBool(s.Enabled, other.Enabled)
|
||||||
|
s.Settings.MergeWith(other.Settings)
|
||||||
|
}
|
||||||
|
|
||||||
|
// overrideWith overrides fields of the receiver
|
||||||
|
// settings object with any field set in the other
|
||||||
|
// settings.
|
||||||
|
func (s *Shadowsocks) overrideWith(other Shadowsocks) {
|
||||||
|
s.Enabled = helpers.OverrideWithBool(s.Enabled, other.Enabled)
|
||||||
|
s.Settings.OverrideWith(other.Settings)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Shadowsocks) setDefaults() {
|
||||||
|
s.Enabled = helpers.DefaultBool(s.Enabled, false)
|
||||||
|
s.Settings.SetDefaults()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Shadowsocks) String() string {
|
||||||
|
return s.toLinesNode().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Shadowsocks) toLinesNode() (node *gotree.Node) {
|
||||||
|
node = gotree.New("Shadowsocks server settings:")
|
||||||
|
|
||||||
|
node.Appendf("Enabled: %s", helpers.BoolPtrToYesNo(s.Enabled))
|
||||||
|
if !*s.Enabled {
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO have ToLinesNode in qdm12/ss-server
|
||||||
|
node.Appendf("Listening address: %s", s.Address)
|
||||||
|
node.Appendf("Cipher: %s", s.CipherName)
|
||||||
|
node.Appendf("Password: %s", helpers.ObfuscatePassword(*s.Password))
|
||||||
|
node.Appendf("Log addresses: %s", helpers.BoolPtrToYesNo(s.LogAddresses))
|
||||||
|
|
||||||
|
return node
|
||||||
|
}
|
||||||
56
internal/configuration/settings/surfshark_retro.go
Normal file
56
internal/configuration/settings/surfshark_retro.go
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func surfsharkRetroRegion(selection ServerSelection) (
|
||||||
|
updatedSelection ServerSelection) {
|
||||||
|
locationData := constants.SurfsharkLocationData()
|
||||||
|
|
||||||
|
retroToLocation := make(map[string]models.SurfsharkLocationData, len(locationData))
|
||||||
|
for _, data := range locationData {
|
||||||
|
if data.RetroLoc == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
retroToLocation[strings.ToLower(data.RetroLoc)] = data
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, region := range selection.Regions {
|
||||||
|
location, ok := retroToLocation[region]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
selection.Regions[i] = strings.ToLower(location.Region)
|
||||||
|
selection.Countries = append(selection.Countries, strings.ToLower(location.Country))
|
||||||
|
selection.Cities = append(selection.Cities, strings.ToLower(location.City)) // even empty string
|
||||||
|
selection.Hostnames = append(selection.Hostnames, location.Hostname)
|
||||||
|
}
|
||||||
|
|
||||||
|
selection.Regions = dedupSlice(selection.Regions)
|
||||||
|
selection.Countries = dedupSlice(selection.Countries)
|
||||||
|
selection.Cities = dedupSlice(selection.Cities)
|
||||||
|
selection.Hostnames = dedupSlice(selection.Hostnames)
|
||||||
|
|
||||||
|
return selection
|
||||||
|
}
|
||||||
|
|
||||||
|
func dedupSlice(slice []string) (deduped []string) {
|
||||||
|
if slice == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
deduped = make([]string, 0, len(slice))
|
||||||
|
seen := make(map[string]struct{}, len(slice))
|
||||||
|
for _, s := range slice {
|
||||||
|
if _, ok := seen[s]; !ok {
|
||||||
|
seen[s] = struct{}{}
|
||||||
|
deduped = append(deduped, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return deduped
|
||||||
|
}
|
||||||
61
internal/configuration/settings/system.go
Normal file
61
internal/configuration/settings/system.go
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
|
"github.com/qdm12/gotree"
|
||||||
|
)
|
||||||
|
|
||||||
|
// System contains settings to configure system related elements.
|
||||||
|
type System struct {
|
||||||
|
PUID *uint16
|
||||||
|
PGID *uint16
|
||||||
|
Timezone string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate validates System settings.
|
||||||
|
func (s System) validate() (err error) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *System) copy() (copied System) {
|
||||||
|
return System{
|
||||||
|
PUID: helpers.CopyUint16Ptr(s.PUID),
|
||||||
|
PGID: helpers.CopyUint16Ptr(s.PGID),
|
||||||
|
Timezone: s.Timezone,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *System) mergeWith(other System) {
|
||||||
|
s.PUID = helpers.MergeWithUint16(s.PUID, other.PUID)
|
||||||
|
s.PGID = helpers.MergeWithUint16(s.PGID, other.PGID)
|
||||||
|
s.Timezone = helpers.MergeWithString(s.Timezone, other.Timezone)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *System) overrideWith(other System) {
|
||||||
|
s.PUID = helpers.OverrideWithUint16(s.PUID, other.PUID)
|
||||||
|
s.PGID = helpers.OverrideWithUint16(s.PGID, other.PGID)
|
||||||
|
s.Timezone = helpers.OverrideWithString(s.Timezone, other.Timezone)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *System) setDefaults() {
|
||||||
|
const defaultID = 1000
|
||||||
|
s.PUID = helpers.DefaultUint16(s.PUID, defaultID)
|
||||||
|
s.PGID = helpers.DefaultUint16(s.PGID, defaultID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s System) String() string {
|
||||||
|
return s.toLinesNode().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s System) toLinesNode() (node *gotree.Node) {
|
||||||
|
node = gotree.New("OS Alpine settings:")
|
||||||
|
|
||||||
|
node.Appendf("Process UID: %d", *s.PUID)
|
||||||
|
node.Appendf("Process GID: %d", *s.PGID)
|
||||||
|
|
||||||
|
if s.Timezone != "" {
|
||||||
|
node.Appendf("Timezone: %s", s.Timezone)
|
||||||
|
}
|
||||||
|
|
||||||
|
return node
|
||||||
|
}
|
||||||
193
internal/configuration/settings/unbound.go
Normal file
193
internal/configuration/settings/unbound.go
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/qdm12/dns/pkg/provider"
|
||||||
|
"github.com/qdm12/dns/pkg/unbound"
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
|
"github.com/qdm12/gotree"
|
||||||
|
"inet.af/netaddr"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Unbound is settings for the Unbound program.
|
||||||
|
type Unbound struct {
|
||||||
|
Providers []string
|
||||||
|
Caching *bool
|
||||||
|
IPv6 *bool
|
||||||
|
VerbosityLevel *uint8
|
||||||
|
VerbosityDetailsLevel *uint8
|
||||||
|
ValidationLogLevel *uint8
|
||||||
|
Username string
|
||||||
|
Allowed []netaddr.IPPrefix
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *Unbound) setDefaults() {
|
||||||
|
if len(u.Providers) == 0 {
|
||||||
|
u.Providers = []string{
|
||||||
|
provider.Cloudflare().String(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u.Caching = helpers.DefaultBool(u.Caching, true)
|
||||||
|
u.IPv6 = helpers.DefaultBool(u.IPv6, false)
|
||||||
|
|
||||||
|
const defaultVerbosityLevel = 1
|
||||||
|
u.VerbosityLevel = helpers.DefaultUint8(u.VerbosityLevel, defaultVerbosityLevel)
|
||||||
|
|
||||||
|
const defaultVerbosityDetailsLevel = 0
|
||||||
|
u.VerbosityDetailsLevel = helpers.DefaultUint8(u.VerbosityDetailsLevel, defaultVerbosityDetailsLevel)
|
||||||
|
|
||||||
|
const defaultValidationLogLevel = 0
|
||||||
|
u.ValidationLogLevel = helpers.DefaultUint8(u.ValidationLogLevel, defaultValidationLogLevel)
|
||||||
|
|
||||||
|
if u.Allowed == nil {
|
||||||
|
u.Allowed = []netaddr.IPPrefix{
|
||||||
|
netaddr.IPPrefixFrom(netaddr.IPv4(0, 0, 0, 0), 0),
|
||||||
|
netaddr.IPPrefixFrom(netaddr.IPv6Raw([16]byte{}), 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u.Username = helpers.DefaultString(u.Username, "root")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrUnboundVerbosityLevelNotValid = errors.New("Unbound verbosity level is not valid")
|
||||||
|
ErrUnboundVerbosityDetailsLevelNotValid = errors.New("Unbound verbosity details level is not valid")
|
||||||
|
ErrUnboundValidationLogLevelNotValid = errors.New("Unbound validation log level is not valid")
|
||||||
|
)
|
||||||
|
|
||||||
|
func (u Unbound) validate() (err error) {
|
||||||
|
for _, s := range u.Providers {
|
||||||
|
_, err := provider.Parse(s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxVerbosityLevel = 5
|
||||||
|
if *u.VerbosityLevel > maxVerbosityLevel {
|
||||||
|
return fmt.Errorf("%w: %d must be between 0 and %d",
|
||||||
|
ErrUnboundVerbosityLevelNotValid,
|
||||||
|
*u.VerbosityLevel,
|
||||||
|
maxVerbosityLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxVerbosityDetailsLevel = 4
|
||||||
|
if *u.VerbosityDetailsLevel > maxVerbosityDetailsLevel {
|
||||||
|
return fmt.Errorf("%w: %d must be between 0 and %d",
|
||||||
|
ErrUnboundVerbosityDetailsLevelNotValid,
|
||||||
|
*u.VerbosityDetailsLevel,
|
||||||
|
maxVerbosityDetailsLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxValidationLogLevel = 2
|
||||||
|
if *u.ValidationLogLevel > maxValidationLogLevel {
|
||||||
|
return fmt.Errorf("%w: %d must be between 0 and %d",
|
||||||
|
ErrUnboundValidationLogLevelNotValid,
|
||||||
|
*u.ValidationLogLevel, maxValidationLogLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u Unbound) copy() (copied Unbound) {
|
||||||
|
return Unbound{
|
||||||
|
Providers: helpers.CopyStringSlice(u.Providers),
|
||||||
|
Caching: helpers.CopyBoolPtr(u.Caching),
|
||||||
|
IPv6: helpers.CopyBoolPtr(u.IPv6),
|
||||||
|
VerbosityLevel: helpers.CopyUint8Ptr(u.VerbosityLevel),
|
||||||
|
VerbosityDetailsLevel: helpers.CopyUint8Ptr(u.VerbosityDetailsLevel),
|
||||||
|
ValidationLogLevel: helpers.CopyUint8Ptr(u.ValidationLogLevel),
|
||||||
|
Username: u.Username,
|
||||||
|
Allowed: helpers.CopyIPPrefixSlice(u.Allowed),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *Unbound) mergeWith(other Unbound) {
|
||||||
|
u.Providers = helpers.MergeStringSlices(u.Providers, other.Providers)
|
||||||
|
u.Caching = helpers.MergeWithBool(u.Caching, other.Caching)
|
||||||
|
u.IPv6 = helpers.MergeWithBool(u.IPv6, other.IPv6)
|
||||||
|
u.VerbosityLevel = helpers.MergeWithUint8(u.VerbosityLevel, other.VerbosityLevel)
|
||||||
|
u.VerbosityDetailsLevel = helpers.MergeWithUint8(u.VerbosityDetailsLevel, other.VerbosityDetailsLevel)
|
||||||
|
u.ValidationLogLevel = helpers.MergeWithUint8(u.ValidationLogLevel, other.ValidationLogLevel)
|
||||||
|
u.Username = helpers.MergeWithString(u.Username, other.Username)
|
||||||
|
u.Allowed = helpers.MergeIPPrefixesSlices(u.Allowed, other.Allowed)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *Unbound) overrideWith(other Unbound) {
|
||||||
|
u.Providers = helpers.OverrideWithStringSlice(u.Providers, other.Providers)
|
||||||
|
u.Caching = helpers.OverrideWithBool(u.Caching, other.Caching)
|
||||||
|
u.IPv6 = helpers.OverrideWithBool(u.IPv6, other.IPv6)
|
||||||
|
u.VerbosityLevel = helpers.OverrideWithUint8(u.VerbosityLevel, other.VerbosityLevel)
|
||||||
|
u.VerbosityDetailsLevel = helpers.OverrideWithUint8(u.VerbosityDetailsLevel, other.VerbosityDetailsLevel)
|
||||||
|
u.ValidationLogLevel = helpers.OverrideWithUint8(u.ValidationLogLevel, other.ValidationLogLevel)
|
||||||
|
u.Username = helpers.OverrideWithString(u.Username, other.Username)
|
||||||
|
u.Allowed = helpers.OverrideWithIPPrefixesSlice(u.Allowed, other.Allowed)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u Unbound) ToUnboundFormat() (settings unbound.Settings, err error) {
|
||||||
|
providers := make([]provider.Provider, len(u.Providers))
|
||||||
|
for i := range providers {
|
||||||
|
providers[i], err = provider.Parse(u.Providers[i])
|
||||||
|
if err != nil {
|
||||||
|
return settings, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const port = 53
|
||||||
|
|
||||||
|
return unbound.Settings{
|
||||||
|
ListeningPort: port,
|
||||||
|
IPv4: true,
|
||||||
|
Providers: providers,
|
||||||
|
Caching: *u.Caching,
|
||||||
|
IPv6: *u.IPv6,
|
||||||
|
VerbosityLevel: *u.VerbosityLevel,
|
||||||
|
VerbosityDetailsLevel: *u.VerbosityDetailsLevel,
|
||||||
|
ValidationLogLevel: *u.ValidationLogLevel,
|
||||||
|
AccessControl: unbound.AccessControlSettings{
|
||||||
|
Allowed: u.Allowed,
|
||||||
|
},
|
||||||
|
Username: u.Username,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u Unbound) GetFirstPlaintextIPv4() (ipv4 net.IP, err error) {
|
||||||
|
s := u.Providers[0]
|
||||||
|
provider, err := provider.Parse(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return provider.DNS().IPv4[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u Unbound) String() string {
|
||||||
|
return u.toLinesNode().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u Unbound) toLinesNode() (node *gotree.Node) {
|
||||||
|
node = gotree.New("Unbound settings:")
|
||||||
|
|
||||||
|
authServers := node.Appendf("Authoritative servers:")
|
||||||
|
for _, provider := range u.Providers {
|
||||||
|
authServers.Appendf(provider)
|
||||||
|
}
|
||||||
|
|
||||||
|
node.Appendf("Caching: %s", helpers.BoolPtrToYesNo(u.Caching))
|
||||||
|
node.Appendf("IPv6: %s", helpers.BoolPtrToYesNo(u.IPv6))
|
||||||
|
node.Appendf("Verbosity level: %d", *u.VerbosityLevel)
|
||||||
|
node.Appendf("Verbosity details level: %d", *u.VerbosityDetailsLevel)
|
||||||
|
node.Appendf("Validation log level: %d", *u.ValidationLogLevel)
|
||||||
|
node.Appendf("System user: %s", u.Username)
|
||||||
|
|
||||||
|
allowedNetworks := node.Appendf("Allowed networks:")
|
||||||
|
for _, network := range u.Allowed {
|
||||||
|
allowedNetworks.Appendf(network.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return node
|
||||||
|
}
|
||||||
43
internal/configuration/settings/unbound_test.go
Normal file
43
internal/configuration/settings/unbound_test.go
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"inet.af/netaddr"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_Unbound_JSON(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
settings := Unbound{
|
||||||
|
Providers: []string{"cloudflare"},
|
||||||
|
Caching: boolPtr(true),
|
||||||
|
IPv6: boolPtr(false),
|
||||||
|
VerbosityLevel: uint8Ptr(1),
|
||||||
|
VerbosityDetailsLevel: nil,
|
||||||
|
ValidationLogLevel: uint8Ptr(0),
|
||||||
|
Username: "user",
|
||||||
|
Allowed: []netaddr.IPPrefix{
|
||||||
|
netaddr.IPPrefixFrom(netaddr.IPv4(0, 0, 0, 0), 0),
|
||||||
|
netaddr.IPPrefixFrom(netaddr.IPv6Raw([16]byte{}), 0),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := json.Marshal(settings)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
const expected = `{"Providers":["cloudflare"],"Caching":true,"IPv6":false,` +
|
||||||
|
`"VerbosityLevel":1,"VerbosityDetailsLevel":null,"ValidationLogLevel":0,` +
|
||||||
|
`"Username":"user","Allowed":["0.0.0.0/0","::/0"]}`
|
||||||
|
|
||||||
|
assert.Equal(t, expected, string(b))
|
||||||
|
|
||||||
|
var resultSettings Unbound
|
||||||
|
err = json.Unmarshal(b, &resultSettings)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, settings, resultSettings)
|
||||||
|
}
|
||||||
120
internal/configuration/settings/updater.go
Normal file
120
internal/configuration/settings/updater.go
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gotree"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Updater contains settings to configure the VPN
|
||||||
|
// server information updater.
|
||||||
|
type Updater struct {
|
||||||
|
// Period is the period for which the updater
|
||||||
|
// should run. It can be set to 0 to disable the
|
||||||
|
// updater. It cannot be nil in the internal state.
|
||||||
|
// TODO change to value and add Enabled field.
|
||||||
|
Period *time.Duration
|
||||||
|
// DNSAddress is the DNS server address to use
|
||||||
|
// to resolve VPN server hostnames to IP addresses.
|
||||||
|
// It cannot be nil in the internal state.
|
||||||
|
DNSAddress net.IP
|
||||||
|
// Providers is the list of VPN service providers
|
||||||
|
// to update server information for.
|
||||||
|
Providers []string
|
||||||
|
// CLI is to precise the updater is running in CLI
|
||||||
|
// mode. This is set automatically and cannot be set
|
||||||
|
// by settings sources. It cannot be nil in the
|
||||||
|
// internal state.
|
||||||
|
CLI *bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u Updater) Validate() (err error) {
|
||||||
|
const minPeriod = time.Minute
|
||||||
|
if *u.Period > 0 && *u.Period < minPeriod {
|
||||||
|
return fmt.Errorf("%w: %d must be larger than %s",
|
||||||
|
ErrUpdaterPeriodTooSmall, *u.Period, minPeriod)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, provider := range u.Providers {
|
||||||
|
valid := false
|
||||||
|
for _, validProvider := range constants.AllProviders() {
|
||||||
|
if validProvider == constants.Custom {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if provider == validProvider {
|
||||||
|
valid = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !valid {
|
||||||
|
return fmt.Errorf("%w: %s at index %d",
|
||||||
|
ErrVPNProviderNameNotValid, provider, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *Updater) copy() (copied Updater) {
|
||||||
|
return Updater{
|
||||||
|
Period: helpers.CopyDurationPtr(u.Period),
|
||||||
|
DNSAddress: helpers.CopyIP(u.DNSAddress),
|
||||||
|
Providers: helpers.CopyStringSlice(u.Providers),
|
||||||
|
CLI: u.CLI,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// mergeWith merges the other settings into any
|
||||||
|
// unset field of the receiver settings object.
|
||||||
|
func (u *Updater) mergeWith(other Updater) {
|
||||||
|
u.Period = helpers.MergeWithDuration(u.Period, other.Period)
|
||||||
|
u.DNSAddress = helpers.MergeWithIP(u.DNSAddress, other.DNSAddress)
|
||||||
|
u.Providers = helpers.MergeStringSlices(u.Providers, other.Providers)
|
||||||
|
u.CLI = helpers.MergeWithBool(u.CLI, other.CLI)
|
||||||
|
}
|
||||||
|
|
||||||
|
// overrideWith overrides fields of the receiver
|
||||||
|
// settings object with any field set in the other
|
||||||
|
// settings.
|
||||||
|
func (u *Updater) overrideWith(other Updater) {
|
||||||
|
u.Period = helpers.OverrideWithDuration(u.Period, other.Period)
|
||||||
|
u.DNSAddress = helpers.OverrideWithIP(u.DNSAddress, other.DNSAddress)
|
||||||
|
u.Providers = helpers.OverrideWithStringSlice(u.Providers, other.Providers)
|
||||||
|
u.CLI = helpers.MergeWithBool(u.CLI, other.CLI)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *Updater) SetDefaults(vpnProvider string) {
|
||||||
|
u.Period = helpers.DefaultDuration(u.Period, 0)
|
||||||
|
u.DNSAddress = helpers.DefaultIP(u.DNSAddress, net.IPv4(1, 1, 1, 1))
|
||||||
|
u.CLI = helpers.DefaultBool(u.CLI, false)
|
||||||
|
if len(u.Providers) == 0 && vpnProvider != constants.Custom {
|
||||||
|
u.Providers = []string{vpnProvider}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u Updater) String() string {
|
||||||
|
return u.toLinesNode().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u Updater) toLinesNode() (node *gotree.Node) {
|
||||||
|
if *u.Period == 0 || len(u.Providers) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
node = gotree.New("Server data updater settings:")
|
||||||
|
node.Appendf("Update period: %s", *u.Period)
|
||||||
|
node.Appendf("DNS address: %s", u.DNSAddress)
|
||||||
|
node.Appendf("Providers to update: %s", strings.Join(u.Providers, ", "))
|
||||||
|
|
||||||
|
if *u.CLI {
|
||||||
|
node.Appendf("CLI mode: enabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
return node
|
||||||
|
}
|
||||||
21
internal/configuration/settings/validation/cyberghost.go
Normal file
21
internal/configuration/settings/validation/cyberghost.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CyberghostCountryChoices(servers []models.CyberghostServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Country
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CyberghostHostnameChoices(servers []models.CyberghostServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Hostname
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
29
internal/configuration/settings/validation/expressvpn.go
Normal file
29
internal/configuration/settings/validation/expressvpn.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExpressvpnCountriesChoices(servers []models.ExpressvpnServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Country
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExpressvpnCityChoices(servers []models.ExpressvpnServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].City
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExpressvpnHostnameChoices(servers []models.ExpressvpnServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Hostname
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
21
internal/configuration/settings/validation/fastestvpn.go
Normal file
21
internal/configuration/settings/validation/fastestvpn.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func FastestvpnCountriesChoices(servers []models.FastestvpnServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Country
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func FastestvpnHostnameChoices(servers []models.FastestvpnServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Hostname
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
23
internal/configuration/settings/validation/helpers.go
Normal file
23
internal/configuration/settings/validation/helpers.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package validation
|
||||||
|
|
||||||
|
import "sort"
|
||||||
|
|
||||||
|
func makeUnique(choices []string) (uniqueChoices []string) {
|
||||||
|
seen := make(map[string]struct{}, len(choices))
|
||||||
|
uniqueChoices = make([]string, 0, len(uniqueChoices))
|
||||||
|
|
||||||
|
for _, choice := range choices {
|
||||||
|
if _, ok := seen[choice]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[choice] = struct{}{}
|
||||||
|
|
||||||
|
uniqueChoices = append(uniqueChoices, choice)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(uniqueChoices, func(i, j int) bool {
|
||||||
|
return uniqueChoices[i] < uniqueChoices[j]
|
||||||
|
})
|
||||||
|
|
||||||
|
return uniqueChoices
|
||||||
|
}
|
||||||
37
internal/configuration/settings/validation/hidemyass.go
Normal file
37
internal/configuration/settings/validation/hidemyass.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func HideMyAssCountryChoices(servers []models.HideMyAssServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Country
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func HideMyAssRegionChoices(servers []models.HideMyAssServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Region
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func HideMyAssCityChoices(servers []models.HideMyAssServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].City
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func HideMyAssHostnameChoices(servers []models.HideMyAssServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Hostname
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
29
internal/configuration/settings/validation/ipvanish.go
Normal file
29
internal/configuration/settings/validation/ipvanish.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func IpvanishCountryChoices(servers []models.IpvanishServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Country
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func IpvanishCityChoices(servers []models.IpvanishServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].City
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func IpvanishHostnameChoices(servers []models.IpvanishServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Hostname
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
37
internal/configuration/settings/validation/ivpn.go
Normal file
37
internal/configuration/settings/validation/ivpn.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func IvpnCountryChoices(servers []models.IvpnServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Country
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func IvpnCityChoices(servers []models.IvpnServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].City
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func IvpnISPChoices(servers []models.IvpnServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].ISP
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func IvpnHostnameChoices(servers []models.IvpnServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Hostname
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
37
internal/configuration/settings/validation/mullvad.go
Normal file
37
internal/configuration/settings/validation/mullvad.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func MullvadCountryChoices(servers []models.MullvadServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Country
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MullvadCityChoices(servers []models.MullvadServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].City
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MullvadHostnameChoices(servers []models.MullvadServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Hostname
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MullvadISPChoices(servers []models.MullvadServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].ISP
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
21
internal/configuration/settings/validation/nordvpn.go
Normal file
21
internal/configuration/settings/validation/nordvpn.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NordvpnRegionChoices(servers []models.NordvpnServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Region
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NordvpnHostnameChoices(servers []models.NordvpnServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Hostname
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
13
internal/configuration/settings/validation/perfectprivacy.go
Normal file
13
internal/configuration/settings/validation/perfectprivacy.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func PerfectprivacyCityChoices(servers []models.PerfectprivacyServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].City
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
29
internal/configuration/settings/validation/pia.go
Normal file
29
internal/configuration/settings/validation/pia.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func PIAGeoChoices(servers []models.PIAServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Region
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func PIAHostnameChoices(servers []models.PIAServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Hostname
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func PIANameChoices(servers []models.PIAServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].ServerName
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
35
internal/configuration/settings/validation/privado.go
Normal file
35
internal/configuration/settings/validation/privado.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package validation
|
||||||
|
|
||||||
|
import "github.com/qdm12/gluetun/internal/models"
|
||||||
|
|
||||||
|
func PrivadoCountryChoices(servers []models.PrivadoServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Country
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func PrivadoRegionChoices(servers []models.PrivadoServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Region
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func PrivadoCityChoices(servers []models.PrivadoServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].City
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func PrivadoHostnameChoices(servers []models.PrivadoServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Hostname
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
27
internal/configuration/settings/validation/privatevpn.go
Normal file
27
internal/configuration/settings/validation/privatevpn.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package validation
|
||||||
|
|
||||||
|
import "github.com/qdm12/gluetun/internal/models"
|
||||||
|
|
||||||
|
func PrivatevpnCountryChoices(servers []models.PrivatevpnServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Country
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func PrivatevpnCityChoices(servers []models.PrivatevpnServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].City
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func PrivatevpnHostnameChoices(servers []models.PrivatevpnServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Hostname
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
43
internal/configuration/settings/validation/protonvpn.go
Normal file
43
internal/configuration/settings/validation/protonvpn.go
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package validation
|
||||||
|
|
||||||
|
import "github.com/qdm12/gluetun/internal/models"
|
||||||
|
|
||||||
|
func ProtonvpnCountryChoices(servers []models.ProtonvpnServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Country
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ProtonvpnRegionChoices(servers []models.ProtonvpnServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Region
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ProtonvpnCityChoices(servers []models.ProtonvpnServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].City
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ProtonvpnNameChoices(servers []models.ProtonvpnServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Name
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ProtonvpnHostnameChoices(servers []models.ProtonvpnServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Hostname
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
35
internal/configuration/settings/validation/purevpn.go
Normal file
35
internal/configuration/settings/validation/purevpn.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package validation
|
||||||
|
|
||||||
|
import "github.com/qdm12/gluetun/internal/models"
|
||||||
|
|
||||||
|
func PurevpnRegionChoices(servers []models.PurevpnServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Region
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func PurevpnCountryChoices(servers []models.PurevpnServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Country
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func PurevpnCityChoices(servers []models.PurevpnServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].City
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func PurevpnHostnameChoices(servers []models.PurevpnServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Hostname
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
60
internal/configuration/settings/validation/surfshark.go
Normal file
60
internal/configuration/settings/validation/surfshark.go
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SurfsharkRegionChoices(servers []models.SurfsharkServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Region
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SurfsharkCountryChoices(servers []models.SurfsharkServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Country
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SurfsharkCityChoices(servers []models.SurfsharkServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].City
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SurfsharkHostnameChoices(servers []models.SurfsharkServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Hostname
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO remove in v4.
|
||||||
|
func SurfsharkRetroLocChoices() (choices []string) {
|
||||||
|
locationData := constants.SurfsharkLocationData()
|
||||||
|
choices = make([]string, 0, len(locationData))
|
||||||
|
seen := make(map[string]struct{}, len(locationData))
|
||||||
|
for _, data := range locationData {
|
||||||
|
if _, ok := seen[data.RetroLoc]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[data.RetroLoc] = struct{}{}
|
||||||
|
choices = append(choices, data.RetroLoc)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(choices, func(i, j int) bool {
|
||||||
|
return choices[i] < choices[j]
|
||||||
|
})
|
||||||
|
|
||||||
|
return choices
|
||||||
|
}
|
||||||
29
internal/configuration/settings/validation/torguard.go
Normal file
29
internal/configuration/settings/validation/torguard.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TorguardCountryChoices(servers []models.TorguardServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Country
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TorguardCityChoices(servers []models.TorguardServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].City
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TorguardHostnameChoices(servers []models.TorguardServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Hostname
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
29
internal/configuration/settings/validation/vpnunlimited.go
Normal file
29
internal/configuration/settings/validation/vpnunlimited.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func VPNUnlimitedCountryChoices(servers []models.VPNUnlimitedServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Country
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func VPNUnlimitedCityChoices(servers []models.VPNUnlimitedServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].City
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func VPNUnlimitedHostnameChoices(servers []models.VPNUnlimitedServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Hostname
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
13
internal/configuration/settings/validation/vyprvpn.go
Normal file
13
internal/configuration/settings/validation/vyprvpn.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func VyprvpnRegionChoices(servers []models.VyprvpnServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Region
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
19
internal/configuration/settings/validation/wevpn.go
Normal file
19
internal/configuration/settings/validation/wevpn.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package validation
|
||||||
|
|
||||||
|
import "github.com/qdm12/gluetun/internal/models"
|
||||||
|
|
||||||
|
func WevpnCityChoices(servers []models.WevpnServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].City
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func WevpnHostnameChoices(servers []models.WevpnServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Hostname
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
27
internal/configuration/settings/validation/windscribe.go
Normal file
27
internal/configuration/settings/validation/windscribe.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package validation
|
||||||
|
|
||||||
|
import "github.com/qdm12/gluetun/internal/models"
|
||||||
|
|
||||||
|
func WindscribeRegionChoices(servers []models.WindscribeServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Region
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func WindscribeCityChoices(servers []models.WindscribeServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].City
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func WindscribeHostnameChoices(servers []models.WindscribeServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Hostname
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
53
internal/configuration/settings/version.go
Normal file
53
internal/configuration/settings/version.go
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
|
"github.com/qdm12/gotree"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Version contains settings to configure the version
|
||||||
|
// information fetcher.
|
||||||
|
type Version struct {
|
||||||
|
// Enabled is true if the version information should
|
||||||
|
// be fetched from Github.
|
||||||
|
Enabled *bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Version) validate() (err error) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Version) copy() (copied Version) {
|
||||||
|
return Version{
|
||||||
|
Enabled: helpers.CopyBoolPtr(v.Enabled),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// mergeWith merges the other settings into any
|
||||||
|
// unset field of the receiver settings object.
|
||||||
|
func (v *Version) mergeWith(other Version) {
|
||||||
|
v.Enabled = helpers.MergeWithBool(v.Enabled, other.Enabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
// overrideWith overrides fields of the receiver
|
||||||
|
// settings object with any field set in the other
|
||||||
|
// settings.
|
||||||
|
func (v *Version) overrideWith(other Version) {
|
||||||
|
v.Enabled = helpers.OverrideWithBool(v.Enabled, other.Enabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Version) setDefaults() {
|
||||||
|
v.Enabled = helpers.DefaultBool(v.Enabled, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Version) String() string {
|
||||||
|
return v.toLinesNode().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Version) toLinesNode() (node *gotree.Node) {
|
||||||
|
node = gotree.New("Version settings:")
|
||||||
|
|
||||||
|
node.Appendf("Enabled: %s", helpers.BoolPtrToYesNo(v.Enabled))
|
||||||
|
|
||||||
|
return node
|
||||||
|
}
|
||||||
98
internal/configuration/settings/vpn.go
Normal file
98
internal/configuration/settings/vpn.go
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gotree"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VPN struct {
|
||||||
|
// Type is the VPN type and can only be
|
||||||
|
// 'openvpn' or 'wireguard'. It cannot be the
|
||||||
|
// empty string in the internal state.
|
||||||
|
Type string
|
||||||
|
Provider Provider
|
||||||
|
OpenVPN OpenVPN
|
||||||
|
Wireguard Wireguard
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO v4 remove pointer for receiver (because of Surfshark).
|
||||||
|
func (v *VPN) validate(allServers models.AllServers) (err error) {
|
||||||
|
// Validate Type
|
||||||
|
validVPNTypes := []string{constants.OpenVPN, constants.Wireguard}
|
||||||
|
if !helpers.IsOneOf(v.Type, validVPNTypes...) {
|
||||||
|
return fmt.Errorf("%w: %q and can only be one of %s",
|
||||||
|
ErrVPNTypeNotValid, v.Type, strings.Join(validVPNTypes, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
err = v.Provider.validate(v.Type, allServers)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("provider settings: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.Type == constants.OpenVPN {
|
||||||
|
err := v.OpenVPN.validate(*v.Provider.Name)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("OpenVPN settings: %w", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err := v.Wireguard.validate(*v.Provider.Name)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Wireguard settings: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *VPN) copy() (copied VPN) {
|
||||||
|
return VPN{
|
||||||
|
Type: v.Type,
|
||||||
|
Provider: v.Provider.copy(),
|
||||||
|
OpenVPN: v.OpenVPN.copy(),
|
||||||
|
Wireguard: v.Wireguard.copy(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *VPN) mergeWith(other VPN) {
|
||||||
|
v.Type = helpers.MergeWithString(v.Type, other.Type)
|
||||||
|
v.Provider.mergeWith(other.Provider)
|
||||||
|
v.OpenVPN.mergeWith(other.OpenVPN)
|
||||||
|
v.Wireguard.mergeWith(other.Wireguard)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *VPN) overrideWith(other VPN) {
|
||||||
|
v.Type = helpers.OverrideWithString(v.Type, other.Type)
|
||||||
|
v.Provider.overrideWith(other.Provider)
|
||||||
|
v.OpenVPN.overrideWith(other.OpenVPN)
|
||||||
|
v.Wireguard.overrideWith(other.Wireguard)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *VPN) setDefaults() {
|
||||||
|
v.Type = helpers.DefaultString(v.Type, constants.OpenVPN)
|
||||||
|
v.Provider.setDefaults()
|
||||||
|
v.OpenVPN.setDefaults(*v.Provider.Name)
|
||||||
|
v.Wireguard.setDefaults()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v VPN) String() string {
|
||||||
|
return v.toLinesNode().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v VPN) toLinesNode() (node *gotree.Node) {
|
||||||
|
node = gotree.New("VPN settings:")
|
||||||
|
|
||||||
|
node.AppendNode(v.Provider.toLinesNode())
|
||||||
|
|
||||||
|
if v.Type == constants.OpenVPN {
|
||||||
|
node.AppendNode(v.OpenVPN.toLinesNode())
|
||||||
|
} else {
|
||||||
|
node.AppendNode(v.Wireguard.toLinesNode())
|
||||||
|
}
|
||||||
|
|
||||||
|
return node
|
||||||
|
}
|
||||||
138
internal/configuration/settings/wireguard.go
Normal file
138
internal/configuration/settings/wireguard.go
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gotree"
|
||||||
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Wireguard contains settings to configure the Wireguard client.
|
||||||
|
type Wireguard struct {
|
||||||
|
// PrivateKey is the Wireguard client peer private key.
|
||||||
|
// It cannot be nil in the internal state.
|
||||||
|
PrivateKey *string
|
||||||
|
// PreSharedKey is the Wireguard pre-shared key.
|
||||||
|
// It can be the empty string to indicate there
|
||||||
|
// is no pre-shared key.
|
||||||
|
// It cannot be nil in the internal state.
|
||||||
|
PreSharedKey *string
|
||||||
|
// Addresses are the Wireguard interface addresses.
|
||||||
|
Addresses []net.IPNet
|
||||||
|
// Interface is the name of the Wireguard interface
|
||||||
|
// to create. It cannot be the empty string in the
|
||||||
|
// internal state.
|
||||||
|
Interface string
|
||||||
|
}
|
||||||
|
|
||||||
|
var regexpInterfaceName = regexp.MustCompile(`^[a-zA-Z0-9_]+$`)
|
||||||
|
|
||||||
|
// Validate validates Wireguard settings.
|
||||||
|
// It should only be ran if the VPN type chosen is Wireguard.
|
||||||
|
func (w Wireguard) validate(vpnProvider string) (err error) {
|
||||||
|
if !helpers.IsOneOf(vpnProvider,
|
||||||
|
constants.Custom,
|
||||||
|
constants.Ivpn,
|
||||||
|
constants.Mullvad,
|
||||||
|
constants.Windscribe,
|
||||||
|
) {
|
||||||
|
// do not validate for VPN provider not supporting Wireguard
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate PrivateKey
|
||||||
|
if *w.PrivateKey == "" {
|
||||||
|
return ErrWireguardPrivateKeyNotSet
|
||||||
|
}
|
||||||
|
_, err = wgtypes.ParseKey(*w.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("private key is not valid: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate PreSharedKey
|
||||||
|
if *w.PreSharedKey != "" { // Note: this is optional
|
||||||
|
_, err = wgtypes.ParseKey(*w.PreSharedKey)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("pre-shared key is not valid: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate Addresses
|
||||||
|
if len(w.Addresses) == 0 {
|
||||||
|
return ErrWireguardInterfaceAddressNotSet
|
||||||
|
}
|
||||||
|
for i, ipNet := range w.Addresses {
|
||||||
|
if ipNet.IP == nil || ipNet.Mask == nil {
|
||||||
|
return fmt.Errorf("%w: for address at index %d: %s",
|
||||||
|
ErrWireguardInterfaceAddressNotSet, i, ipNet.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate interface
|
||||||
|
if !regexpInterfaceName.MatchString(w.Interface) {
|
||||||
|
return fmt.Errorf("%w: '%s' does not match regex '%s'",
|
||||||
|
ErrWireguardInterfaceNotValid, w.Interface, regexpInterfaceName)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Wireguard) copy() (copied Wireguard) {
|
||||||
|
return Wireguard{
|
||||||
|
PrivateKey: helpers.CopyStringPtr(w.PrivateKey),
|
||||||
|
PreSharedKey: helpers.CopyStringPtr(w.PreSharedKey),
|
||||||
|
Addresses: helpers.CopyIPNetSlice(w.Addresses),
|
||||||
|
Interface: w.Interface,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Wireguard) mergeWith(other Wireguard) {
|
||||||
|
w.PrivateKey = helpers.MergeWithStringPtr(w.PrivateKey, other.PrivateKey)
|
||||||
|
w.PreSharedKey = helpers.MergeWithStringPtr(w.PreSharedKey, other.PreSharedKey)
|
||||||
|
w.Addresses = helpers.MergeIPNetsSlices(w.Addresses, other.Addresses)
|
||||||
|
w.Interface = helpers.MergeWithString(w.Interface, other.Interface)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Wireguard) overrideWith(other Wireguard) {
|
||||||
|
w.PrivateKey = helpers.OverrideWithStringPtr(w.PrivateKey, other.PrivateKey)
|
||||||
|
w.PreSharedKey = helpers.OverrideWithStringPtr(w.PreSharedKey, other.PreSharedKey)
|
||||||
|
w.Addresses = helpers.OverrideWithIPNetsSlice(w.Addresses, other.Addresses)
|
||||||
|
w.Interface = helpers.OverrideWithString(w.Interface, other.Interface)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Wireguard) setDefaults() {
|
||||||
|
w.PrivateKey = helpers.DefaultStringPtr(w.PrivateKey, "")
|
||||||
|
w.PreSharedKey = helpers.DefaultStringPtr(w.PreSharedKey, "")
|
||||||
|
w.Interface = helpers.DefaultString(w.Interface, "wg0")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w Wireguard) String() string {
|
||||||
|
return w.toLinesNode().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w Wireguard) toLinesNode() (node *gotree.Node) {
|
||||||
|
node = gotree.New("Wireguard settings:")
|
||||||
|
|
||||||
|
if *w.PrivateKey != "" {
|
||||||
|
s := helpers.ObfuscateWireguardKey(*w.PrivateKey)
|
||||||
|
node.Appendf("Private key: %s", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *w.PreSharedKey != "" {
|
||||||
|
s := helpers.ObfuscateWireguardKey(*w.PreSharedKey)
|
||||||
|
node.Appendf("Pre-shared key: %s", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
addressesNode := node.Appendf("Interface addresses:")
|
||||||
|
for _, address := range w.Addresses {
|
||||||
|
addressesNode.Appendf(address.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
node.Appendf("Network interface: %s", w.Interface)
|
||||||
|
|
||||||
|
return node
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user