Compare commits
1705 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1135bfafd5 | ||
|
|
ac9716c355 | ||
|
|
f501907264 | ||
|
|
39774542a5 | ||
|
|
783b81bc45 | ||
|
|
af57cb0fed | ||
|
|
6470fbe9f7 | ||
|
|
19f3be2895 | ||
|
|
8404dde243 | ||
|
|
aa52d695e6 | ||
|
|
4c83d2cbc3 | ||
|
|
014973a575 | ||
|
|
84c8cb3450 | ||
|
|
9aba52c276 | ||
|
|
bbfeb5ec16 | ||
|
|
25f2c68738 | ||
|
|
cbd2419f63 | ||
|
|
b61ffef9e8 | ||
|
|
18fcc43326 | ||
|
|
dd63154bf7 | ||
|
|
fd2fce8a8d | ||
|
|
48c90786e9 | ||
|
|
152c679565 | ||
|
|
1e428c6a71 | ||
|
|
2b26064c7d | ||
|
|
c25c713ac1 | ||
|
|
141fb7bc61 | ||
|
|
436ee75709 | ||
|
|
939b925dfa | ||
|
|
9fff1f4d03 | ||
|
|
68d2586120 | ||
|
|
6050df45e8 | ||
|
|
ad664f6d60 | ||
|
|
0bc2894700 | ||
|
|
b0d63c0d5b | ||
|
|
3fa1cf5a2c | ||
|
|
33a5c86beb | ||
|
|
37441b26f1 | ||
|
|
9f123b9a26 | ||
|
|
8b6b6319b5 | ||
|
|
5bce566988 | ||
|
|
ca0c845759 | ||
|
|
a60a7879a7 | ||
|
|
1b131b26ee | ||
|
|
16f4fa6c3f | ||
|
|
fa94426133 | ||
|
|
90484aaa24 | ||
|
|
20fae556d7 | ||
|
|
14e0610af8 | ||
|
|
8ae708a682 | ||
|
|
fc27d210bb | ||
|
|
49da63c146 | ||
|
|
e45361d9d3 | ||
|
|
f7941c2251 | ||
|
|
b72eeb29a9 | ||
|
|
b4dace1253 | ||
|
|
4edec8b20b | ||
|
|
797abbd4e6 | ||
|
|
bce8176866 | ||
|
|
005e25c1b8 | ||
|
|
711e36d0bf | ||
|
|
78b52e7b35 | ||
|
|
89638a5473 | ||
|
|
3755a1c970 | ||
|
|
21963ac738 | ||
|
|
b67ea18471 | ||
|
|
6e27530dec | ||
|
|
c9d5e38db2 | ||
|
|
996adc37ff | ||
|
|
4f6e0f9c65 | ||
|
|
5d5d129604 | ||
|
|
dcf02f4509 | ||
|
|
cbaa344467 | ||
|
|
7da5feada0 | ||
|
|
d901de0063 | ||
|
|
9b730e2a71 | ||
|
|
facd386cf7 | ||
|
|
f72902cd8d | ||
|
|
70e1ef0fdb | ||
|
|
74680a83a9 | ||
|
|
1d9b15ad90 | ||
|
|
9c9132ad72 | ||
|
|
69ec57f1bf | ||
|
|
d11adf3fe6 | ||
|
|
faa34bc216 | ||
|
|
76451c9d27 | ||
|
|
7e5802dcd1 | ||
|
|
2b6d1c70a8 | ||
|
|
faaef3b353 | ||
|
|
6ffa1d4a1d | ||
|
|
10f0e0dcca | ||
|
|
0afad2e192 | ||
|
|
6a1e7266b9 | ||
|
|
6ffc552bd8 | ||
|
|
8d8245971a | ||
|
|
a2f730d57e | ||
|
|
bf282c41fd | ||
|
|
3e3f7b2cc6 | ||
|
|
0580e063be | ||
|
|
2924aad25e | ||
|
|
39907299cd | ||
|
|
77705895d5 | ||
|
|
3fb85edf0b | ||
|
|
0e7a968cac | ||
|
|
195a9dc771 | ||
|
|
ca227a846a | ||
|
|
e1057fc76e | ||
|
|
b5168c3174 | ||
|
|
4ced00de50 | ||
|
|
750c929506 | ||
|
|
8b46a5a5af | ||
|
|
10d606a101 | ||
|
|
45d36dadcb | ||
|
|
61484d5cb8 | ||
|
|
b7775a99ef | ||
|
|
c45ba38ffa | ||
|
|
e97d9fb8a0 | ||
|
|
bb56ec9ec5 | ||
|
|
6d8f659558 | ||
|
|
7e9047c8fe | ||
|
|
b5af560b67 | ||
|
|
5622636870 | ||
|
|
372c276afc | ||
|
|
250b5fdd10 | ||
|
|
d1fb0bf8b7 | ||
|
|
490c273d36 | ||
|
|
0773dbaa28 | ||
|
|
4d04964740 | ||
|
|
537e6c9a5e | ||
|
|
082a6721f3 | ||
|
|
d515a20129 | ||
|
|
a66d3ef61a | ||
|
|
3b05b1a933 | ||
|
|
105479ce4b | ||
|
|
1a950e3aad | ||
|
|
79fa76e039 | ||
|
|
9db9a70a68 | ||
|
|
632862c742 | ||
|
|
76d0783f6f | ||
|
|
fc6a0e9813 | ||
|
|
038e289057 | ||
|
|
4b2a4af1e7 | ||
|
|
0fb2555bea | ||
|
|
bd8a809065 | ||
|
|
fc6fb7dce2 | ||
|
|
bacb94abe6 | ||
|
|
eb04421412 | ||
|
|
2fbd5a785e | ||
|
|
d8ae1525be | ||
|
|
daaa165d25 | ||
|
|
27ea02be5e | ||
|
|
8eac1ef307 | ||
|
|
1181974ea5 | ||
|
|
5f57fa0470 | ||
|
|
c0ad74ab7b | ||
|
|
c27905e1cc | ||
|
|
cadb65a85f | ||
|
|
b3c152685a | ||
|
|
16ce0726f8 | ||
|
|
5be306e087 | ||
|
|
0d21fc27ab | ||
|
|
7deaa678bc | ||
|
|
0d230a9237 | ||
|
|
8453d754f1 | ||
|
|
05f77f7a0d | ||
|
|
4334b6f148 | ||
|
|
72955759d7 | ||
|
|
18dc52996d | ||
|
|
0b16d09330 | ||
|
|
027d81a8cd | ||
|
|
d833158784 | ||
|
|
75024c9364 | ||
|
|
1dd5db3c42 | ||
|
|
faba80e315 | ||
|
|
1b7dc8eb96 | ||
|
|
7c0af45dd0 | ||
|
|
daea270132 | ||
|
|
a42e3ac4a6 | ||
|
|
c94c526b43 | ||
|
|
e531787773 | ||
|
|
0eda84a99a | ||
|
|
89b791448a | ||
|
|
2239328b2e | ||
|
|
e440a46080 | ||
|
|
4a0fec6e35 | ||
|
|
a9b46987bc | ||
|
|
6771afead5 | ||
|
|
553fca2981 | ||
|
|
8ef1e36b8b | ||
|
|
9e6c49b67c | ||
|
|
170632ddbc | ||
|
|
4a74488cac | ||
|
|
5fd6a08988 | ||
|
|
c86b91ead6 | ||
|
|
aeebb763c1 | ||
|
|
b05630e3e0 | ||
|
|
51905a0ee3 | ||
|
|
869ebd59d1 | ||
|
|
8a6aedca16 | ||
|
|
cf98c3453a | ||
|
|
1b6f234f39 | ||
|
|
26c7f0f566 | ||
|
|
617ad9b16e | ||
|
|
04a6792dbf | ||
|
|
f2bf8f2dbf | ||
|
|
3aba0abd24 | ||
|
|
b56ccc7cef | ||
|
|
aeebf55e60 | ||
|
|
9a9e0d25f3 | ||
|
|
6b3033e240 | ||
|
|
2859a59340 | ||
|
|
45367dd579 | ||
|
|
fee3fd743f | ||
|
|
ba4cfea47c | ||
|
|
5c11f07f8e | ||
|
|
ffac080611 | ||
|
|
f7a1e60735 | ||
|
|
f22651821c | ||
|
|
b2a5a9ac9b | ||
|
|
245af2bd77 | ||
|
|
9dd2b1d35c | ||
|
|
bdbfd3e055 | ||
|
|
1518c03d2b | ||
|
|
45920d4df2 | ||
|
|
1c106ac6eb | ||
|
|
9f4a32b4ac | ||
|
|
2565b10491 | ||
|
|
34d632a401 | ||
|
|
a9df27c0e1 | ||
|
|
313011a168 | ||
|
|
5c595aad83 | ||
|
|
2c67eac63c | ||
|
|
d20c20cb33 | ||
|
|
fa76f032be | ||
|
|
3f74910640 | ||
|
|
22bd2a57e9 | ||
|
|
e45a1d0367 | ||
|
|
ee7acdc6a0 | ||
|
|
32264f2700 | ||
|
|
249ea32973 | ||
|
|
9c31b69a58 | ||
|
|
6e985cf6cc | ||
|
|
0f35b45704 | ||
|
|
ba9245e724 | ||
|
|
6e806f2822 | ||
|
|
7c6842a859 | ||
|
|
39f9fa0e6b | ||
|
|
0c8ae8960d | ||
|
|
ffd6a20829 | ||
|
|
2ee3e2782c | ||
|
|
61324c28c6 | ||
|
|
e9f70ecb90 | ||
|
|
cc3b802575 | ||
|
|
9b780ff4a3 | ||
|
|
deb1a33e78 | ||
|
|
5021350aa0 | ||
|
|
41b3dab135 | ||
|
|
ecf94170c8 | ||
|
|
56acdf3ce3 | ||
|
|
5d6d970d49 | ||
|
|
b501cf3c88 | ||
|
|
deac2baa18 | ||
|
|
b1e3f54055 | ||
|
|
8ebf79ffbe | ||
|
|
8d7307cddf | ||
|
|
c547837ae3 | ||
|
|
b408996e72 | ||
|
|
6b5866524d | ||
|
|
4994dfb488 | ||
|
|
5d5040651e | ||
|
|
5252188a68 | ||
|
|
3f664882d5 | ||
|
|
15f2a2d517 | ||
|
|
6637b0fb8f | ||
|
|
a06eeb9129 | ||
|
|
a24fce2c22 | ||
|
|
ddbdb64fc4 | ||
|
|
38c6d545ec | ||
|
|
fe880abbb0 | ||
|
|
081ed76c39 | ||
|
|
613a00686f | ||
|
|
11739ee671 | ||
|
|
d06951e3b5 | ||
|
|
63bbacd4d8 | ||
|
|
6ba130b62c | ||
|
|
5529d644ac | ||
|
|
7e5e6fb5b4 | ||
|
|
f5fb1911f7 | ||
|
|
e26e129a63 | ||
|
|
280cedad8d | ||
|
|
7c3d5aa69c | ||
|
|
7224ba1123 | ||
|
|
0f7d5057fd | ||
|
|
43f2804ced | ||
|
|
5bb83555a4 | ||
|
|
af4d14364c | ||
|
|
105e717eb2 | ||
|
|
697ec6f6e3 | ||
|
|
e07c99f34d | ||
|
|
c047bd5896 | ||
|
|
d97ac01d79 | ||
|
|
df19a04ad1 | ||
|
|
cbce8a9934 | ||
|
|
7739a84828 | ||
|
|
96f711b43f | ||
|
|
96b3ae5e7d | ||
|
|
d0508e257a | ||
|
|
ec8548f133 | ||
|
|
f217b4190b | ||
|
|
9a3b9ee8f8 | ||
|
|
1c299c767b | ||
|
|
03b46f4c24 | ||
|
|
0f40b6438a | ||
|
|
4549d93e0d | ||
|
|
844cec1011 | ||
|
|
0464c6ce43 | ||
|
|
0cdaa9bb70 | ||
|
|
dade2eb4ff | ||
|
|
42e1cd560e | ||
|
|
5db14ec84a | ||
|
|
22aca2e80c | ||
|
|
4f105f9327 | ||
|
|
e2a218dd63 | ||
|
|
f9c0226fa0 | ||
|
|
fcccd69cee | ||
|
|
cc60c827da | ||
|
|
3c44e14386 | ||
|
|
7f765f4267 | ||
|
|
2262e6f3ca | ||
|
|
4349d165ac | ||
|
|
9d396af447 | ||
|
|
a665457014 | ||
|
|
92ddd04b07 | ||
|
|
b5f93d6fbc | ||
|
|
374b75b9f1 | ||
|
|
47b7d283a3 | ||
|
|
03a3b8b38f | ||
|
|
d2a27f16d7 | ||
|
|
b9837f526b | ||
|
|
2393f3a701 | ||
|
|
b910339e6e | ||
|
|
438445dab8 | ||
|
|
dc7e62c388 | ||
|
|
3ed8f9ca55 | ||
|
|
0282b5e75f | ||
|
|
f5f8218940 | ||
|
|
a634046757 | ||
|
|
6a4d86e084 | ||
|
|
9f9b41c63d | ||
|
|
d813f6515b | ||
|
|
730a445ef6 | ||
|
|
938947548b | ||
|
|
2d5085e652 | ||
|
|
e665e299f2 | ||
|
|
30b9c5eda5 | ||
|
|
fbf29e774a | ||
|
|
5429971553 | ||
|
|
df81bde6fd | ||
|
|
cb8b56ceb8 | ||
|
|
1886c9954b | ||
|
|
c942503351 | ||
|
|
806fc42379 | ||
|
|
06a49f0272 | ||
|
|
a99bcdc437 | ||
|
|
2d4378423b | ||
|
|
6aaf620f18 | ||
|
|
8e38cb2149 | ||
|
|
49ea05c890 | ||
|
|
54b479fb3a | ||
|
|
182fbd3e23 | ||
|
|
6819e33510 | ||
|
|
d547c657a0 | ||
|
|
62172db59b | ||
|
|
fc507b69ab | ||
|
|
cfde05f5af | ||
|
|
15618ad03b | ||
|
|
d54fec069c | ||
|
|
f3cbb9b9d1 | ||
|
|
01b510f7f8 | ||
|
|
2c1463e071 | ||
|
|
9f90ab1b6b | ||
|
|
ca5bf930d0 | ||
|
|
7eb0ab342a | ||
|
|
f400916351 | ||
|
|
858cf49966 | ||
|
|
a5e97fc425 | ||
|
|
981d1c626b | ||
|
|
ca2b1a6612 | ||
|
|
fc749b6ef8 | ||
|
|
7c4e5b1a57 | ||
|
|
4e0e5e955c | ||
|
|
a07568f412 | ||
|
|
092735680e | ||
|
|
5d43125284 | ||
|
|
05d51a5447 | ||
|
|
dd6b8b7157 | ||
|
|
3218760f84 | ||
|
|
3fa2cdfe44 | ||
|
|
60eef31490 | ||
|
|
3e83e89c2b | ||
|
|
949fd9092d | ||
|
|
4276f3436d | ||
|
|
4735aa12a9 | ||
|
|
5e38b1fbca | ||
|
|
65555e1fe3 | ||
|
|
a5e53df998 | ||
|
|
70452428ab | ||
|
|
a431c57e25 | ||
|
|
a575afdb05 | ||
|
|
40d6c7f87b | ||
|
|
8feae0a80f | ||
|
|
0a5b91f0cf | ||
|
|
08aae4df39 | ||
|
|
0ff8aecbea | ||
|
|
e6cae84764 | ||
|
|
2d2d2931f1 | ||
|
|
69ad5e7d1f | ||
|
|
97abc6aeb9 | ||
|
|
8cf37dd37c | ||
|
|
e11d091ac5 | ||
|
|
e952dfcedd | ||
|
|
2565d5cab2 | ||
|
|
e4e66c74bc | ||
|
|
6607c41823 | ||
|
|
43570f20b5 | ||
|
|
9ea7a93206 | ||
|
|
dec4922fe5 | ||
|
|
ef1f95b347 | ||
|
|
7317312f68 | ||
|
|
2965e8df65 | ||
|
|
4fdcc7fae9 | ||
|
|
6be8f7539d | ||
|
|
a6de65fe39 | ||
|
|
fc5dae5a8e | ||
|
|
db69454d32 | ||
|
|
c824335b0e | ||
|
|
1f1ce11078 | ||
|
|
0ec1571d37 | ||
|
|
407e8b52a5 | ||
|
|
1be13a151d | ||
|
|
4d9d417d7a | ||
|
|
aaff8f0c4a | ||
|
|
8c064daf3f | ||
|
|
b0b3dae147 | ||
|
|
2ea2be43d3 | ||
|
|
81f7364e93 | ||
|
|
c530869530 | ||
|
|
586ccd90ec | ||
|
|
ece237c49f | ||
|
|
11da5ca98c | ||
|
|
276a68bda6 | ||
|
|
fba89d197a | ||
|
|
b3259e2489 | ||
|
|
880704dda0 | ||
|
|
d5e92e9c09 | ||
|
|
df2f5692bd | ||
|
|
dacc99b8b6 | ||
|
|
0e8ade3b61 | ||
|
|
fe2d8f6261 | ||
|
|
927381039b | ||
|
|
6b95947acb | ||
|
|
b05e53ce58 | ||
|
|
a2546582f2 | ||
|
|
a023b3401e | ||
|
|
07142e9872 | ||
|
|
f5fea6c34d | ||
|
|
5eb0fb3422 | ||
|
|
fa915e7c35 | ||
|
|
2b7905d31c | ||
|
|
9cbbf37add | ||
|
|
642a0c3b0d | ||
|
|
ff47a19250 | ||
|
|
1fc1a3fbe3 | ||
|
|
996172bd69 | ||
|
|
df038314ef | ||
|
|
67881c2e34 | ||
|
|
9c691a17b2 | ||
|
|
54df86a4a2 | ||
|
|
70aff759d7 | ||
|
|
d903cc6e3b | ||
|
|
c520271ab5 | ||
|
|
f6936224d9 | ||
|
|
283984a6a6 | ||
|
|
c4fc09f72a | ||
|
|
efdbabfa04 | ||
|
|
a43af333fd | ||
|
|
1f5894ff82 | ||
|
|
79b4054e31 | ||
|
|
376d7d0c78 | ||
|
|
c8b85330ed | ||
|
|
c65dffee13 | ||
|
|
275320ad9b | ||
|
|
f71cdf9065 | ||
|
|
8e0a239de9 | ||
|
|
ed2b95bc3f | ||
|
|
2e23ee4c66 | ||
|
|
58def52e30 | ||
|
|
aea5ef3b68 | ||
|
|
f2e14cf0a6 | ||
|
|
bc9b6d1595 | ||
|
|
fd11b62390 | ||
|
|
8db00a5ab5 | ||
|
|
a6757e9374 | ||
|
|
e0fe385404 | ||
|
|
f9f4258ec1 | ||
|
|
f939d563ad | ||
|
|
fad03add54 | ||
|
|
9ab6dab081 | ||
|
|
5a55268830 | ||
|
|
fc3769239d | ||
|
|
c9fb3153eb | ||
|
|
a31f9ed9c5 | ||
|
|
2f75828ba4 | ||
|
|
89679ec358 | ||
|
|
98f77f52df | ||
|
|
ca647d44bb | ||
|
|
838b7dab5b | ||
|
|
4f7fe247b4 | ||
|
|
cee20bd7d2 | ||
|
|
f99ed0c4d9 | ||
|
|
4ae6523164 | ||
|
|
2ece764dcb | ||
|
|
f3bdd98af5 | ||
|
|
3c3c511c5b | ||
|
|
45167b87e5 | ||
|
|
f7d5755744 | ||
|
|
e2adafd16b | ||
|
|
9d178b238d | ||
|
|
431054a1be | ||
|
|
5bf6428253 | ||
|
|
6a4a03267b | ||
|
|
6598e16974 | ||
|
|
b1943a4cec | ||
|
|
5f70d5afd3 | ||
|
|
7dead36212 | ||
|
|
1f68a7830b | ||
|
|
42c5b7a181 | ||
|
|
6596b47e54 | ||
|
|
02e4a3e244 | ||
|
|
d9af90c76b | ||
|
|
a54405f24f | ||
|
|
2b39c5d517 | ||
|
|
a09d295948 | ||
|
|
2c0b0be8c4 | ||
|
|
b289877273 | ||
|
|
869d54b9a8 | ||
|
|
df48ae8316 | ||
|
|
e42cf2a420 | ||
|
|
bd899b649d | ||
|
|
2bc43ee2a5 | ||
|
|
48e2907426 | ||
|
|
4e33cce128 | ||
|
|
6658028f90 | ||
|
|
3fe67bb179 | ||
|
|
ce9a2d0cc3 | ||
|
|
45b7c7af15 | ||
|
|
3116aaeccb | ||
|
|
bfd5517c6c | ||
|
|
c83598a4f9 | ||
|
|
3dc983871f | ||
|
|
ac6fcefffd | ||
|
|
094080cc9f | ||
|
|
d2dff968fb | ||
|
|
496f82a01f | ||
|
|
9293c3e861 | ||
|
|
da7d42edd5 | ||
|
|
87b96d6526 | ||
|
|
a371821ff8 | ||
|
|
748cb6eb8f | ||
|
|
05bee642e9 | ||
|
|
081be04592 | ||
|
|
f77518d086 | ||
|
|
35e6202b3c | ||
|
|
1d84ea6ab9 | ||
|
|
df461d81d8 | ||
|
|
821d5aa595 | ||
|
|
c25bd71592 | ||
|
|
7e9224680e | ||
|
|
bc89116d3d | ||
|
|
aabe75ee13 | ||
|
|
8903adc523 | ||
|
|
61d8c8cd39 | ||
|
|
2e1d991e0c | ||
|
|
5a57167832 | ||
|
|
c3df181dcc | ||
|
|
699ea47ac5 | ||
|
|
fba69f3109 | ||
|
|
063583dda1 | ||
|
|
a31ca5cfb6 | ||
|
|
c0a13305ad | ||
|
|
9d8cbf87dc | ||
|
|
009f7da26b | ||
|
|
f1d0359dc4 | ||
|
|
473baa8c5b | ||
|
|
1a5dda6c72 | ||
|
|
8a78dcb06e | ||
|
|
185768a80f | ||
|
|
619a2817ce | ||
|
|
12a33ee9fc | ||
|
|
26302ca930 | ||
|
|
7e85555ba7 | ||
|
|
2546676f6a | ||
|
|
93b7edf5c4 | ||
|
|
571e432263 | ||
|
|
580b158567 | ||
|
|
395aa665d7 | ||
|
|
a80e54e0b3 | ||
|
|
8ccd41b551 | ||
|
|
9d6a3a8a0d | ||
|
|
a9d1b4b863 | ||
|
|
459d664a60 | ||
|
|
5f10b0156c | ||
|
|
348c07f847 | ||
|
|
934b1894c4 | ||
|
|
a660f4af93 | ||
|
|
12c0d39b13 | ||
|
|
bc6de68006 | ||
|
|
52bb753594 | ||
|
|
ed6b763d06 | ||
|
|
07e421afea | ||
|
|
71352841bf | ||
|
|
91e8fcbb24 | ||
|
|
5b67a85624 | ||
|
|
b49efa0d5a | ||
|
|
abeb585a0d | ||
|
|
cf621f1cc9 | ||
|
|
389a494e00 | ||
|
|
a9c55dc23b | ||
|
|
3d35b7e71b | ||
|
|
6f78146711 | ||
|
|
8afa47c351 | ||
|
|
20838cfc3e | ||
|
|
d00acd6d2f | ||
|
|
0b58a36779 | ||
|
|
54bc98e9c1 | ||
|
|
836daf2ad9 | ||
|
|
9a8cd9bd87 | ||
|
|
c555d91503 | ||
|
|
e0e7c1bcc4 | ||
|
|
4bf733beec | ||
|
|
e93f23c943 | ||
|
|
5384f4d9f2 | ||
|
|
3c0a97c3cc | ||
|
|
129db6cf4e | ||
|
|
c6bfa5652f | ||
|
|
780472d83e | ||
|
|
0d02e3f15a | ||
|
|
bf82f22d0f | ||
|
|
e18f182ce6 | ||
|
|
761c26b587 | ||
|
|
5e62769dcf | ||
|
|
86b8a718a0 | ||
|
|
a729cfc31d | ||
|
|
96cfda852a | ||
|
|
0423d9246c | ||
|
|
985798757f | ||
|
|
72876f6749 | ||
|
|
03d6e223d8 | ||
|
|
62d9f2ed97 | ||
|
|
a550a44a52 | ||
|
|
b19d586949 | ||
|
|
bbfa3ee57f | ||
|
|
af409dd3b8 | ||
|
|
3db79ca149 | ||
|
|
e880420494 | ||
|
|
28ec17b8fe | ||
|
|
8026a40807 | ||
|
|
068c6d406a | ||
|
|
57470e4ef0 | ||
|
|
ca8e1537f5 | ||
|
|
d67b818398 | ||
|
|
f5f46424bb | ||
|
|
1e259717ce | ||
|
|
91ece99a9c | ||
|
|
d30ebdb369 | ||
|
|
ade8522b69 | ||
|
|
159b308f31 | ||
|
|
837bf25f7b | ||
|
|
8301d3669b | ||
|
|
cc752e8d80 | ||
|
|
d20e6bd42f | ||
|
|
bfee9fe233 | ||
|
|
9c962b09f1 | ||
|
|
46edefead7 | ||
|
|
725cfc8a2b | ||
|
|
1ce48b9ef4 | ||
|
|
37cc28f225 | ||
|
|
5ebe3bb8e0 | ||
|
|
aa01512f89 | ||
|
|
37ff2b886a | ||
|
|
ce18212756 | ||
|
|
08f50a274a | ||
|
|
892ee0013a | ||
|
|
e9a47041fd | ||
|
|
d419fa06e8 | ||
|
|
8b961a890c | ||
|
|
db32915114 | ||
|
|
2ffdb10cce | ||
|
|
507fd7e5d4 | ||
|
|
7df599b5a9 | ||
|
|
9987334f55 | ||
|
|
d8c3365384 | ||
|
|
2e284b5af9 | ||
|
|
89ddd4e6a3 | ||
|
|
36524ea481 | ||
|
|
35cf693610 | ||
|
|
42148a66bd | ||
|
|
96878715bf | ||
|
|
1a5f3342e7 | ||
|
|
3613d13a2b | ||
|
|
7786140d85 | ||
|
|
3a23b57f1b | ||
|
|
5a6e6fba69 | ||
|
|
910b3a6162 | ||
|
|
0dc19bed45 | ||
|
|
a7bdb64301 | ||
|
|
4739072a85 | ||
|
|
07bdae2488 | ||
|
|
b84035d821 | ||
|
|
dc0a7b9dae | ||
|
|
2937bd8de0 | ||
|
|
37315ef4d9 | ||
|
|
1986fece07 | ||
|
|
16b1657f35 | ||
|
|
c115c62cd9 | ||
|
|
d2df7f8d5b | ||
|
|
6cb79864e6 | ||
|
|
982d28c7b4 | ||
|
|
0f57516fdc | ||
|
|
75a89defcb | ||
|
|
22a6c52060 | ||
|
|
37607e4a41 | ||
|
|
5936155998 | ||
|
|
8d76de935f | ||
|
|
9baa530064 | ||
|
|
103414b338 | ||
|
|
72fe68ebfe | ||
|
|
cfed31958b | ||
|
|
3d5fca2d36 | ||
|
|
a5710286ec | ||
|
|
d0ce0c6c58 | ||
|
|
779e2cf0f2 | ||
|
|
2108474777 | ||
|
|
e25e0f1747 | ||
|
|
e8e74b639c | ||
|
|
a14fcd1e50 | ||
|
|
485c0e0891 | ||
|
|
00a19e9d43 | ||
|
|
67d0dc0783 | ||
|
|
3718c35842 | ||
|
|
567ffc80b6 | ||
|
|
5d15a08ac8 | ||
|
|
2b84037346 | ||
|
|
536f11e617 | ||
|
|
5d367a384e | ||
|
|
04933e6bf0 | ||
|
|
12abe9aa69 | ||
|
|
cba642a4bc | ||
|
|
9fce0ac0aa | ||
|
|
681812b619 | ||
|
|
6d0be57698 | ||
|
|
1d521602e1 | ||
|
|
2f67e7937a | ||
|
|
e0078a42dc | ||
|
|
3ec875d49d | ||
|
|
35028d1310 | ||
|
|
7ba3d7c4bb | ||
|
|
c05e64098c | ||
|
|
e82ee56a2c | ||
|
|
5681b61aea | ||
|
|
d6ce7eab25 | ||
|
|
bf597fe41c | ||
|
|
6a920f964f | ||
|
|
977b66ba4e | ||
|
|
4659c29358 | ||
|
|
e3bc95b275 | ||
|
|
bf51255e13 | ||
|
|
915fe6837b | ||
|
|
d1237215c0 | ||
|
|
8ba5cfdfa6 | ||
|
|
0d1097425d | ||
|
|
0789a9ecc8 | ||
|
|
400f764b74 | ||
|
|
46e036e3a4 | ||
|
|
17e6264af8 | ||
|
|
468b6ae125 | ||
|
|
e5f5ee4f6a | ||
|
|
3695082ec2 | ||
|
|
f384d86014 | ||
|
|
34bf5028c3 | ||
|
|
8b727aa939 | ||
|
|
9432600de6 | ||
|
|
bb790ec687 | ||
|
|
8bad658d7c | ||
|
|
a66cc9c08a | ||
|
|
0dc24fb342 | ||
|
|
b965ac6232 | ||
|
|
08d61013c8 | ||
|
|
7796f65814 | ||
|
|
02a3afb9ad | ||
|
|
15a11c384b | ||
|
|
57b8496d89 | ||
|
|
7bbe44f5d2 | ||
|
|
ac11ab0431 | ||
|
|
12e352964c | ||
|
|
7daefc4384 | ||
|
|
29541becd0 | ||
|
|
ef6dc82f88 | ||
|
|
6d66d93180 | ||
|
|
3ded17b920 | ||
|
|
d35651f570 | ||
|
|
235300d034 | ||
|
|
2f7ebf5166 | ||
|
|
4819cc87c6 | ||
|
|
c5da383d1e | ||
|
|
2da17329fa | ||
|
|
70d84acf76 | ||
|
|
fef99a0023 | ||
|
|
76d0935e8f | ||
|
|
42dfe5ada9 | ||
|
|
ef136c25e1 | ||
|
|
4251635a8a | ||
|
|
13f00104dd | ||
|
|
b0b82b29c1 | ||
|
|
e0d43ad3d9 | ||
|
|
ae15115af7 | ||
|
|
a2890c6cb0 | ||
|
|
2b00ece07f | ||
|
|
2e3c34571f | ||
|
|
b8b10b5176 | ||
|
|
e6e62bbd24 | ||
|
|
05e9bbf1d6 | ||
|
|
bf94c2395f | ||
|
|
4a749ca345 | ||
|
|
80ffe6c9a3 | ||
|
|
5414b5a8f9 | ||
|
|
e3ba85a326 | ||
|
|
154fa69dbb | ||
|
|
76c44973e9 | ||
|
|
26fdc82cc1 | ||
|
|
dcc587182c | ||
|
|
7cee1ff5ff | ||
|
|
78b8e3bf0b | ||
|
|
bd4c014c12 | ||
|
|
daa263cd68 | ||
|
|
e2965d39af | ||
|
|
8279d15ebb | ||
|
|
9731fa35d8 | ||
|
|
53be4db22b | ||
|
|
0202fa2ca3 | ||
|
|
d5262b5474 | ||
|
|
cb2e1d54c2 | ||
|
|
6392297a27 | ||
|
|
6001d4eba3 | ||
|
|
79588e4bbc | ||
|
|
2b21c38382 | ||
|
|
481d25845e | ||
|
|
e0f8bfe283 | ||
|
|
d2ecb01358 | ||
|
|
316e793b1e | ||
|
|
6df6809ab3 | ||
|
|
c71f892601 | ||
|
|
d0b5a16ce7 | ||
|
|
84638d3228 | ||
|
|
bb21a2aa5f | ||
|
|
c37c948129 | ||
|
|
5155476dd7 | ||
|
|
7ef4f60309 | ||
|
|
b0865cbbdc | ||
|
|
990c1070e2 | ||
|
|
65bdd413eb | ||
|
|
60fa35eb73 | ||
|
|
32cfd5c233 | ||
|
|
7852495527 | ||
|
|
3679a78f47 | ||
|
|
6b21568408 | ||
|
|
04a5aa41d7 | ||
|
|
fea1a2199c | ||
|
|
9c8492efb9 | ||
|
|
20b89a8ddd | ||
|
|
5a8e281fb1 | ||
|
|
a9bb413199 | ||
|
|
428d8ab1b1 | ||
|
|
c2675bcdb6 | ||
|
|
1fe228e4c0 | ||
|
|
c5a6497f10 | ||
|
|
f25a82585f | ||
|
|
01209b66ac | ||
|
|
4e19817d6f | ||
|
|
a48adff8ac | ||
|
|
ab3b32fda1 | ||
|
|
cafab78ab4 | ||
|
|
2d497dee7d | ||
|
|
280ecd9aea | ||
|
|
3a980a3bc0 | ||
|
|
556a5bdd4e | ||
|
|
eca26a345f | ||
|
|
eacff7232d | ||
|
|
3c071db207 | ||
|
|
6609c56063 | ||
|
|
f7ae3de914 | ||
|
|
d82bc4e77e | ||
|
|
31e1df0afd | ||
|
|
700e9236f9 | ||
|
|
ee1e62aff0 | ||
|
|
aa0c38d66f | ||
|
|
4e4d9e33f0 | ||
|
|
f78daa98bc | ||
|
|
dad8802fb0 | ||
|
|
529b7041e7 | ||
|
|
5de1eecf92 | ||
|
|
08ee301f89 | ||
|
|
4154904b21 | ||
|
|
79282809e3 | ||
|
|
108c2533c2 | ||
|
|
fd7309cd17 | ||
|
|
c5f871edf6 | ||
|
|
3f2d2b238d | ||
|
|
e6a30b99d3 | ||
|
|
08ce2b7799 | ||
|
|
dbe7336f32 | ||
|
|
aac953f483 | ||
|
|
49bc469430 | ||
|
|
f3ac8a5cc5 | ||
|
|
847d08a9bb | ||
|
|
0563a363c2 | ||
|
|
f9dc0d6b54 | ||
|
|
40ef3604aa | ||
|
|
970604dc73 | ||
|
|
d6617f214d | ||
|
|
860fccbd4c | ||
|
|
b3adb839e0 | ||
|
|
5e9654c3bc | ||
|
|
43c6bff964 | ||
|
|
aef84189a4 | ||
|
|
04f8bfc975 | ||
|
|
d139e93160 | ||
|
|
1fcc0694ca | ||
|
|
7ba7858076 | ||
|
|
b1cbc433ed | ||
|
|
8beaf97306 | ||
|
|
e626364f45 | ||
|
|
ebaec51f67 | ||
|
|
36b90451af | ||
|
|
e0b1c2a6a4 | ||
|
|
42c6f4264e | ||
|
|
78641e7052 | ||
|
|
6bf0118d20 | ||
|
|
0415cd3719 | ||
|
|
a3412b2f95 | ||
|
|
6a4b3b026f | ||
|
|
f213976a5d | ||
|
|
3605f71f70 | ||
|
|
dc08847a7d | ||
|
|
a34204e25e | ||
|
|
25d73ac0a2 | ||
|
|
f67c0c0e75 | ||
|
|
08c8255d59 | ||
|
|
157efaa02e | ||
|
|
a56a29495e | ||
|
|
c454cd75b3 | ||
|
|
633684f576 | ||
|
|
de50b5e0a1 | ||
|
|
855f287e39 | ||
|
|
f018dee75e | ||
|
|
7d3b218e24 | ||
|
|
536382ce34 | ||
|
|
f6f003d524 | ||
|
|
8126de4048 | ||
|
|
4c41df85a0 | ||
|
|
2c9f78bb9e | ||
|
|
23899e196e | ||
|
|
ecbc87265e | ||
|
|
9f8731d668 | ||
|
|
12d545138a | ||
|
|
0c054581ac | ||
|
|
c8c2f83763 | ||
|
|
9056d591c3 | ||
|
|
191f58074e | ||
|
|
92d3af3d2b | ||
|
|
2ddc4d62a2 | ||
|
|
81a2967683 | ||
|
|
29e3c55df0 | ||
|
|
27e3cdffb1 | ||
|
|
a1ad56aebb | ||
|
|
2573b3b827 | ||
|
|
bcd9a8fcd2 | ||
|
|
8dbc83fd27 | ||
|
|
275280d24e | ||
|
|
c82fa4709d | ||
|
|
c8e826014b | ||
|
|
75b2e93678 | ||
|
|
b0573df9d8 | ||
|
|
e6c14590f1 | ||
|
|
248ac43f28 | ||
|
|
9361a27dca | ||
|
|
700836903f | ||
|
|
039ce26f58 | ||
|
|
95bfb7f0b6 | ||
|
|
9a181556ca | ||
|
|
9554b6f3ec | ||
|
|
45089437ef | ||
|
|
f47a3b0586 | ||
|
|
1a19c7520a | ||
|
|
6266af66f7 | ||
|
|
d3cdc24ebf | ||
|
|
f37d2fc4d7 | ||
|
|
6cd182b858 | ||
|
|
d46bf37726 | ||
|
|
363295e0ea | ||
|
|
611d5daf27 | ||
|
|
0c1e42078e | ||
|
|
c9acafd3ad | ||
|
|
75dd760148 | ||
|
|
28bfbbfa68 | ||
|
|
36326697d7 | ||
|
|
34b7cfec6f | ||
|
|
f65b725b27 | ||
|
|
d7bceb8f2d | ||
|
|
549f418b69 | ||
|
|
8a95f49f8f | ||
|
|
2c269a87a6 | ||
|
|
c17a27b7bf | ||
|
|
3eaecf2ca0 | ||
|
|
07182639f3 | ||
|
|
046628eda4 | ||
|
|
8f0054b2b0 | ||
|
|
33f86a7730 | ||
|
|
2487c69136 | ||
|
|
492d24f28a | ||
|
|
9a355da75c | ||
|
|
8bae065572 | ||
|
|
efebb5a869 | ||
|
|
32cc745a85 | ||
|
|
42bfe3ffac | ||
|
|
de3a5f87cf | ||
|
|
bd1add6dea | ||
|
|
d275cc6a8b | ||
|
|
556b5bd2e9 | ||
|
|
e26cf2a19d | ||
|
|
94f5f1faf6 | ||
|
|
fb3c58bd60 | ||
|
|
e50c918e4d | ||
|
|
eb3ff3369c | ||
|
|
a07a08991f | ||
|
|
6fcc8f3401 | ||
|
|
36b38c6da4 | ||
|
|
7f2f86b7ea | ||
|
|
68514bc7ed | ||
|
|
f0dfab536c | ||
|
|
3b75c9999e | ||
|
|
084ddf4f5d | ||
|
|
6770b1fa72 | ||
|
|
118d39ed83 | ||
|
|
c24df4f876 | ||
|
|
0ae9c25d6f | ||
|
|
f99b2a9def | ||
|
|
caf9eeae2e | ||
|
|
6afecb5708 | ||
|
|
739d32e2e0 | ||
|
|
4e601298b0 | ||
|
|
74cffced3f | ||
|
|
f8cc76be35 | ||
|
|
dced2dd418 | ||
|
|
418fe97c67 | ||
|
|
1440f9b721 | ||
|
|
9504c086ea | ||
|
|
3a15d6475c | ||
|
|
18f3a00c0f | ||
|
|
5138bbe947 | ||
|
|
a309feb516 | ||
|
|
e9c172c261 | ||
|
|
6ac6842a8d | ||
|
|
71dc53ac1f | ||
|
|
4450fa1379 | ||
|
|
af0f9489c7 | ||
|
|
75b2b2bdb2 | ||
|
|
2d8224fd12 | ||
|
|
c3aea3ba72 | ||
|
|
ad1ff29ed2 | ||
|
|
9887bd99c7 | ||
|
|
9af9d0192d | ||
|
|
62933cf637 | ||
|
|
ee0837571d | ||
|
|
c2b12419c7 | ||
|
|
991aed7e8c | ||
|
|
528d5fc5a9 | ||
|
|
93569227f3 | ||
|
|
46812f9e42 | ||
|
|
61f043319d | ||
|
|
77140d01a0 | ||
|
|
beff326001 | ||
|
|
f2841b7328 | ||
|
|
2ba63b2484 | ||
|
|
f054241a73 | ||
|
|
b5007b9195 | ||
|
|
9deea64097 | ||
|
|
105777da21 | ||
|
|
e13743dfa8 | ||
|
|
35bdd3a41a | ||
|
|
464d757800 | ||
|
|
8076fcd148 | ||
|
|
53b1f07601 | ||
|
|
2626d78835 | ||
|
|
3442ddfae2 | ||
|
|
5ed863c5ec | ||
|
|
00f6387182 | ||
|
|
ca70e8f0c8 | ||
|
|
081e95b293 | ||
|
|
1282e610ba | ||
|
|
287c52cef4 | ||
|
|
dd421fec80 | ||
|
|
9b10bdaf0d | ||
|
|
4b916829a4 | ||
|
|
b33e9d9187 | ||
|
|
9d0d323b50 | ||
|
|
2872569ce0 | ||
|
|
cd2212b754 | ||
|
|
eb6525b8d9 | ||
|
|
10319a1e3d | ||
|
|
6bcfea9372 | ||
|
|
4699d1c2ee | ||
|
|
68f0b2efc3 | ||
|
|
b2af8e196b | ||
|
|
2e626a915f | ||
|
|
0127050d89 | ||
|
|
a25938022f | ||
|
|
c47b2973b0 | ||
|
|
efd3934470 | ||
|
|
fb7da4e07e | ||
|
|
d7d209f694 | ||
|
|
83a88299c2 | ||
|
|
bfae3b86cc | ||
|
|
027ee4d336 | ||
|
|
b07a65c879 | ||
|
|
284b2762f5 | ||
|
|
5ba6e3b332 | ||
|
|
8b0e3d960a | ||
|
|
fba2953974 | ||
|
|
a36b843eff | ||
|
|
0735bc2d8a | ||
|
|
69d6fd645b | ||
|
|
039b11d434 | ||
|
|
485581f680 | ||
|
|
a0c2006e24 | ||
|
|
7a288c360d | ||
|
|
597d39f651 | ||
|
|
7d27f64b8a | ||
|
|
bd280bdc53 | ||
|
|
018429866e | ||
|
|
78ad6526b8 | ||
|
|
8c6e960db7 | ||
|
|
46d38ff8c0 | ||
|
|
db6fe469ad | ||
|
|
5a7630bcd1 | ||
|
|
099b57169d | ||
|
|
e22dfd3314 | ||
|
|
be71992930 | ||
|
|
37433178cb | ||
|
|
14499a4564 | ||
|
|
0355777f59 | ||
|
|
a55834c78d | ||
|
|
79bbb47459 | ||
|
|
d3bcf0605b | ||
|
|
19a02f3a40 | ||
|
|
db20e9308a | ||
|
|
b3f9f28554 | ||
|
|
ea5bd3f346 | ||
|
|
5c13797639 | ||
|
|
f147905532 | ||
|
|
a73314c12a | ||
|
|
619934a275 | ||
|
|
5fe1384e55 | ||
|
|
9af2c4a18a | ||
|
|
8f4c56b24a | ||
|
|
5eb0f65934 | ||
|
|
4ed4f165ac | ||
|
|
eb8419fda0 | ||
|
|
b4a7ee0e89 | ||
|
|
83ad1bc529 | ||
|
|
56948d3035 | ||
|
|
e71e5ad57e | ||
|
|
e06d1fa5b0 | ||
|
|
460439f6bd | ||
|
|
4b4072a47e | ||
|
|
65d19d92e9 | ||
|
|
79e52d1c6e | ||
|
|
59963ee7b9 | ||
|
|
88273b1c9b | ||
|
|
bc1eb994f9 | ||
|
|
136f0fd4bd | ||
|
|
021dc13ce9 | ||
|
|
925b71489d | ||
|
|
580ce9f8f5 | ||
|
|
2b3d8c062d | ||
|
|
41858e091a | ||
|
|
5b7eaf08ae | ||
|
|
7362722adb | ||
|
|
8198ea8819 | ||
|
|
cb615f4cbc | ||
|
|
6623fd8362 | ||
|
|
7f8be85116 | ||
|
|
c61381441c | ||
|
|
b3cecdfea2 | ||
|
|
66f582df58 | ||
|
|
7f6f7e11ce | ||
|
|
0bdda313da | ||
|
|
d5e851cff7 | ||
|
|
92f1ec13f9 | ||
|
|
5376006754 | ||
|
|
361979411e | ||
|
|
3981308083 | ||
|
|
facd5e14cc | ||
|
|
ecce92c528 | ||
|
|
7696940989 | ||
|
|
941ff46c2c | ||
|
|
94036073de | ||
|
|
13216f481c | ||
|
|
eb35df8720 | ||
|
|
bf500fe1a4 | ||
|
|
c173e86e62 | ||
|
|
d4cb148272 | ||
|
|
ffc3f8544e | ||
|
|
b326bfe63a | ||
|
|
12f3f47ef9 | ||
|
|
45734747c1 | ||
|
|
37933d814c | ||
|
|
5b26287264 | ||
|
|
bac20b1d1f | ||
|
|
904c641992 | ||
|
|
36004bfd94 | ||
|
|
0fcba7e90c | ||
|
|
2e9182933e | ||
|
|
e3674fa2c1 | ||
|
|
a14cbe1319 | ||
|
|
c03c35de88 | ||
|
|
dc6c649af8 | ||
|
|
b0d2bdb0ba | ||
|
|
9239dd9a8b | ||
|
|
07a368a0bd | ||
|
|
b8d6d1c249 | ||
|
|
0f1d6a1ad2 | ||
|
|
37c6928ffc | ||
|
|
7478b6dbe0 | ||
|
|
e3cd6e1441 | ||
|
|
8081d968b6 | ||
|
|
a226eee6ef | ||
|
|
8b5a21e593 | ||
|
|
2c71f6c6be | ||
|
|
1a50b01edf | ||
|
|
05f08eeb4c | ||
|
|
fdc3ebb5c5 | ||
|
|
8f06bccd48 | ||
|
|
16b4eb67d4 | ||
|
|
ffa545cb41 | ||
|
|
f67454d51c | ||
|
|
8ae54c56db | ||
|
|
9a17adcc6f | ||
|
|
4dd6903025 | ||
|
|
085770d0ad | ||
|
|
fbaba7c37d | ||
|
|
d5cea208d2 | ||
|
|
36397adca4 | ||
|
|
4971e25d44 | ||
|
|
150357441d | ||
|
|
c4ee663285 | ||
|
|
8c95b4a9b9 | ||
|
|
32ba919851 | ||
|
|
eb37345e85 | ||
|
|
681e454917 | ||
|
|
7c7b82dee4 | ||
|
|
2ae47af8f0 | ||
|
|
b72d91d0d4 | ||
|
|
56010e7203 | ||
|
|
f658698f7b | ||
|
|
b708b9c6df | ||
|
|
c2908e17fa | ||
|
|
302daab824 | ||
|
|
ec49f238d6 | ||
|
|
3e43a5d866 | ||
|
|
a99d5e68e9 | ||
|
|
28514276ec | ||
|
|
b611427c17 | ||
|
|
ce7a4ead04 | ||
|
|
5fe15a85fd | ||
|
|
ae412909f6 | ||
|
|
c6ed579797 | ||
|
|
b2a525268e | ||
|
|
eb78b4881c | ||
|
|
4ce6e5a9f6 | ||
|
|
15d7e75555 | ||
|
|
6453cc6ccc | ||
|
|
d882a2eb63 | ||
|
|
065ac4aa25 | ||
|
|
dea54fc55e | ||
|
|
8f425bd9c7 | ||
|
|
9d909d73b8 | ||
|
|
b417d50a28 | ||
|
|
aa93a2f702 | ||
|
|
ba7125e773 | ||
|
|
46a7eaa4bb | ||
|
|
887439a6fe | ||
|
|
0e2b07d06d | ||
|
|
b7d4bde11b | ||
|
|
9707360948 | ||
|
|
103a8eb092 | ||
|
|
dcba4f9376 | ||
|
|
12aaa6fcb1 | ||
|
|
d8393481a4 | ||
|
|
70d3202a2c | ||
|
|
c7f1bbc03d | ||
|
|
61a55cb3f4 | ||
|
|
6a484af775 | ||
|
|
3074d41cf2 | ||
|
|
54199058e3 | ||
|
|
87a533791b | ||
|
|
c3109bb2c6 | ||
|
|
031cb836d2 | ||
|
|
aa0a9134cb | ||
|
|
749eac74fe | ||
|
|
ac39908737 | ||
|
|
bb8f4bf488 | ||
|
|
db0d157a74 | ||
|
|
bd8e1bbe71 | ||
|
|
7aba898cf5 | ||
|
|
baf039755f | ||
|
|
70977f7d80 | ||
|
|
884342d6af | ||
|
|
411b0fb4c2 | ||
|
|
8053fb2399 | ||
|
|
ed42dcab9c | ||
|
|
866b5b0f2f | ||
|
|
5834a1a0fa | ||
|
|
8c6d845603 | ||
|
|
667f363f3c | ||
|
|
e209ff38d9 | ||
|
|
ea915993b6 | ||
|
|
72d0468c6a | ||
|
|
35ae13b1c3 | ||
|
|
1d9460f565 | ||
|
|
e9a3ed71b4 | ||
|
|
e344e5b7e6 | ||
|
|
e814064403 | ||
|
|
f5aeb5cbcd | ||
|
|
f41164b892 | ||
|
|
03073c8364 | ||
|
|
a359bff531 | ||
|
|
3789ac6433 | ||
|
|
9f53f59f18 | ||
|
|
521bd746e3 | ||
|
|
3d8e43a42b | ||
|
|
9452e1852d | ||
|
|
3920d24af6 | ||
|
|
3af11e6ba8 | ||
|
|
7bcde46d49 | ||
|
|
0b73041718 | ||
|
|
dcdc0cb8c1 | ||
|
|
ad6ac1aad6 | ||
|
|
249dc6accd | ||
|
|
939e5999ca | ||
|
|
1f91e57d56 | ||
|
|
81e749dc60 | ||
|
|
241b2afda8 | ||
|
|
94cc29f227 | ||
|
|
6d6659eee1 | ||
|
|
5220be0775 | ||
|
|
07ebbf0863 | ||
|
|
b60c767fc5 | ||
|
|
371c3b78c3 | ||
|
|
6a3aa219d5 | ||
|
|
df586ddfdd | ||
|
|
f3b2bbfec0 | ||
|
|
3ea2114798 | ||
|
|
809cf70e0e | ||
|
|
390619535f | ||
|
|
3392ac1fa8 | ||
|
|
b09d94abbe | ||
|
|
03ac01d21f | ||
|
|
12b1d785e5 | ||
|
|
71c58e9d2e | ||
|
|
13c2997a52 | ||
|
|
47b840cac9 | ||
|
|
3f7f243f50 | ||
|
|
1d1e83b18d | ||
|
|
6f3a602c76 | ||
|
|
afb7a4c6a7 | ||
|
|
d0c950d4ca | ||
|
|
63ee7d5211 | ||
|
|
6b0d875745 | ||
|
|
a47a9b9c0c | ||
|
|
27040a3e5c | ||
|
|
c7a8a40e22 | ||
|
|
408de6af63 | ||
|
|
8a324afaa1 | ||
|
|
54bc4cede0 | ||
|
|
4cfbea80b0 | ||
|
|
64cb8286bd | ||
|
|
3e92e0afc6 | ||
|
|
8a91308280 | ||
|
|
3da861d71e | ||
|
|
3566e18e99 | ||
|
|
6e608e627a | ||
|
|
65f7fb979b | ||
|
|
558b5e14f1 | ||
|
|
a5ee2dd03b | ||
|
|
9e9fe78b8d | ||
|
|
265e126faf | ||
|
|
af440e5c5b | ||
|
|
32b8c91113 | ||
|
|
dbc60ccca4 | ||
|
|
d5b5af5d3a | ||
|
|
8c1bd3bc4e | ||
|
|
8f638186a3 | ||
|
|
0b5a27e674 | ||
|
|
870f1aaaec | ||
|
|
23cb4dcbe5 | ||
|
|
4f125b4244 | ||
|
|
2b9de7938f | ||
|
|
75bb07184f | ||
|
|
1fb491d2e1 | ||
|
|
0bc8bdd841 | ||
|
|
71d4e2626e | ||
|
|
0b26cbdd01 | ||
|
|
ca72b3c18b | ||
|
|
8676f2711b | ||
|
|
788a86bdcf | ||
|
|
0df6b4b220 | ||
|
|
f14dcd5c28 | ||
|
|
f86548e046 | ||
|
|
9dea11ab11 | ||
|
|
00749f806c | ||
|
|
e4e3591413 | ||
|
|
3f847d7c7f | ||
|
|
67a10994a4 | ||
|
|
4016296e0b | ||
|
|
92777366ec | ||
|
|
beb34f8264 | ||
|
|
f6f8c5c858 | ||
|
|
a19c4dffcd | ||
|
|
a5754971f6 | ||
|
|
dbe6435809 | ||
|
|
7e5b980600 | ||
|
|
a6c3b70ee7 | ||
|
|
0a23e7951a | ||
|
|
e0e3fc8fef | ||
|
|
a8b947f5ca | ||
|
|
0f28df51f1 | ||
|
|
6e36528f35 | ||
|
|
a885fdbea7 | ||
|
|
713de74abb | ||
|
|
8db6a33e15 | ||
|
|
ed2d577be0 | ||
|
|
084eddecbf | ||
|
|
5e09ba302a | ||
|
|
fa37b40435 | ||
|
|
c95dde5187 | ||
|
|
4d5a4d501a | ||
|
|
ce97f20826 | ||
|
|
ab9cd13abc | ||
|
|
7af5e828bb | ||
|
|
98d53c49a0 | ||
|
|
bb32849e47 | ||
|
|
13221faee0 | ||
|
|
abda14f543 | ||
|
|
3f0e459181 | ||
|
|
3df61381a4 | ||
|
|
daf8ae2192 | ||
|
|
3bf374ea5a | ||
|
|
94b3559eee | ||
|
|
1c93b42681 | ||
|
|
1d9d44ecc3 | ||
|
|
19178f963e | ||
|
|
4dc54f95b6 | ||
|
|
0790403923 | ||
|
|
c55008d302 | ||
|
|
df9df94bb8 | ||
|
|
502ce4e414 | ||
|
|
f5f07c0e96 | ||
|
|
9549424835 | ||
|
|
062b9baeab | ||
|
|
08d1fcaf03 | ||
|
|
ad7523a500 | ||
|
|
28aa0f3b76 | ||
|
|
8247843936 | ||
|
|
09ff5a37a9 | ||
|
|
663ec7710c | ||
|
|
0dad0b10e6 | ||
|
|
8c352813c4 | ||
|
|
745f5d9759 | ||
|
|
6536ace4f7 | ||
|
|
67e6e06e72 | ||
|
|
d0bf85de17 | ||
|
|
14cb3bdc67 | ||
|
|
e9dadd5571 | ||
|
|
d5eceb05ce | ||
|
|
b510c88f84 | ||
|
|
16e409e5f5 | ||
|
|
ad257fcd0e | ||
|
|
e84087d6ec | ||
|
|
218c2385f0 | ||
|
|
4ef8c2bc77 | ||
|
|
d9bc1bdfca | ||
|
|
a5c287e8d6 | ||
|
|
1e9f5c9c56 | ||
|
|
fbcf439f89 | ||
|
|
766c3666d8 | ||
|
|
f08289351d | ||
|
|
e426bba8b5 | ||
|
|
4b200762a5 | ||
|
|
fd203ed436 | ||
|
|
04cea4dbd6 | ||
|
|
a8f1056c3c | ||
|
|
c2576f4c62 | ||
|
|
1da5d1094f | ||
|
|
34e6122407 | ||
|
|
ee61feb581 | ||
|
|
7f04f1ed62 | ||
|
|
82646c3576 | ||
|
|
b3f62240c4 | ||
|
|
86a5992e8a | ||
|
|
82a731ed06 | ||
|
|
e650529efb | ||
|
|
1de57e124f | ||
|
|
9e72dcc390 | ||
|
|
80019e2071 | ||
|
|
ab7cdcd1b1 | ||
|
|
7afe1e0a30 | ||
|
|
a39eb80214 | ||
|
|
10e2a08cd2 | ||
|
|
6da4949c98 | ||
|
|
4d092f329b | ||
|
|
69c1d35406 | ||
|
|
a256a7328e | ||
|
|
75c8658366 | ||
|
|
c3e68915a3 | ||
|
|
d97f4da7fa | ||
|
|
06e81dbe37 | ||
|
|
4a46aaa880 | ||
|
|
e766372a81 | ||
|
|
c5ee9f095a | ||
|
|
9734ed1cf8 | ||
|
|
548d122c64 | ||
|
|
c5205ef7a9 | ||
|
|
f71a27960b | ||
|
|
d747e656dc | ||
|
|
867215e2af | ||
|
|
3614b9f3b7 | ||
|
|
cbfa4c85c1 | ||
|
|
b9bef5042a | ||
|
|
46e523d005 | ||
|
|
fa2930dfc5 | ||
|
|
086ab15e8a | ||
|
|
c2e3e784a2 | ||
|
|
9dd01bd36e | ||
|
|
02d08b0762 | ||
|
|
cbd312e67d | ||
|
|
5084d9f87e | ||
|
|
3c3408624d | ||
|
|
6e8403390b | ||
|
|
ef9e5d4bbc | ||
|
|
c0b49948d7 | ||
|
|
48b066fb83 | ||
|
|
7d551d59db | ||
|
|
5a057857dc | ||
|
|
0a9cea722c | ||
|
|
f7eb7b1ec6 | ||
|
|
0eb57a734e | ||
|
|
82ff9a2b06 | ||
|
|
051a48bc73 | ||
|
|
6b9d5cf7a0 | ||
|
|
2e7d64d97b | ||
|
|
dd6050ee0b | ||
|
|
c954910728 | ||
|
|
0fe2bc4a54 | ||
|
|
9f39cd615f | ||
|
|
07bf21dbb4 | ||
|
|
43e8c65473 | ||
|
|
8a926eabbe | ||
|
|
fcbed5f63e | ||
|
|
8d203a1d7c | ||
|
|
d6efb2dd12 | ||
|
|
6ea3938f74 | ||
|
|
63e773ce96 | ||
|
|
60b8e1041c | ||
|
|
fb263c926b | ||
|
|
dbbae30f5a | ||
|
|
7b73c86fa4 | ||
|
|
c16c6ea6a6 | ||
|
|
4ecba0040a | ||
|
|
f65aabe897 | ||
|
|
de2337afe7 | ||
|
|
3360f2fc08 | ||
|
|
7d8f3a9d9b | ||
|
|
63884bc836 | ||
|
|
9c231a2b8c | ||
|
|
6a5c979d2a | ||
|
|
094734ae1f | ||
|
|
97b4d457fd | ||
|
|
9669c0ddda | ||
|
|
daf8257a1f | ||
|
|
40f32c7cf9 | ||
|
|
b8295e5cfc | ||
|
|
aba489217a | ||
|
|
7775689dfa | ||
|
|
90397b2b6f | ||
|
|
32683a2b59 | ||
|
|
9e36847397 | ||
|
|
4c82804622 | ||
|
|
cb911c474e | ||
|
|
ce2537d69d | ||
|
|
38746e83c5 | ||
|
|
be8988d309 | ||
|
|
ed59994db8 | ||
|
|
11a8d3ba70 | ||
|
|
adf91bf3d0 | ||
|
|
7729b88a00 | ||
|
|
e93aecabb4 | ||
|
|
3cf43b2cc1 | ||
|
|
3cba8cf98d | ||
|
|
e2ca94c93b | ||
|
|
1c3e25ba1a | ||
|
|
f297f4ec52 | ||
|
|
534f10b8a7 | ||
|
|
a7aed3d49e | ||
|
|
9a915a48b5 | ||
|
|
81a3b299f0 | ||
|
|
a4edae6692 | ||
|
|
047f6c409f | ||
|
|
963be2bc63 | ||
|
|
bd34a9bd37 | ||
|
|
32b93b2bd9 | ||
|
|
e7c4507b0b | ||
|
|
17ae34c44f | ||
|
|
b466ea2fd1 | ||
|
|
8e4ee54f03 | ||
|
|
c99547d9e3 | ||
|
|
1d22125d6e | ||
|
|
d4689571cb | ||
|
|
cb1cb62e30 | ||
|
|
77ad860d35 | ||
|
|
5082561dc9 | ||
|
|
7d4c80eedb | ||
|
|
af4bd950b7 | ||
|
|
f85f9b0920 | ||
|
|
89ea96c28e | ||
|
|
6f2e3f5f13 | ||
|
|
ee91e8312f | ||
|
|
004a29846e | ||
|
|
01eb22a0fe | ||
|
|
bb2d2c5c86 | ||
|
|
e0c36e3c45 | ||
|
|
9fa9f74b90 | ||
|
|
7308748957 | ||
|
|
748105eae1 | ||
|
|
33f45dfc92 | ||
|
|
dda1e3054a | ||
|
|
a8030fa045 | ||
|
|
9de66f080d | ||
|
|
7a57180ecd | ||
|
|
48e1e0b631 | ||
|
|
d5fe38ba8d | ||
|
|
cb8035452b | ||
|
|
c2cab60940 | ||
|
|
58e7e1b283 | ||
|
|
5d108b1b6f | ||
|
|
8fbbbd65c4 | ||
|
|
7e0a96857c | ||
|
|
139b0e037e | ||
|
|
7cbfa2458b | ||
|
|
abc5162bca | ||
|
|
d3cf71dad8 | ||
|
|
0909164a06 | ||
|
|
6df50d97e9 | ||
|
|
98f8fb3bec | ||
|
|
0f5f175f08 | ||
|
|
16a2a6cb35 | ||
|
|
da38fcaa0c | ||
|
|
b6fd9aeb14 | ||
|
|
201c3e2f97 | ||
|
|
d834c2517a | ||
|
|
dc98081a92 | ||
|
|
30e9753c80 | ||
|
|
70cd06dc85 | ||
|
|
194a919a73 | ||
|
|
b57b242232 | ||
|
|
cc76172a46 | ||
|
|
5858f98ec6 | ||
|
|
941e000142 | ||
|
|
7dd58fd9dc | ||
|
|
2b560a225e | ||
|
|
c768f22059 | ||
|
|
f738ba9152 | ||
|
|
95c27119ac | ||
|
|
2b2b516788 | ||
|
|
0bb63b5bb7 | ||
|
|
48012072d7 | ||
|
|
0053949bc0 | ||
|
|
88e7246366 | ||
|
|
fbedf3b605 | ||
|
|
d5659e627d | ||
|
|
709d4dc805 | ||
|
|
37f052e9a5 | ||
|
|
58c765e97f | ||
|
|
da332c2756 | ||
|
|
e9d329bf8b |
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
*_plus.go
|
||||
*-plus.sh
|
||||
*_plus.html
|
||||
*_plus.js
|
||||
*@plus.js
|
||||
*_plus.less
|
||||
*_plus.css
|
||||
*_plus.css.map
|
||||
74
.golangci.yaml
Normal file
74
.golangci.yaml
Normal file
@@ -0,0 +1,74 @@
|
||||
# https://golangci-lint.run/usage/configuration/
|
||||
|
||||
linters:
|
||||
enable-all: true
|
||||
disable:
|
||||
- ifshort
|
||||
- exhaustivestruct
|
||||
- golint
|
||||
- nosnakecase
|
||||
- scopelint
|
||||
- varcheck
|
||||
- structcheck
|
||||
- interfacer
|
||||
- maligned
|
||||
- deadcode
|
||||
- dogsled
|
||||
- wrapcheck
|
||||
- wastedassign
|
||||
- varnamelen
|
||||
- testpackage
|
||||
- thelper
|
||||
- nilerr
|
||||
- sqlclosecheck
|
||||
- paralleltest
|
||||
- nonamedreturns
|
||||
- nlreturn
|
||||
- nakedret
|
||||
- ireturn
|
||||
- interfacebloat
|
||||
- gosmopolitan
|
||||
- gomnd
|
||||
- goerr113
|
||||
- gochecknoglobals
|
||||
- exhaustruct
|
||||
- errorlint
|
||||
- depguard
|
||||
- exhaustive
|
||||
- containedctx
|
||||
- wsl
|
||||
- cyclop
|
||||
- dupword
|
||||
- errchkjson
|
||||
- contextcheck
|
||||
- tagalign
|
||||
- dupl
|
||||
- forbidigo
|
||||
- funlen
|
||||
- goconst
|
||||
- godox
|
||||
- gosec
|
||||
- lll
|
||||
- nestif
|
||||
- revive
|
||||
- unparam
|
||||
- stylecheck
|
||||
- gocritic
|
||||
- gofumpt
|
||||
- gomoddirectives
|
||||
- godot
|
||||
- gofmt
|
||||
- gocognit
|
||||
- mirror
|
||||
- gocyclo
|
||||
- gochecknoinits
|
||||
- gci
|
||||
- maintidx
|
||||
- prealloc
|
||||
- goimports
|
||||
- errname
|
||||
- musttag
|
||||
- forcetypeassert
|
||||
- whitespace
|
||||
- noctx
|
||||
- reassign
|
||||
47
README.md
47
README.md
@@ -1,18 +1,52 @@
|
||||
# GoEdge目标
|
||||
做一款人人用得起的CDN & WAF系统。
|
||||
|
||||

