Compare commits
774 commits
Author | SHA1 | Date | |
---|---|---|---|
|
07b86521a5 | ||
|
961008bbe1 | ||
|
92e69f561f | ||
|
69b28b9b02 | ||
|
35a68703c2 | ||
|
c49fe04750 | ||
|
31feabbec7 | ||
|
bc3cb2c3c9 | ||
|
5ec4481c92 | ||
|
be5cb48dfe | ||
|
48ff1ece16 | ||
|
ed20ed592f | ||
|
4fb3435c29 | ||
|
37eb14a01a | ||
|
d403bc86e3 | ||
|
0e2f0f2a4d | ||
|
1a4d34a802 | ||
|
bb15af9954 | ||
|
8a250d1011 | ||
|
2f9994f600 | ||
|
1cca06a274 | ||
|
8fdb3ea631 | ||
|
35823d5751 | ||
|
66f90a542a | ||
|
49981c4bee | ||
|
d732c1a845 | ||
|
4d7e25f97b | ||
|
80656f48e0 | ||
|
ebde149980 | ||
|
adc0a81592 | ||
|
b596f00ce5 | ||
|
448442f92b | ||
|
8518201562 | ||
|
17586c4559 | ||
|
f8622da7d4 | ||
|
b1a27e9060 | ||
|
3c6423d444 | ||
|
91b03160ea | ||
|
0c1e20ba48 | ||
|
1dcac85c0d | ||
|
3fc72dbec2 | ||
|
494329f568 | ||
|
a1e8211ba7 | ||
|
80aa7502af | ||
|
67bae76048 | ||
|
bda2aa46b6 | ||
|
6d9ba8dd2f | ||
|
2ca8febff7 | ||
|
e105a523e4 | ||
|
28f8b05dbc | ||
|
d95286db0e | ||
|
8e45c34e8e | ||
|
9e87c42d0c | ||
|
0b52cd002e | ||
|
15adfcca8c | ||
|
1466788f77 | ||
|
760fe3aca9 | ||
|
5f75813e84 | ||
|
59cb06acf4 | ||
|
6349406523 | ||
|
bcc2c59f08 | ||
|
52d46f9879 | ||
|
0b50a5474d | ||
|
5c929badeb | ||
|
3f2de333fb | ||
|
7c12b8ae25 | ||
|
b54ccbfa2f | ||
|
114ce8997f | ||
|
f1bba3b958 | ||
|
053acef728 | ||
|
9f2710185b | ||
|
d000879c01 | ||
|
25ae169fee | ||
|
4443dda0f6 | ||
|
c484e989a9 | ||
|
86a4656651 | ||
|
f25aefeb11 | ||
|
228643c7d7 | ||
|
072d6d7094 | ||
|
de3ce672b8 | ||
|
6f5c191998 | ||
|
bbaea4def0 | ||
|
54f9282166 | ||
|
a39b1db266 | ||
|
2ddb4ec905 | ||
|
7a59e3acf7 | ||
|
b34c3db956 | ||
|
afea958aca | ||
|
dca2a29865 | ||
|
97b8e84143 | ||
|
23eb0da7d7 | ||
|
2edda471e7 | ||
|
676aa1358d | ||
|
87a36d6ae3 | ||
|
b67611094e | ||
|
2e986def78 | ||
|
d16a05959d | ||
|
7e58e0b490 | ||
|
9b01aecf3c | ||
|
86043fd5f8 | ||
|
372a1758e9 | ||
|
0a2b1d9e53 | ||
|
e562946308 | ||
|
398e15b3c6 | ||
|
c225a54dbe | ||
|
5148988dcc | ||
|
28b57ba652 | ||
|
9c7e74ef37 | ||
|
330b28ad9c | ||
|
da7166a7ea | ||
|
e8793c5d8d | ||
|
c63d45e344 | ||
|
1159a79410 | ||
|
5b5e65ac08 | ||
|
417811e94f | ||
|
9c40057c51 | ||
|
a6409c16c3 | ||
|
ba4667528c | ||
|
a2368d7c3f | ||
|
e90b203a7d | ||
|
b7389d74db | ||
|
24321521c7 | ||
|
f85497d446 | ||
|
1f9e92fdb7 | ||
|
b66d2b95c5 | ||
|
6f013b3bc4 | ||
|
cdf8a01c14 | ||
|
547923bbb4 | ||
|
a8c3c1bece | ||
|
0f47a5d51e | ||
|
9f9196427f | ||
|
341d688e79 | ||
|
92c5d1ed46 | ||
|
55d8579b1a | ||
|
51a74c5045 | ||
|
64f1af4d0b | ||
|
8dd5daf495 | ||
|
0ee87d736c | ||
|
ba822af355 | ||
|
78c1990b42 | ||
|
768179a664 | ||
|
1268c46f02 | ||
|
d870a49381 | ||
|
8e476cc07e | ||
|
ea54fd6e98 | ||
|
ea001cc0db | ||
|
2378343c74 | ||
|
018b3346bb | ||
|
d104829a6d | ||
|
0035460712 | ||
|
cae57ffcd8 | ||
|
b587ff663c | ||
|
531a888592 | ||
|
a13b167c41 | ||
|
c1f9195a38 | ||
|
e1ea7c6986 | ||
|
bd21927069 | ||
|
f1f4f7e5f3 | ||
|
ccdcda7f1d | ||
|
e9586b7336 | ||
|
da36a218df | ||
|
1723089844 | ||
|
e99eb04e2f | ||
|
5fddd71fc2 | ||
|
c682b03736 | ||
|
4008767e1b | ||
|
74c768e2fb | ||
|
03b883c320 | ||
|
807ddaa0bb | ||
|
da9d555168 | ||
|
05ba2c0103 | ||
|
de96b722d5 | ||
|
afde493040 | ||
|
cce440cf49 | ||
|
e96dbdb026 | ||
|
f4cd163c34 | ||
|
0af9bdc964 | ||
|
71bb1105f7 | ||
|
84c28645be | ||
|
665ef08733 | ||
|
b3e63a5f8a | ||
|
92c09b8843 | ||
|
7b726a41ef | ||
|
a1a0587a2b | ||
|
98be9cee83 | ||
|
0646934c9d | ||
|
f0f68632ff | ||
|
e557bda48e | ||
|
2992de5139 | ||
|
779532b1c9 | ||
|
300175ac67 | ||
|
a356147164 | ||
|
d1489a9a78 | ||
|
021bc073a2 | ||
|
4e571e1e4d | ||
|
1dff9baa61 | ||
|
19d54778f5 | ||
|
99d0fa974b | ||
|
84ca275f1e | ||
|
963bc5f0bc | ||
|
b446c2ce4b | ||
|
65a4f7af28 | ||
|
f6b2c5bbf3 | ||
|
e99905e3c9 | ||
|
5c0b42446d | ||
|
a3be0d4655 | ||
|
47729c2348 | ||
|
78bfcf5b1c | ||
|
f1291d4d7d | ||
|
ff809416f5 | ||
|
83a4677026 | ||
|
ffe8593a07 | ||
|
60e4db16ff | ||
|
2e510778be | ||
|
c492559010 | ||
|
a8dc36b6d2 | ||
|
379d31aac6 | ||
|
28fc1ab063 | ||
|
75e01018a5 | ||
|
b7df0b122d | ||
|
f9798a8d86 | ||
|
7691bdd181 | ||
|
3dc79da2fa | ||
|
83b00c1cfa | ||
|
d6fdb38c22 | ||
|
3505342a8d | ||
|
78661799f2 | ||
|
ec57996b01 | ||
|
0a97d91aed | ||
|
753027ffc7 | ||
|
d3383f0f1a | ||
|
9075ecb007 | ||
|
4b8cc7c4d3 | ||
|
2f20397c60 | ||
|
7f227932da | ||
|
69d253fba3 | ||
|
b75800c583 | ||
|
a35add3fc6 | ||
|
b17ff57582 | ||
|
915ccdc007 | ||
|
98a261e38c | ||
|
c9f5ffae42 | ||
|
342675276b | ||
|
590296e64d | ||
|
17b39d16a3 | ||
|
515a621eb4 | ||
|
ca0e950cdf | ||
|
cec9e4101d | ||
|
862fabf6ad | ||
|
128a9aae4e | ||
|
1983d0067d | ||
|
bdaf48da20 | ||
|
09024a93e9 | ||
|
064e7071b2 | ||
|
2b2fedb380 | ||
|
5ff7c216b9 | ||
|
c476ac7bc5 | ||
|
e83e8c2ee4 | ||
|
607b168b56 | ||
|
e0cf0916dd | ||
|
0f3a5ce8ba | ||
|
2a6298e9eb | ||
|
f97b133c8c | ||
|
f11fa4f32d | ||
|
f0a1c10ec5 | ||
|
08fed8fe93 | ||
|
813992141a | ||
|
f90129213e | ||
|
838af30a38 | ||
|
13ff15311a | ||
|
56e237c479 | ||
|
ff1e10a025 | ||
|
c259037dbf | ||
|
7b4878620f | ||
|
38fd58c173 | ||
|
d1185d43f7 | ||
|
a093c54b16 | ||
|
30cc85b793 | ||
|
5009906385 | ||
|
6ccc05b183 | ||
|
3994b25a71 | ||
|
5130071a60 | ||
|
d5e67835aa | ||
|
bf8078ed66 | ||
|
1559a2a943 | ||
|
e2d5301376 | ||
|
f31717145f | ||
|
db76558944 | ||
|
1bcb027e05 | ||
|
719d75f8a6 | ||
|
8c164c410d | ||
|
8c6a4a90aa | ||
|
c679875273 | ||
|
1e5141c27c | ||
|
e56330be47 | ||
|
764a63d784 | ||
|
6f280c4664 | ||
|
9b29f38d10 | ||
|
6b60410791 | ||
|
dffaceba6f | ||
|
4ffdd6f74f | ||
|
3499dd4f56 | ||
|
7004820326 | ||
|
24a17235ae | ||
|
66e8a4666c | ||
|
862dd6f0bc | ||
|
83afb23ac4 | ||
|
a0381eb2c6 | ||
|
3b6a44e683 | ||
|
baee745d3c | ||
|
cc930ebf53 | ||
|
809a135721 | ||
|
073c318f12 | ||
|
8f1cfd8037 | ||
|
7bf9cccbf6 | ||
|
d194e230de | ||
|
bd3d9d2da3 | ||
|
e694817b57 | ||
|
66191a9610 | ||
|
9bb4d8b2a3 | ||
|
2f5579b070 | ||
|
bc5d7f5f57 | ||
|
d729be0d71 | ||
|
7ccf7f6f15 | ||
|
918dc98f71 | ||
|
79a72528d7 | ||
|
0f2cc7d425 | ||
|
8f75725987 | ||
|
2918a3f767 | ||
|
598c206bbd | ||
|
34180ca454 | ||
|
fb5010a2b5 | ||
|
0e87b6e48b | ||
|
d45443258b | ||
|
f3b44a3085 | ||
|
9680260104 | ||
|
2ead5f4506 | ||
|
4f68a26049 | ||
|
49b88002fb | ||
|
ead5df0a8c | ||
|
9f38ad9b4d | ||
|
317a15b649 | ||
|
7748fb682d | ||
|
6f601c7814 | ||
|
c63dcd89b5 | ||
|
8c6e3be8ce | ||
|
13bfeae780 | ||
|
051162cd69 | ||
|
9065d21778 | ||
|
cafbad88f3 | ||
|
20965fc67b | ||
|
3efb04a603 | ||
|
d650d1e6eb | ||
|
2b23587200 | ||
|
a839f9146f | ||
|
a6138a02fd | ||
|
0e18fc4700 | ||
|
68015511c1 | ||
|
715d33fe90 | ||
|
85b99852bb | ||
|
a54c88eb32 | ||
|
6a5bdd40b6 | ||
|
d03f45279c | ||
|
e7b45df81f | ||
|
91df8c0556 | ||
|
2fd8134a57 | ||
|
494b54ac32 | ||
|
377eb2b851 | ||
|
bd7e96b8af | ||
|
acf25e8ad7 | ||
|
f300838f8e | ||
|
1bf2e23f5d | ||
|
58ba0d07b0 | ||
|
97ae76e4e7 | ||
|
4b24d722b6 | ||
|
09efed4331 | ||
|
a0ac757982 | ||
|
c86418dbbb | ||
|
c043912f94 | ||
|
b56ba3ee23 | ||
|
0b8bb5a974 | ||
|
3b3d7b134a | ||
|
b84b78a34d | ||
|
954b13ac60 | ||
|
bae540966b | ||
|
79d4ab1671 | ||
|
31104d3d04 | ||
|
f254d98712 | ||
|
b403f5018b | ||
|
fd219d5780 | ||
|
17ac5a5e81 | ||
|
a24431bc3b | ||
|
cdfeb2ff86 | ||
|
8199202dc3 | ||
|
7fd1fb89f1 | ||
|
32e54d0f94 | ||
|
21e9edd201 | ||
|
a0001aaa74 | ||
|
2b77c0fac8 | ||
|
81ab008d83 | ||
|
753307bb99 | ||
|
970feb75dd | ||
|
08556789f9 | ||
|
72d8ad3204 | ||
|
6cdf53e262 | ||
|
f6d81c3a23 | ||
|
4d7b905e98 | ||
|
8c42dee5de | ||
|
23d529bb31 | ||
|
7bbb687047 | ||
|
b39708700d | ||
|
d46b9eaf87 | ||
|
3e60a2bd6f | ||
|
4e2e434947 | ||
|
af32dfbbcd | ||
|
fc19d0ba8b | ||
|
83c10166e2 | ||
|
ddd292422b | ||
|
8bb0401c25 | ||
|
139c9d2ce3 | ||
|
21f4623e3e | ||
|
b86ef93211 | ||
|
6384bcd934 | ||
|
3d4177bd93 | ||
|
c6e1a9a171 | ||
|
13825568fe | ||
|
05c6a010e4 | ||
|
98178eaf24 | ||
|
8266c26ef1 | ||
|
5c682fe923 | ||
|
100dd80764 | ||
|
a59222cabb | ||
|
867f1bcc96 | ||
|
459bc32f9d | ||
|
78b38c91e7 | ||
|
2ee88d6a46 | ||
|
8d651cd44d | ||
|
8aa95db9bc | ||
|
31a41576d8 | ||
|
9a2022a4fe | ||
|
335c9b1fea | ||
|
b395b65b86 | ||
|
768e4745e1 | ||
|
ba33064852 | ||
|
94b5aadd76 | ||
|
a65ea9c360 | ||
|
a6afce5c0e | ||
|
d26ec69445 | ||
|
3c2ea1a75f | ||
|
0537992603 | ||
|
6acc2b6a17 | ||
|
83b4976305 | ||
|
b1cbb1b50f | ||
|
ff9e5a383b | ||
|
d66739f69e | ||
|
fc6c93a08a | ||
|
9897c53ed3 | ||
|
ced34dd2c6 | ||
|
92caac309a | ||
|
3c7a91a047 | ||
|
571db825ad | ||
|
0ae5ac9947 | ||
|
cd35148e48 | ||
|
19ccf098f0 | ||
|
2b64c0e84e | ||
|
d62a3c64cf | ||
|
3503cc3338 | ||
|
6d83e29ee2 | ||
|
5f9c9eed0a | ||
|
97ef363461 | ||
|
c67a2dfa73 | ||
|
f29a5ccc67 | ||
|
cbca88f76b | ||
|
3ee9051bc1 | ||
|
097dafb553 | ||
|
915581dfe7 | ||
|
a2cf4ffac1 | ||
|
6b4e52a725 | ||
|
454d7c4a88 | ||
|
cb85ad460e | ||
|
e41eafd497 | ||
|
d70396a664 | ||
|
3d59556bcd | ||
|
c7018e92b0 | ||
|
a575bace39 | ||
|
2047aa30e1 | ||
|
3b10453af3 | ||
|
363b8b52af | ||
|
3257edc2a0 | ||
|
cd54e7dd38 | ||
|
9177eaba22 | ||
|
33ae2e08cc | ||
|
4fc61386d3 | ||
|
c409266954 | ||
|
57315a36ee | ||
|
63637b91a8 | ||
|
09238cd98a | ||
|
67b149ce4b | ||
|
96151de814 | ||
|
f2e461a1ee | ||
|
8125622c98 | ||
|
1a6942ccc9 | ||
|
7b0e1df778 | ||
|
6f8c538086 | ||
|
b353a8f9b4 | ||
|
0eb35f2221 | ||
|
7d6b114d67 | ||
|
a169256770 | ||
|
2e54afd72f | ||
|
26207bd951 | ||
|
3ed681e277 | ||
|
c135b5e3cf | ||
|
e648307f0b | ||
|
0e4f35e87a | ||
|
553dffd4ee | ||
|
b4b19d2263 | ||
|
c7c39676d1 | ||
|
a6348a3e28 | ||
|
75212f1e05 | ||
|
c1fd38ac39 | ||
|
33e2798313 | ||
|
f0cb65f65c | ||
|
e885676ad8 | ||
|
b75d0a921e | ||
|
34fa5fe438 | ||
|
c2449ce795 | ||
|
9bb6cb14a6 | ||
|
b6f67e0f0b | ||
|
980545c636 | ||
|
92135ff9c1 | ||
|
dd7b91f770 | ||
|
ab843b1a43 | ||
|
4593edbb45 | ||
|
96b451843c | ||
|
54aa3ce7d8 | ||
|
45a70152ee | ||
|
8c5f00a446 | ||
|
af98610d0d | ||
|
875ec662ad | ||
|
8800ec9675 | ||
|
df4da75c57 | ||
|
717dfae26c | ||
|
58a2a9dcc9 | ||
|
27a0df4ed4 | ||
|
6fc6f325a7 | ||
|
b46e49922c | ||
|
2cca561e51 | ||
|
fbc1aa25a3 | ||
|
e8870cf174 | ||
|
17919192e0 | ||
|
d768bb163a | ||
|
dc6fafba41 | ||
|
9f979c5019 | ||
|
c3d2c34279 | ||
|
430f187fde | ||
|
f438d2ddbf | ||
|
6d519af198 | ||
|
a34e88257d | ||
|
ea0ab0e63c | ||
|
80375cd0dc | ||
|
f817ba7664 | ||
|
3398088e03 | ||
|
e586dd50f4 | ||
|
5a71c0ba65 | ||
|
f13b6abd78 | ||
|
34c6b590d7 | ||
|
ab797203eb | ||
|
30e8b1f0fe | ||
|
d03bee98f5 | ||
|
fa365fb7b8 | ||
|
ea1cd4b0d4 | ||
|
be0c7444e9 | ||
|
858c809514 | ||
|
10ff2c8a65 | ||
|
167d0b6867 | ||
|
8c121daf6c | ||
|
a23d437bd3 | ||
|
cd280d1396 | ||
|
d18200739a | ||
|
a62b2e8d10 | ||
|
c92069a1f4 | ||
|
c5e37c1608 | ||
|
948eb7f6d0 | ||
|
62a0104e70 | ||
|
6dd8db5cd1 | ||
|
9ea7275371 | ||
|
c997b8625f | ||
|
6f3514199a | ||
|
0cfc4d7dad | ||
|
56fd366a7d | ||
|
1c18b2bffb | ||
|
23b5dcfbed | ||
|
d523ebe0e0 | ||
|
30ebbaaef0 | ||
|
dba5a73e0e | ||
|
f07e8d08c3 | ||
|
ea24759bb3 | ||
|
b467d6afa1 | ||
|
373441b7ab | ||
|
af3694da34 | ||
|
ae4ef4eb99 | ||
|
547e777eb0 | ||
|
d9ee40c898 | ||
|
eff812eaa8 | ||
|
731ec1da69 | ||
|
b8ed5ac1c5 | ||
|
d2d84be99a | ||
|
96bfc3cf36 | ||
|
6f54e3da9e | ||
|
825730052b | ||
|
edc8716297 | ||
|
3ee4aaf194 | ||
|
b9a5d486b9 | ||
|
d3b9363392 | ||
|
dc66ebeed6 | ||
|
1e55d96376 | ||
|
285b99f1b7 | ||
|
8ad8f98f48 | ||
|
160a7ff3db | ||
|
1f95d7161a | ||
|
3a1f4d7545 | ||
|
408822ab7f | ||
|
840d5c2b66 | ||
|
491b4e7b18 | ||
|
89729a451c | ||
|
0fd3271ef4 | ||
|
fa21934d5d | ||
|
f91a4e88d5 | ||
|
3e9dc4753b | ||
|
b03415a0eb | ||
|
a8e8676b0a | ||
|
8242a66b97 | ||
|
d994a8100d | ||
|
1ee8561e2a | ||
|
bb7421c54e | ||
|
99352aa2a9 | ||
|
31d54eb63c | ||
|
58c12996f1 | ||
|
3dba4aa36d | ||
|
d88fc132cc | ||
|
c6ff868be8 | ||
|
e8d2cde465 | ||
|
2bd06ff493 | ||
|
75dc6edd51 | ||
|
afc6ee596d | ||
|
d47c2f9dcf | ||
|
23f9d314df | ||
|
cae4f5d840 | ||
|
1e72b0f854 | ||
|
4dd9f4736d | ||
|
e38941adf1 | ||
|
b9c7c8c966 | ||
|
87b95986c3 | ||
|
e48a0fcabc | ||
|
e9e9478f6c | ||
|
bc050097c3 | ||
|
dde2f45669 | ||
|
f62f2e3b08 | ||
|
0b235f985f | ||
|
bb0c1c839b | ||
|
4e02a7712a | ||
|
8df01208e0 | ||
|
938cc31b8a | ||
|
08bd3cfd0b | ||
|
3bb4b44f19 | ||
|
a058f4acf3 | ||
|
55222450f3 | ||
|
17789ef1a5 | ||
|
dd24b4ad74 | ||
|
6b8fa28308 | ||
|
d9aab7b3ff | ||
|
5b44f3552d | ||
|
fa1997adc1 | ||
|
29375385c0 | ||
|
4f5c3a86ff | ||
|
6e5391cb8f | ||
|
3d4b9d48e3 | ||
|
aca1cc0518 | ||
|
2543bf356c | ||
|
95fed840d4 | ||
|
8a377d73fd | ||
|
576fda2357 | ||
|
230c08e541 | ||
|
9e572685ba | ||
|
7f4135e0cf | ||
|
9d68c5666f | ||
|
d460dd35c7 | ||
|
059081ad8b | ||
|
7eb08474ff | ||
|
a76dd9c9d1 | ||
|
752c474983 | ||
|
02ccb029ae | ||
|
b01fa82627 | ||
|
0c370e4299 | ||
|
d737fda8bc | ||
|
475a431859 | ||
|
490ddfcd88 | ||
|
fa6fc9e80d | ||
|
f960fb7d67 | ||
|
f6a19631dc | ||
|
e2efd0e65a | ||
|
361f487384 | ||
|
dc49027b30 | ||
|
581fdd67b1 | ||
|
d664aa204f | ||
|
db0328fa71 | ||
|
569635f3ed | ||
|
b7ae712b63 | ||
|
8398f7b7c0 | ||
|
e57574ba9c | ||
|
68ebdda1ff | ||
|
410207f3ca | ||
|
7faf1fba8b | ||
|
8c267489c7 | ||
|
fb5c428147 | ||
|
5ff4215bde | ||
|
96d6ad8142 | ||
|
875fa215c5 | ||
|
bcd80e19d4 | ||
|
56e1684e2e | ||
|
1baa02de89 | ||
|
11cdfa7557 | ||
|
8fbd8a905f | ||
|
cd059728fd | ||
|
473b5bd3db | ||
|
81c7954e0c | ||
|
a665e3aae9 | ||
|
4b6985718a | ||
|
619cfef1c7 | ||
|
15eb666394 | ||
|
dac49f7fdc | ||
|
926ec831e2 | ||
|
87012c47ea | ||
|
fbe7e0a427 | ||
|
779a1c303f | ||
|
14e6136683 | ||
|
1f11a1df02 | ||
|
8e4bccffbf | ||
|
733e0e07c3 | ||
|
8ee6a3f134 | ||
|
bacc5e4213 | ||
|
afd87d07a3 | ||
|
bebe40c8e8 | ||
|
e4c5be4350 | ||
|
a9a9391b39 | ||
|
9f54f4d81a | ||
|
1a63669805 | ||
|
fcf6abd36e | ||
|
b9080a1ec1 | ||
|
450b0bf4fa | ||
|
a4d3a5ad4d | ||
|
6c22a2aeb4 | ||
|
2e36c97d1c | ||
|
f99efbb1e9 | ||
|
6cf3bf0255 | ||
|
a0abe41c8a | ||
|
3830ad65fc | ||
|
204403da67 | ||
|
098723b88d | ||
|
b21e758eb8 | ||
|
b1f4971f25 | ||
|
6e1bfdac58 | ||
|
11920ca997 | ||
|
255e29d9c8 | ||
|
35ccdd3014 | ||
|
757d628bc8 | ||
|
a57d32d05d | ||
|
ef69bf9256 | ||
|
bec303821b | ||
|
346f2db5fb | ||
|
b9de0f8e38 | ||
|
e112fcba29 | ||
|
41983ce356 | ||
|
fb49fb8ddd |
|
@ -26,3 +26,5 @@ install/
|
|||
bruno/
|
||||
LICENSE
|
||||
CONTRIBUTING.md
|
||||
dist
|
||||
.git
|
||||
|
|
40
.github/dependabot.yml
vendored
Normal file
|
@ -0,0 +1,40 @@
|
|||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
groups:
|
||||
dev-patch-updates:
|
||||
dependency-type: "development"
|
||||
update-types:
|
||||
- "patch"
|
||||
dev-minor-updates:
|
||||
dependency-type: "development"
|
||||
update-types:
|
||||
- "minor"
|
||||
prod-patch-updates:
|
||||
dependency-type: "production"
|
||||
update-types:
|
||||
- "patch"
|
||||
prod-minor-updates:
|
||||
dependency-type: "production"
|
||||
update-types:
|
||||
- "minor"
|
||||
|
||||
- package-ecosystem: "docker"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
groups:
|
||||
patch-updates:
|
||||
update-types:
|
||||
- "patch"
|
||||
minor-updates:
|
||||
update-types:
|
||||
- "minor"
|
||||
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
8
.github/workflows/cicd.yml
vendored
|
@ -12,13 +12,13 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
|
||||
|
@ -28,7 +28,7 @@ jobs:
|
|||
run: echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v4
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.23.0
|
||||
|
||||
|
|
34
.github/workflows/linting.yml
vendored
Normal file
|
@ -0,0 +1,34 @@
|
|||
name: ESLint
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- '**/*.js'
|
||||
- '**/*.jsx'
|
||||
- '**/*.ts'
|
||||
- '**/*.tsx'
|
||||
- '.eslintrc*'
|
||||
- 'package.json'
|
||||
- 'yarn.lock'
|
||||
- 'pnpm-lock.yaml'
|
||||
- 'package-lock.json'
|
||||
|
||||
jobs:
|
||||
Linter:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
npm ci
|
||||
|
||||
- name: Run ESLint
|
||||
run: |
|
||||
npx eslint . --ext .js,.jsx,.ts,.tsx
|
55
.github/workflows/test.yml
vendored
Normal file
|
@ -0,0 +1,55 @@
|
|||
name: Run Tests
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Copy config file
|
||||
run: cp config/config.example.yml config/config.yml
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Create database index.ts
|
||||
run: echo 'export * from "./sqlite";' > server/db/index.ts
|
||||
|
||||
- name: Generate database migrations
|
||||
run: npm run db:sqlite:generate
|
||||
|
||||
- name: Apply database migrations
|
||||
run: npm run db:sqlite:push
|
||||
|
||||
- name: Start app in background
|
||||
run: nohup npm run dev &
|
||||
|
||||
- name: Wait for app availability
|
||||
run: |
|
||||
for i in {1..5}; do
|
||||
if curl --silent --fail http://localhost:3002/auth/login; then
|
||||
echo "App is up"
|
||||
exit 0
|
||||
fi
|
||||
echo "Waiting for the app... attempt $i"
|
||||
sleep 5
|
||||
done
|
||||
echo "App failed to start"
|
||||
exit 1
|
||||
|
||||
- name: Build Docker image sqlite
|
||||
run: make build-sqlite
|
||||
|
||||
- name: Build Docker image pg
|
||||
run: make build-pg
|
4
.gitignore
vendored
|
@ -18,6 +18,7 @@ yarn-error.log*
|
|||
next-env.d.ts
|
||||
*.db
|
||||
*.sqlite
|
||||
!Dockerfile.sqlite
|
||||
*.sqlite3
|
||||
*.log
|
||||
.machinelogs*.json
|
||||
|
@ -32,4 +33,5 @@ installer
|
|||
bin
|
||||
.secrets
|
||||
test_event.json
|
||||
.idea/
|
||||
.idea/
|
||||
server/db/index.ts
|
||||
|
|
|
@ -6,10 +6,6 @@ Please see the contribution and local development guide on the docs page before
|
|||
|
||||
https://docs.fossorial.io/development
|
||||
|
||||
For ideas about what features to work on and our future plans, please see the roadmap:
|
||||
|
||||
https://docs.fossorial.io/roadmap
|
||||
|
||||
### Licensing Considerations
|
||||
|
||||
Please note that your contributions will be distributed under the AGPLv3 and the Fossorial Commercial license. For inquiries about commercial licensing, please contact us.
|
||||
|
@ -21,4 +17,4 @@ By creating this pull request, I grant the project maintainers an unlimited,
|
|||
perpetual license to use, modify, and redistribute these contributions under any terms they
|
||||
choose, including both the AGPLv3 and the Fossorial Commercial license terms. I
|
||||
represent that I have the right to grant this license for all contributed content.
|
||||
```
|
||||
```
|
14
Dockerfile.dev
Normal file
|
@ -0,0 +1,14 @@
|
|||
FROM node:20-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package*.json ./
|
||||
|
||||
# Install dependencies
|
||||
RUN npm ci
|
||||
|
||||
# Copy source code
|
||||
COPY . .
|
||||
|
||||
# Use tsx watch for development with hot reload
|
||||
CMD ["npm", "run", "dev"]
|
41
Dockerfile.pg
Normal file
|
@ -0,0 +1,41 @@
|
|||
FROM node:20-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# COPY package.json package-lock.json ./
|
||||
COPY package*.json ./
|
||||
RUN npm ci
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN echo 'export * from "./pg";' > server/db/index.ts
|
||||
|
||||
RUN npx drizzle-kit generate --dialect postgresql --schema ./server/db/pg/schema.ts --out init
|
||||
|
||||
RUN npm run build:pg
|
||||
RUN npm run build:cli
|
||||
|
||||
FROM node:20-alpine AS runner
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Curl used for the health checks
|
||||
RUN apk add --no-cache curl
|
||||
|
||||
# COPY package.json package-lock.json ./
|
||||
COPY package*.json ./
|
||||
RUN npm ci --omit=dev && npm cache clean --force
|
||||
|
||||
COPY --from=builder /app/.next/standalone ./
|
||||
COPY --from=builder /app/.next/static ./.next/static
|
||||
COPY --from=builder /app/dist ./dist
|
||||
COPY --from=builder /app/init ./dist/init
|
||||
|
||||
COPY ./cli/wrapper.sh /usr/local/bin/pangctl
|
||||
RUN chmod +x /usr/local/bin/pangctl ./dist/cli.mjs
|
||||
|
||||
COPY server/db/names.json ./dist/names.json
|
||||
|
||||
COPY public ./public
|
||||
|
||||
CMD ["npm", "run", "start:pg"]
|
|
@ -3,14 +3,17 @@ FROM node:20-alpine AS builder
|
|||
WORKDIR /app
|
||||
|
||||
# COPY package.json package-lock.json ./
|
||||
COPY package.json ./
|
||||
RUN npm install
|
||||
COPY package*.json ./
|
||||
RUN npm ci
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN npx drizzle-kit generate --dialect sqlite --schema ./server/db/schemas/ --out init
|
||||
RUN echo 'export * from "./sqlite";' > server/db/index.ts
|
||||
|
||||
RUN npm run build
|
||||
RUN npx drizzle-kit generate --dialect sqlite --schema ./server/db/sqlite/schema.ts --out init
|
||||
|
||||
RUN npm run build:sqlite
|
||||
RUN npm run build:cli
|
||||
|
||||
FROM node:20-alpine AS runner
|
||||
|
||||
|
@ -20,16 +23,19 @@ WORKDIR /app
|
|||
RUN apk add --no-cache curl
|
||||
|
||||
# COPY package.json package-lock.json ./
|
||||
COPY package.json ./
|
||||
RUN npm install --only=production && npm cache clean --force
|
||||
COPY package*.json ./
|
||||
RUN npm ci --omit=dev && npm cache clean --force
|
||||
|
||||
COPY --from=builder /app/.next/standalone ./
|
||||
COPY --from=builder /app/.next/static ./.next/static
|
||||
COPY --from=builder /app/dist ./dist
|
||||
COPY --from=builder /app/init ./dist/init
|
||||
|
||||
COPY ./cli/wrapper.sh /usr/local/bin/pangctl
|
||||
RUN chmod +x /usr/local/bin/pangctl ./dist/cli.mjs
|
||||
|
||||
COPY server/db/names.json ./dist/names.json
|
||||
|
||||
COPY public ./public
|
||||
|
||||
CMD ["npm", "start"]
|
||||
CMD ["npm", "run", "start:sqlite"]
|
17
Makefile
|
@ -1,10 +1,14 @@
|
|||
.PHONY: build build-pg build-release build-arm build-x86 test clean
|
||||
|
||||
build-release:
|
||||
@if [ -z "$(tag)" ]; then \
|
||||
echo "Error: tag is required. Usage: make build-all tag=<tag>"; \
|
||||
echo "Error: tag is required. Usage: make build-release tag=<tag>"; \
|
||||
exit 1; \
|
||||
fi
|
||||
docker buildx build --platform linux/arm64,linux/amd64 -t fosrl/pangolin:latest -f Dockerfile --push .
|
||||
docker buildx build --platform linux/arm64,linux/amd64 -t fosrl/pangolin:$(tag) -f Dockerfile --push .
|
||||
docker buildx build --platform linux/arm64,linux/amd64 -t fosrl/pangolin:latest -f Dockerfile.sqlite --push .
|
||||
docker buildx build --platform linux/arm64,linux/amd64 -t fosrl/pangolin:$(tag) -f Dockerfile.sqlite --push .
|
||||
docker buildx build --platform linux/arm64,linux/amd64 -t fosrl/pangolin:postgresql-latest -f Dockerfile.pg --push .
|
||||
docker buildx build --platform linux/arm64,linux/amd64 -t fosrl/pangolin:postgresql-$(tag) -f Dockerfile.pg --push .
|
||||
|
||||
build-arm:
|
||||
docker buildx build --platform linux/arm64 -t fosrl/pangolin:latest .
|
||||
|
@ -12,8 +16,11 @@ build-arm:
|
|||
build-x86:
|
||||
docker buildx build --platform linux/amd64 -t fosrl/pangolin:latest .
|
||||
|
||||
build:
|
||||
docker build -t fosrl/pangolin:latest .
|
||||
build-sqlite:
|
||||
docker build -t fosrl/pangolin:latest -f Dockerfile.sqlite .
|
||||
|
||||
build-pg:
|
||||
docker build -t fosrl/pangolin:postgresql-latest -f Dockerfile.pg .
|
||||
|
||||
test:
|
||||
docker run -it -p 3000:3000 -p 3001:3001 -p 3002:3002 -v ./config:/app/config fosrl/pangolin:latest
|
||||
|
|
121
README.md
|
@ -1,24 +1,22 @@
|
|||
<div align="center">
|
||||
<h2 align="center"><a href="https://fossorial.io"><img alt="pangolin" src="public/logo//word_mark.png" width="400" /></a></h2>
|
||||
|
||||
[](https://docs.fossorial.io/)
|
||||
[](https://hub.docker.com/r/fosrl/pangolin)
|
||||

|
||||
[](https://discord.gg/HCJR8Xhme4)
|
||||
[](https://www.youtube.com/@fossorial-app)
|
||||
|
||||
<h2>
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="public/logo/word_mark_white.png">
|
||||
<img alt="Pangolin Logo" src="public/logo/word_mark_black.png" width="250">
|
||||
</picture>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<h3 align="center">Tunneled Mesh Reverse Proxy Server with Access Control</h3>
|
||||
<h4 align="center">Secure gateway to your private networks</h4>
|
||||
<div align="center">
|
||||
|
||||
_Your own self-hosted zero trust tunnel._
|
||||
_Pangolin tunnels your services to the internet so you can access anything from anywhere._
|
||||
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
<h5>
|
||||
<a href="https://fossorial.io">
|
||||
<a href="https://digpangolin.com">
|
||||
Website
|
||||
</a>
|
||||
<span> | </span>
|
||||
|
@ -30,24 +28,39 @@ _Your own self-hosted zero trust tunnel._
|
|||
Contact Us
|
||||
</a>
|
||||
</h5>
|
||||
|
||||
[](https://hub.docker.com/r/fosrl/pangolin)
|
||||

|
||||
[](https://discord.gg/HCJR8Xhme4)
|
||||
[](https://www.youtube.com/@fossorial-app)
|
||||
|
||||
</div>
|
||||
|
||||
<p align="center">
|
||||
<strong>
|
||||
Start testing Pangolin at <a href="https://pangolin.fossorial.io/auth/signup">pangolin.fossorial.io</a>
|
||||
</strong>
|
||||
</p>
|
||||
|
||||
Pangolin is a self-hosted tunneled reverse proxy server with identity and access control, designed to securely expose private resources on distributed networks. Acting as a central hub, it connects isolated networks — even those behind restrictive firewalls — through encrypted tunnels, enabling easy access to remote services without opening ports.
|
||||
|
||||
<img src="public/screenshots/hero.png" alt="Preview"/>
|
||||
|
||||
_Resources page of Pangolin dashboard (dark mode) showing multiple resources available to connect._
|
||||

|
||||
|
||||
## Key Features
|
||||
|
||||
### Reverse Proxy Through WireGuard Tunnel
|
||||
|
||||
- Expose private resources on your network **without opening ports** (firewall punching).
|
||||
- Secure and easy to configure site-to-site connectivity via a custom **user space WireGuard client**, [Newt](https://github.com/fosrl/newt).
|
||||
- Secure and easy to configure private connectivity via a custom **user space WireGuard client**, [Newt](https://github.com/fosrl/newt).
|
||||
- Built-in support for any WireGuard client.
|
||||
- Automated **SSL certificates** (https) via [LetsEncrypt](https://letsencrypt.org/).
|
||||
- Support for HTTP/HTTPS and **raw TCP/UDP services**.
|
||||
- Load balancing.
|
||||
- Extend functionality with existing [Traefik](https://github.com/traefik/traefik) plugins, such as [CrowdSec](https://plugins.traefik.io/plugins/6335346ca4caa9ddeffda116/crowdsec-bouncer-traefik-plugin) and [Geoblock](https://github.com/PascalMinder/geoblock).
|
||||
- **Automatically install and configure Crowdsec via Pangolin's installer script.**
|
||||
- Attach as many sites to the central server as you wish.
|
||||
|
||||
### Identity & Access Management
|
||||
|
||||
|
@ -61,89 +74,73 @@ _Resources page of Pangolin dashboard (dark mode) showing multiple resources ava
|
|||
- **Temporary, self-destructing share links.**
|
||||
- Resource specific pin codes.
|
||||
- Resource specific passwords.
|
||||
- Passkeys
|
||||
- External identity provider (IdP) support with OAuth2/OIDC, such as Authentik, Keycloak, Okta, and others.
|
||||
- Auto-provision users and roles from your IdP.
|
||||
|
||||
### Simple Dashboard UI
|
||||
<img src="public/auth-diagram1.png" alt="Auth and diagram"/>
|
||||
|
||||
- Manage sites, users, and roles with a clean and intuitive UI.
|
||||
- Monitor site usage and connectivity.
|
||||
- Light and dark mode options.
|
||||
- Mobile friendly.
|
||||
## Use Cases
|
||||
|
||||
### Easy Deployment
|
||||
### Manage Access to Internal Apps
|
||||
|
||||
- Run on any cloud provider or on-premises.
|
||||
- **Docker Compose based setup** for simplified deployment.
|
||||
- Future-proof installation script for streamlined setup and feature additions.
|
||||
- Use any WireGuard client to connect, or use **Newt, our custom user space client** for the best experience.
|
||||
- Use the API to create custom integrations and scripts.
|
||||
- Fine-grained access control to the API via scoped API keys.
|
||||
- Comprehensive Swagger documentation for the API.
|
||||
- Grant users access to your apps from anywhere using just a web browser. No client software required.
|
||||
|
||||
### Modular Design
|
||||
### Developers and DevOps
|
||||
|
||||
- Extend functionality with existing [Traefik](https://github.com/traefik/traefik) plugins, such as [CrowdSec](https://plugins.traefik.io/plugins/6335346ca4caa9ddeffda116/crowdsec-bouncer-traefik-plugin) and [Geoblock](github.com/PascalMinder/geoblock).
|
||||
- **Automatically install and configure Crowdsec via Pangolin's installer script.**
|
||||
- Attach as many sites to the central server as you wish.
|
||||
- Expose and test internal tools and dashboards like **Grafana**. Bring localhost or private IPs online for easy access.
|
||||
|
||||
<img src="public/screenshots/collage.png" alt="Collage"/>
|
||||
### Secure API Gateway
|
||||
|
||||
## Deployment and Usage Example
|
||||
- One application load balancer across multiple clouds and on-premises.
|
||||
|
||||
1. **Deploy the Central Server**:
|
||||
### IoT and Edge Devices
|
||||
|
||||
- Deploy the Docker Compose stack onto a VPS hosted on a cloud platform like RackNerd, Amazon EC2, DigitalOcean Droplet, or similar. There are many cheap VPS hosting options available to suit your needs.
|
||||
- Easily expose **IoT devices**, **edge servers**, or **Raspberry Pi** to the internet for field equipment monitoring.
|
||||
|
||||
> [!TIP]
|
||||
> Many of our users have had a great experience with [RackNerd](https://my.racknerd.com/aff.php?aff=13788). Depending on promotions, you can likely get a **VPS with 1 vCPU, 1GB RAM, and ~20GB SSD for just around $12/year**. That's a great deal!
|
||||
> We are part of the [RackNerd](https://my.racknerd.com/aff.php?aff=13788) affiliate program, so if you purchase through [our link](https://my.racknerd.com/aff.php?aff=13788), we receive a small commission which helps us maintain the project and keep it free for everyone.
|
||||
<img src="public/screenshots/sites.png" alt="Sites"/>
|
||||
|
||||
2. **Domain Configuration**:
|
||||
## Deployment Options
|
||||
|
||||
- Point your domain name to the VPS and configure Pangolin with your preferred settings.
|
||||
### Fully Self Hosted
|
||||
|
||||
3. **Connect Private Sites**:
|
||||
Host the full application on your own server or on the cloud with a VPS. Take a look at the [documentation](https://docs.fossorial.io/Getting%20Started/quick-install) to get started.
|
||||
|
||||
- Install Newt or use another WireGuard client on private sites.
|
||||
- Automatically establish a connection from these sites to the central server.
|
||||
> Many of our users have had a great experience with [RackNerd](https://my.racknerd.com/aff.php?aff=13788). Depending on promotions, you can get a [**VPS with 1 vCPU, 1GB RAM, and ~20GB SSD for just around $12/year**](https://my.racknerd.com/aff.php?aff=13788&pid=912). That's a great deal!
|
||||
|
||||
4. **Expose Resources**:
|
||||
### Pangolin Cloud
|
||||
|
||||
- Add resources to the central server and configure access control rules.
|
||||
- Access these resources securely from anywhere.
|
||||
Easy to use with simple [pay as you go pricing](https://digpangolin.com/pricing). [Check it out here](https://pangolin.fossorial.io/auth/signup).
|
||||
|
||||
**Use Case Example - Bypassing Port Restrictions in Home Lab**:
|
||||
Imagine private sites where the ISP restricts port forwarding. By connecting these sites to Pangolin via WireGuard, you can securely expose HTTP and HTTPS resources on the private network without any networking complexity.
|
||||
- Everything you get with self hosted Pangolin, but fully managed for you.
|
||||
|
||||
**Use Case Example - Deploying Services For Your Business**:
|
||||
You can use Pangolin as an easy way to expose your business applications to your users behind a safe authentication portal you can integrate into your IdP solution. Expose resources on prem and on the cloud.
|
||||
### Hybrid & High Availability
|
||||
|
||||
**Use Case Example - IoT Networks**:
|
||||
IoT networks are often fragmented and difficult to manage. By deploying Pangolin on a central server, you can connect all your IoT sites via Newt or another WireGuard client. This creates a simple, secure, and centralized way to access IoT resources without the need for intricate networking setups.
|
||||
Managed control plane, your infrastructure
|
||||
|
||||
## Similar Projects and Inspirations
|
||||
- We manage database and control plane.
|
||||
- You self-host lightweight exit-node.
|
||||
- Traffic flows through your infra.
|
||||
- We coordinate failover between your nodes or to Cloud when things go bad.
|
||||
|
||||
**Cloudflare Tunnels**:
|
||||
A similar approach to proxying private resources securely, but Pangolin is a self-hosted alternative, giving you full control over your infrastructure.
|
||||
If interested, [contact us](mailto:numbat@fossorial.io).
|
||||
|
||||
**Authelia**:
|
||||
This inspired Pangolin’s centralized authentication system for proxies, enabling robust user and role management.
|
||||
### Full Enterprise On-Premises
|
||||
|
||||
[Contact us](mailto:numbat@fossorial.io) for a full distributed and enterprise deployments on your infrastructure controlled by your team.
|
||||
|
||||
## Project Development / Roadmap
|
||||
|
||||
> [!NOTE]
|
||||
> Pangolin is under heavy development. The roadmap is subject to change as we fix bugs, add new features, and make improvements.
|
||||
|
||||
View the [project board](https://github.com/orgs/fosrl/projects/1) for more detailed info.
|
||||
We want to hear your feature requests! Add them to the [discussion board](https://github.com/orgs/fosrl/discussions/categories/feature-requests).
|
||||
|
||||
## Licensing
|
||||
|
||||
Pangolin is dual licensed under the AGPL-3 and the Fossorial Commercial license. Please see the [LICENSE](./LICENSE) file in the repository for details. For inquiries about commercial licensing, please contact us at [numbat@fossorial.io](mailto:numbat@fossorial.io).
|
||||
Pangolin is dual licensed under the AGPL-3 and the Fossorial Commercial license. For inquiries about commercial licensing, please contact us at [numbat@fossorial.io](mailto:numbat@fossorial.io).
|
||||
|
||||
## Contributions
|
||||
|
||||
Looking for something to contribute? Take a look at issues marked with [help wanted](https://github.com/fosrl/pangolin/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22help%20wanted%22).
|
||||
|
||||
Please see [CONTRIBUTING](./CONTRIBUTING.md) in the repository for guidelines and best practices.
|
||||
|
||||
Please post bug reports and other functional issues in the [Issues](https://github.com/fosrl/pangolin/issues) section of the repository.
|
||||
For all feature requests, or other ideas, please use the [Discussions](https://github.com/orgs/fosrl/discussions) section.
|
||||
|
|
22
bruno/Clients/createClient.bru
Normal file
|
@ -0,0 +1,22 @@
|
|||
meta {
|
||||
name: createClient
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
put {
|
||||
url: http://localhost:3000/api/v1/site/1/client
|
||||
body: json
|
||||
auth: none
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"siteId": 1,
|
||||
"name": "test",
|
||||
"type": "olm",
|
||||
"subnet": "100.90.129.4/30",
|
||||
"olmId": "029yzunhx6nh3y5",
|
||||
"secret": "l0ymp075y3d4rccb25l6sqpgar52k09etunui970qq5gj7x6"
|
||||
}
|
||||
}
|
11
bruno/Clients/pickClientDefaults.bru
Normal file
|
@ -0,0 +1,11 @@
|
|||
meta {
|
||||
name: pickClientDefaults
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
get {
|
||||
url: http://localhost:3000/api/v1/site/1/pick-client-defaults
|
||||
body: none
|
||||
auth: none
|
||||
}
|
141
cli/commands/setAdminCredentials.ts
Normal file
|
@ -0,0 +1,141 @@
|
|||
import { CommandModule } from "yargs";
|
||||
import { hashPassword, verifyPassword } from "@server/auth/password";
|
||||
import { db, resourceSessions, sessions } from "@server/db";
|
||||
import { users } from "@server/db";
|
||||
import { eq, inArray } from "drizzle-orm";
|
||||
import moment from "moment";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { passwordSchema } from "@server/auth/passwordSchema";
|
||||
import { UserType } from "@server/types/UserTypes";
|
||||
import { generateRandomString, RandomReader } from "@oslojs/crypto/random";
|
||||
|
||||
type SetAdminCredentialsArgs = {
|
||||
email: string;
|
||||
password: string;
|
||||
};
|
||||
|
||||
export const setAdminCredentials: CommandModule<{}, SetAdminCredentialsArgs> = {
|
||||
command: "set-admin-credentials",
|
||||
describe: "Set the server admin credentials",
|
||||
builder: (yargs) => {
|
||||
return yargs
|
||||
.option("email", {
|
||||
type: "string",
|
||||
demandOption: true,
|
||||
describe: "Admin email address"
|
||||
})
|
||||
.option("password", {
|
||||
type: "string",
|
||||
demandOption: true,
|
||||
describe: "Admin password"
|
||||
});
|
||||
},
|
||||
handler: async (argv: { email: string; password: string }) => {
|
||||
try {
|
||||
const { email, password } = argv;
|
||||
|
||||
const parsed = passwordSchema.safeParse(password);
|
||||
|
||||
if (!parsed.success) {
|
||||
throw Error(
|
||||
`Invalid server admin password: ${fromError(parsed.error).toString()}`
|
||||
);
|
||||
}
|
||||
|
||||
const passwordHash = await hashPassword(password);
|
||||
|
||||
await db.transaction(async (trx) => {
|
||||
try {
|
||||
const [existing] = await trx
|
||||
.select()
|
||||
.from(users)
|
||||
.where(eq(users.serverAdmin, true));
|
||||
|
||||
if (existing) {
|
||||
const passwordChanged = !(await verifyPassword(
|
||||
password,
|
||||
existing.passwordHash!
|
||||
));
|
||||
|
||||
if (passwordChanged) {
|
||||
await trx
|
||||
.update(users)
|
||||
.set({ passwordHash })
|
||||
.where(eq(users.userId, existing.userId));
|
||||
|
||||
await invalidateAllSessions(existing.userId);
|
||||
console.log("Server admin password updated");
|
||||
}
|
||||
|
||||
if (existing.email !== email) {
|
||||
await trx
|
||||
.update(users)
|
||||
.set({ email, username: email })
|
||||
.where(eq(users.userId, existing.userId));
|
||||
|
||||
console.log("Server admin email updated");
|
||||
}
|
||||
} else {
|
||||
const userId = generateId(15);
|
||||
|
||||
await trx.update(users).set({ serverAdmin: false });
|
||||
|
||||
await db.insert(users).values({
|
||||
userId: userId,
|
||||
email: email,
|
||||
type: UserType.Internal,
|
||||
username: email,
|
||||
passwordHash,
|
||||
dateCreated: moment().toISOString(),
|
||||
serverAdmin: true,
|
||||
emailVerified: true
|
||||
});
|
||||
|
||||
console.log("Server admin created");
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Failed to set admin credentials", e);
|
||||
trx.rollback();
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
console.log("Admin credentials updated successfully");
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export async function invalidateAllSessions(userId: string): Promise<void> {
|
||||
try {
|
||||
await db.transaction(async (trx) => {
|
||||
const userSessions = await trx
|
||||
.select()
|
||||
.from(sessions)
|
||||
.where(eq(sessions.userId, userId));
|
||||
await trx.delete(resourceSessions).where(
|
||||
inArray(
|
||||
resourceSessions.userSessionId,
|
||||
userSessions.map((s) => s.sessionId)
|
||||
)
|
||||
);
|
||||
await trx.delete(sessions).where(eq(sessions.userId, userId));
|
||||
});
|
||||
} catch (e) {
|
||||
console.log("Failed to all invalidate user sessions", e);
|
||||
}
|
||||
}
|
||||
|
||||
const random: RandomReader = {
|
||||
read(bytes: Uint8Array): void {
|
||||
crypto.getRandomValues(bytes);
|
||||
}
|
||||
};
|
||||
|
||||
export function generateId(length: number): string {
|
||||
const alphabet = "abcdefghijklmnopqrstuvwxyz0123456789";
|
||||
return generateRandomString(random, alphabet, length);
|
||||
}
|
11
cli/index.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
import yargs from "yargs";
|
||||
import { hideBin } from "yargs/helpers";
|
||||
import { setAdminCredentials } from "@cli/commands/setAdminCredentials";
|
||||
|
||||
yargs(hideBin(process.argv))
|
||||
.scriptName("pangctl")
|
||||
.command(setAdminCredentials)
|
||||
.demandCommand()
|
||||
.help().argv;
|
3
cli/wrapper.sh
Normal file
|
@ -0,0 +1,3 @@
|
|||
#!/bin/sh
|
||||
cd /app/
|
||||
./dist/cli.mjs "$@"
|
|
@ -41,14 +41,8 @@ rate_limits:
|
|||
window_minutes: 1
|
||||
max_requests: 500
|
||||
|
||||
users:
|
||||
server_admin:
|
||||
email: "admin@example.com"
|
||||
password: "Password123!"
|
||||
|
||||
flags:
|
||||
require_email_verification: false
|
||||
disable_signup_without_invite: true
|
||||
disable_user_create_org: true
|
||||
allow_raw_resources: true
|
||||
allow_base_domain_resources: true
|
||||
|
|
BIN
config/db/db.sqlite.bak
Normal file
3
crowdin.yml
Normal file
|
@ -0,0 +1,3 @@
|
|||
files:
|
||||
- source: /messages/en-US.json
|
||||
translation: /messages/%locale%.json
|
|
@ -31,11 +31,12 @@ services:
|
|||
- SYS_MODULE
|
||||
ports:
|
||||
- 51820:51820/udp
|
||||
- 21820:21820/udp
|
||||
- 443:443 # Port for traefik because of the network_mode
|
||||
- 80:80 # Port for traefik because of the network_mode
|
||||
|
||||
traefik:
|
||||
image: traefik:v3.3.3
|
||||
image: traefik:v3.4.0
|
||||
container_name: traefik
|
||||
restart: unless-stopped
|
||||
network_mode: service:gerbil # Ports appear on the gerbil service
|
||||
|
@ -51,4 +52,5 @@ services:
|
|||
networks:
|
||||
default:
|
||||
driver: bridge
|
||||
name: pangolin
|
||||
name: pangolin
|
||||
enable_ipv6: true
|
12
docker-compose.pg.yml
Normal file
|
@ -0,0 +1,12 @@
|
|||
services:
|
||||
# PostgreSQL Service
|
||||
db:
|
||||
image: postgres:17 # Use the PostgreSQL 17 image
|
||||
container_name: dev_postgres # Name your PostgreSQL container
|
||||
environment:
|
||||
POSTGRES_DB: postgres # Default database name
|
||||
POSTGRES_USER: postgres # Default user
|
||||
POSTGRES_PASSWORD: password # Default password (change for production!)
|
||||
ports:
|
||||
- "5432:5432" # Map host port 5432 to container port 5432
|
||||
restart: no
|
29
docker-compose.yml
Normal file
|
@ -0,0 +1,29 @@
|
|||
services:
|
||||
# Development application service
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.dev
|
||||
container_name: dev_pangolin
|
||||
ports:
|
||||
- "3000:3000"
|
||||
- "3001:3001"
|
||||
- "3002:3002"
|
||||
environment:
|
||||
- NODE_ENV=development
|
||||
- ENVIRONMENT=dev
|
||||
- DB_TYPE=pg
|
||||
volumes:
|
||||
# Mount source code for hot reload
|
||||
- ./src:/app/src
|
||||
- ./server:/app/server
|
||||
- ./public:/app/public
|
||||
- ./messages:/app/messages
|
||||
- ./components.json:/app/components.json
|
||||
- ./next.config.mjs:/app/next.config.mjs
|
||||
- ./tsconfig.json:/app/tsconfig.json
|
||||
- ./tailwind.config.js:/app/tailwind.config.js
|
||||
- ./postcss.config.mjs:/app/postcss.config.mjs
|
||||
- ./eslint.config.js:/app/eslint.config.js
|
||||
- ./config:/app/config
|
||||
restart: no
|
12
drizzle.pg.config.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { defineConfig } from "drizzle-kit";
|
||||
import path from "path";
|
||||
|
||||
export default defineConfig({
|
||||
dialect: "postgresql",
|
||||
schema: [path.join("server", "db", "pg", "schema.ts")],
|
||||
out: path.join("server", "migrations"),
|
||||
verbose: true,
|
||||
dbCredentials: {
|
||||
url: process.env.DATABASE_URL as string
|
||||
}
|
||||
});
|
|
@ -4,7 +4,7 @@ import path from "path";
|
|||
|
||||
export default defineConfig({
|
||||
dialect: "sqlite",
|
||||
schema: path.join("server", "db", "schemas"),
|
||||
schema: path.join("server", "db", "sqlite", "schema.ts"),
|
||||
out: path.join("server", "migrations"),
|
||||
verbose: true,
|
||||
dbCredentials: {
|
|
@ -1,9 +1,19 @@
|
|||
// eslint.config.js
|
||||
export default [
|
||||
{
|
||||
rules: {
|
||||
semi: "error",
|
||||
"prefer-const": "error"
|
||||
}
|
||||
import tseslint from 'typescript-eslint';
|
||||
|
||||
export default tseslint.config({
|
||||
files: ["**/*.{ts,tsx,js,jsx}"],
|
||||
languageOptions: {
|
||||
parser: tseslint.parser,
|
||||
parserOptions: {
|
||||
ecmaVersion: "latest",
|
||||
sourceType: "module",
|
||||
ecmaFeatures: {
|
||||
jsx: true
|
||||
}
|
||||
}
|
||||
];
|
||||
},
|
||||
rules: {
|
||||
"semi": "error",
|
||||
"prefer-const": "warn"
|
||||
}
|
||||
});
|
|
@ -1,4 +1,5 @@
|
|||
all: update-versions go-build-release put-back
|
||||
dev-all: dev-update-versions dev-build dev-clean
|
||||
|
||||
go-build-release:
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o bin/installer_linux_amd64
|
||||
|
@ -11,6 +12,12 @@ clean:
|
|||
update-versions:
|
||||
@echo "Fetching latest versions..."
|
||||
cp main.go main.go.bak && \
|
||||
$(MAKE) dev-update-versions
|
||||
|
||||
put-back:
|
||||
mv main.go.bak main.go
|
||||
|
||||
dev-update-versions:
|
||||
PANGOLIN_VERSION=$$(curl -s https://api.github.com/repos/fosrl/pangolin/tags | jq -r '.[0].name') && \
|
||||
GERBIL_VERSION=$$(curl -s https://api.github.com/repos/fosrl/gerbil/tags | jq -r '.[0].name') && \
|
||||
BADGER_VERSION=$$(curl -s https://api.github.com/repos/fosrl/badger/tags | jq -r '.[0].name') && \
|
||||
|
@ -20,5 +27,11 @@ update-versions:
|
|||
sed -i "s/config.BadgerVersion = \".*\"/config.BadgerVersion = \"$$BADGER_VERSION\"/" main.go && \
|
||||
echo "Updated main.go with latest versions"
|
||||
|
||||
put-back:
|
||||
mv main.go.bak main.go
|
||||
dev-build: go-build-release
|
||||
|
||||
dev-clean:
|
||||
@echo "Restoring version values ..."
|
||||
sed -i "s/config.PangolinVersion = \".*\"/config.PangolinVersion = \"replaceme\"/" main.go && \
|
||||
sed -i "s/config.GerbilVersion = \".*\"/config.GerbilVersion = \"replaceme\"/" main.go && \
|
||||
sed -i "s/config.BadgerVersion = \".*\"/config.BadgerVersion = \"replaceme\"/" main.go
|
||||
@echo "Restored version strings in main.go"
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
app:
|
||||
dashboard_url: "https://{{.DashboardDomain}}"
|
||||
log_level: "info"
|
||||
save_logs: false
|
||||
|
||||
domains:
|
||||
domain1:
|
||||
|
@ -12,40 +11,17 @@ domains:
|
|||
cert_resolver: "letsencrypt"
|
||||
|
||||
server:
|
||||
external_port: 3000
|
||||
internal_port: 3001
|
||||
next_port: 3002
|
||||
internal_hostname: "pangolin"
|
||||
session_cookie_name: "p_session_token"
|
||||
resource_access_token_param: "p_token"
|
||||
resource_access_token_headers:
|
||||
id: "P-Access-Token-Id"
|
||||
token: "P-Access-Token"
|
||||
resource_session_request_param: "p_session_request"
|
||||
secret: {{.Secret}}
|
||||
secret: "{{.Secret}}"
|
||||
cors:
|
||||
origins: ["https://{{.DashboardDomain}}"]
|
||||
methods: ["GET", "POST", "PUT", "DELETE", "PATCH"]
|
||||
headers: ["X-CSRF-Token", "Content-Type"]
|
||||
allowed_headers: ["X-CSRF-Token", "Content-Type"]
|
||||
credentials: false
|
||||
|
||||
traefik:
|
||||
cert_resolver: "letsencrypt"
|
||||
http_entrypoint: "web"
|
||||
https_entrypoint: "websecure"
|
||||
|
||||
gerbil:
|
||||
start_port: 51820
|
||||
base_endpoint: "{{.DashboardDomain}}"
|
||||
use_subdomain: false
|
||||
block_size: 24
|
||||
site_block_size: 30
|
||||
subnet_group: 100.89.137.0/20
|
||||
|
||||
rate_limits:
|
||||
global:
|
||||
window_minutes: 1
|
||||
max_requests: 500
|
||||
{{if .EnableEmail}}
|
||||
email:
|
||||
smtp_host: "{{.EmailSMTPHost}}"
|
||||
|
@ -54,14 +30,9 @@ email:
|
|||
smtp_pass: "{{.EmailSMTPPass}}"
|
||||
no_reply: "{{.EmailNoReply}}"
|
||||
{{end}}
|
||||
users:
|
||||
server_admin:
|
||||
email: "{{.AdminUserEmail}}"
|
||||
password: "{{.AdminUserPassword}}"
|
||||
|
||||
flags:
|
||||
require_email_verification: {{.EnableEmail}}
|
||||
disable_signup_without_invite: {{.DisableSignupWithoutInvite}}
|
||||
disable_user_create_org: {{.DisableUserCreateOrg}}
|
||||
disable_signup_without_invite: true
|
||||
disable_user_create_org: false
|
||||
allow_raw_resources: true
|
||||
allow_base_domain_resources: true
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
services:
|
||||
crowdsec:
|
||||
image: crowdsecurity/crowdsec:latest
|
||||
image: docker.io/crowdsecurity/crowdsec:latest
|
||||
container_name: crowdsec
|
||||
environment:
|
||||
GID: "1000"
|
||||
|
|
|
@ -22,4 +22,4 @@ filters:
|
|||
decisions:
|
||||
- type: ban
|
||||
duration: 4h
|
||||
on_success: break
|
||||
on_success: break
|
||||
|
|
|
@ -21,6 +21,10 @@ experimental:
|
|||
log:
|
||||
level: "INFO"
|
||||
format: "json" # Log format changed to json for better parsing
|
||||
maxSize: 100
|
||||
maxBackups: 3
|
||||
maxAge: 3
|
||||
compress: true
|
||||
|
||||
accessLog: # We enable access logs as json
|
||||
filePath: "/var/log/traefik/access.log"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
name: pangolin
|
||||
services:
|
||||
pangolin:
|
||||
image: fosrl/pangolin:{{.PangolinVersion}}
|
||||
image: docker.io/fosrl/pangolin:{{.PangolinVersion}}
|
||||
container_name: pangolin
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
|
@ -13,7 +13,7 @@ services:
|
|||
retries: 15
|
||||
{{if .InstallGerbil}}
|
||||
gerbil:
|
||||
image: fosrl/gerbil:{{.GerbilVersion}}
|
||||
image: docker.io/fosrl/gerbil:{{.GerbilVersion}}
|
||||
container_name: gerbil
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
|
@ -31,11 +31,12 @@ services:
|
|||
- SYS_MODULE
|
||||
ports:
|
||||
- 51820:51820/udp
|
||||
- 21820:21820/udp
|
||||
- 443:443 # Port for traefik because of the network_mode
|
||||
- 80:80 # Port for traefik because of the network_mode
|
||||
{{end}}
|
||||
traefik:
|
||||
image: traefik:v3.3.6
|
||||
image: docker.io/traefik:v3.4.1
|
||||
container_name: traefik
|
||||
restart: unless-stopped
|
||||
{{if .InstallGerbil}}
|
||||
|
@ -58,4 +59,5 @@ services:
|
|||
networks:
|
||||
default:
|
||||
driver: bridge
|
||||
name: pangolin
|
||||
name: pangolin
|
||||
enable_ipv6: true
|
||||
|
|
|
@ -18,6 +18,10 @@ experimental:
|
|||
log:
|
||||
level: "INFO"
|
||||
format: "common"
|
||||
maxSize: 100
|
||||
maxBackups: 3
|
||||
maxAge: 3
|
||||
compress: true
|
||||
|
||||
certificatesResolvers:
|
||||
letsencrypt:
|
||||
|
|
|
@ -13,7 +13,7 @@ import (
|
|||
|
||||
func installCrowdsec(config Config) error {
|
||||
|
||||
if err := stopContainers(); err != nil {
|
||||
if err := stopContainers(config.InstallationContainerType); err != nil {
|
||||
return fmt.Errorf("failed to stop containers: %v", err)
|
||||
}
|
||||
|
||||
|
@ -72,12 +72,12 @@ func installCrowdsec(config Config) error {
|
|||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := startContainers(); err != nil {
|
||||
if err := startContainers(config.InstallationContainerType); err != nil {
|
||||
return fmt.Errorf("failed to start containers: %v", err)
|
||||
}
|
||||
|
||||
// get API key
|
||||
apiKey, err := GetCrowdSecAPIKey()
|
||||
apiKey, err := GetCrowdSecAPIKey(config.InstallationContainerType)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API key: %v", err)
|
||||
}
|
||||
|
@ -87,7 +87,7 @@ func installCrowdsec(config Config) error {
|
|||
return fmt.Errorf("failed to replace bouncer key: %v", err)
|
||||
}
|
||||
|
||||
if err := restartContainer("traefik"); err != nil {
|
||||
if err := restartContainer("traefik", config.InstallationContainerType); err != nil {
|
||||
return fmt.Errorf("failed to restart containers: %v", err)
|
||||
}
|
||||
|
||||
|
@ -110,9 +110,9 @@ func checkIsCrowdsecInstalledInCompose() bool {
|
|||
return bytes.Contains(content, []byte("crowdsec:"))
|
||||
}
|
||||
|
||||
func GetCrowdSecAPIKey() (string, error) {
|
||||
func GetCrowdSecAPIKey(containerType SupportedContainer) (string, error) {
|
||||
// First, ensure the container is running
|
||||
if err := waitForContainer("crowdsec"); err != nil {
|
||||
if err := waitForContainer("crowdsec", containerType); err != nil {
|
||||
return "", fmt.Errorf("waiting for container: %w", err)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
docker
|
||||
example.com
|
||||
pangolin.example.com
|
||||
admin@example.com
|
||||
|
|
446
install/main.go
|
@ -7,16 +7,17 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"math/rand"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"text/template"
|
||||
"time"
|
||||
"unicode"
|
||||
"math/rand"
|
||||
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
@ -32,47 +33,123 @@ func loadVersions(config *Config) {
|
|||
var configFiles embed.FS
|
||||
|
||||
type Config struct {
|
||||
PangolinVersion string
|
||||
GerbilVersion string
|
||||
BadgerVersion string
|
||||
BaseDomain string
|
||||
DashboardDomain string
|
||||
LetsEncryptEmail string
|
||||
AdminUserEmail string
|
||||
AdminUserPassword string
|
||||
DisableSignupWithoutInvite bool
|
||||
DisableUserCreateOrg bool
|
||||
EnableEmail bool
|
||||
EmailSMTPHost string
|
||||
EmailSMTPPort int
|
||||
EmailSMTPUser string
|
||||
EmailSMTPPass string
|
||||
EmailNoReply string
|
||||
InstallGerbil bool
|
||||
TraefikBouncerKey string
|
||||
DoCrowdsecInstall bool
|
||||
Secret string
|
||||
InstallationContainerType SupportedContainer
|
||||
PangolinVersion string
|
||||
GerbilVersion string
|
||||
BadgerVersion string
|
||||
BaseDomain string
|
||||
DashboardDomain string
|
||||
LetsEncryptEmail string
|
||||
EnableEmail bool
|
||||
EmailSMTPHost string
|
||||
EmailSMTPPort int
|
||||
EmailSMTPUser string
|
||||
EmailSMTPPass string
|
||||
EmailNoReply string
|
||||
InstallGerbil bool
|
||||
TraefikBouncerKey string
|
||||
DoCrowdsecInstall bool
|
||||
Secret string
|
||||
}
|
||||
|
||||
func main() {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
type SupportedContainer string
|
||||
|
||||
// check if the user is root
|
||||
if os.Geteuid() != 0 {
|
||||
fmt.Println("This script must be run as root")
|
||||
const (
|
||||
Docker SupportedContainer = "docker"
|
||||
Podman SupportedContainer = "podman"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
// print a banner about prerequisites - opening port 80, 443, 51820, and 21820 on the VPS and firewall and pointing your domain to the VPS IP with a records. Docs are at http://localhost:3000/Getting%20Started/dns-networking
|
||||
|
||||
fmt.Println("Welcome to the Pangolin installer!")
|
||||
fmt.Println("This installer will help you set up Pangolin on your server.")
|
||||
fmt.Println("")
|
||||
fmt.Println("Please make sure you have the following prerequisites:")
|
||||
fmt.Println("- Open TCP ports 80 and 443 and UDP ports 51820 and 21820 on your VPS and firewall.")
|
||||
fmt.Println("- Point your domain to the VPS IP with A records.")
|
||||
fmt.Println("")
|
||||
fmt.Println("http://docs.fossorial.io/Getting%20Started/dns-networking")
|
||||
fmt.Println("")
|
||||
fmt.Println("Lets get started!")
|
||||
fmt.Println("")
|
||||
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
inputContainer := readString(reader, "Would you like to run Pangolin as Docker or Podman containers?", "docker")
|
||||
|
||||
chosenContainer := Docker
|
||||
if strings.EqualFold(inputContainer, "docker") {
|
||||
chosenContainer = Docker
|
||||
} else if strings.EqualFold(inputContainer, "podman") {
|
||||
chosenContainer = Podman
|
||||
} else {
|
||||
fmt.Printf("Unrecognized container type: %s. Valid options are 'docker' or 'podman'.\n", inputContainer)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if chosenContainer == Podman {
|
||||
if !isPodmanInstalled() {
|
||||
fmt.Println("Podman or podman-compose is not installed. Please install both manually. Automated installation will be available in a later release.")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := exec.Command("bash", "-c", "cat /etc/sysctl.conf | grep 'net.ipv4.ip_unprivileged_port_start='").Run(); err != nil {
|
||||
fmt.Println("Would you like to configure ports >= 80 as unprivileged ports? This enables podman containers to listen on low-range ports.")
|
||||
fmt.Println("Pangolin will experience startup issues if this is not configured, because it needs to listen on port 80/443 by default.")
|
||||
approved := readBool(reader, "The installer is about to execute \"echo 'net.ipv4.ip_unprivileged_port_start=80' >> /etc/sysctl.conf && sysctl -p\". Approve?", true)
|
||||
if approved {
|
||||
if os.Geteuid() != 0 {
|
||||
fmt.Println("You need to run the installer as root for such a configuration.")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Podman containers are not able to listen on privileged ports. The official recommendation is to
|
||||
// container low-range ports as unprivileged ports.
|
||||
// Linux only.
|
||||
|
||||
if err := run("bash", "-c", "echo 'net.ipv4.ip_unprivileged_port_start=80' >> /etc/sysctl.conf && sysctl -p"); err != nil {
|
||||
fmt.Sprintf("failed to configure unprivileged ports: %v.\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
} else {
|
||||
fmt.Println("You need to configure port forwarding or adjust the listening ports before running pangolin.")
|
||||
}
|
||||
} else {
|
||||
fmt.Println("Unprivileged ports have been configured.")
|
||||
}
|
||||
|
||||
} else if chosenContainer == Docker {
|
||||
// check if docker is not installed and the user is root
|
||||
if !isDockerInstalled() {
|
||||
if os.Geteuid() != 0 {
|
||||
fmt.Println("Docker is not installed. Please install Docker manually or run this installer as root.")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// check if the user is in the docker group (linux only)
|
||||
if !isUserInDockerGroup() {
|
||||
fmt.Println("You are not in the docker group.")
|
||||
fmt.Println("The installer will not be able to run docker commands without running it as root.")
|
||||
os.Exit(1)
|
||||
}
|
||||
} else {
|
||||
// This shouldn't happen unless there's a third container runtime.
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var config Config
|
||||
|
||||
config.InstallationContainerType = chosenContainer
|
||||
|
||||
// check if there is already a config file
|
||||
if _, err := os.Stat("config/config.yml"); err != nil {
|
||||
config = collectUserInput(reader)
|
||||
|
||||
|
||||
loadVersions(&config)
|
||||
config.DoCrowdsecInstall = false
|
||||
config.Secret = generateRandomSecretKey()
|
||||
|
||||
|
||||
if err := createConfigFiles(config); err != nil {
|
||||
fmt.Printf("Error creating config files: %v\n", err)
|
||||
os.Exit(1)
|
||||
|
@ -80,22 +157,44 @@ func main() {
|
|||
|
||||
moveFile("config/docker-compose.yml", "docker-compose.yml")
|
||||
|
||||
if !isDockerInstalled() && runtime.GOOS == "linux" {
|
||||
if !isDockerInstalled() && runtime.GOOS == "linux" && chosenContainer == Docker {
|
||||
if readBool(reader, "Docker is not installed. Would you like to install it?", true) {
|
||||
installDocker()
|
||||
// try to start docker service but ignore errors
|
||||
if err := startDockerService(); err != nil {
|
||||
fmt.Println("Error starting Docker service:", err)
|
||||
} else {
|
||||
fmt.Println("Docker service started successfully!")
|
||||
}
|
||||
// wait 10 seconds for docker to start checking if docker is running every 2 seconds
|
||||
fmt.Println("Waiting for Docker to start...")
|
||||
for i := 0; i < 5; i++ {
|
||||
if isDockerRunning() {
|
||||
fmt.Println("Docker is running!")
|
||||
break
|
||||
}
|
||||
fmt.Println("Docker is not running yet, waiting...")
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
if !isDockerRunning() {
|
||||
fmt.Println("Docker is still not running after 10 seconds. Please check the installation.")
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println("Docker installed successfully!")
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("\n=== Starting installation ===")
|
||||
|
||||
if isDockerInstalled() {
|
||||
if (isDockerInstalled() && chosenContainer == Docker) ||
|
||||
(isPodmanInstalled() && chosenContainer == Podman) {
|
||||
if readBool(reader, "Would you like to install and start the containers?", true) {
|
||||
if err := pullContainers(); err != nil {
|
||||
if err := pullContainers(chosenContainer); err != nil {
|
||||
fmt.Println("Error: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := startContainers(); err != nil {
|
||||
if err := startContainers(chosenContainer); err != nil {
|
||||
fmt.Println("Error: ", err)
|
||||
return
|
||||
}
|
||||
|
@ -110,6 +209,8 @@ func main() {
|
|||
// check if crowdsec is installed
|
||||
if readBool(reader, "Would you like to install CrowdSec?", false) {
|
||||
fmt.Println("This installer constitutes a minimal viable CrowdSec deployment. CrowdSec will add extra complexity to your Pangolin installation and may not work to the best of its abilities out of the box. Users are expected to implement configuration adjustments on their own to achieve the best security posture. Consult the CrowdSec documentation for detailed configuration instructions.")
|
||||
|
||||
// BUG: crowdsec installation will be skipped if the user chooses to install on the first installation.
|
||||
if readBool(reader, "Are you willing to manage CrowdSec?", false) {
|
||||
if config.DashboardDomain == "" {
|
||||
traefikConfig, err := ReadTraefikConfig("config/traefik/traefik_config.yml", "config/traefik/dynamic_config.yml")
|
||||
|
@ -139,6 +240,7 @@ func main() {
|
|||
}
|
||||
|
||||
fmt.Println("Installation complete!")
|
||||
fmt.Printf("\nTo complete the initial setup, please visit:\nhttps://%s/auth/initial-setup\n", config.DashboardDomain)
|
||||
}
|
||||
|
||||
func readString(reader *bufio.Reader, prompt string, defaultValue string) string {
|
||||
|
@ -204,44 +306,15 @@ func collectUserInput(reader *bufio.Reader) Config {
|
|||
config.LetsEncryptEmail = readString(reader, "Enter email for Let's Encrypt certificates", "")
|
||||
config.InstallGerbil = readBool(reader, "Do you want to use Gerbil to allow tunneled connections", true)
|
||||
|
||||
// Admin user configuration
|
||||
fmt.Println("\n=== Admin User Configuration ===")
|
||||
config.AdminUserEmail = readString(reader, "Enter admin user email", "admin@"+config.BaseDomain)
|
||||
for {
|
||||
pass1 := readPassword("Create admin user password", reader)
|
||||
pass2 := readPassword("Confirm admin user password", reader)
|
||||
|
||||
if pass1 != pass2 {
|
||||
fmt.Println("Passwords do not match")
|
||||
} else {
|
||||
config.AdminUserPassword = pass1
|
||||
if valid, message := validatePassword(config.AdminUserPassword); valid {
|
||||
break
|
||||
} else {
|
||||
fmt.Println("Invalid password:", message)
|
||||
fmt.Println("Password requirements:")
|
||||
fmt.Println("- At least one uppercase English letter")
|
||||
fmt.Println("- At least one lowercase English letter")
|
||||
fmt.Println("- At least one digit")
|
||||
fmt.Println("- At least one special character")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Security settings
|
||||
fmt.Println("\n=== Security Settings ===")
|
||||
config.DisableSignupWithoutInvite = readBool(reader, "Disable signup without invite", true)
|
||||
config.DisableUserCreateOrg = readBool(reader, "Disable users from creating organizations", false)
|
||||
|
||||
// Email configuration
|
||||
fmt.Println("\n=== Email Configuration ===")
|
||||
config.EnableEmail = readBool(reader, "Enable email functionality", false)
|
||||
config.EnableEmail = readBool(reader, "Enable email functionality (SMTP)", false)
|
||||
|
||||
if config.EnableEmail {
|
||||
config.EmailSMTPHost = readString(reader, "Enter SMTP host", "")
|
||||
config.EmailSMTPPort = readInt(reader, "Enter SMTP port (default 587)", 587)
|
||||
config.EmailSMTPUser = readString(reader, "Enter SMTP username", "")
|
||||
config.EmailSMTPPass = readString(reader, "Enter SMTP password", "")
|
||||
config.EmailSMTPPass = readString(reader, "Enter SMTP password", "") // Should this be readPassword?
|
||||
config.EmailNoReply = readString(reader, "Enter no-reply email address", "")
|
||||
}
|
||||
|
||||
|
@ -258,60 +331,10 @@ func collectUserInput(reader *bufio.Reader) Config {
|
|||
fmt.Println("Error: Let's Encrypt email is required")
|
||||
os.Exit(1)
|
||||
}
|
||||
if config.AdminUserEmail == "" || config.AdminUserPassword == "" {
|
||||
fmt.Println("Error: Admin user email and password are required")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
func validatePassword(password string) (bool, string) {
|
||||
if len(password) == 0 {
|
||||
return false, "Password cannot be empty"
|
||||
}
|
||||
|
||||
var (
|
||||
hasUpper bool
|
||||
hasLower bool
|
||||
hasDigit bool
|
||||
hasSpecial bool
|
||||
)
|
||||
|
||||
for _, char := range password {
|
||||
switch {
|
||||
case unicode.IsUpper(char):
|
||||
hasUpper = true
|
||||
case unicode.IsLower(char):
|
||||
hasLower = true
|
||||
case unicode.IsDigit(char):
|
||||
hasDigit = true
|
||||
case unicode.IsPunct(char) || unicode.IsSymbol(char):
|
||||
hasSpecial = true
|
||||
}
|
||||
}
|
||||
|
||||
var missing []string
|
||||
if !hasUpper {
|
||||
missing = append(missing, "an uppercase letter")
|
||||
}
|
||||
if !hasLower {
|
||||
missing = append(missing, "a lowercase letter")
|
||||
}
|
||||
if !hasDigit {
|
||||
missing = append(missing, "a digit")
|
||||
}
|
||||
if !hasSpecial {
|
||||
missing = append(missing, "a special character")
|
||||
}
|
||||
|
||||
if len(missing) > 0 {
|
||||
return false, fmt.Sprintf("Password must contain %s", strings.Join(missing, ", "))
|
||||
}
|
||||
|
||||
return true, ""
|
||||
}
|
||||
|
||||
func createConfigFiles(config Config) error {
|
||||
os.MkdirAll("config", 0755)
|
||||
os.MkdirAll("config/letsencrypt", 0755)
|
||||
|
@ -381,7 +404,6 @@ func createConfigFiles(config Config) error {
|
|||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("error walking config files: %v", err)
|
||||
}
|
||||
|
@ -438,11 +460,31 @@ func installDocker() error {
|
|||
apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
|
||||
`, dockerArch))
|
||||
case strings.Contains(osRelease, "ID=fedora"):
|
||||
installCmd = exec.Command("bash", "-c", `
|
||||
// Detect Fedora version to handle DNF 5 changes
|
||||
versionCmd := exec.Command("bash", "-c", "grep VERSION_ID /etc/os-release | cut -d'=' -f2 | tr -d '\"'")
|
||||
versionOutput, err := versionCmd.Output()
|
||||
var fedoraVersion int
|
||||
if err == nil {
|
||||
if v, parseErr := strconv.Atoi(strings.TrimSpace(string(versionOutput))); parseErr == nil {
|
||||
fedoraVersion = v
|
||||
}
|
||||
}
|
||||
|
||||
// Use appropriate DNF syntax based on version
|
||||
var repoCmd string
|
||||
if fedoraVersion >= 41 {
|
||||
// DNF 5 syntax for Fedora 41+
|
||||
repoCmd = "dnf config-manager addrepo --from-repofile=https://download.docker.com/linux/fedora/docker-ce.repo"
|
||||
} else {
|
||||
// DNF 4 syntax for Fedora < 41
|
||||
repoCmd = "dnf config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo"
|
||||
}
|
||||
|
||||
installCmd = exec.Command("bash", "-c", fmt.Sprintf(`
|
||||
dnf -y install dnf-plugins-core &&
|
||||
dnf config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo &&
|
||||
%s &&
|
||||
dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
|
||||
`)
|
||||
`, repoCmd))
|
||||
case strings.Contains(osRelease, "ID=opensuse") || strings.Contains(osRelease, "ID=\"opensuse-"):
|
||||
installCmd = exec.Command("bash", "-c", `
|
||||
zypper install -y docker docker-compose &&
|
||||
|
@ -466,13 +508,73 @@ func installDocker() error {
|
|||
default:
|
||||
return fmt.Errorf("unsupported Linux distribution")
|
||||
}
|
||||
|
||||
installCmd.Stdout = os.Stdout
|
||||
installCmd.Stderr = os.Stderr
|
||||
return installCmd.Run()
|
||||
}
|
||||
|
||||
func startDockerService() error {
|
||||
if runtime.GOOS == "linux" {
|
||||
cmd := exec.Command("systemctl", "enable", "--now", "docker")
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
return cmd.Run()
|
||||
} else if runtime.GOOS == "darwin" {
|
||||
// On macOS, Docker is usually started via the Docker Desktop application
|
||||
fmt.Println("Please start Docker Desktop manually on macOS.")
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("unsupported operating system for starting Docker service")
|
||||
}
|
||||
|
||||
func isDockerInstalled() bool {
|
||||
cmd := exec.Command("docker", "--version")
|
||||
return isContainerInstalled("docker")
|
||||
}
|
||||
|
||||
func isPodmanInstalled() bool {
|
||||
return isContainerInstalled("podman") && isContainerInstalled("podman-compose")
|
||||
}
|
||||
|
||||
func isContainerInstalled(container string) bool {
|
||||
cmd := exec.Command(container, "--version")
|
||||
if err := cmd.Run(); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func isUserInDockerGroup() bool {
|
||||
if runtime.GOOS == "darwin" {
|
||||
// Docker group is not applicable on macOS
|
||||
// So we assume that the user can run Docker commands
|
||||
return true
|
||||
}
|
||||
|
||||
if os.Geteuid() == 0 {
|
||||
return true // Root user can run Docker commands anyway
|
||||
}
|
||||
|
||||
// Check if the current user is in the docker group
|
||||
if dockerGroup, err := user.LookupGroup("docker"); err == nil {
|
||||
if currentUser, err := user.Current(); err == nil {
|
||||
if currentUserGroupIds, err := currentUser.GroupIds(); err == nil {
|
||||
for _, groupId := range currentUserGroupIds {
|
||||
if groupId == dockerGroup.Gid {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Eventually, if any of the checks fail, we assume the user cannot run Docker commands
|
||||
return false
|
||||
}
|
||||
|
||||
// isDockerRunning checks if the Docker daemon is running by using the `docker info` command.
|
||||
func isDockerRunning() bool {
|
||||
cmd := exec.Command("docker", "info")
|
||||
if err := cmd.Run(); err != nil {
|
||||
return false
|
||||
}
|
||||
|
@ -499,59 +601,105 @@ func executeDockerComposeCommandWithArgs(args ...string) error {
|
|||
return fmt.Errorf("neither 'docker compose' nor 'docker-compose' command is available")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if useNewStyle {
|
||||
cmd = exec.Command("docker", append([]string{"compose"}, args...)...)
|
||||
} else {
|
||||
cmd = exec.Command("docker-compose", args...)
|
||||
}
|
||||
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
return cmd.Run()
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
// pullContainers pulls the containers using the appropriate command.
|
||||
func pullContainers() error {
|
||||
func pullContainers(containerType SupportedContainer) error {
|
||||
fmt.Println("Pulling the container images...")
|
||||
if containerType == Podman {
|
||||
if err := run("podman-compose", "-f", "docker-compose.yml", "pull"); err != nil {
|
||||
return fmt.Errorf("failed to pull the containers: %v", err)
|
||||
}
|
||||
|
||||
if err := executeDockerComposeCommandWithArgs("-f", "docker-compose.yml", "pull", "--policy", "always"); err != nil {
|
||||
return fmt.Errorf("failed to pull the containers: %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
if containerType == Docker {
|
||||
if err := executeDockerComposeCommandWithArgs("-f", "docker-compose.yml", "pull", "--policy", "always"); err != nil {
|
||||
return fmt.Errorf("failed to pull the containers: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("Unsupported container type: %s", containerType)
|
||||
}
|
||||
|
||||
// startContainers starts the containers using the appropriate command.
|
||||
func startContainers() error {
|
||||
func startContainers(containerType SupportedContainer) error {
|
||||
fmt.Println("Starting containers...")
|
||||
if err := executeDockerComposeCommandWithArgs("-f", "docker-compose.yml", "up", "-d", "--force-recreate"); err != nil {
|
||||
return fmt.Errorf("failed to start containers: %v", err)
|
||||
|
||||
if containerType == Podman {
|
||||
if err := run("podman-compose", "-f", "docker-compose.yml", "up", "-d", "--force-recreate"); err != nil {
|
||||
return fmt.Errorf("failed start containers: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
if containerType == Docker {
|
||||
if err := executeDockerComposeCommandWithArgs("-f", "docker-compose.yml", "up", "-d", "--force-recreate"); err != nil {
|
||||
return fmt.Errorf("failed to start containers: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("Unsupported container type: %s", containerType)
|
||||
}
|
||||
|
||||
// stopContainers stops the containers using the appropriate command.
|
||||
func stopContainers() error {
|
||||
func stopContainers(containerType SupportedContainer) error {
|
||||
fmt.Println("Stopping containers...")
|
||||
|
||||
if err := executeDockerComposeCommandWithArgs("-f", "docker-compose.yml", "down"); err != nil {
|
||||
return fmt.Errorf("failed to stop containers: %v", err)
|
||||
if containerType == Podman {
|
||||
if err := run("podman-compose", "-f", "docker-compose.yml", "down"); err != nil {
|
||||
return fmt.Errorf("failed to stop containers: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
if containerType == Docker {
|
||||
if err := executeDockerComposeCommandWithArgs("-f", "docker-compose.yml", "down"); err != nil {
|
||||
return fmt.Errorf("failed to stop containers: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("Unsupported container type: %s", containerType)
|
||||
}
|
||||
|
||||
// restartContainer restarts a specific container using the appropriate command.
|
||||
func restartContainer(container string) error {
|
||||
func restartContainer(container string, containerType SupportedContainer) error {
|
||||
fmt.Println("Restarting containers...")
|
||||
|
||||
if err := executeDockerComposeCommandWithArgs("-f", "docker-compose.yml", "restart", container); err != nil {
|
||||
return fmt.Errorf("failed to stop the container \"%s\": %v", container, err)
|
||||
if containerType == Podman {
|
||||
if err := run("podman-compose", "-f", "docker-compose.yml", "restart"); err != nil {
|
||||
return fmt.Errorf("failed to stop the container \"%s\": %v", container, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
if containerType == Docker {
|
||||
if err := executeDockerComposeCommandWithArgs("-f", "docker-compose.yml", "restart", container); err != nil {
|
||||
return fmt.Errorf("failed to stop the container \"%s\": %v", container, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("Unsupported container type: %s", containerType)
|
||||
}
|
||||
|
||||
func copyFile(src, dst string) error {
|
||||
|
@ -579,13 +727,13 @@ func moveFile(src, dst string) error {
|
|||
return os.Remove(src)
|
||||
}
|
||||
|
||||
func waitForContainer(containerName string) error {
|
||||
func waitForContainer(containerName string, containerType SupportedContainer) error {
|
||||
maxAttempts := 30
|
||||
retryInterval := time.Second * 2
|
||||
|
||||
for attempt := 0; attempt < maxAttempts; attempt++ {
|
||||
// Check if container is running
|
||||
cmd := exec.Command("docker", "container", "inspect", "-f", "{{.State.Running}}", containerName)
|
||||
cmd := exec.Command(string(containerType), "container", "inspect", "-f", "{{.State.Running}}", containerName)
|
||||
var out bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
|
||||
|
@ -619,4 +767,12 @@ func generateRandomSecretKey() string {
|
|||
b[i] = charset[seededRand.Intn(len(charset))]
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
}
|
||||
|
||||
// Run external commands with stdio/stderr attached.
|
||||
func run(name string, args ...string) error {
|
||||
cmd := exec.Command(name, args...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
return cmd.Run()
|
||||
}
|
||||
|
|
|
@ -1,287 +0,0 @@
|
|||
## Authentication Site
|
||||
|
||||
| EN | DE | Notes |
|
||||
| -------------------------------------------------------- | ---------------------------------------------------------------------------------- | ---------- |
|
||||
| Powered by [Pangolin](https://github.com/fosrl/pangolin) | Bereitgestellt von [Pangolin](https://github.com/fosrl/pangolin) | |
|
||||
| Authentication Required | Authentifizierung erforderlich | |
|
||||
| Choose your preferred method to access {resource} | Wählen Sie Ihre bevorzugte Methode, um auf {resource} zuzugreifen | |
|
||||
| PIN | PIN | |
|
||||
| User | Benutzer | |
|
||||
| 6-digit PIN Code | 6-stelliger PIN-Code | pin login |
|
||||
| Login in with PIN | Mit PIN anmelden | pin login |
|
||||
| Email | E-Mail | user login |
|
||||
| Enter your email | Geben Sie Ihre E-Mail-Adresse ein | user login |
|
||||
| Password | Passwort | user login |
|
||||
| Enter your password | Geben Sie Ihr Passwort ein | user login |
|
||||
| Forgot your password? | Passwort vergessen? | user login |
|
||||
| Log in | Anmelden | user login |
|
||||
|
||||
---
|
||||
|
||||
## Login site
|
||||
|
||||
| EN | DE | Notes |
|
||||
| --------------------- | ---------------------------------- | ----------- |
|
||||
| Welcome to Pangolin | Willkommen bei Pangolin | |
|
||||
| Log in to get started | Melden Sie sich an, um zu beginnen | |
|
||||
| Email | E-Mail | |
|
||||
| Enter your email | Geben Sie Ihre E-Mail-Adresse ein | placeholder |
|
||||
| Password | Passwort | |
|
||||
| Enter your password | Geben Sie Ihr Passwort ein | placeholder |
|
||||
| Forgot your password? | Passwort vergessen? | |
|
||||
| Log in | Anmelden | |
|
||||
|
||||
# Ogranization site after successful login
|
||||
|
||||
| EN | DE | Notes |
|
||||
| ----------------------------------------- | -------------------------------------------- | ----- |
|
||||
| Welcome to Pangolin | Willkommen bei Pangolin | |
|
||||
| You're a member of {number} organization. | Sie sind Mitglied von {number} Organisation. | |
|
||||
|
||||
## Shared Header, Navbar and Footer
|
||||
##### Header
|
||||
|
||||
| EN | DE | Notes |
|
||||
| ------------------- | ------------------- | ----- |
|
||||
| Documentation | Dokumentation | |
|
||||
| Support | Support | |
|
||||
| Organization {name} | Organisation {name} | |
|
||||
##### Organization selector
|
||||
|
||||
| EN | DE | Notes |
|
||||
| ---------------- | ----------------- | ----- |
|
||||
| Search… | Suchen… | |
|
||||
| Create | Erstellen | |
|
||||
| New Organization | Neue Organisation | |
|
||||
| Organizations | Organisationen | |
|
||||
|
||||
##### Navbar
|
||||
|
||||
| EN | DE | Notes |
|
||||
| --------------- | ----------------- | ----- |
|
||||
| Sites | Websites | |
|
||||
| Resources | Ressourcen | |
|
||||
| User & Roles | Benutzer & Rollen | |
|
||||
| Shareable Links | Teilbare Links | |
|
||||
| General | Allgemein | |
|
||||
##### Footer
|
||||
| EN | DE | |
|
||||
| ------------------------- | --------------------------- | ------------------- |
|
||||
| Page {number} of {number} | Seite {number} von {number} | |
|
||||
| Rows per page | Zeilen pro Seite | |
|
||||
| Pangolin | Pangolin | unten auf der Seite |
|
||||
| Built by Fossorial | Erstellt von Fossorial | unten auf der Seite |
|
||||
| Open Source | Open Source | unten auf der Seite |
|
||||
| Documentation | Dokumentation | unten auf der Seite |
|
||||
| {version} | {version} | unten auf der Seite |
|
||||
|
||||
## Main “Sites”
|
||||
##### “Hero” section
|
||||
|
||||
| EN | DE | Notes |
|
||||
| ------------------------------------------------------------ | ------------------------------------------------------------ | ----- |
|
||||
| Newt (Recommended) | Newt (empfohlen) | |
|
||||
| For the best user experience, use Newt. It uses WireGuard under the hood and allows you to address your private resources by their LAN address on your private network from within the Pangolin dashboard. | Für das beste Benutzererlebnis verwenden Sie Newt. Es nutzt WireGuard im Hintergrund und ermöglicht es Ihnen, auf Ihre privaten Ressourcen über ihre LAN-Adresse in Ihrem privaten Netzwerk direkt aus dem Pangolin-Dashboard zuzugreifen. | |
|
||||
| Runs in Docker | Läuft in Docker | |
|
||||
| Runs in shell on macOS, Linux, and Windows | Läuft in der Shell auf macOS, Linux und Windows | |
|
||||
| Install Newt | Newt installieren | |
|
||||
| Basic WireGuard<br> | Verwenden Sie einen beliebigen WireGuard-Client, um eine Verbindung herzustellen. Sie müssen auf Ihre internen Ressourcen über die Peer-IP-Adresse zugreifen. | |
|
||||
| Compatible with all WireGuard clients<br> | Kompatibel mit allen WireGuard-Clients<br> | |
|
||||
| Manual configuration required | Manuelle Konfiguration erforderlich<br> | |
|
||||
##### Content
|
||||
|
||||
| EN | DE | Notes |
|
||||
| --------------------------------------------------------- | ------------------------------------------------------------ | -------------------------------- |
|
||||
| Manage Sites | Seiten verwalten | |
|
||||
| Allow connectivity to your network through secure tunnels | Ermöglichen Sie die Verbindung zu Ihrem Netzwerk über ein sicheren Tunnel | |
|
||||
| Search sites | Seiten suchen | placeholder |
|
||||
| Add Site | Seite hinzufügen | |
|
||||
| Name | Name | table header |
|
||||
| Online | Status | table header |
|
||||
| Site | Seite | table header |
|
||||
| Data In | Eingehende Daten | table header |
|
||||
| Data Out | Ausgehende Daten | table header |
|
||||
| Connection Type | Verbindungstyp | table header |
|
||||
| Online | Online | site state |
|
||||
| Offline | Offline | site state |
|
||||
| Edit → | Bearbeiten → | |
|
||||
| View settings | Einstellungen anzeigen | Popup after clicking “…” on site |
|
||||
| Delete | Löschen | Popup after clicking “…” on site |
|
||||
##### Add Site Popup
|
||||
|
||||
| EN | DE | Notes |
|
||||
| ------------------------------------------------------ | ----------------------------------------------------------- | ----------- |
|
||||
| Create Site | Seite erstellen | |
|
||||
| Create a new site to start connection for this site | Erstellen Sie eine neue Seite, um die Verbindung zu starten | |
|
||||
| Name | Name | |
|
||||
| Site name | Seiten-Name | placeholder |
|
||||
| This is the name that will be displayed for this site. | So wird Ihre Seite angezeigt | desc |
|
||||
| Method | Methode | |
|
||||
| Local | Lokal | |
|
||||
| Newt | Newt | |
|
||||
| WireGuard | WireGuard | |
|
||||
| This is how you will expose connections. | So werden Verbindungen freigegeben. | |
|
||||
| You will only be able to see the configuration once. | Diese Konfiguration können Sie nur einmal sehen. | |
|
||||
| Learn how to install Newt on your system | Erfahren Sie, wie Sie Newt auf Ihrem System installieren | |
|
||||
| I have copied the config | Ich habe die Konfiguration kopiert | |
|
||||
| Create Site | Website erstellen | |
|
||||
| Close | Schließen | |
|
||||
|
||||
## Main “Resources”
|
||||
|
||||
##### “Hero” section
|
||||
|
||||
| EN | DE | Notes |
|
||||
| ------------------------------------------------------------ | ------------------------------------------------------------ | ----- |
|
||||
| Resources | Ressourcen | |
|
||||
| Ressourcen sind Proxy-Server für Anwendungen, die in Ihrem privaten Netzwerk laufen. Erstellen Sie eine Ressource für jede HTTP- oder HTTPS-Anwendung in Ihrem privaten Netzwerk. Jede Ressource muss mit einer Website verbunden sein, um eine private und sichere Verbindung über den verschlüsselten WireGuard-Tunnel zu ermöglichen. | Ressourcen sind Proxy-Server für Anwendungen, die in Ihrem privaten Netzwerk laufen. Erstellen Sie eine Ressource für jede HTTP- oder HTTPS-Anwendung in Ihrem privaten Netzwerk. Jede Ressource muss mit einer Website verbunden sein, um eine private und sichere Verbindung über den verschlüsselten WireGuard-Tunnel zu ermöglichen. | |
|
||||
| Secure connectivity with WireGuard encryption | Sichere Verbindung mit WireGuard-Verschlüsselung | |
|
||||
| Configure multiple authentication methods | Konfigurieren Sie mehrere Authentifizierungsmethoden | |
|
||||
| User and role-based access control | Benutzer- und rollenbasierte Zugriffskontrolle | |
|
||||
##### Content
|
||||
|
||||
| EN | DE | Notes |
|
||||
| -------------------------------------------------- | ---------------------------------------------------------- | -------------------- |
|
||||
| Manage Resources | Ressourcen verwalten | |
|
||||
| Create secure proxies to your private applications | Erstellen Sie sichere Proxys für Ihre privaten Anwendungen | |
|
||||
| Search resources | Ressourcen durchsuchen | placeholder |
|
||||
| Name | Name | |
|
||||
| Site | Website | |
|
||||
| Full URL | Vollständige URL | |
|
||||
| Authentication | Authentifizierung | |
|
||||
| Not Protected | Nicht geschützt | authentication state |
|
||||
| Protected | Geschützt | authentication state |
|
||||
| Edit → | Bearbeiten → | |
|
||||
| Add Resource | Ressource hinzufügen | |
|
||||
##### Add Resource Popup
|
||||
|
||||
| EN | DE | Notes |
|
||||
| ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------- |
|
||||
| Create Resource | Ressource erstellen | |
|
||||
| Create a new resource to proxy request to your app | Erstellen Sie eine neue Ressource, um Anfragen an Ihre App zu proxen | |
|
||||
| Name | Name | |
|
||||
| My Resource | Neue Ressource | name placeholder |
|
||||
| This is the name that will be displayed for this resource. | Dies ist der Name, der für diese Ressource angezeigt wird | |
|
||||
| Subdomain | Subdomain | |
|
||||
| Enter subdomain | Subdomain eingeben | |
|
||||
| This is the fully qualified domain name that will be used to access the resource. | Dies ist der vollständige Domainname, der für den Zugriff auf die Ressource verwendet wird. | |
|
||||
| Site | Website | |
|
||||
| Search site… | Website suchen… | Site selector popup |
|
||||
| This is the site that will be used in the dashboard. | Dies ist die Website, die im Dashboard verwendet wird. | |
|
||||
| Create Resource | Ressource erstellen | |
|
||||
| Close | Schließen | |
|
||||
|
||||
|
||||
## Main “User & Roles”
|
||||
##### Content
|
||||
|
||||
| EN | DE | Notes |
|
||||
| ------------------------------------------------------------ | ------------------------------------------------------------ | ----------------------------- |
|
||||
| Manage User & Roles | Benutzer & Rollen verwalten | |
|
||||
| Invite users and add them to roles to manage access to your organization | Laden Sie Benutzer ein und weisen Sie ihnen Rollen zu, um den Zugriff auf Ihre Organisation zu verwalten | |
|
||||
| Users | Benutzer | sidebar item |
|
||||
| Roles | Rollen | sidebar item |
|
||||
| **User tab** | | |
|
||||
| Search users | Benutzer suchen | placeholder |
|
||||
| Invite User | Benutzer einladen | addbutton |
|
||||
| Email | E-Mail | table header |
|
||||
| Status | Status | table header |
|
||||
| Role | Rolle | table header |
|
||||
| Confirmed | Bestätigt | account status |
|
||||
| Not confirmed (?) | Nicht bestätigt (?) | unknown for me account status |
|
||||
| Owner | Besitzer | role |
|
||||
| Admin | Administrator | role |
|
||||
| Member | Mitglied | role |
|
||||
| **Roles Tab** | | |
|
||||
| Search roles | Rollen suchen | placeholder |
|
||||
| Add Role | Rolle hinzufügen | addbutton |
|
||||
| Name | Name | table header |
|
||||
| Description | Beschreibung | table header |
|
||||
| Admin | Administrator | role |
|
||||
| Member | Mitglied | role |
|
||||
| Admin role with the most permissions | Administratorrolle mit den meisten Berechtigungen | admin role desc |
|
||||
| Members can only view resources | Mitglieder können nur Ressourcen anzeigen | member role desc |
|
||||
|
||||
##### Invite User popup
|
||||
|
||||
| EN | DE | Notes |
|
||||
| ----------------- | ------------------------------------------------------- | ----------- |
|
||||
| Invite User | Geben Sie neuen Benutzern Zugriff auf Ihre Organisation | |
|
||||
| Email | E-Mail | |
|
||||
| Enter an email | E-Mail eingeben | placeholder |
|
||||
| Role | Rolle | |
|
||||
| Select role | Rolle auswählen | placeholder |
|
||||
| Gültig für | Gültig bis | |
|
||||
| 1 day | Tag | |
|
||||
| 2 days | 2 Tage | |
|
||||
| 3 days | 3 Tage | |
|
||||
| 4 days | 4 Tage | |
|
||||
| 5 days | 5 Tage | |
|
||||
| 6 days | 6 Tage | |
|
||||
| 7 days | 7 Tage | |
|
||||
| Create Invitation | Einladung erstellen | |
|
||||
| Close | Schließen | |
|
||||
|
||||
|
||||
## Main “Shareable Links”
|
||||
##### “Hero” section
|
||||
|
||||
| EN | DE | Notes |
|
||||
| ------------------------------------------------------------ | ------------------------------------------------------------ | ----- |
|
||||
| Shareable Links | Teilbare Links | |
|
||||
| Create shareable links to your resources. Links provide temporary or unlimited access to your resource. You can configure the expiration duration of the link when you create one. | Erstellen Sie teilbare Links zu Ihren Ressourcen. Links bieten temporären oder unbegrenzten Zugriff auf Ihre Ressource. Sie können die Gültigkeitsdauer des Links beim Erstellen konfigurieren. | |
|
||||
| Easy to create and share | Einfach zu erstellen und zu teilen | |
|
||||
| Configurable expiration duration | Konfigurierbare Gültigkeitsdauer | |
|
||||
| Secure and revocable | Sicher und widerrufbar | |
|
||||
##### Content
|
||||
|
||||
| EN | DE | Notes |
|
||||
| ------------------------------------------------------------ | ------------------------------------------------------------ | ----------------- |
|
||||
| Manage Shareable Links | Teilbare Links verwalten | |
|
||||
| Create shareable links to grant temporary or permanent access to your resources | Erstellen Sie teilbare Links, um temporären oder permanenten Zugriff auf Ihre Ressourcen zu gewähren | |
|
||||
| Search links | Links suchen | placeholder |
|
||||
| Create Share Link | Neuen Link erstellen | addbutton |
|
||||
| Resource | Ressource | table header |
|
||||
| Title | Titel | table header |
|
||||
| Created | Erstellt | table header |
|
||||
| Expires | Gültig bis | table header |
|
||||
| No links. Create one to get started. | Keine Links. Erstellen Sie einen, um zu beginnen. | table placeholder |
|
||||
|
||||
##### Create Shareable Link popup
|
||||
|
||||
| EN | DE | Notes |
|
||||
| ------------------------------------------------------------ | ------------------------------------------------------------ | ----------------------- |
|
||||
| Create Shareable Link | Teilbaren Link erstellen | |
|
||||
| Anyone with this link can access the resource | Jeder mit diesem Link kann auf die Ressource zugreifen | |
|
||||
| Resource | Ressource | |
|
||||
| Select resource | Ressource auswählen | |
|
||||
| Search resources… | Ressourcen suchen… | resource selector popup |
|
||||
| Title (optional) | Titel (optional) | |
|
||||
| Enter title | Titel eingeben | placeholder |
|
||||
| Expire in | Gültig bis | |
|
||||
| Minutes | Minuten | |
|
||||
| Hours | Stunden | |
|
||||
| Days | Tage | |
|
||||
| Months | Monate | |
|
||||
| Years | Jahre | |
|
||||
| Never expire | Nie ablaufen | |
|
||||
| Expiration time is how long the link will be usable and provide access to the resource. After this time, the link will no longer work, and users who used this link will lose access to the resource. | Die Gültigkeitsdauer bestimmt, wie lange der Link nutzbar ist und Zugriff auf die Ressource bietet. Nach Ablauf dieser Zeit funktioniert der Link nicht mehr, und Benutzer, die diesen Link verwendet haben, verlieren den Zugriff auf die Ressource. | |
|
||||
| Create Link | Link erstellen | |
|
||||
| Close | Schließen | |
|
||||
|
||||
|
||||
## Main “General”
|
||||
|
||||
| EN | DE | Notes |
|
||||
| ------------------------------------------------------------ | ------------------------------------------------------------ | ------------ |
|
||||
| General | Allgemein | |
|
||||
| Configure your organization’s general settings | Konfigurieren Sie die allgemeinen Einstellungen Ihrer Organisation | |
|
||||
| General | Allgemein | sidebar item |
|
||||
| Organization Settings | Organisationseinstellungen | |
|
||||
| Manage your organization details and configuration | Verwalten Sie die Details und Konfiguration Ihrer Organisation | |
|
||||
| Name | Name | |
|
||||
| This is the display name of the org | Dies ist der Anzeigename Ihrer Organisation | |
|
||||
| Save Settings | Einstellungen speichern | |
|
||||
| Danger Zone | Gefahrenzone | |
|
||||
| Once you delete this org, there is no going back. Please be certain. | Wenn Sie diese Organisation löschen, gibt es kein Zurück. Bitte seien Sie sicher. | |
|
||||
| Delete Organization Data | Organisationsdaten löschen | |
|
|
@ -1,291 +0,0 @@
|
|||
## Authentication Site
|
||||
|
||||
|
||||
| EN | ES | Notes |
|
||||
| -------------------------------------------------------- | ------------------------------------------------------------ | ---------- |
|
||||
| Powered by [Pangolin](https://github.com/fosrl/pangolin) | Desarrollado por [Pangolin](https://github.com/fosrl/pangolin) | |
|
||||
| Authentication Required | Se requiere autenticación | |
|
||||
| Choose your preferred method to access {resource} | Elije tu método requerido para acceder a {resource} | |
|
||||
| PIN | PIN | |
|
||||
| User | Usuario | |
|
||||
| 6-digit PIN Code | Código PIN de 6 dígitos | pin login |
|
||||
| Login in with PIN | Registrate con PIN | pin login |
|
||||
| Email | Email | user login |
|
||||
| Enter your email | Introduce tu email | user login |
|
||||
| Password | Contraseña | user login |
|
||||
| Enter your password | Introduce tu contraseña | user login |
|
||||
| Forgot your password? | ¿Olvidaste tu contraseña? | user login |
|
||||
| Log in | Iniciar sesión | user login |
|
||||
|
||||
|
||||
## Login site
|
||||
|
||||
| EN | ES | Notes |
|
||||
| --------------------- | ---------------------------------- | ----------- |
|
||||
| Welcome to Pangolin | Binvenido a Pangolin | |
|
||||
| Log in to get started | Registrate para comenzar | |
|
||||
| Email | Email | |
|
||||
| Enter your email | Introduce tu email | placeholder |
|
||||
| Password | Contraseña | |
|
||||
| Enter your password | Introduce tu contraseña | placeholder |
|
||||
| Forgot your password? | ¿Olvidaste tu contraseña? | |
|
||||
| Log in | Iniciar sesión | |
|
||||
|
||||
# Ogranization site after successful login
|
||||
|
||||
| EN | ES | Notes |
|
||||
| ----------------------------------------- | -------------------------------------------- | ----- |
|
||||
| Welcome to Pangolin | Binvenido a Pangolin | |
|
||||
| You're a member of {number} organization. | Eres miembro de la organización {number}. | |
|
||||
|
||||
## Shared Header, Navbar and Footer
|
||||
##### Header
|
||||
|
||||
| EN | ES | Notes |
|
||||
| ------------------- | ------------------- | ----- |
|
||||
| Documentation | Documentación | |
|
||||
| Support | Soporte | |
|
||||
| Organization {name} | Organización {name} | |
|
||||
##### Organization selector
|
||||
|
||||
| EN | ES | Notes |
|
||||
| ---------------- | ----------------- | ----- |
|
||||
| Search… | Buscar… | |
|
||||
| Create | Crear | |
|
||||
| New Organization | Nueva Organización| |
|
||||
| Organizations | Organizaciones | |
|
||||
|
||||
##### Navbar
|
||||
|
||||
| EN | ES | Notes |
|
||||
| --------------- | -----------------------| ----- |
|
||||
| Sites | Sitios | |
|
||||
| Resources | Recursos | |
|
||||
| User & Roles | Usuarios y roles | |
|
||||
| Shareable Links | Enlaces para compartir | |
|
||||
| General | General | |
|
||||
|
||||
##### Footer
|
||||
| EN | ES | |
|
||||
| ------------------------- | --------------------------- | -------|
|
||||
| Page {number} of {number} | Página {number} de {number} | footer |
|
||||
| Rows per page | Filas por página | footer |
|
||||
| Pangolin | Pangolin | footer |
|
||||
| Built by Fossorial | Construido por Fossorial | footer |
|
||||
| Open Source | Código abierto | footer |
|
||||
| Documentation | Documentación | footer |
|
||||
| {version} | {version} | footer |
|
||||
|
||||
## Main “Sites”
|
||||
##### “Hero” section
|
||||
|
||||
| EN | ES | Notes |
|
||||
| ------------------------------------------------------------ | ------------------------------------------------------------ | ----- |
|
||||
| Newt (Recommended) | Newt (Recomendado) | |
|
||||
| For the best user experience, use Newt. It uses WireGuard under the hood and allows you to address your private resources by their LAN address on your private network from within the Pangolin dashboard. | Para obtener la mejor experiencia de usuario, utiliza Newt. Utiliza WireGuard internamente y te permite abordar tus recursos privados mediante tu dirección LAN en tu red privada desde el panel de Pangolin. | |
|
||||
| Runs in Docker | Se ejecuta en Docker | |
|
||||
| Runs in shell on macOS, Linux, and Windows | Se ejecuta en shell en macOS, Linux y Windows | |
|
||||
| Install Newt | Instalar Newt | |
|
||||
| Basic WireGuard<br> | WireGuard básico<br> | |
|
||||
| Compatible with all WireGuard clients<br> | Compatible con todos los clientes WireGuard<br> | |
|
||||
| Manual configuration required | Se requiere configuración manual | |
|
||||
|
||||
##### Content
|
||||
|
||||
| EN | ES | Notes |
|
||||
| --------------------------------------------------------- | ------------------------------------------------------------ | -------------------------------- |
|
||||
| Manage Sites | Administrar sitios | |
|
||||
| Allow connectivity to your network through secure tunnels | Permitir la conectividad a tu red a través de túneles seguros| |
|
||||
| Search sites | Buscar sitios | placeholder |
|
||||
| Add Site | Agregar sitio | |
|
||||
| Name | Nombre | table header |
|
||||
| Online | Conectado | table header |
|
||||
| Site | Sitio | table header |
|
||||
| Data In | Datos en | table header |
|
||||
| Data Out | Datos de salida | table header |
|
||||
| Connection Type | Tipo de conexión | table header |
|
||||
| Online | Conectado | site state |
|
||||
| Offline | Desconectado | site state |
|
||||
| Edit → | Editar → | |
|
||||
| View settings | Ver configuración | Popup after clicking “…” on site |
|
||||
| Delete | Borrar | Popup after clicking “…” on site |
|
||||
|
||||
##### Add Site Popup
|
||||
|
||||
| EN | ES | Notes |
|
||||
| ------------------------------------------------------ | ----------------------------------------------------------- | ----------- |
|
||||
| Create Site | Crear sitio | |
|
||||
| Create a new site to start connection for this site | Crear un nuevo sitio para iniciar la conexión para este sitio | |
|
||||
| Name | Nombre | |
|
||||
| Site name | Nombre del sitio | placeholder |
|
||||
| This is the name that will be displayed for this site. | Este es el nombre que se mostrará para este sitio. | desc |
|
||||
| Method | Método | |
|
||||
| Local | Local | |
|
||||
| Newt | Newt | |
|
||||
| WireGuard | WireGuard | |
|
||||
| This is how you will expose connections. | Así es como expondrás las conexiones. | |
|
||||
| You will only be able to see the configuration once. | Solo podrás ver la configuración una vez. | |
|
||||
| Learn how to install Newt on your system | Aprende a instalar Newt en tu sistema | |
|
||||
| I have copied the config | He copiado la configuración | |
|
||||
| Create Site | Crear sitio | |
|
||||
| Close | Cerrar | |
|
||||
|
||||
## Main “Resources”
|
||||
|
||||
##### “Hero” section
|
||||
|
||||
| EN | ES | Notes |
|
||||
| ------------------------------------------------------------ | ------------------------------------------------------------ | ----- |
|
||||
| Resources | Recursos | |
|
||||
| Ressourcen sind Proxy-Server für Anwendungen, die in Ihrem privaten Netzwerk laufen. Erstellen Sie eine Ressource für jede HTTP- oder HTTPS-Anwendung in Ihrem privaten Netzwerk. Jede Ressource muss mit einer Website verbunden sein, um eine private und sichere Verbindung über den verschlüsselten WireGuard-Tunnel zu ermöglichen. |Los recursos son servidores proxy para aplicaciones que se ejecutan en su red privada. Cree un recurso para cada aplicación HTTP o HTTPS en su red privada. Cada recurso debe estar conectado a un sitio web para proporcionar una conexión privada y segura a través del túnel cifrado WireGuard. | |
|
||||
| Secure connectivity with WireGuard encryption | Conectividad segura con encriptación WireGuard | |
|
||||
| Configure multiple authentication methods | Configura múltiples métodos de autenticación | |
|
||||
| User and role-based access control | Control de acceso basado en usuarios y roles | |
|
||||
|
||||
##### Content
|
||||
|
||||
| EN | ES | Notes |
|
||||
| -------------------------------------------------- | ---------------------------------------------------------- | -------------------- |
|
||||
| Manage Resources | Administrar recursos | |
|
||||
| Create secure proxies to your private applications | Crea servidores proxy seguros para tus aplicaciones privadas | |
|
||||
| Search resources | Buscar recursos | placeholder |
|
||||
| Name | Nombre | |
|
||||
| Site | Sitio | |
|
||||
| Full URL | URL completa | |
|
||||
| Authentication | Autenticación | |
|
||||
| Not Protected | No protegido | authentication state |
|
||||
| Protected | Protegido | authentication state |
|
||||
| Edit → | Editar → | |
|
||||
| Add Resource | Agregar recurso | |
|
||||
|
||||
##### Add Resource Popup
|
||||
|
||||
| EN | ES | Notes |
|
||||
| ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------- |
|
||||
| Create Resource | Crear recurso | |
|
||||
| Create a new resource to proxy request to your app | Crea un nuevo recurso para enviar solicitudes a tu aplicación | |
|
||||
| Name | Nombre | |
|
||||
| My Resource | Mi recurso | name placeholder |
|
||||
| This is the name that will be displayed for this resource. | Este es el nombre que se mostrará para este recurso. | |
|
||||
| Subdomain | Subdominio | |
|
||||
| Enter subdomain | Ingresar subdominio | |
|
||||
| This is the fully qualified domain name that will be used to access the resource. | Este es el nombre de dominio completo que se utilizará para acceder al recurso. | |
|
||||
| Site | Sitio | |
|
||||
| Search site… | Buscar sitio… | Site selector popup |
|
||||
| This is the site that will be used in the dashboard. | Este es el sitio que se utilizará en el panel de control. | |
|
||||
| Create Resource | Crear recurso | |
|
||||
| Close | Cerrar | |
|
||||
|
||||
## Main “User & Roles”
|
||||
##### Content
|
||||
|
||||
| EN | ES | Notes |
|
||||
| ------------------------------------------------------------ | ------------------------------------------------------------ | ----------------------------- |
|
||||
| Manage User & Roles | Administrar usuarios y roles | |
|
||||
| Invite users and add them to roles to manage access to your organization | Invita a usuarios y agrégalos a roles para administrar el acceso a tu organización | |
|
||||
| Users | Usuarios | sidebar item |
|
||||
| Roles | Roles | sidebar item |
|
||||
| **User tab** | **Pestaña de usuario** | |
|
||||
| Search users | Buscar usuarios | placeholder |
|
||||
| Invite User | Invitar usuario | addbutton |
|
||||
| Email | Email | table header |
|
||||
| Status | Estado | table header |
|
||||
| Role | Role | table header |
|
||||
| Confirmed | Confirmado | account status |
|
||||
| Not confirmed (?) | No confirmado (?) | unknown for me account status |
|
||||
| Owner | Dueño | role |
|
||||
| Admin | Administrador | role |
|
||||
| Member | Miembro | role |
|
||||
| **Roles Tab** | **Pestaña Roles** | |
|
||||
| Search roles | Buscar roles | placeholder |
|
||||
| Add Role | Agregar rol | addbutton |
|
||||
| Name | Nombre | table header |
|
||||
| Description | Descripción | table header |
|
||||
| Admin | Administrador | role |
|
||||
| Member | Miembro | role |
|
||||
| Admin role with the most permissions | Rol de administrador con más permisos | admin role desc |
|
||||
| Members can only view resources | Los miembros sólo pueden ver los recursos | member role desc |
|
||||
|
||||
##### Invite User popup
|
||||
|
||||
| EN | ES | Notes |
|
||||
| ----------------- | ------------------------------------------------------- | ----------- |
|
||||
| Invite User | Invitar usuario | |
|
||||
| Email | Email | |
|
||||
| Enter an email | Introduzca un email | placeholder |
|
||||
| Role | Rol | |
|
||||
| Select role | Seleccionar rol | placeholder |
|
||||
| Gültig für | Válido para | |
|
||||
| 1 day | 1 día | |
|
||||
| 2 days | 2 días | |
|
||||
| 3 days | 3 días | |
|
||||
| 4 days | 4 días | |
|
||||
| 5 days | 5 días | |
|
||||
| 6 days | 6 días | |
|
||||
| 7 days | 7 días | |
|
||||
| Create Invitation | Crear invitación | |
|
||||
| Close | Cerrar | |
|
||||
|
||||
|
||||
## Main “Shareable Links”
|
||||
##### “Hero” section
|
||||
|
||||
| EN | ES | Notes |
|
||||
| ------------------------------------------------------------ | ------------------------------------------------------------ | ----- |
|
||||
| Shareable Links | Enlaces para compartir | |
|
||||
| Create shareable links to your resources. Links provide temporary or unlimited access to your resource. You can configure the expiration duration of the link when you create one. | Crear enlaces que se puedan compartir a tus recursos. Los enlaces proporcionan acceso temporal o ilimitado a tu recurso. Puedes configurar la duración de caducidad del enlace cuando lo creas. | |
|
||||
| Easy to create and share | Fácil de crear y compartir | |
|
||||
| Configurable expiration duration | Duración de expiración configurable | |
|
||||
| Secure and revocable | Seguro y revocable | |
|
||||
##### Content
|
||||
|
||||
| EN | ES | Notes |
|
||||
| ------------------------------------------------------------ | ------------------------------------------------------------ | ----------------- |
|
||||
| Manage Shareable Links | Administrar enlaces compartibles | |
|
||||
| Create shareable links to grant temporary or permanent access to your resources | Crear enlaces compartibles para otorgar acceso temporal o permanente a tus recursos | |
|
||||
| Search links | Buscar enlaces | placeholder |
|
||||
| Create Share Link | Crear enlace para compartir | addbutton |
|
||||
| Resource | Recurso | table header |
|
||||
| Title | Título | table header |
|
||||
| Created | Creado | table header |
|
||||
| Expires | Caduca | table header |
|
||||
| No links. Create one to get started. | No hay enlaces. Crea uno para comenzar. | table placeholder |
|
||||
|
||||
##### Create Shareable Link popup
|
||||
|
||||
| EN | ES | Notes |
|
||||
| ------------------------------------------------------------ | ------------------------------------------------------------ | ----------------------- |
|
||||
| Create Shareable Link | Crear un enlace para compartir | |
|
||||
| Anyone with this link can access the resource | Cualquier persona con este enlace puede acceder al recurso. | |
|
||||
| Resource | Recurso | |
|
||||
| Select resource | Seleccionar recurso | |
|
||||
| Search resources… | Buscar recursos… | resource selector popup |
|
||||
| Title (optional) | Título (opcional) | |
|
||||
| Enter title | Introducir título | placeholder |
|
||||
| Expire in | Caduca en | |
|
||||
| Minutes | Minutos | |
|
||||
| Hours | Horas | |
|
||||
| Days | Días | |
|
||||
| Months | Meses | |
|
||||
| Years | Años | |
|
||||
| Never expire | Nunca caduca | |
|
||||
| Expiration time is how long the link will be usable and provide access to the resource. After this time, the link will no longer work, and users who used this link will lose access to the resource. | El tiempo de expiración es el tiempo durante el cual el enlace se podrá utilizar y brindará acceso al recurso. Después de este tiempo, el enlace dejará de funcionar y los usuarios que lo hayan utilizado perderán el acceso al recurso. | |
|
||||
| Create Link | Crear enlace | |
|
||||
| Close | Cerrar | |
|
||||
|
||||
|
||||
## Main “General”
|
||||
|
||||
| EN | ES | Notes |
|
||||
| ------------------------------------------------------------ | ------------------------------------------------------------ | ------------ |
|
||||
| General | General | |
|
||||
| Configure your organization’s general settings | Configura los ajustes generales de tu organización | |
|
||||
| General | General | sidebar item |
|
||||
| Organization Settings | Configuración de la organización | |
|
||||
| Manage your organization details and configuration | Administra los detalles y la configuración de tu organización| |
|
||||
| Name | Nombre | |
|
||||
| This is the display name of the org | Este es el nombre para mostrar de la organización. | |
|
||||
| Save Settings | Guardar configuración | |
|
||||
| Danger Zone | Zona de peligro | |
|
||||
| Once you delete this org, there is no going back. Please be certain. | Una vez que elimines esta organización, no habrá vuelta atrás. Asegúrate de hacerlo. | |
|
||||
| Delete Organization Data | Eliminar datos de la organización | |
|
|
@ -1,287 +0,0 @@
|
|||
## Authentication Site
|
||||
|
||||
|
||||
| EN | PL | Notes |
|
||||
| -------------------------------------------------------- | ------------------------------------------------------------ | ---------- |
|
||||
| Powered by [Pangolin](https://github.com/fosrl/pangolin) | Zasilane przez [Pangolin](https://github.com/fosrl/pangolin) | |
|
||||
| Authentication Required | Wymagane uwierzytelnienie | |
|
||||
| Choose your preferred method to access {resource} | Wybierz preferowaną metodę dostępu do {resource} | |
|
||||
| PIN | PIN | |
|
||||
| User | Zaloguj | |
|
||||
| 6-digit PIN Code | 6-cyfrowy kod PIN | pin login |
|
||||
| Login in with PIN | Zaloguj się PIN’em | pin login |
|
||||
| Email | Email | user login |
|
||||
| Enter your email | Wprowadź swój email | user login |
|
||||
| Password | Hasło | user login |
|
||||
| Enter your password | Wprowadź swoje hasło | user login |
|
||||
| Forgot your password? | Zapomniałeś hasła? | user login |
|
||||
| Log in | Zaloguj | user login |
|
||||
|
||||
|
||||
## Login site
|
||||
|
||||
| EN | PL | Notes |
|
||||
| --------------------- | ------------------------------ | ----------- |
|
||||
| Welcome to Pangolin | Witaj w Pangolin | |
|
||||
| Log in to get started | Zaloguj się, aby rozpocząć<br> | |
|
||||
| Email | Email | |
|
||||
| Enter your email | Wprowadź swój adres e-mail<br> | placeholder |
|
||||
| Password | Hasło | |
|
||||
| Enter your password | Wprowadź swoje hasło | placeholder |
|
||||
| Forgot your password? | Nie pamiętasz hasła? | |
|
||||
| Log in | Zaloguj | |
|
||||
|
||||
# Ogranization site after successful login
|
||||
|
||||
|
||||
| EN | PL | Notes |
|
||||
| ----------------------------------------- | ------------------------------------------ | ----- |
|
||||
| Welcome to Pangolin | Witaj w Pangolin | |
|
||||
| You're a member of {number} organization. | Jesteś użytkownikiem {number} organizacji. | |
|
||||
|
||||
## Shared Header, Navbar and Footer
|
||||
##### Header
|
||||
|
||||
| EN | PL | Notes |
|
||||
| ------------------- | ------------------ | ----- |
|
||||
| Documentation | Dokumentacja | |
|
||||
| Support | Wsparcie | |
|
||||
| Organization {name} | Organizacja {name} | |
|
||||
##### Organization selector
|
||||
|
||||
| EN | PL | Notes |
|
||||
| ---------------- | ---------------- | ----- |
|
||||
| Search… | Szukaj… | |
|
||||
| Create | Utwórz | |
|
||||
| New Organization | Nowa organizacja | |
|
||||
| Organizations | Organizacje | |
|
||||
|
||||
##### Navbar
|
||||
|
||||
| EN | PL | Notes |
|
||||
| --------------- | ---------------------- | ----- |
|
||||
| Sites | Witryny | |
|
||||
| Resources | Zasoby | |
|
||||
| User & Roles | Użytkownicy i Role | |
|
||||
| Shareable Links | Łącza do udostępniania | |
|
||||
| General | Ogólne | |
|
||||
##### Footer
|
||||
| EN | PL | |
|
||||
| ------------------------- | -------------------------- | -------------- |
|
||||
| Page {number} of {number} | Strona {number} z {number} | |
|
||||
| Rows per page | Wierszy na stronę | |
|
||||
| Pangolin | Pangolin | bottom of site |
|
||||
| Built by Fossorial | Stworzone przez Fossorial | bottom of site |
|
||||
| Open Source | Open source | bottom of site |
|
||||
| Documentation | Dokumentacja | bottom of site |
|
||||
| {version} | {version} | bottom of site |
|
||||
## Main “Sites”
|
||||
##### “Hero” section
|
||||
|
||||
| EN | PL | Notes |
|
||||
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----- |
|
||||
| Newt (Recommended) | Newt (zalecane) | |
|
||||
| For the best user experience, use Newt. It uses WireGuard under the hood and allows you to address your private resources by their LAN address on your private network from within the Pangolin dashboard. | Aby zapewnić najlepsze doświadczenie użytkownika, korzystaj z Newt. Wykorzystuje on technologię WireGuard w tle i pozwala na dostęp do Twoich prywatnych zasobów za pomocą ich adresu LAN w prywatnej sieci bezpośrednio z poziomu pulpitu nawigacyjnego Pangolin. | |
|
||||
| Runs in Docker | Działa w Dockerze | |
|
||||
| Runs in shell on macOS, Linux, and Windows | Działa w powłoce na systemach macOS, Linux i Windows | |
|
||||
| Install Newt | Zainstaluj Newt | |
|
||||
| Podstawowy WireGuard<br> | Użyj dowolnego klienta WireGuard, aby się połączyć. Będziesz musiał uzyskiwać dostęp do swoich wewnętrznych zasobów za pomocą adresu IP równorzędnego | |
|
||||
| Compatible with all WireGuard clients<br> | Kompatybilny ze wszystkimi klientami WireGuard<br> | |
|
||||
| Manual configuration required | Wymagana ręczna konfiguracja<br> | |
|
||||
##### Content
|
||||
|
||||
| EN | PL | Notes |
|
||||
| --------------------------------------------------------- | ------------------------------------------------------------------------ | -------------------------------- |
|
||||
| Manage Sites | Zarządzanie witrynami | |
|
||||
| Allow connectivity to your network through secure tunnels | Zezwalaj na łączność z Twoją siecią za pośrednictwem bezpiecznych tuneli | |
|
||||
| Search sites | Szukaj witryny | placeholder |
|
||||
| Add Site | Dodaj witrynę | |
|
||||
| Name | Nazwa | table header |
|
||||
| Online | Status | table header |
|
||||
| Site | Witryna | table header |
|
||||
| Data In | Dane wchodzące | table header |
|
||||
| Data Out | Dane wychodzące | table header |
|
||||
| Connection Type | Typ połączenia | table header |
|
||||
| Online | Online | site state |
|
||||
| Offline | Poza siecią | site state |
|
||||
| Edit → | Edytuj → | |
|
||||
| View settings | Pokaż ustawienia | Popup after clicking “…” on site |
|
||||
| Delete | Usuń | Popup after clicking “…” on site |
|
||||
##### Add Site Popup
|
||||
|
||||
| EN | PL | Notes |
|
||||
| ------------------------------------------------------ | --------------------------------------------------- | ----------- |
|
||||
| Create Site | Utwórz witrynę | |
|
||||
| Create a new site to start connection for this site | Utwórz nową witrynę aby rozpocząć połączenie | |
|
||||
| Name | Nazwa | |
|
||||
| Site name | Nazwa witryny | placeholder |
|
||||
| This is the name that will be displayed for this site. | Tak będzie wyświetlana twoja witryna | desc |
|
||||
| Method | Metoda | |
|
||||
| Local | Lokalna | |
|
||||
| Newt | Newt | |
|
||||
| WireGuard | WireGuard | |
|
||||
| This is how you will expose connections. | Tak będą eksponowane połączenie. | |
|
||||
| You will only be able to see the configuration once. | Tą konfigurację możesz zobaczyć tylko raz. | |
|
||||
| Learn how to install Newt on your system | Dowiedz się jak zainstalować Newt na twoim systemie | |
|
||||
| I have copied the config | Skopiowałem konfigurację | |
|
||||
| Create Site | Utwórz witrynę | |
|
||||
| Close | Zamknij | |
|
||||
|
||||
## Main “Resources”
|
||||
|
||||
##### “Hero” section
|
||||
|
||||
| EN | PL | Notes |
|
||||
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----- |
|
||||
| Resources | Zasoby | |
|
||||
| Zasoby to serwery proxy dla aplikacji działających w Twojej prywatnej sieci. Utwórz zasób dla dowolnej aplikacji HTTP lub HTTPS w swojej prywatnej sieci. Każdy zasób musi być połączony z witryną, aby umożliwić prywatne i bezpieczne połączenie przez szyfrowany tunel WireGuard. | Zasoby to serwery proxy dla aplikacji działających w Twojej prywatnej sieci. Utwórz zasób dla dowolnej aplikacji HTTP lub HTTPS w swojej prywatnej sieci. Każdy zasób musi być połączony z witryną, aby umożliwić prywatne i bezpieczne połączenie przez szyfrowany tunel WireGuard. | |
|
||||
| Secure connectivity with WireGuard encryption | Bezpieczna łączność z szyfrowaniem WireGuard | |
|
||||
| Configure multiple authentication methods | Konfigurowanie wielu metod uwierzytelniania | |
|
||||
| User and role-based access control | Kontrola dostępu oparta na użytkownikach i rolach | |
|
||||
##### Content
|
||||
|
||||
| EN | PL | Notes |
|
||||
| -------------------------------------------------- | -------------------------------------------------------------- | -------------------- |
|
||||
| Manage Resources | Zarządzaj zasobami | |
|
||||
| Create secure proxies to your private applications | Twórz bezpieczne serwery proxy dla swoich prywatnych aplikacji | |
|
||||
| Search resources | Szukaj w zasobach | placeholder |
|
||||
| Name | Nazwa | |
|
||||
| Site | Witryna | |
|
||||
| Full URL | Pełny URL | |
|
||||
| Authentication | Uwierzytelnianie | |
|
||||
| Not Protected | Niezabezpieczony | authentication state |
|
||||
| Protected | Zabezpieczony | authentication state |
|
||||
| Edit → | Edytuj → | |
|
||||
| Add Resource | Dodaj zasób | |
|
||||
##### Add Resource Popup
|
||||
|
||||
| EN | PL | Notes |
|
||||
| --------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | ------------------- |
|
||||
| Create Resource | Utwórz zasób | |
|
||||
| Create a new resource to proxy request to your app | Utwórz nowy zasób, aby przekazywać żądania do swojej aplikacji | |
|
||||
| Name | Nazwa | |
|
||||
| My Resource | Nowy zasób | name placeholder |
|
||||
| This is the name that will be displayed for this resource. | To jest nazwa, która będzie wyświetlana dla tego zasobu | |
|
||||
| Subdomain | Subdomena | |
|
||||
| Enter subdomain | Wprowadź subdomenę | |
|
||||
| This is the fully qualified domain name that will be used to access the resource. | To jest pełna nazwa domeny, która będzie używana do dostępu do zasobu. | |
|
||||
| Site | Witryna | |
|
||||
| Search site… | Szukaj witryny… | Site selector popup |
|
||||
| This is the site that will be used in the dashboard. | To jest witryna, która będzie używana w pulpicie nawigacyjnym. | |
|
||||
| Create Resource | Utwórz zasób | |
|
||||
| Close | Zamknij | |
|
||||
|
||||
|
||||
## Main “User & Roles”
|
||||
##### Content
|
||||
|
||||
| EN | PL | Notes |
|
||||
| ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------ | ----------------------------- |
|
||||
| Manage User & Roles | Zarządzanie użytkownikami i rolami | |
|
||||
| Invite users and add them to roles to manage access to your organization | Zaproś użytkowników i przypisz im role, aby zarządzać dostępem do Twojej organizacji | |
|
||||
| Users | Użytkownicy | sidebar item |
|
||||
| Roles | Role | sidebar item |
|
||||
| **User tab** | | |
|
||||
| Search users | Wyszukaj użytkownika | placeholder |
|
||||
| Invite User | Zaproś użytkownika | addbutton |
|
||||
| Email | Email | table header |
|
||||
| Status | Status | table header |
|
||||
| Role | Rola | table header |
|
||||
| Confirmed | Zatwierdzony | account status |
|
||||
| Not confirmed (?) | Niezatwierdzony (?) | unknown for me account status |
|
||||
| Owner | Właściciel | role |
|
||||
| Admin | Administrator | role |
|
||||
| Member | Użytkownik | role |
|
||||
| **Roles Tab** | | |
|
||||
| Search roles | Wyszukaj role | placeholder |
|
||||
| Add Role | Dodaj role | addbutton |
|
||||
| Name | Nazwa | table header |
|
||||
| Description | Opis | table header |
|
||||
| Admin | Administrator | role |
|
||||
| Member | Użytkownik | role |
|
||||
| Admin role with the most permissions | Rola administratora z najszerszymi uprawnieniami | admin role desc |
|
||||
| Members can only view resources | Członkowie mogą jedynie przeglądać zasoby | member role desc |
|
||||
|
||||
##### Invite User popup
|
||||
|
||||
| EN | PL | Notes |
|
||||
| ----------------- | ------------------------------------------ | ----------- |
|
||||
| Invite User | Give new users access to your organization | |
|
||||
| Email | Email | |
|
||||
| Enter an email | Wprowadź email | placeholder |
|
||||
| Role | Rola | |
|
||||
| Select role | Wybierz role | placeholder |
|
||||
| Vaild for | Ważne do | |
|
||||
| 1 day | Dzień | |
|
||||
| 2 days | 2 dni | |
|
||||
| 3 days | 3 dni | |
|
||||
| 4 days | 4 dni | |
|
||||
| 5 days | 5 dni | |
|
||||
| 6 days | 6 dni | |
|
||||
| 7 days | 7 dni | |
|
||||
| Create Invitation | Utwórz zaproszenie | |
|
||||
| Close | Zamknij | |
|
||||
|
||||
|
||||
## Main “Shareable Links”
|
||||
##### “Hero” section
|
||||
|
||||
| EN | PL | Notes |
|
||||
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- |
|
||||
| Shareable Links | Łącza do udostępniania | |
|
||||
| Create shareable links to your resources. Links provide temporary or unlimited access to your resource. You can configure the expiration duration of the link when you create one. | Twórz linki do udostępniania swoich zasobów. Linki zapewniają tymczasowy lub nieograniczony dostęp do zasobu. Możesz skonfigurować czas wygaśnięcia linku podczas jego tworzenia. | |
|
||||
| Easy to create and share | Łatwe tworzenie i udostępnianie | |
|
||||
| Configurable expiration duration | Konfigurowalny czas wygaśnięcia | |
|
||||
| Secure and revocable | Bezpieczne i odwołalne | |
|
||||
##### Content
|
||||
|
||||
| EN | PL | Notes |
|
||||
| ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | ----------------- |
|
||||
| Manage Shareable Links | Zarządzaj łączami do udostępniania | |
|
||||
| Create shareable links to grant temporary or permament access to your resources | Utwórz łącze do udostępniania w celu przyznania tymczasowego lub stałego dostępu do zasobów | |
|
||||
| Search links | Szukaj łączy | placeholder |
|
||||
| Create Share Link | Utwórz nowe łącze | addbutton |
|
||||
| Resource | Zasób | table header |
|
||||
| Title | Tytuł | table header |
|
||||
| Created | Utworzone | table header |
|
||||
| Expires | Wygasa | table header |
|
||||
| No links. Create one to get started. | Brak łączy. Utwórz, aby rozpocząć. | table placeholder |
|
||||
|
||||
##### Create Shareable Link popup
|
||||
|
||||
| EN | PL | Notes |
|
||||
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- |
|
||||
| Create Shareable Link | Utwórz łącze do udostępnienia | |
|
||||
| Anyone with this link can access the resource | Każdy kto ma ten link może korzystać z zasobu | |
|
||||
| Resource | Zasób | |
|
||||
| Select resource | Wybierz zasób | |
|
||||
| Search resources… | Szukaj zasobów… | resource selector popup |
|
||||
| Title (optional) | Tytuł (opcjonalny) | |
|
||||
| Enter title | Wprowadź tytuł | placeholder |
|
||||
| Expire in | Wygasa za | |
|
||||
| Minutes | Minut | |
|
||||
| Hours | Godzin | |
|
||||
| Days | Dni | |
|
||||
| Months | Miesięcy | |
|
||||
| Years | Lat | |
|
||||
| Never expire | Nie wygasa | |
|
||||
| Expiration time is how long the link will be usable and provide access to the resource. After this time, the link will no longer work, and users who used this link will lose access to the resource. | Czas wygaśnięcia to okres, przez który link będzie aktywny i zapewni dostęp do zasobu. Po upływie tego czasu link przestanie działać, a użytkownicy, którzy go użyli, stracą dostęp do zasobu. | |
|
||||
| Create Link | Utwórz łącze | |
|
||||
| Close | Zamknij | |
|
||||
|
||||
|
||||
## Main “General”
|
||||
|
||||
| EN | PL | Notes |
|
||||
| -------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------ |
|
||||
| General | Ogólne | |
|
||||
| Configure your organization’s general settings | Zarządzaj ogólnymi ustawieniami twoich organizacji | |
|
||||
| General | Ogólne | sidebar item |
|
||||
| Organization Settings | Ustawienia organizacji | |
|
||||
| Manage your organization details and configuration | Zarządzaj szczegółami i konfiguracją organizacji | |
|
||||
| Name | Nazwa | |
|
||||
| This is the display name of the org | To jest wyświetlana nazwa Twojej organizacji | |
|
||||
| Save Settings | Zapisz ustawienia | |
|
||||
| Danger Zone | Niebezpieczna strefa | |
|
||||
| Once you delete this org, there is no going back. Please be certain. | Jeśli usuniesz swoją tą organizację, nie ma odwrotu. Bądź ostrożny! | |
|
||||
| Delete Organization Data | Usuń dane organizacji | |
|
|
@ -1,310 +0,0 @@
|
|||
## Authentication Site
|
||||
|
||||
| EN | TR | Notes |
|
||||
| -------------------------------------------------------- | ---------------------------------------------------------------------------------- | ---------- |
|
||||
| Powered by [Pangolin](https://github.com/fosrl/pangolin) | Pangolin Tarafından Destekleniyor | |
|
||||
| Authentication Required | Kimlik Doğrulaması Gerekli | |
|
||||
| Choose your preferred method to access {resource} | {resource}'a erişmek için tercih ettiğiniz yöntemi seçin | |
|
||||
| PIN | PIN | |
|
||||
| User | Kullanıcı | |
|
||||
| 6-digit PIN Code | 6 haneli PIN Kodu | pin login |
|
||||
| Login in with PIN | PIN ile Giriş Yap | pin login |
|
||||
| Email | E-posta | user login |
|
||||
| Enter your email | E-postanızı girin | user login |
|
||||
| Password | Şifre | user login |
|
||||
| Enter your password | Şifrenizi girin | user login |
|
||||
| Forgot your password? | Şifrenizi mi unuttunuz? | user login |
|
||||
| Log in | Giriş Yap | user login |
|
||||
|
||||
---
|
||||
|
||||
## Login site
|
||||
|
||||
| EN | TR | Notes |
|
||||
| --------------------- | ------------------------------------------------------ | ----------- |
|
||||
| Welcome to Pangolin | Pangolin'e Hoşgeldiniz | |
|
||||
| Log in to get started | Başlamak için giriş yapın | |
|
||||
| Email | E-posta | |
|
||||
| Enter your email | E-posta adresinizi girin | placeholder |
|
||||
| Password | Şifre | |
|
||||
| Enter your password | Şifrenizi girin | placeholder |
|
||||
| Forgot your password? | Şifrenizi mi unuttunuz? | |
|
||||
| Log in | Giriş Yap | |
|
||||
|
||||
---
|
||||
|
||||
# Organization site after successful login
|
||||
|
||||
| EN | TR | Notes |
|
||||
| ----------------------------------------- | ------------------------------------------------------------------- | ----- |
|
||||
| Welcome to Pangolin | Pangolin'e Hoşgeldiniz | |
|
||||
| You're a member of {number} organization. | {number} organizasyonunun üyesiniz. | |
|
||||
|
||||
---
|
||||
|
||||
## Shared Header, Navbar and Footer
|
||||
|
||||
##### Header
|
||||
|
||||
| EN | TR | Notes |
|
||||
| ------------------- | -------------------------- | ----- |
|
||||
| Documentation | Dokümantasyon | |
|
||||
| Support | Destek | |
|
||||
| Organization {name} | Organizasyon {name} | |
|
||||
|
||||
##### Organization selector
|
||||
|
||||
| EN | TR | Notes |
|
||||
| ---------------- | ---------------------- | ----- |
|
||||
| Search… | Ara… | |
|
||||
| Create | Oluştur | |
|
||||
| New Organization | Yeni Organizasyon | |
|
||||
| Organizations | Organizasyonlar | |
|
||||
|
||||
##### Navbar
|
||||
|
||||
| EN | TR | Notes |
|
||||
| --------------- | ------------------------------- | ----- |
|
||||
| Sites | Siteler | |
|
||||
| Resources | Kaynaklar | |
|
||||
| User & Roles | Kullanıcılar ve Roller | |
|
||||
| Shareable Links | Paylaşılabilir Linkler | |
|
||||
| General | Genel | |
|
||||
|
||||
##### Footer
|
||||
|
||||
| EN | TR | Notes |
|
||||
| ------------------------- | ------------------------------------------------ | -------------------- |
|
||||
| Page {number} of {number} | Sayfa {number} / {number} | |
|
||||
| Rows per page | Sayfa başına satırlar | |
|
||||
| Pangolin | Pangolin | Footer'da yer alır |
|
||||
| Built by Fossorial | Fossorial tarafından oluşturuldu | Footer'da yer alır |
|
||||
| Open Source | Açık Kaynak | Footer'da yer alır |
|
||||
| Documentation | Dokümantasyon | Footer'da yer alır |
|
||||
| {version} | {version} | Footer'da yer alır |
|
||||
|
||||
---
|
||||
|
||||
## Main “Sites”
|
||||
|
||||
##### “Hero” section
|
||||
|
||||
| EN | TR | Notes |
|
||||
| ------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------- | ----- |
|
||||
| Newt (Recommended) | Newt (Tavsiye Edilen) | |
|
||||
| For the best user experience, use Newt. It uses WireGuard under the hood and allows you to address your private resources by their LAN address on your private network from within the Pangolin dashboard. | En iyi kullanıcı deneyimi için Newt'i kullanın. Newt, arka planda WireGuard kullanır ve Pangolin kontrol paneli üzerinden özel ağınızdaki kaynaklarınıza LAN adresleriyle erişmenizi sağlar. | |
|
||||
| Runs in Docker | Docker üzerinde çalışır | |
|
||||
| Runs in shell on macOS, Linux, and Windows | macOS, Linux ve Windows’ta komut satırında çalışır | |
|
||||
| Install Newt | Newt'i Yükle | |
|
||||
| Basic WireGuard<br> | Temel WireGuard<br> | |
|
||||
| Compatible with all WireGuard clients<br> | Tüm WireGuard istemcileriyle uyumlu<br> | |
|
||||
| Manual configuration required | Manuel yapılandırma gereklidir | |
|
||||
|
||||
##### Content
|
||||
|
||||
| EN | TR | Notes |
|
||||
| --------------------------------------------------------- | --------------------------------------------------------------------------- | ------------ |
|
||||
| Manage Sites | Siteleri Yönet | |
|
||||
| Allow connectivity to your network through secure tunnels | Güvenli tüneller aracılığıyla ağınıza bağlantı sağlayın | |
|
||||
| Search sites | Siteleri ara | placeholder |
|
||||
| Add Site | Site Ekle | |
|
||||
| Name | Ad | Table Header |
|
||||
| Online | Çevrimiçi | Table Header |
|
||||
| Site | Site | Table Header |
|
||||
| Data In | Gelen Veri | Table Header |
|
||||
| Data Out | Giden Veri | Table Header |
|
||||
| Connection Type | Bağlantı Türü | Table Header |
|
||||
| Online | Çevrimiçi | Site state |
|
||||
| Offline | Çevrimdışı | Site state |
|
||||
| Edit → | Düzenle → | |
|
||||
| View settings | Ayarları Görüntüle | Popup |
|
||||
| Delete | Sil | Popup |
|
||||
|
||||
##### Add Site Popup
|
||||
|
||||
| EN | TR | Notes |
|
||||
| ------------------------------------------------------ | ------------------------------------------------------------------------------------------- | ----------- |
|
||||
| Create Site | Site Oluştur | |
|
||||
| Create a new site to start connection for this site | Bu site için bağlantıyı başlatmak amacıyla yeni bir site oluşturun | |
|
||||
| Name | Ad | |
|
||||
| Site name | Site adı | placeholder |
|
||||
| This is the name that will be displayed for this site. | Bu, site için görüntülenecek addır. | desc |
|
||||
| Method | Yöntem | |
|
||||
| Local | Yerel | |
|
||||
| Newt | Newt | |
|
||||
| WireGuard | WireGuard | |
|
||||
| This is how you will expose connections. | Bağlantılarınızı bu şekilde açığa çıkaracaksınız. | |
|
||||
| You will only be able to see the configuration once. | Yapılandırmayı yalnızca bir kez görüntüleyebilirsiniz. | |
|
||||
| Learn how to install Newt on your system | Sisteminizde Newt'in nasıl kurulacağını öğrenin | |
|
||||
| I have copied the config | Yapılandırmayı kopyaladım | |
|
||||
| Create Site | Site Oluştur | |
|
||||
| Close | Kapat | |
|
||||
|
||||
---
|
||||
|
||||
## Main “Resources”
|
||||
|
||||
##### “Hero” section
|
||||
|
||||
| EN | TR | Notes |
|
||||
| ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ | ----- |
|
||||
| Resources | Kaynaklar | |
|
||||
| Ressourcen sind Proxy-Server für Anwendungen, die in Ihrem privaten Netzwerk laufen. Erstellen Sie eine Ressource für jede HTTP- oder HTTPS-Anwendung in Ihrem privaten Netzwerk. Jede Ressource muss mit einer Website verbunden sein, um eine private und sichere Verbindung über den verschlüsselten WireGuard-Tunnel zu ermöglichen. | Kaynaklar, özel ağınızda çalışan uygulamalar için proxy sunucularıdır. Özel ağınızdaki her HTTP veya HTTPS uygulaması için bir kaynak oluşturun. Her kaynağın, şifrelenmiş WireGuard tüneli üzerinden özel ve güvenli bağlantı sağlamak üzere bir siteyle ilişkili olması gerekir. | |
|
||||
| Secure connectivity with WireGuard encryption | WireGuard şifrelemesiyle güvenli bağlantı | |
|
||||
| Configure multiple authentication methods | Birden çok kimlik doğrulama yöntemini yapılandırın | |
|
||||
| User and role-based access control | Kullanıcı ve role dayalı erişim kontrolü | |
|
||||
|
||||
##### Content
|
||||
|
||||
| EN | TR | Notes |
|
||||
| -------------------------------------------------- | ------------------------------------------------------------- | -------------------- |
|
||||
| Manage Resources | Kaynakları Yönet | |
|
||||
| Create secure proxies to your private applications | Özel uygulamalarınız için güvenli proxy’ler oluşturun | |
|
||||
| Search resources | Kaynakları ara | placeholder |
|
||||
| Name | Ad | |
|
||||
| Site | Site | |
|
||||
| Full URL | Tam URL | |
|
||||
| Authentication | Kimlik Doğrulama | |
|
||||
| Not Protected | Korunmayan | authentication state |
|
||||
| Protected | Korunan | authentication state |
|
||||
| Edit → | Düzenle → | |
|
||||
| Add Resource | Kaynak Ekle | |
|
||||
|
||||
##### Add Resource Popup
|
||||
|
||||
| EN | TR | Notes |
|
||||
| ------------------------------------------------------------ | ----------------------------------------------------------------------------------------------- | ------------- |
|
||||
| Create Resource | Kaynak Oluştur | |
|
||||
| Create a new resource to proxy request to your app | Uygulamanıza gelen istekleri yönlendirmek için yeni bir kaynak oluşturun | |
|
||||
| Name | Ad | |
|
||||
| My Resource | Kaynağım | name placeholder |
|
||||
| This is the name that will be displayed for this resource. | Bu, kaynağın görüntülenecek adıdır. | |
|
||||
| Subdomain | Alt alan adı | |
|
||||
| Enter subdomain | Alt alan adını girin | |
|
||||
| This is the fully qualified domain name that will be used to access the resource. | Kaynağa erişmek için kullanılacak tam nitelikli alan adıdır. | |
|
||||
| Site | Site | |
|
||||
| Search site… | Site ara… | Site selector popup |
|
||||
| This is the site that will be used in the dashboard. | Kontrol panelinde kullanılacak sitedir. | |
|
||||
| Create Resource | Kaynak Oluştur | |
|
||||
| Close | Kapat | |
|
||||
|
||||
---
|
||||
|
||||
## Main “User & Roles”
|
||||
|
||||
##### Content
|
||||
|
||||
| EN | TR | Notes |
|
||||
| ------------------------------------------------------------ | ------------------------------------------------------------------------------------------ | ----------------------------- |
|
||||
| Manage User & Roles | Kullanıcılar ve Rolleri Yönet | |
|
||||
| Invite users and add them to roles to manage access to your organization | Organizasyonunuza erişimi yönetmek için kullanıcıları davet edin ve rollere atayın | |
|
||||
| Users | Kullanıcılar | sidebar item |
|
||||
| Roles | Roller | sidebar item |
|
||||
| **User tab** | **Kullanıcı Sekmesi** | |
|
||||
| Search users | Kullanıcıları ara | placeholder |
|
||||
| Invite User | Kullanıcı Davet Et | addbutton |
|
||||
| Email | E-posta | table header |
|
||||
| Status | Durum | table header |
|
||||
| Role | Rol | table header |
|
||||
| Confirmed | Onaylandı | account status |
|
||||
| Not confirmed (?) | Onaylanmadı (?) | account status |
|
||||
| Owner | Sahip | role |
|
||||
| Admin | Yönetici | role |
|
||||
| Member | Üye | role |
|
||||
| **Roles Tab** | **Roller Sekmesi** | |
|
||||
| Search roles | Rolleri ara | placeholder |
|
||||
| Add Role | Rol Ekle | addbutton |
|
||||
| Name | Ad | table header |
|
||||
| Description | Açıklama | table header |
|
||||
| Admin | Yönetici | role |
|
||||
| Member | Üye | role |
|
||||
| Admin role with the most permissions | En fazla yetkiye sahip yönetici rolü | admin role desc |
|
||||
| Members can only view resources | Üyeler yalnızca kaynakları görüntüleyebilir | member role desc |
|
||||
|
||||
##### Invite User popup
|
||||
|
||||
| EN | TR | Notes |
|
||||
| ----------------- | ----------------------------------------------------------------------- | ----------- |
|
||||
| Invite User | Kullanıcı Davet Et | |
|
||||
| Email | E-posta | |
|
||||
| Enter an email | Bir e-posta adresi girin | placeholder |
|
||||
| Role | Rol | |
|
||||
| Select role | Rol seçin | placeholder |
|
||||
| Gültig für | Geçerlilik Süresi | |
|
||||
| 1 day | 1 gün | |
|
||||
| 2 days | 2 gün | |
|
||||
| 3 days | 3 gün | |
|
||||
| 4 days | 4 gün | |
|
||||
| 5 days | 5 gün | |
|
||||
| 6 days | 6 gün | |
|
||||
| 7 days | 7 gün | |
|
||||
| Create Invitation | Davetiye Oluştur | |
|
||||
| Close | Kapat | |
|
||||
|
||||
---
|
||||
|
||||
## Main “Shareable Links”
|
||||
|
||||
##### “Hero” section
|
||||
|
||||
| EN | TR | Notes |
|
||||
| ------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------- | ----- |
|
||||
| Shareable Links | Paylaşılabilir Bağlantılar | |
|
||||
| Create shareable links to your resources. Links provide temporary or unlimited access to your resource. You can configure the expiration duration of the link when you create one. | Kaynaklarınıza paylaşılabilir bağlantılar oluşturun. Bağlantılar, kaynağınıza geçici veya sınırsız erişim sağlar. Oluştururken bağlantının geçerlilik süresini ayarlayabilirsiniz. | |
|
||||
| Easy to create and share | Oluşturması ve paylaşması kolay | |
|
||||
| Configurable expiration duration | Yapılandırılabilir geçerlilik süresi | |
|
||||
| Secure and revocable | Güvenli ve iptal edilebilir | |
|
||||
|
||||
##### Content
|
||||
|
||||
| EN | TR | Notes |
|
||||
| ------------------------------------------------------------ | ---------------------------------------------------------------------------------------- | -------------- |
|
||||
| Manage Shareable Links | Paylaşılabilir Bağlantıları Yönet | |
|
||||
| Create shareable links to grant temporary or permanent access to your resources | Kaynaklarınıza geçici veya kalıcı erişim sağlamak için paylaşılabilir bağlantılar oluşturun | |
|
||||
| Search links | Bağlantıları ara | placeholder |
|
||||
| Create Share Link | Bağlantı Oluştur | addbutton |
|
||||
| Resource | Kaynak | table header |
|
||||
| Title | Başlık | table header |
|
||||
| Created | Oluşturulma Tarihi | table header |
|
||||
| Expires | Son Kullanma Tarihi | table header |
|
||||
| No links. Create one to get started. | Bağlantı yok. Başlamak için bir tane oluşturun. | table placeholder |
|
||||
|
||||
##### Create Shareable Link popup
|
||||
|
||||
| EN | TR | Notes |
|
||||
| ------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------- | ----------------------- |
|
||||
| Create Shareable Link | Paylaşılabilir Bağlantı Oluştur | |
|
||||
| Anyone with this link can access the resource | Bu bağlantıya sahip olan herkes kaynağa erişebilir | |
|
||||
| Resource | Kaynak | |
|
||||
| Select resource | Kaynak seçin | |
|
||||
| Search resources… | Kaynak ara… | resource selector popup |
|
||||
| Title (optional) | Başlık (isteğe bağlı) | |
|
||||
| Enter title | Başlık girin | placeholder |
|
||||
| Expire in | Sona Erme Süresi | |
|
||||
| Minutes | Dakika | |
|
||||
| Hours | Saat | |
|
||||
| Days | Gün | |
|
||||
| Months | Ay | |
|
||||
| Years | Yıl | |
|
||||
| Never expire | Asla sona erme | |
|
||||
| Expiration time is how long the link will be usable and provide access to the resource. After this time, the link will no longer work, and users who used this link will lose access to the resource. | Bağlantının geçerlilik süresi, bağlantının ne kadar süreyle kullanılabilir olacağını ve kaynağa erişim sağlayacağını belirler. Bu sürenin sonunda bağlantı çalışmaz hale gelir ve bağlantıyı kullananlar kaynağa erişimini kaybeder. | |
|
||||
| Create Link | Bağlantı Oluştur | |
|
||||
| Close | Kapat | |
|
||||
|
||||
---
|
||||
|
||||
## Main “General”
|
||||
|
||||
| EN | TR | Notes |
|
||||
| ------------------------------------------------------------ | ------------------------------------------------------------------------------------------- | ------------ |
|
||||
| General | Genel | |
|
||||
| Configure your organization’s general settings | Organizasyonunuzun genel ayarlarını yapılandırın | |
|
||||
| General | Genel | sidebar item |
|
||||
| Organization Settings | Organizasyon Ayarları | |
|
||||
| Manage your organization details and configuration | Organizasyonunuzun detaylarını ve yapılandırmasını yönetin | |
|
||||
| Name | Ad | |
|
||||
| This is the display name of the org | Bu, organizasyonunuzun görüntülenecek adıdır. | |
|
||||
| Save Settings | Ayarları Kaydet | |
|
||||
| Danger Zone | Tehlikeli Bölge | |
|
||||
| Once you delete this org, there is no going back. Please be certain. | Bu organizasyonu sildikten sonra geri dönüş yoktur. Lütfen emin olun. | |
|
||||
| Delete Organization Data | Organizasyon Verilerini Sil | |
|
1322
messages/cs-CZ.json
Normal file
1322
messages/de-DE.json
Normal file
1322
messages/en-US.json
Normal file
1322
messages/es-ES.json
Normal file
1322
messages/fr-FR.json
Normal file
1322
messages/it-IT.json
Normal file
1322
messages/ko-KR.json
Normal file
1322
messages/nl-NL.json
Normal file
1322
messages/pl-PL.json
Normal file
1322
messages/pt-PT.json
Normal file
1322
messages/ru-RU.json
Normal file
1322
messages/tr-TR.json
Normal file
1322
messages/zh-CN.json
Normal file
BIN
newt
|
@ -1,9 +1,13 @@
|
|||
/** @type {import('next').NextConfig} */
|
||||
import createNextIntlPlugin from "next-intl/plugin";
|
||||
|
||||
const withNextIntl = createNextIntlPlugin();
|
||||
|
||||
/** @type {import("next").NextConfig} */
|
||||
const nextConfig = {
|
||||
eslint: {
|
||||
ignoreDuringBuilds: true,
|
||||
ignoreDuringBuilds: true
|
||||
},
|
||||
output: "standalone"
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
export default withNextIntl(nextConfig);
|
||||
|
|
7079
package-lock.json
generated
152
package.json
|
@ -12,123 +12,139 @@
|
|||
"license": "SEE LICENSE IN LICENSE AND README.md",
|
||||
"scripts": {
|
||||
"dev": "NODE_ENV=development ENVIRONMENT=dev tsx watch server/index.ts",
|
||||
"db:generate": "drizzle-kit generate",
|
||||
"db:push": "npx tsx server/db/migrate.ts",
|
||||
"db:studio": "drizzle-kit studio",
|
||||
"build": "mkdir -p dist && next build && node esbuild.mjs -e server/index.ts -o dist/server.mjs && node esbuild.mjs -e server/setup/migrations.ts -o dist/migrations.mjs",
|
||||
"start": "NODE_OPTIONS=--enable-source-maps NODE_ENV=development ENVIRONMENT=prod sh -c 'node dist/migrations.mjs && node dist/server.mjs'",
|
||||
"email": "email dev --dir server/emails/templates --port 3005"
|
||||
"db:pg:generate": "drizzle-kit generate --config=./drizzle.pg.config.ts",
|
||||
"db:sqlite:generate": "drizzle-kit generate --config=./drizzle.sqlite.config.ts",
|
||||
"db:pg:push": "npx tsx server/db/pg/migrate.ts",
|
||||
"db:sqlite:push": "npx tsx server/db/sqlite/migrate.ts",
|
||||
"db:sqlite:studio": "drizzle-kit studio --config=./drizzle.sqlite.config.ts",
|
||||
"db:pg:studio": "drizzle-kit studio --config=./drizzle.pg.config.ts",
|
||||
"db:clear-migrations": "rm -rf server/migrations",
|
||||
"build:sqlite": "mkdir -p dist && next build && node esbuild.mjs -e server/index.ts -o dist/server.mjs && node esbuild.mjs -e server/setup/migrationsSqlite.ts -o dist/migrations.mjs",
|
||||
"build:pg": "mkdir -p dist && next build && node esbuild.mjs -e server/index.ts -o dist/server.mjs && node esbuild.mjs -e server/setup/migrationsPg.ts -o dist/migrations.mjs",
|
||||
"start:sqlite": "DB_TYPE=sqlite NODE_OPTIONS=--enable-source-maps NODE_ENV=development ENVIRONMENT=prod sh -c 'node dist/migrations.mjs && node dist/server.mjs'",
|
||||
"start:pg": "DB_TYPE=pg NODE_OPTIONS=--enable-source-maps NODE_ENV=development ENVIRONMENT=prod sh -c 'node dist/migrations.mjs && node dist/server.mjs'",
|
||||
"email": "email dev --dir server/emails/templates --port 3005",
|
||||
"build:cli": "node esbuild.mjs -e cli/index.ts -o dist/cli.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@asteasolutions/zod-to-openapi": "^7.3.0",
|
||||
"@asteasolutions/zod-to-openapi": "^7.3.4",
|
||||
"@hookform/resolvers": "3.9.1",
|
||||
"@node-rs/argon2": "2.0.2",
|
||||
"@node-rs/argon2": "^2.0.2",
|
||||
"@oslojs/crypto": "1.0.1",
|
||||
"@oslojs/encoding": "1.1.0",
|
||||
"@radix-ui/react-avatar": "1.1.2",
|
||||
"@radix-ui/react-checkbox": "1.1.3",
|
||||
"@radix-ui/react-collapsible": "1.1.2",
|
||||
"@radix-ui/react-dialog": "1.1.4",
|
||||
"@radix-ui/react-dropdown-menu": "2.1.4",
|
||||
"@radix-ui/react-avatar": "1.1.10",
|
||||
"@radix-ui/react-checkbox": "1.3.2",
|
||||
"@radix-ui/react-collapsible": "1.1.11",
|
||||
"@radix-ui/react-dialog": "1.1.14",
|
||||
"@radix-ui/react-dropdown-menu": "2.1.15",
|
||||
"@radix-ui/react-icons": "1.3.2",
|
||||
"@radix-ui/react-label": "2.1.1",
|
||||
"@radix-ui/react-popover": "1.1.4",
|
||||
"@radix-ui/react-progress": "^1.1.4",
|
||||
"@radix-ui/react-radio-group": "1.2.2",
|
||||
"@radix-ui/react-select": "2.1.4",
|
||||
"@radix-ui/react-separator": "1.1.1",
|
||||
"@radix-ui/react-slot": "1.1.1",
|
||||
"@radix-ui/react-switch": "1.1.2",
|
||||
"@radix-ui/react-tabs": "1.1.2",
|
||||
"@radix-ui/react-toast": "1.2.4",
|
||||
"@react-email/components": "0.0.36",
|
||||
"@react-email/render": "^1.0.6",
|
||||
"@react-email/tailwind": "1.0.4",
|
||||
"@radix-ui/react-label": "2.1.7",
|
||||
"@radix-ui/react-popover": "1.1.14",
|
||||
"@radix-ui/react-progress": "^1.1.7",
|
||||
"@radix-ui/react-radio-group": "1.3.7",
|
||||
"@radix-ui/react-scroll-area": "^1.2.9",
|
||||
"@radix-ui/react-select": "2.2.5",
|
||||
"@radix-ui/react-separator": "1.1.7",
|
||||
"@radix-ui/react-slot": "1.2.3",
|
||||
"@radix-ui/react-switch": "1.2.5",
|
||||
"@radix-ui/react-tabs": "1.1.12",
|
||||
"@radix-ui/react-toast": "1.2.14",
|
||||
"@radix-ui/react-tooltip": "^1.2.7",
|
||||
"@react-email/components": "0.3.1",
|
||||
"@react-email/render": "^1.1.2",
|
||||
"@simplewebauthn/browser": "^13.1.0",
|
||||
"@simplewebauthn/server": "^9.0.3",
|
||||
"@react-email/tailwind": "1.2.1",
|
||||
"@tailwindcss/forms": "^0.5.10",
|
||||
"@tanstack/react-table": "8.20.6",
|
||||
"arctic": "^3.6.0",
|
||||
"axios": "1.8.4",
|
||||
"@tanstack/react-table": "8.21.3",
|
||||
"arctic": "^3.7.0",
|
||||
"axios": "1.10.0",
|
||||
"better-sqlite3": "11.7.0",
|
||||
"canvas-confetti": "1.9.3",
|
||||
"class-variance-authority": "0.7.1",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "2.1.1",
|
||||
"cmdk": "1.0.4",
|
||||
"cmdk": "1.1.1",
|
||||
"cookie": "^1.0.2",
|
||||
"cookie-parser": "1.4.7",
|
||||
"cookies": "^0.9.1",
|
||||
"cors": "2.8.5",
|
||||
"crypto-js": "^4.2.0",
|
||||
"drizzle-orm": "0.38.3",
|
||||
"eslint": "9.17.0",
|
||||
"eslint-config-next": "15.1.3",
|
||||
"drizzle-orm": "0.44.2",
|
||||
"eslint": "9.31.0",
|
||||
"eslint-config-next": "15.3.5",
|
||||
"express": "4.21.2",
|
||||
"express-rate-limit": "7.5.0",
|
||||
"glob": "11.0.0",
|
||||
"helmet": "8.0.0",
|
||||
"express-rate-limit": "7.5.1",
|
||||
"glob": "11.0.3",
|
||||
"helmet": "8.1.0",
|
||||
"http-errors": "2.0.0",
|
||||
"i": "^0.3.7",
|
||||
"input-otp": "1.4.1",
|
||||
"input-otp": "1.4.2",
|
||||
"jmespath": "^0.16.0",
|
||||
"js-yaml": "4.1.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"lucide-react": "0.469.0",
|
||||
"lucide-react": "0.525.0",
|
||||
"moment": "2.30.1",
|
||||
"next": "15.2.4",
|
||||
"next-themes": "0.4.4",
|
||||
"next": "15.3.5",
|
||||
"next-intl": "^4.3.4",
|
||||
"next-themes": "0.4.6",
|
||||
"node-cache": "5.1.2",
|
||||
"node-fetch": "3.3.2",
|
||||
"nodemailer": "6.9.16",
|
||||
"npm": "^11.2.0",
|
||||
"nodemailer": "7.0.5",
|
||||
"npm": "^11.4.2",
|
||||
"oslo": "1.2.1",
|
||||
"pg": "^8.16.2",
|
||||
"qrcode.react": "4.2.0",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"react-easy-sort": "^1.6.0",
|
||||
"react-hook-form": "7.54.2",
|
||||
"react-hook-form": "7.60.0",
|
||||
"react-icons": "^5.5.0",
|
||||
"rebuild": "0.1.2",
|
||||
"semver": "7.6.3",
|
||||
"semver": "^7.7.2",
|
||||
"swagger-ui-express": "^5.0.1",
|
||||
"tailwind-merge": "2.6.0",
|
||||
"tw-animate-css": "^1.2.5",
|
||||
"tailwind-merge": "3.3.1",
|
||||
"tw-animate-css": "^1.3.5",
|
||||
"uuid": "^11.1.0",
|
||||
"vaul": "1.1.2",
|
||||
"winston": "3.17.0",
|
||||
"winston-daily-rotate-file": "5.0.0",
|
||||
"ws": "8.18.0",
|
||||
"zod": "3.24.1",
|
||||
"zod-validation-error": "3.4.0"
|
||||
"ws": "8.18.3",
|
||||
"zod": "3.25.76",
|
||||
"zod-validation-error": "3.5.2",
|
||||
"yargs": "18.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@dotenvx/dotenvx": "1.32.0",
|
||||
"@dotenvx/dotenvx": "1.47.6",
|
||||
"@esbuild-plugins/tsconfig-paths": "0.1.2",
|
||||
"@tailwindcss/postcss": "^4.1.3",
|
||||
"@tailwindcss/postcss": "^4.1.10",
|
||||
"@types/better-sqlite3": "7.6.12",
|
||||
"@types/cookie-parser": "1.4.8",
|
||||
"@types/cors": "2.8.17",
|
||||
"@types/cookie-parser": "1.4.9",
|
||||
"@types/cors": "2.8.19",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/express": "5.0.0",
|
||||
"@types/express-session": "^1.18.2",
|
||||
"@types/jmespath": "^0.15.2",
|
||||
"@types/js-yaml": "4.0.9",
|
||||
"@types/jsonwebtoken": "^9.0.9",
|
||||
"@types/node": "^22",
|
||||
"@types/jsonwebtoken": "^9.0.10",
|
||||
"@types/node": "^24",
|
||||
"@types/nodemailer": "6.4.17",
|
||||
"@types/react": "19.1.1",
|
||||
"@types/react-dom": "19.1.2",
|
||||
"@types/semver": "7.5.8",
|
||||
"@types/pg": "8.15.4",
|
||||
"@types/react": "19.1.8",
|
||||
"@types/react-dom": "19.1.6",
|
||||
"@types/semver": "^7.7.0",
|
||||
"@types/swagger-ui-express": "^4.1.8",
|
||||
"@types/ws": "8.5.13",
|
||||
"@types/ws": "8.18.1",
|
||||
"@types/yargs": "17.0.33",
|
||||
"drizzle-kit": "0.30.6",
|
||||
"esbuild": "0.25.2",
|
||||
"drizzle-kit": "0.31.4",
|
||||
"esbuild": "0.25.6",
|
||||
"esbuild-node-externals": "1.18.0",
|
||||
"postcss": "^8",
|
||||
"react-email": "4.0.6",
|
||||
"react-email": "4.1.0",
|
||||
"tailwindcss": "^4.1.4",
|
||||
"tsc-alias": "1.8.10",
|
||||
"tsx": "4.19.3",
|
||||
"tsc-alias": "1.8.16",
|
||||
"tsx": "4.20.3",
|
||||
"typescript": "^5",
|
||||
"yargs": "17.7.2"
|
||||
"typescript-eslint": "^8.36.0"
|
||||
},
|
||||
"overrides": {
|
||||
"emblor": {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/** @type {import('postcss-load-config').Config} */
|
||||
const config = {
|
||||
plugins: {
|
||||
'@tailwindcss/postcss': {},
|
||||
"@tailwindcss/postcss": {},
|
||||
},
|
||||
};
|
||||
|
||||
|
|
BIN
public/auth-diagram1.png
Normal file
After Width: | Height: | Size: 647 KiB |
BIN
public/clip.gif
Normal file
After Width: | Height: | Size: 500 KiB |
132
public/diagram-dark.svg
Normal file
After Width: | Height: | Size: 52 KiB |
132
public/diagram.svg
Normal file
After Width: | Height: | Size: 52 KiB |
|
@ -1,22 +1,21 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="900.82861"
|
||||
height="955.20648"
|
||||
viewBox="0 0 238.34422 252.7317"
|
||||
version="1.1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 399.99999 400.00002"
|
||||
enable-background="new 0 0 419.528 419.528"
|
||||
xml:space="preserve"
|
||||
id="svg52"
|
||||
sodipodi:docname="noun-pangolin-1798092.svg"
|
||||
width="400"
|
||||
height="400"
|
||||
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||
id="svg420"
|
||||
inkscape:export-filename="logo.svg"
|
||||
inkscape:export-xdpi="221.14999"
|
||||
inkscape:export-ydpi="221.14999"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs56" /><sodipodi:namedview
|
||||
id="namedview54"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview422"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
|
@ -24,15 +23,18 @@
|
|||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.9583914"
|
||||
inkscape:cx="209.86611"
|
||||
inkscape:cy="262.20499"
|
||||
inkscape:window-width="3840"
|
||||
inkscape:window-height="2136"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg52" /><path
|
||||
d="m 62.232921,184.91974 c 0,2.431 -1.97,4.402 -4.399,4.402 -2.429,0 -4.399,-1.972 -4.399,-4.402 0,-2.429 1.97,-4.399 4.399,-4.399 2.429,-10e-4 4.399,1.97 4.399,4.399 z m 58.993999,-4.821 c -25.943999,-2.826 -38.978999,7.453 -71.181999,31.357 -27.572,20.467 -32.767,4.381 -31.748,-2.614 1.499,-10.282 25.222,-58.573 48.079,-88.461 28.273,7.34 49.869999,30.727 54.850999,59.718 z m -55.915999,4.821 c 0,-4.131 -3.349,-7.478 -7.478,-7.478 -4.129,0 -7.478,3.347 -7.478,7.478 0,4.131 3.349,7.481 7.478,7.481 4.13,0 7.478,-3.35 7.478,-7.481 z m -15.032,48.424 -0.234,14.041 20.413,22.687 -9.818,7.353 33.306,27.492 -11.759,8.124 42.631999,19.939 -10.825,9.747 48.291,8.078 -7.526,10.307 48.758,-4.531 -3.997,11.725 53.916,-18.153 -2.76,13.357 48.077,-34.345 1.479,13.562 34.087,-48.576 7.478,14.206 15.187,-58.89 10.391,8.533 -2.14,-57.884 13.814,5.13 -21.082,-51.204 13.404,0.048 -33.696,-42.131 15.312,-1.366 -47.026,-32.831002 14.255,-8.399 -54.817,-14.682 9.257,-11.695 -49.625,0.352 0.6,-13.337 -38.537,14.084 -1.597,-12.689 -29.984,21.429 -6.446,-10.852 -22.59,26.504 -7.021,-9.572 -18.923,30.294 -9.595999,-8.744 -16.754,30.138002 c 31.509999,10.197 54.979999,37.951 59.126999,71.547 0.404,0.087 -22.37,31.257 10.955,57.85 -0.576,-2.985 -6.113,-53.902 47.496,-57.61 26.668,-1.844 48.4,21.666 48.4,48.399 0,8.184 -2.05,15.883 -5.636,22.64 -15.927,29.611 -64.858,30.755 -80.429,30.596 -45.154,-0.459 -104.051999,-51.521 -104.051999,-51.521 z"
|
||||
id="path46" /></svg>
|
||||
inkscape:document-units="mm"
|
||||
showgrid="false" />
|
||||
<defs
|
||||
id="defs417" />
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-13.119542,-5.9258171)">
|
||||
<path
|
||||
d="m 213.66176,90.072122 c 4.95655,0 8.97383,4.018046 8.97383,8.973827 0,4.956581 -4.01728,8.974621 -8.97383,8.974621 -4.95657,0 -8.97462,-4.01804 -8.97462,-8.974621 0,-4.955781 4.01805,-8.973827 8.97462,-8.973827 z m 35.2316,37.450998 c -0.90048,29.80928 -23.66033,69.21262 -54.51292,79.34466 -36.04206,11.836 -63.40991,-5.92226 -72.08409,-26.74061 -6.75754,-16.21966 -1.65117,-35.62363 10.96266,-43.83669 10.6506,-6.93533 30.48543,-8.76736 47.15454,2.19144 -5.85627,-15.34246 -21.62491,-25.4256 -35.59101,-28.49424 -13.96613,-3.06867 -28.38324,0.43858 -38.74504,5.69946 13.29071,-14.68572 44.40801,-28.946049 78.24077,-10.95958 22.67676,12.05491 32.43775,28.93208 42.0489,51.72763 C 251.59637,117.87858 234.026,71.411066 203.39074,43.794029 172.15544,15.636686 129.95516,4.340214 97.668803,6.103155 108.32483,12.678273 120.84625,22.06586 132.41209,33.053363 81.298533,26.697169 39.174705,38.314245 13.119542,73.749217 27.67508,70.878527 46.868833,69.073666 65.974711,70.016861 28.737658,96.252107 7.1124298,140.38147 18.105298,186.43137 c 6.718497,-11.74129 16.767711,-25.84558 28.726275,-38.62863 -3.677175,34.36994 1.42836,80.83745 45.62293,110.85478 -2.25587,-9.42394 -4.08014,-20.88443 -4.91466,-33.0154 20.673197,16.1282 50.685067,29.42205 87.917917,20.24096 65.77679,-16.21975 83.34719,-79.78335 73.4356,-118.35996"
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0776283"
|
||||
id="path32" />
|
||||
</g>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.5 KiB |
|
@ -1,39 +1,22 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="900.82861"
|
||||
height="955.20648"
|
||||
viewBox="0 0 238.34422 252.7317"
|
||||
version="1.1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 399.99999 400.00002"
|
||||
enable-background="new 0 0 419.528 419.528"
|
||||
xml:space="preserve"
|
||||
id="svg52"
|
||||
sodipodi:docname="pangolin_orange.svg"
|
||||
width="400"
|
||||
height="400"
|
||||
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
id="svg420"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs56" /><sodipodi:namedview
|
||||
id="namedview54"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.9583914"
|
||||
inkscape:cx="127.40048"
|
||||
inkscape:cy="262.71561"
|
||||
inkscape:window-width="1436"
|
||||
inkscape:window-height="1236"
|
||||
inkscape:window-x="2208"
|
||||
inkscape:window-y="511"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg52" /><path
|
||||
d="m 62.232921,184.91974 c 0,2.431 -1.97,4.402 -4.399,4.402 -2.429,0 -4.399,-1.972 -4.399,-4.402 0,-2.429 1.97,-4.399 4.399,-4.399 2.429,-10e-4 4.399,1.97 4.399,4.399 z m 58.993999,-4.821 c -25.943999,-2.826 -38.978999,7.453 -71.181999,31.357 -27.572,20.467 -32.767,4.381 -31.748,-2.614 1.499,-10.282 25.222,-58.573 48.079,-88.461 28.273,7.34 49.869999,30.727 54.850999,59.718 z m -55.915999,4.821 c 0,-4.131 -3.349,-7.478 -7.478,-7.478 -4.129,0 -7.478,3.347 -7.478,7.478 0,4.131 3.349,7.481 7.478,7.481 4.13,0 7.478,-3.35 7.478,-7.481 z m -15.032,48.424 -0.234,14.041 20.413,22.687 -9.818,7.353 33.306,27.492 -11.759,8.124 42.631999,19.939 -10.825,9.747 48.291,8.078 -7.526,10.307 48.758,-4.531 -3.997,11.725 53.916,-18.153 -2.76,13.357 48.077,-34.345 1.479,13.562 34.087,-48.576 7.478,14.206 15.187,-58.89 10.391,8.533 -2.14,-57.884 13.814,5.13 -21.082,-51.204 13.404,0.048 -33.696,-42.131 15.312,-1.366 -47.026,-32.831002 14.255,-8.399 -54.817,-14.682 9.257,-11.695 -49.625,0.352 0.6,-13.337 -38.537,14.084 -1.597,-12.689 -29.984,21.429 -6.446,-10.852 -22.59,26.504 -7.021,-9.572 -18.923,30.294 -9.595999,-8.744 -16.754,30.138002 c 31.509999,10.197 54.979999,37.951 59.126999,71.547 0.404,0.087 -22.37,31.257 10.955,57.85 -0.576,-2.985 -6.113,-53.902 47.496,-57.61 26.668,-1.844 48.4,21.666 48.4,48.399 0,8.184 -2.05,15.883 -5.636,22.64 -15.927,29.611 -64.858,30.755 -80.429,30.596 -45.154,-0.459 -104.051999,-51.521 -104.051999,-51.521 z"
|
||||
id="path46"
|
||||
style="fill:#f97315;fill-opacity:1" /></svg>
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs417" />
|
||||
<g
|
||||
id="layer1"
|
||||
transform="translate(-13.119542,-5.9258171)">
|
||||
<path
|
||||
d="m 213.66176,90.072122 c 4.95655,0 8.97383,4.018046 8.97383,8.973827 0,4.956581 -4.01728,8.974621 -8.97383,8.974621 -4.95657,0 -8.97462,-4.01804 -8.97462,-8.974621 0,-4.955781 4.01805,-8.973827 8.97462,-8.973827 z m 35.2316,37.450998 c -0.90048,29.80928 -23.66033,69.21262 -54.51292,79.34466 -36.04206,11.836 -63.40991,-5.92226 -72.08409,-26.74061 -6.75754,-16.21966 -1.65117,-35.62363 10.96266,-43.83669 10.6506,-6.93533 30.48543,-8.76736 47.15454,2.19144 -5.85627,-15.34246 -21.62491,-25.4256 -35.59101,-28.49424 -13.96613,-3.06867 -28.38324,0.43858 -38.74504,5.69946 13.29071,-14.68572 44.40801,-28.946049 78.24077,-10.95958 22.67676,12.05491 32.43775,28.93208 42.0489,51.72763 C 251.59637,117.87858 234.026,71.411066 203.39074,43.794029 172.15544,15.636686 129.95516,4.340214 97.668803,6.103155 108.32483,12.678273 120.84625,22.06586 132.41209,33.053363 81.298533,26.697169 39.174705,38.314245 13.119542,73.749217 27.67508,70.878527 46.868833,69.073666 65.974711,70.016861 28.737658,96.252107 7.1124298,140.38147 18.105298,186.43137 c 6.718497,-11.74129 16.767711,-25.84558 28.726275,-38.62863 -3.677175,34.36994 1.42836,80.83745 45.62293,110.85478 -2.25587,-9.42394 -4.08014,-20.88443 -4.91466,-33.0154 20.673197,16.1282 50.685067,29.42205 87.917917,20.24096 65.77679,-16.21975 83.34719,-79.78335 73.4356,-118.35996"
|
||||
style="fill:#f36118;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0776283"
|
||||
id="path32" />
|
||||
</g>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 7.4 KiB |
BIN
public/logo/pangolin_profile_picture.png
Normal file
After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 36 KiB |
BIN
public/logo/word_mark_black.png
Normal file
After Width: | Height: | Size: 34 KiB |
BIN
public/logo/word_mark_white.png
Normal file
After Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 574 KiB |
BIN
public/screenshots/create-api-key.png
Normal file
After Width: | Height: | Size: 748 KiB |
BIN
public/screenshots/create-idp.png
Normal file
After Width: | Height: | Size: 688 KiB |
BIN
public/screenshots/create-resource.png
Normal file
After Width: | Height: | Size: 687 KiB |
BIN
public/screenshots/create-share-link.png
Normal file
After Width: | Height: | Size: 669 KiB |
BIN
public/screenshots/create-site.png
Normal file
After Width: | Height: | Size: 713 KiB |
BIN
public/screenshots/edit-resource.png
Normal file
After Width: | Height: | Size: 636 KiB |
Before Width: | Height: | Size: 434 KiB After Width: | Height: | Size: 713 KiB |
BIN
public/screenshots/resource-auth.png
Normal file
After Width: | Height: | Size: 356 KiB |
BIN
public/screenshots/resource-authentication.png
Normal file
After Width: | Height: | Size: 707 KiB |
BIN
public/screenshots/resources.png
Normal file
After Width: | Height: | Size: 713 KiB |
BIN
public/screenshots/roles.png
Normal file
After Width: | Height: | Size: 556 KiB |
BIN
public/screenshots/site-online.png
Normal file
After Width: | Height: | Size: 585 KiB |
BIN
public/screenshots/sites-fade.png
Normal file
After Width: | Height: | Size: 456 KiB |
BIN
public/screenshots/sites.png
Normal file
After Width: | Height: | Size: 674 KiB |
BIN
public/screenshots/users.png
Normal file
After Width: | Height: | Size: 597 KiB |
|
@ -5,23 +5,29 @@ import config from "@server/lib/config";
|
|||
import logger from "@server/logger";
|
||||
import {
|
||||
errorHandlerMiddleware,
|
||||
notFoundMiddleware,
|
||||
rateLimitMiddleware
|
||||
notFoundMiddleware
|
||||
} from "@server/middlewares";
|
||||
import { authenticated, unauthenticated } from "@server/routers/external";
|
||||
import { router as wsRouter, handleWSUpgrade } from "@server/routers/ws";
|
||||
import { logIncomingMiddleware } from "./middlewares/logIncoming";
|
||||
import { csrfProtectionMiddleware } from "./middlewares/csrfProtection";
|
||||
import helmet from "helmet";
|
||||
import rateLimit from "express-rate-limit";
|
||||
import createHttpError from "http-errors";
|
||||
import HttpCode from "./types/HttpCode";
|
||||
import requestTimeoutMiddleware from "./middlewares/requestTimeout";
|
||||
import { createStore } from "./lib/rateLimitStore";
|
||||
|
||||
const dev = config.isDev;
|
||||
const externalPort = config.getRawConfig().server.external_port;
|
||||
|
||||
export function createApiServer() {
|
||||
const apiServer = express();
|
||||
const prefix = `/api/v1`;
|
||||
|
||||
if (config.getRawConfig().server.trust_proxy) {
|
||||
apiServer.set("trust proxy", 1);
|
||||
const trustProxy = config.getRawConfig().server.trust_proxy;
|
||||
if (trustProxy) {
|
||||
apiServer.set("trust proxy", trustProxy);
|
||||
}
|
||||
|
||||
const corsConfig = config.getRawConfig().server.cors;
|
||||
|
@ -53,19 +59,30 @@ export function createApiServer() {
|
|||
apiServer.use(cookieParser());
|
||||
apiServer.use(express.json());
|
||||
|
||||
// Add request timeout middleware
|
||||
apiServer.use(requestTimeoutMiddleware(60000)); // 60 second timeout
|
||||
|
||||
if (!dev) {
|
||||
apiServer.use(
|
||||
rateLimitMiddleware({
|
||||
windowMin:
|
||||
config.getRawConfig().rate_limits.global.window_minutes,
|
||||
rateLimit({
|
||||
windowMs:
|
||||
config.getRawConfig().rate_limits.global.window_minutes *
|
||||
60 *
|
||||
1000,
|
||||
max: config.getRawConfig().rate_limits.global.max_requests,
|
||||
type: "IP_AND_PATH"
|
||||
keyGenerator: (req) => `apiServerGlobal:${req.ip}:${req.path}`,
|
||||
handler: (req, res, next) => {
|
||||
const message = `Rate limit exceeded. You can make ${config.getRawConfig().rate_limits.global.max_requests} requests every ${config.getRawConfig().rate_limits.global.window_minutes} minute(s).`;
|
||||
return next(
|
||||
createHttpError(HttpCode.TOO_MANY_REQUESTS, message)
|
||||
);
|
||||
},
|
||||
store: createStore()
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// API routes
|
||||
const prefix = `/api/v1`;
|
||||
apiServer.use(logIncomingMiddleware);
|
||||
apiServer.use(prefix, unauthenticated);
|
||||
apiServer.use(prefix, authenticated);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Request } from "express";
|
||||
import { db } from "@server/db";
|
||||
import { userActions, roleActions, userOrgs } from "@server/db/schemas";
|
||||
import { userActions, roleActions, userOrgs } from "@server/db";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import createHttpError from "http-errors";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
|
@ -56,6 +56,8 @@ export enum ActionsEnum {
|
|||
// removeUserAction = "removeUserAction",
|
||||
// removeUserSite = "removeUserSite",
|
||||
getOrgUser = "getOrgUser",
|
||||
updateUser = "updateUser",
|
||||
getUser = "getUser",
|
||||
setResourcePassword = "setResourcePassword",
|
||||
setResourcePincode = "setResourcePincode",
|
||||
setResourceWhitelist = "setResourceWhitelist",
|
||||
|
@ -67,6 +69,11 @@ export enum ActionsEnum {
|
|||
deleteResourceRule = "deleteResourceRule",
|
||||
listResourceRules = "listResourceRules",
|
||||
updateResourceRule = "updateResourceRule",
|
||||
createClient = "createClient",
|
||||
deleteClient = "deleteClient",
|
||||
updateClient = "updateClient",
|
||||
listClients = "listClients",
|
||||
getClient = "getClient",
|
||||
listOrgDomains = "listOrgDomains",
|
||||
createNewt = "createNewt",
|
||||
createIdp = "createIdp",
|
||||
|
@ -85,7 +92,10 @@ export enum ActionsEnum {
|
|||
setApiKeyOrgs = "setApiKeyOrgs",
|
||||
listApiKeyActions = "listApiKeyActions",
|
||||
listApiKeys = "listApiKeys",
|
||||
getApiKey = "getApiKey"
|
||||
getApiKey = "getApiKey",
|
||||
createOrgDomain = "createOrgDomain",
|
||||
deleteOrgDomain = "deleteOrgDomain",
|
||||
restartOrgDomain = "restartOrgDomain"
|
||||
}
|
||||
|
||||
export async function checkUserActionPermission(
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import db from "@server/db";
|
||||
import { db } from "@server/db";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { roleResources, userResources } from "@server/db/schemas";
|
||||
import { roleResources, userResources } from "@server/db";
|
||||
|
||||
export async function canUserAccessResource({
|
||||
userId,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import db from "@server/db";
|
||||
import { UserInvite, userInvites } from "@server/db/schemas";
|
||||
import { db } from "@server/db";
|
||||
import { UserInvite, userInvites } from "@server/db";
|
||||
import { isWithinExpirationDate } from "oslo";
|
||||
import { verifyPassword } from "./password";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
import { db } from '@server/db';
|
||||
import { limitsTable } from '@server/db/schemas';
|
||||
import { and, eq } from 'drizzle-orm';
|
||||
import createHttpError from 'http-errors';
|
||||
import HttpCode from '@server/types/HttpCode';
|
||||
|
||||
interface CheckLimitOptions {
|
||||
orgId: string;
|
||||
limitName: string;
|
||||
currentValue: number;
|
||||
increment?: number;
|
||||
}
|
||||
|
||||
export async function checkOrgLimit({ orgId, limitName, currentValue, increment = 0 }: CheckLimitOptions): Promise<boolean> {
|
||||
try {
|
||||
const limit = await db.select()
|
||||
.from(limitsTable)
|
||||
.where(
|
||||
and(
|
||||
eq(limitsTable.orgId, orgId),
|
||||
eq(limitsTable.name, limitName)
|
||||
)
|
||||
)
|
||||
.limit(1);
|
||||
|
||||
if (limit.length === 0) {
|
||||
throw createHttpError(HttpCode.NOT_FOUND, `Limit "${limitName}" not found for organization`);
|
||||
}
|
||||
|
||||
const limitValue = limit[0].value;
|
||||
|
||||
// Check if the current value plus the increment is within the limit
|
||||
return (currentValue + increment) <= limitValue;
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
throw createHttpError(HttpCode.INTERNAL_SERVER_ERROR, `Error checking limit: ${error.message}`);
|
||||
}
|
||||
throw createHttpError(HttpCode.INTERNAL_SERVER_ERROR, 'Unknown error occurred while checking limit');
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import db from "@server/db";
|
||||
import { resourceOtp } from "@server/db/schemas";
|
||||
import { db } from "@server/db";
|
||||
import { resourceOtp } from "@server/db";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { createDate, isWithinExpirationDate, TimeSpan } from "oslo";
|
||||
import { alphabet, generateRandomString, sha256 } from "oslo/crypto";
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { TimeSpan, createDate } from "oslo";
|
||||
import { generateRandomString, alphabet } from "oslo/crypto";
|
||||
import db from "@server/db";
|
||||
import { users, emailVerificationCodes } from "@server/db/schemas";
|
||||
import { db } from "@server/db";
|
||||
import { users, emailVerificationCodes } from "@server/db";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { sendEmail } from "@server/emails";
|
||||
import config from "@server/lib/config";
|
||||
|
|
|
@ -9,8 +9,8 @@ import {
|
|||
sessions,
|
||||
User,
|
||||
users
|
||||
} from "@server/db/schemas";
|
||||
import db from "@server/db";
|
||||
} from "@server/db";
|
||||
import { db } from "@server/db";
|
||||
import { eq, inArray } from "drizzle-orm";
|
||||
import config from "@server/lib/config";
|
||||
import type { RandomReader } from "@oslojs/crypto/random";
|
||||
|
|
|
@ -2,8 +2,8 @@ import {
|
|||
encodeHexLowerCase,
|
||||
} from "@oslojs/encoding";
|
||||
import { sha256 } from "@oslojs/crypto/sha2";
|
||||
import { Newt, newts, newtSessions, NewtSession } from "@server/db/schemas";
|
||||
import db from "@server/db";
|
||||
import { Newt, newts, newtSessions, NewtSession } from "@server/db";
|
||||
import { db } from "@server/db";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
export const EXPIRES = 1000 * 60 * 60 * 24 * 30;
|
||||
|
|
72
server/auth/sessions/olm.ts
Normal file
|
@ -0,0 +1,72 @@
|
|||
import {
|
||||
encodeHexLowerCase,
|
||||
} from "@oslojs/encoding";
|
||||
import { sha256 } from "@oslojs/crypto/sha2";
|
||||
import { Olm, olms, olmSessions, OlmSession } from "@server/db";
|
||||
import { db } from "@server/db";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
export const EXPIRES = 1000 * 60 * 60 * 24 * 30;
|
||||
|
||||
export async function createOlmSession(
|
||||
token: string,
|
||||
olmId: string,
|
||||
): Promise<OlmSession> {
|
||||
const sessionId = encodeHexLowerCase(
|
||||
sha256(new TextEncoder().encode(token)),
|
||||
);
|
||||
const session: OlmSession = {
|
||||
sessionId: sessionId,
|
||||
olmId,
|
||||
expiresAt: new Date(Date.now() + EXPIRES).getTime(),
|
||||
};
|
||||
await db.insert(olmSessions).values(session);
|
||||
return session;
|
||||
}
|
||||
|
||||
export async function validateOlmSessionToken(
|
||||
token: string,
|
||||
): Promise<SessionValidationResult> {
|
||||
const sessionId = encodeHexLowerCase(
|
||||
sha256(new TextEncoder().encode(token)),
|
||||
);
|
||||
const result = await db
|
||||
.select({ olm: olms, session: olmSessions })
|
||||
.from(olmSessions)
|
||||
.innerJoin(olms, eq(olmSessions.olmId, olms.olmId))
|
||||
.where(eq(olmSessions.sessionId, sessionId));
|
||||
if (result.length < 1) {
|
||||
return { session: null, olm: null };
|
||||
}
|
||||
const { olm, session } = result[0];
|
||||
if (Date.now() >= session.expiresAt) {
|
||||
await db
|
||||
.delete(olmSessions)
|
||||
.where(eq(olmSessions.sessionId, session.sessionId));
|
||||
return { session: null, olm: null };
|
||||
}
|
||||
if (Date.now() >= session.expiresAt - (EXPIRES / 2)) {
|
||||
session.expiresAt = new Date(
|
||||
Date.now() + EXPIRES,
|
||||
).getTime();
|
||||
await db
|
||||
.update(olmSessions)
|
||||
.set({
|
||||
expiresAt: session.expiresAt,
|
||||
})
|
||||
.where(eq(olmSessions.sessionId, session.sessionId));
|
||||
}
|
||||
return { session, olm };
|
||||
}
|
||||
|
||||
export async function invalidateOlmSession(sessionId: string): Promise<void> {
|
||||
await db.delete(olmSessions).where(eq(olmSessions.sessionId, sessionId));
|
||||
}
|
||||
|
||||
export async function invalidateAllOlmSessions(olmId: string): Promise<void> {
|
||||
await db.delete(olmSessions).where(eq(olmSessions.olmId, olmId));
|
||||
}
|
||||
|
||||
export type SessionValidationResult =
|
||||
| { session: OlmSession; olm: Olm }
|
||||
| { session: null; olm: null };
|
|
@ -1,7 +1,7 @@
|
|||
import { encodeHexLowerCase } from "@oslojs/encoding";
|
||||
import { sha256 } from "@oslojs/crypto/sha2";
|
||||
import { resourceSessions, ResourceSession } from "@server/db/schemas";
|
||||
import db from "@server/db";
|
||||
import { resourceSessions, ResourceSession } from "@server/db";
|
||||
import { db } from "@server/db";
|
||||
import { eq, and } from "drizzle-orm";
|
||||
import config from "@server/lib/config";
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { verify } from "@node-rs/argon2";
|
||||
import db from "@server/db";
|
||||
import { twoFactorBackupCodes } from "@server/db/schemas";
|
||||
import { db } from "@server/db";
|
||||
import { twoFactorBackupCodes } from "@server/db";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { decodeHex } from "oslo/encoding";
|
||||
import { TOTPController } from "oslo/otp";
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import db from "@server/db";
|
||||
import { db } from "@server/db";
|
||||
import {
|
||||
Resource,
|
||||
ResourceAccessToken,
|
||||
resourceAccessToken,
|
||||
resources
|
||||
} from "@server/db/schemas";
|
||||
} from "@server/db";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { isWithinExpirationDate } from "oslo";
|
||||
import { verifyPassword } from "./password";
|
||||
|
|