|
||||

|
||||
|
||||
## 特性
|
||||
* `免费` - 开源、免费、自由、开放
|
||||
* `简单` - 架构简单清晰,安装简单,使用简单,运维简单
|
||||
* `高扩展性` - 可以自由扩展新的节点,支持亿级数据
|
||||
|
||||
## 功能介绍
|
||||
* 多用户
|
||||
* 日志审计
|
||||
* 集群管理
|
||||
* HTTP/HTTPS/TCP/UDP等协议支持
|
||||
* WAF
|
||||
* 缓存
|
||||
* DNS自动解析
|
||||
* 多域名绑定
|
||||
* 免费证书申请
|
||||
* IP黑白名单
|
||||
* 访问日志
|
||||
* 统计
|
||||
* 内容压缩
|
||||
* Protocol Proxy协议
|
||||
* 本地静态文件
|
||||
* URL跳转
|
||||
* 路由规则
|
||||
* 重写规则
|
||||
* 访问控制
|
||||
* 字符编码
|
||||
* 自定义页面
|
||||
* 自定义HTTP Header
|
||||
* Websocket
|
||||
* WebP自动转换
|
||||
* Fastcgi
|
||||
* 请求限制
|
||||
* 流量限制
|
||||
|
||||
## 在线演示
|
||||
* [http://demo.goedge.cn](http://demo.goedge.cn)
|
||||
|
||||
## 文档
|
||||
[点这里查看文档](http://edge.teaos.cn/docs)
|
||||
* [新手指南](https://goedge.cn/docs/QuickStart/Index.md)
|
||||
* [完整文档](https://goedge.cn/docs)
|
||||
* [开发者指南](https://goedge.cn/docs/Developer/Build.md)
|
||||
|
||||
## 架构
|
||||

|
||||

|
||||
|
||||
其中的组件源码地址如下:
|
||||
* [边缘节点](https://github.com/TeaOSLab/EdgeNode)
|
||||
@@ -20,7 +54,10 @@
|
||||
* [管理平台](https://github.com/TeaOSLab/EdgeAdmin)
|
||||
|
||||
## 联系我们
|
||||
有什么问题和建议都可以加入QQ群 `659832182`。
|
||||
有什么问题和建议都可以加入 [Telegram群](https://t.me/+5kVCMGxQhZxiODY9)
|
||||
|
||||
## 企业版
|
||||
* [GoEdge企业版](https://goedge.cn/commercial) - 功能更强大的CDN系统
|
||||
|
||||
## 感谢
|
||||
* 感谢[JetBrains公司](https://www.jetbrains.com/)提供免费的IDE开发Licence。
|
||||
* 感谢 [Gitee](https://gitee.com/) 提供国内源代码托管平台
|
||||
3
build/.gitignore
vendored
3
build/.gitignore
vendored
@@ -1 +1,2 @@
|
||||
edge-api/
|
||||
edge-api/
|
||||
build-all-test.sh
|
||||
@@ -1,5 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
ROOT=$(dirname $0)
|
||||
|
||||
# build all nodes
|
||||
if [ -f $ROOT"/../../EdgeNode/build/build-all-plus.sh" ]; then
|
||||
echo "=============================="
|
||||
echo "build all edge-node"
|
||||
echo "=============================="
|
||||
cd $ROOT"/../../EdgeNode/build"
|
||||
./build-all-plus.sh
|
||||
cd -
|
||||
fi
|
||||
|
||||
./build.sh linux amd64
|
||||
./build.sh linux 386
|
||||
./build.sh linux arm64
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
function build() {
|
||||
ROOT=$(dirname $0)
|
||||
NAME="edge-admin"
|
||||
DIST=$ROOT/"../dist/${NAME}"
|
||||
OS=${1}
|
||||
ARCH=${2}
|
||||
|
||||
if [ -z $OS ]; then
|
||||
echo "usage: build.sh OS ARCH"
|
||||
exit
|
||||
fi
|
||||
if [ -z $ARCH ]; then
|
||||
echo "usage: build.sh OS ARCH"
|
||||
exit
|
||||
fi
|
||||
|
||||
VERSION=$(lookup-version $ROOT/../internal/const/const.go)
|
||||
ZIP="${NAME}-${OS}-${ARCH}-v${VERSION}.zip"
|
||||
|
||||
# check edge-api
|
||||
APINodeVersion=$(lookup-version $ROOT"/../../EdgeAPI/internal/const/const.go")
|
||||
echo "building edge-api v${APINodeVersion} ..."
|
||||
EDGE_API_BUILD_SCRIPT=$ROOT"/../../EdgeAPI/build/build.sh"
|
||||
if [ ! -f $EDGE_API_BUILD_SCRIPT ]; then
|
||||
echo "unable to find edge-api build script 'EdgeAPI/build/build.sh'"
|
||||
exit
|
||||
fi
|
||||
|
||||
cd $ROOT"/../../EdgeAPI/build"
|
||||
echo "=============================="
|
||||
./build.sh $OS $ARCH
|
||||
echo "=============================="
|
||||
cd -
|
||||
|
||||
# create dir & copy files
|
||||
echo "copying ..."
|
||||
if [ ! -d $DIST ]; then
|
||||
mkdir $DIST
|
||||
mkdir $DIST/bin
|
||||
mkdir $DIST/configs
|
||||
mkdir $DIST/logs
|
||||
fi
|
||||
|
||||
cp -R $ROOT/../web $DIST/
|
||||
rm -f $DIST/web/tmp/*
|
||||
cp $ROOT/configs/server.template.yaml $DIST/configs/
|
||||
|
||||
EDGE_API_ZIP_FILE=$ROOT"/../../EdgeAPI/dist/edge-api-${OS}-${ARCH}-v${APINodeVersion}.zip"
|
||||
cp $EDGE_API_ZIP_FILE $DIST/
|
||||
cd $DIST/
|
||||
unzip -q $(basename $EDGE_API_ZIP_FILE)
|
||||
rm -f $(basename $EDGE_API_ZIP_FILE)
|
||||
cd -
|
||||
|
||||
# build
|
||||
echo "building "${NAME}" ..."
|
||||
env GOOS=$OS GOARCH=$GOARCH go build -ldflags="-s -w" -tags demo -o $DIST/bin/${NAME} $ROOT/../cmd/edge-admin/main.go
|
||||
|
||||
# delete hidden files
|
||||
find $DIST -name ".DS_Store" -delete
|
||||
find $DIST -name ".gitignore" -delete
|
||||
|
||||
# zip
|
||||
echo "zip files ..."
|
||||
cd "${DIST}/../" || exit
|
||||
if [ -f "${ZIP}" ]; then
|
||||
rm -f "${ZIP}"
|
||||
fi
|
||||
zip -r -X -q "${ZIP}" ${NAME}/
|
||||
rm -rf ${NAME}
|
||||
cd - || exit
|
||||
|
||||
echo "[done]"
|
||||
}
|
||||
|
||||
function lookup-version() {
|
||||
FILE=$1
|
||||
VERSION_DATA=$(cat $FILE)
|
||||
re="Version[ ]+=[ ]+\"([0-9.]+)\""
|
||||
if [[ $VERSION_DATA =~ $re ]]; then
|
||||
VERSION=${BASH_REMATCH[1]}
|
||||
echo $VERSION
|
||||
else
|
||||
echo "could not match version"
|
||||
exit
|
||||
fi
|
||||
}
|
||||
|
||||
build $1 $2
|
||||
139
build/build.sh
139
build/build.sh
@@ -1,66 +1,141 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
function build() {
|
||||
ROOT=$(dirname $0)
|
||||
ROOT=$(dirname "$0")
|
||||
JS_ROOT=$ROOT/../web/public/js
|
||||
NAME="edge-admin"
|
||||
DIST=$ROOT/"../dist/${NAME}"
|
||||
OS=${1}
|
||||
ARCH=${2}
|
||||
TAG=${3}
|
||||
|
||||
if [ -z $OS ]; then
|
||||
if [ -z "$OS" ]; then
|
||||
echo "usage: build.sh OS ARCH"
|
||||
exit
|
||||
fi
|
||||
if [ -z $ARCH ]; then
|
||||
if [ -z "$ARCH" ]; then
|
||||
echo "usage: build.sh OS ARCH"
|
||||
exit
|
||||
fi
|
||||
if [ -z "$TAG" ]; then
|
||||
TAG="community"
|
||||
fi
|
||||
|
||||
VERSION=$(lookup-version $ROOT/../internal/const/const.go)
|
||||
ZIP="${NAME}-${OS}-${ARCH}-v${VERSION}.zip"
|
||||
# checking environment
|
||||
echo "checking required commands ..."
|
||||
commands=("zip" "unzip" "go" "find" "sed")
|
||||
for cmd in "${commands[@]}"; do
|
||||
if [ "$(which "${cmd}")" ]; then
|
||||
echo "checking ${cmd}: ok"
|
||||
else
|
||||
echo "checking ${cmd}: not found"
|
||||
return
|
||||
fi
|
||||
done
|
||||
|
||||
# check edge-api
|
||||
APINodeVersion=$(lookup-version $ROOT"/../../EdgeAPI/internal/const/const.go")
|
||||
VERSION=$(lookup-version "$ROOT"/../internal/const/const.go)
|
||||
ZIP="${NAME}-${OS}-${ARCH}-${TAG}-v${VERSION}.zip"
|
||||
|
||||
# build edge-api
|
||||
APINodeVersion=$(lookup-version "$ROOT""/../../EdgeAPI/internal/const/const.go")
|
||||
echo "building edge-api v${APINodeVersion} ..."
|
||||
EDGE_API_BUILD_SCRIPT=$ROOT"/../../EdgeAPI/build/build.sh"
|
||||
if [ ! -f $EDGE_API_BUILD_SCRIPT ]; then
|
||||
if [ ! -f "$EDGE_API_BUILD_SCRIPT" ]; then
|
||||
echo "unable to find edge-api build script 'EdgeAPI/build/build.sh'"
|
||||
exit
|
||||
fi
|
||||
|
||||
cd $ROOT"/../../EdgeAPI/build"
|
||||
cd "$ROOT""/../../EdgeAPI/build" || exit
|
||||
echo "=============================="
|
||||
./build.sh $OS $ARCH
|
||||
./build.sh "$OS" "$ARCH" $TAG
|
||||
echo "=============================="
|
||||
cd -
|
||||
cd - || exit
|
||||
|
||||
# generate files
|
||||
echo "generating files ..."
|
||||
env CGO_ENABLED=0 go run -tags $TAG "$ROOT"/../cmd/edge-admin/main.go generate
|
||||
if [ "$(which uglifyjs)" ]; then
|
||||
echo "compress to component.js ..."
|
||||
uglifyjs --compress --mangle -- "${JS_ROOT}"/components.src.js > "${JS_ROOT}"/components.js
|
||||
uglifyjs --compress --mangle -- "${JS_ROOT}"/utils.js > "${JS_ROOT}"/utils.min.js
|
||||
else
|
||||
echo "copy to component.js ..."
|
||||
cp "${JS_ROOT}"/components.src.js "${JS_ROOT}"/components.js
|
||||
cp "${JS_ROOT}"/utils.js "${JS_ROOT}"/utils.min.js
|
||||
fi
|
||||
|
||||
# create dir & copy files
|
||||
echo "copying ..."
|
||||
if [ ! -d $DIST ]; then
|
||||
mkdir $DIST
|
||||
mkdir $DIST/bin
|
||||
mkdir $DIST/configs
|
||||
mkdir $DIST/logs
|
||||
if [ ! -d "$DIST" ]; then
|
||||
mkdir "$DIST"
|
||||
mkdir "$DIST"/bin
|
||||
mkdir "$DIST"/configs
|
||||
mkdir "$DIST"/logs
|
||||
fi
|
||||
|
||||
cp -R $ROOT/../web $DIST/
|
||||
rm -f $DIST/web/tmp/*
|
||||
cp $ROOT/configs/server.template.yaml $DIST/configs/
|
||||
cp -R "$ROOT"/../web "$DIST"/
|
||||
rm -f "$DIST"/web/tmp/*
|
||||
rm -rf "$DIST"/web/public/js/components
|
||||
rm -f "$DIST"/web/public/js/components.src.js
|
||||
cp "$ROOT"/configs/server.template.yaml "$DIST"/configs/
|
||||
|
||||
EDGE_API_ZIP_FILE=$ROOT"/../../EdgeAPI/dist/edge-api-${OS}-${ARCH}-v${APINodeVersion}.zip"
|
||||
cp $EDGE_API_ZIP_FILE $DIST/
|
||||
cd $DIST/
|
||||
unzip -q $(basename $EDGE_API_ZIP_FILE)
|
||||
rm -f $(basename $EDGE_API_ZIP_FILE)
|
||||
cd -
|
||||
# change _plus.[ext] to .[ext]
|
||||
if [ "${TAG}" = "plus" ]; then
|
||||
echo "converting filenames ..."
|
||||
exts=("html" "js" "css")
|
||||
for ext in "${exts[@]}"; do
|
||||
pattern="*_plus."${ext}
|
||||
find "$DIST"/web/views -type f -name "$pattern" | \
|
||||
while read filename; do
|
||||
mv "${filename}" "${filename/_plus."${ext}"/."${ext}"}"
|
||||
done
|
||||
done
|
||||
fi
|
||||
|
||||
EDGE_API_ZIP_FILE=$ROOT"/../../EdgeAPI/dist/edge-api-${OS}-${ARCH}-${TAG}-v${APINodeVersion}.zip"
|
||||
cp "$EDGE_API_ZIP_FILE" "$DIST"/
|
||||
cd "$DIST"/ || exit
|
||||
unzip -q "$(basename "$EDGE_API_ZIP_FILE")"
|
||||
rm -f "$(basename "$EDGE_API_ZIP_FILE")"
|
||||
cd - || exit
|
||||
|
||||
# find gcc
|
||||
GCC_DIR=""
|
||||
CC_PATH=""
|
||||
CXX_PATH=""
|
||||
if [ "${ARCH}" == "amd64" ]; then
|
||||
GCC_DIR="/usr/local/gcc/x86_64-unknown-linux-gnu/bin"
|
||||
CC_PATH="x86_64-unknown-linux-gnu-gcc"
|
||||
CXX_PATH="x86_64-unknown-linux-gnu-g++"
|
||||
fi
|
||||
if [ "${ARCH}" == "arm64" ]; then
|
||||
GCC_DIR="/usr/local/gcc/aarch64-unknown-linux-gnu/bin"
|
||||
CC_PATH="aarch64-unknown-linux-gnu-gcc"
|
||||
CXX_PATH="aarch64-unknown-linux-gnu-g++"
|
||||
fi
|
||||
|
||||
# build
|
||||
echo "building "${NAME}" ..."
|
||||
env GOOS=$OS GOARCH=$GOARCH go build -ldflags="-s -w" -o $DIST/bin/${NAME} $ROOT/../cmd/edge-admin/main.go
|
||||
echo "building ${NAME} ..."
|
||||
if [ -f "${GCC_DIR}/${CC_PATH}" ]; then
|
||||
echo " building ${NAME} with gcc ..."
|
||||
env CC="${GCC_DIR}/${CC_PATH}" \
|
||||
CXX="${GCC_DIR}/${CXX_PATH}" \
|
||||
CGO_ENABLED=1 \
|
||||
GOOS="$OS" GOARCH="$ARCH" go build -trimpath -tags "${TAG} gcc" -ldflags="-linkmode external -extldflags -static -s -w" -o "$DIST"/bin/${NAME} "$ROOT"/../cmd/edge-admin/main.go
|
||||
else
|
||||
GOOS="$OS" GOARCH="$ARCH" go build -trimpath -tags $TAG -ldflags="-s -w" -o "$DIST"/bin/${NAME} "$ROOT"/../cmd/edge-admin/main.go
|
||||
fi
|
||||
if [ ! -f "${DIST}/bin/${NAME}" ]; then
|
||||
echo "build '${NAME}' failed!"
|
||||
exit
|
||||
fi
|
||||
|
||||
# delete hidden files
|
||||
find $DIST -name ".DS_Store" -delete
|
||||
find $DIST -name ".gitignore" -delete
|
||||
find "$DIST" -name ".DS_Store" -delete
|
||||
find "$DIST" -name ".gitignore" -delete
|
||||
find "$DIST" -name "*.less" -delete
|
||||
#find "$DIST" -name "*.css.map" -delete
|
||||
#find "$DIST" -name "*.js.map" -delete
|
||||
|
||||
# zip
|
||||
echo "zip files ..."
|
||||
@@ -77,15 +152,15 @@ function build() {
|
||||
|
||||
function lookup-version() {
|
||||
FILE=$1
|
||||
VERSION_DATA=$(cat $FILE)
|
||||
VERSION_DATA=$(cat "$FILE")
|
||||
re="Version[ ]+=[ ]+\"([0-9.]+)\""
|
||||
if [[ $VERSION_DATA =~ $re ]]; then
|
||||
VERSION=${BASH_REMATCH[1]}
|
||||
echo $VERSION
|
||||
echo "$VERSION"
|
||||
else
|
||||
echo "could not match version"
|
||||
exit
|
||||
fi
|
||||
}
|
||||
|
||||
build $1 $2
|
||||
build "$1" "$2" "$3"
|
||||
|
||||
4
build/configs/.gitignore
vendored
4
build/configs/.gitignore
vendored
@@ -1,4 +1,6 @@
|
||||
api.yaml
|
||||
api_admin.yaml
|
||||
server.yaml
|
||||
api_db.yaml
|
||||
*.pem
|
||||
*.pem
|
||||
*.cache.json
|
||||
@@ -1,4 +0,0 @@
|
||||
rpc:
|
||||
endpoints: [ "http://127.0.0.1:8003" ]
|
||||
nodeId: ""
|
||||
secret: ""
|
||||
3
build/configs/api_admin.template.yaml
Normal file
3
build/configs/api_admin.template.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
rpc.endpoints: [ "http://127.0.0.1:8003" ]
|
||||
nodeId: ""
|
||||
secret: ""
|
||||
22
build/generate.sh
Executable file
22
build/generate.sh
Executable file
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
JS_ROOT=../web/public/js
|
||||
|
||||
echo "generating component.src.js ..."
|
||||
env CGO_ENABLED=0 go run -tags=community ../cmd/edge-admin/main.go generate
|
||||
|
||||
if [ "$(which uglifyjs)" ]; then
|
||||
echo "compress to component.js ..."
|
||||
uglifyjs --compress --mangle -- ${JS_ROOT}/components.src.js > ${JS_ROOT}/components.js
|
||||
|
||||
echo "compress to utils.min.js ..."
|
||||
uglifyjs --compress --mangle -- ${JS_ROOT}/utils.js > ${JS_ROOT}/utils.min.js
|
||||
else
|
||||
echo "copy to component.js ..."
|
||||
cp ${JS_ROOT}/components.src.js ${JS_ROOT}/components.js
|
||||
|
||||
echo "copy to utils.min.js ..."
|
||||
cp ${JS_ROOT}/utils.js ${JS_ROOT}/utils.min.js
|
||||
fi
|
||||
|
||||
echo "ok"
|
||||
1
build/licenses/README.md
Normal file
1
build/licenses/README.md
Normal file
@@ -0,0 +1 @@
|
||||
这个目录下我们列举了所有需要公开声明的第三方License,如果有遗漏,烦请告知 goedge.cdn@gmail.com。再次感谢这些开源软件项目和贡献人员!
|
||||
30
build/licenses/miekg-dns.md
Normal file
30
build/licenses/miekg-dns.md
Normal file
@@ -0,0 +1,30 @@
|
||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
As this is fork of the official Go code the same license applies.
|
||||
Extensions of the original work are copyright (c) 2011 Miek Gieben
|
||||
@@ -1,27 +1,48 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/apps"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/gen"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/nodes"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web"
|
||||
_ "github.com/TeaOSLab/EdgeCommon/pkg/langs/messages"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
_ "github.com/iwind/TeaGo/bootstrap"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/gosock/pkg/gosock"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := apps.NewAppCmd().
|
||||
var app = apps.NewAppCmd().
|
||||
Version(teaconst.Version).
|
||||
Product(teaconst.ProductName).
|
||||
Usage(teaconst.ProcessName+" [-v|start|stop|restart|service|daemon|reset]").
|
||||
Usage(teaconst.ProcessName+" [-h|-v|start|stop|restart|service|daemon|reset|recover|demo|upgrade]").
|
||||
Usage(teaconst.ProcessName+" [dev|prod]").
|
||||
Option("-h", "show this help").
|
||||
Option("-v", "show version").
|
||||
Option("start", "start the service").
|
||||
Option("stop", "stop the service").
|
||||
Option("restart", "restart the service").
|
||||
Option("service", "register service into systemd").
|
||||
Option("daemon", "start the service with daemon").
|
||||
Option("reset", "reset configs")
|
||||
Option("reset", "reset configs").
|
||||
Option("recover", "enter recovery mode").
|
||||
Option("demo", "switch to demo mode").
|
||||
Option("dev", "switch to 'dev' mode").
|
||||
Option("prod", "switch to 'prod' mode").
|
||||
Option("upgrade [--url=URL]", "upgrade from official site or an url").
|
||||
Option("install-local-node", "install a local node").
|
||||
Option("security.reset", "reset security config")
|
||||
|
||||
app.On("daemon", func() {
|
||||
nodes.NewAdminNode().Daemon()
|
||||
@@ -40,10 +61,138 @@ func main() {
|
||||
fmt.Println("[ERROR]reset failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// reset local api
|
||||
var apiNodeExe = Tea.Root + "/edge-api/bin/edge-api"
|
||||
_, err = os.Stat(apiNodeExe)
|
||||
if err == nil {
|
||||
var cmd = exec.Command(apiNodeExe, "reset")
|
||||
var stderr = &bytes.Buffer{}
|
||||
cmd.Stderr = stderr
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
fmt.Println("reset api node failed: " + stderr.String())
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("done")
|
||||
})
|
||||
app.On("recover", func() {
|
||||
var sock = gosock.NewTmpSock(teaconst.ProcessName)
|
||||
if !sock.IsListening() {
|
||||
fmt.Println("[ERROR]the service not started yet, you should start the service first")
|
||||
return
|
||||
}
|
||||
_, err := sock.Send(&gosock.Command{Code: "recover"})
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]enter recovery mode failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
fmt.Println("enter recovery mode successfully")
|
||||
})
|
||||
app.On("demo", func() {
|
||||
var sock = gosock.NewTmpSock(teaconst.ProcessName)
|
||||
if !sock.IsListening() {
|
||||
fmt.Println("[ERROR]the service not started yet, you should start the service first")
|
||||
return
|
||||
}
|
||||
reply, err := sock.Send(&gosock.Command{Code: "demo"})
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]change demo mode failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
var isDemo = maps.NewMap(reply.Params).GetBool("isDemo")
|
||||
if isDemo {
|
||||
fmt.Println("change demo mode to: on")
|
||||
} else {
|
||||
fmt.Println("change demo mode to: off")
|
||||
}
|
||||
})
|
||||
app.On("generate", func() {
|
||||
err := gen.Generate()
|
||||
if err != nil {
|
||||
fmt.Println("generate failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
})
|
||||
app.On("dev", func() {
|
||||
var env = "dev"
|
||||
var sock = gosock.NewTmpSock(teaconst.ProcessName)
|
||||
_, err := sock.Send(&gosock.Command{
|
||||
Code: env,
|
||||
Params: nil,
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Println("failed to switch to '" + env + "': " + err.Error())
|
||||
} else {
|
||||
fmt.Println("switch to '" + env + "' ok")
|
||||
}
|
||||
})
|
||||
app.On("prod", func() {
|
||||
var env = "prod"
|
||||
var sock = gosock.NewTmpSock(teaconst.ProcessName)
|
||||
_, err := sock.Send(&gosock.Command{
|
||||
Code: env,
|
||||
Params: nil,
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Println("failed to switch to '" + env + "': " + err.Error())
|
||||
} else {
|
||||
fmt.Println("switch to '" + env + "' ok")
|
||||
}
|
||||
})
|
||||
app.On("upgrade", func() {
|
||||
var downloadURL = ""
|
||||
var flagSet = flag.NewFlagSet("", flag.ContinueOnError)
|
||||
flagSet.StringVar(&downloadURL, "url", "", "new version download url")
|
||||
_ = flagSet.Parse(os.Args[2:])
|
||||
|
||||
var manager = utils.NewUpgradeManager("admin", downloadURL)
|
||||
log.Println("checking latest version ...")
|
||||
var ticker = time.NewTicker(1 * time.Second)
|
||||
go func() {
|
||||
var lastProgress float32 = 0
|
||||
var isStarted = false
|
||||
for range ticker.C {
|
||||
if manager.IsDownloading() {
|
||||
if !isStarted {
|
||||
log.Println("start downloading v" + manager.NewVersion() + " ...")
|
||||
isStarted = true
|
||||
}
|
||||
var progress = manager.Progress()
|
||||
if progress >= 0 {
|
||||
if progress == 0 || progress == 1 || progress-lastProgress >= 0.1 {
|
||||
lastProgress = progress
|
||||
log.Printf("%.2f%%", manager.Progress()*100)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
err := manager.Start()
|
||||
if err != nil {
|
||||
log.Println("upgrade failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
log.Println("finished!")
|
||||
log.Println("restarting ...")
|
||||
app.RunRestart()
|
||||
})
|
||||
app.On("security.reset", func() {
|
||||
var sock = gosock.NewTmpSock(teaconst.ProcessName)
|
||||
if !sock.IsListening() {
|
||||
fmt.Println("[ERROR]the service not started yet, you should start the service first")
|
||||
return
|
||||
}
|
||||
_, _ = sock.Send(&gosock.Command{
|
||||
Code: "security.reset",
|
||||
})
|
||||
fmt.Println("ok")
|
||||
})
|
||||
app.Run(func() {
|
||||
adminNode := nodes.NewAdminNode()
|
||||
var adminNode = nodes.NewAdminNode()
|
||||
adminNode.Run()
|
||||
})
|
||||
}
|
||||
|
||||
2
dist/.gitignore
vendored
2
dist/.gitignore
vendored
@@ -1,2 +1,2 @@
|
||||
*.zip
|
||||
shield-admin
|
||||
edge-admin
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 72 KiB |
BIN
doc/architect-zh.png
Normal file
BIN
doc/architect-zh.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.4 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 203 KiB After Width: | Height: | Size: 225 KiB |
1
docker/.gitignore
vendored
Normal file
1
docker/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.zip
|
||||
41
docker/Dockerfile
Normal file
41
docker/Dockerfile
Normal file
@@ -0,0 +1,41 @@
|
||||
FROM --platform=linux/amd64 alpine:latest
|
||||
LABEL maintainer="goedge.cdn@gmail.com"
|
||||
ENV TZ "Asia/Shanghai"
|
||||
ENV VERSION 1.3.5
|
||||
ENV ROOT_DIR /usr/local/goedge
|
||||
ENV TAR_FILE edge-admin-linux-amd64-plus-v${VERSION}.zip
|
||||
|
||||
# remote official repository
|
||||
ENV TAR_URL "https://dl.goedge.cn/edge/v${VERSION}/edge-admin-linux-amd64-plus-v${VERSION}.zip"
|
||||
|
||||
# your local repository
|
||||
#ENV TAR_URL "http://192.168.2.61:8080/edge-admin-linux-amd64-plus-v${VERSION}.zip"
|
||||
|
||||
RUN apk add --no-cache tzdata
|
||||
|
||||
RUN apk add wget
|
||||
RUN mkdir ${ROOT_DIR}; \
|
||||
cd ${ROOT_DIR}; \
|
||||
wget ${TAR_URL} -O ${TAR_FILE}; \
|
||||
apk add unzip; \
|
||||
unzip ${TAR_FILE}; \
|
||||
rm -f ${TAR_FILE}
|
||||
|
||||
RUN apk add mysql mysql-client; \
|
||||
sed -e "s/\[mysqld\]/\[mysqld\]\n\ndatadir=\/var\/lib\/mysql\nport=3306\ninnodb_flush_log_at_trx_commit=2\nmax_connections=256\nmax_prepared_stmt_count=65535\nbinlog_cache_size=1M\nbinlog_stmt_cache_size=1M\nthread_cache_size=32\nbinlog_expire_logs_seconds=1209600\n\n/" /etc/my.cnf > /tmp/my.cnf; \
|
||||
cp /tmp/my.cnf /etc/my.cnf; \
|
||||
sed -e "s/skip-networking/#skip-networking/" /etc/my.cnf.d/mariadb-server.cnf > /tmp/mariadb-server.cnf; \
|
||||
cp /tmp/mariadb-server.cnf /etc/my.cnf.d/mariadb-server.cnf; \
|
||||
mysql_install_db --user=mysql
|
||||
RUN mysqld_safe --user=mysql & \
|
||||
sleep 5; \
|
||||
mysql -uroot -hlocalhost --execute="ALTER USER 'root'@'localhost' IDENTIFIED BY '123456';"
|
||||
|
||||
RUN echo -e "#!/usr/bin/env sh\n\nmysqld_safe --user=mysql &\n/usr/local/goedge/edge-admin/bin/edge-admin\n" > ${ROOT_DIR}/run.sh; \
|
||||
chmod u+x ${ROOT_DIR}/run.sh
|
||||
|
||||
EXPOSE 7788
|
||||
EXPOSE 8001
|
||||
EXPOSE 3306
|
||||
|
||||
ENTRYPOINT [ "/usr/local/goedge/run.sh" ]
|
||||
5
docker/build.sh
Executable file
5
docker/build.sh
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
VERSION=latest
|
||||
|
||||
docker build --no-cache -t goedge/edge-admin:${VERSION} .
|
||||
5
docker/run.sh
Executable file
5
docker/run.sh
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
VERSION=latest
|
||||
|
||||
docker run -d -p 7788:7788 -p 8001:8001 -p 3306:3306 --name edge-admin goedge/edge-admin:${VERSION}
|
||||
49
go.mod
49
go.mod
@@ -1,21 +1,50 @@
|
||||
module github.com/TeaOSLab/EdgeAdmin
|
||||
|
||||
go 1.15
|
||||
go 1.21
|
||||
|
||||
replace github.com/TeaOSLab/EdgeCommon => ../EdgeCommon
|
||||
|
||||
require (
|
||||
github.com/TeaOSLab/EdgeCommon v0.0.0-00010101000000-000000000000
|
||||
github.com/cespare/xxhash v1.1.0
|
||||
github.com/cespare/xxhash/v2 v2.2.0
|
||||
github.com/go-sql-driver/mysql v1.5.0
|
||||
github.com/go-yaml/yaml v2.1.0+incompatible
|
||||
github.com/iwind/TeaGo v0.0.0-20201206115018-cdd967bfb13d
|
||||
github.com/miekg/dns v1.1.35
|
||||
github.com/iwind/TeaGo v0.0.0-20240312020455-6f20b5121caf
|
||||
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4
|
||||
github.com/miekg/dns v1.1.43
|
||||
github.com/quic-go/quic-go v0.42.0
|
||||
github.com/shirou/gopsutil/v3 v3.22.5
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||
github.com/tealeg/xlsx/v3 v3.2.3
|
||||
github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119
|
||||
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c // indirect
|
||||
google.golang.org/grpc v1.32.0
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776
|
||||
github.com/xlzd/gotp v0.1.0
|
||||
golang.org/x/crypto v0.22.0
|
||||
golang.org/x/sys v0.19.0
|
||||
google.golang.org/grpc v1.63.2
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/frankban/quicktest v1.11.3 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/google/btree v1.0.0 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20240402174815-29b9bb013b0f // indirect
|
||||
github.com/kr/pretty v0.2.1 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.17.1 // indirect
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/quic-go/qpack v0.4.0 // indirect
|
||||
github.com/rogpeppe/fastuuid v1.2.0 // indirect
|
||||
github.com/shabbyrobe/xmlwriter v0.0.0-20200208144257-9fca06d00ffa // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
go.uber.org/mock v0.4.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 // indirect
|
||||
golang.org/x/mod v0.17.0 // indirect
|
||||
golang.org/x/net v0.24.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/tools v0.19.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
)
|
||||
|
||||
263
go.sum
263
go.sum
@@ -1,206 +1,131 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/DataDog/sketches-go v0.0.0-20190923095040-43f19ad77ff7/go.mod h1:Q5DbzQ+3AkgGwymQO7aZFNP7ns2lZKGtvRBzRXfdi60=
|
||||
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200624174652-8d2f3be8b2d9/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/frankban/quicktest v1.5.0 h1:Tb4jWdSpdjKzTUicPnY61PZxKbDoGa7ABbrReT3gQVY=
|
||||
github.com/frankban/quicktest v1.5.0/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-redis/redis/v8 v8.0.0-beta.7/go.mod h1:FGJAWDWFht1sQ4qxyJHZZbVyvnVcKQN0E3u5/5lRz+g=
|
||||
github.com/frankban/quicktest v1.11.3 h1:8sXhOn0uLys67V8EsXLc6eszDs8VXWxL3iRvebPhedY=
|
||||
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
|
||||
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-yaml/yaml v2.1.0+incompatible h1:RYi2hDdss1u4YE7GwixGzWwVo47T8UQwnTLB6vQiq+o=
|
||||
github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/iwind/TeaGo v0.0.0-20200923021120-f5d76441fe9e/go.mod h1:KU4mS7QNiZ7QWEuDBk1zw0/Q2LrAPZv3tycEFBsuUwc=
|
||||
github.com/iwind/TeaGo v0.0.0-20201206115018-cdd967bfb13d h1:J4ohNUwOqCQkY62LFwaygfGtHJj+87pnzr+RJxEYSBo=
|
||||
github.com/iwind/TeaGo v0.0.0-20201206115018-cdd967bfb13d/go.mod h1:KU4mS7QNiZ7QWEuDBk1zw0/Q2LrAPZv3tycEFBsuUwc=
|
||||
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/pprof v0.0.0-20240402174815-29b9bb013b0f h1:f00RU+zOX+B3rLAmMMkzHUF2h1z4DeYR9tTCvEq2REY=
|
||||
github.com/google/pprof v0.0.0-20240402174815-29b9bb013b0f/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
|
||||
github.com/iwind/TeaGo v0.0.0-20240312020455-6f20b5121caf h1:WA9qgiynESu/DDTnLH6npRI5AK6UL9qwJ2YZ5qhJX5E=
|
||||
github.com/iwind/TeaGo v0.0.0-20240312020455-6f20b5121caf/go.mod h1:SfqVbWyIPdVflyA6lMgicZzsoGS8pyeLiTRe8/CIpGI=
|
||||
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4 h1:VWGsCqTzObdlbf7UUE3oceIpcEKi4C/YBUszQXk118A=
|
||||
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4/go.mod h1:H5Q7SXwbx3a97ecJkaS2sD77gspzE7HFUafBO0peEyA=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/miekg/dns v1.1.35 h1:oTfOaDH+mZkdcgdIjH6yBajRGtIwcwcaR+rt23ZSrJs=
|
||||
github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
||||
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/opentracing/opentracing-go v1.1.1-0.20190913142402-a7454ce5950e/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg=
|
||||
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
|
||||
github.com/onsi/ginkgo/v2 v2.17.1 h1:V++EzdbhI4ZV4ev0UTIj0PzhzOcReJFyJaLjtSF55M8=
|
||||
github.com/onsi/ginkgo/v2 v2.17.1/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs=
|
||||
github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
|
||||
github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||
github.com/pkg/profile v1.5.0 h1:042Buzk+NhDI+DeSAA62RwJL8VAuZUMQZUjCsRz1Mug=
|
||||
github.com/pkg/profile v1.5.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||
github.com/quic-go/quic-go v0.42.0 h1:uSfdap0eveIl8KXnipv9K7nlwZ5IqLlYOpJ58u5utpM=
|
||||
github.com/quic-go/quic-go v0.42.0/go.mod h1:132kz4kL3F9vxhW3CtQJLDVwcFe5wdWeJXXijhsO57M=
|
||||
github.com/rogpeppe/fastuuid v1.2.0 h1:Ppwyp6VYCF1nvBTXL3trRso7mXMlRrw9ooo375wvi2s=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/shabbyrobe/xmlwriter v0.0.0-20200208144257-9fca06d00ffa h1:2cO3RojjYl3hVTbEvJVqrMaFmORhL6O06qdW42toftk=
|
||||
github.com/shabbyrobe/xmlwriter v0.0.0-20200208144257-9fca06d00ffa/go.mod h1:Yjr3bdWaVWyME1kha7X0jsz3k2DgXNa1Pj3XGyUAbx8=
|
||||
github.com/shirou/gopsutil/v3 v3.22.5 h1:atX36I/IXgFiB81687vSiBI5zrMsxcIBkP9cQMJQoJA=
|
||||
github.com/shirou/gopsutil/v3 v3.22.5/go.mod h1:so9G9VzeHt/hsd0YwqprnjHnfARAUktauykSbr+y2gA=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/tealeg/xlsx/v3 v3.2.3 h1:MXnVh+9Y8cUglowItTy2HL3Kv6z+q/0aNjeKuTsVqZQ=
|
||||
github.com/tealeg/xlsx/v3 v3.2.3/go.mod h1:0hGmAEoZ48SS1ZAE6eqZJkJVXgOMY+8a33vjXa8S8HA=
|
||||
github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk=
|
||||
github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ=
|
||||
github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119 h1:YyPWX3jLOtYKulBR6AScGIs74lLrJcgeKRwcbAuQOG4=
|
||||
github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119/go.mod h1:/nuTSlK+okRfR/vnIPqR89fFKonnWPiZymN5ydRJkX8=
|
||||
go.opentelemetry.io/otel v0.7.0/go.mod h1:aZMyHG5TqDOXEgH2tyLiXSUKly1jT3yqE9PmrzIeCdo=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20200513190911-00229845015e/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c h1:UIcGWL6/wpCfyGuJnRFJRurA+yj8RrW7Q6x2YMCXt6c=
|
||||
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
github.com/xlzd/gotp v0.1.0 h1:37blvlKCh38s+fkem+fFh7sMnceltoIEBYTVXyoa5Po=
|
||||
github.com/xlzd/gotp v0.1.0/go.mod h1:ndLJ3JKzi3xLmUProq4LLxCuECL93dG9WASNLpHz8qg=
|
||||
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
|
||||
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
|
||||
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
||||
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
||||
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88pSyOt+UgdZw2BFZ+lEw=
|
||||
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ=
|
||||
golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
|
||||
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
|
||||
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
|
||||
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20191009194640-548a555dbc03/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0=
|
||||
google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be h1:LG9vZxsWGOmUKieR8wPAUR3u3MpnYFQZROPIMaXh7/A=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
|
||||
google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
|
||||
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
@@ -1,30 +1,39 @@
|
||||
package apps
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"github.com/iwind/gosock/pkg/gosock"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// App命令帮助
|
||||
// AppCmd App命令帮助
|
||||
type AppCmd struct {
|
||||
product string
|
||||
version string
|
||||
usage string
|
||||
usages []string
|
||||
options []*CommandHelpOption
|
||||
appendStrings []string
|
||||
|
||||
directives []*Directive
|
||||
|
||||
sock *gosock.Sock
|
||||
}
|
||||
|
||||
func NewAppCmd() *AppCmd {
|
||||
return &AppCmd{}
|
||||
return &AppCmd{
|
||||
sock: gosock.NewTmpSock(teaconst.ProcessName),
|
||||
}
|
||||
}
|
||||
|
||||
type CommandHelpOption struct {
|
||||
@@ -32,25 +41,25 @@ type CommandHelpOption struct {
|
||||
Description string
|
||||
}
|
||||
|
||||
// 产品
|
||||
// Product 产品
|
||||
func (this *AppCmd) Product(product string) *AppCmd {
|
||||
this.product = product
|
||||
return this
|
||||
}
|
||||
|
||||
// 版本
|
||||
// Version 版本
|
||||
func (this *AppCmd) Version(version string) *AppCmd {
|
||||
this.version = version
|
||||
return this
|
||||
}
|
||||
|
||||
// 使用方法
|
||||
// Usage 使用方法
|
||||
func (this *AppCmd) Usage(usage string) *AppCmd {
|
||||
this.usage = usage
|
||||
this.usages = append(this.usages, usage)
|
||||
return this
|
||||
}
|
||||
|
||||
// 选项
|
||||
// Option 选项
|
||||
func (this *AppCmd) Option(code string, description string) *AppCmd {
|
||||
this.options = append(this.options, &CommandHelpOption{
|
||||
Code: code,
|
||||
@@ -59,18 +68,20 @@ func (this *AppCmd) Option(code string, description string) *AppCmd {
|
||||
return this
|
||||
}
|
||||
|
||||
// 附加内容
|
||||
// Append 附加内容
|
||||
func (this *AppCmd) Append(appendString string) *AppCmd {
|
||||
this.appendStrings = append(this.appendStrings, appendString)
|
||||
return this
|
||||
}
|
||||
|
||||
// 打印
|
||||
// Print 打印
|
||||
func (this *AppCmd) Print() {
|
||||
fmt.Println(this.product + " v" + this.version)
|
||||
|
||||
usage := this.usage
|
||||
fmt.Println("Usage:", "\n "+usage)
|
||||
fmt.Println("Usage:")
|
||||
for _, usage := range this.usages {
|
||||
fmt.Println(" " + usage)
|
||||
}
|
||||
|
||||
if len(this.options) > 0 {
|
||||
fmt.Println("")
|
||||
@@ -104,7 +115,7 @@ func (this *AppCmd) Print() {
|
||||
}
|
||||
}
|
||||
|
||||
// 添加指令
|
||||
// On 添加指令
|
||||
func (this *AppCmd) On(arg string, callback func()) {
|
||||
this.directives = append(this.directives, &Directive{
|
||||
Arg: arg,
|
||||
@@ -112,10 +123,10 @@ func (this *AppCmd) On(arg string, callback func()) {
|
||||
})
|
||||
}
|
||||
|
||||
// 运行
|
||||
// Run 运行
|
||||
func (this *AppCmd) Run(main func()) {
|
||||
// 获取参数
|
||||
args := os.Args[1:]
|
||||
var args = os.Args[1:]
|
||||
if len(args) > 0 {
|
||||
switch args[0] {
|
||||
case "-v", "version", "-version", "--version":
|
||||
@@ -131,7 +142,7 @@ func (this *AppCmd) Run(main func()) {
|
||||
this.runStop()
|
||||
return
|
||||
case "restart":
|
||||
this.runRestart()
|
||||
this.RunRestart()
|
||||
return
|
||||
case "status":
|
||||
this.runStatus()
|
||||
@@ -151,11 +162,8 @@ func (this *AppCmd) Run(main func()) {
|
||||
return
|
||||
}
|
||||
|
||||
// 记录PID
|
||||
_ = this.writePid()
|
||||
|
||||
// 日志
|
||||
writer := new(LogWriter)
|
||||
var writer = new(LogWriter)
|
||||
writer.Init()
|
||||
logs.SetWriter(writer)
|
||||
|
||||
@@ -165,7 +173,7 @@ func (this *AppCmd) Run(main func()) {
|
||||
|
||||
// 版本号
|
||||
func (this *AppCmd) runVersion() {
|
||||
fmt.Println(this.product+" v"+this.version, "(build: "+runtime.Version(), runtime.GOOS, runtime.GOARCH+")")
|
||||
fmt.Println(this.product+" v"+this.version, "(build: "+runtime.Version(), runtime.GOOS, runtime.GOARCH, teaconst.Tag+")")
|
||||
}
|
||||
|
||||
// 帮助
|
||||
@@ -175,40 +183,40 @@ func (this *AppCmd) runHelp() {
|
||||
|
||||
// 启动
|
||||
func (this *AppCmd) runStart() {
|
||||
proc := this.checkPid()
|
||||
if proc != nil {
|
||||
fmt.Println(this.product+" already started, pid:", proc.Pid)
|
||||
var pid = this.getPID()
|
||||
if pid > 0 {
|
||||
fmt.Println(this.product+" already started, pid:", pid)
|
||||
return
|
||||
}
|
||||
|
||||
cmd := exec.Command(os.Args[0])
|
||||
var cmd = exec.Command(this.exe())
|
||||
err := cmd.Start()
|
||||
if err != nil {
|
||||
fmt.Println(this.product+" start failed:", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// create symbolic links
|
||||
_ = this.createSymLinks()
|
||||
|
||||
fmt.Println(this.product+" started ok, pid:", cmd.Process.Pid)
|
||||
}
|
||||
|
||||
// 停止
|
||||
func (this *AppCmd) runStop() {
|
||||
proc := this.checkPid()
|
||||
if proc == nil {
|
||||
var pid = this.getPID()
|
||||
if pid == 0 {
|
||||
fmt.Println(this.product + " not started yet")
|
||||
return
|
||||
}
|
||||
|
||||
// 停止进程
|
||||
_ = proc.Signal(syscall.SIGQUIT)
|
||||
_, _ = this.sock.Send(&gosock.Command{Code: "stop"})
|
||||
|
||||
// 在Windows上经常不能及时释放资源
|
||||
_ = DeletePid(Tea.Root + "/bin/pid")
|
||||
fmt.Println(this.product+" stopped ok, pid:", proc.Pid)
|
||||
fmt.Println(this.product+" stopped ok, pid:", types.String(pid))
|
||||
}
|
||||
|
||||
// 重启
|
||||
func (this *AppCmd) runRestart() {
|
||||
// RunRestart 重启
|
||||
func (this *AppCmd) RunRestart() {
|
||||
this.runStop()
|
||||
time.Sleep(1 * time.Second)
|
||||
this.runStart()
|
||||
@@ -216,20 +224,79 @@ func (this *AppCmd) runRestart() {
|
||||
|
||||
// 状态
|
||||
func (this *AppCmd) runStatus() {
|
||||
proc := this.checkPid()
|
||||
if proc == nil {
|
||||
var pid = this.getPID()
|
||||
if pid == 0 {
|
||||
fmt.Println(this.product + " not started yet")
|
||||
} else {
|
||||
fmt.Println(this.product + " is running, pid: " + fmt.Sprintf("%d", proc.Pid))
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(this.product + " is running, pid: " + types.String(pid))
|
||||
}
|
||||
|
||||
// 检查PID
|
||||
func (this *AppCmd) checkPid() *os.Process {
|
||||
return CheckPid(Tea.Root + "/bin/pid")
|
||||
// 获取当前的PID
|
||||
func (this *AppCmd) getPID() int {
|
||||
if !this.sock.IsListening() {
|
||||
return 0
|
||||
}
|
||||
|
||||
reply, err := this.sock.Send(&gosock.Command{Code: "pid"})
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return maps.NewMap(reply.Params).GetInt("pid")
|
||||
}
|
||||
|
||||
// 写入PID
|
||||
func (this *AppCmd) writePid() error {
|
||||
return WritePid(Tea.Root + "/bin/pid")
|
||||
func (this *AppCmd) exe() string {
|
||||
var exe, _ = os.Executable()
|
||||
if len(exe) == 0 {
|
||||
exe = os.Args[0]
|
||||
}
|
||||
return exe
|
||||
}
|
||||
|
||||
// 创建软链接
|
||||
func (this *AppCmd) createSymLinks() error {
|
||||
if runtime.GOOS != "linux" {
|
||||
return nil
|
||||
}
|
||||
|
||||
var exe, _ = os.Executable()
|
||||
if len(exe) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var errorList = []string{}
|
||||
|
||||
// bin
|
||||
{
|
||||
var target = "/usr/bin/" + teaconst.ProcessName
|
||||
old, _ := filepath.EvalSymlinks(target)
|
||||
if old != exe {
|
||||
_ = os.Remove(target)
|
||||
err := os.Symlink(exe, target)
|
||||
if err != nil {
|
||||
errorList = append(errorList, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// log
|
||||
{
|
||||
var realPath = filepath.Dir(filepath.Dir(exe)) + "/logs/run.log"
|
||||
var target = "/var/log/" + teaconst.ProcessName + ".log"
|
||||
old, _ := filepath.EvalSymlinks(target)
|
||||
if old != realPath {
|
||||
_ = os.Remove(target)
|
||||
err := os.Symlink(realPath, target)
|
||||
if err != nil {
|
||||
errorList = append(errorList, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(errorList) > 0 {
|
||||
return errors.New(strings.Join(errorList, "\n"))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
// +build !windows
|
||||
|
||||
package apps
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// lock file
|
||||
func LockFile(fp *os.File) error {
|
||||
return syscall.Flock(int(fp.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
|
||||
}
|
||||
|
||||
func UnlockFile(fp *os.File) error {
|
||||
return syscall.Flock(int(fp.Fd()), syscall.LOCK_UN)
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
// +build windows
|
||||
|
||||
package apps
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
)
|
||||
|
||||
// lock file
|
||||
func LockFile(fp *os.File) error {
|
||||
return errors.New("not implemented on windows")
|
||||
}
|
||||
|
||||
func UnlockFile(fp *os.File) error {
|
||||
return errors.New("not implemented on windows")
|
||||
}
|
||||
@@ -1,51 +1,108 @@
|
||||
package apps
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils/sizes"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/files"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/utils/time"
|
||||
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||
"log"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type LogWriter struct {
|
||||
fileAppender *files.Appender
|
||||
fp *os.File
|
||||
c chan string
|
||||
}
|
||||
|
||||
func (this *LogWriter) Init() {
|
||||
// 创建目录
|
||||
dir := files.NewFile(Tea.LogDir())
|
||||
var dir = files.NewFile(Tea.LogDir())
|
||||
if !dir.Exists() {
|
||||
err := dir.Mkdir()
|
||||
if err != nil {
|
||||
log.Println("[error]" + err.Error())
|
||||
log.Println("[LOG]create log dir failed: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
logFile := files.NewFile(Tea.LogFile("run.log"))
|
||||
|
||||
// 打开要写入的日志文件
|
||||
appender, err := logFile.Appender()
|
||||
var logPath = Tea.LogFile("run.log")
|
||||
fp, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
log.Println("[LOG]open log file failed: " + err.Error())
|
||||
} else {
|
||||
this.fileAppender = appender
|
||||
this.fp = fp
|
||||
}
|
||||
|
||||
this.c = make(chan string, 1024)
|
||||
|
||||
// 异步写入文件
|
||||
var maxFileSize = 2 * sizes.G // 文件最大尺寸,超出此尺寸则清空
|
||||
if fp != nil {
|
||||
goman.New(func() {
|
||||
var totalSize int64 = 0
|
||||
stat, err := fp.Stat()
|
||||
if err == nil {
|
||||
totalSize = stat.Size()
|
||||
}
|
||||
|
||||
for message := range this.c {
|
||||
totalSize += int64(len(message))
|
||||
_, err := fp.WriteString(timeutil.Format("Y/m/d H:i:s ") + message + "\n")
|
||||
if err != nil {
|
||||
log.Println("[LOG]write log failed: " + err.Error())
|
||||
} else {
|
||||
// 如果太大则Truncate
|
||||
if totalSize > maxFileSize {
|
||||
_ = fp.Truncate(0)
|
||||
totalSize = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (this *LogWriter) Write(message string) {
|
||||
log.Println(message)
|
||||
backgroundEnv, _ := os.LookupEnv("EdgeBackground")
|
||||
if backgroundEnv != "on" {
|
||||
// 文件和行号
|
||||
var file string
|
||||
var line int
|
||||
if Tea.IsTesting() {
|
||||
var callDepth = 3
|
||||
var ok bool
|
||||
_, file, line, ok = runtime.Caller(callDepth)
|
||||
if ok {
|
||||
file = this.packagePath(file)
|
||||
}
|
||||
}
|
||||
|
||||
if this.fileAppender != nil {
|
||||
_, err := this.fileAppender.AppendString(timeutil.Format("Y/m/d H:i:s ") + message + "\n")
|
||||
if err != nil {
|
||||
log.Println("[error]" + err.Error())
|
||||
if len(file) > 0 {
|
||||
log.Println(message + " (" + file + ":" + strconv.Itoa(line) + ")")
|
||||
} else {
|
||||
log.Println(message)
|
||||
}
|
||||
}
|
||||
|
||||
this.c <- message
|
||||
}
|
||||
|
||||
func (this *LogWriter) Close() {
|
||||
if this.fileAppender != nil {
|
||||
_ = this.fileAppender.Close()
|
||||
if this.fp != nil {
|
||||
_ = this.fp.Close()
|
||||
}
|
||||
|
||||
close(this.c)
|
||||
}
|
||||
|
||||
func (this *LogWriter) packagePath(path string) string {
|
||||
var pieces = strings.Split(path, "/")
|
||||
if len(pieces) >= 2 {
|
||||
return strings.Join(pieces[len(pieces)-2:], "/")
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
package apps
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
var pidFileList = []*os.File{}
|
||||
|
||||
// 检查Pid
|
||||
func CheckPid(path string) *os.Process {
|
||||
// windows上打开的文件是不能删除的
|
||||
if runtime.GOOS == "windows" {
|
||||
if os.Remove(path) == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = file.Close()
|
||||
}()
|
||||
|
||||
// 是否能取得Lock
|
||||
err = LockFile(file)
|
||||
if err == nil {
|
||||
_ = UnlockFile(file)
|
||||
return nil
|
||||
}
|
||||
|
||||
pidBytes, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
pid := types.Int(string(pidBytes))
|
||||
|
||||
if pid <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
proc, _ := os.FindProcess(pid)
|
||||
return proc
|
||||
}
|
||||
|
||||
// 写入Pid
|
||||
func WritePid(path string) error {
|
||||
fp, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY|os.O_RDONLY, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if runtime.GOOS != "windows" {
|
||||
err = LockFile(fp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
pidFileList = append(pidFileList, fp) // hold the file pointers
|
||||
|
||||
_, err = fp.WriteString(fmt.Sprintf("%d", os.Getpid()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 写入Ppid
|
||||
func WritePpid(path string) error {
|
||||
fp, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY|os.O_RDONLY, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if runtime.GOOS != "windows" {
|
||||
err = LockFile(fp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
pidFileList = append(pidFileList, fp) // hold the file pointers
|
||||
|
||||
_, err = fp.WriteString(fmt.Sprintf("%d", os.Getppid()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 删除Pid
|
||||
func DeletePid(path string) error {
|
||||
_, err := os.Stat(path)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
for _, fp := range pidFileList {
|
||||
_ = UnlockFile(fp)
|
||||
_ = fp.Close()
|
||||
}
|
||||
return os.Remove(path)
|
||||
}
|
||||
@@ -1,9 +1,13 @@
|
||||
package configloaders
|
||||
|
||||
import (
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
@@ -14,11 +18,14 @@ const (
|
||||
AdminModuleCodeServer AdminModuleCode = "server" // 网站
|
||||
AdminModuleCodeNode AdminModuleCode = "node" // 节点
|
||||
AdminModuleCodeDNS AdminModuleCode = "dns" // DNS
|
||||
AdminModuleCodeNS AdminModuleCode = "ns" // 域名服务
|
||||
AdminModuleCodeAdmin AdminModuleCode = "admin" // 系统用户
|
||||
AdminModuleCodeUser AdminModuleCode = "user" // 平台用户
|
||||
AdminModuleCodeFinance AdminModuleCode = "finance" // 财务
|
||||
AdminModuleCodePlan AdminModuleCode = "plan" // 套餐
|
||||
AdminModuleCodeLog AdminModuleCode = "log" // 日志
|
||||
AdminModuleCodeSetting AdminModuleCode = "setting" // 设置
|
||||
AdminModuleCodeTicket AdminModuleCode = "ticket" // 工单
|
||||
AdminModuleCodeCommon AdminModuleCode = "common" // 只要登录就可以访问的模块
|
||||
)
|
||||
|
||||
@@ -42,6 +49,8 @@ func loadAdminModuleMapping() (map[int64]*AdminModuleList, error) {
|
||||
list := &AdminModuleList{
|
||||
IsSuper: m.IsSuper,
|
||||
Fullname: m.Fullname,
|
||||
Theme: m.Theme,
|
||||
Lang: m.Lang,
|
||||
}
|
||||
|
||||
for _, pbModule := range m.Modules {
|
||||
@@ -68,7 +77,7 @@ func NotifyAdminModuleMappingChange() error {
|
||||
return err
|
||||
}
|
||||
|
||||
// 检查用户是否存在
|
||||
// CheckAdmin 检查用户是否存在
|
||||
func CheckAdmin(adminId int64) bool {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
@@ -82,7 +91,7 @@ func CheckAdmin(adminId int64) bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
// 检查模块是否允许访问
|
||||
// AllowModule 检查模块是否允许访问
|
||||
func AllowModule(adminId int64, module string) bool {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
@@ -103,7 +112,7 @@ func AllowModule(adminId int64, module string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// 获取管理员第一个可访问模块
|
||||
// FindFirstAdminModule 获取管理员第一个可访问模块
|
||||
func FindFirstAdminModule(adminId int64) (module AdminModuleCode, ok bool) {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
@@ -118,7 +127,7 @@ func FindFirstAdminModule(adminId int64) (module AdminModuleCode, ok bool) {
|
||||
return
|
||||
}
|
||||
|
||||
// 查找某个管理员名称
|
||||
// FindAdminFullname 查找某个管理员名称
|
||||
func FindAdminFullname(adminId int64) string {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
@@ -130,53 +139,145 @@ func FindAdminFullname(adminId int64) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// 所有权限列表
|
||||
func AllModuleMaps() []maps.Map {
|
||||
return []maps.Map{
|
||||
// FindAdminTheme 查找某个管理员选择的风格
|
||||
func FindAdminTheme(adminId int64) string {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
list, ok := sharedAdminModuleMapping[adminId]
|
||||
if ok {
|
||||
return list.Theme
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// UpdateAdminTheme 设置某个管理员的风格
|
||||
func UpdateAdminTheme(adminId int64, theme string) {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
list, ok := sharedAdminModuleMapping[adminId]
|
||||
if ok {
|
||||
list.Theme = theme
|
||||
}
|
||||
}
|
||||
|
||||
// FindAdminLang 查找某个管理员选择的语言
|
||||
func FindAdminLang(adminId int64) string {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
list, ok := sharedAdminModuleMapping[adminId]
|
||||
if ok {
|
||||
return list.Lang
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// UpdateAdminLang 修改某个管理员选择的语言
|
||||
func UpdateAdminLang(adminId int64, langCode string) {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
list, ok := sharedAdminModuleMapping[adminId]
|
||||
if ok {
|
||||
list.Lang = langCode
|
||||
}
|
||||
}
|
||||
|
||||
func FindAdminLangForAction(actionPtr actions.ActionWrapper) (langCode langs.LangCode) {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
var adminId = actionPtr.Object().Session().GetInt64(teaconst.SessionAdminId)
|
||||
list, ok := sharedAdminModuleMapping[adminId]
|
||||
var result = ""
|
||||
if ok {
|
||||
result = list.Lang
|
||||
}
|
||||
|
||||
if len(result) == 0 {
|
||||
result = langs.ParseLangFromAction(actionPtr)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// AllModuleMaps 所有权限列表
|
||||
func AllModuleMaps(langCode string) []maps.Map {
|
||||
var m = []maps.Map{
|
||||
{
|
||||
"name": "看板",
|
||||
"name": langs.Message(langCode, codes.AdminMenu_Dashboard),
|
||||
"code": AdminModuleCodeDashboard,
|
||||
"url": "/dashboard",
|
||||
},
|
||||
{
|
||||
"name": "网站服务",
|
||||
"name": langs.Message(langCode, codes.AdminMenu_Servers),
|
||||
"code": AdminModuleCodeServer,
|
||||
"url": "/servers",
|
||||
},
|
||||
{
|
||||
"name": "边缘节点",
|
||||
"name": langs.Message(langCode, codes.AdminMenu_Nodes),
|
||||
"code": AdminModuleCodeNode,
|
||||
"url": "/clusters",
|
||||
},
|
||||
{
|
||||
"name": "域名解析",
|
||||
"name": langs.Message(langCode, codes.AdminMenu_DNS),
|
||||
"code": AdminModuleCodeDNS,
|
||||
"url": "/dns",
|
||||
},
|
||||
}
|
||||
if teaconst.IsPlus {
|
||||
m = append(m, maps.Map{
|
||||
"name": langs.Message(langCode, codes.AdminMenu_NS),
|
||||
"code": AdminModuleCodeNS,
|
||||
"url": "/ns",
|
||||
})
|
||||
}
|
||||
m = append(m, []maps.Map{
|
||||
{
|
||||
"name": "平台用户",
|
||||
"name": langs.Message(langCode, codes.AdminMenu_Users),
|
||||
"code": AdminModuleCodeUser,
|
||||
"url": "/users",
|
||||
},
|
||||
{
|
||||
"name": "系统用户",
|
||||
"name": langs.Message(langCode, codes.AdminMenu_Admins),
|
||||
"code": AdminModuleCodeAdmin,
|
||||
"url": "/admins",
|
||||
},
|
||||
{
|
||||
"name": "财务管理",
|
||||
"name": langs.Message(langCode, codes.AdminMenu_Finance),
|
||||
"code": AdminModuleCodeFinance,
|
||||
"url": "/finance",
|
||||
},
|
||||
}...)
|
||||
|
||||
if teaconst.IsPlus {
|
||||
m = append(m, []maps.Map{
|
||||
{
|
||||
"name": langs.Message(langCode, codes.AdminMenu_Plans),
|
||||
"code": AdminModuleCodePlan,
|
||||
"url": "/plans",
|
||||
},
|
||||
{
|
||||
"name": langs.Message(langCode, codes.AdminMenu_Tickets),
|
||||
"code": AdminModuleCodeTicket,
|
||||
"url": "/tickets",
|
||||
},
|
||||
}...)
|
||||
}
|
||||
|
||||
m = append(m, []maps.Map{
|
||||
{
|
||||
"name": "日志审计",
|
||||
"name": langs.Message(langCode, codes.AdminMenu_Logs),
|
||||
"code": AdminModuleCodeLog,
|
||||
"url": "/log",
|
||||
},
|
||||
{
|
||||
"name": "系统设置",
|
||||
"name": langs.Message(langCode, codes.AdminMenu_Settings),
|
||||
"code": AdminModuleCodeSetting,
|
||||
"url": "/settings",
|
||||
},
|
||||
}
|
||||
}...)
|
||||
return m
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ type AdminModuleList struct {
|
||||
IsSuper bool
|
||||
Modules []*systemconfigs.AdminModule
|
||||
Fullname string
|
||||
Theme string
|
||||
Lang string
|
||||
}
|
||||
|
||||
func (this *AdminModuleList) Allow(module string) bool {
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
)
|
||||
|
||||
func TestLoadAdminModuleMapping(t *testing.T) {
|
||||
m, err := LoadAdminModuleMapping()
|
||||
m, err := loadAdminModuleMapping()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -3,18 +3,18 @@ package configloaders
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
var sharedAdminUIConfig *systemconfigs.AdminUIConfig = nil
|
||||
|
||||
const (
|
||||
AdminUISettingName = "adminUIConfig"
|
||||
)
|
||||
|
||||
func LoadAdminUIConfig() (*systemconfigs.AdminUIConfig, error) {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
@@ -28,6 +28,15 @@ func LoadAdminUIConfig() (*systemconfigs.AdminUIConfig, error) {
|
||||
return &v, nil
|
||||
}
|
||||
|
||||
func ReloadAdminUIConfig() error {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
sharedAdminUIConfig = nil
|
||||
_, err := loadAdminUIConfig()
|
||||
return err
|
||||
}
|
||||
|
||||
func UpdateAdminUIConfig(uiConfig *systemconfigs.AdminUIConfig) error {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
@@ -41,7 +50,7 @@ func UpdateAdminUIConfig(uiConfig *systemconfigs.AdminUIConfig) error {
|
||||
return err
|
||||
}
|
||||
_, err = rpcClient.SysSettingRPC().UpdateSysSetting(rpcClient.Context(0), &pb.UpdateSysSettingRequest{
|
||||
Code: AdminUISettingName,
|
||||
Code: systemconfigs.SettingCodeAdminUIConfig,
|
||||
ValueJSON: valueJSON,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -49,10 +58,13 @@ func UpdateAdminUIConfig(uiConfig *systemconfigs.AdminUIConfig) error {
|
||||
}
|
||||
sharedAdminUIConfig = uiConfig
|
||||
|
||||
// timezone
|
||||
updateTimeZone(uiConfig)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 是否显示财务信息
|
||||
// ShowFinance 是否显示财务信息
|
||||
func ShowFinance() bool {
|
||||
config, _ := LoadAdminUIConfig()
|
||||
if config != nil && !config.ShowFinance {
|
||||
@@ -70,7 +82,7 @@ func loadAdminUIConfig() (*systemconfigs.AdminUIConfig, error) {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := rpcClient.SysSettingRPC().ReadSysSetting(rpcClient.Context(0), &pb.ReadSysSettingRequest{
|
||||
Code: AdminUISettingName,
|
||||
Code: systemconfigs.SettingCodeAdminUIConfig,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -80,23 +92,39 @@ func loadAdminUIConfig() (*systemconfigs.AdminUIConfig, error) {
|
||||
return sharedAdminUIConfig, nil
|
||||
}
|
||||
|
||||
config := &systemconfigs.AdminUIConfig{}
|
||||
var config = &systemconfigs.AdminUIConfig{}
|
||||
err = json.Unmarshal(resp.ValueJSON, config)
|
||||
if err != nil {
|
||||
logs.Println("[UI_MANAGER]" + err.Error())
|
||||
sharedAdminUIConfig = defaultAdminUIConfig()
|
||||
return sharedAdminUIConfig, nil
|
||||
}
|
||||
|
||||
// timezone
|
||||
updateTimeZone(config)
|
||||
|
||||
sharedAdminUIConfig = config
|
||||
return sharedAdminUIConfig, nil
|
||||
}
|
||||
|
||||
func defaultAdminUIConfig() *systemconfigs.AdminUIConfig {
|
||||
return &systemconfigs.AdminUIConfig{
|
||||
ProductName: "GoEdge",
|
||||
AdminSystemName: "GoEdge管理员系统",
|
||||
ProductName: langs.DefaultMessage(codes.AdminUI_DefaultProductName),
|
||||
AdminSystemName: langs.DefaultMessage(codes.AdminUI_DefaultSystemName),
|
||||
ShowOpenSourceInfo: true,
|
||||
ShowVersion: true,
|
||||
ShowFinance: true,
|
||||
DefaultPageSize: 10,
|
||||
TimeZone: nodeconfigs.DefaultTimeZoneLocation,
|
||||
}
|
||||
}
|
||||
|
||||
// 修改时区
|
||||
func updateTimeZone(config *systemconfigs.AdminUIConfig) {
|
||||
if len(config.TimeZone) > 0 {
|
||||
location, err := time.LoadLocation(config.TimeZone)
|
||||
if err == nil && time.Local != location {
|
||||
time.Local = location
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
func TestLoadUIConfig(t *testing.T) {
|
||||
for i := 0; i < 10; i++ {
|
||||
before := time.Now()
|
||||
config, err := LoadUIConfig()
|
||||
config, err := LoadAdminUIConfig()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -20,7 +20,7 @@ func TestLoadUIConfig(t *testing.T) {
|
||||
|
||||
func TestLoadUIConfig2(t *testing.T) {
|
||||
for i := 0; i < 10; i++ {
|
||||
config, err := LoadUIConfig()
|
||||
config, err := LoadAdminUIConfig()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ func LoadSecurityConfig() (*systemconfigs.SecurityConfig, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v := reflect.Indirect(reflect.ValueOf(config)).Interface().(systemconfigs.SecurityConfig)
|
||||
var v = reflect.Indirect(reflect.ValueOf(config)).Interface().(systemconfigs.SecurityConfig)
|
||||
return &v, nil
|
||||
}
|
||||
|
||||
@@ -79,15 +79,22 @@ func loadSecurityConfig() (*systemconfigs.SecurityConfig, error) {
|
||||
return nil, err
|
||||
}
|
||||
if len(resp.ValueJSON) == 0 {
|
||||
sharedSecurityConfig = defaultSecurityConfig()
|
||||
sharedSecurityConfig = NewSecurityConfig()
|
||||
return sharedSecurityConfig, nil
|
||||
}
|
||||
|
||||
config := &systemconfigs.SecurityConfig{}
|
||||
var config = &systemconfigs.SecurityConfig{
|
||||
Frame: FrameSameOrigin,
|
||||
AllowLocal: true,
|
||||
CheckClientFingerprint: false,
|
||||
CheckClientRegion: true,
|
||||
DenySearchEngines: true,
|
||||
DenySpiders: true,
|
||||
}
|
||||
err = json.Unmarshal(resp.ValueJSON, config)
|
||||
if err != nil {
|
||||
logs.Println("[SECURITY_MANAGER]" + err.Error())
|
||||
sharedSecurityConfig = defaultSecurityConfig()
|
||||
sharedSecurityConfig = NewSecurityConfig()
|
||||
return sharedSecurityConfig, nil
|
||||
}
|
||||
err = config.Init()
|
||||
@@ -98,9 +105,14 @@ func loadSecurityConfig() (*systemconfigs.SecurityConfig, error) {
|
||||
return sharedSecurityConfig, nil
|
||||
}
|
||||
|
||||
func defaultSecurityConfig() *systemconfigs.SecurityConfig {
|
||||
// NewSecurityConfig create new security config
|
||||
func NewSecurityConfig() *systemconfigs.SecurityConfig {
|
||||
return &systemconfigs.SecurityConfig{
|
||||
Frame: FrameSameOrigin,
|
||||
AllowLocal: true,
|
||||
Frame: FrameSameOrigin,
|
||||
AllowLocal: true,
|
||||
CheckClientFingerprint: false,
|
||||
CheckClientRegion: true,
|
||||
DenySearchEngines: true,
|
||||
DenySpiders: true,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
package configloaders
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
var sharedUserUIConfig *systemconfigs.UserUIConfig = nil
|
||||
|
||||
const (
|
||||
UserUISettingName = "userUIConfig"
|
||||
)
|
||||
|
||||
func LoadUserUIConfig() (*systemconfigs.UserUIConfig, error) {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
config, err := loadUserUIConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v := reflect.Indirect(reflect.ValueOf(config)).Interface().(systemconfigs.UserUIConfig)
|
||||
return &v, nil
|
||||
}
|
||||
|
||||
func UpdateUserUIConfig(uiConfig *systemconfigs.UserUIConfig) error {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
var rpcClient, err = rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
valueJSON, err := json.Marshal(uiConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = rpcClient.SysSettingRPC().UpdateSysSetting(rpcClient.Context(0), &pb.UpdateSysSettingRequest{
|
||||
Code: UserUISettingName,
|
||||
ValueJSON: valueJSON,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sharedUserUIConfig = uiConfig
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadUserUIConfig() (*systemconfigs.UserUIConfig, error) {
|
||||
if sharedUserUIConfig != nil {
|
||||
return sharedUserUIConfig, nil
|
||||
}
|
||||
var rpcClient, err = rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := rpcClient.SysSettingRPC().ReadSysSetting(rpcClient.Context(0), &pb.ReadSysSettingRequest{
|
||||
Code: UserUISettingName,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(resp.ValueJSON) == 0 {
|
||||
sharedUserUIConfig = defaultUserUIConfig()
|
||||
return sharedUserUIConfig, nil
|
||||
}
|
||||
|
||||
config := &systemconfigs.UserUIConfig{}
|
||||
err = json.Unmarshal(resp.ValueJSON, config)
|
||||
if err != nil {
|
||||
logs.Println("[UI_MANAGER]" + err.Error())
|
||||
sharedUserUIConfig = defaultUserUIConfig()
|
||||
return sharedUserUIConfig, nil
|
||||
}
|
||||
sharedUserUIConfig = config
|
||||
return sharedUserUIConfig, nil
|
||||
}
|
||||
|
||||
func defaultUserUIConfig() *systemconfigs.UserUIConfig {
|
||||
return &systemconfigs.UserUIConfig{
|
||||
ProductName: "GoEdge",
|
||||
UserSystemName: "GoEdge用户系统",
|
||||
ShowOpenSourceInfo: true,
|
||||
ShowVersion: true,
|
||||
ShowFinance: true,
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,27 @@
|
||||
package configs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/go-yaml/yaml"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"io/ioutil"
|
||||
"gopkg.in/yaml.v3"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const ConfigFileName = "api_admin.yaml"
|
||||
const oldConfigFileName = "api.yaml"
|
||||
|
||||
// APIConfig API配置
|
||||
type APIConfig struct {
|
||||
RPC struct {
|
||||
Endpoints []string `yaml:"endpoints"`
|
||||
} `yaml:"rpc"`
|
||||
OldRPC struct {
|
||||
Endpoints []string `yaml:"endpoints"`
|
||||
DisableUpdate bool `yaml:"disableUpdate"`
|
||||
} `yaml:"rpc,omitempty"`
|
||||
|
||||
RPCEndpoints []string `yaml:"rpc.endpoints,flow" json:"rpc.endpoints"`
|
||||
RPCDisableUpdate bool `yaml:"rpc.disableUpdate" json:"rpc.disableUpdate"`
|
||||
|
||||
NodeId string `yaml:"nodeId"`
|
||||
Secret string `yaml:"secret"`
|
||||
}
|
||||
@@ -21,23 +29,29 @@ type APIConfig struct {
|
||||
// LoadAPIConfig 加载API配置
|
||||
func LoadAPIConfig() (*APIConfig, error) {
|
||||
// 候选文件
|
||||
localFile := Tea.ConfigFile("api.yaml")
|
||||
isFromLocal := false
|
||||
paths := []string{localFile}
|
||||
var realFile = Tea.ConfigFile(ConfigFileName)
|
||||
var oldRealFile = Tea.ConfigFile(oldConfigFileName)
|
||||
var isFromLocal = false
|
||||
var paths = []string{realFile, oldRealFile}
|
||||
homeDir, homeErr := os.UserHomeDir()
|
||||
if homeErr == nil {
|
||||
paths = append(paths, homeDir+"/."+teaconst.ProcessName+"/api.yaml")
|
||||
paths = append(paths, homeDir+"/."+teaconst.ProcessName+"/"+ConfigFileName)
|
||||
}
|
||||
paths = append(paths, "/etc/"+teaconst.ProcessName+"/api.yaml")
|
||||
paths = append(paths, "/etc/"+teaconst.ProcessName+"/"+ConfigFileName)
|
||||
|
||||
var data []byte
|
||||
var err error
|
||||
var isFromOld = false
|
||||
for _, path := range paths {
|
||||
data, err = ioutil.ReadFile(path)
|
||||
data, err = os.ReadFile(path)
|
||||
if err == nil {
|
||||
if path == localFile {
|
||||
if path == realFile || path == oldRealFile {
|
||||
isFromLocal = true
|
||||
}
|
||||
|
||||
// 自动生成新的配置文件
|
||||
isFromOld = path == oldRealFile
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -45,15 +59,26 @@ func LoadAPIConfig() (*APIConfig, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config := &APIConfig{}
|
||||
var config = &APIConfig{}
|
||||
err = yaml.Unmarshal(data, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = config.Init()
|
||||
if err != nil {
|
||||
return nil, errors.New("init error: " + err.Error())
|
||||
}
|
||||
|
||||
if !isFromLocal {
|
||||
// 恢复文件
|
||||
_ = ioutil.WriteFile(localFile, data, 0666)
|
||||
_ = os.WriteFile(realFile, data, 0666)
|
||||
}
|
||||
|
||||
// 自动生成新配置文件
|
||||
if isFromOld {
|
||||
config.OldRPC.Endpoints = nil
|
||||
_ = config.WriteFile(Tea.ConfigFile(ConfigFileName))
|
||||
}
|
||||
|
||||
return config, nil
|
||||
@@ -61,10 +86,11 @@ func LoadAPIConfig() (*APIConfig, error) {
|
||||
|
||||
// ResetAPIConfig 重置配置
|
||||
func ResetAPIConfig() error {
|
||||
filename := "api.yaml"
|
||||
var filename = ConfigFileName
|
||||
|
||||
// 重置 configs/api_admin.yaml
|
||||
{
|
||||
configFile := Tea.ConfigFile(filename)
|
||||
var configFile = Tea.ConfigFile(filename)
|
||||
stat, err := os.Stat(configFile)
|
||||
if err == nil && !stat.IsDir() {
|
||||
err = os.Remove(configFile)
|
||||
@@ -74,10 +100,10 @@ func ResetAPIConfig() error {
|
||||
}
|
||||
}
|
||||
|
||||
// 重置 ~/.edge-admin/api.yaml
|
||||
// 重置 ~/.edge-admin/api_admin.yaml
|
||||
homeDir, homeErr := os.UserHomeDir()
|
||||
if homeErr == nil {
|
||||
configFile := homeDir + "/." + teaconst.ProcessName + "/" + filename
|
||||
var configFile = homeDir + "/." + teaconst.ProcessName + "/" + filename
|
||||
stat, err := os.Stat(configFile)
|
||||
if err == nil && !stat.IsDir() {
|
||||
err = os.Remove(configFile)
|
||||
@@ -87,9 +113,9 @@ func ResetAPIConfig() error {
|
||||
}
|
||||
}
|
||||
|
||||
// 重置 /etc/edge-admin/api.yaml
|
||||
// 重置 /etc/edge-admin/api_admin.yaml
|
||||
{
|
||||
configFile := "/etc/" + teaconst.ProcessName + "/" + filename
|
||||
var configFile = "/etc/" + teaconst.ProcessName + "/" + filename
|
||||
stat, err := os.Stat(configFile)
|
||||
if err == nil && !stat.IsDir() {
|
||||
err = os.Remove(configFile)
|
||||
@@ -102,44 +128,99 @@ func ResetAPIConfig() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func IsNewInstalled() bool {
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, filename := range []string{ConfigFileName, oldConfigFileName} {
|
||||
_, err = os.Stat(homeDir + "/." + teaconst.ProcessName + "/" + filename)
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// WriteFile 写入API配置
|
||||
func (this *APIConfig) WriteFile(path string) error {
|
||||
data, err := yaml.Marshal(this)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ioutil.WriteFile(path, data, 0666)
|
||||
|
||||
err = os.WriteFile(path, data, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 写入 ~/ 和 /etc/ 目录,因为是备份需要,所以不需要提示错误信息
|
||||
// 写入 ~/.edge-admin/
|
||||
filename := filepath.Base(path)
|
||||
// 这个用来判断用户是否为重装,所以比较重要
|
||||
var filename = filepath.Base(path)
|
||||
homeDir, homeErr := os.UserHomeDir()
|
||||
if homeErr == nil {
|
||||
dir := homeDir + "/." + teaconst.ProcessName
|
||||
stat, err := os.Stat(dir)
|
||||
if err == nil && stat.IsDir() {
|
||||
_ = ioutil.WriteFile(dir+"/"+filename, data, 0666)
|
||||
err = os.WriteFile(dir+"/"+filename, data, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err != nil && os.IsNotExist(err) {
|
||||
err = os.Mkdir(dir, 0777)
|
||||
if err == nil {
|
||||
_ = ioutil.WriteFile(dir+"/"+filename, data, 0666)
|
||||
err = os.WriteFile(dir+"/"+filename, data, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 写入 /etc/edge-admin
|
||||
{
|
||||
dir := "/etc/" + teaconst.ProcessName
|
||||
var dir = "/etc/" + teaconst.ProcessName
|
||||
stat, err := os.Stat(dir)
|
||||
if err == nil && stat.IsDir() {
|
||||
_ = ioutil.WriteFile(dir+"/"+filename, data, 0666)
|
||||
_ = os.WriteFile(dir+"/"+filename, data, 0666)
|
||||
} else if err != nil && os.IsNotExist(err) {
|
||||
err = os.Mkdir(dir, 0777)
|
||||
if err == nil {
|
||||
_ = ioutil.WriteFile(dir+"/"+filename, data, 0666)
|
||||
_ = os.WriteFile(dir+"/"+filename, data, 0666)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clone 克隆当前配置
|
||||
func (this *APIConfig) Clone() *APIConfig {
|
||||
return &APIConfig{
|
||||
NodeId: this.NodeId,
|
||||
Secret: this.Secret,
|
||||
}
|
||||
}
|
||||
|
||||
func (this *APIConfig) Init() error {
|
||||
// compatible with old
|
||||
if len(this.RPCEndpoints) == 0 && len(this.OldRPC.Endpoints) > 0 {
|
||||
this.RPCEndpoints = this.OldRPC.Endpoints
|
||||
this.RPCDisableUpdate = this.OldRPC.DisableUpdate
|
||||
}
|
||||
|
||||
if len(this.RPCEndpoints) == 0 {
|
||||
return errors.New("no valid 'rpc.endpoints'")
|
||||
}
|
||||
|
||||
if len(this.NodeId) == 0 {
|
||||
return errors.New("'nodeId' required")
|
||||
}
|
||||
if len(this.Secret) == 0 {
|
||||
return errors.New("'secret' required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package configs
|
||||
|
||||
import (
|
||||
_ "github.com/iwind/TeaGo/bootstrap"
|
||||
"gopkg.in/yaml.v3"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -11,16 +12,16 @@ func TestLoadAPIConfig(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(config)
|
||||
|
||||
configData, err := yaml.Marshal(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(string(configData))
|
||||
}
|
||||
|
||||
func TestAPIConfig_WriteFile(t *testing.T) {
|
||||
config := &APIConfig{
|
||||
RPC: struct {
|
||||
Endpoints []string `yaml:"endpoints"`
|
||||
}{},
|
||||
NodeId: "1",
|
||||
Secret: "2",
|
||||
}
|
||||
var config = &APIConfig{}
|
||||
err := config.WriteFile("/tmp/api_config.yaml")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
8
internal/const/build.go
Normal file
8
internal/const/build.go
Normal file
@@ -0,0 +1,8 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
//go:build !plus
|
||||
|
||||
package teaconst
|
||||
|
||||
const BuildCommunity = true
|
||||
const BuildPlus = false
|
||||
const Tag = "community"
|
||||
@@ -1,7 +1,9 @@
|
||||
package teaconst
|
||||
|
||||
const (
|
||||
Version = "0.1.0"
|
||||
Version = "1.3.6"
|
||||
|
||||
APINodeVersion = "1.3.6"
|
||||
|
||||
ProductName = "Edge Admin"
|
||||
ProcessName = "edge-admin"
|
||||
@@ -12,8 +14,9 @@ const (
|
||||
EncryptKey = "8f983f4d69b83aaa0d74b21a212f6967"
|
||||
EncryptMethod = "aes-256-cfb"
|
||||
|
||||
ErrServer = "服务器出了点小问题,请联系技术人员处理。"
|
||||
CookieSID = "edgesid"
|
||||
CookieSID = "geadsid"
|
||||
SessionAdminId = "adminId"
|
||||
|
||||
SystemdServiceName = "edge-admin"
|
||||
UpdatesURL = "https://goedge.cn/api/boot/versions?os=${os}&arch=${arch}&version=${version}"
|
||||
)
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
// +build demo
|
||||
|
||||
package teaconst
|
||||
|
||||
const (
|
||||
IsDemo = true
|
||||
)
|
||||
@@ -1,7 +0,0 @@
|
||||
// +build !demo
|
||||
|
||||
package teaconst
|
||||
|
||||
const (
|
||||
IsDemo = false
|
||||
)
|
||||
@@ -1,6 +1,6 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
//go:build !plus
|
||||
|
||||
package teaconst
|
||||
|
||||
// IsPlus 是否为企业版
|
||||
var IsPlus = false
|
||||
|
||||
32
internal/const/vars.go
Normal file
32
internal/const/vars.go
Normal file
@@ -0,0 +1,32 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package teaconst
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
IsRecoverMode = false
|
||||
|
||||
IsDemoMode = false
|
||||
ErrorDemoOperation = "DEMO模式下无法进行创建、修改、删除等操作"
|
||||
|
||||
NewVersionCode = "" // 有新的版本
|
||||
NewVersionDownloadURL = "" // 新版本下载地址
|
||||
|
||||
IsMain = checkMain()
|
||||
)
|
||||
|
||||
// 检查是否为主程序
|
||||
func checkMain() bool {
|
||||
if len(os.Args) == 1 ||
|
||||
(len(os.Args) >= 2 && os.Args[1] == "pprof") {
|
||||
return true
|
||||
}
|
||||
exe, _ := os.Executable()
|
||||
return strings.HasSuffix(exe, ".test") ||
|
||||
strings.HasSuffix(exe, ".test.exe") ||
|
||||
strings.Contains(exe, "___")
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// 生成Token
|
||||
// Generate 生成Token
|
||||
func Generate() string {
|
||||
timestamp := strconv.FormatInt(time.Now().Unix(), 10)
|
||||
|
||||
@@ -23,7 +23,7 @@ func Generate() string {
|
||||
return token
|
||||
}
|
||||
|
||||
// 校验Token
|
||||
// Validate 校验Token
|
||||
func Validate(token string) (b bool) {
|
||||
if len(token) == 0 {
|
||||
return
|
||||
|
||||
@@ -19,7 +19,6 @@ func TestAES128CFBMethod_Encrypt(t *testing.T) {
|
||||
dst = dst[:len(src)]
|
||||
t.Log("dst:", string(dst))
|
||||
|
||||
src = make([]byte, len(src))
|
||||
src, err = method.Decrypt(dst)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -64,7 +63,6 @@ func TestAES128CFBMethod_Encrypt2(t *testing.T) {
|
||||
|
||||
for _, dst := range sources {
|
||||
dst2 := append([]byte{}, dst...)
|
||||
src2 := make([]byte, len(dst2))
|
||||
src2, err := method.Decrypt(dst2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -5,22 +5,22 @@ import "sync"
|
||||
var eventsMap = map[string][]func(){} // event => []callbacks
|
||||
var locker = sync.Mutex{}
|
||||
|
||||
// 增加事件回调
|
||||
// On 增加事件回调
|
||||
func On(event string, callback func()) {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
callbacks, _ := eventsMap[event]
|
||||
var callbacks = eventsMap[event]
|
||||
callbacks = append(callbacks, callback)
|
||||
eventsMap[event] = callbacks
|
||||
}
|
||||
|
||||
// 通知事件
|
||||
// Notify 通知事件
|
||||
func Notify(event string) {
|
||||
locker.Lock()
|
||||
callbacks, _ := eventsMap[event]
|
||||
var callbacks = eventsMap[event]
|
||||
locker.Unlock()
|
||||
|
||||
|
||||
for _, callback := range callbacks {
|
||||
callback()
|
||||
}
|
||||
|
||||
169
internal/gen/generate.go
Normal file
169
internal/gen/generate.go
Normal file
@@ -0,0 +1,169 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package gen
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/settings/conds/condutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/files"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func Generate() error {
|
||||
err := generateComponentsJSFile()
|
||||
if err != nil {
|
||||
return fmt.Errorf("generate 'components.src.js' failed: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 生成Javascript文件
|
||||
func generateComponentsJSFile() error {
|
||||
var buffer = bytes.NewBuffer([]byte{})
|
||||
|
||||
var webRoot string
|
||||
if Tea.IsTesting() {
|
||||
webRoot = Tea.Root + "/../web/public/js/components/"
|
||||
} else {
|
||||
webRoot = Tea.Root + "/web/public/js/components/"
|
||||
}
|
||||
f := files.NewFile(webRoot)
|
||||
|
||||
f.Range(func(file *files.File) {
|
||||
if !file.IsFile() {
|
||||
return
|
||||
}
|
||||
if file.Ext() != ".js" {
|
||||
return
|
||||
}
|
||||
data, err := file.ReadAll()
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
return
|
||||
}
|
||||
buffer.Write(data)
|
||||
buffer.Write([]byte{'\n', '\n'})
|
||||
})
|
||||
|
||||
// 条件组件
|
||||
typesJSON, err := json.Marshal(condutils.ReadAllAvailableCondTypes())
|
||||
if err != nil {
|
||||
logs.Println("ComponentsAction marshal request cond types failed: " + err.Error())
|
||||
} else {
|
||||
buffer.WriteString("window.REQUEST_COND_COMPONENTS = ")
|
||||
buffer.Write(typesJSON)
|
||||
buffer.Write([]byte{';', '\n', '\n'})
|
||||
}
|
||||
|
||||
// 条件操作符
|
||||
requestOperatorsJSON, err := json.Marshal(shared.AllRequestOperators())
|
||||
if err != nil {
|
||||
logs.Println("ComponentsAction marshal request operators failed: " + err.Error())
|
||||
} else {
|
||||
buffer.WriteString("window.REQUEST_COND_OPERATORS = ")
|
||||
buffer.Write(requestOperatorsJSON)
|
||||
buffer.Write([]byte{';', '\n', '\n'})
|
||||
}
|
||||
|
||||
// 请求变量
|
||||
requestVariablesJSON, err := json.Marshal(shared.DefaultRequestVariables())
|
||||
if err != nil {
|
||||
logs.Println("ComponentsAction marshal request variables failed: " + err.Error())
|
||||
} else {
|
||||
buffer.WriteString("window.REQUEST_VARIABLES = ")
|
||||
buffer.Write(requestVariablesJSON)
|
||||
buffer.Write([]byte{';', '\n', '\n'})
|
||||
}
|
||||
|
||||
// 指标
|
||||
metricHTTPKeysJSON, err := json.Marshal(serverconfigs.FindAllMetricKeyDefinitions(serverconfigs.MetricItemCategoryHTTP))
|
||||
if err != nil {
|
||||
logs.Println("ComponentsAction marshal metric http keys failed: " + err.Error())
|
||||
} else {
|
||||
buffer.WriteString("window.METRIC_HTTP_KEYS = ")
|
||||
buffer.Write(metricHTTPKeysJSON)
|
||||
buffer.Write([]byte{';', '\n', '\n'})
|
||||
}
|
||||
|
||||
// IP地址阈值项目
|
||||
ipAddrThresholdItemsJSON, err := json.Marshal(nodeconfigs.FindAllIPAddressThresholdItems())
|
||||
if err != nil {
|
||||
logs.Println("ComponentsAction marshal ip addr threshold items failed: " + err.Error())
|
||||
} else {
|
||||
buffer.WriteString("window.IP_ADDR_THRESHOLD_ITEMS = ")
|
||||
buffer.Write(ipAddrThresholdItemsJSON)
|
||||
buffer.Write([]byte{';', '\n', '\n'})
|
||||
}
|
||||
|
||||
// IP地址阈值动作
|
||||
ipAddrThresholdActionsJSON, err := json.Marshal(nodeconfigs.FindAllIPAddressThresholdActions())
|
||||
if err != nil {
|
||||
logs.Println("ComponentsAction marshal ip addr threshold actions failed: " + err.Error())
|
||||
} else {
|
||||
buffer.WriteString("window.IP_ADDR_THRESHOLD_ACTIONS = ")
|
||||
buffer.Write(ipAddrThresholdActionsJSON)
|
||||
buffer.Write([]byte{';', '\n', '\n'})
|
||||
}
|
||||
|
||||
// WAF checkpoints
|
||||
var wafCheckpointsMaps = []maps.Map{}
|
||||
for _, checkpoint := range firewallconfigs.AllCheckpoints {
|
||||
wafCheckpointsMaps = append(wafCheckpointsMaps, maps.Map{
|
||||
"name": checkpoint.Name,
|
||||
"prefix": checkpoint.Prefix,
|
||||
"description": checkpoint.Description,
|
||||
})
|
||||
}
|
||||
wafCheckpointsJSON, err := json.Marshal(wafCheckpointsMaps)
|
||||
if err != nil {
|
||||
logs.Println("ComponentsAction marshal waf rule checkpoints failed: " + err.Error())
|
||||
} else {
|
||||
buffer.WriteString("window.WAF_RULE_CHECKPOINTS = ")
|
||||
buffer.Write(wafCheckpointsJSON)
|
||||
buffer.Write([]byte{';', '\n', '\n'})
|
||||
}
|
||||
|
||||
// WAF操作符
|
||||
wafOperatorsJSON, err := json.Marshal(firewallconfigs.AllRuleOperators)
|
||||
if err != nil {
|
||||
logs.Println("ComponentsAction marshal waf rule operators failed: " + err.Error())
|
||||
} else {
|
||||
buffer.WriteString("window.WAF_RULE_OPERATORS = ")
|
||||
buffer.Write(wafOperatorsJSON)
|
||||
buffer.Write([]byte{';', '\n', '\n'})
|
||||
}
|
||||
|
||||
// WAF验证码类型
|
||||
captchaTypesJSON, err := json.Marshal(firewallconfigs.FindAllCaptchaTypes())
|
||||
if err != nil {
|
||||
logs.Println("ComponentsAction marshal captcha types failed: " + err.Error())
|
||||
} else {
|
||||
buffer.WriteString("window.WAF_CAPTCHA_TYPES = ")
|
||||
buffer.Write(captchaTypesJSON)
|
||||
buffer.Write([]byte{';', '\n', '\n'})
|
||||
}
|
||||
|
||||
fp, err := os.OpenFile(filepath.Clean(Tea.PublicFile("/js/components.src.js")), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0777)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = io.Copy(fp, buffer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
13
internal/gen/generate_test.go
Normal file
13
internal/gen/generate_test.go
Normal file
@@ -0,0 +1,13 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package gen
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestGenerate(t *testing.T) {
|
||||
err := Generate()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("ok")
|
||||
}
|
||||
12
internal/goman/instance.go
Normal file
12
internal/goman/instance.go
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package goman
|
||||
|
||||
import "time"
|
||||
|
||||
type Instance struct {
|
||||
Id uint64
|
||||
CreatedTime time.Time
|
||||
File string
|
||||
Line int
|
||||
}
|
||||
81
internal/goman/lib.go
Normal file
81
internal/goman/lib.go
Normal file
@@ -0,0 +1,81 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package goman
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var locker = &sync.Mutex{}
|
||||
var instanceMap = map[uint64]*Instance{} // id => *Instance
|
||||
var instanceId = uint64(0)
|
||||
|
||||
// New 新创建goroutine
|
||||
func New(f func()) {
|
||||
_, file, line, _ := runtime.Caller(1)
|
||||
|
||||
go func() {
|
||||
locker.Lock()
|
||||
instanceId++
|
||||
|
||||
var instance = &Instance{
|
||||
Id: instanceId,
|
||||
CreatedTime: time.Now(),
|
||||
}
|
||||
|
||||
instance.File = file
|
||||
instance.Line = line
|
||||
|
||||
instanceMap[instanceId] = instance
|
||||
locker.Unlock()
|
||||
|
||||
// run function
|
||||
f()
|
||||
|
||||
locker.Lock()
|
||||
delete(instanceMap, instanceId)
|
||||
locker.Unlock()
|
||||
}()
|
||||
}
|
||||
|
||||
// NewWithArgs 创建带有参数的goroutine
|
||||
func NewWithArgs(f func(args ...interface{}), args ...interface{}) {
|
||||
_, file, line, _ := runtime.Caller(1)
|
||||
|
||||
go func() {
|
||||
locker.Lock()
|
||||
instanceId++
|
||||
|
||||
var instance = &Instance{
|
||||
Id: instanceId,
|
||||
CreatedTime: time.Now(),
|
||||
}
|
||||
|
||||
instance.File = file
|
||||
instance.Line = line
|
||||
|
||||
instanceMap[instanceId] = instance
|
||||
locker.Unlock()
|
||||
|
||||
// run function
|
||||
f(args...)
|
||||
|
||||
locker.Lock()
|
||||
delete(instanceMap, instanceId)
|
||||
locker.Unlock()
|
||||
}()
|
||||
}
|
||||
|
||||
// List 列出所有正在运行goroutine
|
||||
func List() []*Instance {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
var result = []*Instance{}
|
||||
for _, instance := range instanceMap {
|
||||
result = append(result, instance)
|
||||
}
|
||||
return result
|
||||
}
|
||||
28
internal/goman/lib_test.go
Normal file
28
internal/goman/lib_test.go
Normal file
@@ -0,0 +1,28 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package goman
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
New(func() {
|
||||
t.Log("Hello")
|
||||
|
||||
t.Log(List())
|
||||
})
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
t.Log(List())
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
func TestNewWithArgs(t *testing.T) {
|
||||
NewWithArgs(func(args ...interface{}) {
|
||||
t.Log(args[0], args[1])
|
||||
}, 1, 2)
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
@@ -1,23 +1,29 @@
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/errors"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/events"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
|
||||
"github.com/iwind/TeaGo"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
"github.com/iwind/TeaGo/sessions"
|
||||
"io/ioutil"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"github.com/iwind/gosock/pkg/gosock"
|
||||
"gopkg.in/yaml.v3"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
@@ -25,6 +31,7 @@ import (
|
||||
var SharedAdminNode *AdminNode = nil
|
||||
|
||||
type AdminNode struct {
|
||||
sock *gosock.Sock
|
||||
subPIDs []int
|
||||
}
|
||||
|
||||
@@ -36,13 +43,13 @@ func (this *AdminNode) Run() {
|
||||
SharedAdminNode = this
|
||||
|
||||
// 启动管理界面
|
||||
secret := this.genSecret()
|
||||
var secret = this.genSecret()
|
||||
configs.Secret = secret
|
||||
|
||||
// 本地Sock
|
||||
err := this.listenSock()
|
||||
if err != nil {
|
||||
logs.Println("NODE" + err.Error())
|
||||
logs.Println("[NODE]", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -56,8 +63,11 @@ func (this *AdminNode) Run() {
|
||||
return
|
||||
}
|
||||
|
||||
// 添加端口到防火墙
|
||||
this.addPortsToFirewall()
|
||||
|
||||
// 监听信号
|
||||
sigQueue := make(chan os.Signal)
|
||||
var sigQueue = make(chan os.Signal, 8)
|
||||
signal.Notify(sigQueue, syscall.SIGTERM, syscall.SIGINT, syscall.SIGKILL, syscall.SIGQUIT)
|
||||
go func() {
|
||||
for range sigQueue {
|
||||
@@ -77,23 +87,30 @@ func (this *AdminNode) Run() {
|
||||
// 启动API节点
|
||||
this.startAPINode()
|
||||
|
||||
// 启动IP库
|
||||
this.startIPLibrary()
|
||||
|
||||
// 启动Web服务
|
||||
sessionManager, err := NewSessionManager()
|
||||
if err != nil {
|
||||
log.Fatal("start session failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
TeaGo.NewServer(false).
|
||||
AccessLog(false).
|
||||
EndAll().
|
||||
Session(sessions.NewFileSessionManager(86400, secret), teaconst.CookieSID).
|
||||
Session(sessionManager, teaconst.CookieSID).
|
||||
ReadHeaderTimeout(3 * time.Second).
|
||||
ReadTimeout(600 * time.Second).
|
||||
ReadTimeout(1200 * time.Second).
|
||||
Start()
|
||||
}
|
||||
|
||||
// Daemon 实现守护进程
|
||||
func (this *AdminNode) Daemon() {
|
||||
path := os.TempDir() + "/edge-admin.sock"
|
||||
isDebug := lists.ContainsString(os.Args, "debug")
|
||||
isDebug = true
|
||||
var sock = gosock.NewTmpSock(teaconst.ProcessName)
|
||||
var isDebug = lists.ContainsString(os.Args, "debug")
|
||||
for {
|
||||
conn, err := net.DialTimeout("unix", path, 1*time.Second)
|
||||
conn, err := sock.Dial()
|
||||
if err != nil {
|
||||
if isDebug {
|
||||
log.Println("[DAEMON]starting ...")
|
||||
@@ -165,11 +182,11 @@ func (this *AdminNode) checkServer() error {
|
||||
if os.IsNotExist(err) {
|
||||
// 创建文件
|
||||
templateFile := Tea.ConfigFile("server.template.yaml")
|
||||
data, err := ioutil.ReadFile(templateFile)
|
||||
data, err := os.ReadFile(templateFile)
|
||||
if err == nil {
|
||||
err = ioutil.WriteFile(configFile, data, 0666)
|
||||
err = os.WriteFile(configFile, data, 0666)
|
||||
if err != nil {
|
||||
return errors.New("create config file failed: " + err.Error())
|
||||
return fmt.Errorf("create config file failed: %w", err)
|
||||
}
|
||||
} else {
|
||||
templateYAML := `# environment code
|
||||
@@ -187,21 +204,59 @@ https:
|
||||
cert: ""
|
||||
key: ""
|
||||
`
|
||||
err = ioutil.WriteFile(configFile, []byte(templateYAML), 0666)
|
||||
err = os.WriteFile(configFile, []byte(templateYAML), 0666)
|
||||
if err != nil {
|
||||
return errors.New("create config file failed: " + err.Error())
|
||||
return fmt.Errorf("create config file failed: %w", err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return errors.New("can not read config from 'configs/server.yaml': " + err.Error())
|
||||
return fmt.Errorf("can not read config from 'configs/server.yaml': %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 添加端口到防火墙
|
||||
func (this *AdminNode) addPortsToFirewall() {
|
||||
var configFile = Tea.ConfigFile("server.yaml")
|
||||
data, err := os.ReadFile(configFile)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var config = &TeaGo.ServerConfig{}
|
||||
err = yaml.Unmarshal(data, config)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var ports = []int{}
|
||||
if config.Http.On {
|
||||
for _, listen := range config.Http.Listen {
|
||||
_, portString, _ := net.SplitHostPort(listen)
|
||||
var port = types.Int(portString)
|
||||
if port > 0 && !lists.ContainsInt(ports, port) {
|
||||
ports = append(ports, port)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if config.Https.On {
|
||||
for _, listen := range config.Https.Listen {
|
||||
_, portString, _ := net.SplitHostPort(listen)
|
||||
var port = types.Int(portString)
|
||||
if port > 0 && !lists.ContainsInt(ports, port) {
|
||||
ports = append(ports, port)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
utils.AddPortsToFirewall(ports)
|
||||
}
|
||||
|
||||
// 启动API节点
|
||||
func (this *AdminNode) startAPINode() {
|
||||
configPath := Tea.Root + "/edge-api/configs/api.yaml"
|
||||
var configPath = Tea.Root + "/edge-api/configs/api.yaml"
|
||||
_, err := os.Stat(configPath)
|
||||
canStart := false
|
||||
if err == nil {
|
||||
@@ -217,9 +272,9 @@ func (this *AdminNode) startAPINode() {
|
||||
for _, path := range paths {
|
||||
_, err = os.Stat(path)
|
||||
if err == nil {
|
||||
data, err := ioutil.ReadFile(path)
|
||||
data, err := os.ReadFile(path)
|
||||
if err == nil {
|
||||
err = ioutil.WriteFile(configPath, data, 0666)
|
||||
err = os.WriteFile(configPath, data, 0666)
|
||||
if err == nil {
|
||||
logs.Println("[NODE]recover 'edge-api/configs/api.yaml' from '" + path + "'")
|
||||
canStart = true
|
||||
@@ -243,9 +298,9 @@ func (this *AdminNode) startAPINode() {
|
||||
for _, path := range paths {
|
||||
_, err = os.Stat(path)
|
||||
if err == nil {
|
||||
data, err := ioutil.ReadFile(path)
|
||||
data, err := os.ReadFile(path)
|
||||
if err == nil {
|
||||
err = ioutil.WriteFile(dbPath, data, 0666)
|
||||
err = os.WriteFile(dbPath, data, 0666)
|
||||
if err == nil {
|
||||
logs.Println("[NODE]recover 'edge-api/configs/db.yaml' from '" + path + "'")
|
||||
break
|
||||
@@ -270,48 +325,105 @@ func (this *AdminNode) startAPINode() {
|
||||
// 生成Secret
|
||||
func (this *AdminNode) genSecret() string {
|
||||
tmpFile := os.TempDir() + "/edge-admin-secret.tmp"
|
||||
data, err := ioutil.ReadFile(tmpFile)
|
||||
data, err := os.ReadFile(tmpFile)
|
||||
if err == nil && len(data) == 32 {
|
||||
return string(data)
|
||||
}
|
||||
secret := rands.String(32)
|
||||
_ = ioutil.WriteFile(tmpFile, []byte(secret), 0666)
|
||||
_ = os.WriteFile(tmpFile, []byte(secret), 0666)
|
||||
return secret
|
||||
}
|
||||
|
||||
// 监听本地sock
|
||||
func (this *AdminNode) listenSock() error {
|
||||
path := os.TempDir() + "/edge-admin.sock"
|
||||
this.sock = gosock.NewTmpSock(teaconst.ProcessName)
|
||||
|
||||
// 检查是否已经存在
|
||||
_, err := os.Stat(path)
|
||||
if err == nil {
|
||||
conn, err := net.Dial("unix", path)
|
||||
if err != nil {
|
||||
_ = os.Remove(path)
|
||||
// 检查是否在运行
|
||||
if this.sock.IsListening() {
|
||||
reply, err := this.sock.Send(&gosock.Command{Code: "pid"})
|
||||
if err == nil {
|
||||
return errors.New("error: the process is already running, pid: " + maps.NewMap(reply.Params).GetString("pid"))
|
||||
} else {
|
||||
_ = conn.Close()
|
||||
return errors.New("error: the process is already running")
|
||||
}
|
||||
}
|
||||
|
||||
// 新的监听任务
|
||||
listener, err := net.Listen("unix", path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
events.On(events.EventQuit, func() {
|
||||
logs.Println("NODE", "quit unix sock")
|
||||
_ = listener.Close()
|
||||
})
|
||||
|
||||
// 启动监听
|
||||
go func() {
|
||||
for {
|
||||
_, err := listener.Accept()
|
||||
if err != nil {
|
||||
return
|
||||
this.sock.OnCommand(func(cmd *gosock.Command) {
|
||||
switch cmd.Code {
|
||||
case "pid":
|
||||
_ = cmd.Reply(&gosock.Command{
|
||||
Code: "pid",
|
||||
Params: map[string]interface{}{
|
||||
"pid": os.Getpid(),
|
||||
},
|
||||
})
|
||||
case "stop":
|
||||
_ = cmd.ReplyOk()
|
||||
|
||||
// 关闭子进程
|
||||
for _, pid := range this.subPIDs {
|
||||
p, err := os.FindProcess(pid)
|
||||
if err == nil && p != nil {
|
||||
_ = p.Kill()
|
||||
}
|
||||
}
|
||||
|
||||
// 停止当前目录下的API节点
|
||||
var apiSock = gosock.NewTmpSock("edge-api")
|
||||
apiReply, err := apiSock.Send(&gosock.Command{Code: "info"})
|
||||
if err == nil {
|
||||
adminExe, _ := os.Executable()
|
||||
if len(adminExe) > 0 && apiReply != nil && strings.HasPrefix(maps.NewMap(apiReply.Params).GetString("path"), filepath.Dir(filepath.Dir(adminExe))) {
|
||||
_, _ = apiSock.Send(&gosock.Command{Code: "stop"})
|
||||
}
|
||||
}
|
||||
|
||||
// 退出主进程
|
||||
events.Notify(events.EventQuit)
|
||||
os.Exit(0)
|
||||
case "recover":
|
||||
teaconst.IsRecoverMode = true
|
||||
_ = cmd.ReplyOk()
|
||||
case "demo":
|
||||
teaconst.IsDemoMode = !teaconst.IsDemoMode
|
||||
_ = cmd.Reply(&gosock.Command{
|
||||
Params: map[string]interface{}{"isDemo": teaconst.IsDemoMode},
|
||||
})
|
||||
case "info":
|
||||
exePath, _ := os.Executable()
|
||||
_ = cmd.Reply(&gosock.Command{
|
||||
Code: "info",
|
||||
Params: map[string]interface{}{
|
||||
"pid": os.Getpid(),
|
||||
"version": teaconst.Version,
|
||||
"path": exePath,
|
||||
},
|
||||
})
|
||||
case "dev": // 切换到dev
|
||||
Tea.Env = Tea.EnvDev
|
||||
_ = cmd.ReplyOk()
|
||||
case "prod": // 切换到prod
|
||||
Tea.Env = Tea.EnvProd
|
||||
_ = cmd.ReplyOk()
|
||||
case "security.reset":
|
||||
var newConfig = configloaders.NewSecurityConfig()
|
||||
_ = configloaders.UpdateSecurityConfig(newConfig)
|
||||
_ = cmd.ReplyOk()
|
||||
}
|
||||
})
|
||||
|
||||
err := this.sock.Listen()
|
||||
if err != nil {
|
||||
logs.Println("NODE", err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
events.On(events.EventQuit, func() {
|
||||
logs.Println("NODE", "quit unix sock")
|
||||
_ = this.sock.Close()
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
18
internal/nodes/admin_node_ext.go
Normal file
18
internal/nodes/admin_node_ext.go
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
//go:build !plus
|
||||
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
)
|
||||
|
||||
// 启动IP库
|
||||
func (this *AdminNode) startIPLibrary() {
|
||||
logs.Println("[NODE]initializing ip library ...")
|
||||
err := iplibrary.InitDefault()
|
||||
if err != nil {
|
||||
logs.Println("[NODE]initialize ip library failed: "+err.Error())
|
||||
}
|
||||
}
|
||||
118
internal/nodes/session_manager.go
Normal file
118
internal/nodes/session_manager.go
Normal file
@@ -0,0 +1,118 @@
|
||||
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/ttlcache"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SessionManager SESSION管理
|
||||
type SessionManager struct {
|
||||
life uint
|
||||
}
|
||||
|
||||
func NewSessionManager() (*SessionManager, error) {
|
||||
return &SessionManager{}, nil
|
||||
}
|
||||
|
||||
func (this *SessionManager) Init(config *actions.SessionConfig) {
|
||||
this.life = config.Life
|
||||
}
|
||||
|
||||
func (this *SessionManager) Read(sid string) map[string]string {
|
||||
// 忽略OTP
|
||||
if strings.HasSuffix(sid, "_otp") {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
var result = map[string]string{}
|
||||
|
||||
var cacheKey = "SESSION@" + sid
|
||||
var item = ttlcache.DefaultCache.Read(cacheKey)
|
||||
if item != nil && item.Value != nil {
|
||||
itemMap, ok := item.Value.(map[string]string)
|
||||
if ok {
|
||||
return itemMap
|
||||
}
|
||||
}
|
||||
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
resp, err := rpcClient.LoginSessionRPC().FindLoginSession(rpcClient.Context(0), &pb.FindLoginSessionRequest{Sid: sid})
|
||||
if err != nil {
|
||||
logs.Println("SESSION", "read '"+sid+"' failed: "+err.Error())
|
||||
result["@error"] = err.Error()
|
||||
return result
|
||||
}
|
||||
|
||||
var session = resp.LoginSession
|
||||
if session == nil || len(session.ValuesJSON) == 0 {
|
||||
return result
|
||||
}
|
||||
|
||||
err = json.Unmarshal(session.ValuesJSON, &result)
|
||||
if err != nil {
|
||||
logs.Println("SESSION", "decode '"+sid+"' values failed: "+err.Error())
|
||||
}
|
||||
|
||||
// Write to cache
|
||||
ttlcache.DefaultCache.Write(cacheKey, result, time.Now().Unix()+300 /** must not be too long **/)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (this *SessionManager) WriteItem(sid string, key string, value string) bool {
|
||||
// 删除缓存
|
||||
defer ttlcache.DefaultCache.Delete( "SESSION@" + sid)
|
||||
|
||||
// 忽略OTP
|
||||
if strings.HasSuffix(sid, "_otp") {
|
||||
return false
|
||||
}
|
||||
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
_, err = rpcClient.LoginSessionRPC().WriteLoginSessionValue(rpcClient.Context(0), &pb.WriteLoginSessionValueRequest{
|
||||
Sid: sid,
|
||||
Key: key,
|
||||
Value: value,
|
||||
})
|
||||
if err != nil {
|
||||
logs.Println("SESSION", "write sid:'"+sid+"' key:'"+key+"' failed: "+err.Error())
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (this *SessionManager) Delete(sid string) bool {
|
||||
// 删除缓存
|
||||
defer ttlcache.DefaultCache.Delete( "SESSION@" + sid)
|
||||
|
||||
// 忽略OTP
|
||||
if strings.HasSuffix(sid, "_otp") {
|
||||
return false
|
||||
}
|
||||
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
_, err = rpcClient.LoginSessionRPC().DeleteLoginSession(rpcClient.Context(0), &pb.DeleteLoginSessionRequest{Sid: sid})
|
||||
if err != nil {
|
||||
logs.Println("SESSION", "delete '"+sid+"' failed: "+err.Error())
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package rpc
|
||||
|
||||
import "context"
|
||||
|
||||
type ContextInterface interface {
|
||||
AdminContext() context.Context
|
||||
}
|
||||
@@ -5,38 +5,45 @@ import (
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/encrypt"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/connectivity"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/grpc/encoding/gzip"
|
||||
"google.golang.org/grpc/keepalive"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// RPC客户端
|
||||
// RPCClient RPC客户端
|
||||
type RPCClient struct {
|
||||
apiConfig *configs.APIConfig
|
||||
conns []*grpc.ClientConn
|
||||
|
||||
locker sync.Mutex
|
||||
locker sync.RWMutex
|
||||
}
|
||||
|
||||
// 构造新的RPC客户端
|
||||
func NewRPCClient(apiConfig *configs.APIConfig) (*RPCClient, error) {
|
||||
// NewRPCClient 构造新的RPC客户端
|
||||
func NewRPCClient(apiConfig *configs.APIConfig, isPrimary bool) (*RPCClient, error) {
|
||||
if apiConfig == nil {
|
||||
return nil, errors.New("api config should not be nil")
|
||||
}
|
||||
|
||||
client := &RPCClient{
|
||||
var client = &RPCClient{
|
||||
apiConfig: apiConfig,
|
||||
}
|
||||
|
||||
@@ -46,11 +53,17 @@ func NewRPCClient(apiConfig *configs.APIConfig) (*RPCClient, error) {
|
||||
}
|
||||
|
||||
// 设置RPC
|
||||
dao.SetRPC(client)
|
||||
if isPrimary {
|
||||
dao.SetRPC(client)
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (this *RPCClient) APITokenRPC() pb.APITokenServiceClient {
|
||||
return pb.NewAPITokenServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) AdminRPC() pb.AdminServiceClient {
|
||||
return pb.NewAdminServiceClient(this.pickConn())
|
||||
}
|
||||
@@ -67,6 +80,10 @@ func (this *RPCClient) NodeGrantRPC() pb.NodeGrantServiceClient {
|
||||
return pb.NewNodeGrantServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NodeLoginRPC() pb.NodeLoginServiceClient {
|
||||
return pb.NewNodeLoginServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NodeClusterRPC() pb.NodeClusterServiceClient {
|
||||
return pb.NewNodeClusterServiceClient(this.pickConn())
|
||||
}
|
||||
@@ -83,14 +100,18 @@ func (this *RPCClient) NodeRegionRPC() pb.NodeRegionServiceClient {
|
||||
return pb.NewNodeRegionServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NodePriceItemRPC() pb.NodePriceItemServiceClient {
|
||||
return pb.NewNodePriceItemServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NodeIPAddressRPC() pb.NodeIPAddressServiceClient {
|
||||
return pb.NewNodeIPAddressServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NodeIPAddressLogRPC() pb.NodeIPAddressLogServiceClient {
|
||||
return pb.NewNodeIPAddressLogServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NodeIPAddressThresholdRPC() pb.NodeIPAddressThresholdServiceClient {
|
||||
return pb.NewNodeIPAddressThresholdServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NodeValueRPC() pb.NodeValueServiceClient {
|
||||
return pb.NewNodeValueServiceClient(this.pickConn())
|
||||
}
|
||||
@@ -103,6 +124,10 @@ func (this *RPCClient) ServerRPC() pb.ServerServiceClient {
|
||||
return pb.NewServerServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ServerBandwidthStatRPC() pb.ServerBandwidthStatServiceClient {
|
||||
return pb.NewServerBandwidthStatServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ServerClientSystemMonthlyStatRPC() pb.ServerClientSystemMonthlyStatServiceClient {
|
||||
return pb.NewServerClientSystemMonthlyStatServiceClient(this.pickConn())
|
||||
}
|
||||
@@ -131,6 +156,10 @@ func (this *RPCClient) ServerHTTPFirewallDailyStatRPC() pb.ServerHTTPFirewallDai
|
||||
return pb.NewServerHTTPFirewallDailyStatServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ServerDailyStatRPC() pb.ServerDailyStatServiceClient {
|
||||
return pb.NewServerDailyStatServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ServerGroupRPC() pb.ServerGroupServiceClient {
|
||||
return pb.NewServerGroupServiceClient(this.pickConn())
|
||||
}
|
||||
@@ -139,18 +168,14 @@ func (this *RPCClient) APINodeRPC() pb.APINodeServiceClient {
|
||||
return pb.NewAPINodeServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) UserNodeRPC() pb.UserNodeServiceClient {
|
||||
return pb.NewUserNodeServiceClient(this.pickConn())
|
||||
func (this *RPCClient) APIMethodStatRPC() pb.APIMethodStatServiceClient {
|
||||
return pb.NewAPIMethodStatServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) DBNodeRPC() pb.DBNodeServiceClient {
|
||||
return pb.NewDBNodeServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) MonitorNodeRPC() pb.MonitorNodeServiceClient {
|
||||
return pb.NewMonitorNodeServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) DBRPC() pb.DBServiceClient {
|
||||
return pb.NewDBServiceClient(this.pickConn())
|
||||
}
|
||||
@@ -191,6 +216,14 @@ func (this *RPCClient) HTTPCachePolicyRPC() pb.HTTPCachePolicyServiceClient {
|
||||
return pb.NewHTTPCachePolicyServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) HTTPCacheTaskRPC() pb.HTTPCacheTaskServiceClient {
|
||||
return pb.NewHTTPCacheTaskServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) HTTPCacheTaskKeyRPC() pb.HTTPCacheTaskKeyServiceClient {
|
||||
return pb.NewHTTPCacheTaskKeyServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) HTTPFirewallPolicyRPC() pb.HTTPFirewallPolicyServiceClient {
|
||||
return pb.NewHTTPFirewallPolicyServiceClient(this.pickConn())
|
||||
}
|
||||
@@ -203,6 +236,10 @@ func (this *RPCClient) HTTPFirewallRuleSetRPC() pb.HTTPFirewallRuleSetServiceCli
|
||||
return pb.NewHTTPFirewallRuleSetServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) FirewallRPC() pb.FirewallServiceClient {
|
||||
return pb.NewFirewallServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) HTTPLocationRPC() pb.HTTPLocationServiceClient {
|
||||
return pb.NewHTTPLocationServiceClient(this.pickConn())
|
||||
}
|
||||
@@ -224,6 +261,10 @@ func (this *RPCClient) HTTPFastcgiRPC() pb.HTTPFastcgiServiceClient {
|
||||
return pb.NewHTTPFastcgiServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) HTTPAuthPolicyRPC() pb.HTTPAuthPolicyServiceClient {
|
||||
return pb.NewHTTPAuthPolicyServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) SSLCertRPC() pb.SSLCertServiceClient {
|
||||
return pb.NewSSLCertServiceClient(this.pickConn())
|
||||
}
|
||||
@@ -240,38 +281,18 @@ func (this *RPCClient) MessageRPC() pb.MessageServiceClient {
|
||||
return pb.NewMessageServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) MessageRecipientGroupRPC() pb.MessageRecipientGroupServiceClient {
|
||||
return pb.NewMessageRecipientGroupServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) MessageRecipientRPC() pb.MessageRecipientServiceClient {
|
||||
return pb.NewMessageRecipientServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) MessageMediaRPC() pb.MessageMediaServiceClient {
|
||||
return pb.NewMessageMediaServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) MessageMediaInstanceRPC() pb.MessageMediaInstanceServiceClient {
|
||||
return pb.NewMessageMediaInstanceServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) MessageTaskRPC() pb.MessageTaskServiceClient {
|
||||
return pb.NewMessageTaskServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) MessageTaskLogRPC() pb.MessageTaskLogServiceClient {
|
||||
return pb.NewMessageTaskLogServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) MessageReceiverRPC() pb.MessageReceiverServiceClient {
|
||||
return pb.NewMessageReceiverServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) IPLibraryRPC() pb.IPLibraryServiceClient {
|
||||
return pb.NewIPLibraryServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) IPLibraryFileRPC() pb.IPLibraryFileServiceClient {
|
||||
return pb.NewIPLibraryFileServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) IPLibraryArtifactRPC() pb.IPLibraryArtifactServiceClient {
|
||||
return pb.NewIPLibraryArtifactServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) IPListRPC() pb.IPListServiceClient {
|
||||
return pb.NewIPListServiceClient(this.pickConn())
|
||||
}
|
||||
@@ -296,6 +317,18 @@ func (this *RPCClient) RegionProvinceRPC() pb.RegionProvinceServiceClient {
|
||||
return pb.NewRegionProvinceServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) RegionCityRPC() pb.RegionCityServiceClient {
|
||||
return pb.NewRegionCityServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) RegionTownRPC() pb.RegionTownServiceClient {
|
||||
return pb.NewRegionTownServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) RegionProviderRPC() pb.RegionProviderServiceClient {
|
||||
return pb.NewRegionProviderServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) LogRPC() pb.LogServiceClient {
|
||||
return pb.NewLogServiceClient(this.pickConn())
|
||||
}
|
||||
@@ -324,38 +357,78 @@ func (this *RPCClient) ACMETaskRPC() pb.ACMETaskServiceClient {
|
||||
return pb.NewACMETaskServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ACMEProviderRPC() pb.ACMEProviderServiceClient {
|
||||
return pb.NewACMEProviderServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ACMEProviderAccountRPC() pb.ACMEProviderAccountServiceClient {
|
||||
return pb.NewACMEProviderAccountServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) UserRPC() pb.UserServiceClient {
|
||||
return pb.NewUserServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) UserBillRPC() pb.UserBillServiceClient {
|
||||
return pb.NewUserBillServiceClient(this.pickConn())
|
||||
func (this *RPCClient) UserAccessKeyRPC() pb.UserAccessKeyServiceClient {
|
||||
return pb.NewUserAccessKeyServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) UserIdentityRPC() pb.UserIdentityServiceClient {
|
||||
return pb.NewUserIdentityServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) LoginRPC() pb.LoginServiceClient {
|
||||
return pb.NewLoginServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) LoginSessionRPC() pb.LoginSessionServiceClient {
|
||||
return pb.NewLoginSessionServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NodeTaskRPC() pb.NodeTaskServiceClient {
|
||||
return pb.NewNodeTaskServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) AuthorityKeyRPC() pb.AuthorityKeyServiceClient {
|
||||
return pb.NewAuthorityKeyServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) AuthorityNodeRPC() pb.AuthorityNodeServiceClient {
|
||||
return pb.NewAuthorityNodeServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) LatestItemRPC() pb.LatestItemServiceClient {
|
||||
return pb.NewLatestItemServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) MetricItemRPC() pb.MetricItemServiceClient {
|
||||
return pb.NewMetricItemServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) MetricStatRPC() pb.MetricStatServiceClient {
|
||||
return pb.NewMetricStatServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) MetricChartRPC() pb.MetricChartServiceClient {
|
||||
return pb.NewMetricChartServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NodeClusterMetricItemRPC() pb.NodeClusterMetricItemServiceClient {
|
||||
return pb.NewNodeClusterMetricItemServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ServerStatBoardRPC() pb.ServerStatBoardServiceClient {
|
||||
return pb.NewServerStatBoardServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ServerDomainHourlyStatRPC() pb.ServerDomainHourlyStatServiceClient {
|
||||
return pb.NewServerDomainHourlyStatServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ServerStatBoardChartRPC() pb.ServerStatBoardChartServiceClient {
|
||||
return pb.NewServerStatBoardChartServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) TrafficDailyStatRPC() pb.TrafficDailyStatServiceClient {
|
||||
return pb.NewTrafficDailyStatServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
// Context 构造Admin上下文
|
||||
func (this *RPCClient) Context(adminId int64) context.Context {
|
||||
ctx := context.Background()
|
||||
m := maps.Map{
|
||||
var ctx = context.Background()
|
||||
var m = maps.Map{
|
||||
"timestamp": time.Now().Unix(),
|
||||
"type": "admin",
|
||||
"userId": adminId,
|
||||
@@ -370,15 +443,15 @@ func (this *RPCClient) Context(adminId int64) context.Context {
|
||||
utils.PrintError(err)
|
||||
return context.Background()
|
||||
}
|
||||
token := base64.StdEncoding.EncodeToString(data)
|
||||
var token = base64.StdEncoding.EncodeToString(data)
|
||||
ctx = metadata.AppendToOutgoingContext(ctx, "nodeId", this.apiConfig.NodeId, "token", token)
|
||||
return ctx
|
||||
}
|
||||
|
||||
// APIContext 构造API上下文
|
||||
func (this *RPCClient) APIContext(apiNodeId int64) context.Context {
|
||||
ctx := context.Background()
|
||||
m := maps.Map{
|
||||
var ctx = context.Background()
|
||||
var m = maps.Map{
|
||||
"timestamp": time.Now().Unix(),
|
||||
"type": "api",
|
||||
"userId": apiNodeId,
|
||||
@@ -393,7 +466,7 @@ func (this *RPCClient) APIContext(apiNodeId int64) context.Context {
|
||||
utils.PrintError(err)
|
||||
return context.Background()
|
||||
}
|
||||
token := base64.StdEncoding.EncodeToString(data)
|
||||
var token = base64.StdEncoding.EncodeToString(data)
|
||||
ctx = metadata.AppendToOutgoingContext(ctx, "nodeId", this.apiConfig.NodeId, "token", token)
|
||||
return ctx
|
||||
}
|
||||
@@ -401,25 +474,56 @@ func (this *RPCClient) APIContext(apiNodeId int64) context.Context {
|
||||
// UpdateConfig 修改配置
|
||||
func (this *RPCClient) UpdateConfig(config *configs.APIConfig) error {
|
||||
this.apiConfig = config
|
||||
return this.init()
|
||||
|
||||
this.locker.Lock()
|
||||
err := this.init()
|
||||
this.locker.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
// 初始化
|
||||
func (this *RPCClient) init() error {
|
||||
// 当前的IP地址
|
||||
var localIPAddrs = this.localIPAddrs()
|
||||
|
||||
// 重新连接
|
||||
conns := []*grpc.ClientConn{}
|
||||
for _, endpoint := range this.apiConfig.RPC.Endpoints {
|
||||
var conns = []*grpc.ClientConn{}
|
||||
for _, endpoint := range this.apiConfig.RPCEndpoints {
|
||||
u, err := url.Parse(endpoint)
|
||||
if err != nil {
|
||||
return errors.New("parse endpoint failed: " + err.Error())
|
||||
return fmt.Errorf("parse endpoint failed: %w", err)
|
||||
}
|
||||
|
||||
var apiHost = u.Host
|
||||
|
||||
// 如果本机,则将地址修改为回路地址
|
||||
if lists.ContainsString(localIPAddrs, u.Hostname()) {
|
||||
if strings.Contains(apiHost, "[") { // IPv6 [host]:port
|
||||
apiHost = "[::1]"
|
||||
} else {
|
||||
apiHost = "127.0.0.1"
|
||||
}
|
||||
var port = u.Port()
|
||||
if len(port) > 0 {
|
||||
apiHost += ":" + port
|
||||
}
|
||||
}
|
||||
|
||||
var conn *grpc.ClientConn
|
||||
var callOptions = grpc.WithDefaultCallOptions(
|
||||
grpc.MaxCallRecvMsgSize(128<<20),
|
||||
grpc.MaxCallSendMsgSize(128<<20),
|
||||
grpc.UseCompressor(gzip.Name),
|
||||
)
|
||||
var keepaliveParams = grpc.WithKeepaliveParams(keepalive.ClientParameters{
|
||||
Time: 30 * time.Second,
|
||||
})
|
||||
if u.Scheme == "http" {
|
||||
conn, err = grpc.Dial(u.Host, grpc.WithInsecure(), grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(128*1024*1024)))
|
||||
conn, err = grpc.Dial(apiHost, grpc.WithTransportCredentials(insecure.NewCredentials()), callOptions, keepaliveParams)
|
||||
} else if u.Scheme == "https" {
|
||||
conn, err = grpc.Dial(u.Host, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{
|
||||
conn, err = grpc.Dial(apiHost, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
})), grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(128*1024*1024)))
|
||||
})), callOptions, keepaliveParams)
|
||||
} else {
|
||||
return errors.New("parse endpoint failed: invalid scheme '" + u.Scheme + "'")
|
||||
}
|
||||
@@ -443,39 +547,71 @@ func (this *RPCClient) pickConn() *grpc.ClientConn {
|
||||
defer this.locker.Unlock()
|
||||
|
||||
// 检查连接状态
|
||||
if len(this.conns) > 0 {
|
||||
availableConns := []*grpc.ClientConn{}
|
||||
for _, state := range []connectivity.State{connectivity.Ready, connectivity.Idle, connectivity.Connecting} {
|
||||
var countConns = len(this.conns)
|
||||
if countConns > 0 {
|
||||
if countConns == 1 {
|
||||
return this.conns[0]
|
||||
}
|
||||
for _, state := range []connectivity.State{
|
||||
connectivity.Ready,
|
||||
connectivity.Idle,
|
||||
connectivity.Connecting,
|
||||
connectivity.TransientFailure,
|
||||
} {
|
||||
var availableConns = []*grpc.ClientConn{}
|
||||
for _, conn := range this.conns {
|
||||
if conn.GetState() == state {
|
||||
availableConns = append(availableConns, conn)
|
||||
}
|
||||
}
|
||||
if len(availableConns) > 0 {
|
||||
break
|
||||
return this.randConn(availableConns)
|
||||
}
|
||||
}
|
||||
|
||||
if len(availableConns) > 0 {
|
||||
return availableConns[rands.Int(0, len(availableConns)-1)]
|
||||
}
|
||||
|
||||
// 关闭
|
||||
for _, conn := range this.conns {
|
||||
_ = conn.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// 重新初始化
|
||||
err := this.init()
|
||||
if err != nil {
|
||||
// 错误提示已经在构造对象时打印过,所以这里不再重复打印
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(this.conns) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return this.conns[rands.Int(0, len(this.conns)-1)]
|
||||
return this.randConn(this.conns)
|
||||
}
|
||||
|
||||
// Close 关闭
|
||||
func (this *RPCClient) Close() error {
|
||||
this.locker.Lock()
|
||||
defer this.locker.Unlock()
|
||||
|
||||
var lastErr error
|
||||
for _, conn := range this.conns {
|
||||
var err = conn.Close()
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return lastErr
|
||||
}
|
||||
|
||||
func (this *RPCClient) localIPAddrs() []string {
|
||||
localInterfaceAddrs, err := net.InterfaceAddrs()
|
||||
var localIPAddrs = []string{}
|
||||
if err == nil {
|
||||
for _, addr := range localInterfaceAddrs {
|
||||
var addrString = addr.String()
|
||||
var index = strings.Index(addrString, "/")
|
||||
if index > 0 {
|
||||
localIPAddrs = append(localIPAddrs, addrString[:index])
|
||||
}
|
||||
}
|
||||
}
|
||||
return localIPAddrs
|
||||
}
|
||||
|
||||
func (this *RPCClient) randConn(conns []*grpc.ClientConn) *grpc.ClientConn {
|
||||
var l = len(conns)
|
||||
if l == 0 {
|
||||
return nil
|
||||
}
|
||||
if l == 1 {
|
||||
return conns[0]
|
||||
}
|
||||
return conns[rands.Int(0, l-1)]
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ func TestRPCClient_NodeRPC(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rpc, err := NewRPCClient(config)
|
||||
rpc, err := NewRPCClient(config, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -34,14 +34,10 @@ func TestRPCClient_NodeRPC(t *testing.T) {
|
||||
|
||||
func TestRPC_Dial_HTTP(t *testing.T) {
|
||||
client, err := NewRPCClient(&configs.APIConfig{
|
||||
RPC: struct {
|
||||
Endpoints []string `yaml:"endpoints"`
|
||||
}{
|
||||
Endpoints: []string{"127.0.0.1:8003"},
|
||||
},
|
||||
NodeId: "a7e55782dab39bce0901058a1e14a0e6",
|
||||
Secret: "lvyPobI3BszkJopz5nPTocOs0OLkEJ7y",
|
||||
})
|
||||
RPCEndpoints: []string{"https://127.0.0.1:8003"},
|
||||
NodeId: "a7e55782dab39bce0901058a1e14a0e6",
|
||||
Secret: "lvyPobI3BszkJopz5nPTocOs0OLkEJ7y",
|
||||
}, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -55,14 +51,10 @@ func TestRPC_Dial_HTTP(t *testing.T) {
|
||||
|
||||
func TestRPC_Dial_HTTP_2(t *testing.T) {
|
||||
client, err := NewRPCClient(&configs.APIConfig{
|
||||
RPC: struct {
|
||||
Endpoints []string `yaml:"endpoints"`
|
||||
}{
|
||||
Endpoints: []string{"http://127.0.0.1:8003"},
|
||||
},
|
||||
NodeId: "a7e55782dab39bce0901058a1e14a0e6",
|
||||
Secret: "lvyPobI3BszkJopz5nPTocOs0OLkEJ7y",
|
||||
})
|
||||
RPCEndpoints: []string{"https://127.0.0.1:8003"},
|
||||
NodeId: "a7e55782dab39bce0901058a1e14a0e6",
|
||||
Secret: "lvyPobI3BszkJopz5nPTocOs0OLkEJ7y",
|
||||
}, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -76,14 +68,10 @@ func TestRPC_Dial_HTTP_2(t *testing.T) {
|
||||
|
||||
func TestRPC_Dial_HTTPS(t *testing.T) {
|
||||
client, err := NewRPCClient(&configs.APIConfig{
|
||||
RPC: struct {
|
||||
Endpoints []string `yaml:"endpoints"`
|
||||
}{
|
||||
Endpoints: []string{"https://127.0.0.1:8004"},
|
||||
},
|
||||
NodeId: "a7e55782dab39bce0901058a1e14a0e6",
|
||||
Secret: "lvyPobI3BszkJopz5nPTocOs0OLkEJ7y",
|
||||
})
|
||||
RPCEndpoints: []string{"https://127.0.0.1:8004"},
|
||||
NodeId: "a7e55782dab39bce0901058a1e14a0e6",
|
||||
Secret: "lvyPobI3BszkJopz5nPTocOs0OLkEJ7y",
|
||||
}, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -94,3 +82,53 @@ func TestRPC_Dial_HTTPS(t *testing.T) {
|
||||
}
|
||||
t.Log(resp.Node)
|
||||
}
|
||||
|
||||
func BenchmarkNewRPCClient(b *testing.B) {
|
||||
config, err := configs.LoadAPIConfig()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
rpc, err := NewRPCClient(config, true)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
resp, err := rpc.AdminRPC().LoginAdmin(rpc.Context(0), &pb.LoginAdminRequest{
|
||||
Username: "admin",
|
||||
Password: stringutil.Md5("123456"),
|
||||
})
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
_ = resp
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNewRPCClient_2(b *testing.B) {
|
||||
config, err := configs.LoadAPIConfig()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
rpc, err := NewRPCClient(config, true)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
var conn = rpc.AdminRPC()
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
resp, err := conn.LoginAdmin(rpc.Context(0), &pb.LoginAdminRequest{
|
||||
Username: "admin",
|
||||
Password: stringutil.Md5("123456"),
|
||||
})
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
_ = resp
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@ package rpc
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
@@ -20,7 +23,7 @@ func SharedRPC() (*RPCClient, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client, err := NewRPCClient(config)
|
||||
client, err := NewRPCClient(config, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -28,3 +31,39 @@ func SharedRPC() (*RPCClient, error) {
|
||||
sharedRPC = client
|
||||
return sharedRPC, nil
|
||||
}
|
||||
|
||||
// IsConnError 是否为连接错误
|
||||
func IsConnError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查是否为连接错误
|
||||
statusErr, ok := status.FromError(err)
|
||||
if ok {
|
||||
var errorCode = statusErr.Code()
|
||||
return errorCode == codes.Unavailable || errorCode == codes.Canceled
|
||||
}
|
||||
|
||||
if strings.Contains(err.Error(), "code = Canceled") {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// IsUnimplementedError 检查是否为未实现错误
|
||||
func IsUnimplementedError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
statusErr, ok := status.FromError(err)
|
||||
if ok {
|
||||
if statusErr.Code() == codes.Unimplemented {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -16,3 +16,8 @@ func IsConfigured() bool {
|
||||
isConfigured = err == nil
|
||||
return isConfigured
|
||||
}
|
||||
|
||||
// IsNewInstalled IsNew 检查是否新安装
|
||||
func IsNewInstalled() bool {
|
||||
return configs.IsNewInstalled()
|
||||
}
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package tasks
|
||||
|
||||
import (
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/events"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
events.On(events.EventStart, func() {
|
||||
task := NewAuthorityTask()
|
||||
go task.Start()
|
||||
})
|
||||
}
|
||||
|
||||
type AuthorityTask struct {
|
||||
}
|
||||
|
||||
func NewAuthorityTask() *AuthorityTask {
|
||||
return &AuthorityTask{}
|
||||
}
|
||||
|
||||
func (this *AuthorityTask) Start() {
|
||||
ticker := time.NewTicker(10 * time.Minute)
|
||||
if Tea.IsTesting() {
|
||||
// 快速测试
|
||||
ticker = time.NewTicker(1 * time.Minute)
|
||||
}
|
||||
|
||||
// 初始化的时候先获取一次
|
||||
timeout := time.NewTimer(5 * time.Second)
|
||||
<-timeout.C
|
||||
err := this.Loop()
|
||||
if err != nil {
|
||||
logs.Println("[TASK][AuthorityTask]" + err.Error())
|
||||
}
|
||||
|
||||
// 定时获取
|
||||
for range ticker.C {
|
||||
err := this.Loop()
|
||||
if err != nil {
|
||||
logs.Println("[TASK][AuthorityTask]" + err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (this *AuthorityTask) Loop() error {
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp, err := rpcClient.AuthorityKeyRPC().ReadAuthorityKey(rpcClient.Context(0), &pb.ReadAuthorityKeyRequest{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.AuthorityKey != nil {
|
||||
teaconst.IsPlus = true
|
||||
} else {
|
||||
teaconst.IsPlus = false
|
||||
}
|
||||
return nil
|
||||
}
|
||||
133
internal/tasks/task_check_updates.go
Normal file
133
internal/tasks/task_check_updates.go
Normal file
@@ -0,0 +1,133 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/events"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||
"io"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
events.On(events.EventStart, func() {
|
||||
var task = NewCheckUpdatesTask()
|
||||
goman.New(func() {
|
||||
task.Start()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
type CheckUpdatesTask struct {
|
||||
ticker *time.Ticker
|
||||
}
|
||||
|
||||
func NewCheckUpdatesTask() *CheckUpdatesTask {
|
||||
return &CheckUpdatesTask{}
|
||||
}
|
||||
|
||||
func (this *CheckUpdatesTask) Start() {
|
||||
this.ticker = time.NewTicker(12 * time.Hour)
|
||||
for range this.ticker.C {
|
||||
err := this.Loop()
|
||||
if err != nil {
|
||||
logs.Println("[TASK][CHECK_UPDATES_TASK]" + err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (this *CheckUpdatesTask) Loop() error {
|
||||
// 检查是否开启
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
valueResp, err := rpcClient.SysSettingRPC().ReadSysSetting(rpcClient.Context(0), &pb.ReadSysSettingRequest{Code: systemconfigs.SettingCodeCheckUpdates})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var valueJSON = valueResp.ValueJSON
|
||||
var config = systemconfigs.NewCheckUpdatesConfig()
|
||||
if len(valueJSON) > 0 {
|
||||
err = json.Unmarshal(valueJSON, config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decode config failed: %w", err)
|
||||
}
|
||||
if !config.AutoCheck {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 开始检查
|
||||
type Response struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
|
||||
// 目前支持Linux
|
||||
if runtime.GOOS != "linux" {
|
||||
return nil
|
||||
}
|
||||
|
||||
var apiURL = teaconst.UpdatesURL
|
||||
apiURL = strings.ReplaceAll(apiURL, "${os}", runtime.GOOS)
|
||||
apiURL = strings.ReplaceAll(apiURL, "${arch}", runtime.GOARCH)
|
||||
apiURL = strings.ReplaceAll(apiURL, "${version}", teaconst.Version)
|
||||
resp, err := http.Get(apiURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read api failed: %w", err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read api failed: %w", err)
|
||||
}
|
||||
|
||||
var apiResponse = &Response{}
|
||||
err = json.Unmarshal(data, apiResponse)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decode version data failed: %w", err)
|
||||
}
|
||||
|
||||
if apiResponse.Code != 200 {
|
||||
return errors.New("invalid response: " + apiResponse.Message)
|
||||
}
|
||||
|
||||
var m = maps.NewMap(apiResponse.Data)
|
||||
var dlHost = m.GetString("host")
|
||||
var versions = m.GetSlice("versions")
|
||||
if len(versions) > 0 {
|
||||
for _, version := range versions {
|
||||
var vMap = maps.NewMap(version)
|
||||
if vMap.GetString("code") == "admin" {
|
||||
var latestVersion = vMap.GetString("version")
|
||||
if stringutil.VersionCompare(teaconst.Version, latestVersion) < 0 && (len(config.IgnoredVersion) == 0 || stringutil.VersionCompare(latestVersion, config.IgnoredVersion) > 0) {
|
||||
teaconst.NewVersionCode = latestVersion
|
||||
teaconst.NewVersionDownloadURL = dlHost + vMap.GetString("url")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,25 +1,37 @@
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/events"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/setup"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
events.On(events.EventStart, func() {
|
||||
task := NewSyncAPINodesTask()
|
||||
go task.Start()
|
||||
goman.New(func() {
|
||||
task.Start()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// API节点同步任务
|
||||
// SyncAPINodesTask API节点同步任务
|
||||
type SyncAPINodesTask struct {
|
||||
}
|
||||
|
||||
@@ -42,6 +54,21 @@ func (this *SyncAPINodesTask) Start() {
|
||||
}
|
||||
|
||||
func (this *SyncAPINodesTask) Loop() error {
|
||||
// 如果还没有安装直接返回
|
||||
if !setup.IsConfigured() || teaconst.IsRecoverMode {
|
||||
return nil
|
||||
}
|
||||
|
||||
config, err := configs.LoadAPIConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 是否禁止自动升级
|
||||
if config.RPCDisableUpdate {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 获取所有可用的节点
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
@@ -52,8 +79,8 @@ func (this *SyncAPINodesTask) Loop() error {
|
||||
return err
|
||||
}
|
||||
|
||||
newEndpoints := []string{}
|
||||
for _, node := range resp.Nodes {
|
||||
var newEndpoints = []string{}
|
||||
for _, node := range resp.ApiNodes {
|
||||
if !node.IsOn {
|
||||
continue
|
||||
}
|
||||
@@ -61,23 +88,25 @@ func (this *SyncAPINodesTask) Loop() error {
|
||||
}
|
||||
|
||||
// 和现有的对比
|
||||
config, err := configs.LoadAPIConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
if this.isSame(newEndpoints, config.RPCEndpoints) {
|
||||
return nil
|
||||
}
|
||||
if this.isSame(newEndpoints, config.RPC.Endpoints) {
|
||||
|
||||
// 测试是否有API节点可用
|
||||
hasOk := this.testEndpoints(newEndpoints)
|
||||
if !hasOk {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 修改RPC对象配置
|
||||
config.RPC.Endpoints = newEndpoints
|
||||
config.RPCEndpoints = newEndpoints
|
||||
err = rpcClient.UpdateConfig(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 保存到文件
|
||||
err = config.WriteFile(Tea.ConfigFile("api.yaml"))
|
||||
err = config.WriteFile(Tea.ConfigFile(configs.ConfigFileName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -90,3 +119,48 @@ func (this *SyncAPINodesTask) isSame(endpoints1 []string, endpoints2 []string) b
|
||||
sort.Strings(endpoints2)
|
||||
return strings.Join(endpoints1, "&") == strings.Join(endpoints2, "&")
|
||||
}
|
||||
|
||||
func (this *SyncAPINodesTask) testEndpoints(endpoints []string) bool {
|
||||
if len(endpoints) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
var wg = sync.WaitGroup{}
|
||||
wg.Add(len(endpoints))
|
||||
|
||||
var ok = false
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
go func(endpoint string) {
|
||||
defer wg.Done()
|
||||
|
||||
u, err := url.Parse(endpoint)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer func() {
|
||||
cancel()
|
||||
}()
|
||||
var conn *grpc.ClientConn
|
||||
|
||||
if u.Scheme == "http" {
|
||||
conn, err = grpc.DialContext(ctx, u.Host, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock())
|
||||
} else if u.Scheme == "https" {
|
||||
conn, err = grpc.DialContext(ctx, u.Host, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
})), grpc.WithBlock())
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_ = conn.Close()
|
||||
|
||||
ok = true
|
||||
}(endpoint)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package tasks
|
||||
|
||||
import (
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/events"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/setup"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/nodes/nodeutils"
|
||||
@@ -16,11 +18,13 @@ import (
|
||||
func init() {
|
||||
events.On(events.EventStart, func() {
|
||||
task := NewSyncClusterTask()
|
||||
go task.Start()
|
||||
goman.New(func() {
|
||||
task.Start()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 自动同步集群任务
|
||||
// SyncClusterTask 自动同步集群任务
|
||||
type SyncClusterTask struct {
|
||||
}
|
||||
|
||||
@@ -40,7 +44,7 @@ func (this *SyncClusterTask) Start() {
|
||||
|
||||
func (this *SyncClusterTask) loop() error {
|
||||
// 如果还没有安装直接返回
|
||||
if !setup.IsConfigured() {
|
||||
if !setup.IsConfigured() || teaconst.IsRecoverMode {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -50,7 +54,7 @@ func (this *SyncClusterTask) loop() error {
|
||||
}
|
||||
ctx := rpcClient.Context(0)
|
||||
|
||||
tasksResp, err := rpcClient.NodeTaskRPC().FindNotifyingNodeTasks(ctx, &pb.FindNotifyingNodeTasksRequest{Size: 100})
|
||||
tasksResp, err := rpcClient.NodeTaskRPC().FindNotifyingNodeTasks(ctx, &pb.FindNotifyingNodeTasksRequest{Size: 500})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -10,8 +10,10 @@ var DefaultCache = NewCache()
|
||||
// TTL缓存
|
||||
// 最大的缓存时间为30 * 86400
|
||||
// Piece数据结构:
|
||||
// Piece1 | Piece2 | Piece3 | ...
|
||||
// [ Item1, Item2, ... | ...
|
||||
//
|
||||
// Piece1 | Piece2 | Piece3 | ...
|
||||
// [ Item1, Item2, ... | ...
|
||||
//
|
||||
// KeyMap列表数据结构
|
||||
// { timestamp1 => [key1, key2, ...] }, ...
|
||||
type Cache struct {
|
||||
@@ -109,19 +111,11 @@ func (this *Cache) Read(key string) (item *Item) {
|
||||
return this.pieces[uint64Key%this.countPieces].Read(uint64Key)
|
||||
}
|
||||
|
||||
func (this *Cache) readIntKey(key uint64) (value *Item) {
|
||||
return this.pieces[key%this.countPieces].Read(key)
|
||||
}
|
||||
|
||||
func (this *Cache) Delete(key string) {
|
||||
uint64Key := HashKey([]byte(key))
|
||||
this.pieces[uint64Key%this.countPieces].Delete(uint64Key)
|
||||
}
|
||||
|
||||
func (this *Cache) deleteIntKey(key uint64) {
|
||||
this.pieces[key%this.countPieces].Delete(key)
|
||||
}
|
||||
|
||||
func (this *Cache) Count() (count int) {
|
||||
for _, piece := range this.pieces {
|
||||
count += piece.Count()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package ttlcache
|
||||
|
||||
import "github.com/cespare/xxhash"
|
||||
import "github.com/cespare/xxhash/v2"
|
||||
|
||||
func HashKey(key []byte) uint64 {
|
||||
return xxhash.Sum64(key)
|
||||
|
||||
79
internal/utils/apinodeutils/deploy_file.go
Normal file
79
internal/utils/apinodeutils/deploy_file.go
Normal file
@@ -0,0 +1,79 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package apinodeutils
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
// DeployFile 部署文件描述
|
||||
type DeployFile struct {
|
||||
OS string
|
||||
Arch string
|
||||
Version string
|
||||
Path string
|
||||
}
|
||||
|
||||
// Sum 计算概要
|
||||
func (this *DeployFile) Sum() (string, error) {
|
||||
fp, err := os.Open(this.Path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer func() {
|
||||
_ = fp.Close()
|
||||
}()
|
||||
|
||||
m := md5.New()
|
||||
buffer := make([]byte, 128*1024)
|
||||
for {
|
||||
n, err := fp.Read(buffer)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
_, err = m.Write(buffer[:n])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
sum := m.Sum(nil)
|
||||
return fmt.Sprintf("%x", sum), nil
|
||||
}
|
||||
|
||||
// Read 读取一个片段数据
|
||||
func (this *DeployFile) Read(offset int64) (data []byte, newOffset int64, err error) {
|
||||
fp, err := os.Open(this.Path)
|
||||
if err != nil {
|
||||
return nil, offset, err
|
||||
}
|
||||
defer func() {
|
||||
_ = fp.Close()
|
||||
}()
|
||||
|
||||
stat, err := fp.Stat()
|
||||
if err != nil {
|
||||
return nil, offset, err
|
||||
}
|
||||
if offset >= stat.Size() {
|
||||
return nil, offset, io.EOF
|
||||
}
|
||||
|
||||
_, err = fp.Seek(offset, io.SeekStart)
|
||||
if err != nil {
|
||||
return nil, offset, err
|
||||
}
|
||||
|
||||
buffer := make([]byte, 128*1024)
|
||||
n, err := fp.Read(buffer)
|
||||
if err != nil {
|
||||
return nil, offset, err
|
||||
}
|
||||
|
||||
return buffer[:n], offset + int64(n), nil
|
||||
}
|
||||
96
internal/utils/apinodeutils/deploy_manager.go
Normal file
96
internal/utils/apinodeutils/deploy_manager.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package apinodeutils
|
||||
|
||||
import (
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/files"
|
||||
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// DeployManager 节点部署文件管理器
|
||||
// 如果节点部署文件有变化,需要重启API节点以便于生效
|
||||
type DeployManager struct {
|
||||
dir string
|
||||
}
|
||||
|
||||
// NewDeployManager 获取新节点部署文件管理器
|
||||
func NewDeployManager() *DeployManager {
|
||||
var manager = &DeployManager{
|
||||
dir: Tea.Root + "/edge-api/deploy",
|
||||
}
|
||||
manager.LoadNodeFiles()
|
||||
manager.LoadNSNodeFiles()
|
||||
return manager
|
||||
}
|
||||
|
||||
// LoadNodeFiles 加载所有边缘节点文件
|
||||
func (this *DeployManager) LoadNodeFiles() []*DeployFile {
|
||||
var keyMap = map[string]*DeployFile{} // key => File
|
||||
|
||||
var reg = regexp.MustCompile(`^edge-node-(\w+)-(\w+)-v([0-9.]+)\.zip$`)
|
||||
for _, file := range files.NewFile(this.dir).List() {
|
||||
var name = file.Name()
|
||||
if !reg.MatchString(name) {
|
||||
continue
|
||||
}
|
||||
var matches = reg.FindStringSubmatch(name)
|
||||
var osName = matches[1]
|
||||
var arch = matches[2]
|
||||
var version = matches[3]
|
||||
|
||||
var key = osName + "_" + arch
|
||||
oldFile, ok := keyMap[key]
|
||||
if ok && stringutil.VersionCompare(oldFile.Version, version) > 0 {
|
||||
continue
|
||||
}
|
||||
keyMap[key] = &DeployFile{
|
||||
OS: osName,
|
||||
Arch: arch,
|
||||
Version: version,
|
||||
Path: file.Path(),
|
||||
}
|
||||
}
|
||||
|
||||
var result = []*DeployFile{}
|
||||
for _, v := range keyMap {
|
||||
result = append(result, v)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// LoadNSNodeFiles 加载所有NS节点安装文件
|
||||
func (this *DeployManager) LoadNSNodeFiles() []*DeployFile {
|
||||
var keyMap = map[string]*DeployFile{} // key => File
|
||||
|
||||
var reg = regexp.MustCompile(`^edge-dns-(\w+)-(\w+)-v([0-9.]+)\.zip$`)
|
||||
for _, file := range files.NewFile(this.dir).List() {
|
||||
var name = file.Name()
|
||||
if !reg.MatchString(name) {
|
||||
continue
|
||||
}
|
||||
var matches = reg.FindStringSubmatch(name)
|
||||
var osName = matches[1]
|
||||
var arch = matches[2]
|
||||
var version = matches[3]
|
||||
|
||||
var key = osName + "_" + arch
|
||||
oldFile, ok := keyMap[key]
|
||||
if ok && stringutil.VersionCompare(oldFile.Version, version) > 0 {
|
||||
continue
|
||||
}
|
||||
keyMap[key] = &DeployFile{
|
||||
OS: osName,
|
||||
Arch: arch,
|
||||
Version: version,
|
||||
Path: file.Path(),
|
||||
}
|
||||
}
|
||||
|
||||
var result = []*DeployFile{}
|
||||
for _, v := range keyMap {
|
||||
result = append(result, v)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
30
internal/utils/apinodeutils/manager.go
Normal file
30
internal/utils/apinodeutils/manager.go
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package apinodeutils
|
||||
|
||||
var SharedManager = NewManager()
|
||||
|
||||
type Manager struct {
|
||||
upgraderMap map[int64]*Upgrader
|
||||
}
|
||||
|
||||
func NewManager() *Manager {
|
||||
return &Manager{
|
||||
upgraderMap: map[int64]*Upgrader{},
|
||||
}
|
||||
}
|
||||
|
||||
func (this *Manager) AddUpgrader(upgrader *Upgrader) {
|
||||
this.upgraderMap[upgrader.apiNodeId] = upgrader
|
||||
}
|
||||
|
||||
func (this *Manager) FindUpgrader(apiNodeId int64) *Upgrader {
|
||||
return this.upgraderMap[apiNodeId]
|
||||
}
|
||||
|
||||
func (this *Manager) RemoveUpgrader(upgrader *Upgrader) {
|
||||
if upgrader == nil {
|
||||
return
|
||||
}
|
||||
delete(this.upgraderMap, upgrader.apiNodeId)
|
||||
}
|
||||
349
internal/utils/apinodeutils/upgrader.go
Normal file
349
internal/utils/apinodeutils/upgrader.go
Normal file
@@ -0,0 +1,349 @@
|
||||
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package apinodeutils
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
type Progress struct {
|
||||
Percent float64
|
||||
}
|
||||
|
||||
type Upgrader struct {
|
||||
progress *Progress
|
||||
apiExe string
|
||||
apiNodeId int64
|
||||
}
|
||||
|
||||
func NewUpgrader(apiNodeId int64) *Upgrader {
|
||||
return &Upgrader{
|
||||
apiExe: apiExe(),
|
||||
progress: &Progress{Percent: 0},
|
||||
apiNodeId: apiNodeId,
|
||||
}
|
||||
}
|
||||
|
||||
func (this *Upgrader) Upgrade() error {
|
||||
sharedClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
apiNodeResp, err := sharedClient.APINodeRPC().FindEnabledAPINode(sharedClient.Context(0), &pb.FindEnabledAPINodeRequest{ApiNodeId: this.apiNodeId})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var apiNode = apiNodeResp.ApiNode
|
||||
if apiNode == nil {
|
||||
return errors.New("could not find api node with id '" + types.String(this.apiNodeId) + "'")
|
||||
}
|
||||
|
||||
apiConfig, err := configs.LoadAPIConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var newAPIConfig = apiConfig.Clone()
|
||||
newAPIConfig.RPCEndpoints = apiNode.AccessAddrs
|
||||
|
||||
rpcClient, err := rpc.NewRPCClient(newAPIConfig, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 升级边缘节点
|
||||
err = this.upgradeNodes(sharedClient.Context(0), rpcClient)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 升级NS节点
|
||||
err = this.upgradeNSNodes(sharedClient.Context(0), rpcClient)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 升级API节点
|
||||
err = this.upgradeAPINode(sharedClient.Context(0), rpcClient)
|
||||
if err != nil {
|
||||
return fmt.Errorf("upgrade api node failed: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Progress 查看升级进程
|
||||
func (this *Upgrader) Progress() *Progress {
|
||||
return this.progress
|
||||
}
|
||||
|
||||
// 升级API节点
|
||||
func (this *Upgrader) upgradeAPINode(ctx context.Context, rpcClient *rpc.RPCClient) error {
|
||||
versionResp, err := rpcClient.APINodeRPC().FindCurrentAPINodeVersion(ctx, &pb.FindCurrentAPINodeVersionRequest{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !Tea.IsTesting() /** 开发环境下允许突破此限制方便测试 **/ &&
|
||||
(stringutil.VersionCompare(versionResp.Version, "0.6.4" /** 从0.6.4开始支持 **/) < 0 || versionResp.Os != runtime.GOOS || versionResp.Arch != runtime.GOARCH) {
|
||||
return errors.New("could not upgrade api node v" + versionResp.Version + "/" + versionResp.Os + "/" + versionResp.Arch)
|
||||
}
|
||||
|
||||
// 检查本地文件版本
|
||||
canUpgrade, reason := CanUpgrade(versionResp.Version, versionResp.Os, versionResp.Arch)
|
||||
if !canUpgrade {
|
||||
return errors.New(reason)
|
||||
}
|
||||
|
||||
localVersion, err := lookupLocalVersion()
|
||||
if err != nil {
|
||||
return fmt.Errorf("lookup version failed: %w", err)
|
||||
}
|
||||
|
||||
// 检查要升级的文件
|
||||
var gzFile = this.apiExe + "." + localVersion + ".gz"
|
||||
|
||||
gzReader, err := os.Open(gzFile)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
err = func() error {
|
||||
// 压缩文件
|
||||
exeReader, err := os.Open(this.apiExe)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = exeReader.Close()
|
||||
}()
|
||||
var tmpGzFile = gzFile + ".tmp"
|
||||
gzFileWriter, err := os.OpenFile(tmpGzFile, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var gzWriter = gzip.NewWriter(gzFileWriter)
|
||||
defer func() {
|
||||
_ = gzWriter.Close()
|
||||
_ = gzFileWriter.Close()
|
||||
|
||||
_ = os.Rename(tmpGzFile, gzFile)
|
||||
}()
|
||||
_, err = io.Copy(gzWriter, exeReader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gzReader, err = os.Open(gzFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = gzReader.Close()
|
||||
}()
|
||||
|
||||
// 开始上传
|
||||
var hash = md5.New()
|
||||
var buf = make([]byte, 128*4096)
|
||||
var isFirst = true
|
||||
stat, err := gzReader.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var totalSize = stat.Size()
|
||||
if totalSize == 0 {
|
||||
_ = gzReader.Close()
|
||||
_ = os.Remove(gzFile)
|
||||
return errors.New("invalid gz file")
|
||||
}
|
||||
|
||||
var uploadedSize int64 = 0
|
||||
for {
|
||||
n, err := gzReader.Read(buf)
|
||||
if n > 0 {
|
||||
// 计算Hash
|
||||
hash.Write(buf[:n])
|
||||
|
||||
// 上传
|
||||
_, uploadErr := rpcClient.APINodeRPC().UploadAPINodeFile(rpcClient.Context(0), &pb.UploadAPINodeFileRequest{
|
||||
Filename: filepath.Base(this.apiExe),
|
||||
Sum: "",
|
||||
ChunkData: buf[:n],
|
||||
IsFirstChunk: isFirst,
|
||||
IsLastChunk: false,
|
||||
})
|
||||
if uploadErr != nil {
|
||||
return uploadErr
|
||||
}
|
||||
|
||||
// 进度
|
||||
uploadedSize += int64(n)
|
||||
this.progress = &Progress{Percent: float64(uploadedSize*100) / float64(totalSize)}
|
||||
}
|
||||
if isFirst {
|
||||
isFirst = false
|
||||
}
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
return err
|
||||
}
|
||||
if err == io.EOF {
|
||||
_, uploadErr := rpcClient.APINodeRPC().UploadAPINodeFile(rpcClient.Context(0), &pb.UploadAPINodeFileRequest{
|
||||
Filename: filepath.Base(this.apiExe),
|
||||
Sum: fmt.Sprintf("%x", hash.Sum(nil)),
|
||||
ChunkData: buf[:n],
|
||||
IsFirstChunk: isFirst,
|
||||
IsLastChunk: true,
|
||||
})
|
||||
if uploadErr != nil {
|
||||
return uploadErr
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 升级边缘节点
|
||||
func (this *Upgrader) upgradeNodes(ctx context.Context, rpcClient *rpc.RPCClient) error {
|
||||
// 本地的
|
||||
var manager = NewDeployManager()
|
||||
var localFileMap = map[string]*DeployFile{} // os_arch => *DeployFile
|
||||
for _, deployFile := range manager.LoadNodeFiles() {
|
||||
localFileMap[deployFile.OS+"_"+deployFile.Arch] = deployFile
|
||||
}
|
||||
|
||||
remoteFilesResp, err := rpcClient.APINodeRPC().FindLatestDeployFiles(ctx, &pb.FindLatestDeployFilesRequest{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var remoteFileMap = map[string]*pb.FindLatestDeployFilesResponse_DeployFile{} // os_arch => *DeployFile
|
||||
for _, nodeFile := range remoteFilesResp.NodeDeployFiles {
|
||||
remoteFileMap[nodeFile.Os+"_"+nodeFile.Arch] = nodeFile
|
||||
}
|
||||
|
||||
// 对比版本
|
||||
for key, deployFile := range localFileMap {
|
||||
remoteDeployFile, ok := remoteFileMap[key]
|
||||
if !ok || stringutil.VersionCompare(remoteDeployFile.Version, deployFile.Version) < 0 {
|
||||
err = this.uploadNodeDeployFile(ctx, rpcClient, deployFile.Path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("upload deploy file '%s' failed: %w", filepath.Base(deployFile.Path), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 升级NS节点
|
||||
func (this *Upgrader) upgradeNSNodes(ctx context.Context, rpcClient *rpc.RPCClient) error {
|
||||
// 本地的
|
||||
var manager = NewDeployManager()
|
||||
var localFileMap = map[string]*DeployFile{} // os_arch => *DeployFile
|
||||
for _, deployFile := range manager.LoadNSNodeFiles() {
|
||||
localFileMap[deployFile.OS+"_"+deployFile.Arch] = deployFile
|
||||
}
|
||||
|
||||
remoteFilesResp, err := rpcClient.APINodeRPC().FindLatestDeployFiles(ctx, &pb.FindLatestDeployFilesRequest{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var remoteFileMap = map[string]*pb.FindLatestDeployFilesResponse_DeployFile{} // os_arch => *DeployFile
|
||||
for _, nodeFile := range remoteFilesResp.NsNodeDeployFiles {
|
||||
remoteFileMap[nodeFile.Os+"_"+nodeFile.Arch] = nodeFile
|
||||
}
|
||||
|
||||
// 对比版本
|
||||
for key, deployFile := range localFileMap {
|
||||
remoteDeployFile, ok := remoteFileMap[key]
|
||||
if !ok || stringutil.VersionCompare(remoteDeployFile.Version, deployFile.Version) < 0 {
|
||||
err = this.uploadNodeDeployFile(ctx, rpcClient, deployFile.Path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("upload deploy file '%s' failed: %w", filepath.Base(deployFile.Path), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 上传节点文件
|
||||
func (this *Upgrader) uploadNodeDeployFile(ctx context.Context, rpcClient *rpc.RPCClient, path string) error {
|
||||
fp, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = fp.Close()
|
||||
}()
|
||||
|
||||
var buf = make([]byte, 128*4096)
|
||||
var isFirst = true
|
||||
|
||||
var hash = md5.New()
|
||||
|
||||
for {
|
||||
n, err := fp.Read(buf)
|
||||
if n > 0 {
|
||||
hash.Write(buf[:n])
|
||||
|
||||
_, uploadErr := rpcClient.APINodeRPC().UploadDeployFileToAPINode(ctx, &pb.UploadDeployFileToAPINodeRequest{
|
||||
Filename: filepath.Base(path),
|
||||
Sum: "",
|
||||
ChunkData: buf[:n],
|
||||
IsFirstChunk: isFirst,
|
||||
IsLastChunk: false,
|
||||
})
|
||||
if uploadErr != nil {
|
||||
return uploadErr
|
||||
}
|
||||
isFirst = false
|
||||
}
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
|
||||
_, uploadErr := rpcClient.APINodeRPC().UploadDeployFileToAPINode(ctx, &pb.UploadDeployFileToAPINodeRequest{
|
||||
Filename: filepath.Base(path),
|
||||
Sum: fmt.Sprintf("%x", hash.Sum(nil)),
|
||||
ChunkData: nil,
|
||||
IsFirstChunk: false,
|
||||
IsLastChunk: true,
|
||||
})
|
||||
if uploadErr != nil {
|
||||
return uploadErr
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
22
internal/utils/apinodeutils/upgrader_test.go
Normal file
22
internal/utils/apinodeutils/upgrader_test.go
Normal file
@@ -0,0 +1,22 @@
|
||||
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package apinodeutils_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils/apinodeutils"
|
||||
_ "github.com/iwind/TeaGo/bootstrap"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUpgrader_CanUpgrade(t *testing.T) {
|
||||
t.Log(apinodeutils.CanUpgrade("0.6.3", runtime.GOOS, runtime.GOARCH))
|
||||
}
|
||||
|
||||
func TestUpgrader_Upgrade(t *testing.T) {
|
||||
var upgrader = apinodeutils.NewUpgrader(1)
|
||||
err := upgrader.Upgrade()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
77
internal/utils/apinodeutils/utils.go
Normal file
77
internal/utils/apinodeutils/utils.go
Normal file
@@ -0,0 +1,77 @@
|
||||
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package apinodeutils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func CanUpgrade(apiVersion string, osName string, arch string) (canUpgrade bool, reason string) {
|
||||
if len(apiVersion) == 0 {
|
||||
return false, "current api version should not be empty"
|
||||
}
|
||||
|
||||
if stringutil.VersionCompare(apiVersion, "0.6.4") < 0 {
|
||||
return false, "api node version must greater than or equal to 0.6.4"
|
||||
}
|
||||
|
||||
if osName != runtime.GOOS {
|
||||
return false, "os not match: " + osName
|
||||
}
|
||||
if arch != runtime.GOARCH {
|
||||
return false, "arch not match: " + arch
|
||||
}
|
||||
|
||||
stat, err := os.Stat(apiExe())
|
||||
if err != nil {
|
||||
return false, "stat error: " + err.Error()
|
||||
}
|
||||
if stat.IsDir() {
|
||||
return false, "is directory"
|
||||
}
|
||||
|
||||
localVersion, err := lookupLocalVersion()
|
||||
if err != nil {
|
||||
return false, "lookup version failed: " + err.Error()
|
||||
}
|
||||
if localVersion != teaconst.APINodeVersion {
|
||||
return false, "not newest api node"
|
||||
}
|
||||
if stringutil.VersionCompare(localVersion, apiVersion) <= 0 {
|
||||
return false, "need not upgrade, local '" + localVersion + "' vs remote '" + apiVersion + "'"
|
||||
}
|
||||
|
||||
return true, ""
|
||||
}
|
||||
|
||||
func lookupLocalVersion() (string, error) {
|
||||
var cmd = exec.Command(apiExe(), "-V")
|
||||
var output = &bytes.Buffer{}
|
||||
cmd.Stdout = output
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var localVersion = strings.TrimSpace(output.String())
|
||||
|
||||
// 检查版本号
|
||||
var reg = regexp.MustCompile(`^[\d.]+$`)
|
||||
if !reg.MatchString(localVersion) {
|
||||
return "", errors.New("lookup version failed: " + localVersion)
|
||||
}
|
||||
|
||||
return localVersion, nil
|
||||
}
|
||||
|
||||
func apiExe() string {
|
||||
return Tea.Root + "/edge-api/bin/edge-api"
|
||||
}
|
||||
12
internal/utils/dateutils/utils.go
Normal file
12
internal/utils/dateutils/utils.go
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package dateutils
|
||||
|
||||
// SplitYmd 分隔Ymd格式的日期
|
||||
// Ymd => Y-m-d
|
||||
func SplitYmd(day string) string {
|
||||
if len(day) != 8 {
|
||||
return day
|
||||
}
|
||||
return day[:4] + "-" + day[4:6] + "-" + day[6:]
|
||||
}
|
||||
12
internal/utils/email.go
Normal file
12
internal/utils/email.go
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package utils
|
||||
|
||||
import "regexp"
|
||||
|
||||
var emailReg = regexp.MustCompile(`(?i)^[a-z\d]+([._+-]*[a-z\d]+)*@([a-z\d]+[a-z\d-]*[a-z\d]+\.)+[a-z\d]+$`)
|
||||
|
||||
// ValidateEmail 校验电子邮箱格式
|
||||
func ValidateEmail(email string) bool {
|
||||
return emailReg.MatchString(email)
|
||||
}
|
||||
22
internal/utils/email_test.go
Normal file
22
internal/utils/email_test.go
Normal file
@@ -0,0 +1,22 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package utils_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
|
||||
"github.com/iwind/TeaGo/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestValidateEmail(t *testing.T) {
|
||||
var a = assert.NewAssertion(t)
|
||||
a.IsTrue(utils.ValidateEmail("aaaa@gmail.com"))
|
||||
a.IsTrue(utils.ValidateEmail("a.b@gmail.com"))
|
||||
a.IsTrue(utils.ValidateEmail("a.b.c.d@gmail.com"))
|
||||
a.IsTrue(utils.ValidateEmail("aaaa@gmail.com.cn"))
|
||||
a.IsTrue(utils.ValidateEmail("hello.world.123@gmail.123.com"))
|
||||
a.IsTrue(utils.ValidateEmail("10000@qq.com"))
|
||||
a.IsFalse(utils.ValidateEmail("aaaa.@gmail.com"))
|
||||
a.IsFalse(utils.ValidateEmail("aaaa@gmail"))
|
||||
a.IsFalse(utils.ValidateEmail("aaaa@123"))
|
||||
}
|
||||
162
internal/utils/exec/cmd.go
Normal file
162
internal/utils/exec/cmd.go
Normal file
@@ -0,0 +1,162 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package executils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Cmd struct {
|
||||
name string
|
||||
args []string
|
||||
env []string
|
||||
dir string
|
||||
|
||||
ctx context.Context
|
||||
timeout time.Duration
|
||||
cancelFunc func()
|
||||
|
||||
captureStdout bool
|
||||
captureStderr bool
|
||||
|
||||
stdout *bytes.Buffer
|
||||
stderr *bytes.Buffer
|
||||
|
||||
rawCmd *exec.Cmd
|
||||
}
|
||||
|
||||
func NewCmd(name string, args ...string) *Cmd {
|
||||
return &Cmd{
|
||||
name: name,
|
||||
args: args,
|
||||
}
|
||||
}
|
||||
|
||||
func NewTimeoutCmd(timeout time.Duration, name string, args ...string) *Cmd {
|
||||
return (&Cmd{
|
||||
name: name,
|
||||
args: args,
|
||||
}).WithTimeout(timeout)
|
||||
}
|
||||
|
||||
func (this *Cmd) WithTimeout(timeout time.Duration) *Cmd {
|
||||
this.timeout = timeout
|
||||
|
||||
ctx, cancelFunc := context.WithTimeout(context.Background(), timeout)
|
||||
this.ctx = ctx
|
||||
this.cancelFunc = cancelFunc
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
func (this *Cmd) WithStdout() *Cmd {
|
||||
this.captureStdout = true
|
||||
return this
|
||||
}
|
||||
|
||||
func (this *Cmd) WithStderr() *Cmd {
|
||||
this.captureStderr = true
|
||||
return this
|
||||
}
|
||||
|
||||
func (this *Cmd) WithEnv(env []string) *Cmd {
|
||||
this.env = env
|
||||
return this
|
||||
}
|
||||
|
||||
func (this *Cmd) WithDir(dir string) *Cmd {
|
||||
this.dir = dir
|
||||
return this
|
||||
}
|
||||
|
||||
func (this *Cmd) Start() error {
|
||||
var cmd = this.compose()
|
||||
return cmd.Start()
|
||||
}
|
||||
|
||||
func (this *Cmd) Wait() error {
|
||||
var cmd = this.compose()
|
||||
return cmd.Wait()
|
||||
}
|
||||
|
||||
func (this *Cmd) Run() error {
|
||||
if this.cancelFunc != nil {
|
||||
defer this.cancelFunc()
|
||||
}
|
||||
|
||||
var cmd = this.compose()
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
func (this *Cmd) RawStdout() string {
|
||||
if this.stdout != nil {
|
||||
return this.stdout.String()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (this *Cmd) Stdout() string {
|
||||
return strings.TrimSpace(this.RawStdout())
|
||||
}
|
||||
|
||||
func (this *Cmd) RawStderr() string {
|
||||
if this.stderr != nil {
|
||||
return this.stderr.String()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (this *Cmd) Stderr() string {
|
||||
return strings.TrimSpace(this.RawStderr())
|
||||
}
|
||||
|
||||
func (this *Cmd) String() string {
|
||||
if this.rawCmd != nil {
|
||||
return this.rawCmd.String()
|
||||
}
|
||||
var newCmd = exec.Command(this.name, this.args...)
|
||||
return newCmd.String()
|
||||
}
|
||||
|
||||
func (this *Cmd) Process() *os.Process {
|
||||
if this.rawCmd != nil {
|
||||
return this.rawCmd.Process
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *Cmd) compose() *exec.Cmd {
|
||||
if this.rawCmd != nil {
|
||||
return this.rawCmd
|
||||
}
|
||||
|
||||
if this.ctx != nil {
|
||||
this.rawCmd = exec.CommandContext(this.ctx, this.name, this.args...)
|
||||
} else {
|
||||
this.rawCmd = exec.Command(this.name, this.args...)
|
||||
}
|
||||
|
||||
if this.env != nil {
|
||||
this.rawCmd.Env = this.env
|
||||
}
|
||||
|
||||
if len(this.dir) > 0 {
|
||||
this.rawCmd.Dir = this.dir
|
||||
}
|
||||
|
||||
if this.captureStdout {
|
||||
this.stdout = &bytes.Buffer{}
|
||||
this.rawCmd.Stdout = this.stdout
|
||||
}
|
||||
if this.captureStderr {
|
||||
this.stderr = &bytes.Buffer{}
|
||||
this.rawCmd.Stderr = this.stderr
|
||||
}
|
||||
|
||||
return this.rawCmd
|
||||
}
|
||||
61
internal/utils/exec/cmd_test.go
Normal file
61
internal/utils/exec/cmd_test.go
Normal file
@@ -0,0 +1,61 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package executils_test
|
||||
|
||||
import (
|
||||
executils "github.com/TeaOSLab/EdgeAdmin/internal/utils/exec"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNewTimeoutCmd_Sleep(t *testing.T) {
|
||||
var cmd = executils.NewTimeoutCmd(1*time.Second, "sleep", "3")
|
||||
cmd.WithStdout()
|
||||
cmd.WithStderr()
|
||||
err := cmd.Run()
|
||||
t.Log("error:", err)
|
||||
t.Log("stdout:", cmd.Stdout())
|
||||
t.Log("stderr:", cmd.Stderr())
|
||||
}
|
||||
|
||||
func TestNewTimeoutCmd_Echo(t *testing.T) {
|
||||
var cmd = executils.NewTimeoutCmd(10*time.Second, "echo", "-n", "hello")
|
||||
cmd.WithStdout()
|
||||
cmd.WithStderr()
|
||||
err := cmd.Run()
|
||||
t.Log("error:", err)
|
||||
t.Log("stdout:", cmd.Stdout())
|
||||
t.Log("stderr:", cmd.Stderr())
|
||||
}
|
||||
|
||||
func TestNewTimeoutCmd_Echo2(t *testing.T) {
|
||||
var cmd = executils.NewCmd("echo", "hello")
|
||||
cmd.WithStdout()
|
||||
cmd.WithStderr()
|
||||
err := cmd.Run()
|
||||
t.Log("error:", err)
|
||||
t.Log("stdout:", cmd.Stdout())
|
||||
t.Log("raw stdout:", cmd.RawStdout())
|
||||
t.Log("stderr:", cmd.Stderr())
|
||||
t.Log("raw stderr:", cmd.RawStderr())
|
||||
}
|
||||
|
||||
func TestNewTimeoutCmd_Echo3(t *testing.T) {
|
||||
var cmd = executils.NewCmd("echo", "-n", "hello")
|
||||
err := cmd.Run()
|
||||
t.Log("error:", err)
|
||||
t.Log("stdout:", cmd.Stdout())
|
||||
t.Log("stderr:", cmd.Stderr())
|
||||
}
|
||||
|
||||
func TestCmd_Process(t *testing.T) {
|
||||
var cmd = executils.NewCmd("echo", "-n", "hello")
|
||||
err := cmd.Run()
|
||||
t.Log("error:", err)
|
||||
t.Log(cmd.Process())
|
||||
}
|
||||
|
||||
func TestNewTimeoutCmd_String(t *testing.T) {
|
||||
var cmd = executils.NewCmd("echo", "-n", "hello")
|
||||
t.Log("stdout:", cmd.String())
|
||||
}
|
||||
58
internal/utils/exec/look_linux.go
Normal file
58
internal/utils/exec/look_linux.go
Normal file
@@ -0,0 +1,58 @@
|
||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
//go:build linux
|
||||
|
||||
package executils
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/unix"
|
||||
"io/fs"
|
||||
"os"
|
||||
"os/exec"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// LookPath customize our LookPath() function, to work in broken $PATH environment variable
|
||||
func LookPath(file string) (string, error) {
|
||||
result, err := exec.LookPath(file)
|
||||
if err == nil && len(result) > 0 {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// add common dirs contains executable files these may be excluded in $PATH environment variable
|
||||
var binPaths = []string{
|
||||
"/usr/sbin",
|
||||
"/usr/bin",
|
||||
"/usr/local/sbin",
|
||||
"/usr/local/bin",
|
||||
}
|
||||
|
||||
for _, binPath := range binPaths {
|
||||
var fullPath = binPath + string(os.PathSeparator) + file
|
||||
|
||||
stat, err := os.Stat(fullPath)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if stat.IsDir() {
|
||||
return "", syscall.EISDIR
|
||||
}
|
||||
|
||||
var mode = stat.Mode()
|
||||
if mode.IsDir() {
|
||||
return "", syscall.EISDIR
|
||||
}
|
||||
err = syscall.Faccessat(unix.AT_FDCWD, fullPath, unix.X_OK, unix.AT_EACCESS)
|
||||
if err == nil || (err != syscall.ENOSYS && err != syscall.EPERM) {
|
||||
return fullPath, err
|
||||
}
|
||||
if mode&0111 != 0 {
|
||||
return fullPath, nil
|
||||
}
|
||||
return "", fs.ErrPermission
|
||||
}
|
||||
|
||||
return "", &exec.Error{
|
||||
Name: file,
|
||||
Err: exec.ErrNotFound,
|
||||
}
|
||||
}
|
||||
10
internal/utils/exec/look_others.go
Normal file
10
internal/utils/exec/look_others.go
Normal file
@@ -0,0 +1,10 @@
|
||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
//go:build !linux
|
||||
|
||||
package executils
|
||||
|
||||
import "os/exec"
|
||||
|
||||
func LookPath(file string) (string, error) {
|
||||
return exec.LookPath(file)
|
||||
}
|
||||
29
internal/utils/firewall.go
Normal file
29
internal/utils/firewall.go
Normal file
@@ -0,0 +1,29 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
executils "github.com/TeaOSLab/EdgeAdmin/internal/utils/exec"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func AddPortsToFirewall(ports []int) {
|
||||
for _, port := range ports {
|
||||
// Linux
|
||||
if runtime.GOOS == "linux" {
|
||||
// firewalld
|
||||
firewallCmd, _ := executils.LookPath("firewall-cmd")
|
||||
if len(firewallCmd) > 0 {
|
||||
err := exec.Command(firewallCmd, "--add-port="+types.String(port)+"/tcp").Run()
|
||||
if err == nil {
|
||||
logs.Println("ADMIN_NODE", "add port '"+types.String(port)+"' to firewalld")
|
||||
|
||||
_ = exec.Command(firewallCmd, "--add-port="+types.String(port)+"/tcp", "--permanent").Run()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,43 +1,174 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"math/big"
|
||||
"bytes"
|
||||
"errors"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/iputils"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"net"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 将IP转换为整型
|
||||
func IP2Long(ip string) uint64 {
|
||||
s := net.ParseIP(ip)
|
||||
if len(s) != 16 {
|
||||
return 0
|
||||
// ParseIPValue 解析IP值
|
||||
func ParseIPValue(value string) (newValue string, ipFrom string, ipTo string, ok bool) {
|
||||
if len(value) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if strings.Contains(ip, ":") { // IPv6
|
||||
bigInt := big.NewInt(0)
|
||||
bigInt.SetBytes(s.To16())
|
||||
return bigInt.Uint64()
|
||||
newValue = value
|
||||
|
||||
// ip1-ip2
|
||||
if strings.Contains(value, "-") {
|
||||
var pieces = strings.Split(value, "-")
|
||||
if len(pieces) != 2 {
|
||||
return
|
||||
}
|
||||
|
||||
ipFrom = strings.TrimSpace(pieces[0])
|
||||
ipTo = strings.TrimSpace(pieces[1])
|
||||
|
||||
if !iputils.IsValid(ipFrom) || !iputils.IsValid(ipTo) {
|
||||
return
|
||||
}
|
||||
|
||||
if !iputils.IsSameVersion(ipFrom, ipTo) {
|
||||
return
|
||||
}
|
||||
|
||||
if iputils.CompareIP(ipFrom, ipTo) > 0 {
|
||||
ipFrom, ipTo = ipTo, ipFrom
|
||||
newValue = ipFrom + "-" + ipTo
|
||||
}
|
||||
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
return uint64(binary.BigEndian.Uint32(s.To4()))
|
||||
|
||||
// ip/mask
|
||||
if strings.Contains(value, "/") {
|
||||
cidr, err := iputils.ParseCIDR(value)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return newValue, cidr.From().String(), cidr.To().String(), true
|
||||
}
|
||||
|
||||
// single value
|
||||
if iputils.IsValid(value) {
|
||||
ipFrom = value
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 判断是否为IPv4
|
||||
func IsIPv4(ip string) bool {
|
||||
if !regexp.MustCompile(`^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$`).MatchString(ip) {
|
||||
return false
|
||||
// ExtractIP 分解IP
|
||||
// 只支持D段掩码的CIDR
|
||||
// 最多只记录255个值
|
||||
func ExtractIP(ipStrings string) ([]string, error) {
|
||||
ipStrings = strings.ReplaceAll(ipStrings, " ", "")
|
||||
|
||||
// CIDR
|
||||
if strings.Contains(ipStrings, "/") {
|
||||
_, cidrNet, err := net.ParseCIDR(ipStrings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var index = strings.Index(ipStrings, "/")
|
||||
var ipFrom = ipStrings[:index]
|
||||
var bits = types.Int(ipStrings[index+1:])
|
||||
if bits < 24 {
|
||||
return nil, errors.New("CIDR bits should be greater than 24")
|
||||
}
|
||||
|
||||
var ipv4 = net.ParseIP(ipFrom).To4()
|
||||
if len(ipv4) == 0 {
|
||||
return nil, errors.New("support IPv4 only")
|
||||
}
|
||||
|
||||
var result = []string{}
|
||||
ipv4[3] = 0 // 从0开始
|
||||
for i := 0; i <= 255; i++ {
|
||||
if cidrNet.Contains(ipv4) {
|
||||
result = append(result, ipv4.String())
|
||||
}
|
||||
ipv4 = NextIP(ipv4)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
if IP2Long(ip) == 0 {
|
||||
return false
|
||||
|
||||
// IP Range
|
||||
if strings.Contains(ipStrings, "-") {
|
||||
var index = strings.Index(ipStrings, "-")
|
||||
var ipFromString = ipStrings[:index]
|
||||
var ipToString = ipStrings[index+1:]
|
||||
|
||||
var ipFrom = net.ParseIP(ipFromString).To4()
|
||||
if len(ipFrom) == 0 {
|
||||
return nil, errors.New("invalid ip '" + ipFromString + "'")
|
||||
}
|
||||
|
||||
var ipTo = net.ParseIP(ipToString).To4()
|
||||
if len(ipTo) == 0 {
|
||||
return nil, errors.New("invalid ip '" + ipToString + "'")
|
||||
}
|
||||
|
||||
if bytes.Compare(ipFrom, ipTo) > 0 {
|
||||
ipFrom, ipTo = ipTo, ipFrom
|
||||
}
|
||||
|
||||
var result = []string{}
|
||||
for i := 0; i < 255; i++ {
|
||||
if bytes.Compare(ipFrom, ipTo) > 0 {
|
||||
break
|
||||
}
|
||||
result = append(result, ipFrom.String())
|
||||
ipFrom = NextIP(ipFrom)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
return true
|
||||
|
||||
return []string{ipStrings}, nil
|
||||
}
|
||||
|
||||
// 判断是否为IPv6
|
||||
func IsIPv6(ip string) bool {
|
||||
if !strings.Contains(ip, ":") {
|
||||
// NextIP IP增加1
|
||||
func NextIP(prevIP net.IP) net.IP {
|
||||
var ip = make(net.IP, len(prevIP))
|
||||
copy(ip, prevIP)
|
||||
var index = len(ip) - 1
|
||||
for {
|
||||
if ip[index] == 255 {
|
||||
ip[index] = 0
|
||||
index--
|
||||
if index < 0 {
|
||||
break
|
||||
}
|
||||
} else {
|
||||
ip[index]++
|
||||
break
|
||||
}
|
||||
}
|
||||
return ip
|
||||
}
|
||||
|
||||
// IsLocalIP 判断是否为本地IP
|
||||
// ip 是To4()或者To16()的结果
|
||||
func IsLocalIP(ip net.IP) bool {
|
||||
if ip == nil {
|
||||
return false
|
||||
}
|
||||
return len(net.ParseIP(ip)) == net.IPv6len
|
||||
|
||||
if ip[0] == 127 ||
|
||||
ip[0] == 10 ||
|
||||
(ip[0] == 172 && ip[1]&0xf0 == 16) ||
|
||||
(ip[0] == 192 && ip[1] == 168) {
|
||||
return true
|
||||
}
|
||||
|
||||
if ip.String() == "::1" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -1,85 +1,30 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIP2Long(t *testing.T) {
|
||||
for _, ip := range []string{
|
||||
"0.0.0.1",
|
||||
"0.0.0.2",
|
||||
"127.0.0.1",
|
||||
"192.0.0.2",
|
||||
"255.255.255.255",
|
||||
"2001:db8:0:1::101",
|
||||
"2001:db8:0:1::102",
|
||||
"2406:8c00:0:3409:133:18:203:0",
|
||||
"2406:8c00:0:3409:133:18:203:158",
|
||||
"2406:8c00:0:3409:133:18:203:159",
|
||||
"2406:8c00:0:3409:133:18:203:160",
|
||||
} {
|
||||
t.Log(ip, " -> ", IP2Long(ip))
|
||||
func TestExtractIP(t *testing.T) {
|
||||
t.Log(ExtractIP("192.168.1.100"))
|
||||
}
|
||||
|
||||
func TestExtractIP_CIDR(t *testing.T) {
|
||||
t.Log(ExtractIP("192.168.2.100/24"))
|
||||
}
|
||||
|
||||
func TestExtractIP_Range(t *testing.T) {
|
||||
t.Log(ExtractIP("192.168.2.100 - 192.168.4.2"))
|
||||
}
|
||||
|
||||
func TestNextIP(t *testing.T) {
|
||||
for _, ip := range []string{"192.168.1.1", "0.0.0.0", "255.255.255.255", "192.168.2.255", "192.168.255.255"} {
|
||||
t.Log(ip+":", NextIP(net.ParseIP(ip).To4()))
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsIPv4(t *testing.T) {
|
||||
type testIP struct {
|
||||
ip string
|
||||
ok bool
|
||||
}
|
||||
for _, item := range []testIP{
|
||||
{
|
||||
ip: "1",
|
||||
ok: false,
|
||||
},
|
||||
{
|
||||
ip: "192.168.0.1",
|
||||
ok: true,
|
||||
},
|
||||
{
|
||||
ip: "1.1.0.1",
|
||||
ok: true,
|
||||
},
|
||||
{
|
||||
ip: "255.255.255.255",
|
||||
ok: true,
|
||||
},
|
||||
{
|
||||
ip: "192.168.0.1233",
|
||||
ok: false,
|
||||
},
|
||||
} {
|
||||
if IsIPv4(item.ip) != item.ok {
|
||||
t.Fatal(item.ip, "should be", item.ok)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsIPv6(t *testing.T) {
|
||||
type testIP struct {
|
||||
ip string
|
||||
ok bool
|
||||
}
|
||||
for _, item := range []testIP{
|
||||
{
|
||||
ip: "1",
|
||||
ok: false,
|
||||
},
|
||||
{
|
||||
ip: "2406:8c00:0:3409:133:18:203:158",
|
||||
ok: true,
|
||||
},
|
||||
{
|
||||
ip: "2406::8c00:0:3409:133:18:203:158",
|
||||
ok: false,
|
||||
},
|
||||
{
|
||||
ip: "2001:db8:0:1::101",
|
||||
ok: true,
|
||||
},
|
||||
} {
|
||||
if IsIPv6(item.ip) != item.ok {
|
||||
t.Fatal(item.ip, "should be", item.ok)
|
||||
}
|
||||
}
|
||||
func TestNextIP_Copy(t *testing.T) {
|
||||
var ip = net.ParseIP("192.168.1.100")
|
||||
var nextIP = NextIP(ip)
|
||||
t.Log(ip, nextIP)
|
||||
}
|
||||
|
||||
60
internal/utils/json.go
Normal file
60
internal/utils/json.go
Normal file
@@ -0,0 +1,60 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// JSONClone 使用JSON克隆对象
|
||||
func JSONClone(v interface{}) (interface{}, error) {
|
||||
data, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var nv = reflect.New(reflect.TypeOf(v).Elem()).Interface()
|
||||
err = json.Unmarshal(data, nv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nv, nil
|
||||
}
|
||||
|
||||
// JSONIsNull 判断JSON数据是否为null
|
||||
func JSONIsNull(jsonData []byte) bool {
|
||||
return len(jsonData) == 0 || bytes.Equal(jsonData, []byte("null"))
|
||||
}
|
||||
|
||||
// JSONDecodeConfig 解码并重新编码
|
||||
// 是为了去除原有JSON中不需要的数据
|
||||
func JSONDecodeConfig(data []byte, ptr any) (encodeJSON []byte, err error) {
|
||||
err = json.Unmarshal(data, ptr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
encodeJSON, err = json.Marshal(ptr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// validate config
|
||||
if ptr != nil {
|
||||
config, ok := ptr.(interface {
|
||||
Init() error
|
||||
})
|
||||
if ok {
|
||||
initErr := config.Init()
|
||||
if initErr != nil {
|
||||
err = errors.New("validate config failed: " + initErr.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
35
internal/utils/json_test.go
Normal file
35
internal/utils/json_test.go
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package utils_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
|
||||
"github.com/iwind/TeaGo/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestJSONClone(t *testing.T) {
|
||||
type A struct {
|
||||
B int `json:"b"`
|
||||
C string `json:"c"`
|
||||
}
|
||||
|
||||
var a = &A{B: 123, C: "456"}
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
c, err := utils.JSONClone(a)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Logf("%p, %#v", c, c)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func TestJSONIsNull(t *testing.T) {
|
||||
var a = assert.NewAssertion(t)
|
||||
a.IsTrue(utils.JSONIsNull(nil))
|
||||
a.IsTrue(utils.JSONIsNull([]byte{}))
|
||||
a.IsTrue(utils.JSONIsNull([]byte("null")))
|
||||
a.IsFalse(utils.JSONIsNull([]byte{1, 2, 3}))
|
||||
}
|
||||
@@ -1,28 +1,81 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/miekg/dns"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// 获取CNAME
|
||||
func LookupCNAME(host string) (string, error) {
|
||||
config, err := dns.ClientConfigFromFile("/etc/resolv.conf")
|
||||
if err != nil {
|
||||
return "", err
|
||||
var sharedDNSClient *dns.Client
|
||||
var sharedDNSConfig *dns.ClientConfig
|
||||
|
||||
func init() {
|
||||
if !teaconst.IsMain {
|
||||
return
|
||||
}
|
||||
|
||||
c := new(dns.Client)
|
||||
m := new(dns.Msg)
|
||||
config, err := dns.ClientConfigFromFile("/etc/resolv.conf")
|
||||
if err != nil {
|
||||
logs.Println("ERROR: configure dns client failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
sharedDNSConfig = config
|
||||
sharedDNSClient = &dns.Client{}
|
||||
}
|
||||
|
||||
// LookupCNAME 获取CNAME
|
||||
func LookupCNAME(host string) (string, error) {
|
||||
var m = new(dns.Msg)
|
||||
|
||||
m.SetQuestion(host+".", dns.TypeCNAME)
|
||||
m.RecursionDesired = true
|
||||
r, _, err := c.Exchange(m, config.Servers[0]+":"+config.Port)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(r.Answer) == 0 {
|
||||
return "", nil
|
||||
|
||||
var lastErr error
|
||||
var success = false
|
||||
var result = ""
|
||||
|
||||
var serverAddrs = sharedDNSConfig.Servers
|
||||
|
||||
{
|
||||
var publicDNSHosts = []string{"8.8.8.8" /** Google **/, "8.8.4.4" /** Google **/}
|
||||
for _, publicDNSHost := range publicDNSHosts {
|
||||
if !lists.ContainsString(serverAddrs, publicDNSHost) {
|
||||
serverAddrs = append(serverAddrs, publicDNSHost)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return r.Answer[0].(*dns.CNAME).Target, nil
|
||||
var wg = &sync.WaitGroup{}
|
||||
|
||||
for _, serverAddr := range serverAddrs {
|
||||
wg.Add(1)
|
||||
|
||||
go func(serverAddr string) {
|
||||
defer wg.Done()
|
||||
r, _, err := sharedDNSClient.Exchange(m, configutils.QuoteIP(serverAddr)+":"+sharedDNSConfig.Port)
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
return
|
||||
}
|
||||
|
||||
success = true
|
||||
|
||||
if len(r.Answer) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
result = r.Answer[0].(*dns.CNAME).Target
|
||||
}(serverAddr)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
if success {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
return "", lastErr
|
||||
}
|
||||
|
||||
15
internal/utils/lookup_test.go
Normal file
15
internal/utils/lookup_test.go
Normal file
@@ -0,0 +1,15 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package utils_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLookupCNAME(t *testing.T) {
|
||||
for _, domain := range []string{"www.yun4s.cn", "example.com"} {
|
||||
result, err := utils.LookupCNAME(domain)
|
||||
t.Log(domain, "=>", result, err)
|
||||
}
|
||||
}
|
||||
28
internal/utils/nodelogutils/utils.go
Normal file
28
internal/utils/nodelogutils/utils.go
Normal file
@@ -0,0 +1,28 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
//go:build !plus
|
||||
|
||||
package nodelogutils
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
// FindCommonTags 查找常用的标签
|
||||
func FindNodeCommonTags(langCode langs.LangCode) []maps.Map {
|
||||
return []maps.Map{
|
||||
{
|
||||
"name": langs.Message(langCode, codes.Log_TagListener),
|
||||
"code": "LISTENER",
|
||||
},
|
||||
{
|
||||
"name": langs.Message(langCode, codes.Log_TagWAF),
|
||||
"code": "WAF",
|
||||
},
|
||||
{
|
||||
"name": langs.Message(langCode, codes.Log_TagAccessLog),
|
||||
"code": "ACCESS_LOG",
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,10 @@ package numberutils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func FormatInt64(value int64) string {
|
||||
@@ -13,38 +16,151 @@ func FormatInt(value int) string {
|
||||
return strconv.Itoa(value)
|
||||
}
|
||||
|
||||
func Pow1024(n int) int64 {
|
||||
if n <= 0 {
|
||||
return 1
|
||||
}
|
||||
if n == 1 {
|
||||
return 1024
|
||||
}
|
||||
return Pow1024(n-1) * 1024
|
||||
}
|
||||
|
||||
func FormatBytes(bytes int64) string {
|
||||
if bytes < 1024 {
|
||||
if bytes < Pow1024(1) {
|
||||
return FormatInt64(bytes) + "B"
|
||||
} else if bytes < 1024*1024 {
|
||||
return fmt.Sprintf("%.2fKB", float64(bytes)/1024)
|
||||
} else if bytes < 1024*1024*1024 {
|
||||
return fmt.Sprintf("%.2fMB", float64(bytes)/1024/1024)
|
||||
} else if bytes < 1024*1024*1024*1024 {
|
||||
return fmt.Sprintf("%.2fGB", float64(bytes)/1024/1024/1024)
|
||||
} else if bytes < 1024*1024*1024*1024*1024 {
|
||||
return fmt.Sprintf("%.2fTB", float64(bytes)/1024/1024/1024/1024)
|
||||
} else if bytes < 1024*1024*1024*1024*1024*1024 {
|
||||
return fmt.Sprintf("%.2fPB", float64(bytes)/1024/1024/1024/1024/1024)
|
||||
} else if bytes < Pow1024(2) {
|
||||
return TrimZeroSuffix(fmt.Sprintf("%.2fKiB", float64(bytes)/float64(Pow1024(1))))
|
||||
} else if bytes < Pow1024(3) {
|
||||
return TrimZeroSuffix(fmt.Sprintf("%.2fMiB", float64(bytes)/float64(Pow1024(2))))
|
||||
} else if bytes < Pow1024(4) {
|
||||
return TrimZeroSuffix(fmt.Sprintf("%.2fGiB", float64(bytes)/float64(Pow1024(3))))
|
||||
} else if bytes < Pow1024(5) {
|
||||
return TrimZeroSuffix(fmt.Sprintf("%.2fTiB", float64(bytes)/float64(Pow1024(4))))
|
||||
} else if bytes < Pow1024(6) {
|
||||
return TrimZeroSuffix(fmt.Sprintf("%.2fPiB", float64(bytes)/float64(Pow1024(5))))
|
||||
} else {
|
||||
return fmt.Sprintf("%.2fEB", float64(bytes)/1024/1024/1024/1024/1024/1024)
|
||||
return TrimZeroSuffix(fmt.Sprintf("%.2fEiB", float64(bytes)/float64(Pow1024(6))))
|
||||
}
|
||||
}
|
||||
|
||||
func FormatBits(bits int64) string {
|
||||
if bits < 1000 {
|
||||
return FormatInt64(bits) + "B"
|
||||
} else if bits < 1000*1000 {
|
||||
return fmt.Sprintf("%.2fKB", float64(bits)/1000)
|
||||
} else if bits < 1000*1000*1000 {
|
||||
return fmt.Sprintf("%.2fMB", float64(bits)/1000/1000)
|
||||
} else if bits < 1000*1000*1000*1000 {
|
||||
return fmt.Sprintf("%.2fGB", float64(bits)/1000/1000/1000)
|
||||
} else if bits < 1000*1000*1000*1000*1000 {
|
||||
return fmt.Sprintf("%.2fTB", float64(bits)/1000/1000/1000/1000)
|
||||
} else if bits < 1000*1000*1000*1000*1000*1000 {
|
||||
return fmt.Sprintf("%.2fPB", float64(bits)/1000/1000/1000/1000/1000)
|
||||
if bits < Pow1024(1) {
|
||||
return FormatInt64(bits) + "bps"
|
||||
} else if bits < Pow1024(2) {
|
||||
return TrimZeroSuffix(fmt.Sprintf("%.4fKbps", float64(bits)/float64(Pow1024(1))))
|
||||
} else if bits < Pow1024(3) {
|
||||
return TrimZeroSuffix(fmt.Sprintf("%.4fMbps", float64(bits)/float64(Pow1024(2))))
|
||||
} else if bits < Pow1024(4) {
|
||||
return TrimZeroSuffix(fmt.Sprintf("%.4fGbps", float64(bits)/float64(Pow1024(3))))
|
||||
} else if bits < Pow1024(5) {
|
||||
return TrimZeroSuffix(fmt.Sprintf("%.4fTbps", float64(bits)/float64(Pow1024(4))))
|
||||
} else if bits < Pow1024(6) {
|
||||
return TrimZeroSuffix(fmt.Sprintf("%.4fPbps", float64(bits)/float64(Pow1024(5))))
|
||||
} else {
|
||||
return fmt.Sprintf("%.2fEB", float64(bits)/1000/1000/1000/1000/1000/1000)
|
||||
return TrimZeroSuffix(fmt.Sprintf("%.4fEbps", float64(bits)/float64(Pow1024(6))))
|
||||
}
|
||||
}
|
||||
|
||||
func FormatCount(count int64) string {
|
||||
if count < 1000 {
|
||||
return types.String(count)
|
||||
}
|
||||
if count < 1000*1000 {
|
||||
return fmt.Sprintf("%.1fK", float32(count)/1000)
|
||||
}
|
||||
if count < 1000*1000*1000 {
|
||||
return fmt.Sprintf("%.1fM", float32(count)/1000/1000)
|
||||
}
|
||||
return fmt.Sprintf("%.1fB", float32(count)/1000/1000/1000)
|
||||
}
|
||||
|
||||
func FormatFloat(f any, decimal int) string {
|
||||
if f == nil {
|
||||
return ""
|
||||
}
|
||||
switch x := f.(type) {
|
||||
case float32, float64:
|
||||
var s = fmt.Sprintf("%."+types.String(decimal)+"f", x)
|
||||
|
||||
// 分隔
|
||||
var dotIndex = strings.Index(s, ".")
|
||||
if dotIndex > 0 {
|
||||
var d = s[:dotIndex]
|
||||
var f2 = s[dotIndex:]
|
||||
f2 = strings.TrimRight(strings.TrimRight(f2, "0"), ".")
|
||||
return formatDigit(d) + f2
|
||||
}
|
||||
|
||||
return s
|
||||
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
|
||||
return formatDigit(types.String(x))
|
||||
case string:
|
||||
return x
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func FormatFloat2(f any) string {
|
||||
return FormatFloat(f, 2)
|
||||
}
|
||||
|
||||
// PadFloatZero 为浮点型数字字符串填充足够的0
|
||||
func PadFloatZero(s string, countZero int) string {
|
||||
if countZero <= 0 {
|
||||
return s
|
||||
}
|
||||
if len(s) == 0 {
|
||||
s = "0"
|
||||
}
|
||||
var index = strings.Index(s, ".")
|
||||
if index < 0 {
|
||||
return s + "." + strings.Repeat("0", countZero)
|
||||
}
|
||||
var decimalLen = len(s) - 1 - index
|
||||
if decimalLen < countZero {
|
||||
return s + strings.Repeat("0", countZero-decimalLen)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
var decimalReg = regexp.MustCompile(`^(\d+\.\d+)([a-zA-Z]+)?$`)
|
||||
|
||||
// TrimZeroSuffix 去除小数数字尾部多余的0
|
||||
func TrimZeroSuffix(s string) string {
|
||||
var matches = decimalReg.FindStringSubmatch(s)
|
||||
if len(matches) < 3 {
|
||||
return s
|
||||
}
|
||||
return strings.TrimRight(strings.TrimRight(matches[1], "0"), ".") + matches[2]
|
||||
}
|
||||
|
||||
func formatDigit(d string) string {
|
||||
if len(d) == 0 {
|
||||
return d
|
||||
}
|
||||
|
||||
var prefix = ""
|
||||
if d[0] < '0' || d[0] > '9' {
|
||||
prefix = d[:1]
|
||||
d = d[1:]
|
||||
}
|
||||
|
||||
var l = len(d)
|
||||
if l > 3 {
|
||||
var pieces = l / 3
|
||||
var commIndex = l - pieces*3
|
||||
var d2 = ""
|
||||
if commIndex > 0 {
|
||||
d2 = d[:commIndex] + ", "
|
||||
}
|
||||
for i := 0; i < pieces; i++ {
|
||||
d2 += d[commIndex+i*3 : commIndex+i*3+3]
|
||||
if i != pieces-1 {
|
||||
d2 += ", "
|
||||
}
|
||||
}
|
||||
return prefix + d2
|
||||
}
|
||||
return prefix + d
|
||||
}
|
||||
|
||||
86
internal/utils/numberutils/utils_test.go
Normal file
86
internal/utils/numberutils/utils_test.go
Normal file
@@ -0,0 +1,86 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package numberutils_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
|
||||
"github.com/iwind/TeaGo/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFormatBytes(t *testing.T) {
|
||||
t.Log(numberutils.FormatBytes(1))
|
||||
t.Log(numberutils.FormatBytes(1000))
|
||||
t.Log(numberutils.FormatBytes(1_000_000))
|
||||
t.Log(numberutils.FormatBytes(1_000_000_000))
|
||||
t.Log(numberutils.FormatBytes(1_000_000_000_000))
|
||||
t.Log(numberutils.FormatBytes(1_000_000_000_000_000))
|
||||
t.Log(numberutils.FormatBytes(1_000_000_000_000_000_000))
|
||||
t.Log(numberutils.FormatBytes(9_000_000_000_000_000_000))
|
||||
}
|
||||
|
||||
func TestFormatCount(t *testing.T) {
|
||||
t.Log(numberutils.FormatCount(1))
|
||||
t.Log(numberutils.FormatCount(1000))
|
||||
t.Log(numberutils.FormatCount(1500))
|
||||
t.Log(numberutils.FormatCount(1_000_000))
|
||||
t.Log(numberutils.FormatCount(1_500_000))
|
||||
t.Log(numberutils.FormatCount(1_000_000_000))
|
||||
t.Log(numberutils.FormatCount(1_500_000_000))
|
||||
}
|
||||
|
||||
func TestFormatFloat(t *testing.T) {
|
||||
t.Log(numberutils.FormatFloat(1, 2))
|
||||
t.Log(numberutils.FormatFloat(100.23456, 2))
|
||||
t.Log(numberutils.FormatFloat(100.000023, 2))
|
||||
t.Log(numberutils.FormatFloat(100.012, 2))
|
||||
t.Log(numberutils.FormatFloat(123.012, 2))
|
||||
t.Log(numberutils.FormatFloat(1234.012, 2))
|
||||
t.Log(numberutils.FormatFloat(12345.012, 2))
|
||||
t.Log(numberutils.FormatFloat(123456.012, 2))
|
||||
t.Log(numberutils.FormatFloat(1234567.012, 2))
|
||||
t.Log(numberutils.FormatFloat(12345678.012, 2))
|
||||
t.Log(numberutils.FormatFloat(123456789.012, 2))
|
||||
t.Log(numberutils.FormatFloat(1234567890.012, 2))
|
||||
t.Log(numberutils.FormatFloat(123, 2))
|
||||
t.Log(numberutils.FormatFloat(1234, 2))
|
||||
t.Log(numberutils.FormatFloat(1234.00001, 4))
|
||||
t.Log(numberutils.FormatFloat(1234.56700, 4))
|
||||
t.Log(numberutils.FormatFloat(-1234.56700, 2))
|
||||
t.Log(numberutils.FormatFloat(-221745.12, 2))
|
||||
}
|
||||
|
||||
func TestFormatFloat2(t *testing.T) {
|
||||
t.Log(numberutils.FormatFloat2(0))
|
||||
t.Log(numberutils.FormatFloat2(0.0))
|
||||
t.Log(numberutils.FormatFloat2(1.23456))
|
||||
t.Log(numberutils.FormatFloat2(1.0))
|
||||
}
|
||||
|
||||
func TestPadFloatZero(t *testing.T) {
|
||||
var a = assert.NewAssertion(t)
|
||||
a.IsTrue(numberutils.PadFloatZero("1", 0) == "1")
|
||||
a.IsTrue(numberutils.PadFloatZero("1", 2) == "1.00")
|
||||
a.IsTrue(numberutils.PadFloatZero("1.1", 2) == "1.10")
|
||||
a.IsTrue(numberutils.PadFloatZero("1.12", 2) == "1.12")
|
||||
a.IsTrue(numberutils.PadFloatZero("1.123", 2) == "1.123")
|
||||
a.IsTrue(numberutils.PadFloatZero("10000.123", 2) == "10000.123")
|
||||
a.IsTrue(numberutils.PadFloatZero("", 2) == "0.00")
|
||||
}
|
||||
|
||||
func TestTrimZeroSuffix(t *testing.T) {
|
||||
for _, s := range []string{
|
||||
"1",
|
||||
"1.0000",
|
||||
"1.10",
|
||||
"100",
|
||||
"100.0000",
|
||||
"100.0",
|
||||
"100.0123",
|
||||
"100.0010",
|
||||
"100.000KB",
|
||||
"100.010MB",
|
||||
} {
|
||||
t.Log(s, "=>", numberutils.TrimZeroSuffix(s))
|
||||
}
|
||||
}
|
||||
31
internal/utils/otputils/utils.go
Normal file
31
internal/utils/otputils/utils.go
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package otputils
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// FixIssuer fix issuer in otp provisioning url
|
||||
func FixIssuer(urlString string) string {
|
||||
u, err := url.Parse(urlString)
|
||||
if err != nil {
|
||||
return urlString
|
||||
}
|
||||
|
||||
var query = u.Query()
|
||||
|
||||
if query != nil {
|
||||
var issuerName = query.Get("issuer")
|
||||
if len(issuerName) > 0 {
|
||||
unescapedIssuerName, unescapeErr := url.QueryUnescape(issuerName)
|
||||
if unescapeErr == nil {
|
||||
query.Set("issuer", unescapedIssuerName)
|
||||
u.RawQuery = query.Encode()
|
||||
}
|
||||
}
|
||||
return u.String()
|
||||
}
|
||||
|
||||
return urlString
|
||||
}
|
||||
18
internal/utils/otputils/utils_test.go
Normal file
18
internal/utils/otputils/utils_test.go
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package otputils_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils/otputils"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFixIssuer(t *testing.T) {
|
||||
var beforeURL = "otpauth://totp/GoEdge%25E7%25AE%25A1%25E7%2590%2586%25E5%2591%2598%25E7%25B3%25BB%25E7%25BB%259F:admin?issuer=GoEdge%25E7%25AE%25A1%25E7%2590%2586%25E5%2591%2598%25E7%25B3%25BB%25E7%25BB%259F&secret=Q3J4WNOWBRFLP3HI"
|
||||
var afterURL = otputils.FixIssuer(beforeURL)
|
||||
t.Log(afterURL)
|
||||
|
||||
if beforeURL == afterURL {
|
||||
t.Fatal("'afterURL' should not be equal to 'beforeURL'")
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ func (this *ServiceManager) setup() {
|
||||
this.onceLocker.Do(func() {
|
||||
logFile := files.NewFile(Tea.Root + "/logs/service.log")
|
||||
if logFile.Exists() {
|
||||
logFile.Delete()
|
||||
_ = logFile.Delete()
|
||||
}
|
||||
|
||||
//logger
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
// +build linux
|
||||
//go:build linux
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
executils "github.com/TeaOSLab/EdgeAdmin/internal/utils/exec"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/files"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
@@ -16,13 +16,13 @@ import (
|
||||
var systemdServiceFile = "/etc/systemd/system/edge-admin.service"
|
||||
var initServiceFile = "/etc/init.d/" + teaconst.SystemdServiceName
|
||||
|
||||
// 安装服务
|
||||
// Install 安装服务
|
||||
func (this *ServiceManager) Install(exePath string, args []string) error {
|
||||
if os.Getgid() != 0 {
|
||||
return errors.New("only root users can install the service")
|
||||
}
|
||||
|
||||
systemd, err := exec.LookPath("systemctl")
|
||||
systemd, err := executils.LookPath("systemctl")
|
||||
if err != nil {
|
||||
return this.installInitService(exePath, args)
|
||||
}
|
||||
@@ -30,14 +30,14 @@ func (this *ServiceManager) Install(exePath string, args []string) error {
|
||||
return this.installSystemdService(systemd, exePath, args)
|
||||
}
|
||||
|
||||
// 启动服务
|
||||
// Start 启动服务
|
||||
func (this *ServiceManager) Start() error {
|
||||
if os.Getgid() != 0 {
|
||||
return errors.New("only root users can start the service")
|
||||
}
|
||||
|
||||
if files.NewFile(systemdServiceFile).Exists() {
|
||||
systemd, err := exec.LookPath("systemctl")
|
||||
systemd, err := executils.LookPath("systemctl")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -47,23 +47,23 @@ func (this *ServiceManager) Start() error {
|
||||
return exec.Command("service", teaconst.ProcessName, "start").Start()
|
||||
}
|
||||
|
||||
// 删除服务
|
||||
// Uninstall 删除服务
|
||||
func (this *ServiceManager) Uninstall() error {
|
||||
if os.Getgid() != 0 {
|
||||
return errors.New("only root users can uninstall the service")
|
||||
}
|
||||
|
||||
if files.NewFile(systemdServiceFile).Exists() {
|
||||
systemd, err := exec.LookPath("systemctl")
|
||||
systemd, err := executils.LookPath("systemctl")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// disable service
|
||||
exec.Command(systemd, "disable", teaconst.SystemdServiceName+".service").Start()
|
||||
_ = exec.Command(systemd, "disable", teaconst.SystemdServiceName+".service").Start()
|
||||
|
||||
// reload
|
||||
exec.Command(systemd, "daemon-reload")
|
||||
_ = exec.Command(systemd, "daemon-reload").Start()
|
||||
|
||||
return files.NewFile(systemdServiceFile).Delete()
|
||||
}
|
||||
@@ -83,18 +83,18 @@ func (this *ServiceManager) installInitService(exePath string, args []string) er
|
||||
return errors.New("'scripts/" + shortName + "' file not exists")
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadFile(scriptFile)
|
||||
data, err := os.ReadFile(scriptFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data = regexp.MustCompile("INSTALL_DIR=.+").ReplaceAll(data, []byte("INSTALL_DIR="+Tea.Root))
|
||||
err = ioutil.WriteFile(initServiceFile, data, 0777)
|
||||
err = os.WriteFile(initServiceFile, data, 0777)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
chkCmd, err := exec.LookPath("chkconfig")
|
||||
chkCmd, err := executils.LookPath("chkconfig")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -112,7 +112,14 @@ func (this *ServiceManager) installSystemdService(systemd, exePath string, args
|
||||
shortName := teaconst.SystemdServiceName
|
||||
longName := "GoEdge Admin" // TODO 将来可以修改
|
||||
|
||||
desc := `# Provides: ` + shortName + `
|
||||
var startCmd = exePath + " daemon"
|
||||
bashPath, _ := executils.LookPath("bash")
|
||||
if len(bashPath) > 0 {
|
||||
startCmd = bashPath + " -c \"" + startCmd + "\""
|
||||
}
|
||||
|
||||
desc := `### BEGIN INIT INFO
|
||||
# Provides: ` + shortName + `
|
||||
# Required-Start: $all
|
||||
# Required-Stop:
|
||||
# Default-Start: 2 3 4 5
|
||||
@@ -128,8 +135,8 @@ After=network-online.target
|
||||
[Service]
|
||||
Type=simple
|
||||
Restart=always
|
||||
RestartSec=1s
|
||||
ExecStart=` + exePath + ` daemon
|
||||
RestartSec=5s
|
||||
ExecStart=` + startCmd + `
|
||||
ExecStop=` + exePath + ` stop
|
||||
ExecReload=` + exePath + ` reload
|
||||
|
||||
@@ -137,16 +144,16 @@ ExecReload=` + exePath + ` reload
|
||||
WantedBy=multi-user.target`
|
||||
|
||||
// write file
|
||||
err := ioutil.WriteFile(systemdServiceFile, []byte(desc), 0777)
|
||||
err := os.WriteFile(systemdServiceFile, []byte(desc), 0777)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// stop current systemd service if running
|
||||
exec.Command(systemd, "stop", shortName+".service")
|
||||
_ = exec.Command(systemd, "stop", shortName+".service").Start()
|
||||
|
||||
// reload
|
||||
exec.Command(systemd, "daemon-reload")
|
||||
_ = exec.Command(systemd, "daemon-reload").Start()
|
||||
|
||||
// enable
|
||||
cmd := exec.Command(systemd, "enable", shortName+".service")
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// +build !linux,!windows
|
||||
//go:build !linux && !windows
|
||||
|
||||
package utils
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
// +build windows
|
||||
//go:build windows
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"golang.org/x/sys/windows"
|
||||
"golang.org/x/sys/windows/svc"
|
||||
@@ -15,7 +16,7 @@ import (
|
||||
func (this *ServiceManager) Install(exePath string, args []string) error {
|
||||
m, err := mgr.Connect()
|
||||
if err != nil {
|
||||
return fmt.Errorf("connecting: %s please 'Run as administrator' again", err.Error())
|
||||
return fmt.Errorf("connecting: %w please 'Run as administrator' again", err)
|
||||
}
|
||||
defer m.Disconnect()
|
||||
s, err := m.OpenService(this.Name)
|
||||
@@ -30,7 +31,7 @@ func (this *ServiceManager) Install(exePath string, args []string) error {
|
||||
StartType: windows.SERVICE_AUTO_START,
|
||||
}, args...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating: %s", err.Error())
|
||||
return fmt.Errorf("creating: %w", err)
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
@@ -46,12 +47,12 @@ func (this *ServiceManager) Start() error {
|
||||
defer m.Disconnect()
|
||||
s, err := m.OpenService(this.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not access service: %v", err)
|
||||
return fmt.Errorf("could not access service: %w", err)
|
||||
}
|
||||
defer s.Close()
|
||||
err = s.Start("service")
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not start service: %v", err)
|
||||
return fmt.Errorf("could not start service: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -61,12 +62,12 @@ func (this *ServiceManager) Start() error {
|
||||
func (this *ServiceManager) Uninstall() error {
|
||||
m, err := mgr.Connect()
|
||||
if err != nil {
|
||||
return fmt.Errorf("connecting: %s please 'Run as administrator' again", err.Error())
|
||||
return fmt.Errorf("connecting: %w please 'Run as administrator' again", err)
|
||||
}
|
||||
defer m.Disconnect()
|
||||
s, err := m.OpenService(this.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("open service: %s", err.Error())
|
||||
return fmt.Errorf("open service: %w", err)
|
||||
}
|
||||
|
||||
// shutdown service
|
||||
@@ -78,7 +79,7 @@ func (this *ServiceManager) Uninstall() error {
|
||||
defer s.Close()
|
||||
err = s.Delete()
|
||||
if err != nil {
|
||||
return fmt.Errorf("deleting: %s", err.Error())
|
||||
return fmt.Errorf("deleting: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
10
internal/utils/sizes/sizes.go
Normal file
10
internal/utils/sizes/sizes.go
Normal file
@@ -0,0 +1,10 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package sizes
|
||||
|
||||
const (
|
||||
K int64 = 1024
|
||||
M = 1024 * K
|
||||
G = 1024 * M
|
||||
T = 1024 * G
|
||||
)
|
||||
17
internal/utils/sizes/sizes_test.go
Normal file
17
internal/utils/sizes/sizes_test.go
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package sizes_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils/sizes"
|
||||
"github.com/iwind/TeaGo/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSizes(t *testing.T) {
|
||||
var a = assert.NewAssertion(t)
|
||||
a.IsTrue(sizes.K == 1024)
|
||||
a.IsTrue(sizes.M == 1024*1024)
|
||||
a.IsTrue(sizes.G == 1024*1024*1024)
|
||||
a.IsTrue(sizes.T == 1024*1024*1024*1024)
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// format address
|
||||
// FormatAddress format address
|
||||
func FormatAddress(addr string) string {
|
||||
if strings.HasSuffix(addr, "unix:") {
|
||||
return addr
|
||||
@@ -17,7 +17,7 @@ func FormatAddress(addr string) string {
|
||||
return addr
|
||||
}
|
||||
|
||||
// 分割数字
|
||||
// SplitNumbers 分割数字
|
||||
func SplitNumbers(numbers string) (result []int64) {
|
||||
if len(numbers) == 0 {
|
||||
return
|
||||
|
||||
67
internal/utils/strings_stream.go
Normal file
67
internal/utils/strings_stream.go
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func FilterNotEmpty(item string) bool {
|
||||
return len(item) > 0
|
||||
}
|
||||
|
||||
func MapAddPrefixFunc(prefix string) func(item string) string {
|
||||
return func(item string) string {
|
||||
if !strings.HasPrefix(item, prefix) {
|
||||
return prefix + item
|
||||
}
|
||||
return item
|
||||
}
|
||||
}
|
||||
|
||||
type StringsStream struct {
|
||||
s []string
|
||||
}
|
||||
|
||||
func NewStringsStream(s []string) *StringsStream {
|
||||
return &StringsStream{s: s}
|
||||
}
|
||||
|
||||
func (this *StringsStream) Map(f ...func(item string) string) *StringsStream {
|
||||
for index, item := range this.s {
|
||||
for _, f1 := range f {
|
||||
item = f1(item)
|
||||
}
|
||||
this.s[index] = item
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
func (this *StringsStream) Filter(f ...func(item string) bool) *StringsStream {
|
||||
for _, f1 := range f {
|
||||
var newStrings = []string{}
|
||||
for _, item := range this.s {
|
||||
if f1(item) {
|
||||
newStrings = append(newStrings, item)
|
||||
}
|
||||
}
|
||||
this.s = newStrings
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
func (this *StringsStream) Unique() *StringsStream {
|
||||
var newStrings = []string{}
|
||||
for _, item := range this.s {
|
||||
if !lists.ContainsString(newStrings, item) {
|
||||
newStrings = append(newStrings, item)
|
||||
}
|
||||
}
|
||||
this.s = newStrings
|
||||
return this
|
||||
}
|
||||
|
||||
func (this *StringsStream) Result() []string {
|
||||
return this.s
|
||||
}
|
||||
25
internal/utils/strings_stream_test.go
Normal file
25
internal/utils/strings_stream_test.go
Normal file
@@ -0,0 +1,25 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package utils_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStringsStream_Filter(t *testing.T) {
|
||||
var stream = utils.NewStringsStream([]string{"a", "b", "1", "2", "", "png", "a"})
|
||||
stream.Filter(func(item string) bool {
|
||||
return len(item) > 0
|
||||
})
|
||||
t.Log(stream.Result())
|
||||
stream.Map(func(item string) string {
|
||||
return "." + item
|
||||
})
|
||||
t.Log(stream.Result())
|
||||
stream.Unique()
|
||||
t.Log(stream.Result())
|
||||
stream.Map(strings.ToUpper, strings.ToLower)
|
||||
t.Log(stream.Result())
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user