mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-09 16:00:29 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
20b89a8fdd | ||
|
|
8def011e63 | ||
|
|
7c5b2f41e3 | ||
|
|
abd3c24562 | ||
|
|
f4e64ca8b3 | ||
|
|
97fde371d9 | ||
|
|
85bbb9cb15 | ||
|
|
1d34b4aff7 | ||
|
|
dc8c50c07a | ||
|
|
4ac490c6b4 | ||
|
|
0d548c7df9 | ||
|
|
8ec5a5bd4b | ||
|
|
2e8bcc09e6 | ||
|
|
29a304e2c8 | ||
|
|
b1adc37920 | ||
|
|
03fac3ec06 | ||
|
|
6e465ac215 | ||
|
|
21bbadc37b | ||
|
|
3061d640ce | ||
|
|
874717cbcd | ||
|
|
6850acabae | ||
|
|
890ea42749 | ||
|
|
6aaf61cde4 | ||
|
|
73930c5e29 | ||
|
|
8f8d280e90 | ||
|
|
b46192024f | ||
|
|
8f939ad5be | ||
|
|
a6363ce4e5 | ||
|
|
fd490b2642 | ||
|
|
7624670ee0 | ||
|
|
8c3145e1c6 | ||
|
|
bec7b6989f | ||
|
|
326f2654e6 | ||
|
|
708499a335 | ||
|
|
bdeb842939 | ||
|
|
edefdce3ff | ||
|
|
b348c36571 | ||
|
|
6ae150b64e | ||
|
|
3687b9bea2 | ||
|
|
711431da82 | ||
|
|
e93afc33aa | ||
|
|
82dd993751 | ||
|
|
9ba293e154 | ||
|
|
8fa9f128d8 | ||
|
|
ed9f1599e6 | ||
|
|
787a29769d | ||
|
|
6b379389d3 | ||
|
|
5bcbf32823 | ||
|
|
0aa90e1b2d | ||
|
|
a7b87211b4 | ||
|
|
2c666933a5 | ||
|
|
f280b533c6 | ||
|
|
47a16bc9a9 | ||
|
|
713202b6eb | ||
|
|
79020abb1c | ||
|
|
83ef41cf1a | ||
|
|
91675c69e9 | ||
|
|
0e68065b40 | ||
|
|
600b769083 | ||
|
|
e7670f96de | ||
|
|
342c04b30a | ||
|
|
5ab8377ac1 | ||
|
|
74eb2c8dd6 | ||
|
|
f136d97941 | ||
|
|
a5629e3f42 | ||
|
|
515d6ca500 | ||
|
|
c0ccc0318d | ||
|
|
8196c11010 | ||
|
|
170bd22062 | ||
|
|
097a33c68f | ||
|
|
3be3bf9926 | ||
|
|
038f2b4f54 | ||
|
|
f84e284c20 | ||
|
|
48e75b5916 | ||
|
|
a12ceaee6e | ||
|
|
9050680115 | ||
|
|
0022a9b508 | ||
|
|
3437d62f31 | ||
|
|
dc880835d0 | ||
|
|
1663bf8677 | ||
|
|
aed236cbc9 | ||
|
|
90be74504e | ||
|
|
bba8aab18c | ||
|
|
47d135ecca | ||
|
|
d96fe49993 | ||
|
|
ac396b61ce | ||
|
|
6798d0c7ee | ||
|
|
1490ed7541 | ||
|
|
48b3032de6 | ||
|
|
ed12bec460 | ||
|
|
3c4a1ea614 | ||
|
|
79eb0288b4 | ||
|
|
99775e1eb4 | ||
|
|
db848b41d2 | ||
|
|
b5b11dff86 | ||
|
|
9cf4bfc606 | ||
|
|
221162d44f | ||
|
|
617970a1f8 | ||
|
|
5610c73d67 | ||
|
|
51fe02661f | ||
|
|
8f491b2933 | ||
|
|
b7e16bd6d5 | ||
|
|
38ca2ddd01 | ||
|
|
402b41100b | ||
|
|
766beae4e1 | ||
|
|
05f72e9dcf | ||
|
|
23553c68ae | ||
|
|
4dd1eb7a1e | ||
|
|
db307732c6 | ||
|
|
33d4ade1d4 | ||
|
|
186075ff9a | ||
|
|
34df745d1c | ||
|
|
ec363ac157 | ||
|
|
193b0f3cd4 | ||
|
|
67c1bf2924 | ||
|
|
b1373ff77e | ||
|
|
55d0e27212 | ||
|
|
1c052b7c13 | ||
|
|
6c8d48c2dc | ||
|
|
b61dae638b | ||
|
|
872a93aa07 | ||
|
|
b1f337020c | ||
|
|
56858dd74c | ||
|
|
358415f737 | ||
|
|
5e73260103 | ||
|
|
e169f46784 | ||
|
|
39416abd47 | ||
|
|
640a0ca815 | ||
|
|
8e54cd5162 | ||
|
|
0f0099accb | ||
|
|
718ebeb203 | ||
|
|
4101bf691d | ||
|
|
a97deb3dca | ||
|
|
10203d5c0c | ||
|
|
a357d6697d | ||
|
|
555b0aa999 | ||
|
|
6dd0dbe2c3 | ||
|
|
c31bbae768 | ||
|
|
2bb0f15973 | ||
|
|
be229551e3 | ||
|
|
6283eb4c69 | ||
|
|
c329fb6599 | ||
|
|
c3685f6b74 | ||
|
|
f4f5a36475 | ||
|
|
ea1261b48e | ||
|
|
0c68f16a66 | ||
|
|
33e5c9230f | ||
|
|
96eefab36f | ||
|
|
b0b6bf458f | ||
|
|
f3d85d1309 | ||
|
|
39385ea0a0 | ||
|
|
e492c6573c | ||
|
|
10a7fb3be2 | ||
|
|
8bc7cfade3 | ||
|
|
ea044d6c32 | ||
|
|
f6ee3dc75b | ||
|
|
367a74503f | ||
|
|
53b1a7af4c | ||
|
|
078d7fbe1e | ||
|
|
c709e48ceb | ||
|
|
0741f60896 | ||
|
|
af8eef4a91 | ||
|
|
f233ef7069 | ||
|
|
1c199bd3d7 | ||
|
|
036c973fec | ||
|
|
8bb225d272 | ||
|
|
ab932ae154 | ||
|
|
89f969ec6e | ||
|
|
afa8cf22b1 | ||
|
|
67aa8471dc | ||
|
|
0617b81b3b | ||
|
|
98c535a12c | ||
|
|
ec46518936 | ||
|
|
a7b034387f | ||
|
|
7905dd00a6 | ||
|
|
74583aa1aa | ||
|
|
4be9d71edf | ||
|
|
57bfc38eb8 | ||
|
|
aaaa5980b5 | ||
|
|
4f57dfc76e | ||
|
|
2c31ffc66b | ||
|
|
b104a13b71 | ||
|
|
28633d2fef | ||
|
|
9052386c83 | ||
|
|
7bc45f2d77 | ||
|
|
2ef6ecb951 | ||
|
|
ca0dc71338 | ||
|
|
04a1ec9a9a | ||
|
|
999e5a779a | ||
|
|
99f62b7230 | ||
|
|
4dee4e1cc2 | ||
|
|
539addc656 | ||
|
|
1a35b307fa | ||
|
|
a04f4a8b96 | ||
|
|
6b88e1adf2 | ||
|
|
99bc8177d4 | ||
|
|
cf2a1372f2 | ||
|
|
42c30f324a | ||
|
|
dab83fee20 | ||
|
|
6f3c5586fc | ||
|
|
aab8df6abc | ||
|
|
b9be79b05a | ||
|
|
ec3024f2c3 | ||
|
|
070f61e229 | ||
|
|
a82a4ebfc5 | ||
|
|
87009ddb27 | ||
|
|
3650ff27b3 | ||
|
|
6c08a3114a | ||
|
|
381cb61185 | ||
|
|
8d69da7702 | ||
|
|
8922395fdc | ||
|
|
83db40e261 | ||
|
|
95ec6c62ef | ||
|
|
56fc9ea428 | ||
|
|
88c6fc050b | ||
|
|
2392f91567 | ||
|
|
5f0c740eb0 | ||
|
|
120d0dd7c6 | ||
|
|
2844f6db7f | ||
|
|
570074f92d | ||
|
|
a13047c855 | ||
|
|
ca0d24bf56 | ||
|
|
2b18251360 | ||
|
|
f7c875acda | ||
|
|
a291a50c9f | ||
|
|
5bc7bc6aac | ||
|
|
9c376300ff | ||
|
|
5187b20252 | ||
|
|
fc401ffd8f | ||
|
|
667ee5256e | ||
|
|
f8356de93c | ||
|
|
55bb997d51 | ||
|
|
7c46f54e15 | ||
|
|
6dbb6d2247 | ||
|
|
9d0ae45edc | ||
|
|
b467dfa3b2 | ||
|
|
c13387bc70 | ||
|
|
41b2523750 | ||
|
|
afe82bd4be | ||
|
|
0f296fb70b | ||
|
|
d96ccd06a3 | ||
|
|
14095affa9 | ||
|
|
ed60565bc9 | ||
|
|
8313912c4f | ||
|
|
6274015d06 | ||
|
|
d195ce9155 | ||
|
|
cd0cd8a152 | ||
|
|
1f45ed6b06 | ||
|
|
7f6ad89edf | ||
|
|
5cdea288e2 | ||
|
|
e56feaf826 | ||
|
|
1288a09647 | ||
|
|
0cdaafa1c0 | ||
|
|
0b3334253c | ||
|
|
10f72f12d2 | ||
|
|
01643b2816 | ||
|
|
56e0da4fe3 | ||
|
|
144c397bf4 | ||
|
|
e06541984f | ||
|
|
ea7e92db84 | ||
|
|
8d48640c1d | ||
|
|
cfe5cb4b6b | ||
|
|
05c4d7dd38 | ||
|
|
13c6e0df99 | ||
|
|
dafd6ba71e | ||
|
|
2751c37716 | ||
|
|
50c01762e7 | ||
|
|
a77ebd3eb6 | ||
|
|
d84f0b5597 | ||
|
|
a3cdf87ea2 | ||
|
|
8ab3c16b3d | ||
|
|
18e943fd06 | ||
|
|
3b7a067dda | ||
|
|
4636ba815b | ||
|
|
317fad2a28 | ||
|
|
0ae28a1dd9 | ||
|
|
bbfe5f1d96 | ||
|
|
b713f20b66 | ||
|
|
3eb7a5adc5 | ||
|
|
956af78749 | ||
|
|
69ad01687e | ||
|
|
e83d07e88e | ||
|
|
6683696032 | ||
|
|
3396a5ffea | ||
|
|
18a14d3b75 | ||
|
|
2b13352004 | ||
|
|
94e5d7e642 | ||
|
|
001b4dbada | ||
|
|
1192dec689 | ||
|
|
956d47f9da | ||
|
|
063a071228 | ||
|
|
c54a7de827 | ||
|
|
0410e48aad | ||
|
|
a4d95737d3 | ||
|
|
bc64d83ab2 | ||
|
|
87616e388b | ||
|
|
3f35f90fc2 | ||
|
|
c3ba69814c | ||
|
|
66a517e58d | ||
|
|
3108c927af | ||
|
|
5738cceac9 | ||
|
|
db2f648bce | ||
|
|
6012a6cf9f | ||
|
|
a4dcbc90e6 | ||
|
|
955ecce952 | ||
|
|
91fba21ed3 | ||
|
|
490e2934b8 | ||
|
|
cb6d3d4fc3 | ||
|
|
7f2a09eab3 | ||
|
|
c522a308b9 | ||
|
|
dde686d55f | ||
|
|
2f0b4888de | ||
|
|
35e7b11709 | ||
|
|
6cf3f158c7 | ||
|
|
4944e705dc | ||
|
|
3b38b9d86b | ||
|
|
6275eec918 | ||
|
|
ad652ca6f5 | ||
|
|
7029d985c6 | ||
|
|
9539fd40eb | ||
|
|
4c03a482bd | ||
|
|
f58ed5614e | ||
|
|
198e78e0b4 | ||
|
|
1f16e09daa | ||
|
|
16f47f8664 | ||
|
|
40604c7880 | ||
|
|
fa86c4f39a | ||
|
|
7f3cc784cd | ||
|
|
836f6c3520 | ||
|
|
2ff1e3a245 | ||
|
|
f7078a6140 | ||
|
|
141786e4e9 | ||
|
|
2d098f8078 | ||
|
|
229254e3d3 | ||
|
|
96329967ba | ||
|
|
0d988bbd6a | ||
|
|
1ff4f33ceb | ||
|
|
c95d61397d | ||
|
|
590238dc13 | ||
|
|
ed1b2613f2 | ||
|
|
c0e0db670c | ||
|
|
9132a2c4a9 | ||
|
|
5e880ba25e | ||
|
|
5a1928bee6 | ||
|
|
82e9e72c70 | ||
|
|
c35f55f70c | ||
|
|
0542b39373 | ||
|
|
013cf21863 | ||
|
|
c648f9b14a | ||
|
|
d9407c425f | ||
|
|
2c8dad980b | ||
|
|
75d94f5b89 | ||
|
|
4d760d3019 | ||
|
|
20c9230291 | ||
|
|
02c3a34f13 | ||
|
|
c2dadc2b92 | ||
|
|
f5de005ad7 | ||
|
|
335a82332f | ||
|
|
a81cb5c1ab | ||
|
|
aaf1d86a55 | ||
|
|
cbad4dd172 | ||
|
|
932da58759 | ||
|
|
01defdf0f6 | ||
|
|
b33cce3059 | ||
|
|
17ef66aa49 | ||
|
|
f01d770fec | ||
|
|
06cc7fcc02 | ||
|
|
4635047004 | ||
|
|
88b70eba53 | ||
|
|
6ad48250f8 | ||
|
|
9ac9f76a1d | ||
|
|
b7702daea3 | ||
|
|
db7180a761 | ||
|
|
dbe62082ff | ||
|
|
06bf2bbc2f | ||
|
|
162a0c543c | ||
|
|
2bc07c91df | ||
|
|
3d84a5589f | ||
|
|
b88438a0a3 | ||
|
|
b73dcc9388 | ||
|
|
12ad08a619 | ||
|
|
0c000a4994 | ||
|
|
d2da7e2ea5 | ||
|
|
873bba92db | ||
|
|
3e14186d3d | ||
|
|
5be325405d | ||
|
|
5b92b75090 | ||
|
|
7202de6f4c | ||
|
|
e6a772ebfd | ||
|
|
f320492164 | ||
|
|
d8fd430856 | ||
|
|
8e3a06b07f | ||
|
|
b6f65f278d | ||
|
|
3e8dfb6841 | ||
|
|
ffeb47eaac | ||
|
|
4cffe1d6bc | ||
|
|
49b3b6ded5 | ||
|
|
f294130746 | ||
|
|
1910f1b78a | ||
|
|
cc1d2f1eae | ||
|
|
048121a936 | ||
|
|
e41e74d730 | ||
|
|
0ef1a26e95 | ||
|
|
00701a3999 | ||
|
|
8453e60609 | ||
|
|
d139073948 | ||
|
|
f6e6f9cbfb | ||
|
|
66e4af2be5 | ||
|
|
7327ee3ff8 | ||
|
|
23087bdb6f | ||
|
|
5e9a69d8f3 | ||
|
|
3e96e83c3c | ||
|
|
83ce744fb2 | ||
|
|
ae03b85a97 | ||
|
|
53e91a87b0 | ||
|
|
519c6bca66 | ||
|
|
f7dcacab92 | ||
|
|
00c6466423 | ||
|
|
b8d8867ebb | ||
|
|
7b1d746ce7 | ||
|
|
7dc0a2f664 | ||
|
|
52f7df0350 | ||
|
|
38d1cac5d9 | ||
|
|
c3565f72c4 | ||
|
|
7fdb0a5078 | ||
|
|
ce30a7d5cd | ||
|
|
28597721f2 | ||
|
|
c2fd02ac48 | ||
|
|
a8aa1f87f2 | ||
|
|
2ba17d89ef | ||
|
|
aaae8a5d3b | ||
|
|
5ca91157ca | ||
|
|
bad1b0b105 | ||
|
|
d14a149c57 | ||
|
|
9c6c77615e | ||
|
|
913e64bb75 | ||
|
|
fd8ef545e8 | ||
|
|
d068cc40f0 | ||
|
|
ee0e17a496 | ||
|
|
f1afc0f4d9 | ||
|
|
ea1afe7610 | ||
|
|
4ade175a3d | ||
|
|
c6ea06153a | ||
|
|
eb36a0f8cb | ||
|
|
3087c1a6e8 | ||
|
|
e5b0a1c591 | ||
|
|
af41e8e429 | ||
|
|
7e677ee9fa | ||
|
|
eea4cf68ad | ||
|
|
3a400dd15a | ||
|
|
fbbbd52d72 | ||
|
|
27664c7dce | ||
|
|
e1bdd215b1 | ||
|
|
d04a35565e | ||
|
|
7aad2cf4af | ||
|
|
bfa667584a | ||
|
|
1858cb4df8 | ||
|
|
dc30a3d1c3 | ||
|
|
6e690cc6f2 | ||
|
|
911b4076f5 | ||
|
|
4c01b1e542 | ||
|
|
ec372dbb98 | ||
|
|
0b87025c52 | ||
|
|
7d4230ecfb | ||
|
|
d06583773a | ||
|
|
67c1fbd07a | ||
|
|
5c7fab8f01 | ||
|
|
06554375cb | ||
|
|
e65329e7b3 | ||
|
|
f5435c581b | ||
|
|
5e8b23db6a | ||
|
|
f2849aa6ec | ||
|
|
ee947b6468 | ||
|
|
49c072b40d | ||
|
|
204f9f1fbc | ||
|
|
cccbaafd1b | ||
|
|
7bc06bb6b1 | ||
|
|
c16d5190c6 | ||
|
|
ffdd9b9d79 | ||
|
|
8d35c54672 | ||
|
|
eb0164907c | ||
|
|
478d44885c | ||
|
|
541bc456be | ||
|
|
5ce75992a3 | ||
|
|
c8b3ba7f6c | ||
|
|
d92ce86bb8 | ||
|
|
f7b4c8cd28 | ||
|
|
24c063b106 | ||
|
|
e478d2e624 | ||
|
|
dc0f726029 | ||
|
|
784b8da7af | ||
|
|
79e9dc9914 | ||
|
|
4849c6b42d | ||
|
|
024d142c8d | ||
|
|
126aea931c | ||
|
|
2372281d5e | ||
|
|
376d6609f2 | ||
|
|
96ab4c53af | ||
|
|
96cf46d834 | ||
|
|
208414704d | ||
|
|
7f453aa145 | ||
|
|
123997fa13 | ||
|
|
7faef2f8a7 | ||
|
|
f93cf3b996 | ||
|
|
df5b69756c | ||
|
|
745bb98abe | ||
|
|
cf1d02771f | ||
|
|
52377b0042 | ||
|
|
270501a237 | ||
|
|
2e5283b8d3 | ||
|
|
8cad47b29e | ||
|
|
2458d60d80 | ||
|
|
9a79dc4cdd | ||
|
|
e531b509d6 | ||
|
|
d287fc1186 | ||
|
|
f47f7c7361 | ||
|
|
7a6c99d57d | ||
|
|
5105114321 | ||
|
|
60beebcb4c | ||
|
|
fa03b4226a | ||
|
|
b047a053c5 | ||
|
|
42907cd228 | ||
|
|
425892cfc4 | ||
|
|
16fd804e93 | ||
|
|
2f147dde13 | ||
|
|
0d04f07f2a | ||
|
|
96485218b7 | ||
|
|
227cc57426 | ||
|
|
b99061777a | ||
|
|
8749dc649a | ||
|
|
65da51a478 | ||
|
|
0c31ba1c5f | ||
|
|
acf94b052b | ||
|
|
556441d811 | ||
|
|
efcf17e47c | ||
|
|
02b7744995 | ||
|
|
c86947b69c | ||
|
|
839b8c2844 | ||
|
|
e4a634e6e8 | ||
|
|
eb99c10ad0 | ||
|
|
175b7b89e4 | ||
|
|
5374afb226 | ||
|
|
1a44f48fad | ||
|
|
50dbe7cfc0 | ||
|
|
c7e576c879 | ||
|
|
a3e4847744 | ||
|
|
903f8ed5a7 | ||
|
|
b091c7f364 | ||
|
|
e069db3c45 | ||
|
|
047264dbbc | ||
|
|
0c216ba87a | ||
|
|
31cdc28143 | ||
|
|
e03b728240 | ||
|
|
4c275ba52f | ||
|
|
2ac9a888a8 | ||
|
|
f9bdcf8938 | ||
|
|
c3bef26ae6 | ||
|
|
634849c347 | ||
|
|
c557b737f6 | ||
|
|
1d326afa93 | ||
|
|
cb6c58cb5d | ||
|
|
ec4d708569 | ||
|
|
05b19cefa5 | ||
|
|
fdd57d0801 | ||
|
|
c16d6d1644 | ||
|
|
656b39c880 | ||
|
|
753c996291 | ||
|
|
dcdd0cc2bb | ||
|
|
2f32df16d9 | ||
|
|
9caa9c47b4 | ||
|
|
55bec8a911 | ||
|
|
43f95fd40e | ||
|
|
641812e7c1 | ||
|
|
95d3e75425 | ||
|
|
7f06c7e92e | ||
|
|
0e6fdffeec | ||
|
|
f77681b9c9 | ||
|
|
535d4bbe1b | ||
|
|
1dfbca3d5b | ||
|
|
d2ca265cc2 | ||
|
|
fc70345fcd | ||
|
|
6ae9b982a9 | ||
|
|
0122b4c6ce | ||
|
|
08c0faa20e | ||
|
|
ab7efea8c5 | ||
|
|
f4623664d5 | ||
|
|
df147435c5 | ||
|
|
71c1c5ae83 | ||
|
|
0ea142d6db | ||
|
|
f88a3059be | ||
|
|
48281ddf86 | ||
|
|
7cdcf9b757 | ||
|
|
ef0bb12fbd | ||
|
|
a0857f86b2 | ||
|
|
2219b3f47b | ||
|
|
b6c873f22d | ||
|
|
64bc92a7fd | ||
|
|
6c4c0f7595 | ||
|
|
2d9c977805 | ||
|
|
dff0375848 | ||
|
|
ab825a1a70 | ||
|
|
466617847f | ||
|
|
a53fff2416 | ||
|
|
dd1430dbbf | ||
|
|
5aad16968b | ||
|
|
f2d3acb650 | ||
|
|
666f8c813d | ||
|
|
e87fb7b50e | ||
|
|
75612879c7 | ||
|
|
94e0c80d47 | ||
|
|
feb6f4cbe0 | ||
|
|
1173bbf628 | ||
|
|
1cdc2e93ad | ||
|
|
3356bd8825 | ||
|
|
decc7a4a1e | ||
|
|
a397cab61c | ||
|
|
228e3d9ae3 | ||
|
|
051f670453 | ||
|
|
cf8aa86a67 | ||
|
|
5b00286e65 | ||
|
|
6597caaf05 | ||
|
|
5851124dee | ||
|
|
b18689a0d6 | ||
|
|
a5e7f78a1f | ||
|
|
a1d394fbc2 | ||
|
|
5e82bda666 | ||
|
|
3d5580ad9d | ||
|
|
c83cfbe8b0 | ||
|
|
00cc1b62ef | ||
|
|
e782f75863 | ||
|
|
f5f379465f | ||
|
|
70a8fda449 | ||
|
|
ca14302191 | ||
|
|
7297c2626f | ||
|
|
92a3982f21 | ||
|
|
626f5efdcd | ||
|
|
24e780b984 | ||
|
|
596d4aceb6 | ||
|
|
e770254b65 | ||
|
|
7901c26d32 | ||
|
|
fc65f8f689 | ||
|
|
e0a5e14978 | ||
|
|
07562c203a | ||
|
|
8187a19e6e | ||
|
|
32cd684307 | ||
|
|
3a72fc95d9 | ||
|
|
64405fba3d | ||
|
|
cc7981229c | ||
|
|
3ec208dc85 | ||
|
|
1b92b6cbfa | ||
|
|
779addfcb4 | ||
|
|
7da5f7a884 | ||
|
|
34e14636d4 | ||
|
|
aa222aac31 | ||
|
|
5134caa103 | ||
|
|
dca3c92d72 | ||
|
|
a85d5bad46 | ||
|
|
716613a4db | ||
|
|
12eaa66bc5 | ||
|
|
a6af97e3c1 | ||
|
|
d561c86c68 | ||
|
|
f243d1c9b4 | ||
|
|
1cc2cc6400 | ||
|
|
15f4c05772 | ||
|
|
c9a204b7a7 | ||
|
|
3d0018c0bd | ||
|
|
d639e46bc7 | ||
|
|
fa633bf419 | ||
|
|
40f7ef90b3 | ||
|
|
50efe607d4 | ||
|
|
3b057f2c27 | ||
|
|
d3d018bd44 | ||
|
|
1378609aa2 | ||
|
|
001c896eed | ||
|
|
1cf3a0143c | ||
|
|
f04be521e5 | ||
|
|
2873632a40 | ||
|
|
075879b5b5 | ||
|
|
9b12930376 | ||
|
|
3339ba9bad | ||
|
|
68922676fc | ||
|
|
dcf2afb4a6 | ||
|
|
16a35141dd | ||
|
|
f6c334bdad | ||
|
|
cbb119117b | ||
|
|
cae0a6118f |
@@ -47,4 +47,4 @@ body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: "Additional Information"
|
||||
description: "If you have any additional information to provide, you can include it here (screenshots, videos, etc., are welcome)."
|
||||
description: "If you have any additional information to provide, you can include it here (screenshots, videos, etc., are welcome)."
|
||||
|
||||
@@ -2,4 +2,4 @@ blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Questions & Discussions
|
||||
url: https://github.com/1Panel-dev/1Panel/discussions
|
||||
about: Raise questions about the installation, deployment, use and other aspects of the project.
|
||||
about: Raise questions about the installation, deployment, use and other aspects of the project.
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
name: General PR Handling for 1Panel
|
||||
on: pull_request
|
||||
permissions:
|
||||
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20.2'
|
||||
node-version: '22.19.0'
|
||||
- name: Build Web
|
||||
run: |
|
||||
cd frontend && npm install && npm run build:pro
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.23'
|
||||
go-version: '1.24'
|
||||
- name: Build Release
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
@@ -44,4 +44,4 @@ jobs:
|
||||
access-key-secret: ${{ secrets.OSS_ACCESS_KEY_SECRET }}
|
||||
ossutil-version: '1.7.18'
|
||||
- name: Upload Assets to OSS
|
||||
run: ossutil cp -r dist/ oss://resource-fit2cloud-com/1panel/package/v2/stable/${{ github.ref_name }}/release/ --include "*.tar.gz" --include "checksums.txt" --only-current-dir --force
|
||||
run: ossutil cp -r dist/ oss://resource-fit2cloud-com/1panel/package/stable/${{ github.ref_name }}/release/ --include "*.tar.gz" --include "checksums.txt" --only-current-dir --force
|
||||
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20.2'
|
||||
node-version: '22.19.0'
|
||||
- name: Build Web
|
||||
run: |
|
||||
cd frontend && npm install && npm run build:pro
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.23'
|
||||
go-version: '1.24'
|
||||
- name: Build Release
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
@@ -42,4 +42,4 @@ jobs:
|
||||
rclone_config: ${{ secrets.RCLONE_CONFIG }}
|
||||
- name: Upload to Cloudflare R2
|
||||
run: |
|
||||
rclone copy dist/ cloudflare_r2:package/v2/stable/${{ github.ref_name }}/release/ --include "*.tar.gz" --include "checksums.txt" --progress
|
||||
rclone copy dist/ cloudflare_r2:package/stable/${{ github.ref_name }}/release/ --include "*.tar.gz" --include "checksums.txt" --progress
|
||||
|
||||
@@ -11,4 +11,4 @@ jobs:
|
||||
- uses: usthe/issues-translate-action@v2.7
|
||||
with:
|
||||
IS_MODIFY_TITLE: true
|
||||
BOT_GITHUB_TOKEN: ${{ secrets.ISSUE_TRANSLATOR_TOKEN }}
|
||||
BOT_GITHUB_TOKEN: ${{ secrets.ISSUE_TRANSLATOR_TOKEN }}
|
||||
|
||||
@@ -22,4 +22,4 @@ jobs:
|
||||
# max_tokens: 10000
|
||||
MAX_PATCH_LENGTH: 10000
|
||||
IGNORE_PATTERNS: "/node_modules,*.md,/dist,/.github"
|
||||
FILE_PATTERNS: "*.java,*.go,*.py,*.vue,*.ts,*.js,*.css,*.scss,*.html"
|
||||
FILE_PATTERNS: "*.java,*.go,*.py,*.vue,*.ts,*.js,*.css,*.scss,*.html"
|
||||
|
||||
@@ -17,4 +17,4 @@ jobs:
|
||||
uses: SonarSource/sonarcloud-github-action@master
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUBTOKEN }}
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||
|
||||
@@ -12,4 +12,4 @@ jobs:
|
||||
dst_key: ${{ secrets.GITEE_PRIVATE_KEY }}
|
||||
dst_token: ${{ secrets.GITEE_TOKEN }}
|
||||
static_list: "1Panel"
|
||||
force_update: true
|
||||
force_update: true
|
||||
|
||||
@@ -8,4 +8,4 @@ jobs:
|
||||
- name: Checkout Actions Repository
|
||||
uses: actions/checkout@v2
|
||||
- name: Check spelling
|
||||
uses: crate-ci/typos@master
|
||||
uses: crate-ci/typos@master
|
||||
|
||||
+7
-21
@@ -4,8 +4,7 @@
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
build/1panel-agent
|
||||
build/1panel-core
|
||||
build/1panel
|
||||
|
||||
# Mac
|
||||
.DS_Store
|
||||
@@ -15,7 +14,6 @@ build/1panel-core
|
||||
.vscode
|
||||
*.project
|
||||
*.factorypath
|
||||
__debug*
|
||||
|
||||
# IntelliJ IDEA
|
||||
.idea/*
|
||||
@@ -24,7 +22,6 @@ __debug*
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
@@ -34,23 +31,15 @@ __debug*
|
||||
# Dependency directories
|
||||
/pkg/
|
||||
backend/__debug_bin
|
||||
core/cmd/server/__debug_bin
|
||||
core/cmd/server/web/assets
|
||||
core/cmd/server/web/monacoeditorwork
|
||||
core/cmd/server/web/index.html
|
||||
cmd/server/__debug_bin
|
||||
cmd/server/web/assets
|
||||
cmd/server/web/monacoeditorwork
|
||||
cmd/server/web/index.html
|
||||
frontend/auto-imports.d.ts
|
||||
frontend/components.d.ts
|
||||
frontend/src/xpack
|
||||
agent/xpack
|
||||
agent/router/entry_xpack.go
|
||||
agent/server/init_xpack.go
|
||||
agent/utils/xpack/xpack_xpack.go
|
||||
core/xpack
|
||||
core/router/entry_xpack.go
|
||||
core/server/init_xpack.go
|
||||
core/utils/xpack/xpack_xpack.go
|
||||
xpack
|
||||
|
||||
backend/xpack
|
||||
*_xpack.go
|
||||
.history/
|
||||
dist/
|
||||
1pctl
|
||||
@@ -60,6 +49,3 @@ quick_start.sh
|
||||
cmd/server/fileList.txt
|
||||
.fileList.txt
|
||||
1Panel.code-workspace
|
||||
|
||||
core/.golangci.yml
|
||||
agent/.golangci.yml
|
||||
+8
-37
@@ -9,37 +9,11 @@ before:
|
||||
- chmod +x ./ci/script.sh
|
||||
- ./ci/script.sh
|
||||
- sed -i 's@ORIGINAL_VERSION=.*@ORIGINAL_VERSION=v{{ .Version }}@g' 1pctl
|
||||
|
||||
builds:
|
||||
- id: agent
|
||||
dir: agent
|
||||
main: cmd/server/main.go
|
||||
binary: 1panel-agent
|
||||
flags:
|
||||
- -tags=xpack
|
||||
- -trimpath
|
||||
ldflags:
|
||||
- -w -s
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
- linux
|
||||
goarm:
|
||||
- 7
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
- arm
|
||||
- ppc64le
|
||||
- s390x
|
||||
hooks:
|
||||
post:
|
||||
- ./ci/script.sh compress_binary {{ .Path }} {{ .Arch }}
|
||||
- go mod tidy
|
||||
|
||||
- id: core
|
||||
dir: core
|
||||
main: cmd/server/main.go
|
||||
binary: 1panel-core
|
||||
builds:
|
||||
- main: ./cmd/server/main.go
|
||||
binary: 1panel
|
||||
flags:
|
||||
- -tags=xpack
|
||||
- -trimpath
|
||||
@@ -57,22 +31,19 @@ builds:
|
||||
- arm
|
||||
- ppc64le
|
||||
- s390x
|
||||
hooks:
|
||||
post:
|
||||
- ./ci/script.sh compress_binary {{ .Path }} {{ .Arch }}
|
||||
- riscv64
|
||||
|
||||
archives:
|
||||
- formats: [ 'tar.gz' ]
|
||||
ids: [core, agent]
|
||||
name_template: "1panel-v{{ .Version }}-{{ .Os }}-{{ .Arch }}{{- if .Arm }}v{{ .Arm }}{{ end }}"
|
||||
wrap_in_directory: true
|
||||
files:
|
||||
- 1pctl
|
||||
- 1panel-core.service
|
||||
- 1panel-agent.service
|
||||
- install.sh
|
||||
- GeoIP.mmdb
|
||||
- 1panel.service
|
||||
- initscript/*
|
||||
- lang/*
|
||||
- GeoIP.mmdb
|
||||
|
||||
checksum:
|
||||
name_template: 'checksums.txt'
|
||||
|
||||
Generated
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 3.3 KiB |
Vendored
-3
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"ansible.python.interpreterPath": "/opt/homebrew/bin/python3"
|
||||
}
|
||||
+1
-1
@@ -125,4 +125,4 @@ enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
https://www.contributor-covenant.org/translations.
|
||||
|
||||
@@ -2,48 +2,33 @@ GOCMD=go
|
||||
GOBUILD=$(GOCMD) build
|
||||
GOCLEAN=$(GOCMD) clean
|
||||
GOARCH=$(shell go env GOARCH)
|
||||
GOOS=$(shell go env GOOS )
|
||||
GOOS=$(shell go env GOOS)
|
||||
|
||||
BASE_PATH := $(shell pwd)
|
||||
BUILD_PATH = $(BASE_PATH)/build
|
||||
WEB_PATH=$(BASE_PATH)/frontend
|
||||
ASSERT_PATH= $(BASE_PATH)/core/cmd/server/web/assets
|
||||
|
||||
CORE_PATH=$(BASE_PATH)/core
|
||||
CORE_MAIN=$(CORE_PATH)/cmd/server/main.go
|
||||
CORE_NAME=1panel-core
|
||||
|
||||
AGENT_PATH=$(BASE_PATH)/agent
|
||||
AGENT_MAIN=$(AGENT_PATH)/cmd/server/main.go
|
||||
AGENT_NAME=1panel-agent
|
||||
|
||||
SERVER_PATH=$(BASE_PATH)/backend
|
||||
MAIN= $(BASE_PATH)/cmd/server/main.go
|
||||
APP_NAME=1panel
|
||||
ASSERT_PATH= $(BASE_PATH)/cmd/server/web/assets
|
||||
|
||||
clean_assets:
|
||||
rm -rf $(ASSERT_PATH)
|
||||
|
||||
upx_bin:
|
||||
upx $(BUILD_PATH)/$(CORE_NAME)
|
||||
upx $(BUILD_PATH)/$(AGENT_NAME)
|
||||
upx $(BUILD_PATH)/$(APP_NAME)
|
||||
|
||||
build_frontend:
|
||||
cd $(WEB_PATH) && npm install && npm run build:pro
|
||||
|
||||
build_core_on_linux:
|
||||
cd $(CORE_PATH) \
|
||||
&& GOOS=$(GOOS) GOARCH=$(GOARCH) $(GOBUILD) -trimpath -ldflags '-s -w' -o $(BUILD_PATH)/$(CORE_NAME) $(CORE_MAIN)
|
||||
build_backend_on_linux:
|
||||
cd $(SERVER_PATH) \
|
||||
&& CGO_ENABLED=0 GOOS=$(GOOS) GOARCH=$(GOARCH) $(GOBUILD) -trimpath -ldflags '-s -w' -o $(BUILD_PATH)/$(APP_NAME) $(MAIN)
|
||||
|
||||
build_agent_on_linux:
|
||||
cd $(AGENT_PATH) \
|
||||
&& GOOS=$(GOOS) GOARCH=$(GOARCH) $(GOBUILD) -trimpath -ldflags '-s -w' -o $(BUILD_PATH)/$(AGENT_NAME) $(AGENT_MAIN)
|
||||
build_backend_on_darwin:
|
||||
cd $(SERVER_PATH) \
|
||||
&& GOOS=linux GOARCH=amd64 $(GOBUILD) -trimpath -ldflags '-s -w' -o $(BUILD_PATH)/$(APP_NAME) $(MAIN)
|
||||
|
||||
build_core_on_darwin:
|
||||
cd $(CORE_PATH) \
|
||||
&& GOOS=linux GOARCH=amd64 $(GOBUILD) -trimpath -ldflags '-s -w' -o $(BUILD_PATH)/$(CORE_NAME) $(CORE_MAIN)
|
||||
build_all: build_frontend build_backend_on_linux
|
||||
|
||||
build_agent_on_darwin:
|
||||
cd $(AGENT_PATH) \
|
||||
&& GOOS=linux GOARCH=amd64 $(GOBUILD) -trimpath -ldflags '-s -w' -o $(BUILD_PATH)/$(AGENT_NAME) $(AGENT_MAIN)
|
||||
|
||||
build_all: build_frontend build_core_on_linux build_agent_on_linux
|
||||
|
||||
build_on_local: clean_assets build_frontend build_core_on_darwin build_agent_on_darwin upx_bin
|
||||
build_on_local: clean_assets build_frontend build_backend_on_darwin
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<p align="center"><a href="https://1panel.pro"><img src="https://resource.1panel.pro/img/1panel-logo.png" alt="1Panel" width="300" /></a></p>
|
||||
<p align="center"><b>Top-Rated Web-based Linux Server Management Tool</b><br>Best VPS control panel<br>新一代的 Linux 服务器运维管理面板</p>
|
||||
<p align="center"><b>Simplify Linux Server Management</b></p>
|
||||
<p align="center">
|
||||
<a href="https://trendshift.io/repositories/2462" target="_blank"><img src="https://trendshift.io/api/badge/repositories/2462" alt="1Panel-dev%2F1Panel | Trendshift" style="width: 240px; height: auto;" /></a>
|
||||
</p>
|
||||
@@ -31,13 +31,14 @@
|
||||
|
||||
------------------------------
|
||||
|
||||
1Panel is an open-source, modern web-based control panel for Linux server management.
|
||||
1Panel provides an intuitive web interface and MCP Server to manage websites, files, containers, databases, and LLMs on a Linux server.
|
||||
|
||||
- **Efficient Management**: Through a user-friendly web graphical interface, 1Panel enables users to effortlessly manage their Linux servers. Key features include host monitoring, file management, database administration, container management, LLMs management.
|
||||
- **Rapid Website Deployment**: With deep integration of the popular open-source website building software WordPress, 1Panel streamlines the process of domain binding and SSL certificate configuration, all achievable with just one click.
|
||||
- **Application Store**: 1Panel curates a wide range of high-quality open-source tools and applications, facilitating easy installation and updates for its users.
|
||||
- **Security and Reliability**: By leveraging containerization and secure application deployment practices, 1Panel minimizes vulnerability exposure. It further enhances security through integrated firewall management and log auditing capabilities.
|
||||
- **One-Click Backup & Restore**: Data protection is made simple with 1Panel's one-click backup and restore functionality, supporting various cloud storage solutions to ensure data integrity and availability.
|
||||
- **MCP Server**: [1Panel MCP Server](https://github.com/1Panel-dev/mcp-1panel) allow user to execute server operations via natural language.
|
||||
|
||||
## Quick Start
|
||||
|
||||
@@ -49,7 +50,7 @@ curl -sSL https://resource.1panel.pro/quick_start.sh -o quick_start.sh && bash q
|
||||
|
||||
Please refer to our [documentation](https://docs.1panel.pro/quick_start/) for more details.
|
||||
|
||||
中国用户请使用这个 [安装脚本](https://1panel.cn/docs/installation/online_installation/),其应用数量比国际版本更丰富。
|
||||
中国用户请使用此 [安装脚本](https://1panel.cn/docs/v2/installation/online_installation/),默认安装 V2 版本,应用数量相较国际版本更丰富。
|
||||
|
||||
## Screenshot
|
||||
|
||||
@@ -61,16 +62,26 @@ Please refer to our [documentation](https://docs.1panel.pro/quick_start/) for mo
|
||||
|
||||
## Pro Edition
|
||||
|
||||
Compared to the OSS Edition, 1Panel Pro Edition provides users with a wealth of enhanced features and technical support services. Enhanced features include WAF enhancement, website tamper protection, website monitoring, GPU monitoring, custom logo and theme color, etc. [Click to view the detailed introduction of the Pro Edition](https://1panel.pro/pricing).
|
||||
Compared to the OSS Edition, 1Panel Pro Edition provides users with a wealth of enhanced features and technical support services. Enhanced features include WAF enhancement, Website monitoring, Mobile APP, custom logo and theme, etc.
|
||||
|
||||
[Click to see Pro Edition details](https://1panel.pro/pricing).
|
||||
|
||||
## Security Information
|
||||
|
||||
If you discover any security issues, please refer to [SECURITY.md](/SECURITY.md).
|
||||
|
||||
## GitAds Sponsored
|
||||
|
||||
[](https://gitads.dev/v1/ad-track?source=1panel-dev/1panel@github)
|
||||
|
||||
## License
|
||||
|
||||
Licensed under The GNU General Public License version 3 (GPLv3) (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
|
||||
|
||||
<https://www.gnu.org/licenses/gpl-3.0.html>
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
||||
|
||||
<!-- GitAds-Verify: 9M8AMBB11GYJ3RUNXGB5R1MR71KPATU9 -->
|
||||
|
||||
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// @Tags App
|
||||
// @Summary List Upgrade Ignored App
|
||||
// @Accept json
|
||||
// @Success 200 {array} model.AppIgnoreUpgrade
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /apps/ignored/detail [get]
|
||||
func (b *BaseApi) ListAppIgnored(c *gin.Context) {
|
||||
res, err := appIgnoreUpgradeService.List()
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, res)
|
||||
}
|
||||
|
||||
// @Tags App
|
||||
// @Summary Ignore Upgrade App
|
||||
// @Accept json
|
||||
// @Param request body request.AppIgnoreUpgradeReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /apps/installed/ignore [post]
|
||||
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"忽略应用升级","formatEN":"Ignore application upgrade"}
|
||||
func (b *BaseApi) IgnoreAppUpgrade(c *gin.Context) {
|
||||
var req request.AppIgnoreUpgradeReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := appIgnoreUpgradeService.CreateAppIgnore(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// 写一个去掉忽略的接口
|
||||
// @Tags App
|
||||
// @Summary Cancel Ignore Upgrade App
|
||||
// @Accept json
|
||||
// @Param request body request.ReqWithID true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /apps/ignored/cancel [post]
|
||||
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"取消忽略应用升级","formatEN":"Cancel ignore application upgrade"}
|
||||
func (b *BaseApi) CancelIgnoreAppUpgrade(c *gin.Context) {
|
||||
var req request.ReqWithID
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := appIgnoreUpgradeService.Delete(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
@@ -1,169 +0,0 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// @Tags Dashboard
|
||||
// @Summary Load os info
|
||||
// @Accept json
|
||||
// @Success 200 {object} dto.OsInfo
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /dashboard/base/os [get]
|
||||
func (b *BaseApi) LoadDashboardOsInfo(c *gin.Context) {
|
||||
data, err := dashboardService.LoadOsInfo()
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags Dashboard
|
||||
// @Summary Load app launcher
|
||||
// @Accept json
|
||||
// @Success 200 {Array} dto.AppLauncher
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /dashboard/app/launcher [get]
|
||||
func (b *BaseApi) LoadAppLauncher(c *gin.Context) {
|
||||
data, err := dashboardService.LoadAppLauncher(c)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags Dashboard
|
||||
// @Summary Load app launcher options
|
||||
// @Accept json
|
||||
// @Param request body dto.SearchByFilter true "request"
|
||||
// @Success 200 {Array} dto.LauncherOption
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /dashboard/app/launcher/option [post]
|
||||
func (b *BaseApi) LoadAppLauncherOption(c *gin.Context) {
|
||||
var req dto.SearchByFilter
|
||||
if err := helper.CheckBind(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
data, err := dashboardService.ListLauncherOption(req.Filter)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags Dashboard
|
||||
// @Summary Update app Launcher
|
||||
// @Accept json
|
||||
// @Param request body dto.SettingUpdate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /dashboard/app/launcher/show [post]
|
||||
// @x-panel-log {"bodyKeys":["key", "value"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"首页应用 [key] => 显示:[value]","formatEN":"app launcher [key] => show: [value]"}
|
||||
func (b *BaseApi) UpdateAppLauncher(c *gin.Context) {
|
||||
var req dto.SettingUpdate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := dashboardService.ChangeShow(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Dashboard
|
||||
// @Summary Load dashboard base info
|
||||
// @Accept json
|
||||
// @Param ioOption path string true "request"
|
||||
// @Param netOption path string true "request"
|
||||
// @Success 200 {object} dto.DashboardBase
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /dashboard/base/:ioOption/:netOption [get]
|
||||
func (b *BaseApi) LoadDashboardBaseInfo(c *gin.Context) {
|
||||
ioOption, ok := c.Params.Get("ioOption")
|
||||
if !ok {
|
||||
helper.BadRequest(c, errors.New("error ioOption in path"))
|
||||
return
|
||||
}
|
||||
netOption, ok := c.Params.Get("netOption")
|
||||
if !ok {
|
||||
helper.BadRequest(c, errors.New("error ioOption in path"))
|
||||
return
|
||||
}
|
||||
data, err := dashboardService.LoadBaseInfo(ioOption, netOption)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags Dashboard
|
||||
// @Summary Load dashboard current info for node
|
||||
// @Success 200 {object} dto.NodeCurrent
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /dashboard/current/node [get]
|
||||
func (b *BaseApi) LoadCurrentInfoForNode(c *gin.Context) {
|
||||
data := dashboardService.LoadCurrentInfoForNode()
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags Dashboard
|
||||
// @Summary Load dashboard current info
|
||||
// @Accept json
|
||||
// @Param ioOption path string true "request"
|
||||
// @Param netOption path string true "request"
|
||||
// @Success 200 {object} dto.DashboardCurrent
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /dashboard/current/:ioOption/:netOption [get]
|
||||
func (b *BaseApi) LoadDashboardCurrentInfo(c *gin.Context) {
|
||||
ioOption, ok := c.Params.Get("ioOption")
|
||||
if !ok {
|
||||
helper.BadRequest(c, errors.New("error ioOption in path"))
|
||||
return
|
||||
}
|
||||
netOption, ok := c.Params.Get("netOption")
|
||||
if !ok {
|
||||
helper.BadRequest(c, errors.New("error netOption in path"))
|
||||
return
|
||||
}
|
||||
|
||||
data := dashboardService.LoadCurrentInfo(ioOption, netOption)
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags Dashboard
|
||||
// @Summary System restart
|
||||
// @Accept json
|
||||
// @Param operation path string true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /dashboard/system/restart/:operation [post]
|
||||
func (b *BaseApi) SystemRestart(c *gin.Context) {
|
||||
operation, ok := c.Params.Get("operation")
|
||||
if !ok {
|
||||
helper.BadRequest(c, errors.New("error operation in path"))
|
||||
return
|
||||
}
|
||||
if err := dashboardService.Restart(operation); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func (b *BaseApi) CheckHealth(c *gin.Context) {
|
||||
helper.Success(c)
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
package helper
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func ErrorWithDetail(ctx *gin.Context, code int, msgKey string, err error) {
|
||||
res := dto.Response{
|
||||
Code: code,
|
||||
Message: "",
|
||||
}
|
||||
res.Message = i18n.GetMsgWithDetail(msgKey, err.Error())
|
||||
ctx.JSON(http.StatusOK, res)
|
||||
ctx.Abort()
|
||||
}
|
||||
|
||||
func InternalServer(ctx *gin.Context, err error) {
|
||||
ErrorWithDetail(ctx, http.StatusInternalServerError, "ErrInternalServer", err)
|
||||
}
|
||||
|
||||
func BadRequest(ctx *gin.Context, err error) {
|
||||
ErrorWithDetail(ctx, http.StatusBadRequest, "ErrInvalidParams", err)
|
||||
}
|
||||
|
||||
func SuccessWithData(ctx *gin.Context, data interface{}) {
|
||||
if data == nil {
|
||||
data = gin.H{}
|
||||
}
|
||||
res := dto.Response{
|
||||
Code: http.StatusOK,
|
||||
Data: data,
|
||||
}
|
||||
ctx.JSON(http.StatusOK, res)
|
||||
ctx.Abort()
|
||||
}
|
||||
|
||||
func Success(ctx *gin.Context) {
|
||||
res := dto.Response{
|
||||
Code: http.StatusOK,
|
||||
Message: "success",
|
||||
}
|
||||
ctx.JSON(http.StatusOK, res)
|
||||
ctx.Abort()
|
||||
}
|
||||
|
||||
func SuccessWithMsg(ctx *gin.Context, msg string) {
|
||||
res := dto.Response{
|
||||
Code: http.StatusOK,
|
||||
Message: msg,
|
||||
}
|
||||
ctx.JSON(http.StatusOK, res)
|
||||
ctx.Abort()
|
||||
}
|
||||
|
||||
func GetParamID(c *gin.Context) (uint, error) {
|
||||
idParam, ok := c.Params.Get("id")
|
||||
if !ok {
|
||||
return 0, errors.New("error id in path")
|
||||
}
|
||||
intNum, _ := strconv.Atoi(idParam)
|
||||
return uint(intNum), nil
|
||||
}
|
||||
|
||||
func GetIntParamByKey(c *gin.Context, key string) (uint, error) {
|
||||
idParam, ok := c.Params.Get(key)
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("error %s in path", key)
|
||||
}
|
||||
intNum, _ := strconv.Atoi(idParam)
|
||||
return uint(intNum), nil
|
||||
}
|
||||
|
||||
func GetStrParamByKey(c *gin.Context, key string) (string, error) {
|
||||
idParam, ok := c.Params.Get(key)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("error %s in path", key)
|
||||
}
|
||||
return idParam, nil
|
||||
}
|
||||
|
||||
func GetTxAndContext() (tx *gorm.DB, ctx context.Context) {
|
||||
tx = global.DB.Begin()
|
||||
ctx = context.WithValue(context.Background(), constant.DB, tx)
|
||||
return
|
||||
}
|
||||
|
||||
func CheckBindAndValidate(req interface{}, c *gin.Context) error {
|
||||
if err := c.ShouldBindJSON(req); err != nil {
|
||||
ErrorWithDetail(c, http.StatusBadRequest, "ErrInvalidParams", err)
|
||||
return err
|
||||
}
|
||||
if err := global.VALID.Struct(req); err != nil {
|
||||
ErrorWithDetail(c, http.StatusBadRequest, "ErrInvalidParams", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func CheckBind(req interface{}, c *gin.Context) error {
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
ErrorWithDetail(c, http.StatusBadRequest, "ErrInvalidParams", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ErrResponse(ctx *gin.Context, code int) {
|
||||
ctx.JSON(code, nil)
|
||||
ctx.Abort()
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// @Tags Logs
|
||||
// @Summary Load system log files
|
||||
// @Success 200 {array} string
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /logs/system/files [get]
|
||||
func (b *BaseApi) GetSystemFiles(c *gin.Context) {
|
||||
data, err := logService.ListSystemLogFile()
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/shirou/gopsutil/v3/disk"
|
||||
"github.com/shirou/gopsutil/v3/net"
|
||||
)
|
||||
|
||||
// @Tags Monitor
|
||||
// @Summary Load monitor data
|
||||
// @Param request body dto.MonitorSearch true "request"
|
||||
// @Success 200 {array} dto.MonitorData
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/monitor/search [post]
|
||||
func (b *BaseApi) LoadMonitor(c *gin.Context) {
|
||||
var req dto.MonitorSearch
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
data, err := monitorService.LoadMonitorData(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags Monitor
|
||||
// @Summary Clean monitor data
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/monitor/clean [post]
|
||||
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"清空监控数据","formatEN":"clean monitor datas"}
|
||||
func (b *BaseApi) CleanMonitor(c *gin.Context) {
|
||||
if err := monitorService.CleanData(); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Monitor
|
||||
// @Summary Load monitor setting
|
||||
// @Success 200 {object} dto.MonitorSetting
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/monitor/setting [get]
|
||||
func (b *BaseApi) LoadMonitorSetting(c *gin.Context) {
|
||||
setting, err := monitorService.LoadSetting()
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.SuccessWithData(c, setting)
|
||||
}
|
||||
|
||||
// @Tags Monitor
|
||||
// @Summary Update monitor setting
|
||||
// @Param request body dto.MonitorSettingUpdate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/monitor/setting/update [post]
|
||||
// @x-panel-log {"bodyKeys":["key", "value"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改默认监控网卡 [name]-[value]","formatEN":"update default monitor [name]-[value]"}
|
||||
func (b *BaseApi) UpdateMonitorSetting(c *gin.Context) {
|
||||
var req dto.MonitorSettingUpdate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := monitorService.UpdateSetting(req.Key, req.Value); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
func (b *BaseApi) GetNetworkOptions(c *gin.Context) {
|
||||
netStat, _ := net.IOCounters(true)
|
||||
var options []string
|
||||
options = append(options, "all")
|
||||
for _, net := range netStat {
|
||||
options = append(options, net.Name)
|
||||
}
|
||||
sort.Strings(options)
|
||||
helper.SuccessWithData(c, options)
|
||||
}
|
||||
|
||||
func (b *BaseApi) GetIOOptions(c *gin.Context) {
|
||||
diskStat, _ := disk.IOCounters()
|
||||
var options []string
|
||||
options = append(options, "all")
|
||||
for _, net := range diskStat {
|
||||
options = append(options, net.Name)
|
||||
}
|
||||
sort.Strings(options)
|
||||
helper.SuccessWithData(c, options)
|
||||
}
|
||||
@@ -1,537 +0,0 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// @Tags Runtime
|
||||
// @Summary List runtimes
|
||||
// @Accept json
|
||||
// @Param request body request.RuntimeSearch true "request"
|
||||
// @Success 200 {object} dto.PageResult
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /runtimes/search [post]
|
||||
func (b *BaseApi) SearchRuntimes(c *gin.Context) {
|
||||
var req request.RuntimeSearch
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
total, items, err := runtimeService.Page(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, dto.PageResult{
|
||||
Total: total,
|
||||
Items: items,
|
||||
})
|
||||
}
|
||||
|
||||
// @Tags Runtime
|
||||
// @Summary Create runtime
|
||||
// @Accept json
|
||||
// @Param request body request.RuntimeCreate true "request"
|
||||
// @Success 200 {object} model.Runtime
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /runtimes [post]
|
||||
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建运行环境 [name]","formatEN":"Create runtime [name]"}
|
||||
func (b *BaseApi) CreateRuntime(c *gin.Context) {
|
||||
var req request.RuntimeCreate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
ssl, err := runtimeService.Create(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, ssl)
|
||||
}
|
||||
|
||||
// @Tags Website
|
||||
// @Summary Delete runtime
|
||||
// @Accept json
|
||||
// @Param request body request.RuntimeDelete true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /runtimes/del [post]
|
||||
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"删除运行环境 [name]","formatEN":"Delete runtime [name]"}
|
||||
func (b *BaseApi) DeleteRuntime(c *gin.Context) {
|
||||
var req request.RuntimeDelete
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
err := runtimeService.Delete(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Website
|
||||
// @Summary Delete runtime
|
||||
// @Accept json
|
||||
// @Success 200 {array} dto.AppResource
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /installed/delete/check/:id [get]
|
||||
func (b *BaseApi) DeleteRuntimeCheck(c *gin.Context) {
|
||||
runTimeId, err := helper.GetIntParamByKey(c, "id")
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
checkData, err := runtimeService.DeleteCheck(runTimeId)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, checkData)
|
||||
}
|
||||
|
||||
// @Tags Runtime
|
||||
// @Summary Update runtime
|
||||
// @Accept json
|
||||
// @Param request body request.RuntimeUpdate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /runtimes/update [post]
|
||||
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新运行环境 [name]","formatEN":"Update runtime [name]"}
|
||||
func (b *BaseApi) UpdateRuntime(c *gin.Context) {
|
||||
var req request.RuntimeUpdate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := runtimeService.Update(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Runtime
|
||||
// @Summary Get runtime
|
||||
// @Accept json
|
||||
// @Param id path string true "request"
|
||||
// @Success 200 {object} response.RuntimeDTO
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /runtimes/:id [get]
|
||||
func (b *BaseApi) GetRuntime(c *gin.Context) {
|
||||
id, err := helper.GetIntParamByKey(c, "id")
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
res, err := runtimeService.Get(id)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, res)
|
||||
}
|
||||
|
||||
// @Tags Runtime
|
||||
// @Summary Get Node package scripts
|
||||
// @Accept json
|
||||
// @Param request body request.NodePackageReq true "request"
|
||||
// @Success 200 {array} response.PackageScripts
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /runtimes/node/package [post]
|
||||
func (b *BaseApi) GetNodePackageRunScript(c *gin.Context) {
|
||||
var req request.NodePackageReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
res, err := runtimeService.GetNodePackageRunScript(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, res)
|
||||
}
|
||||
|
||||
// @Tags Runtime
|
||||
// @Summary Operate runtime
|
||||
// @Accept json
|
||||
// @Param request body request.RuntimeOperate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /runtimes/operate [post]
|
||||
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"操作运行环境 [name]","formatEN":"Operate runtime [name]"}
|
||||
func (b *BaseApi) OperateRuntime(c *gin.Context) {
|
||||
var req request.RuntimeOperate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
err := runtimeService.OperateRuntime(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Runtime
|
||||
// @Summary Get Node modules
|
||||
// @Accept json
|
||||
// @Param request body request.NodeModuleReq true "request"
|
||||
// @Success 200 {array} response.NodeModule
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /runtimes/node/modules [post]
|
||||
func (b *BaseApi) GetNodeModules(c *gin.Context) {
|
||||
var req request.NodeModuleReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
res, err := runtimeService.GetNodeModules(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, res)
|
||||
}
|
||||
|
||||
// @Tags Runtime
|
||||
// @Summary Operate Node modules
|
||||
// @Accept json
|
||||
// @Param request body request.NodeModuleReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /runtimes/node/modules/operate [post]
|
||||
func (b *BaseApi) OperateNodeModules(c *gin.Context) {
|
||||
var req request.NodeModuleOperateReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
err := runtimeService.OperateNodeModules(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Runtime
|
||||
// @Summary Sync runtime status
|
||||
// @Accept json
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /runtimes/sync [post]
|
||||
func (b *BaseApi) SyncStatus(c *gin.Context) {
|
||||
err := runtimeService.SyncRuntimeStatus()
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Runtime
|
||||
// @Summary Get php runtime extension
|
||||
// @Accept json
|
||||
// @Param id path string true "request"
|
||||
// @Success 200 {object} response.PHPExtensionRes
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /runtimes/php/:id/extensions [get]
|
||||
func (b *BaseApi) GetRuntimeExtension(c *gin.Context) {
|
||||
id, err := helper.GetIntParamByKey(c, "id")
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
res, err := runtimeService.GetPHPExtensions(id)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, res)
|
||||
}
|
||||
|
||||
// @Tags Runtime
|
||||
// @Summary Install php extension
|
||||
// @Accept json
|
||||
// @Param request body request.PHPExtensionInstallReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /runtimes/php/extensions/install [post]
|
||||
func (b *BaseApi) InstallPHPExtension(c *gin.Context) {
|
||||
var req request.PHPExtensionInstallReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
err := runtimeService.InstallPHPExtension(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Runtime
|
||||
// @Summary UnInstall php extension
|
||||
// @Accept json
|
||||
// @Param request body request.PHPExtensionInstallReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /runtimes/php/extensions/uninstall [post]
|
||||
func (b *BaseApi) UnInstallPHPExtension(c *gin.Context) {
|
||||
var req request.PHPExtensionInstallReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
err := runtimeService.UnInstallPHPExtension(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Runtime
|
||||
// @Summary Load php runtime conf
|
||||
// @Accept json
|
||||
// @Param id path integer true "request"
|
||||
// @Success 200 {object} response.PHPConfig
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /runtimes/php/config/:id [get]
|
||||
func (b *BaseApi) GetPHPConfig(c *gin.Context) {
|
||||
id, err := helper.GetParamID(c)
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
data, err := runtimeService.GetPHPConfig(id)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags Runtime
|
||||
// @Summary Update runtime php conf
|
||||
// @Accept json
|
||||
// @Param request body request.PHPConfigUpdate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /runtimes/php/config [post]
|
||||
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"[domain] PHP 配置修改","formatEN":"[domain] PHP conf update"}
|
||||
func (b *BaseApi) UpdatePHPConfig(c *gin.Context) {
|
||||
var req request.PHPConfigUpdate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := runtimeService.UpdatePHPConfig(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Runtime
|
||||
// @Summary Update php conf file
|
||||
// @Accept json
|
||||
// @Param request body request.PHPFileUpdate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /runtimes/php/update [post]
|
||||
// @x-panel-log {"bodyKeys":["websiteId"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"websiteId","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"php 配置修改 [domain]","formatEN":"Nginx conf update [domain]"}
|
||||
func (b *BaseApi) UpdatePHPFile(c *gin.Context) {
|
||||
var req request.PHPFileUpdate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := runtimeService.UpdatePHPConfigFile(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Runtime
|
||||
// @Summary Get php conf file
|
||||
// @Accept json
|
||||
// @Param request body request.PHPFileReq true "request"
|
||||
// @Success 200 {object} response.FileInfo
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /runtimes/php/file [post]
|
||||
func (b *BaseApi) GetPHPConfigFile(c *gin.Context) {
|
||||
var req request.PHPFileReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
data, err := runtimeService.GetPHPConfigFile(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags Runtime
|
||||
// @Summary Update fpm config
|
||||
// @Accept json
|
||||
// @Param request body request.FPMConfig true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /runtimes/php/fpm/config [post]
|
||||
func (b *BaseApi) UpdateFPMConfig(c *gin.Context) {
|
||||
var req request.FPMConfig
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := runtimeService.UpdateFPMConfig(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Runtime
|
||||
// @Summary Get fpm config
|
||||
// @Accept json
|
||||
// @Param id path integer true "request"
|
||||
// @Success 200 {object} request.FPMConfig
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /runtimes/php/fpm/config/:id [get]
|
||||
func (b *BaseApi) GetFPMConfig(c *gin.Context) {
|
||||
id, err := helper.GetParamID(c)
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
data, err := runtimeService.GetFPMConfig(id)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags Runtime
|
||||
// @Summary Get supervisor process
|
||||
// @Accept json
|
||||
// @Param id path integer true "request"
|
||||
// @Success 200 {array} response.SupervisorProcessConfig
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /runtimes/supervisor/process/:id [get]
|
||||
func (b *BaseApi) GetSupervisorProcess(c *gin.Context) {
|
||||
id, err := helper.GetParamID(c)
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
data, err := runtimeService.GetSupervisorProcess(id)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags Runtime
|
||||
// @Summary Operate supervisor process
|
||||
// @Accept json
|
||||
// @Param request body request.PHPSupervisorProcessConfig true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /runtimes/supervisor/process/operate [post]
|
||||
func (b *BaseApi) OperateSupervisorProcess(c *gin.Context) {
|
||||
var req request.PHPSupervisorProcessConfig
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
err := runtimeService.OperateSupervisorProcess(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Runtime
|
||||
// @Summary Operate supervisor process file
|
||||
// @Accept json
|
||||
// @Param request body request.PHPSupervisorProcessFileReq true "request"
|
||||
// @Success 200 {string} content
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /runtimes/supervisor/process/file/operate [post]
|
||||
func (b *BaseApi) OperateSupervisorProcessFile(c *gin.Context) {
|
||||
var req request.PHPSupervisorProcessFileReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
res, err := runtimeService.OperateSupervisorProcessFile(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, res)
|
||||
}
|
||||
|
||||
// @Tags Runtime
|
||||
// @Summary Update PHP container config
|
||||
// @Accept json
|
||||
// @Param request body request.PHPContainerUpdate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /runtimes/php/container/update [post]
|
||||
func (b *BaseApi) UpdatePHPContainer(c *gin.Context) {
|
||||
var req request.PHPContainerConfig
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := runtimeService.UpdatePHPContainer(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Runtime
|
||||
// @Summary Get PHP container config
|
||||
// @Accept json
|
||||
// @Param id path integer true "request"
|
||||
// @Success 200 {object} response.PHPContainerConfig
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /runtimes/php/container/:id [get]
|
||||
func (b *BaseApi) GetPHPContainerConfig(c *gin.Context) {
|
||||
id, err := helper.GetParamID(c)
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
data, err := runtimeService.GetPHPContainerConfig(id)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"os/user"
|
||||
"path"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/ssh"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// @Tags System Setting
|
||||
// @Summary Load system setting info
|
||||
// @Success 200 {object} dto.SettingInfo
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /settings/search [post]
|
||||
func (b *BaseApi) GetSettingInfo(c *gin.Context) {
|
||||
setting, err := settingService.GetSettingInfo()
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, setting)
|
||||
}
|
||||
|
||||
// @Tags System Setting
|
||||
// @Summary Load system available status
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /settings/search/available [get]
|
||||
func (b *BaseApi) GetSystemAvailable(c *gin.Context) {
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags System Setting
|
||||
// @Summary Update system setting
|
||||
// @Accept json
|
||||
// @Param request body dto.SettingUpdate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /settings/update [post]
|
||||
// @x-panel-log {"bodyKeys":["key","value"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改系统配置 [key] => [value]","formatEN":"update system setting [key] => [value]"}
|
||||
func (b *BaseApi) UpdateSetting(c *gin.Context) {
|
||||
var req dto.SettingUpdate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := settingService.Update(req.Key, req.Value); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags System Setting
|
||||
// @Summary Load local backup dir
|
||||
// @Success 200 {string} path
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /settings/basedir [get]
|
||||
func (b *BaseApi) LoadBaseDir(c *gin.Context) {
|
||||
helper.SuccessWithData(c, global.Dir.DataDir)
|
||||
}
|
||||
|
||||
func (b *BaseApi) CheckLocalConn(c *gin.Context) {
|
||||
_, err := loadLocalConn()
|
||||
helper.SuccessWithData(c, err == nil)
|
||||
}
|
||||
|
||||
// @Tags System Setting
|
||||
// @Summary Check local conn info
|
||||
// @Success 200 {bool} isOk
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /settings/ssh/check/info [post]
|
||||
func (b *BaseApi) CheckLocalConnByInfo(c *gin.Context) {
|
||||
var req dto.SSHConnData
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, settingService.TestConnByInfo(req))
|
||||
}
|
||||
|
||||
// @Tags System Setting
|
||||
// @Summary Save local conn info
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /settings/ssh [post]
|
||||
func (b *BaseApi) SaveLocalConnInfo(c *gin.Context) {
|
||||
var req dto.SSHConnData
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, settingService.SaveConnInfo(req))
|
||||
}
|
||||
|
||||
func loadLocalConn() (*ssh.SSHClient, error) {
|
||||
itemPath := ""
|
||||
currentInfo, _ := user.Current()
|
||||
if len(currentInfo.HomeDir) == 0 {
|
||||
itemPath = "/root/.ssh/id_ed25519_1panel"
|
||||
} else {
|
||||
itemPath = path.Join(currentInfo.HomeDir, ".ssh/id_ed25519_1panel")
|
||||
}
|
||||
if _, err := os.Stat(itemPath); err != nil {
|
||||
_ = sshService.GenerateSSH(dto.GenerateSSH{EncryptionMode: "ed25519", Name: "_1panel"})
|
||||
}
|
||||
|
||||
privateKey, _ := os.ReadFile(itemPath)
|
||||
connWithKey := ssh.ConnInfo{
|
||||
Addr: "127.0.0.1",
|
||||
User: "root",
|
||||
Port: 22,
|
||||
AuthMode: "key",
|
||||
PrivateKey: privateKey,
|
||||
}
|
||||
client, err := ssh.NewClient(connWithKey)
|
||||
if err == nil {
|
||||
return client, nil
|
||||
}
|
||||
|
||||
connInfoInDB, err := settingService.GetSSHInfo()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(connInfoInDB) == 0 {
|
||||
return nil, errors.New("no such ssh conn info in db!")
|
||||
}
|
||||
var connInDB ssh.ConnInfo
|
||||
if err := json.Unmarshal([]byte(connInfoInDB), &connInDB); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ssh.NewClient(connInDB)
|
||||
}
|
||||
|
||||
// @Tags System Setting
|
||||
// @Summary Load system setting by key
|
||||
// @Param key path string true "key"
|
||||
// @Success 200 {object} dto.SettingInfo
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /settings/get/{key} [get]
|
||||
func (b *BaseApi) GetSettingByKey(c *gin.Context) {
|
||||
key := c.Param("key")
|
||||
if len(key) == 0 {
|
||||
helper.BadRequest(c, errors.New("key is empty"))
|
||||
return
|
||||
}
|
||||
value := settingService.GetSettingByKey(key)
|
||||
helper.SuccessWithData(c, value)
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// @Tags TaskLog
|
||||
// @Summary Page task logs
|
||||
// @Accept json
|
||||
// @Param request body dto.SearchTaskLogReq true "request"
|
||||
// @Success 200 {object} dto.PageResult
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /logs/tasks/search [post]
|
||||
func (b *BaseApi) PageTasks(c *gin.Context) {
|
||||
var req dto.SearchTaskLogReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
total, list, err := taskService.Page(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, dto.PageResult{
|
||||
Items: list,
|
||||
Total: total,
|
||||
})
|
||||
}
|
||||
|
||||
// @Tags TaskLog
|
||||
// @Summary Get the number of executing tasks
|
||||
// @Success 200 {object} int64
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /logs/tasks/executing/count [get]
|
||||
func (b *BaseApi) CountExecutingTasks(c *gin.Context) {
|
||||
count, err := taskService.CountExecutingTask()
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, count)
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type BackupOperate struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type" validate:"required"`
|
||||
IsPublic bool `json:"isPublic"`
|
||||
Bucket string `json:"bucket"`
|
||||
AccessKey string `json:"accessKey"`
|
||||
Credential string `json:"credential"`
|
||||
BackupPath string `json:"backupPath"`
|
||||
Vars string `json:"vars" validate:"required"`
|
||||
|
||||
RememberAuth bool `json:"rememberAuth"`
|
||||
}
|
||||
|
||||
type BackupInfo struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
IsPublic bool `json:"isPublic"`
|
||||
Bucket string `json:"bucket"`
|
||||
AccessKey string `json:"accessKey"`
|
||||
Credential string `json:"credential"`
|
||||
BackupPath string `json:"backupPath"`
|
||||
Vars string `json:"vars"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
|
||||
RememberAuth bool `json:"rememberAuth"`
|
||||
}
|
||||
|
||||
type ForBuckets struct {
|
||||
Type string `json:"type" validate:"required"`
|
||||
AccessKey string `json:"accessKey"`
|
||||
Credential string `json:"credential" validate:"required"`
|
||||
Vars string `json:"vars" validate:"required"`
|
||||
}
|
||||
|
||||
type SyncFromMaster struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Operation string `json:"operation" validate:"required,oneof=create delete update"`
|
||||
Data string `json:"data"`
|
||||
}
|
||||
|
||||
type BackupOption struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
IsPublic bool `json:"isPublic"`
|
||||
}
|
||||
|
||||
type CommonBackup struct {
|
||||
Type string `json:"type" validate:"required,oneof=app mysql mariadb redis website postgresql"`
|
||||
Name string `json:"name"`
|
||||
DetailName string `json:"detailName"`
|
||||
Secret string `json:"secret"`
|
||||
TaskID string `json:"taskID"`
|
||||
FileName string `json:"fileName"`
|
||||
|
||||
Description string `json:"description"`
|
||||
}
|
||||
type CommonRecover struct {
|
||||
DownloadAccountID uint `json:"downloadAccountID" validate:"required"`
|
||||
Type string `json:"type" validate:"required,oneof=app mysql mariadb redis website postgresql"`
|
||||
Name string `json:"name"`
|
||||
DetailName string `json:"detailName"`
|
||||
File string `json:"file"`
|
||||
Secret string `json:"secret"`
|
||||
TaskID string `json:"taskID"`
|
||||
BackupRecordID uint `json:"backupRecordID"`
|
||||
}
|
||||
|
||||
type RecordSearch struct {
|
||||
PageInfo
|
||||
Type string `json:"type" validate:"required"`
|
||||
Name string `json:"name"`
|
||||
DetailName string `json:"detailName"`
|
||||
}
|
||||
|
||||
type RecordSearchByCronjob struct {
|
||||
PageInfo
|
||||
CronjobID uint `json:"cronjobID" validate:"required"`
|
||||
}
|
||||
|
||||
type BackupRecords struct {
|
||||
ID uint `json:"id"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
AccountType string `json:"accountType"`
|
||||
AccountName string `json:"accountName"`
|
||||
DownloadAccountID uint `json:"downloadAccountID"`
|
||||
FileDir string `json:"fileDir"`
|
||||
FileName string `json:"fileName"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type DownloadRecord struct {
|
||||
DownloadAccountID uint `json:"downloadAccountID" validate:"required"`
|
||||
FileDir string `json:"fileDir" validate:"required"`
|
||||
FileName string `json:"fileName" validate:"required"`
|
||||
}
|
||||
|
||||
type SearchForSize struct {
|
||||
PageInfo
|
||||
Type string `json:"type" validate:"required"`
|
||||
Name string `json:"name"`
|
||||
DetailName string `json:"detailName"`
|
||||
Info string `json:"info"`
|
||||
CronjobID uint `json:"cronjobID"`
|
||||
OrderBy string `json:"orderBy"`
|
||||
Order string `json:"order"`
|
||||
}
|
||||
type RecordFileSize struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Size int64 `json:"size"`
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type PageCronjob struct {
|
||||
PageInfo
|
||||
Info string `json:"info"`
|
||||
OrderBy string `json:"orderBy" validate:"required,oneof=name status createdAt"`
|
||||
Order string `json:"order" validate:"required,oneof=null ascending descending"`
|
||||
}
|
||||
|
||||
type CronjobSpec struct {
|
||||
Spec string `json:"spec" validate:"required"`
|
||||
}
|
||||
|
||||
type CronjobOperate struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
Type string `json:"type" validate:"required"`
|
||||
SpecCustom bool `json:"specCustom"`
|
||||
Spec string `json:"spec" validate:"required"`
|
||||
|
||||
Executor string `json:"executor"`
|
||||
ScriptMode string `json:"scriptMode"`
|
||||
Script string `json:"script"`
|
||||
Command string `json:"command"`
|
||||
ContainerName string `json:"containerName"`
|
||||
User string `json:"user"`
|
||||
|
||||
ScriptID uint `json:"scriptID"`
|
||||
AppID string `json:"appID"`
|
||||
Website string `json:"website"`
|
||||
ExclusionRules string `json:"exclusionRules"`
|
||||
DBType string `json:"dbType"`
|
||||
DBName string `json:"dbName"`
|
||||
URL string `json:"url"`
|
||||
IsDir bool `json:"isDir"`
|
||||
SourceDir string `json:"sourceDir"`
|
||||
|
||||
SourceAccountIDs string `json:"sourceAccountIDs"`
|
||||
DownloadAccountID uint `json:"downloadAccountID"`
|
||||
RetainCopies int `json:"retainCopies" validate:"number,min=1"`
|
||||
RetryTimes int `json:"retryTimes" validate:"number,min=0"`
|
||||
Timeout uint `json:"timeout" validate:"number,min=1"`
|
||||
Secret string `json:"secret"`
|
||||
|
||||
AlertCount uint `json:"alertCount"`
|
||||
AlertTitle string `json:"alertTitle"`
|
||||
}
|
||||
|
||||
type CronjobUpdateStatus struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
Status string `json:"status" validate:"required"`
|
||||
}
|
||||
|
||||
type CronjobDownload struct {
|
||||
RecordID uint `json:"recordID" validate:"required"`
|
||||
BackupAccountID uint `json:"backupAccountID" validate:"required"`
|
||||
}
|
||||
|
||||
type CronjobClean struct {
|
||||
IsDelete bool `json:"isDelete"`
|
||||
CleanData bool `json:"cleanData"`
|
||||
CronjobID uint `json:"cronjobID" validate:"required"`
|
||||
CleanRemoteData bool `json:"cleanRemoteData"`
|
||||
}
|
||||
|
||||
type CronjobBatchDelete struct {
|
||||
CleanData bool `json:"cleanData"`
|
||||
CleanRemoteData bool `json:"cleanRemoteData"`
|
||||
IDs []uint `json:"ids" validate:"required"`
|
||||
}
|
||||
|
||||
type CronjobInfo struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
SpecCustom bool `json:"specCustom"`
|
||||
Spec string `json:"spec"`
|
||||
|
||||
Executor string `json:"executor"`
|
||||
ScriptMode string `json:"scriptMode"`
|
||||
Script string `json:"script"`
|
||||
Command string `json:"command"`
|
||||
ContainerName string `json:"containerName"`
|
||||
User string `json:"user"`
|
||||
|
||||
ScriptID uint `json:"scriptID"`
|
||||
AppID string `json:"appID"`
|
||||
Website string `json:"website"`
|
||||
ExclusionRules string `json:"exclusionRules"`
|
||||
DBType string `json:"dbType"`
|
||||
DBName string `json:"dbName"`
|
||||
URL string `json:"url"`
|
||||
IsDir bool `json:"isDir"`
|
||||
SourceDir string `json:"sourceDir"`
|
||||
RetainCopies int `json:"retainCopies"`
|
||||
RetryTimes int `json:"retryTimes"`
|
||||
Timeout uint `json:"timeout"`
|
||||
|
||||
SourceAccounts []string `json:"sourceAccounts"`
|
||||
DownloadAccount string `json:"downloadAccount"`
|
||||
SourceAccountIDs string `json:"sourceAccountIDs"`
|
||||
DownloadAccountID uint `json:"downloadAccountID"`
|
||||
|
||||
LastRecordStatus string `json:"lastRecordStatus"`
|
||||
LastRecordTime string `json:"lastRecordTime"`
|
||||
Status string `json:"status"`
|
||||
Secret string `json:"secret"`
|
||||
|
||||
AlertCount uint `json:"alertCount"`
|
||||
}
|
||||
|
||||
type ScriptOptions struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type SearchRecord struct {
|
||||
PageInfo
|
||||
CronjobID int `json:"cronjobID"`
|
||||
StartTime time.Time `json:"startTime"`
|
||||
EndTime time.Time `json:"endTime"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
type Record struct {
|
||||
ID uint `json:"id"`
|
||||
TaskID string `json:"taskID"`
|
||||
StartTime string `json:"startTime"`
|
||||
Records string `json:"records"`
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
TargetPath string `json:"targetPath"`
|
||||
Interval int `json:"interval"`
|
||||
File string `json:"file"`
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package dto
|
||||
|
||||
type GroupCreate struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
Type string `json:"type" validate:"required"`
|
||||
}
|
||||
|
||||
type GroupSearch struct {
|
||||
Type string `json:"type" validate:"required"`
|
||||
}
|
||||
|
||||
type GroupUpdate struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type" validate:"required"`
|
||||
IsDefault bool `json:"isDefault"`
|
||||
}
|
||||
|
||||
type GroupInfo struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
IsDefault bool `json:"isDefault"`
|
||||
}
|
||||
|
||||
type OperateByType struct {
|
||||
Type string `json:"type"`
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
)
|
||||
|
||||
type SearchTaskLogReq struct {
|
||||
Status string `json:"status"`
|
||||
Type string `json:"type"`
|
||||
PageInfo
|
||||
}
|
||||
|
||||
type TaskDTO struct {
|
||||
model.Task
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package request
|
||||
|
||||
type AppIgnoreUpgradeReq struct {
|
||||
AppID uint `json:"appID" validate:"required"`
|
||||
AppDetailID uint `json:"appDetailID"`
|
||||
Scope string `json:"scope" validate:"required,oneof=all version"`
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
package request
|
||||
|
||||
type ReqWithID struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
package request
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
)
|
||||
|
||||
type RuntimeSearch struct {
|
||||
dto.PageInfo
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
type RuntimeCreate struct {
|
||||
AppDetailID uint `json:"appDetailId"`
|
||||
Name string `json:"name"`
|
||||
Params map[string]interface{} `json:"params"`
|
||||
Resource string `json:"resource"`
|
||||
Image string `json:"image"`
|
||||
Type string `json:"type"`
|
||||
Version string `json:"version"`
|
||||
Source string `json:"source"`
|
||||
CodeDir string `json:"codeDir"`
|
||||
NodeConfig
|
||||
}
|
||||
|
||||
type NodeConfig struct {
|
||||
Install bool `json:"install"`
|
||||
Clean bool `json:"clean"`
|
||||
ExposedPorts []ExposedPort `json:"exposedPorts"`
|
||||
Environments []Environment `json:"environments"`
|
||||
Volumes []Volume `json:"volumes"`
|
||||
}
|
||||
|
||||
type Environment struct {
|
||||
Key string `json:"key"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
type Volume struct {
|
||||
Source string `json:"source"`
|
||||
Target string `json:"target"`
|
||||
}
|
||||
|
||||
type ExposedPort struct {
|
||||
HostPort int `json:"hostPort"`
|
||||
ContainerPort int `json:"containerPort"`
|
||||
HostIP string `json:"hostIP"`
|
||||
}
|
||||
|
||||
type RuntimeDelete struct {
|
||||
ID uint `json:"id"`
|
||||
ForceDelete bool `json:"forceDelete"`
|
||||
}
|
||||
|
||||
type RuntimeUpdate struct {
|
||||
Name string `json:"name"`
|
||||
ID uint `json:"id"`
|
||||
Params map[string]interface{} `json:"params"`
|
||||
Image string `json:"image"`
|
||||
Version string `json:"version"`
|
||||
Rebuild bool `json:"rebuild"`
|
||||
Source string `json:"source"`
|
||||
CodeDir string `json:"codeDir"`
|
||||
NodeConfig
|
||||
}
|
||||
|
||||
type NodePackageReq struct {
|
||||
CodeDir string `json:"codeDir"`
|
||||
}
|
||||
|
||||
type RuntimeOperate struct {
|
||||
Operate string `json:"operate"`
|
||||
ID uint `json:"ID"`
|
||||
}
|
||||
|
||||
type NodeModuleOperateReq struct {
|
||||
Operate string `json:"operate" validate:"oneof=install uninstall update"`
|
||||
ID uint `json:"ID" validate:"required"`
|
||||
Module string `json:"module"`
|
||||
PkgManager string `json:"pkgManager" validate:"oneof=npm yarn"`
|
||||
}
|
||||
|
||||
type NodeModuleReq struct {
|
||||
ID uint `json:"ID" validate:"required"`
|
||||
}
|
||||
|
||||
type PHPExtensionInstallReq struct {
|
||||
ID uint `json:"ID" validate:"required"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
TaskID string `json:"taskID"`
|
||||
}
|
||||
|
||||
type PHPConfigUpdate struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
Params map[string]string `json:"params"`
|
||||
Scope string `json:"scope" validate:"required"`
|
||||
DisableFunctions []string `json:"disableFunctions"`
|
||||
UploadMaxSize string `json:"uploadMaxSize"`
|
||||
}
|
||||
|
||||
type PHPFileUpdate struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
Type string `json:"type" validate:"required"`
|
||||
Content string `json:"content" validate:"required"`
|
||||
}
|
||||
|
||||
type PHPFileReq struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
Type string `json:"type" validate:"required"`
|
||||
}
|
||||
|
||||
type FPMConfig struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
Params map[string]interface{} `json:"params" validate:"required"`
|
||||
}
|
||||
|
||||
type PHPSupervisorProcessConfig struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
SupervisorProcessConfig
|
||||
}
|
||||
|
||||
type PHPSupervisorProcessFileReq struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
SupervisorProcessFileReq
|
||||
}
|
||||
|
||||
type PHPContainerConfig struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
ContainerName string `json:"containerName"`
|
||||
ExposedPorts []ExposedPort `json:"exposedPorts"`
|
||||
Environments []Environment `json:"environments"`
|
||||
Volumes []Volume `json:"volumes"`
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
package request
|
||||
@@ -1,10 +0,0 @@
|
||||
package response
|
||||
|
||||
type AppIgnoreUpgradeDTO struct {
|
||||
ID uint `json:"ID"`
|
||||
AppID uint `json:"appID"`
|
||||
AppDetailID uint `json:"appDetailID"`
|
||||
Scope string `json:"scope"`
|
||||
Version string `json:"version"`
|
||||
Icon string `json:"icon"`
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
package response
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
)
|
||||
|
||||
type RuntimeDTO struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Resource string `json:"resource"`
|
||||
AppDetailID uint `json:"appDetailID"`
|
||||
AppID uint `json:"appID"`
|
||||
Source string `json:"source"`
|
||||
Status string `json:"status"`
|
||||
Type string `json:"type"`
|
||||
Image string `json:"image"`
|
||||
Params map[string]interface{} `json:"params"`
|
||||
Message string `json:"message"`
|
||||
Version string `json:"version"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
CodeDir string `json:"codeDir"`
|
||||
AppParams []AppParam `json:"appParams"`
|
||||
Port string `json:"port"`
|
||||
Path string `json:"path"`
|
||||
ExposedPorts []request.ExposedPort `json:"exposedPorts"`
|
||||
Environments []request.Environment `json:"environments"`
|
||||
Volumes []request.Volume `json:"volumes"`
|
||||
ContainerStatus string `json:"containerStatus"`
|
||||
Container string `json:"container"`
|
||||
}
|
||||
|
||||
type PackageScripts struct {
|
||||
Name string `json:"name"`
|
||||
Script string `json:"script"`
|
||||
}
|
||||
|
||||
func NewRuntimeDTO(runtime model.Runtime) RuntimeDTO {
|
||||
return RuntimeDTO{
|
||||
ID: runtime.ID,
|
||||
Name: runtime.Name,
|
||||
Resource: runtime.Resource,
|
||||
AppDetailID: runtime.AppDetailID,
|
||||
Status: runtime.Status,
|
||||
Type: runtime.Type,
|
||||
Image: runtime.Image,
|
||||
Message: runtime.Message,
|
||||
CreatedAt: runtime.CreatedAt,
|
||||
CodeDir: runtime.CodeDir,
|
||||
Version: runtime.Version,
|
||||
Port: runtime.Port,
|
||||
Path: runtime.GetPath(),
|
||||
Container: runtime.ContainerName,
|
||||
}
|
||||
}
|
||||
|
||||
type NodeModule struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
License string `json:"license"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type SupportExtension struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Installed bool `json:"installed"`
|
||||
Check string `json:"check"`
|
||||
Versions []string `json:"versions"`
|
||||
File string `json:"file"`
|
||||
}
|
||||
|
||||
type PHPExtensionRes struct {
|
||||
Extensions []string `json:"extensions"`
|
||||
SupportExtensions []SupportExtension `json:"supportExtensions"`
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
package response
|
||||
@@ -1,82 +0,0 @@
|
||||
package dto
|
||||
|
||||
type SettingInfo struct {
|
||||
DockerSockPath string `json:"dockerSockPath"`
|
||||
SystemVersion string `json:"systemVersion"`
|
||||
SystemIP string `json:"systemIP"`
|
||||
|
||||
LocalTime string `json:"localTime"`
|
||||
TimeZone string `json:"timeZone"`
|
||||
NtpSite string `json:"ntpSite"`
|
||||
|
||||
DefaultNetwork string `json:"defaultNetwork"`
|
||||
LastCleanTime string `json:"lastCleanTime"`
|
||||
LastCleanSize string `json:"lastCleanSize"`
|
||||
LastCleanData string `json:"lastCleanData"`
|
||||
|
||||
MonitorStatus string `json:"monitorStatus"`
|
||||
MonitorInterval string `json:"monitorInterval"`
|
||||
MonitorStoreDays string `json:"monitorStoreDays"`
|
||||
|
||||
AppStoreVersion string `json:"appStoreVersion"`
|
||||
AppStoreLastModified string `json:"appStoreLastModified"`
|
||||
AppStoreSyncStatus string `json:"appStoreSyncStatus"`
|
||||
|
||||
FileRecycleBin string `json:"fileRecycleBin"`
|
||||
|
||||
SnapshotIgnore string `json:"snapshotIgnore"`
|
||||
}
|
||||
|
||||
type SettingUpdate struct {
|
||||
Key string `json:"key" validate:"required"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type SyncTime struct {
|
||||
NtpSite string `json:"ntpSite" validate:"required"`
|
||||
}
|
||||
|
||||
type CleanData struct {
|
||||
SystemClean []CleanTree `json:"systemClean"`
|
||||
UploadClean []CleanTree `json:"uploadClean"`
|
||||
DownloadClean []CleanTree `json:"downloadClean"`
|
||||
SystemLogClean []CleanTree `json:"systemLogClean"`
|
||||
ContainerClean []CleanTree `json:"containerClean"`
|
||||
}
|
||||
|
||||
type CleanTree struct {
|
||||
ID string `json:"id"`
|
||||
Label string `json:"label"`
|
||||
Children []CleanTree `json:"children"`
|
||||
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
|
||||
Size uint64 `json:"size"`
|
||||
IsCheck bool `json:"isCheck"`
|
||||
IsRecommend bool `json:"isRecommend"`
|
||||
}
|
||||
|
||||
type Clean struct {
|
||||
TreeType string `json:"treeType"`
|
||||
Name string `json:"name"`
|
||||
Size uint64 `json:"size"`
|
||||
}
|
||||
|
||||
type SSHConnData struct {
|
||||
Addr string `json:"addr" validate:"required"`
|
||||
Port uint `json:"port" validate:"required,number,max=65535,min=1"`
|
||||
User string `json:"user" validate:"required"`
|
||||
AuthMode string `json:"authMode" validate:"oneof=password key"`
|
||||
Password string `json:"password"`
|
||||
PrivateKey string `json:"privateKey"`
|
||||
PassPhrase string `json:"passPhrase"`
|
||||
}
|
||||
|
||||
type SystemProxy struct {
|
||||
Type string `json:"type"`
|
||||
URL string `json:"url"`
|
||||
Port string `json:"port"`
|
||||
User string `json:"user"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
package dto
|
||||
|
||||
import "time"
|
||||
|
||||
type PageSnapshot struct {
|
||||
PageInfo
|
||||
Info string `json:"info"`
|
||||
OrderBy string `json:"orderBy" validate:"required,oneof=name createdAt"`
|
||||
Order string `json:"order" validate:"required,oneof=null ascending descending"`
|
||||
}
|
||||
|
||||
type SnapshotCreate struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
TaskID string `json:"taskID"`
|
||||
SourceAccountIDs string `json:"sourceAccountIDs" validate:"required"`
|
||||
DownloadAccountID uint `json:"downloadAccountID" validate:"required"`
|
||||
Description string `json:"description" validate:"max=256"`
|
||||
Secret string `json:"secret"`
|
||||
InterruptStep string `json:"interruptStep"`
|
||||
|
||||
AppData []DataTree `json:"appData"`
|
||||
BackupData []DataTree `json:"backupData"`
|
||||
PanelData []DataTree `json:"panelData"`
|
||||
|
||||
WithMonitorData bool `json:"withMonitorData"`
|
||||
WithLoginLog bool `json:"withLoginLog"`
|
||||
WithOperationLog bool `json:"withOperationLog"`
|
||||
WithSystemLog bool `json:"withSystemLog"`
|
||||
WithTaskLog bool `json:"withTaskLog"`
|
||||
}
|
||||
|
||||
type SnapshotData struct {
|
||||
AppData []DataTree `json:"appData"`
|
||||
BackupData []DataTree `json:"backupData"`
|
||||
PanelData []DataTree `json:"panelData"`
|
||||
|
||||
WithMonitorData bool `json:"withMonitorData"`
|
||||
WithLoginLog bool `json:"withLoginLog"`
|
||||
WithOperationLog bool `json:"withOperationLog"`
|
||||
WithSystemLog bool `json:"withSystemLog"`
|
||||
WithTaskLog bool `json:"withTaskLog"`
|
||||
}
|
||||
type DataTree struct {
|
||||
ID string `json:"id"`
|
||||
Label string `json:"label"`
|
||||
Key string `json:"key"`
|
||||
Name string `json:"name"`
|
||||
Size uint64 `json:"size"`
|
||||
IsCheck bool `json:"isCheck"`
|
||||
IsDisable bool `json:"isDisable"`
|
||||
|
||||
Path string `json:"path"`
|
||||
|
||||
RelationItemID string `json:"relationItemID"`
|
||||
Children []DataTree `json:"children"`
|
||||
}
|
||||
type SnapshotRecover struct {
|
||||
IsNew bool `json:"isNew"`
|
||||
ReDownload bool `json:"reDownload"`
|
||||
ID uint `json:"id" validate:"required"`
|
||||
TaskID string `json:"taskID"`
|
||||
Secret string `json:"secret"`
|
||||
}
|
||||
type SnapshotBatchDelete struct {
|
||||
DeleteWithFile bool `json:"deleteWithFile"`
|
||||
Ids []uint `json:"ids" validate:"required"`
|
||||
}
|
||||
|
||||
type SnapshotImport struct {
|
||||
BackupAccountID uint `json:"backupAccountID"`
|
||||
Names []string `json:"names"`
|
||||
Description string `json:"description" validate:"max=256"`
|
||||
}
|
||||
|
||||
type SnapshotInfo struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description" validate:"max=256"`
|
||||
SourceAccounts []string `json:"sourceAccounts"`
|
||||
DownloadAccount string `json:"downloadAccount"`
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
Version string `json:"version"`
|
||||
Size int64 `json:"size"`
|
||||
|
||||
TaskID string `json:"taskID"`
|
||||
TaskRecoverID string `json:"taskRecoverID"`
|
||||
TaskRollbackID string `json:"taskRollbackID"`
|
||||
|
||||
InterruptStep string `json:"interruptStep"`
|
||||
RecoverStatus string `json:"recoverStatus"`
|
||||
RecoverMessage string `json:"recoverMessage"`
|
||||
LastRecoveredAt string `json:"lastRecoveredAt"`
|
||||
RollbackStatus string `json:"rollbackStatus"`
|
||||
RollbackMessage string `json:"rollbackMessage"`
|
||||
LastRollbackedAt string `json:"lastRollbackedAt"`
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gin-gonic/gin"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
BaseModel
|
||||
Name string `json:"name" gorm:"not null"`
|
||||
Key string `json:"key" gorm:"not null;"`
|
||||
ShortDescZh string `json:"shortDescZh" yaml:"shortDescZh"`
|
||||
ShortDescEn string `json:"shortDescEn" yaml:"shortDescEn"`
|
||||
Description string `json:"description"`
|
||||
Icon string `json:"icon"`
|
||||
Type string `json:"type" gorm:"not null"`
|
||||
Status string `json:"status" gorm:"not null"`
|
||||
Required string `json:"required"`
|
||||
CrossVersionUpdate bool `json:"crossVersionUpdate" yaml:"crossVersionUpdate"`
|
||||
Limit int `json:"limit" gorm:"not null"`
|
||||
Website string `json:"website" gorm:"not null"`
|
||||
Github string `json:"github" gorm:"not null"`
|
||||
Document string `json:"document" gorm:"not null"`
|
||||
Recommend int `json:"recommend" gorm:"not null"`
|
||||
Resource string `json:"resource" gorm:"not null;default:remote"`
|
||||
ReadMe string `json:"readMe"`
|
||||
LastModified int `json:"lastModified"`
|
||||
Architectures string `json:"architectures"`
|
||||
MemoryRequired int `json:"memoryRequired"`
|
||||
GpuSupport bool `json:"gpuSupport"`
|
||||
RequiredPanelVersion float64 `json:"requiredPanelVersion"`
|
||||
|
||||
Details []AppDetail `json:"-" gorm:"-:migration"`
|
||||
TagsKey []string `json:"tags" yaml:"tags" gorm:"-"`
|
||||
AppTags []AppTag `json:"-" gorm:"-:migration"`
|
||||
}
|
||||
|
||||
func (i *App) IsLocalApp() bool {
|
||||
return i.Resource == constant.ResourceLocal
|
||||
}
|
||||
func (i *App) GetAppResourcePath() string {
|
||||
if i.IsLocalApp() {
|
||||
//这里要去掉本地应用的local前缀
|
||||
return filepath.Join(global.Dir.LocalAppResourceDir, strings.TrimPrefix(i.Key, "local"))
|
||||
}
|
||||
return filepath.Join(global.Dir.RemoteAppResourceDir, i.Key)
|
||||
}
|
||||
|
||||
func getLang(c *gin.Context) string {
|
||||
lang := c.GetHeader("Accept-Language")
|
||||
if lang == "" {
|
||||
lang = "en"
|
||||
}
|
||||
return lang
|
||||
}
|
||||
|
||||
func (i *App) GetDescription(ctx *gin.Context) string {
|
||||
var translations = make(map[string]string)
|
||||
_ = json.Unmarshal([]byte(i.Description), &translations)
|
||||
lang := strings.ToLower(getLang(ctx))
|
||||
if desc, ok := translations[lang]; ok && desc != "" {
|
||||
return desc
|
||||
}
|
||||
if lang == "zh" && i.ShortDescZh != "" {
|
||||
return i.ShortDescZh
|
||||
}
|
||||
if desc, ok := translations["en"]; ok && desc != "" {
|
||||
return desc
|
||||
}
|
||||
return i.ShortDescEn
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package model
|
||||
|
||||
type AppDetail struct {
|
||||
BaseModel
|
||||
AppId uint `json:"appId" gorm:"not null"`
|
||||
Version string `json:"version" gorm:"not null"`
|
||||
Params string `json:"-"`
|
||||
DockerCompose string `json:"dockerCompose"`
|
||||
Status string `json:"status" gorm:"not null"`
|
||||
LastVersion string `json:"lastVersion"`
|
||||
LastModified int `json:"lastModified"`
|
||||
DownloadUrl string `json:"downloadUrl"`
|
||||
DownloadCallBackUrl string `json:"downloadCallBackUrl"`
|
||||
Update bool `json:"update"`
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package model
|
||||
|
||||
type AppIgnoreUpgrade struct {
|
||||
BaseModel
|
||||
AppID uint `json:"appID"`
|
||||
AppDetailID uint `json:"appDetailID"`
|
||||
Scope string `json:"scope"`
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
)
|
||||
|
||||
type AppInstall struct {
|
||||
BaseModel
|
||||
Name string `json:"name" gorm:"not null;UNIQUE"`
|
||||
AppId uint `json:"appId" gorm:"not null"`
|
||||
AppDetailId uint `json:"appDetailId" gorm:"not null"`
|
||||
Version string `json:"version" gorm:"not null"`
|
||||
Param string `json:"param"`
|
||||
Env string `json:"env"`
|
||||
DockerCompose string `json:"dockerCompose" `
|
||||
Status string `json:"status" gorm:"not null"`
|
||||
Description string `json:"description"`
|
||||
Message string `json:"message"`
|
||||
ContainerName string `json:"containerName" gorm:"not null"`
|
||||
ServiceName string `json:"serviceName" gorm:"not null"`
|
||||
HttpPort int `json:"httpPort"`
|
||||
HttpsPort int `json:"httpsPort"`
|
||||
WebUI string `json:"webUI"`
|
||||
Favorite bool `json:"favorite"`
|
||||
|
||||
App App `json:"app" gorm:"-:migration"`
|
||||
}
|
||||
|
||||
func (i *AppInstall) GetPath() string {
|
||||
return path.Join(i.GetAppPath(), i.Name)
|
||||
}
|
||||
|
||||
func (i *AppInstall) GetComposePath() string {
|
||||
return path.Join(i.GetAppPath(), i.Name, "docker-compose.yml")
|
||||
}
|
||||
|
||||
func (i *AppInstall) GetEnvPath() string {
|
||||
return path.Join(i.GetAppPath(), i.Name, ".env")
|
||||
}
|
||||
|
||||
func (i *AppInstall) GetAppPath() string {
|
||||
if i.App.Resource == constant.AppResourceLocal {
|
||||
return path.Join(global.Dir.LocalAppInstallDir, strings.TrimPrefix(i.App.Key, constant.AppResourceLocal))
|
||||
} else {
|
||||
return path.Join(global.Dir.AppInstallDir, i.App.Key)
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package model
|
||||
|
||||
type AppInstallResource struct {
|
||||
BaseModel
|
||||
AppInstallId uint `json:"appInstallId" gorm:"not null;"`
|
||||
LinkId uint `json:"linkId" gorm:"not null;"`
|
||||
ResourceId uint `json:"resourceId"`
|
||||
Key string `json:"key" gorm:"not null"`
|
||||
From string `json:"from" gorm:"not null;default:local"`
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
package model
|
||||
|
||||
type AppLauncher struct {
|
||||
BaseModel
|
||||
Key string `json:"key"`
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package model
|
||||
|
||||
type AppTag struct {
|
||||
BaseModel
|
||||
AppId uint `json:"appId" gorm:"not null"`
|
||||
TagId uint `json:"tagId" gorm:"not null"`
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package model
|
||||
|
||||
type BackupAccount struct {
|
||||
BaseModel
|
||||
Name string `gorm:"not null;default:''" json:"name"`
|
||||
Type string `gorm:"not null;default:''" json:"type"`
|
||||
IsPublic bool `json:"isPublic"`
|
||||
Bucket string `json:"bucket"`
|
||||
AccessKey string `json:"accessKey"`
|
||||
Credential string `json:"credential"`
|
||||
BackupPath string `json:"backupPath"`
|
||||
Vars string `json:"vars"`
|
||||
|
||||
RememberAuth bool `json:"rememberAuth"`
|
||||
}
|
||||
|
||||
type BackupRecord struct {
|
||||
BaseModel
|
||||
From string `json:"from"`
|
||||
CronjobID uint `json:"cronjobID"`
|
||||
SourceAccountIDs string `json:"sourceAccountIDs"`
|
||||
DownloadAccountID uint `json:"downloadAccountID"`
|
||||
|
||||
Type string `gorm:"not null;default:''" json:"type"`
|
||||
Name string `gorm:"not null;default:''" json:"name"`
|
||||
DetailName string `json:"detailName"`
|
||||
FileDir string `json:"fileDir"`
|
||||
FileName string `json:"fileName"`
|
||||
|
||||
Description string `json:"description"`
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package model
|
||||
|
||||
type Clam struct {
|
||||
BaseModel
|
||||
|
||||
Name string `gorm:"not null" json:"name"`
|
||||
Status string `json:"status"`
|
||||
Path string `gorm:"not null" json:"path"`
|
||||
InfectedStrategy string `json:"infectedStrategy"`
|
||||
InfectedDir string `json:"infectedDir"`
|
||||
Spec string `json:"spec"`
|
||||
EntryID int `json:"entryID"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package model
|
||||
|
||||
type ComposeTemplate struct {
|
||||
BaseModel
|
||||
|
||||
Name string `gorm:"not null;unique" json:"name"`
|
||||
Description string `json:"description"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
type Compose struct {
|
||||
BaseModel
|
||||
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path"`
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// BackupAccounts ---> SourceAccountIDs
|
||||
// BackupAccounts ---> DownloadAccountID
|
||||
type Cronjob struct {
|
||||
BaseModel
|
||||
|
||||
Name string `gorm:"not null" json:"name"`
|
||||
Type string `gorm:"not null" json:"type"`
|
||||
SpecCustom bool `json:"specCustom"`
|
||||
Spec string `gorm:"not null" json:"spec"`
|
||||
|
||||
Executor string `json:"executor"`
|
||||
Command string `json:"command"`
|
||||
ContainerName string `json:"containerName"`
|
||||
ScriptMode string `json:"scriptMode"`
|
||||
Script string `json:"script"`
|
||||
User string `json:"user"`
|
||||
|
||||
ScriptID uint `json:"scriptID"`
|
||||
Website string `json:"website"`
|
||||
AppID string `json:"appID"`
|
||||
DBType string `json:"dbType"`
|
||||
DBName string `json:"dbName"`
|
||||
URL string `json:"url"`
|
||||
IsDir bool `json:"isDir"`
|
||||
SourceDir string `json:"sourceDir"`
|
||||
ExclusionRules string `json:"exclusionRules"`
|
||||
|
||||
SourceAccountIDs string `json:"sourceAccountIDs"`
|
||||
DownloadAccountID uint `json:"downloadAccountID"`
|
||||
RetryTimes uint `json:"retryTimes"`
|
||||
Timeout uint `json:"timeout"`
|
||||
RetainCopies uint64 `json:"retainCopies"`
|
||||
|
||||
Status string `json:"status"`
|
||||
EntryIDs string `json:"entryIDs"`
|
||||
Records []JobRecords `json:"records"`
|
||||
Secret string `json:"secret"`
|
||||
}
|
||||
|
||||
type JobRecords struct {
|
||||
BaseModel
|
||||
|
||||
CronjobID uint `json:"cronjobID"`
|
||||
TaskID string `json:"taskID"`
|
||||
StartTime time.Time `json:"startTime"`
|
||||
Interval float64 `json:"interval"`
|
||||
Records string `json:"records"`
|
||||
FromLocal bool `json:"source"`
|
||||
File string `json:"file"`
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type ScriptLibrary struct {
|
||||
BaseModel
|
||||
Name string `json:"name" gorm:"not null;"`
|
||||
Script string `json:"script" gorm:"not null;"`
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package model
|
||||
|
||||
type Database struct {
|
||||
BaseModel
|
||||
AppInstallID uint `json:"appInstallID"`
|
||||
Name string `json:"name" gorm:"not null;unique"`
|
||||
Type string `json:"type" gorm:"not null"`
|
||||
Version string `json:"version" gorm:"not null"`
|
||||
From string `json:"from" gorm:"not null"`
|
||||
Address string `json:"address" gorm:"not null"`
|
||||
Port uint `json:"port" gorm:"not null"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
|
||||
SSL bool `json:"ssl"`
|
||||
RootCert string `json:"rootCert"`
|
||||
ClientKey string `json:"clientKey"`
|
||||
ClientCert string `json:"clientCert"`
|
||||
SkipVerify bool `json:"skipVerify"`
|
||||
|
||||
Description string `json:"description"`
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package model
|
||||
|
||||
type DatabaseMysql struct {
|
||||
BaseModel
|
||||
Name string `json:"name" gorm:"not null"`
|
||||
From string `json:"from" gorm:"not null;default:local"`
|
||||
MysqlName string `json:"mysqlName" gorm:"not null"`
|
||||
Format string `json:"format" gorm:"not null"`
|
||||
Username string `json:"username" gorm:"not null"`
|
||||
Password string `json:"password" gorm:"not null"`
|
||||
Permission string `json:"permission" gorm:"not null"`
|
||||
IsDelete bool `json:"isDelete"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package model
|
||||
|
||||
type DatabasePostgresql struct {
|
||||
BaseModel
|
||||
Name string `json:"name" gorm:"not null"`
|
||||
From string `json:"from" gorm:"not null;default:local"`
|
||||
PostgresqlName string `json:"postgresqlName" gorm:"not null"`
|
||||
Format string `json:"format" gorm:"not null"`
|
||||
Username string `json:"username" gorm:"not null"`
|
||||
Password string `json:"password" gorm:"not null"`
|
||||
SuperUser bool `json:"superUser"`
|
||||
IsDelete bool `json:"isDelete"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package model
|
||||
|
||||
type Favorite struct {
|
||||
BaseModel
|
||||
Name string `gorm:"not null;" json:"name" `
|
||||
Path string `gorm:"not null;unique" json:"path"`
|
||||
Type string `json:"type"`
|
||||
IsDir bool `json:"isDir"`
|
||||
IsTxt bool `json:"isTxt"`
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package model
|
||||
|
||||
type Firewall struct {
|
||||
BaseModel
|
||||
|
||||
Type string `gorm:"not null" json:"type"`
|
||||
Port string `gorm:"not null" json:"port"`
|
||||
Protocol string `gorm:"not null" json:"protocol"`
|
||||
Address string `gorm:"not null" json:"address"`
|
||||
Strategy string `gorm:"not null" json:"strategy"`
|
||||
Description string `gorm:"not null" json:"description"`
|
||||
}
|
||||
|
||||
type Forward struct {
|
||||
BaseModel
|
||||
|
||||
Protocol string `gorm:"not null" json:"protocol"`
|
||||
Port string `gorm:"not null" json:"port"`
|
||||
TargetIP string `gorm:"not null" json:"targetIP"`
|
||||
TargetPort string `gorm:"not null" json:"targetPort"`
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package model
|
||||
|
||||
type Ftp struct {
|
||||
BaseModel
|
||||
|
||||
User string `gorm:"not null" json:"user"`
|
||||
Password string `gorm:"not null" json:"password"`
|
||||
Status string `gorm:"not null" json:"status"`
|
||||
Path string `gorm:"not null" json:"path"`
|
||||
Description string `gorm:"not null" json:"description"`
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package model
|
||||
|
||||
type Group struct {
|
||||
BaseModel
|
||||
IsDefault bool `json:"isDefault"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package model
|
||||
|
||||
type ImageRepo struct {
|
||||
BaseModel
|
||||
|
||||
Name string `gorm:"not null" json:"name"`
|
||||
DownloadUrl string `json:"downloadUrl"`
|
||||
Protocol string `json:"protocol"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Auth bool `json:"auth"`
|
||||
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package model
|
||||
|
||||
type MonitorBase struct {
|
||||
BaseModel
|
||||
Cpu float64 `json:"cpu"`
|
||||
|
||||
LoadUsage float64 `json:"loadUsage"`
|
||||
CpuLoad1 float64 `json:"cpuLoad1"`
|
||||
CpuLoad5 float64 `json:"cpuLoad5"`
|
||||
CpuLoad15 float64 `json:"cpuLoad15"`
|
||||
|
||||
Memory float64 `json:"memory"`
|
||||
}
|
||||
|
||||
type MonitorIO struct {
|
||||
BaseModel
|
||||
Name string `json:"name"`
|
||||
Read uint64 `json:"read"`
|
||||
Write uint64 `json:"write"`
|
||||
Count uint64 `json:"count"`
|
||||
Time uint64 `json:"time"`
|
||||
}
|
||||
|
||||
type MonitorNetwork struct {
|
||||
BaseModel
|
||||
Name string `json:"name"`
|
||||
Up float64 `json:"up"`
|
||||
Down float64 `json:"down"`
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"path"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
)
|
||||
|
||||
type Runtime struct {
|
||||
BaseModel
|
||||
Name string `gorm:"not null" json:"name"`
|
||||
AppDetailID uint `json:"appDetailID"`
|
||||
Image string `json:"image"`
|
||||
WorkDir string `json:"workDir"`
|
||||
DockerCompose string `json:"dockerCompose"`
|
||||
Env string `json:"env"`
|
||||
Params string `json:"params"`
|
||||
Version string `gorm:"not null" json:"version"`
|
||||
Type string `gorm:"not null" json:"type"`
|
||||
Status string `gorm:"not null" json:"status"`
|
||||
Resource string `gorm:"not null" json:"resource"`
|
||||
Port string `json:"port"`
|
||||
Message string `json:"message"`
|
||||
CodeDir string `json:"codeDir"`
|
||||
ContainerName string `json:"containerName"`
|
||||
}
|
||||
|
||||
func (r *Runtime) GetComposePath() string {
|
||||
return path.Join(r.GetPath(), "docker-compose.yml")
|
||||
}
|
||||
|
||||
func (r *Runtime) GetEnvPath() string {
|
||||
return path.Join(r.GetPath(), ".env")
|
||||
}
|
||||
|
||||
func (r *Runtime) GetPath() string {
|
||||
return path.Join(global.Dir.RuntimeDir, r.Type, r.Name)
|
||||
}
|
||||
|
||||
func (r *Runtime) GetFPMPath() string {
|
||||
return path.Join(global.Dir.RuntimeDir, r.Type, r.Name, "conf", "php-fpm.conf")
|
||||
}
|
||||
|
||||
func (r *Runtime) GetPHPPath() string {
|
||||
return path.Join(global.Dir.RuntimeDir, r.Type, r.Name, "conf", "php.ini")
|
||||
}
|
||||
|
||||
func (r *Runtime) GetLogPath() string {
|
||||
return path.Join(r.GetPath(), "build.log")
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package model
|
||||
|
||||
type Setting struct {
|
||||
BaseModel
|
||||
Key string `json:"key" gorm:"not null;"`
|
||||
Value string `json:"value"`
|
||||
About string `json:"about"`
|
||||
}
|
||||
|
||||
type NodeInfo struct {
|
||||
Scope string `json:"scope"`
|
||||
BaseDir string `json:"baseDir"`
|
||||
NodePort uint `json:"nodePort"`
|
||||
Version string `json:"version"`
|
||||
ServerCrt string `json:"serverCrt"`
|
||||
ServerKey string `json:"serverKey"`
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package model
|
||||
|
||||
type Snapshot struct {
|
||||
BaseModel
|
||||
Name string `json:"name" gorm:"not null;unique"`
|
||||
Secret string `json:"secret"`
|
||||
Description string `json:"description"`
|
||||
SourceAccountIDs string `json:"sourceAccountIDs"`
|
||||
DownloadAccountID uint `json:"downloadAccountID"`
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
Version string `json:"version"`
|
||||
|
||||
TaskID string `json:"taskID"`
|
||||
TaskRecoverID string `json:"taskRecoverID"`
|
||||
TaskRollbackID string `json:"taskRollbackID"`
|
||||
|
||||
AppData string `json:"appData"`
|
||||
PanelData string `json:"panelData"`
|
||||
BackupData string `json:"backupData"`
|
||||
WithMonitorData bool `json:"withMonitorData"`
|
||||
WithLoginLog bool `json:"withLoginLog"`
|
||||
WithOperationLog bool `json:"withOperationLog"`
|
||||
WithSystemLog bool `json:"withSystemLog"`
|
||||
WithTaskLog bool `json:"withTaskLog"`
|
||||
|
||||
InterruptStep string `json:"interruptStep"`
|
||||
RecoverStatus string `json:"recoverStatus"`
|
||||
RecoverMessage string `json:"recoverMessage"`
|
||||
RollbackStatus string `json:"rollbackStatus"`
|
||||
RollbackMessage string `json:"rollbackMessage"`
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package model
|
||||
|
||||
type Tag struct {
|
||||
BaseModel
|
||||
Key string `json:"key" gorm:"not null"`
|
||||
Name string `json:"name" gorm:"not null"`
|
||||
Sort int `json:"sort" gorm:"not null;default:1"`
|
||||
Translations string `json:"translations"`
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
type Task struct {
|
||||
ID string `gorm:"primarykey;" json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Operate string `json:"operate"`
|
||||
LogFile string `json:"logFile"`
|
||||
Status string `json:"status"`
|
||||
ErrorMsg string `json:"errorMsg"`
|
||||
OperationLogID uint `json:"operationLogID"`
|
||||
ResourceID uint `json:"resourceID"`
|
||||
CurrentStep string `json:"currentStep"`
|
||||
EndAt time.Time `json:"endAt"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
type Website struct {
|
||||
BaseModel
|
||||
Protocol string `gorm:"not null" json:"protocol"`
|
||||
PrimaryDomain string `gorm:"not null" json:"primaryDomain"`
|
||||
Type string `gorm:"not null" json:"type"`
|
||||
Alias string `gorm:"not null" json:"alias"`
|
||||
Remark string `json:"remark"`
|
||||
Status string `gorm:"not null" json:"status"`
|
||||
HttpConfig string `gorm:"not null" json:"httpConfig"`
|
||||
ExpireDate time.Time `json:"expireDate"`
|
||||
|
||||
Proxy string `json:"proxy"`
|
||||
ProxyType string `json:"proxyType"`
|
||||
SiteDir string `json:"siteDir"`
|
||||
ErrorLog bool `json:"errorLog"`
|
||||
AccessLog bool `json:"accessLog"`
|
||||
DefaultServer bool `json:"defaultServer"`
|
||||
IPV6 bool `json:"IPV6"`
|
||||
Rewrite string `json:"rewrite"`
|
||||
|
||||
WebsiteGroupID uint `json:"webSiteGroupId"`
|
||||
WebsiteSSLID uint `json:"webSiteSSLId"`
|
||||
RuntimeID uint `json:"runtimeID"`
|
||||
AppInstallID uint `json:"appInstallId"`
|
||||
FtpID uint `json:"ftpId"`
|
||||
ParentWebsiteID uint `json:"parentWebsiteID"`
|
||||
|
||||
User string `json:"user"`
|
||||
Group string `json:"group"`
|
||||
|
||||
DbType string `json:"dbType"`
|
||||
DbID uint `json:"dbID"`
|
||||
|
||||
Favorite bool `json:"favorite"`
|
||||
|
||||
Domains []WebsiteDomain `json:"domains" gorm:"-:migration"`
|
||||
WebsiteSSL WebsiteSSL `json:"webSiteSSL" gorm:"-:migration"`
|
||||
}
|
||||
|
||||
func (w Website) TableName() string {
|
||||
return "websites"
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package model
|
||||
|
||||
type WebsiteDnsAccount struct {
|
||||
BaseModel
|
||||
Name string `gorm:"not null" json:"name"`
|
||||
Type string `gorm:"not null" json:"type"`
|
||||
Authorization string `gorm:"not null" json:"-"`
|
||||
}
|
||||
|
||||
func (w WebsiteDnsAccount) TableName() string {
|
||||
return "website_dns_accounts"
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package model
|
||||
|
||||
type WebsiteDomain struct {
|
||||
BaseModel
|
||||
WebsiteID uint `gorm:"column:website_id;not null;" json:"websiteId"`
|
||||
Domain string `gorm:"not null" json:"domain"`
|
||||
SSL bool `json:"ssl"`
|
||||
Port int `json:"port"`
|
||||
}
|
||||
|
||||
func (w WebsiteDomain) TableName() string {
|
||||
return "website_domains"
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type AppIgnoreUpgradeRepo struct {
|
||||
}
|
||||
|
||||
type IAppIgnoreUpgradeRepo interface {
|
||||
WithScope(scope string) DBOption
|
||||
List(opts ...DBOption) ([]model.AppIgnoreUpgrade, error)
|
||||
Create(appIgnoreUpgrade *model.AppIgnoreUpgrade) error
|
||||
Delete(opts ...DBOption) error
|
||||
}
|
||||
|
||||
func NewIAppIgnoreUpgradeRepo() IAppIgnoreUpgradeRepo {
|
||||
return &AppIgnoreUpgradeRepo{}
|
||||
}
|
||||
|
||||
func (a AppIgnoreUpgradeRepo) WithScope(scope string) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("scope = ?", scope)
|
||||
}
|
||||
}
|
||||
|
||||
func (a AppIgnoreUpgradeRepo) List(opts ...DBOption) ([]model.AppIgnoreUpgrade, error) {
|
||||
var appIgnoreUpgradeList []model.AppIgnoreUpgrade
|
||||
err := getDb(opts...).Find(&appIgnoreUpgradeList).Error
|
||||
return appIgnoreUpgradeList, err
|
||||
}
|
||||
|
||||
func (a AppIgnoreUpgradeRepo) Create(appIgnoreUpgrade *model.AppIgnoreUpgrade) error {
|
||||
return global.DB.Create(appIgnoreUpgrade).Error
|
||||
}
|
||||
|
||||
func (a AppIgnoreUpgradeRepo) Delete(opts ...DBOption) error {
|
||||
return getDb(opts...).Delete(&model.AppIgnoreUpgrade{}).Error
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
)
|
||||
|
||||
type LauncherRepo struct{}
|
||||
|
||||
type ILauncherRepo interface {
|
||||
Get(opts ...DBOption) (model.AppLauncher, error)
|
||||
ListName(opts ...DBOption) ([]string, error)
|
||||
Create(launcher *model.AppLauncher) error
|
||||
Save(launcher *model.AppLauncher) error
|
||||
Delete(opts ...DBOption) error
|
||||
}
|
||||
|
||||
func NewILauncherRepo() ILauncherRepo {
|
||||
return &LauncherRepo{}
|
||||
}
|
||||
|
||||
func (u *LauncherRepo) Get(opts ...DBOption) (model.AppLauncher, error) {
|
||||
var launcher model.AppLauncher
|
||||
db := global.DB
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
err := db.First(&launcher).Error
|
||||
return launcher, err
|
||||
}
|
||||
func (u *LauncherRepo) ListName(opts ...DBOption) ([]string, error) {
|
||||
var ops []model.AppLauncher
|
||||
db := global.DB.Model(&model.AppLauncher{})
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
_ = db.Find(&ops).Error
|
||||
var names []string
|
||||
for i := 0; i < len(ops); i++ {
|
||||
names = append(names, ops[i].Key)
|
||||
}
|
||||
return names, nil
|
||||
}
|
||||
|
||||
func (u *LauncherRepo) Create(launcher *model.AppLauncher) error {
|
||||
return global.DB.Create(launcher).Error
|
||||
}
|
||||
|
||||
func (u *LauncherRepo) Save(launcher *model.AppLauncher) error {
|
||||
return global.DB.Save(launcher).Error
|
||||
}
|
||||
|
||||
func (u *LauncherRepo) Delete(opts ...DBOption) error {
|
||||
db := global.DB
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
return db.Delete(&model.AppLauncher{}).Error
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
)
|
||||
|
||||
type HostRepo struct{}
|
||||
|
||||
type IHostRepo interface {
|
||||
GetFirewallRecord(opts ...DBOption) (model.Firewall, error)
|
||||
ListFirewallRecord() ([]model.Firewall, error)
|
||||
SaveFirewallRecord(firewall *model.Firewall) error
|
||||
DeleteFirewallRecordByID(id uint) error
|
||||
DeleteFirewallRecord(fType, port, protocol, address, strategy string) error
|
||||
}
|
||||
|
||||
func NewIHostRepo() IHostRepo {
|
||||
return &HostRepo{}
|
||||
}
|
||||
|
||||
func (h *HostRepo) GetFirewallRecord(opts ...DBOption) (model.Firewall, error) {
|
||||
var firewall model.Firewall
|
||||
db := global.DB
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
err := db.First(&firewall).Error
|
||||
return firewall, err
|
||||
}
|
||||
|
||||
func (h *HostRepo) ListFirewallRecord() ([]model.Firewall, error) {
|
||||
var datas []model.Firewall
|
||||
if err := global.DB.Find(&datas).Error; err != nil {
|
||||
return datas, nil
|
||||
}
|
||||
return datas, nil
|
||||
}
|
||||
|
||||
func (h *HostRepo) SaveFirewallRecord(firewall *model.Firewall) error {
|
||||
if firewall.ID != 0 {
|
||||
return global.DB.Save(firewall).Error
|
||||
}
|
||||
var data model.Firewall
|
||||
if firewall.Type == "port" {
|
||||
_ = global.DB.Where("type = ? AND port = ? AND protocol = ? AND address = ? AND strategy = ?", "port", firewall.Port, firewall.Protocol, firewall.Address, firewall.Strategy).First(&data)
|
||||
if data.ID != 0 {
|
||||
firewall.ID = data.ID
|
||||
}
|
||||
} else {
|
||||
_ = global.DB.Where("type = ? AND address = ? AND strategy = ?", "address", firewall.Address, firewall.Strategy).First(&data)
|
||||
if data.ID != 0 {
|
||||
firewall.ID = data.ID
|
||||
}
|
||||
}
|
||||
return global.DB.Save(firewall).Error
|
||||
}
|
||||
|
||||
func (h *HostRepo) DeleteFirewallRecordByID(id uint) error {
|
||||
return global.DB.Where("id = ?", id).Delete(&model.Firewall{}).Error
|
||||
}
|
||||
|
||||
func (h *HostRepo) DeleteFirewallRecord(fType, port, protocol, address, strategy string) error {
|
||||
return global.DB.Where("type = ? AND port = ? AND protocol = ? AND address = ? AND strategy = ?", fType, port, protocol, address, strategy).Delete(&model.Firewall{}).Error
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
)
|
||||
|
||||
type MonitorRepo struct{}
|
||||
|
||||
type IMonitorRepo interface {
|
||||
GetBase(opts ...DBOption) ([]model.MonitorBase, error)
|
||||
GetIO(opts ...DBOption) ([]model.MonitorIO, error)
|
||||
GetNetwork(opts ...DBOption) ([]model.MonitorNetwork, error)
|
||||
|
||||
CreateMonitorBase(model model.MonitorBase) error
|
||||
BatchCreateMonitorIO(ioList []model.MonitorIO) error
|
||||
BatchCreateMonitorNet(ioList []model.MonitorNetwork) error
|
||||
DelMonitorBase(timeForDelete time.Time) error
|
||||
DelMonitorIO(timeForDelete time.Time) error
|
||||
DelMonitorNet(timeForDelete time.Time) error
|
||||
}
|
||||
|
||||
func NewIMonitorRepo() IMonitorRepo {
|
||||
return &MonitorRepo{}
|
||||
}
|
||||
|
||||
func (u *MonitorRepo) GetBase(opts ...DBOption) ([]model.MonitorBase, error) {
|
||||
var data []model.MonitorBase
|
||||
db := global.MonitorDB
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
err := db.Find(&data).Error
|
||||
return data, err
|
||||
}
|
||||
func (u *MonitorRepo) GetIO(opts ...DBOption) ([]model.MonitorIO, error) {
|
||||
var data []model.MonitorIO
|
||||
db := global.MonitorDB
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
err := db.Find(&data).Error
|
||||
return data, err
|
||||
}
|
||||
func (u *MonitorRepo) GetNetwork(opts ...DBOption) ([]model.MonitorNetwork, error) {
|
||||
var data []model.MonitorNetwork
|
||||
db := global.MonitorDB
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
err := db.Find(&data).Error
|
||||
return data, err
|
||||
}
|
||||
|
||||
func (u *MonitorRepo) CreateMonitorBase(model model.MonitorBase) error {
|
||||
return global.MonitorDB.Create(&model).Error
|
||||
}
|
||||
func (u *MonitorRepo) BatchCreateMonitorIO(ioList []model.MonitorIO) error {
|
||||
return global.MonitorDB.CreateInBatches(ioList, len(ioList)).Error
|
||||
}
|
||||
func (u *MonitorRepo) BatchCreateMonitorNet(ioList []model.MonitorNetwork) error {
|
||||
return global.MonitorDB.CreateInBatches(ioList, len(ioList)).Error
|
||||
}
|
||||
func (u *MonitorRepo) DelMonitorBase(timeForDelete time.Time) error {
|
||||
return global.MonitorDB.Where("created_at < ?", timeForDelete).Delete(&model.MonitorBase{}).Error
|
||||
}
|
||||
func (u *MonitorRepo) DelMonitorIO(timeForDelete time.Time) error {
|
||||
return global.MonitorDB.Where("created_at < ?", timeForDelete).Delete(&model.MonitorIO{}).Error
|
||||
}
|
||||
func (u *MonitorRepo) DelMonitorNet(timeForDelete time.Time) error {
|
||||
return global.MonitorDB.Where("created_at < ?", timeForDelete).Delete(&model.MonitorNetwork{}).Error
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
)
|
||||
|
||||
type ScriptRepo struct{}
|
||||
|
||||
type IScriptRepo interface {
|
||||
Get(opts ...DBOption) (model.ScriptLibrary, error)
|
||||
List(opts ...DBOption) ([]model.ScriptLibrary, error)
|
||||
|
||||
SyncAll(data []model.ScriptLibrary) error
|
||||
}
|
||||
|
||||
func NewIScriptRepo() IScriptRepo {
|
||||
return &ScriptRepo{}
|
||||
}
|
||||
|
||||
func (u *ScriptRepo) Get(opts ...DBOption) (model.ScriptLibrary, error) {
|
||||
var script model.ScriptLibrary
|
||||
db := global.DB
|
||||
if global.IsMaster {
|
||||
db = global.CoreDB
|
||||
}
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
err := db.First(&script).Error
|
||||
return script, err
|
||||
}
|
||||
|
||||
func (u *ScriptRepo) List(opts ...DBOption) ([]model.ScriptLibrary, error) {
|
||||
var ops []model.ScriptLibrary
|
||||
if global.IsMaster {
|
||||
db := global.CoreDB.Model(&model.ScriptLibrary{})
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
err := db.Where("is_interactive = ?", false).Find(&ops).Error
|
||||
return ops, err
|
||||
}
|
||||
db := global.DB.Model(&model.ScriptLibrary{})
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
err := db.Find(&ops).Error
|
||||
return ops, err
|
||||
}
|
||||
|
||||
func (u *ScriptRepo) SyncAll(data []model.ScriptLibrary) error {
|
||||
tx := global.DB.Begin()
|
||||
var oldScripts []model.ScriptLibrary
|
||||
_ = tx.Where("1 = 1").Find(&oldScripts).Error
|
||||
oldScriptMap := make(map[string]uint)
|
||||
for _, item := range oldScripts {
|
||||
oldScriptMap[item.Name] = item.ID
|
||||
}
|
||||
for _, item := range data {
|
||||
if val, ok := oldScriptMap[item.Name]; ok {
|
||||
item.ID = val
|
||||
delete(oldScriptMap, item.Name)
|
||||
} else {
|
||||
item.ID = 0
|
||||
}
|
||||
if err := tx.Model(model.ScriptLibrary{}).Where("id = ?", item.ID).Save(&item).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, val := range oldScriptMap {
|
||||
if err := tx.Where("id = ?", val).Delete(&model.ScriptLibrary{}).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
}
|
||||
tx.Commit()
|
||||
return nil
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type TaskRepo struct {
|
||||
}
|
||||
|
||||
type ITaskRepo interface {
|
||||
Save(ctx context.Context, task *model.Task) error
|
||||
GetFirst(opts ...DBOption) (model.Task, error)
|
||||
Page(page, size int, opts ...DBOption) (int64, []model.Task, error)
|
||||
Update(ctx context.Context, task *model.Task) error
|
||||
UpdateRunningTaskToFailed() error
|
||||
CountExecutingTask() (int64, error)
|
||||
Delete(opts ...DBOption) error
|
||||
DeleteAll() error
|
||||
|
||||
WithByID(id string) DBOption
|
||||
WithResourceID(id uint) DBOption
|
||||
WithOperate(taskOperate string) DBOption
|
||||
WithByStatus(status string) DBOption
|
||||
}
|
||||
|
||||
func NewITaskRepo() ITaskRepo {
|
||||
return &TaskRepo{}
|
||||
}
|
||||
|
||||
func getTaskDb(opts ...DBOption) *gorm.DB {
|
||||
db := global.TaskDB
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
return db
|
||||
}
|
||||
|
||||
func getTaskTx(ctx context.Context, opts ...DBOption) *gorm.DB {
|
||||
tx, ok := ctx.Value(constant.DB).(*gorm.DB)
|
||||
if ok {
|
||||
for _, opt := range opts {
|
||||
tx = opt(tx)
|
||||
}
|
||||
return tx
|
||||
}
|
||||
return getTaskDb(opts...)
|
||||
}
|
||||
|
||||
func (t TaskRepo) WithByID(id string) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("id = ?", id)
|
||||
}
|
||||
}
|
||||
|
||||
func (t TaskRepo) WithOperate(taskOperate string) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("operate = ?", taskOperate)
|
||||
}
|
||||
}
|
||||
|
||||
func (t TaskRepo) WithResourceID(id uint) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("resource_id = ?", id)
|
||||
}
|
||||
}
|
||||
|
||||
func (t TaskRepo) WithByStatus(status string) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("status = ?", status)
|
||||
}
|
||||
}
|
||||
|
||||
func (t TaskRepo) Save(ctx context.Context, task *model.Task) error {
|
||||
return getTaskTx(ctx).Save(&task).Error
|
||||
}
|
||||
|
||||
func (t TaskRepo) GetFirst(opts ...DBOption) (model.Task, error) {
|
||||
var task model.Task
|
||||
db := getTaskDb(opts...).Model(&model.Task{})
|
||||
if err := db.First(&task).Error; err != nil {
|
||||
return task, err
|
||||
}
|
||||
return task, nil
|
||||
}
|
||||
|
||||
func (t TaskRepo) Page(page, size int, opts ...DBOption) (int64, []model.Task, error) {
|
||||
var tasks []model.Task
|
||||
db := getTaskDb(opts...).Model(&model.Task{})
|
||||
count := int64(0)
|
||||
db = db.Count(&count)
|
||||
err := db.Limit(size).Offset(size * (page - 1)).Find(&tasks).Error
|
||||
return count, tasks, err
|
||||
}
|
||||
|
||||
func (t TaskRepo) Update(ctx context.Context, task *model.Task) error {
|
||||
return getTaskTx(ctx).Save(&task).Error
|
||||
}
|
||||
|
||||
func (t TaskRepo) UpdateRunningTaskToFailed() error {
|
||||
return getTaskDb(t.WithByStatus(constant.StatusExecuting)).Model(&model.Task{}).Updates(map[string]interface{}{"status": constant.StatusFailed, "error_msg": "1Panel restart causes failure"}).Error
|
||||
}
|
||||
|
||||
func (t TaskRepo) CountExecutingTask() (int64, error) {
|
||||
var count int64
|
||||
err := getTaskDb(t.WithByStatus(constant.StatusExecuting)).Model(&model.Task{}).Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
func (t TaskRepo) Delete(opts ...DBOption) error {
|
||||
db := global.TaskDB
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
return db.Delete(&model.Task{}).Error
|
||||
}
|
||||
|
||||
func (t TaskRepo) DeleteAll() error {
|
||||
return global.TaskDB.Where("1 = 1").Delete(&model.Task{}).Error
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,76 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/response"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"github.com/pkg/errors"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type AppIgnoreUpgradeService struct {
|
||||
}
|
||||
|
||||
type IAppIgnoreUpgradeService interface {
|
||||
List() ([]response.AppIgnoreUpgradeDTO, error)
|
||||
CreateAppIgnore(req request.AppIgnoreUpgradeReq) error
|
||||
Delete(req request.ReqWithID) error
|
||||
}
|
||||
|
||||
func NewIAppIgnoreUpgradeService() IAppIgnoreUpgradeService {
|
||||
return AppIgnoreUpgradeService{}
|
||||
}
|
||||
|
||||
func (a AppIgnoreUpgradeService) List() ([]response.AppIgnoreUpgradeDTO, error) {
|
||||
var res []response.AppIgnoreUpgradeDTO
|
||||
ignores, err := appIgnoreUpgradeRepo.List()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, ignore := range ignores {
|
||||
dto := response.AppIgnoreUpgradeDTO{
|
||||
ID: ignore.ID,
|
||||
AppID: ignore.AppID,
|
||||
AppDetailID: ignore.AppDetailID,
|
||||
Scope: ignore.Scope,
|
||||
}
|
||||
app, err := appRepo.GetFirst(repo.WithByID(ignore.AppID))
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
_ = appIgnoreUpgradeRepo.Delete(repo.WithByID(ignore.ID))
|
||||
continue
|
||||
}
|
||||
dto.Icon = app.Icon
|
||||
if ignore.Scope == "version" {
|
||||
appDetail, err := appDetailRepo.GetFirst(repo.WithByID(ignore.AppDetailID))
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
_ = appIgnoreUpgradeRepo.Delete(repo.WithByID(ignore.ID))
|
||||
continue
|
||||
}
|
||||
dto.Version = appDetail.Version
|
||||
}
|
||||
res = append(res, dto)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (a AppIgnoreUpgradeService) CreateAppIgnore(req request.AppIgnoreUpgradeReq) error {
|
||||
appIgnoreUpgrade := model.AppIgnoreUpgrade{
|
||||
AppID: req.AppID,
|
||||
Scope: req.Scope,
|
||||
}
|
||||
if req.Scope == "version" {
|
||||
appIgnoreUpgrade.AppDetailID = req.AppDetailID
|
||||
}
|
||||
if req.Scope == "all" {
|
||||
_ = appIgnoreUpgradeRepo.Delete(appInstallRepo.WithAppId(req.AppID))
|
||||
}
|
||||
if err := appIgnoreUpgradeRepo.Create(&appIgnoreUpgrade); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a AppIgnoreUpgradeService) Delete(req request.ReqWithID) error {
|
||||
return appIgnoreUpgradeRepo.Delete(repo.WithByID(req.ID))
|
||||
}
|
||||
@@ -1,553 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cloud_storage"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cloud_storage/client"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/encrypt"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/files"
|
||||
"github.com/jinzhu/copier"
|
||||
)
|
||||
|
||||
type BackupService struct{}
|
||||
|
||||
type IBackupService interface {
|
||||
CheckUsed(name string, isPublic bool) error
|
||||
|
||||
LoadBackupOptions() ([]dto.BackupOption, error)
|
||||
SearchWithPage(search dto.SearchPageWithType) (int64, interface{}, error)
|
||||
Create(backupDto dto.BackupOperate) error
|
||||
GetBuckets(backupDto dto.ForBuckets) ([]interface{}, error)
|
||||
Update(req dto.BackupOperate) error
|
||||
Delete(id uint) error
|
||||
RefreshToken(req dto.OperateByID) error
|
||||
GetLocalDir() (string, error)
|
||||
|
||||
MysqlBackup(db dto.CommonBackup) error
|
||||
PostgresqlBackup(db dto.CommonBackup) error
|
||||
MysqlRecover(db dto.CommonRecover) error
|
||||
PostgresqlRecover(db dto.CommonRecover) error
|
||||
MysqlRecoverByUpload(req dto.CommonRecover) error
|
||||
PostgresqlRecoverByUpload(req dto.CommonRecover) error
|
||||
|
||||
RedisBackup(db dto.CommonBackup) error
|
||||
RedisRecover(db dto.CommonRecover) error
|
||||
|
||||
WebsiteBackup(db dto.CommonBackup) error
|
||||
WebsiteRecover(req dto.CommonRecover) error
|
||||
|
||||
AppBackup(db dto.CommonBackup) (*model.BackupRecord, error)
|
||||
AppRecover(req dto.CommonRecover) error
|
||||
}
|
||||
|
||||
func NewIBackupService() IBackupService {
|
||||
return &BackupService{}
|
||||
}
|
||||
|
||||
func (u *BackupService) GetLocalDir() (string, error) {
|
||||
account, err := backupRepo.Get(repo.WithByType(constant.Local))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return account.BackupPath, nil
|
||||
}
|
||||
|
||||
func (u *BackupService) SearchWithPage(req dto.SearchPageWithType) (int64, interface{}, error) {
|
||||
options := []repo.DBOption{repo.WithOrderBy("created_at desc")}
|
||||
if len(req.Type) != 0 {
|
||||
options = append(options, repo.WithByType(req.Type))
|
||||
}
|
||||
if len(req.Info) != 0 {
|
||||
options = append(options, repo.WithByType(req.Info))
|
||||
}
|
||||
count, accounts, err := backupRepo.Page(req.Page, req.PageSize, options...)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
var data []dto.BackupInfo
|
||||
for _, account := range accounts {
|
||||
var item dto.BackupInfo
|
||||
if err := copier.Copy(&item, &account); err != nil {
|
||||
global.LOG.Errorf("copy backup account to dto backup info failed, err: %v", err)
|
||||
}
|
||||
if item.Type != constant.Sftp && item.Type != constant.Local {
|
||||
item.BackupPath = path.Join("/", strings.TrimPrefix(item.BackupPath, "/"))
|
||||
}
|
||||
if !item.RememberAuth {
|
||||
item.AccessKey = ""
|
||||
item.Credential = ""
|
||||
if account.Type == constant.Sftp {
|
||||
varMap := make(map[string]interface{})
|
||||
if err := json.Unmarshal([]byte(item.Vars), &varMap); err != nil {
|
||||
continue
|
||||
}
|
||||
delete(varMap, "passPhrase")
|
||||
itemVars, _ := json.Marshal(varMap)
|
||||
item.Vars = string(itemVars)
|
||||
}
|
||||
} else {
|
||||
item.AccessKey, _ = encrypt.StringDecryptWithBase64(item.AccessKey)
|
||||
item.Credential, _ = encrypt.StringDecryptWithBase64(item.Credential)
|
||||
}
|
||||
|
||||
if account.Type == constant.OneDrive || account.Type == constant.ALIYUN || account.Type == constant.GoogleDrive {
|
||||
varMap := make(map[string]interface{})
|
||||
if err := json.Unmarshal([]byte(item.Vars), &varMap); err != nil {
|
||||
continue
|
||||
}
|
||||
delete(varMap, "refresh_token")
|
||||
delete(varMap, "drive_id")
|
||||
itemVars, _ := json.Marshal(varMap)
|
||||
item.Vars = string(itemVars)
|
||||
}
|
||||
data = append(data, item)
|
||||
}
|
||||
return count, data, nil
|
||||
}
|
||||
|
||||
func (u *BackupService) Create(req dto.BackupOperate) error {
|
||||
if req.Type == constant.Local {
|
||||
return buserr.New("ErrBackupLocalCreate")
|
||||
}
|
||||
if req.Type != constant.Sftp {
|
||||
req.BackupPath = strings.TrimPrefix(req.BackupPath, "/")
|
||||
}
|
||||
backup, _ := backupRepo.Get(repo.WithByName(req.Name))
|
||||
if backup.ID != 0 {
|
||||
return buserr.New("ErrRecordExist")
|
||||
}
|
||||
if err := copier.Copy(&backup, &req); err != nil {
|
||||
return buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
||||
}
|
||||
itemAccessKey, err := base64.StdEncoding.DecodeString(backup.AccessKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
backup.AccessKey = string(itemAccessKey)
|
||||
itemCredential, err := base64.StdEncoding.DecodeString(backup.Credential)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
backup.Credential = string(itemCredential)
|
||||
|
||||
if req.Type == constant.OneDrive || req.Type == constant.GoogleDrive || req.Type == constant.ALIYUN {
|
||||
if err := loadRefreshTokenByCode(&backup); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if req.Type != constant.Local {
|
||||
isOk, err := u.checkBackupConn(&backup)
|
||||
if err != nil || !isOk {
|
||||
return buserr.WithMap("ErrBackupCheck", map[string]interface{}{"err": err.Error()}, err)
|
||||
}
|
||||
}
|
||||
|
||||
backup.AccessKey, err = encrypt.StringEncrypt(backup.AccessKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
backup.Credential, err = encrypt.StringEncrypt(backup.Credential)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := backupRepo.Create(&backup); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *BackupService) GetBuckets(req dto.ForBuckets) ([]interface{}, error) {
|
||||
itemAccessKey, err := base64.StdEncoding.DecodeString(req.AccessKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.AccessKey = string(itemAccessKey)
|
||||
itemCredential, err := base64.StdEncoding.DecodeString(req.Credential)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Credential = string(itemCredential)
|
||||
|
||||
varMap := make(map[string]interface{})
|
||||
if err := json.Unmarshal([]byte(req.Vars), &varMap); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch req.Type {
|
||||
case constant.Sftp, constant.WebDAV:
|
||||
varMap["username"] = req.AccessKey
|
||||
varMap["password"] = req.Credential
|
||||
case constant.OSS, constant.S3, constant.MinIo, constant.Cos, constant.Kodo:
|
||||
varMap["accessKey"] = req.AccessKey
|
||||
varMap["secretKey"] = req.Credential
|
||||
}
|
||||
client, err := cloud_storage.NewCloudStorageClient(req.Type, varMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return client.ListBuckets()
|
||||
}
|
||||
|
||||
func (u *BackupService) Delete(id uint) error {
|
||||
backup, _ := backupRepo.Get(repo.WithByID(id))
|
||||
if backup.ID == 0 {
|
||||
return buserr.New("ErrRecordNotFound")
|
||||
}
|
||||
if backup.Type == constant.Local {
|
||||
return buserr.New("ErrBackupLocalDelete")
|
||||
}
|
||||
if err := u.CheckUsed(backup.Name, false); err != nil {
|
||||
return err
|
||||
}
|
||||
return backupRepo.Delete(repo.WithByID(id))
|
||||
}
|
||||
|
||||
func (u *BackupService) Update(req dto.BackupOperate) error {
|
||||
backup, _ := backupRepo.Get(repo.WithByID(req.ID))
|
||||
if backup.ID == 0 {
|
||||
return buserr.New("ErrRecordNotFound")
|
||||
}
|
||||
if req.Type != constant.Sftp && req.Type != constant.Local && req.BackupPath != "/" {
|
||||
req.BackupPath = strings.TrimPrefix(req.BackupPath, "/")
|
||||
}
|
||||
var newBackup model.BackupAccount
|
||||
if err := copier.Copy(&newBackup, &req); err != nil {
|
||||
return buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
||||
}
|
||||
itemAccessKey, err := base64.StdEncoding.DecodeString(newBackup.AccessKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newBackup.AccessKey = string(itemAccessKey)
|
||||
itemCredential, err := base64.StdEncoding.DecodeString(newBackup.Credential)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newBackup.Credential = string(itemCredential)
|
||||
if backup.Type == constant.Local {
|
||||
if err := changeLocalBackup(backup.BackupPath, newBackup.BackupPath); err != nil {
|
||||
return err
|
||||
}
|
||||
global.Dir.LocalBackupDir = newBackup.BackupPath
|
||||
}
|
||||
|
||||
if newBackup.Type == constant.OneDrive || newBackup.Type == constant.GoogleDrive || newBackup.Type == constant.ALIYUN {
|
||||
if err := loadRefreshTokenByCode(&newBackup); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if backup.Type != constant.Local {
|
||||
isOk, err := u.checkBackupConn(&newBackup)
|
||||
if err != nil || !isOk {
|
||||
return buserr.WithMap("ErrBackupCheck", map[string]interface{}{"err": err.Error()}, err)
|
||||
}
|
||||
newBackup.AccessKey, err = encrypt.StringEncrypt(newBackup.AccessKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newBackup.Credential, err = encrypt.StringEncrypt(newBackup.Credential)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
newBackup.ID = backup.ID
|
||||
newBackup.CreatedAt = backup.CreatedAt
|
||||
newBackup.UpdatedAt = backup.UpdatedAt
|
||||
if err := backupRepo.Save(&newBackup); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *BackupService) RefreshToken(req dto.OperateByID) error {
|
||||
backup, _ := backupRepo.Get(repo.WithByID(req.ID))
|
||||
if backup.ID == 0 {
|
||||
return buserr.New("ErrRecordNotFound")
|
||||
}
|
||||
varMap := make(map[string]interface{})
|
||||
if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil {
|
||||
return fmt.Errorf("failed to refresh %s - %s token, please retry, err: %v", backup.Type, backup.Name, err)
|
||||
}
|
||||
var (
|
||||
refreshToken string
|
||||
err error
|
||||
)
|
||||
switch backup.Type {
|
||||
case constant.OneDrive:
|
||||
refreshToken, err = client.RefreshToken("refresh_token", "refreshToken", varMap)
|
||||
case constant.GoogleDrive:
|
||||
refreshToken, err = client.RefreshGoogleToken("refresh_token", "refreshToken", varMap)
|
||||
case constant.ALIYUN:
|
||||
refreshToken, err = client.RefreshALIToken(varMap)
|
||||
}
|
||||
if err != nil {
|
||||
varMap["refresh_status"] = constant.StatusFailed
|
||||
varMap["refresh_msg"] = err.Error()
|
||||
return fmt.Errorf("failed to refresh %s-%s token, please retry, err: %v", backup.Type, backup.Name, err)
|
||||
}
|
||||
varMap["refresh_status"] = constant.StatusSuccess
|
||||
varMap["refresh_time"] = time.Now().Format(constant.DateTimeLayout)
|
||||
varMap["refresh_token"] = refreshToken
|
||||
|
||||
varsItem, _ := json.Marshal(varMap)
|
||||
backup.Vars = string(varsItem)
|
||||
return backupRepo.Save(&backup)
|
||||
}
|
||||
|
||||
func (u *BackupService) checkBackupConn(backup *model.BackupAccount) (bool, error) {
|
||||
client, err := newClient(backup, false)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
fileItem := path.Join(global.Dir.BaseDir, "1panel/tmp/test/1panel")
|
||||
if _, err := os.Stat(path.Dir(fileItem)); err != nil && os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(path.Dir(fileItem), os.ModePerm); err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
file, err := os.OpenFile(fileItem, os.O_WRONLY|os.O_CREATE, constant.FilePerm)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer file.Close()
|
||||
write := bufio.NewWriter(file)
|
||||
_, _ = write.WriteString("1Panel 备份账号测试文件。\n")
|
||||
_, _ = write.WriteString("1Panel 備份賬號測試文件。\n")
|
||||
_, _ = write.WriteString("1Panel Backs up account test files.\n")
|
||||
_, _ = write.WriteString("1Panelアカウントのテストファイルをバックアップします。\n")
|
||||
write.Flush()
|
||||
|
||||
targetPath := path.Join(backup.BackupPath, "test/1panel")
|
||||
if backup.Type != constant.Sftp && backup.Type != constant.Local && targetPath != "/" {
|
||||
targetPath = strings.TrimPrefix(targetPath, "/")
|
||||
}
|
||||
|
||||
if _, err := client.Upload(fileItem, targetPath); err != nil {
|
||||
return false, err
|
||||
}
|
||||
_, _ = client.Delete(path.Join(backup.BackupPath, "test/1panel"))
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (u *BackupService) LoadBackupOptions() ([]dto.BackupOption, error) {
|
||||
accounts, err := backupRepo.List(repo.WithOrderBy("created_at desc"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var data []dto.BackupOption
|
||||
for _, account := range accounts {
|
||||
var item dto.BackupOption
|
||||
if err := copier.Copy(&item, &account); err != nil {
|
||||
global.LOG.Errorf("copy backup account to dto backup info failed, err: %v", err)
|
||||
}
|
||||
data = append(data, item)
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (u *BackupService) CheckUsed(name string, isPublic bool) error {
|
||||
account, _ := backupRepo.Get(repo.WithByName(name), backupRepo.WithByPublic(isPublic))
|
||||
if account.ID == 0 {
|
||||
return nil
|
||||
}
|
||||
cronjobs, _ := cronjobRepo.List()
|
||||
for _, job := range cronjobs {
|
||||
if job.DownloadAccountID == account.ID {
|
||||
return buserr.New("ErrBackupInUsed")
|
||||
}
|
||||
ids := strings.Split(job.SourceAccountIDs, ",")
|
||||
for _, idItem := range ids {
|
||||
if idItem == fmt.Sprintf("%v", account.ID) {
|
||||
return buserr.New("ErrBackupInUsed")
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewBackupClientWithID(id uint) (*model.BackupAccount, cloud_storage.CloudStorageClient, error) {
|
||||
account, _ := backupRepo.Get(repo.WithByID(id))
|
||||
backClient, err := newClient(&account, true)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return &account, backClient, nil
|
||||
}
|
||||
|
||||
type backupClientHelper struct {
|
||||
id uint
|
||||
accountType string
|
||||
name string
|
||||
backupPath string
|
||||
client cloud_storage.CloudStorageClient
|
||||
}
|
||||
|
||||
func NewBackupClientMap(ids []string) (map[string]backupClientHelper, error) {
|
||||
var accounts []model.BackupAccount
|
||||
var idItems []uint
|
||||
for i := 0; i < len(ids); i++ {
|
||||
item, _ := strconv.Atoi(ids[i])
|
||||
idItems = append(idItems, uint(item))
|
||||
}
|
||||
accounts, _ = backupRepo.List(repo.WithByIDs(idItems))
|
||||
clientMap := make(map[string]backupClientHelper)
|
||||
for _, item := range accounts {
|
||||
backClient, err := newClient(&item, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
clientMap[fmt.Sprintf("%v", item.ID)] = backupClientHelper{
|
||||
client: backClient,
|
||||
name: item.Name,
|
||||
backupPath: item.BackupPath,
|
||||
accountType: item.Type,
|
||||
id: item.ID,
|
||||
}
|
||||
}
|
||||
return clientMap, nil
|
||||
}
|
||||
|
||||
func newClient(account *model.BackupAccount, isEncrypt bool) (cloud_storage.CloudStorageClient, error) {
|
||||
varMap := make(map[string]interface{})
|
||||
if len(account.Vars) != 0 {
|
||||
if err := json.Unmarshal([]byte(account.Vars), &varMap); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
varMap["bucket"] = account.Bucket
|
||||
varMap["backupPath"] = account.BackupPath
|
||||
if isEncrypt {
|
||||
account.AccessKey, _ = encrypt.StringDecrypt(account.AccessKey)
|
||||
account.Credential, _ = encrypt.StringDecrypt(account.Credential)
|
||||
}
|
||||
switch account.Type {
|
||||
case constant.Sftp, constant.WebDAV:
|
||||
varMap["username"] = account.AccessKey
|
||||
varMap["password"] = account.Credential
|
||||
case constant.OSS, constant.S3, constant.MinIo, constant.Cos, constant.Kodo:
|
||||
varMap["accessKey"] = account.AccessKey
|
||||
varMap["secretKey"] = account.Credential
|
||||
case constant.UPYUN:
|
||||
varMap["operator"] = account.AccessKey
|
||||
varMap["password"] = account.Credential
|
||||
}
|
||||
|
||||
client, err := cloud_storage.NewCloudStorageClient(account.Type, varMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func loadRefreshTokenByCode(backup *model.BackupAccount) error {
|
||||
varMap := make(map[string]interface{})
|
||||
if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil {
|
||||
return fmt.Errorf("unmarshal backup vars failed, err: %v", err)
|
||||
}
|
||||
refreshToken := ""
|
||||
var err error
|
||||
if backup.Type == constant.GoogleDrive {
|
||||
refreshToken, err = client.RefreshGoogleToken("authorization_code", "refreshToken", varMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if backup.Type == constant.OneDrive {
|
||||
refreshToken, err = client.RefreshToken("authorization_code", "refreshToken", varMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if backup.Type != constant.ALIYUN {
|
||||
delete(varMap, "code")
|
||||
varMap["refresh_token"] = refreshToken
|
||||
}
|
||||
varMap["refresh_status"] = constant.StatusSuccess
|
||||
varMap["refresh_time"] = time.Now().Format(constant.DateTimeLayout)
|
||||
itemVars, err := json.Marshal(varMap)
|
||||
if err != nil {
|
||||
return fmt.Errorf("json marshal var map failed, err: %v", err)
|
||||
}
|
||||
backup.Vars = string(itemVars)
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadBackupNamesByID(accountIDs string, downloadID uint) ([]string, string, error) {
|
||||
accountIDList := strings.Split(accountIDs, ",")
|
||||
var ids []uint
|
||||
for _, item := range accountIDList {
|
||||
if len(item) != 0 {
|
||||
itemID, _ := strconv.Atoi(item)
|
||||
ids = append(ids, uint(itemID))
|
||||
}
|
||||
}
|
||||
list, err := backupRepo.List(repo.WithByIDs(ids))
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
var accounts []string
|
||||
var downloadAccount string
|
||||
for _, item := range list {
|
||||
accounts = append(accounts, item.Name)
|
||||
if item.ID == downloadID {
|
||||
downloadAccount = item.Name
|
||||
}
|
||||
}
|
||||
return accounts, downloadAccount, nil
|
||||
}
|
||||
|
||||
func changeLocalBackup(oldPath, newPath string) error {
|
||||
fileOp := files.NewFileOp()
|
||||
if fileOp.Stat(path.Join(oldPath, "app")) {
|
||||
if err := fileOp.CopyDir(path.Join(oldPath, "app"), newPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if fileOp.Stat(path.Join(oldPath, "database")) {
|
||||
if err := fileOp.CopyDir(path.Join(oldPath, "database"), newPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if fileOp.Stat(path.Join(oldPath, "directory")) {
|
||||
if err := fileOp.CopyDir(path.Join(oldPath, "directory"), newPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if fileOp.Stat(path.Join(oldPath, "system_snapshot")) {
|
||||
if err := fileOp.CopyDir(path.Join(oldPath, "system_snapshot"), newPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if fileOp.Stat(path.Join(oldPath, "website")) {
|
||||
if err := fileOp.CopyDir(path.Join(oldPath, "website"), newPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if fileOp.Stat(path.Join(oldPath, "log")) {
|
||||
if err := fileOp.CopyDir(path.Join(oldPath, "log"), newPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
_ = fileOp.RmRf(path.Join(oldPath, "app"))
|
||||
_ = fileOp.RmRf(path.Join(oldPath, "database"))
|
||||
_ = fileOp.RmRf(path.Join(oldPath, "directory"))
|
||||
_ = fileOp.RmRf(path.Join(oldPath, "system_snapshot"))
|
||||
_ = fileOp.RmRf(path.Join(oldPath, "website"))
|
||||
_ = fileOp.RmRf(path.Join(oldPath, "log"))
|
||||
return nil
|
||||
}
|
||||
@@ -1,401 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/compose"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/files"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (u *BackupService) AppBackup(req dto.CommonBackup) (*model.BackupRecord, error) {
|
||||
app, err := appRepo.GetFirst(appRepo.WithKey(req.Name))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
install, err := appInstallRepo.GetFirst(repo.WithByName(req.DetailName), appInstallRepo.WithAppId(app.ID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
timeNow := time.Now().Format(constant.DateTimeSlimLayout)
|
||||
itemDir := fmt.Sprintf("app/%s/%s", req.Name, req.DetailName)
|
||||
backupDir := path.Join(global.Dir.LocalBackupDir, itemDir)
|
||||
|
||||
fileName := req.FileName
|
||||
if req.FileName == "" {
|
||||
fileName = fmt.Sprintf("%s_%s.tar.gz", req.DetailName, timeNow+common.RandStrAndNum(5))
|
||||
}
|
||||
|
||||
backupApp := func() (*model.BackupRecord, error) {
|
||||
if err = handleAppBackup(&install, nil, backupDir, fileName, "", req.Secret, req.TaskID); err != nil {
|
||||
global.LOG.Errorf("backup app %s failed, err: %v", req.DetailName, err)
|
||||
return nil, err
|
||||
}
|
||||
record := &model.BackupRecord{
|
||||
Type: "app",
|
||||
Name: req.Name,
|
||||
DetailName: req.DetailName,
|
||||
SourceAccountIDs: "1",
|
||||
DownloadAccountID: 1,
|
||||
FileDir: itemDir,
|
||||
FileName: fileName,
|
||||
Description: req.Description,
|
||||
}
|
||||
if err := backupRepo.CreateRecord(record); err != nil {
|
||||
global.LOG.Errorf("save backup record failed, err: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
return record, nil
|
||||
}
|
||||
|
||||
if req.TaskID != "" {
|
||||
go func() {
|
||||
_, _ = backupApp()
|
||||
}()
|
||||
} else {
|
||||
return backupApp()
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (u *BackupService) AppRecover(req dto.CommonRecover) error {
|
||||
app, err := appRepo.GetFirst(appRepo.WithKey(req.Name))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
install, err := appInstallRepo.GetFirst(repo.WithByName(req.DetailName), appInstallRepo.WithAppId(app.ID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fileOp := files.NewFileOp()
|
||||
if !fileOp.Stat(req.File) {
|
||||
return buserr.WithName("ErrFileNotFound", req.File)
|
||||
}
|
||||
if _, err := compose.Down(install.GetComposePath()); err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
if err := handleAppRecover(&install, nil, req.File, false, req.Secret, req.TaskID); err != nil {
|
||||
global.LOG.Errorf("recover app %s failed, err: %v", req.DetailName, err)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func backupDatabaseWithTask(parentTask *task.Task, resourceKey, tmpDir, name string, databaseID uint) error {
|
||||
switch resourceKey {
|
||||
case constant.AppMysql, constant.AppMariaDB:
|
||||
db, err := mysqlRepo.Get(repo.WithByID(databaseID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
parentTask.LogStart(task.GetTaskName(db.Name, task.TaskBackup, task.TaskScopeDatabase))
|
||||
databaseHelper := DatabaseHelper{Database: db.MysqlName, DBType: resourceKey, Name: db.Name}
|
||||
if err := handleMysqlBackup(databaseHelper, parentTask, tmpDir, fmt.Sprintf("%s.sql.gz", name), ""); err != nil {
|
||||
return err
|
||||
}
|
||||
parentTask.LogSuccess(task.GetTaskName(db.Name, task.TaskBackup, task.TaskScopeDatabase))
|
||||
case constant.AppPostgresql:
|
||||
db, err := postgresqlRepo.Get(repo.WithByID(databaseID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
parentTask.LogStart(task.GetTaskName(db.Name, task.TaskBackup, task.TaskScopeDatabase))
|
||||
databaseHelper := DatabaseHelper{Database: db.PostgresqlName, DBType: resourceKey, Name: db.Name}
|
||||
if err := handlePostgresqlBackup(databaseHelper, parentTask, tmpDir, fmt.Sprintf("%s.sql.gz", name), ""); err != nil {
|
||||
return err
|
||||
}
|
||||
parentTask.LogSuccess(task.GetTaskName(db.Name, task.TaskBackup, task.TaskScopeDatabase))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleAppBackup(install *model.AppInstall, parentTask *task.Task, backupDir, fileName, excludes, secret, taskID string) error {
|
||||
var (
|
||||
err error
|
||||
backupTask *task.Task
|
||||
)
|
||||
backupTask = parentTask
|
||||
if parentTask == nil {
|
||||
backupTask, err = task.NewTaskWithOps(install.Name, task.TaskBackup, task.TaskScopeApp, taskID, install.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
itemHandler := func() error { return doAppBackup(install, backupTask, backupDir, fileName, excludes, secret) }
|
||||
backupTask.AddSubTask(task.GetTaskName(install.Name, task.TaskBackup, task.TaskScopeApp), func(t *task.Task) error { return itemHandler() }, nil)
|
||||
if parentTask != nil {
|
||||
return itemHandler()
|
||||
}
|
||||
return backupTask.Execute()
|
||||
}
|
||||
|
||||
func handleAppRecover(install *model.AppInstall, parentTask *task.Task, recoverFile string, isRollback bool, secret, taskID string) error {
|
||||
var (
|
||||
err error
|
||||
recoverTask *task.Task
|
||||
isOk = false
|
||||
rollbackFile string
|
||||
)
|
||||
recoverTask = parentTask
|
||||
if parentTask == nil {
|
||||
recoverTask, err = task.NewTaskWithOps(install.Name, task.TaskRecover, task.TaskScopeApp, taskID, install.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
recoverApp := func(t *task.Task) error {
|
||||
fileOp := files.NewFileOp()
|
||||
if !isRollback {
|
||||
rollbackFile = path.Join(global.Dir.TmpDir, fmt.Sprintf("app/%s_%s.tar.gz", install.Name, time.Now().Format(constant.DateTimeSlimLayout)))
|
||||
if err := handleAppBackup(install, recoverTask, path.Dir(rollbackFile), path.Base(rollbackFile), "", "", taskID); err != nil {
|
||||
t.Log(fmt.Sprintf("backup app %s for rollback before recover failed, err: %v", install.Name, err))
|
||||
}
|
||||
}
|
||||
|
||||
if err := fileOp.TarGzExtractPro(recoverFile, path.Dir(recoverFile), secret); err != nil {
|
||||
return err
|
||||
}
|
||||
tmpPath := strings.ReplaceAll(recoverFile, ".tar.gz", "")
|
||||
defer func() {
|
||||
_, _ = compose.Up(install.GetComposePath())
|
||||
_ = os.RemoveAll(strings.ReplaceAll(recoverFile, ".tar.gz", ""))
|
||||
}()
|
||||
|
||||
if !fileOp.Stat(tmpPath+"/app.json") || !fileOp.Stat(tmpPath+"/app.tar.gz") {
|
||||
return errors.New(i18n.GetMsgByKey("AppBackupFileIncomplete"))
|
||||
}
|
||||
var oldInstall model.AppInstall
|
||||
appJson, err := os.ReadFile(tmpPath + "/app.json")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := json.Unmarshal(appJson, &oldInstall); err != nil {
|
||||
return fmt.Errorf("unmarshal app.json failed, err: %v", err)
|
||||
}
|
||||
if oldInstall.App.Key != install.App.Key || oldInstall.Name != install.Name {
|
||||
return errors.New(i18n.GetMsgByKey("AppAttributesNotMatch"))
|
||||
}
|
||||
|
||||
newEnvFile := ""
|
||||
resources, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithAppInstallId(install.ID))
|
||||
for _, resource := range resources {
|
||||
var database model.Database
|
||||
switch resource.From {
|
||||
case constant.AppResourceRemote:
|
||||
database, err = databaseRepo.Get(repo.WithByID(resource.LinkId))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case constant.AppResourceLocal:
|
||||
resourceApp, err := appInstallRepo.GetFirst(repo.WithByID(resource.LinkId))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
database, err = databaseRepo.Get(databaseRepo.WithAppInstallID(resourceApp.ID), repo.WithByType(resource.Key), repo.WithByFrom(constant.AppResourceLocal), repo.WithByName(resourceApp.Name))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
switch database.Type {
|
||||
case constant.AppPostgresql:
|
||||
db, err := postgresqlRepo.Get(repo.WithByID(resource.ResourceId))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
taskName := task.GetTaskName(db.Name, task.TaskRecover, task.TaskScopeDatabase)
|
||||
t.LogStart(taskName)
|
||||
if err := handlePostgresqlRecover(dto.CommonRecover{
|
||||
Name: database.Name,
|
||||
DetailName: db.Name,
|
||||
File: fmt.Sprintf("%s/%s.sql.gz", tmpPath, install.Name),
|
||||
}, parentTask, true); err != nil {
|
||||
t.LogFailedWithErr(taskName, err)
|
||||
return err
|
||||
}
|
||||
t.LogSuccess(taskName)
|
||||
case constant.AppMysql, constant.AppMariaDB:
|
||||
db, err := mysqlRepo.Get(repo.WithByID(resource.ResourceId))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newDB, envMap, err := reCreateDB(db.ID, database, oldInstall.Env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
oldHost := fmt.Sprintf("\"PANEL_DB_HOST\":\"%v\"", envMap["PANEL_DB_HOST"].(string))
|
||||
newHost := fmt.Sprintf("\"PANEL_DB_HOST\":\"%v\"", database.Address)
|
||||
oldInstall.Env = strings.ReplaceAll(oldInstall.Env, oldHost, newHost)
|
||||
envMap["PANEL_DB_HOST"] = database.Address
|
||||
newEnvFile, err = coverEnvJsonToStr(oldInstall.Env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = appInstallResourceRepo.BatchUpdateBy(map[string]interface{}{"resource_id": newDB.ID}, repo.WithByID(resource.ID))
|
||||
taskName := task.GetTaskName(db.Name, task.TaskRecover, task.TaskScopeDatabase)
|
||||
t.LogStart(taskName)
|
||||
if err := handleMysqlRecover(dto.CommonRecover{
|
||||
Name: newDB.MysqlName,
|
||||
DetailName: newDB.Name,
|
||||
File: fmt.Sprintf("%s/%s.sql.gz", tmpPath, install.Name),
|
||||
}, parentTask, true, ""); err != nil {
|
||||
t.LogFailedWithErr(taskName, err)
|
||||
return err
|
||||
}
|
||||
t.LogSuccess(taskName)
|
||||
}
|
||||
}
|
||||
|
||||
appDir := install.GetPath()
|
||||
backPath := fmt.Sprintf("%s_bak", appDir)
|
||||
_ = fileOp.Rename(appDir, backPath)
|
||||
_ = fileOp.CreateDir(appDir, constant.DirPerm)
|
||||
|
||||
deCompressName := i18n.GetWithName("DeCompressFile", "app.tar.gz")
|
||||
t.LogStart(deCompressName)
|
||||
if err := fileOp.TarGzExtractPro(tmpPath+"/app.tar.gz", install.GetAppPath(), ""); err != nil {
|
||||
t.LogFailedWithErr(deCompressName, err)
|
||||
_ = fileOp.DeleteDir(appDir)
|
||||
_ = fileOp.Rename(backPath, appDir)
|
||||
return err
|
||||
}
|
||||
t.LogSuccess(deCompressName)
|
||||
_ = fileOp.DeleteDir(backPath)
|
||||
|
||||
if len(newEnvFile) != 0 {
|
||||
envPath := fmt.Sprintf("%s/%s/.env", install.GetAppPath(), install.Name)
|
||||
file, err := os.OpenFile(envPath, os.O_WRONLY|os.O_TRUNC, 0640)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
_, _ = file.WriteString(newEnvFile)
|
||||
}
|
||||
|
||||
oldInstall.ID = install.ID
|
||||
oldInstall.Status = constant.StatusRunning
|
||||
oldInstall.AppId = install.AppId
|
||||
oldInstall.AppDetailId = install.AppDetailId
|
||||
oldInstall.App.ID = install.AppId
|
||||
if err := appInstallRepo.Save(context.Background(), &oldInstall); err != nil {
|
||||
global.LOG.Errorf("save db app install failed, err: %v", err)
|
||||
return err
|
||||
}
|
||||
isOk = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
rollBackApp := func(t *task.Task) {
|
||||
if isRollback {
|
||||
return
|
||||
}
|
||||
if !isOk {
|
||||
t.Log(i18n.GetMsgByKey("RecoverFailedStartRollBack"))
|
||||
if err := handleAppRecover(install, t, rollbackFile, true, "", ""); err != nil {
|
||||
t.LogFailedWithErr(i18n.GetMsgByKey("Rollback"), err)
|
||||
return
|
||||
}
|
||||
t.LogSuccess(i18n.GetMsgByKey("Rollback"))
|
||||
_ = os.RemoveAll(rollbackFile)
|
||||
} else {
|
||||
_ = os.RemoveAll(rollbackFile)
|
||||
}
|
||||
}
|
||||
|
||||
recoverTask.AddSubTask(task.GetTaskName(install.Name, task.TaskRecover, task.TaskScopeApp), recoverApp, rollBackApp)
|
||||
if parentTask != nil {
|
||||
return recoverApp(parentTask)
|
||||
}
|
||||
return recoverTask.Execute()
|
||||
}
|
||||
|
||||
func doAppBackup(install *model.AppInstall, parentTask *task.Task, backupDir, fileName, excludes, secret string) error {
|
||||
fileOp := files.NewFileOp()
|
||||
tmpDir := fmt.Sprintf("%s/%s", backupDir, strings.ReplaceAll(fileName, ".tar.gz", ""))
|
||||
if !fileOp.Stat(tmpDir) {
|
||||
if err := os.MkdirAll(tmpDir, os.ModePerm); err != nil {
|
||||
return fmt.Errorf("mkdir %s failed, err: %v", backupDir, err)
|
||||
}
|
||||
}
|
||||
defer func() {
|
||||
_ = os.RemoveAll(tmpDir)
|
||||
}()
|
||||
|
||||
remarkInfo, _ := json.Marshal(install)
|
||||
remarkInfoPath := fmt.Sprintf("%s/app.json", tmpDir)
|
||||
if err := fileOp.SaveFile(remarkInfoPath, string(remarkInfo), fs.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
appPath := install.GetPath()
|
||||
if err := fileOp.TarGzCompressPro(true, appPath, path.Join(tmpDir, "app.tar.gz"), "", excludes); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resources, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithAppInstallId(install.ID))
|
||||
for _, resource := range resources {
|
||||
if err := backupDatabaseWithTask(parentTask, resource.Key, tmpDir, install.Name, resource.ResourceId); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
parentTask.LogStart(i18n.GetMsgByKey("CompressDir"))
|
||||
if err := fileOp.TarGzCompressPro(true, tmpDir, path.Join(backupDir, fileName), secret, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
parentTask.Log(i18n.GetWithName("CompressFileSuccess", fileName))
|
||||
return nil
|
||||
}
|
||||
|
||||
func reCreateDB(dbID uint, database model.Database, oldEnv string) (*model.DatabaseMysql, map[string]interface{}, error) {
|
||||
mysqlService := NewIMysqlService()
|
||||
ctx := context.Background()
|
||||
_ = mysqlService.Delete(ctx, dto.MysqlDBDelete{ID: dbID, Database: database.Name, Type: database.Type, DeleteBackup: false, ForceDelete: true})
|
||||
|
||||
envMap := make(map[string]interface{})
|
||||
if err := json.Unmarshal([]byte(oldEnv), &envMap); err != nil {
|
||||
return nil, envMap, err
|
||||
}
|
||||
oldName, _ := envMap["PANEL_DB_NAME"].(string)
|
||||
oldUser, _ := envMap["PANEL_DB_USER"].(string)
|
||||
oldPassword, _ := envMap["PANEL_DB_USER_PASSWORD"].(string)
|
||||
createDB, err := mysqlService.Create(context.Background(), dto.MysqlDBCreate{
|
||||
Name: oldName,
|
||||
From: database.From,
|
||||
Database: database.Name,
|
||||
Format: "utf8mb4",
|
||||
Username: oldUser,
|
||||
Password: oldPassword,
|
||||
Permission: "%",
|
||||
})
|
||||
cronjobs, _ := cronjobRepo.List(cronjobRepo.WithByDbName(fmt.Sprintf("%v", dbID)))
|
||||
for _, job := range cronjobs {
|
||||
_ = cronjobRepo.Update(job.ID, map[string]interface{}{"db_name": fmt.Sprintf("%v", createDB.ID)})
|
||||
}
|
||||
if err != nil {
|
||||
return nil, envMap, err
|
||||
}
|
||||
return createDB, envMap, nil
|
||||
}
|
||||
@@ -1,245 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/files"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/mysql/client"
|
||||
)
|
||||
|
||||
func (u *BackupService) MysqlBackup(req dto.CommonBackup) error {
|
||||
timeNow := time.Now().Format(constant.DateTimeSlimLayout)
|
||||
itemDir := fmt.Sprintf("database/%s/%s/%s", req.Type, req.Name, req.DetailName)
|
||||
targetDir := path.Join(global.Dir.LocalBackupDir, itemDir)
|
||||
fileName := fmt.Sprintf("%s_%s.sql.gz", req.DetailName, timeNow+common.RandStrAndNum(5))
|
||||
|
||||
databaseHelper := DatabaseHelper{Database: req.Name, DBType: req.Type, Name: req.DetailName}
|
||||
if err := handleMysqlBackup(databaseHelper, nil, targetDir, fileName, req.TaskID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
record := &model.BackupRecord{
|
||||
Type: req.Type,
|
||||
Name: req.Name,
|
||||
DetailName: req.DetailName,
|
||||
SourceAccountIDs: "1",
|
||||
DownloadAccountID: 1,
|
||||
FileDir: itemDir,
|
||||
FileName: fileName,
|
||||
Description: req.Description,
|
||||
}
|
||||
if err := backupRepo.CreateRecord(record); err != nil {
|
||||
global.LOG.Errorf("save backup record failed, err: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *BackupService) MysqlRecover(req dto.CommonRecover) error {
|
||||
if err := handleMysqlRecover(req, nil, false, req.TaskID); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *BackupService) MysqlRecoverByUpload(req dto.CommonRecover) error {
|
||||
file := req.File
|
||||
fileName := path.Base(req.File)
|
||||
if strings.HasSuffix(fileName, ".tar.gz") {
|
||||
fileNameItem := time.Now().Format(constant.DateTimeSlimLayout)
|
||||
dstDir := fmt.Sprintf("%s/%s", path.Dir(req.File), fileNameItem)
|
||||
fileOp := files.NewFileOp()
|
||||
if !fileOp.Stat(dstDir) {
|
||||
if err := fileOp.CreateDir(dstDir, os.ModePerm); err != nil {
|
||||
return fmt.Errorf("mkdir %s failed, err: %v", dstDir, err)
|
||||
}
|
||||
}
|
||||
if err := fileOp.TarGzExtractPro(req.File, dstDir, ""); err != nil {
|
||||
_ = os.RemoveAll(dstDir)
|
||||
return err
|
||||
}
|
||||
global.LOG.Infof("decompress file %s successful, now start to check test.sql is exist", req.File)
|
||||
hasTestSql := false
|
||||
_ = filepath.Walk(dstDir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if !info.IsDir() && info.Name() == "test.sql" {
|
||||
hasTestSql = true
|
||||
file = path
|
||||
fileName = "test.sql"
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if !hasTestSql {
|
||||
_ = os.RemoveAll(dstDir)
|
||||
return fmt.Errorf("no such file named test.sql in %s", fileName)
|
||||
}
|
||||
defer func() {
|
||||
_ = os.RemoveAll(dstDir)
|
||||
}()
|
||||
}
|
||||
|
||||
req.File = path.Dir(file) + "/" + fileName
|
||||
if err := handleMysqlRecover(req, nil, false, req.TaskID); err != nil {
|
||||
return err
|
||||
}
|
||||
global.LOG.Info("recover from uploads successful!")
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleMysqlBackup(db DatabaseHelper, parentTask *task.Task, targetDir, fileName, taskID string) error {
|
||||
var (
|
||||
err error
|
||||
backupTask *task.Task
|
||||
)
|
||||
backupTask = parentTask
|
||||
dbInfo, err := mysqlRepo.Get(repo.WithByName(db.Name), mysqlRepo.WithByMysqlName(db.Database))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
itemName := fmt.Sprintf("%s[%s] - %s", db.Database, db.DBType, db.Name)
|
||||
if parentTask == nil {
|
||||
backupTask, err = task.NewTaskWithOps(itemName, task.TaskBackup, task.TaskScopeDatabase, taskID, dbInfo.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
itemHandler := func() error { return doMysqlBackup(db, targetDir, fileName) }
|
||||
backupTask.AddSubTask(task.GetTaskName(itemName, task.TaskBackup, task.TaskScopeDatabase), func(t *task.Task) error { return itemHandler() }, nil)
|
||||
if parentTask != nil {
|
||||
return itemHandler()
|
||||
}
|
||||
return backupTask.Execute()
|
||||
}
|
||||
|
||||
func handleMysqlRecover(req dto.CommonRecover, parentTask *task.Task, isRollback bool, taskID string) error {
|
||||
var (
|
||||
err error
|
||||
itemTask *task.Task
|
||||
)
|
||||
itemTask = parentTask
|
||||
dbInfo, err := mysqlRepo.Get(repo.WithByName(req.DetailName), mysqlRepo.WithByMysqlName(req.Name))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
itemName := fmt.Sprintf("%s[%s] - %s", req.Name, req.Type, req.DetailName)
|
||||
if parentTask == nil {
|
||||
itemTask, err = task.NewTaskWithOps(itemName, task.TaskRecover, task.TaskScopeDatabase, taskID, dbInfo.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
recoverDatabase := func(t *task.Task) error {
|
||||
isOk := false
|
||||
fileOp := files.NewFileOp()
|
||||
if !fileOp.Stat(req.File) {
|
||||
return buserr.WithName("ErrFileNotFound", req.File)
|
||||
}
|
||||
dbInfo, err := mysqlRepo.Get(repo.WithByName(req.DetailName), mysqlRepo.WithByMysqlName(req.Name))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cli, version, err := LoadMysqlClientByFrom(req.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !isRollback {
|
||||
rollbackFile := path.Join(global.Dir.TmpDir, fmt.Sprintf("database/%s/%s_%s.sql.gz", req.Type, req.DetailName, time.Now().Format(constant.DateTimeSlimLayout)))
|
||||
if err := cli.Backup(client.BackupInfo{
|
||||
Name: req.DetailName,
|
||||
Type: req.Type,
|
||||
Version: version,
|
||||
Format: dbInfo.Format,
|
||||
TargetDir: path.Dir(rollbackFile),
|
||||
FileName: path.Base(rollbackFile),
|
||||
|
||||
Timeout: 300,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("backup mysql db %s for rollback before recover failed, err: %v", req.DetailName, err)
|
||||
}
|
||||
defer func() {
|
||||
if !isOk {
|
||||
global.LOG.Info("recover failed, start to rollback now")
|
||||
if err := cli.Recover(client.RecoverInfo{
|
||||
Name: req.DetailName,
|
||||
Type: req.Type,
|
||||
Version: version,
|
||||
Format: dbInfo.Format,
|
||||
SourceFile: rollbackFile,
|
||||
|
||||
Timeout: 300,
|
||||
}); err != nil {
|
||||
global.LOG.Errorf("rollback mysql db %s from %s failed, err: %v", req.DetailName, rollbackFile, err)
|
||||
} else {
|
||||
global.LOG.Infof("rollback mysql db %s from %s successful", req.DetailName, rollbackFile)
|
||||
}
|
||||
_ = os.RemoveAll(rollbackFile)
|
||||
} else {
|
||||
_ = os.RemoveAll(rollbackFile)
|
||||
}
|
||||
}()
|
||||
}
|
||||
if err := cli.Recover(client.RecoverInfo{
|
||||
Name: req.DetailName,
|
||||
Type: req.Type,
|
||||
Version: version,
|
||||
Format: dbInfo.Format,
|
||||
SourceFile: req.File,
|
||||
|
||||
Timeout: 300,
|
||||
}); err != nil {
|
||||
global.LOG.Errorf("recover mysql db %s from %s failed, err: %v", req.DetailName, req.File, err)
|
||||
return err
|
||||
}
|
||||
isOk = true
|
||||
return nil
|
||||
}
|
||||
itemTask.AddSubTask(i18n.GetMsgByKey("TaskRecover"), recoverDatabase, nil)
|
||||
if parentTask != nil {
|
||||
return recoverDatabase(parentTask)
|
||||
}
|
||||
|
||||
return itemTask.Execute()
|
||||
}
|
||||
|
||||
func doMysqlBackup(db DatabaseHelper, targetDir, fileName string) error {
|
||||
dbInfo, err := mysqlRepo.Get(repo.WithByName(db.Name), mysqlRepo.WithByMysqlName(db.Database))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cli, version, err := LoadMysqlClientByFrom(db.Database)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
backupInfo := client.BackupInfo{
|
||||
Name: db.Name,
|
||||
Type: db.DBType,
|
||||
Version: version,
|
||||
Format: dbInfo.Format,
|
||||
TargetDir: targetDir,
|
||||
FileName: fileName,
|
||||
|
||||
Timeout: 300,
|
||||
}
|
||||
return cli.Backup(backupInfo)
|
||||
}
|
||||
@@ -1,223 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
||||
pgclient "github.com/1Panel-dev/1Panel/agent/utils/postgresql/client"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/files"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/postgresql/client"
|
||||
)
|
||||
|
||||
func (u *BackupService) PostgresqlBackup(req dto.CommonBackup) error {
|
||||
timeNow := time.Now().Format(constant.DateTimeSlimLayout)
|
||||
itemDir := fmt.Sprintf("database/%s/%s/%s", req.Type, req.Name, req.DetailName)
|
||||
targetDir := path.Join(global.Dir.LocalBackupDir, itemDir)
|
||||
fileName := fmt.Sprintf("%s_%s.sql.gz", req.DetailName, timeNow+common.RandStrAndNum(5))
|
||||
|
||||
databaseHelper := DatabaseHelper{Database: req.Name, DBType: req.Type, Name: req.DetailName}
|
||||
if err := handlePostgresqlBackup(databaseHelper, nil, targetDir, fileName, req.TaskID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
record := &model.BackupRecord{
|
||||
Type: req.Type,
|
||||
Name: req.Name,
|
||||
DetailName: req.DetailName,
|
||||
SourceAccountIDs: "1",
|
||||
DownloadAccountID: 1,
|
||||
FileDir: itemDir,
|
||||
FileName: fileName,
|
||||
Description: req.Description,
|
||||
}
|
||||
if err := backupRepo.CreateRecord(record); err != nil {
|
||||
global.LOG.Errorf("save backup record failed, err: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (u *BackupService) PostgresqlRecover(req dto.CommonRecover) error {
|
||||
if err := handlePostgresqlRecover(req, nil, false); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *BackupService) PostgresqlRecoverByUpload(req dto.CommonRecover) error {
|
||||
file := req.File
|
||||
fileName := path.Base(req.File)
|
||||
if strings.HasSuffix(fileName, ".tar.gz") {
|
||||
fileNameItem := time.Now().Format(constant.DateTimeSlimLayout)
|
||||
dstDir := fmt.Sprintf("%s/%s", path.Dir(req.File), fileNameItem)
|
||||
fileOp := files.NewFileOp()
|
||||
if !fileOp.Stat(dstDir) {
|
||||
if err := fileOp.CreateDir(dstDir, os.ModePerm); err != nil {
|
||||
return fmt.Errorf("mkdir %s failed, err: %v", dstDir, err)
|
||||
}
|
||||
}
|
||||
if err := fileOp.TarGzExtractPro(req.File, dstDir, ""); err != nil {
|
||||
_ = os.RemoveAll(dstDir)
|
||||
return err
|
||||
}
|
||||
global.LOG.Infof("decompress file %s successful, now start to check test.sql is exist", req.File)
|
||||
hasTestSql := false
|
||||
_ = filepath.Walk(dstDir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if !info.IsDir() && info.Name() == "test.sql" {
|
||||
hasTestSql = true
|
||||
file = path
|
||||
fileName = "test.sql"
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if !hasTestSql {
|
||||
_ = os.RemoveAll(dstDir)
|
||||
return fmt.Errorf("no such file named test.sql in %s", fileName)
|
||||
}
|
||||
defer func() {
|
||||
_ = os.RemoveAll(dstDir)
|
||||
}()
|
||||
}
|
||||
|
||||
req.File = path.Dir(file) + "/" + fileName
|
||||
if err := handlePostgresqlRecover(req, nil, false); err != nil {
|
||||
return err
|
||||
}
|
||||
global.LOG.Info("recover from uploads successful!")
|
||||
return nil
|
||||
}
|
||||
|
||||
func handlePostgresqlBackup(db DatabaseHelper, parentTask *task.Task, targetDir, fileName, taskID string) error {
|
||||
var (
|
||||
err error
|
||||
backupTask *task.Task
|
||||
)
|
||||
backupTask = parentTask
|
||||
itemName := fmt.Sprintf("%s - %s", db.Database, db.Name)
|
||||
if parentTask == nil {
|
||||
backupTask, err = task.NewTaskWithOps(itemName, task.TaskBackup, task.TaskScopeDatabase, taskID, db.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
itemHandler := func() error { return doPostgresqlgBackup(db, targetDir, fileName) }
|
||||
backupTask.AddSubTask(task.GetTaskName(itemName, task.TaskBackup, task.TaskScopeWebsite), func(task *task.Task) error { return itemHandler() }, nil)
|
||||
if parentTask != nil {
|
||||
return itemHandler()
|
||||
}
|
||||
return backupTask.Execute()
|
||||
}
|
||||
|
||||
func handlePostgresqlRecover(req dto.CommonRecover, parentTask *task.Task, isRollback bool) error {
|
||||
var (
|
||||
err error
|
||||
itemTask *task.Task
|
||||
)
|
||||
dbInfo, err := postgresqlRepo.Get(repo.WithByName(req.DetailName), postgresqlRepo.WithByPostgresqlName(req.Name))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
itemTask = parentTask
|
||||
if parentTask == nil {
|
||||
itemTask, err = task.NewTaskWithOps("Redis", task.TaskRecover, task.TaskScopeDatabase, req.TaskID, dbInfo.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
recoverDatabase := func(t *task.Task) error {
|
||||
isOk := false
|
||||
fileOp := files.NewFileOp()
|
||||
if !fileOp.Stat(req.File) {
|
||||
return buserr.WithName("ErrFileNotFound", req.File)
|
||||
}
|
||||
|
||||
cli, err := LoadPostgresqlClientByFrom(req.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cli.Close()
|
||||
|
||||
if !isRollback {
|
||||
rollbackFile := path.Join(global.Dir.TmpDir, fmt.Sprintf("database/%s/%s_%s.sql.gz", req.Type, req.DetailName, time.Now().Format(constant.DateTimeSlimLayout)))
|
||||
if err := cli.Backup(client.BackupInfo{
|
||||
Name: req.DetailName,
|
||||
TargetDir: path.Dir(rollbackFile),
|
||||
FileName: path.Base(rollbackFile),
|
||||
|
||||
Timeout: 300,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("backup postgresql db %s for rollback before recover failed, err: %v", req.DetailName, err)
|
||||
}
|
||||
defer func() {
|
||||
if !isOk {
|
||||
global.LOG.Info("recover failed, start to rollback now")
|
||||
if err := cli.Recover(client.RecoverInfo{
|
||||
Name: req.DetailName,
|
||||
SourceFile: rollbackFile,
|
||||
|
||||
Timeout: 300,
|
||||
}); err != nil {
|
||||
global.LOG.Errorf("rollback postgresql db %s from %s failed, err: %v", req.DetailName, rollbackFile, err)
|
||||
} else {
|
||||
global.LOG.Infof("rollback postgresql db %s from %s successful", req.DetailName, rollbackFile)
|
||||
}
|
||||
_ = os.RemoveAll(rollbackFile)
|
||||
} else {
|
||||
_ = os.RemoveAll(rollbackFile)
|
||||
}
|
||||
}()
|
||||
}
|
||||
if err := cli.Recover(client.RecoverInfo{
|
||||
Name: req.DetailName,
|
||||
SourceFile: req.File,
|
||||
Username: dbInfo.Username,
|
||||
Timeout: 300,
|
||||
}); err != nil {
|
||||
global.LOG.Errorf("recover postgresql db %s from %s failed, err: %v", req.DetailName, req.File, err)
|
||||
return err
|
||||
}
|
||||
isOk = true
|
||||
return nil
|
||||
}
|
||||
itemTask.AddSubTask(i18n.GetMsgByKey("TaskRecover"), recoverDatabase, nil)
|
||||
if parentTask != nil {
|
||||
return recoverDatabase(parentTask)
|
||||
}
|
||||
|
||||
return itemTask.Execute()
|
||||
}
|
||||
|
||||
func doPostgresqlgBackup(db DatabaseHelper, targetDir, fileName string) error {
|
||||
cli, err := LoadPostgresqlClientByFrom(db.Database)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cli.Close()
|
||||
backupInfo := pgclient.BackupInfo{
|
||||
Name: db.Name,
|
||||
TargetDir: targetDir,
|
||||
FileName: fileName,
|
||||
|
||||
Timeout: 300,
|
||||
}
|
||||
return cli.Backup(backupInfo)
|
||||
}
|
||||
@@ -1,276 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/jinzhu/copier"
|
||||
)
|
||||
|
||||
type BackupRecordService struct{}
|
||||
|
||||
type IBackupRecordService interface {
|
||||
SearchRecordsWithPage(search dto.RecordSearch) (int64, []dto.BackupRecords, error)
|
||||
SearchRecordsByCronjobWithPage(search dto.RecordSearchByCronjob) (int64, []dto.BackupRecords, error)
|
||||
DownloadRecord(info dto.DownloadRecord) (string, error)
|
||||
DeleteRecordByName(backupType, name, detailName string, withDeleteFile bool) error
|
||||
BatchDeleteRecord(ids []uint) error
|
||||
ListAppRecords(name, detailName, fileName string) ([]model.BackupRecord, error)
|
||||
|
||||
ListFiles(req dto.OperateByID) []string
|
||||
LoadRecordSize(req dto.SearchForSize) ([]dto.RecordFileSize, error)
|
||||
UpdateDescription(req dto.UpdateDescription) error
|
||||
}
|
||||
|
||||
func NewIBackupRecordService() IBackupRecordService {
|
||||
return &BackupRecordService{}
|
||||
}
|
||||
|
||||
func (u *BackupRecordService) SearchRecordsWithPage(search dto.RecordSearch) (int64, []dto.BackupRecords, error) {
|
||||
total, records, err := backupRepo.PageRecord(
|
||||
search.Page, search.PageSize,
|
||||
repo.WithOrderBy("created_at desc"),
|
||||
repo.WithByName(search.Name),
|
||||
repo.WithByType(search.Type),
|
||||
repo.WithByDetailName(search.DetailName),
|
||||
)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
accounts, _ := backupRepo.List()
|
||||
var data []dto.BackupRecords
|
||||
for _, record := range records {
|
||||
var item dto.BackupRecords
|
||||
if err := copier.Copy(&item, &record); err != nil {
|
||||
global.LOG.Errorf("copy backup account to dto backup info failed, err: %v", err)
|
||||
}
|
||||
for _, account := range accounts {
|
||||
if account.ID == record.DownloadAccountID {
|
||||
item.DownloadAccountID = account.ID
|
||||
item.AccountName = account.Name
|
||||
item.AccountType = account.Type
|
||||
break
|
||||
}
|
||||
}
|
||||
data = append(data, item)
|
||||
}
|
||||
return total, data, err
|
||||
}
|
||||
|
||||
func (u *BackupRecordService) SearchRecordsByCronjobWithPage(search dto.RecordSearchByCronjob) (int64, []dto.BackupRecords, error) {
|
||||
total, records, err := backupRepo.PageRecord(
|
||||
search.Page, search.PageSize,
|
||||
repo.WithOrderBy("created_at desc"),
|
||||
backupRepo.WithByCronID(search.CronjobID),
|
||||
)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
accounts, _ := backupRepo.List()
|
||||
var data []dto.BackupRecords
|
||||
for _, record := range records {
|
||||
var item dto.BackupRecords
|
||||
if err := copier.Copy(&item, &record); err != nil {
|
||||
global.LOG.Errorf("copy backup account to dto backup info failed, err: %v", err)
|
||||
}
|
||||
for _, account := range accounts {
|
||||
if account.ID == record.DownloadAccountID {
|
||||
item.DownloadAccountID = account.ID
|
||||
item.AccountName = account.Name
|
||||
item.AccountType = account.Type
|
||||
break
|
||||
}
|
||||
}
|
||||
data = append(data, item)
|
||||
}
|
||||
return total, data, err
|
||||
}
|
||||
|
||||
func (u *BackupRecordService) DownloadRecord(info dto.DownloadRecord) (string, error) {
|
||||
account, client, err := NewBackupClientWithID(info.DownloadAccountID)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("new cloud storage client failed, err: %v", err)
|
||||
}
|
||||
if account.Type == "LOCAL" {
|
||||
return path.Join(global.Dir.LocalBackupDir, info.FileDir, info.FileName), nil
|
||||
}
|
||||
targetPath := fmt.Sprintf("%s/download/%s/%s", global.Dir.DataDir, info.FileDir, info.FileName)
|
||||
if _, err := os.Stat(path.Dir(targetPath)); err != nil && os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(path.Dir(targetPath), os.ModePerm); err != nil {
|
||||
global.LOG.Errorf("mkdir %s failed, err: %v", path.Dir(targetPath), err)
|
||||
}
|
||||
}
|
||||
srcPath := fmt.Sprintf("%s/%s", info.FileDir, info.FileName)
|
||||
if len(account.BackupPath) != 0 {
|
||||
srcPath = path.Join(account.BackupPath, srcPath)
|
||||
}
|
||||
if exist, _ := client.Exist(srcPath); exist {
|
||||
isOK, err := client.Download(srcPath, targetPath)
|
||||
if !isOK || err != nil {
|
||||
return "", fmt.Errorf("cloud storage download failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
return targetPath, nil
|
||||
}
|
||||
|
||||
func (u *BackupRecordService) DeleteRecordByName(backupType, name, detailName string, withDeleteFile bool) error {
|
||||
if !withDeleteFile {
|
||||
return backupRepo.DeleteRecord(context.Background(), repo.WithByType(backupType), repo.WithByName(name), repo.WithByDetailName(detailName))
|
||||
}
|
||||
|
||||
records, err := backupRepo.ListRecord(repo.WithByType(backupType), repo.WithByName(name), repo.WithByDetailName(detailName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, record := range records {
|
||||
backup, client, err := NewBackupClientWithID(record.DownloadAccountID)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("new client for backup account failed, err: %v", err)
|
||||
continue
|
||||
}
|
||||
if _, err = client.Delete(path.Join(backup.BackupPath, record.FileDir, record.FileName)); err != nil {
|
||||
global.LOG.Errorf("remove file %s failed, err: %v", path.Join(record.FileDir, record.FileName), err)
|
||||
}
|
||||
_ = backupRepo.DeleteRecord(context.Background(), repo.WithByID(record.ID))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *BackupRecordService) BatchDeleteRecord(ids []uint) error {
|
||||
records, err := backupRepo.ListRecord(repo.WithByIDs(ids))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, record := range records {
|
||||
backup, client, err := NewBackupClientWithID(record.DownloadAccountID)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("new client for backup account failed, err: %v", err)
|
||||
continue
|
||||
}
|
||||
if _, err = client.Delete(path.Join(backup.BackupPath, record.FileDir, record.FileName)); err != nil {
|
||||
global.LOG.Errorf("remove file %s failed, err: %v", path.Join(record.FileDir, record.FileName), err)
|
||||
}
|
||||
}
|
||||
return backupRepo.DeleteRecord(context.Background(), repo.WithByIDs(ids))
|
||||
}
|
||||
|
||||
func (u *BackupRecordService) ListAppRecords(name, detailName, fileName string) ([]model.BackupRecord, error) {
|
||||
records, err := backupRepo.ListRecord(
|
||||
repo.WithOrderBy("created_at asc"),
|
||||
repo.WithByName(name),
|
||||
repo.WithByType("app"),
|
||||
backupRepo.WithFileNameStartWith(fileName),
|
||||
backupRepo.WithByDetailName(detailName),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return records, err
|
||||
}
|
||||
|
||||
func (u *BackupRecordService) ListFiles(req dto.OperateByID) []string {
|
||||
backupItem, client, err := NewBackupClientWithID(req.ID)
|
||||
if err != nil {
|
||||
return []string{}
|
||||
}
|
||||
prefix := "system_snapshot"
|
||||
if len(backupItem.BackupPath) != 0 {
|
||||
prefix = path.Join(backupItem.BackupPath, prefix)
|
||||
}
|
||||
files, err := client.ListObjects(prefix)
|
||||
if err != nil {
|
||||
global.LOG.Debugf("load files failed, err: %v", err)
|
||||
return []string{}
|
||||
}
|
||||
var datas []string
|
||||
for _, file := range files {
|
||||
fileName := path.Base(file)
|
||||
if len(file) != 0 && (strings.HasPrefix(fileName, "1panel-v2.") || strings.HasPrefix(fileName, "snapshot-1panel-v2.")) {
|
||||
datas = append(datas, path.Base(file))
|
||||
}
|
||||
}
|
||||
return datas
|
||||
}
|
||||
|
||||
type backupSizeHelper struct {
|
||||
ID uint `json:"id"`
|
||||
DownloadID uint `json:"downloadID"`
|
||||
FilePath string `json:"filePath"`
|
||||
Size uint `json:"size"`
|
||||
}
|
||||
|
||||
func (u *BackupRecordService) LoadRecordSize(req dto.SearchForSize) ([]dto.RecordFileSize, error) {
|
||||
var list []backupSizeHelper
|
||||
switch req.Type {
|
||||
case "snapshot":
|
||||
_, records, err := snapshotRepo.Page(req.Page, req.PageSize, repo.WithByLikeName(req.Info), repo.WithOrderRuleBy(req.OrderBy, req.Order))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, item := range records {
|
||||
list = append(list, backupSizeHelper{ID: item.ID, DownloadID: item.DownloadAccountID, FilePath: fmt.Sprintf("system_snapshot/%s.tar.gz", item.Name)})
|
||||
}
|
||||
case "cronjob":
|
||||
_, records, err := backupRepo.PageRecord(req.Page, req.PageSize, repo.WithOrderBy("created_at desc"), backupRepo.WithByCronID(req.CronjobID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, item := range records {
|
||||
list = append(list, backupSizeHelper{ID: item.ID, DownloadID: item.DownloadAccountID, FilePath: path.Join(item.FileDir, item.FileName)})
|
||||
}
|
||||
default:
|
||||
_, records, err := backupRepo.PageRecord(
|
||||
req.Page, req.PageSize,
|
||||
repo.WithOrderBy("created_at desc"),
|
||||
repo.WithByName(req.Name),
|
||||
repo.WithByType(req.Type),
|
||||
repo.WithByDetailName(req.DetailName),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, item := range records {
|
||||
list = append(list, backupSizeHelper{ID: item.ID, DownloadID: item.DownloadAccountID, FilePath: path.Join(item.FileDir, item.FileName)})
|
||||
}
|
||||
}
|
||||
recordMap := make(map[uint]struct{})
|
||||
var recordIds []string
|
||||
for _, record := range list {
|
||||
if _, ok := recordMap[record.DownloadID]; !ok {
|
||||
recordMap[record.DownloadID] = struct{}{}
|
||||
recordIds = append(recordIds, fmt.Sprintf("%v", record.DownloadID))
|
||||
}
|
||||
}
|
||||
clientMap, err := NewBackupClientMap(recordIds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var datas []dto.RecordFileSize
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < len(list); i++ {
|
||||
datas = append(datas, dto.RecordFileSize{ID: list[i].ID})
|
||||
if val, ok := clientMap[fmt.Sprintf("%v", list[i].DownloadID)]; ok {
|
||||
wg.Add(1)
|
||||
go func(index int) {
|
||||
datas[index].Size, _ = val.client.Size(path.Join(val.backupPath, list[i].FilePath))
|
||||
wg.Done()
|
||||
}(i)
|
||||
}
|
||||
}
|
||||
wg.Wait()
|
||||
return datas, nil
|
||||
}
|
||||
|
||||
func (u *BackupRecordService) UpdateDescription(req dto.UpdateDescription) error {
|
||||
return backupRepo.UpdateRecordByMap(req.ID, map[string]interface{}{"description": req.Description})
|
||||
}
|
||||
@@ -1,233 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/compose"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/files"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (u *BackupService) RedisBackup(req dto.CommonBackup) error {
|
||||
redisInfo, err := appInstallRepo.LoadBaseInfo("redis", req.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
appendonly, err := configGetStr(redisInfo.ContainerName, redisInfo.Password, "appendonly")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
global.LOG.Infof("appendonly in redis conf is %s", appendonly)
|
||||
|
||||
timeNow := time.Now().Format(constant.DateTimeSlimLayout) + common.RandStrAndNum(5)
|
||||
fileName := fmt.Sprintf("%s.rdb", timeNow)
|
||||
if appendonly == "yes" {
|
||||
if strings.HasPrefix(redisInfo.Version, "6.") {
|
||||
fileName = fmt.Sprintf("%s.aof", timeNow)
|
||||
} else {
|
||||
fileName = fmt.Sprintf("%s.tar.gz", timeNow)
|
||||
}
|
||||
}
|
||||
itemDir := fmt.Sprintf("database/redis/%s", redisInfo.Name)
|
||||
backupDir := path.Join(global.Dir.LocalBackupDir, itemDir)
|
||||
if err := handleRedisBackup(redisInfo, nil, backupDir, fileName, req.Secret, req.TaskID); err != nil {
|
||||
return err
|
||||
}
|
||||
record := &model.BackupRecord{
|
||||
Type: "redis",
|
||||
Name: req.Name,
|
||||
SourceAccountIDs: "1",
|
||||
DownloadAccountID: 1,
|
||||
FileDir: itemDir,
|
||||
FileName: fileName,
|
||||
Description: req.Description,
|
||||
}
|
||||
if err := backupRepo.CreateRecord(record); err != nil {
|
||||
global.LOG.Errorf("save backup record failed, err: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *BackupService) RedisRecover(req dto.CommonRecover) error {
|
||||
redisInfo, err := appInstallRepo.LoadBaseInfo("redis", req.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
global.LOG.Infof("recover redis from backup file %s", req.File)
|
||||
if err := handleRedisRecover(redisInfo, nil, req.File, false, req.Secret, req.TaskID); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleRedisBackup(redisInfo *repo.RootInfo, parentTask *task.Task, backupDir, fileName, secret, taskID string) error {
|
||||
var (
|
||||
err error
|
||||
itemTask *task.Task
|
||||
)
|
||||
itemTask = parentTask
|
||||
if parentTask == nil {
|
||||
itemTask, err = task.NewTaskWithOps("Redis", task.TaskBackup, task.TaskScopeDatabase, taskID, redisInfo.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
backupDatabase := func(t *task.Task) error {
|
||||
fileOp := files.NewFileOp()
|
||||
if !fileOp.Stat(backupDir) {
|
||||
if err := os.MkdirAll(backupDir, os.ModePerm); err != nil {
|
||||
return fmt.Errorf("mkdir %s failed, err: %v", backupDir, err)
|
||||
}
|
||||
}
|
||||
|
||||
stdout, err := cmd.RunDefaultWithStdoutBashCf("docker exec %s redis-cli -a %s --no-auth-warning save", redisInfo.ContainerName, redisInfo.Password)
|
||||
if err != nil {
|
||||
return errors.New(string(stdout))
|
||||
}
|
||||
|
||||
if strings.HasSuffix(fileName, ".tar.gz") {
|
||||
redisDataDir := fmt.Sprintf("%s/%s/%s/data/appendonlydir", global.Dir.AppInstallDir, "redis", redisInfo.Name)
|
||||
if err := fileOp.TarGzCompressPro(true, redisDataDir, path.Join(backupDir, fileName), secret, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if strings.HasSuffix(fileName, ".aof") {
|
||||
stdout1, err := cmd.RunDefaultWithStdoutBashCf("docker cp %s:/data/appendonly.aof %s/%s", redisInfo.ContainerName, backupDir, fileName)
|
||||
if err != nil {
|
||||
return errors.New(string(stdout1))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
stdout1, err1 := cmd.RunDefaultWithStdoutBashCf("docker cp %s:/data/dump.rdb %s/%s", redisInfo.ContainerName, backupDir, fileName)
|
||||
if err1 != nil {
|
||||
return errors.New(string(stdout1))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
itemTask.AddSubTask(i18n.GetMsgByKey("TaskBackup"), backupDatabase, nil)
|
||||
if parentTask != nil {
|
||||
return backupDatabase(parentTask)
|
||||
}
|
||||
|
||||
return itemTask.Execute()
|
||||
}
|
||||
|
||||
func handleRedisRecover(redisInfo *repo.RootInfo, parentTask *task.Task, recoverFile string, isRollback bool, secret, taskID string) error {
|
||||
var (
|
||||
err error
|
||||
itemTask *task.Task
|
||||
)
|
||||
itemTask = parentTask
|
||||
if parentTask == nil {
|
||||
itemTask, err = task.NewTaskWithOps("Redis", task.TaskRecover, task.TaskScopeDatabase, taskID, redisInfo.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
recoverDatabase := func(t *task.Task) error {
|
||||
fileOp := files.NewFileOp()
|
||||
if !fileOp.Stat(recoverFile) {
|
||||
return buserr.WithName("ErrFileNotFound", recoverFile)
|
||||
}
|
||||
|
||||
appendonly, err := configGetStr(redisInfo.ContainerName, redisInfo.Password, "appendonly")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if appendonly == "yes" {
|
||||
if strings.HasPrefix(redisInfo.Version, "6.") && !strings.HasSuffix(recoverFile, ".aof") {
|
||||
return buserr.New("ErrTypeOfRedis")
|
||||
}
|
||||
if strings.HasPrefix(redisInfo.Version, "7.") && !strings.HasSuffix(recoverFile, ".tar.gz") {
|
||||
return buserr.New("ErrTypeOfRedis")
|
||||
}
|
||||
} else {
|
||||
if !strings.HasSuffix(recoverFile, ".rdb") {
|
||||
return buserr.New("ErrTypeOfRedis")
|
||||
}
|
||||
}
|
||||
|
||||
global.LOG.Infof("appendonly in redis conf is %s", appendonly)
|
||||
isOk := false
|
||||
if !isRollback {
|
||||
suffix := "rdb"
|
||||
if appendonly == "yes" {
|
||||
if strings.HasPrefix(redisInfo.Version, "6.") {
|
||||
suffix = "aof"
|
||||
} else {
|
||||
suffix = "tar.gz"
|
||||
}
|
||||
}
|
||||
rollbackFile := path.Join(global.Dir.TmpDir, fmt.Sprintf("database/redis/%s_%s.%s", redisInfo.Name, time.Now().Format(constant.DateTimeSlimLayout), suffix))
|
||||
if err := handleRedisBackup(redisInfo, nil, path.Dir(rollbackFile), path.Base(rollbackFile), secret, ""); err != nil {
|
||||
return fmt.Errorf("backup database %s for rollback before recover failed, err: %v", redisInfo.Name, err)
|
||||
}
|
||||
defer func() {
|
||||
if !isOk {
|
||||
global.LOG.Info("recover failed, start to rollback now")
|
||||
if err := handleRedisRecover(redisInfo, itemTask, rollbackFile, true, secret, ""); err != nil {
|
||||
global.LOG.Errorf("rollback redis from %s failed, err: %v", rollbackFile, err)
|
||||
return
|
||||
}
|
||||
global.LOG.Infof("rollback redis from %s successful", rollbackFile)
|
||||
_ = os.RemoveAll(rollbackFile)
|
||||
} else {
|
||||
_ = os.RemoveAll(rollbackFile)
|
||||
}
|
||||
}()
|
||||
}
|
||||
composeDir := fmt.Sprintf("%s/redis/%s", global.Dir.AppInstallDir, redisInfo.Name)
|
||||
if _, err := compose.Down(composeDir + "/docker-compose.yml"); err != nil {
|
||||
return err
|
||||
}
|
||||
if appendonly == "yes" && strings.HasPrefix(redisInfo.Version, "7.") {
|
||||
redisDataDir := fmt.Sprintf("%s/%s/%s/data", global.Dir.AppInstallDir, "redis", redisInfo.Name)
|
||||
if err := fileOp.TarGzExtractPro(recoverFile, redisDataDir, secret); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
itemName := "dump.rdb"
|
||||
if appendonly == "yes" && strings.HasPrefix(redisInfo.Version, "6.") {
|
||||
itemName = "appendonly.aof"
|
||||
}
|
||||
input, err := os.ReadFile(recoverFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = os.WriteFile(composeDir+"/data/"+itemName, input, 0640); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if _, err := compose.Up(composeDir + "/docker-compose.yml"); err != nil {
|
||||
return err
|
||||
}
|
||||
isOk = true
|
||||
return nil
|
||||
}
|
||||
itemTask.AddSubTask(i18n.GetMsgByKey("TaskRecover"), recoverDatabase, nil)
|
||||
if parentTask != nil {
|
||||
return recoverDatabase(parentTask)
|
||||
}
|
||||
|
||||
return itemTask.Execute()
|
||||
}
|
||||
@@ -1,353 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/compose"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/files"
|
||||
)
|
||||
|
||||
func (u *BackupService) WebsiteBackup(req dto.CommonBackup) error {
|
||||
website, err := websiteRepo.GetFirst(websiteRepo.WithAlias(req.DetailName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
timeNow := time.Now().Format(constant.DateTimeSlimLayout)
|
||||
itemDir := fmt.Sprintf("website/%s", website.Alias)
|
||||
backupDir := path.Join(global.Dir.LocalBackupDir, itemDir)
|
||||
fileName := fmt.Sprintf("%s_%s.tar.gz", website.Alias, timeNow+common.RandStrAndNum(5))
|
||||
|
||||
go func() {
|
||||
if err = handleWebsiteBackup(&website, nil, backupDir, fileName, "", req.Secret, req.TaskID); err != nil {
|
||||
global.LOG.Errorf("backup website %s failed, err: %v", website.Alias, err)
|
||||
return
|
||||
}
|
||||
record := &model.BackupRecord{
|
||||
Type: "website",
|
||||
Name: website.Alias,
|
||||
DetailName: website.Alias,
|
||||
SourceAccountIDs: "1",
|
||||
DownloadAccountID: 1,
|
||||
FileDir: itemDir,
|
||||
FileName: fileName,
|
||||
Description: req.Description,
|
||||
}
|
||||
if err = backupRepo.CreateRecord(record); err != nil {
|
||||
global.LOG.Errorf("save backup record failed, err: %v", err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *BackupService) WebsiteRecover(req dto.CommonRecover) error {
|
||||
website, err := websiteRepo.GetFirst(websiteRepo.WithAlias(req.DetailName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
if err := handleWebsiteRecover(&website, req.File, false, req.Secret, req.TaskID); err != nil {
|
||||
global.LOG.Errorf("recover website %s failed, err: %v", website.Alias, err)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleWebsiteRecover(website *model.Website, recoverFile string, isRollback bool, secret, taskID string) error {
|
||||
recoverTask, err := task.NewTaskWithOps(website.PrimaryDomain, task.TaskRecover, task.TaskScopeWebsite, taskID, website.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
recoverTask.AddSubTask(task.GetTaskName(website.PrimaryDomain, task.TaskRecover, task.TaskScopeWebsite), func(t *task.Task) error {
|
||||
isOk := false
|
||||
if !isRollback {
|
||||
rollbackFile := path.Join(global.Dir.TmpDir, fmt.Sprintf("website/%s_%s.tar.gz", website.Alias, time.Now().Format(constant.DateTimeSlimLayout)))
|
||||
if err := handleWebsiteBackup(website, recoverTask, path.Dir(rollbackFile), path.Base(rollbackFile), "", "", taskID); err != nil {
|
||||
return fmt.Errorf("backup website %s for rollback before recover failed, err: %v", website.Alias, err)
|
||||
}
|
||||
defer func() {
|
||||
if !isOk {
|
||||
t.LogStart(i18n.GetMsgByKey("Rollback"))
|
||||
if err := handleWebsiteRecover(website, rollbackFile, true, "", taskID); err != nil {
|
||||
t.LogFailedWithErr(i18n.GetMsgByKey("Rollback"), err)
|
||||
return
|
||||
}
|
||||
t.LogSuccess(i18n.GetMsgByKey("Rollback"))
|
||||
_ = os.RemoveAll(rollbackFile)
|
||||
} else {
|
||||
_ = os.RemoveAll(rollbackFile)
|
||||
}
|
||||
}()
|
||||
}
|
||||
fileOp := files.NewFileOp()
|
||||
tmpPath := strings.ReplaceAll(recoverFile, ".tar.gz", "")
|
||||
t.Log(i18n.GetWithName("DeCompressFile", recoverFile))
|
||||
if err = fileOp.TarGzExtractPro(recoverFile, path.Dir(recoverFile), secret); err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = os.RemoveAll(tmpPath)
|
||||
}()
|
||||
|
||||
var oldWebsite model.Website
|
||||
websiteJson, err := os.ReadFile(tmpPath + "/website.json")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = json.Unmarshal(websiteJson, &oldWebsite); err != nil {
|
||||
return fmt.Errorf("unmarshal app.json failed, err: %v", err)
|
||||
}
|
||||
|
||||
if err = checkValidOfWebsite(&oldWebsite, website); err != nil {
|
||||
t.Log(i18n.GetWithName("ErrCheckValid", err.Error()))
|
||||
return err
|
||||
}
|
||||
|
||||
temPathWithName := tmpPath + "/" + website.Alias
|
||||
if !fileOp.Stat(tmpPath+"/website.json") || !fileOp.Stat(temPathWithName+".conf") || !fileOp.Stat(temPathWithName+".web.tar.gz") {
|
||||
return buserr.WithDetail("ErrBackupExist", ".conf or .web.tar.gz", nil)
|
||||
}
|
||||
if website.Type == constant.Deployment {
|
||||
if !fileOp.Stat(temPathWithName + ".app.tar.gz") {
|
||||
return buserr.WithDetail("ErrBackupExist", ".app.tar.gz", nil)
|
||||
}
|
||||
}
|
||||
|
||||
nginxInfo, err := appInstallRepo.LoadBaseInfo(constant.AppOpenresty, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = fileOp.CopyFile(fmt.Sprintf("%s/%s.conf", tmpPath, website.Alias), GetOpenrestyDir(SiteConfDir)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch website.Type {
|
||||
case constant.Deployment:
|
||||
app, err := appInstallRepo.GetFirst(repo.WithByID(website.AppInstallID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
taskName := task.GetTaskName(app.Name, task.TaskRecover, task.TaskScopeApp)
|
||||
t.LogStart(taskName)
|
||||
if err := handleAppRecover(&app, recoverTask, fmt.Sprintf("%s/%s.app.tar.gz", tmpPath, website.Alias), true, "", ""); err != nil {
|
||||
t.LogFailedWithErr(taskName, err)
|
||||
return err
|
||||
}
|
||||
t.LogSuccess(taskName)
|
||||
if _, err = compose.DownAndUp(fmt.Sprintf("%s/%s/%s/docker-compose.yml", global.Dir.AppInstallDir, app.App.Key, app.Name)); err != nil {
|
||||
t.LogFailedWithErr("Run", err)
|
||||
return err
|
||||
}
|
||||
case constant.Runtime:
|
||||
runtime, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(website.RuntimeID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
taskName := task.GetTaskName(runtime.Name, task.TaskRecover, task.TaskScopeRuntime)
|
||||
t.LogStart(taskName)
|
||||
if err := handleRuntimeRecover(runtime, fmt.Sprintf("%s/%s.runtime.tar.gz", tmpPath, website.Alias), true, ""); err != nil {
|
||||
t.LogFailedWithErr(taskName, err)
|
||||
return err
|
||||
}
|
||||
t.LogSuccess(taskName)
|
||||
if oldWebsite.DbID > 0 {
|
||||
if err := recoverWebsiteDatabase(t, oldWebsite.DbID, oldWebsite.DbType, tmpPath, website.Alias); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case constant.Static:
|
||||
if oldWebsite.DbID > 0 {
|
||||
if err := recoverWebsiteDatabase(t, oldWebsite.DbID, oldWebsite.DbType, tmpPath, website.Alias); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
taskName := i18n.GetMsgByKey("TaskRecover") + i18n.GetMsgByKey("websiteDir")
|
||||
t.Log(taskName)
|
||||
if err = fileOp.TarGzExtractPro(fmt.Sprintf("%s/%s.web.tar.gz", tmpPath, website.Alias), GetOpenrestyDir(SitesRootDir), ""); err != nil {
|
||||
t.LogFailedWithErr(taskName, err)
|
||||
return err
|
||||
}
|
||||
stdout, err := cmd.RunDefaultWithStdoutBashCf("docker exec -i %s nginx -s reload", nginxInfo.ContainerName)
|
||||
if err != nil {
|
||||
return errors.New(stdout)
|
||||
}
|
||||
oldWebsite.ID = website.ID
|
||||
if err := websiteRepo.SaveWithoutCtx(&oldWebsite); err != nil {
|
||||
return err
|
||||
}
|
||||
isOk = true
|
||||
return nil
|
||||
}, nil)
|
||||
return recoverTask.Execute()
|
||||
}
|
||||
|
||||
func handleWebsiteBackup(website *model.Website, parentTask *task.Task, backupDir, fileName, excludes, secret, taskID string) error {
|
||||
var (
|
||||
err error
|
||||
backupTask *task.Task
|
||||
)
|
||||
backupTask = parentTask
|
||||
if parentTask == nil {
|
||||
backupTask, err = task.NewTaskWithOps(website.Alias, task.TaskBackup, task.TaskScopeWebsite, taskID, website.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
itemHandler := func() error { return doWebsiteBackup(website, backupTask, backupDir, fileName, excludes, secret) }
|
||||
backupTask.AddSubTask(task.GetTaskName(website.Alias, task.TaskBackup, task.TaskScopeWebsite), func(t *task.Task) error { return itemHandler() }, nil)
|
||||
if parentTask != nil {
|
||||
return itemHandler()
|
||||
}
|
||||
return backupTask.Execute()
|
||||
}
|
||||
|
||||
func doWebsiteBackup(website *model.Website, parentTask *task.Task, backupDir, fileName, excludes, secret string) error {
|
||||
fileOp := files.NewFileOp()
|
||||
tmpDir := fmt.Sprintf("%s/%s", backupDir, strings.ReplaceAll(fileName, ".tar.gz", ""))
|
||||
if !fileOp.Stat(tmpDir) {
|
||||
if err := os.MkdirAll(tmpDir, os.ModePerm); err != nil {
|
||||
return fmt.Errorf("mkdir %s failed, err: %v", backupDir, err)
|
||||
}
|
||||
}
|
||||
defer func() {
|
||||
_ = os.RemoveAll(tmpDir)
|
||||
}()
|
||||
|
||||
remarkInfo, _ := json.Marshal(website)
|
||||
if err := fileOp.SaveFile(tmpDir+"/website.json", string(remarkInfo), fs.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
nginxConfFile := GetSitePath(*website, SiteConf)
|
||||
if err := fileOp.CopyFile(nginxConfFile, tmpDir); err != nil {
|
||||
return err
|
||||
}
|
||||
parentTask.Log(i18n.GetMsgByKey("BackupNginxConfig"))
|
||||
|
||||
switch website.Type {
|
||||
case constant.Deployment:
|
||||
app, err := appInstallRepo.GetFirst(repo.WithByID(website.AppInstallID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
parentTask.LogStart(task.GetTaskName(app.Name, task.TaskBackup, task.TaskScopeApp))
|
||||
if err = handleAppBackup(&app, parentTask, tmpDir, fmt.Sprintf("%s.app.tar.gz", website.Alias), excludes, "", ""); err != nil {
|
||||
return err
|
||||
}
|
||||
parentTask.LogSuccess(task.GetTaskName(app.Name, task.TaskBackup, task.TaskScopeApp))
|
||||
case constant.Runtime:
|
||||
runtime, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(website.RuntimeID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
parentTask.LogStart(task.GetTaskName(runtime.Name, task.TaskBackup, task.TaskScopeRuntime))
|
||||
if err = handleRuntimeBackup(runtime, tmpDir, fmt.Sprintf("%s.runtime.tar.gz", website.Alias), excludes, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
parentTask.LogSuccess(task.GetTaskName(runtime.Name, task.TaskBackup, task.TaskScopeRuntime))
|
||||
if website.DbID > 0 {
|
||||
if err = backupDatabaseWithTask(parentTask, website.DbType, tmpDir, website.Alias, website.DbID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case constant.Static:
|
||||
if website.DbID > 0 {
|
||||
if err := backupDatabaseWithTask(parentTask, website.DbType, tmpDir, website.Alias, website.DbID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
websiteDir := GetSitePath(*website, SiteDir)
|
||||
parentTask.LogStart(i18n.GetMsgByKey("CompressDir"))
|
||||
if err := fileOp.TarGzCompressPro(true, websiteDir, path.Join(tmpDir, fmt.Sprintf("%s.web.tar.gz", website.Alias)), "", excludes); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fileOp.TarGzCompressPro(true, tmpDir, path.Join(backupDir, fileName), secret, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
parentTask.Log(i18n.GetWithName("CompressFileSuccess", fileName))
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkValidOfWebsite(oldWebsite, website *model.Website) error {
|
||||
if oldWebsite.Alias != website.Alias || oldWebsite.Type != website.Type {
|
||||
return buserr.WithDetail("ErrBackupMatch", fmt.Sprintf("oldName: %s, oldType: %v", oldWebsite.Alias, oldWebsite.Type), nil)
|
||||
}
|
||||
if oldWebsite.AppInstallID != 0 {
|
||||
_, err := appInstallRepo.GetFirst(repo.WithByID(oldWebsite.AppInstallID))
|
||||
if err != nil {
|
||||
return buserr.WithDetail("ErrBackupMatch", "app", nil)
|
||||
}
|
||||
}
|
||||
if oldWebsite.RuntimeID != 0 {
|
||||
if _, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(oldWebsite.RuntimeID)); err != nil {
|
||||
return buserr.WithDetail("ErrBackupMatch", "runtime", nil)
|
||||
}
|
||||
}
|
||||
if oldWebsite.WebsiteSSLID != 0 {
|
||||
if _, err := websiteSSLRepo.GetFirst(repo.WithByID(oldWebsite.WebsiteSSLID)); err != nil {
|
||||
return buserr.WithDetail("ErrBackupMatch", "ssl", nil)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func recoverWebsiteDatabase(t *task.Task, dbID uint, dbType, tmpPath, websiteKey string) error {
|
||||
switch dbType {
|
||||
case constant.AppPostgresql:
|
||||
db, err := postgresqlRepo.Get(repo.WithByID(dbID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
taskName := task.GetTaskName(db.Name, task.TaskRecover, task.TaskScopeDatabase)
|
||||
t.LogStart(taskName)
|
||||
if err := handlePostgresqlRecover(dto.CommonRecover{
|
||||
Name: db.PostgresqlName,
|
||||
DetailName: db.Name,
|
||||
File: fmt.Sprintf("%s/%s.sql.gz", tmpPath, websiteKey),
|
||||
}, t, true); err != nil {
|
||||
t.LogFailedWithErr(taskName, err)
|
||||
return err
|
||||
}
|
||||
t.LogSuccess(taskName)
|
||||
case constant.AppMysql, constant.AppMariaDB:
|
||||
db, err := mysqlRepo.Get(repo.WithByID(dbID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
taskName := task.GetTaskName(db.Name, task.TaskRecover, task.TaskScopeDatabase)
|
||||
t.LogStart(taskName)
|
||||
if err := handleMysqlRecover(dto.CommonRecover{
|
||||
Name: db.MysqlName,
|
||||
DetailName: db.Name,
|
||||
File: fmt.Sprintf("%s/%s.sql.gz", tmpPath, websiteKey),
|
||||
}, t, true, ""); err != nil {
|
||||
t.LogFailedWithErr(taskName, err)
|
||||
return err
|
||||
}
|
||||
t.LogSuccess(taskName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,453 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/files"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (u *CronjobService) handleApp(cronjob model.Cronjob, startTime time.Time, taskItem *task.Task) error {
|
||||
var apps []model.AppInstall
|
||||
if cronjob.AppID == "all" {
|
||||
apps, _ = appInstallRepo.ListBy(context.Background())
|
||||
} else {
|
||||
appIds := strings.Split(cronjob.AppID, ",")
|
||||
var idItems []uint
|
||||
for i := 0; i < len(appIds); i++ {
|
||||
itemID, _ := strconv.Atoi(appIds[i])
|
||||
idItems = append(idItems, uint(itemID))
|
||||
}
|
||||
appItems, err := appInstallRepo.ListBy(context.Background(), repo.WithByIDs(idItems))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
apps = appItems
|
||||
}
|
||||
if len(apps) == 0 {
|
||||
return errors.New("no such app in database!")
|
||||
}
|
||||
accountMap, err := NewBackupClientMap(strings.Split(cronjob.SourceAccountIDs, ","))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, app := range apps {
|
||||
taskItem.AddSubTaskWithOps(task.GetTaskName(app.Name, task.TaskBackup, task.TaskScopeCronjob), func(task *task.Task) error {
|
||||
var record model.BackupRecord
|
||||
record.From = "cronjob"
|
||||
record.Type = "app"
|
||||
record.CronjobID = cronjob.ID
|
||||
record.Name = app.App.Key
|
||||
record.DetailName = app.Name
|
||||
record.DownloadAccountID, record.SourceAccountIDs = cronjob.DownloadAccountID, cronjob.SourceAccountIDs
|
||||
backupDir := path.Join(global.Dir.TmpDir, fmt.Sprintf("app/%s/%s", app.App.Key, app.Name))
|
||||
record.FileName = fmt.Sprintf("app_%s_%s.tar.gz", app.Name, startTime.Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(5))
|
||||
if err := doAppBackup(&app, task, backupDir, record.FileName, cronjob.ExclusionRules, cronjob.Secret); err != nil {
|
||||
return err
|
||||
}
|
||||
downloadPath, err := u.uploadCronjobBackFile(cronjob, accountMap, path.Join(backupDir, record.FileName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
record.FileDir = path.Dir(downloadPath)
|
||||
if err := backupRepo.CreateRecord(&record); err != nil {
|
||||
global.LOG.Errorf("save backup record failed, err: %v", err)
|
||||
return err
|
||||
}
|
||||
u.removeExpiredBackup(cronjob, accountMap, record)
|
||||
return nil
|
||||
}, nil, int(cronjob.RetryTimes), time.Duration(cronjob.Timeout)*time.Second)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *CronjobService) handleWebsite(cronjob model.Cronjob, startTime time.Time, taskItem *task.Task) error {
|
||||
webs := loadWebsForJob(cronjob)
|
||||
if len(webs) == 0 {
|
||||
return errors.New("no such website in database!")
|
||||
}
|
||||
accountMap, err := NewBackupClientMap(strings.Split(cronjob.SourceAccountIDs, ","))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, web := range webs {
|
||||
taskItem.AddSubTaskWithOps(task.GetTaskName(web.Alias, task.TaskBackup, task.TaskScopeCronjob), func(task *task.Task) error {
|
||||
var record model.BackupRecord
|
||||
record.From = "cronjob"
|
||||
record.Type = "website"
|
||||
record.CronjobID = cronjob.ID
|
||||
record.Name = web.Alias
|
||||
record.DetailName = web.Alias
|
||||
record.DownloadAccountID, record.SourceAccountIDs = cronjob.DownloadAccountID, cronjob.SourceAccountIDs
|
||||
backupDir := path.Join(global.Dir.TmpDir, fmt.Sprintf("website/%s", web.Alias))
|
||||
record.FileName = fmt.Sprintf("website_%s_%s.tar.gz", web.Alias, startTime.Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(5))
|
||||
|
||||
if err := doWebsiteBackup(&web, taskItem, backupDir, record.FileName, cronjob.ExclusionRules, cronjob.Secret); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
downloadPath, err := u.uploadCronjobBackFile(cronjob, accountMap, path.Join(backupDir, record.FileName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
record.FileDir = path.Dir(downloadPath)
|
||||
if err := backupRepo.CreateRecord(&record); err != nil {
|
||||
global.LOG.Errorf("save backup record failed, err: %v", err)
|
||||
return err
|
||||
}
|
||||
u.removeExpiredBackup(cronjob, accountMap, record)
|
||||
return nil
|
||||
}, nil, int(cronjob.RetryTimes), time.Duration(cronjob.Timeout)*time.Second)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *CronjobService) handleDatabase(cronjob model.Cronjob, startTime time.Time, taskItem *task.Task) error {
|
||||
dbs := loadDbsForJob(cronjob)
|
||||
if len(dbs) == 0 {
|
||||
return errors.New("no such db in database!")
|
||||
}
|
||||
accountMap, err := NewBackupClientMap(strings.Split(cronjob.SourceAccountIDs, ","))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, dbInfo := range dbs {
|
||||
itemName := fmt.Sprintf("%s[%s] - %s", dbInfo.Database, dbInfo.DBType, dbInfo.Name)
|
||||
taskItem.AddSubTaskWithOps(task.GetTaskName(itemName, task.TaskBackup, task.TaskScopeCronjob), func(task *task.Task) error {
|
||||
var record model.BackupRecord
|
||||
record.From = "cronjob"
|
||||
record.Type = dbInfo.DBType
|
||||
record.CronjobID = cronjob.ID
|
||||
record.Name = dbInfo.Database
|
||||
record.DetailName = dbInfo.Name
|
||||
record.DownloadAccountID, record.SourceAccountIDs = cronjob.DownloadAccountID, cronjob.SourceAccountIDs
|
||||
|
||||
backupDir := path.Join(global.Dir.TmpDir, fmt.Sprintf("database/%s/%s/%s", dbInfo.DBType, record.Name, dbInfo.Name))
|
||||
record.FileName = fmt.Sprintf("db_%s_%s.sql.gz", dbInfo.Name, startTime.Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(5))
|
||||
if cronjob.DBType == "mysql" || cronjob.DBType == "mariadb" {
|
||||
if err := doMysqlBackup(dbInfo, backupDir, record.FileName); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := doPostgresqlgBackup(dbInfo, backupDir, record.FileName); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
downloadPath, err := u.uploadCronjobBackFile(cronjob, accountMap, path.Join(backupDir, record.FileName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
record.FileDir = path.Dir(downloadPath)
|
||||
if err := backupRepo.CreateRecord(&record); err != nil {
|
||||
global.LOG.Errorf("save backup record failed, err: %v", err)
|
||||
return err
|
||||
}
|
||||
u.removeExpiredBackup(cronjob, accountMap, record)
|
||||
return nil
|
||||
}, nil, int(cronjob.RetryTimes), time.Duration(cronjob.Timeout)*time.Second)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *CronjobService) handleDirectory(cronjob model.Cronjob, startTime time.Time, taskItem *task.Task) error {
|
||||
taskItem.AddSubTaskWithOps(task.GetTaskName(cronjob.SourceDir, task.TaskBackup, task.TaskScopeCronjob), func(task *task.Task) error {
|
||||
accountMap, err := NewBackupClientMap(strings.Split(cronjob.SourceAccountIDs, ","))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fileName := fmt.Sprintf("%s.tar.gz", startTime.Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(5))
|
||||
backupDir := path.Join(global.Dir.TmpDir, fmt.Sprintf("%s/%s", cronjob.Type, cronjob.Name))
|
||||
|
||||
fileOp := files.NewFileOp()
|
||||
if cronjob.IsDir {
|
||||
taskItem.Logf("Dir: %s, Excludes: %s", cronjob.SourceDir, cronjob.ExclusionRules)
|
||||
if err := fileOp.TarGzCompressPro(true, cronjob.SourceDir, path.Join(backupDir, fileName), cronjob.Secret, cronjob.ExclusionRules); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
taskItem.Logf("Files: %s", cronjob.SourceDir)
|
||||
fileLists := strings.Split(cronjob.SourceDir, ",")
|
||||
if err := fileOp.TarGzFilesWithCompressPro(fileLists, path.Join(backupDir, fileName), cronjob.Secret); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
var record model.BackupRecord
|
||||
record.From = "cronjob"
|
||||
record.Type = "directory"
|
||||
record.CronjobID = cronjob.ID
|
||||
record.Name = cronjob.Name
|
||||
record.DownloadAccountID, record.SourceAccountIDs = cronjob.DownloadAccountID, cronjob.SourceAccountIDs
|
||||
downloadPath, err := u.uploadCronjobBackFile(cronjob, accountMap, path.Join(backupDir, fileName))
|
||||
if err != nil {
|
||||
taskItem.LogFailedWithErr("Upload backup file", err)
|
||||
return err
|
||||
}
|
||||
record.FileDir = path.Dir(downloadPath)
|
||||
record.FileName = fileName
|
||||
if err := backupRepo.CreateRecord(&record); err != nil {
|
||||
taskItem.LogFailedWithErr("Save record", err)
|
||||
return err
|
||||
}
|
||||
u.removeExpiredBackup(cronjob, accountMap, record)
|
||||
return nil
|
||||
}, nil, int(cronjob.RetryTimes), time.Duration(cronjob.Timeout)*time.Second)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *CronjobService) handleSystemLog(cronjob model.Cronjob, startTime time.Time, taskItem *task.Task) error {
|
||||
taskItem.AddSubTaskWithOps(task.GetTaskName(i18n.GetMsgByKey("BackupSystemLog"), task.TaskBackup, task.TaskScopeCronjob), func(task *task.Task) error {
|
||||
accountMap, err := NewBackupClientMap(strings.Split(cronjob.SourceAccountIDs, ","))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nameItem := startTime.Format(constant.DateTimeSlimLayout) + common.RandStrAndNum(5)
|
||||
fileName := fmt.Sprintf("system_log_%s.tar.gz", nameItem)
|
||||
backupDir := path.Join(global.Dir.TmpDir, "log", nameItem)
|
||||
if err := handleBackupLogs(taskItem, backupDir, fileName, cronjob.Secret); err != nil {
|
||||
return err
|
||||
}
|
||||
var record model.BackupRecord
|
||||
record.From = "cronjob"
|
||||
record.Type = "log"
|
||||
record.CronjobID = cronjob.ID
|
||||
record.Name = cronjob.Name
|
||||
record.DownloadAccountID, record.SourceAccountIDs = cronjob.DownloadAccountID, cronjob.SourceAccountIDs
|
||||
downloadPath, err := u.uploadCronjobBackFile(cronjob, accountMap, path.Join(path.Dir(backupDir), fileName))
|
||||
if err != nil {
|
||||
taskItem.LogFailedWithErr("Upload backup file", err)
|
||||
return err
|
||||
}
|
||||
record.FileDir = path.Dir(downloadPath)
|
||||
record.FileName = fileName
|
||||
if err := backupRepo.CreateRecord(&record); err != nil {
|
||||
taskItem.LogFailedWithErr("Save record", err)
|
||||
return err
|
||||
}
|
||||
u.removeExpiredBackup(cronjob, accountMap, record)
|
||||
return nil
|
||||
}, nil, int(cronjob.RetryTimes), time.Duration(cronjob.Timeout)*time.Second)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *CronjobService) handleSnapshot(cronjob model.Cronjob, jobRecord model.JobRecords, taskItem *task.Task) error {
|
||||
accountMap, err := NewBackupClientMap(strings.Split(cronjob.SourceAccountIDs, ","))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
itemData, err := NewISnapshotService().LoadSnapshotData()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var record model.BackupRecord
|
||||
record.From = "cronjob"
|
||||
record.Type = "snapshot"
|
||||
record.CronjobID = cronjob.ID
|
||||
record.Name = cronjob.Name
|
||||
record.DownloadAccountID, record.SourceAccountIDs = cronjob.DownloadAccountID, cronjob.SourceAccountIDs
|
||||
record.FileDir = "system_snapshot"
|
||||
|
||||
versionItem, _ := settingRepo.Get(settingRepo.WithByKey("SystemVersion"))
|
||||
scope := "core"
|
||||
if !global.IsMaster {
|
||||
scope = "agent"
|
||||
}
|
||||
req := dto.SnapshotCreate{
|
||||
Name: fmt.Sprintf("snapshot-1panel-%s-%s-linux-%s-%s", scope, versionItem.Value, loadOs(), jobRecord.StartTime.Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(5)),
|
||||
Secret: cronjob.Secret,
|
||||
TaskID: jobRecord.TaskID,
|
||||
|
||||
SourceAccountIDs: record.SourceAccountIDs,
|
||||
DownloadAccountID: cronjob.DownloadAccountID,
|
||||
AppData: itemData.AppData,
|
||||
PanelData: itemData.PanelData,
|
||||
BackupData: itemData.BackupData,
|
||||
WithMonitorData: true,
|
||||
WithLoginLog: true,
|
||||
WithOperationLog: true,
|
||||
WithSystemLog: true,
|
||||
WithTaskLog: true,
|
||||
}
|
||||
|
||||
if err := NewISnapshotService().SnapshotCreate(taskItem, req, jobRecord.ID, cronjob.RetryTimes, cronjob.Timeout); err != nil {
|
||||
return err
|
||||
}
|
||||
record.FileName = req.Name + ".tar.gz"
|
||||
|
||||
if err := backupRepo.CreateRecord(&record); err != nil {
|
||||
global.LOG.Errorf("save backup record failed, err: %v", err)
|
||||
return err
|
||||
}
|
||||
u.removeExpiredBackup(cronjob, accountMap, record)
|
||||
return nil
|
||||
}
|
||||
|
||||
type DatabaseHelper struct {
|
||||
ID uint
|
||||
DBType string
|
||||
Database string
|
||||
Name string
|
||||
}
|
||||
|
||||
func loadDbsForJob(cronjob model.Cronjob) []DatabaseHelper {
|
||||
var dbs []DatabaseHelper
|
||||
if cronjob.DBName == "all" {
|
||||
if cronjob.DBType == "mysql" || cronjob.DBType == "mariadb" {
|
||||
databaseService := NewIDatabaseService()
|
||||
mysqlItems, _ := databaseService.LoadItems(cronjob.DBType)
|
||||
for _, mysql := range mysqlItems {
|
||||
dbs = append(dbs, DatabaseHelper{
|
||||
ID: mysql.ID,
|
||||
DBType: cronjob.DBType,
|
||||
Database: mysql.Database,
|
||||
Name: mysql.Name,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
pgItems, _ := postgresqlRepo.List()
|
||||
for _, pg := range pgItems {
|
||||
dbs = append(dbs, DatabaseHelper{
|
||||
ID: pg.ID,
|
||||
DBType: cronjob.DBType,
|
||||
Database: pg.PostgresqlName,
|
||||
Name: pg.Name,
|
||||
})
|
||||
}
|
||||
}
|
||||
return dbs
|
||||
}
|
||||
dbNames := strings.Split(cronjob.DBName, ",")
|
||||
for _, name := range dbNames {
|
||||
itemID, _ := strconv.Atoi(name)
|
||||
if cronjob.DBType == "mysql" || cronjob.DBType == "mariadb" {
|
||||
mysqlItem, _ := mysqlRepo.Get(repo.WithByID(uint(itemID)))
|
||||
dbs = append(dbs, DatabaseHelper{
|
||||
ID: mysqlItem.ID,
|
||||
DBType: cronjob.DBType,
|
||||
Database: mysqlItem.MysqlName,
|
||||
Name: mysqlItem.Name,
|
||||
})
|
||||
} else {
|
||||
pgItem, _ := postgresqlRepo.Get(repo.WithByID(uint(itemID)))
|
||||
dbs = append(dbs, DatabaseHelper{
|
||||
ID: pgItem.ID,
|
||||
DBType: cronjob.DBType,
|
||||
Database: pgItem.PostgresqlName,
|
||||
Name: pgItem.Name,
|
||||
})
|
||||
}
|
||||
}
|
||||
return dbs
|
||||
}
|
||||
|
||||
func loadWebsForJob(cronjob model.Cronjob) []model.Website {
|
||||
var weblist []model.Website
|
||||
if cronjob.Website == "all" {
|
||||
weblist, _ = websiteRepo.List()
|
||||
return weblist
|
||||
}
|
||||
websites := strings.Split(cronjob.Website, ",")
|
||||
var idItems []uint
|
||||
for i := 0; i < len(websites); i++ {
|
||||
itemID, _ := strconv.Atoi(websites[i])
|
||||
idItems = append(idItems, uint(itemID))
|
||||
}
|
||||
weblist, _ = websiteRepo.GetBy(repo.WithByIDs(idItems))
|
||||
return weblist
|
||||
}
|
||||
|
||||
func handleBackupLogs(taskItem *task.Task, targetDir, fileName string, secret string) error {
|
||||
fileOp := files.NewFileOp()
|
||||
websites, err := websiteRepo.List()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(websites) != 0 {
|
||||
webItem := GetOpenrestyDir(SitesRootDir)
|
||||
for _, website := range websites {
|
||||
taskItem.Logf("%s Website logs %s...", i18n.GetMsgByKey("TaskBackup"), website.Alias)
|
||||
dirItem := path.Join(targetDir, "website", website.Alias)
|
||||
if _, err := os.Stat(dirItem); err != nil && os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(dirItem, os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
itemDir := path.Join(webItem, website.Alias, "log")
|
||||
logFiles, _ := os.ReadDir(itemDir)
|
||||
if len(logFiles) != 0 {
|
||||
for i := 0; i < len(logFiles); i++ {
|
||||
if !logFiles[i].IsDir() {
|
||||
_ = fileOp.CopyFile(path.Join(itemDir, logFiles[i].Name()), dirItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
itemDir2 := path.Join(global.Dir.LocalBackupDir, "log/website", website.Alias)
|
||||
logFiles2, _ := os.ReadDir(itemDir2)
|
||||
if len(logFiles2) != 0 {
|
||||
for i := 0; i < len(logFiles2); i++ {
|
||||
if !logFiles2[i].IsDir() {
|
||||
_ = fileOp.CopyFile(path.Join(itemDir2, logFiles2[i].Name()), dirItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
systemDir := path.Join(targetDir, "system")
|
||||
if _, err := os.Stat(systemDir); err != nil && os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(systemDir, os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
taskItem.Logf("%s System logs...", i18n.GetMsgByKey("TaskBackup"))
|
||||
systemLogFiles, _ := os.ReadDir(global.Dir.LogDir)
|
||||
if len(systemLogFiles) != 0 {
|
||||
for i := 0; i < len(systemLogFiles); i++ {
|
||||
if !systemLogFiles[i].IsDir() {
|
||||
_ = fileOp.CopyFile(path.Join(global.Dir.LogDir, systemLogFiles[i].Name()), systemDir)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
taskItem.Logf("%s SSH logs...", i18n.GetMsgByKey("TaskBackup"))
|
||||
loginLogFiles, _ := os.ReadDir("/var/log")
|
||||
loginDir := path.Join(targetDir, "login")
|
||||
if _, err := os.Stat(loginDir); err != nil && os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(loginDir, os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(loginLogFiles) != 0 {
|
||||
for i := 0; i < len(loginLogFiles); i++ {
|
||||
if !loginLogFiles[i].IsDir() && (strings.HasPrefix(loginLogFiles[i].Name(), "secure") || strings.HasPrefix(loginLogFiles[i].Name(), "auth.log")) {
|
||||
_ = fileOp.CopyFile(path.Join("/var/log", loginLogFiles[i].Name()), loginDir)
|
||||
}
|
||||
}
|
||||
}
|
||||
taskItem.Log("backup ssh log successful!")
|
||||
|
||||
if err := fileOp.TarGzCompressPro(true, targetDir, path.Join(path.Dir(targetDir), fileName), secret, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = os.RemoveAll(targetDir)
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
@@ -1,348 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
pathUtils "path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/files"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/ntp"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/xpack"
|
||||
)
|
||||
|
||||
func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
|
||||
record := cronjobRepo.StartRecords(cronjob.ID, "", cronjob.Type)
|
||||
go func() {
|
||||
taskItem, err := task.NewTaskWithOps(fmt.Sprintf("cronjob-%s", cronjob.Name), task.TaskHandle, task.TaskScopeCronjob, record.TaskID, cronjob.ID)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("new task for exec shell failed, err: %v", err)
|
||||
return
|
||||
}
|
||||
err = u.loadTask(cronjob, &record, taskItem)
|
||||
if cronjob.Type == "snapshot" {
|
||||
if err != nil {
|
||||
taskItem, _ := taskRepo.GetFirst(taskRepo.WithByID(record.TaskID))
|
||||
if len(taskItem.ID) == 0 {
|
||||
record.TaskID = ""
|
||||
}
|
||||
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), record.Records)
|
||||
handleCronJobAlert(cronjob)
|
||||
return
|
||||
}
|
||||
cronjobRepo.EndRecords(record, constant.StatusSuccess, "", record.Records)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
global.LOG.Debugf("preper to handle cron job [%s] %s failed, err: %v", cronjob.Type, cronjob.Name, err)
|
||||
record.TaskID = ""
|
||||
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), record.Records)
|
||||
return
|
||||
}
|
||||
if err := taskItem.Execute(); err != nil {
|
||||
taskItem, _ := taskRepo.GetFirst(taskRepo.WithByID(record.TaskID))
|
||||
if len(taskItem.ID) == 0 {
|
||||
record.TaskID = ""
|
||||
}
|
||||
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), record.Records)
|
||||
handleCronJobAlert(cronjob)
|
||||
} else {
|
||||
cronjobRepo.EndRecords(record, constant.StatusSuccess, "", record.Records)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (u *CronjobService) loadTask(cronjob *model.Cronjob, record *model.JobRecords, taskItem *task.Task) error {
|
||||
var err error
|
||||
switch cronjob.Type {
|
||||
case "shell":
|
||||
if cronjob.ScriptMode == "library" {
|
||||
scriptItem, _ := scriptRepo.Get(repo.WithByID(cronjob.ScriptID))
|
||||
if scriptItem.ID == 0 {
|
||||
return fmt.Errorf("load script from db failed, err: %v", err)
|
||||
}
|
||||
cronjob.Script = scriptItem.Script
|
||||
cronjob.ScriptMode = "input"
|
||||
}
|
||||
if len(cronjob.Script) == 0 {
|
||||
return fmt.Errorf("the script content is empty and is skipped")
|
||||
}
|
||||
u.handleShell(*cronjob, taskItem)
|
||||
u.removeExpiredLog(*cronjob)
|
||||
case "curl":
|
||||
if len(cronjob.URL) == 0 {
|
||||
return fmt.Errorf("the url is empty and is skipped")
|
||||
}
|
||||
u.handleCurl(*cronjob, taskItem)
|
||||
u.removeExpiredLog(*cronjob)
|
||||
case "ntp":
|
||||
u.handleNtpSync(*cronjob, taskItem)
|
||||
u.removeExpiredLog(*cronjob)
|
||||
case "cutWebsiteLog":
|
||||
err = u.handleCutWebsiteLog(cronjob, record.StartTime, taskItem)
|
||||
case "clean":
|
||||
u.handleSystemClean(*cronjob, taskItem)
|
||||
u.removeExpiredLog(*cronjob)
|
||||
case "website":
|
||||
err = u.handleWebsite(*cronjob, record.StartTime, taskItem)
|
||||
case "app":
|
||||
err = u.handleApp(*cronjob, record.StartTime, taskItem)
|
||||
case "database":
|
||||
err = u.handleDatabase(*cronjob, record.StartTime, taskItem)
|
||||
case "directory":
|
||||
if len(cronjob.SourceDir) == 0 {
|
||||
return fmt.Errorf("the source dir is empty and is skipped")
|
||||
}
|
||||
err = u.handleDirectory(*cronjob, record.StartTime, taskItem)
|
||||
case "log":
|
||||
err = u.handleSystemLog(*cronjob, record.StartTime, taskItem)
|
||||
case "snapshot":
|
||||
_ = cronjobRepo.UpdateRecords(record.ID, map[string]interface{}{"records": record.Records})
|
||||
err = u.handleSnapshot(*cronjob, *record, taskItem)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (u *CronjobService) handleShell(cronjob model.Cronjob, taskItem *task.Task) {
|
||||
cmdMgr := cmd.NewCommandMgr(cmd.WithTask(*taskItem))
|
||||
taskItem.AddSubTaskWithOps(i18n.GetWithName("HandleShell", cronjob.Name), func(t *task.Task) error {
|
||||
if len(cronjob.ContainerName) != 0 {
|
||||
command := "sh"
|
||||
if len(cronjob.Command) != 0 {
|
||||
command = cronjob.Command
|
||||
}
|
||||
return cmdMgr.Run("docker", "exec", cronjob.ContainerName, command, "-c", strings.ReplaceAll(cronjob.Script, "\"", "\\\""))
|
||||
}
|
||||
if len(cronjob.Executor) == 0 {
|
||||
cronjob.Executor = "bash"
|
||||
}
|
||||
if cronjob.ScriptMode == "input" {
|
||||
fileItem := pathUtils.Join(global.Dir.DataDir, "task", "shell", cronjob.Name, cronjob.Name+".sh")
|
||||
_ = os.MkdirAll(pathUtils.Dir(fileItem), os.ModePerm)
|
||||
shellFile, err := os.OpenFile(fileItem, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, constant.FilePerm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer shellFile.Close()
|
||||
if _, err := shellFile.WriteString(cronjob.Script); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(cronjob.User) == 0 {
|
||||
return cmdMgr.Run(cronjob.Executor, fileItem)
|
||||
}
|
||||
return cmdMgr.Run("sudo", "-u", cronjob.User, cronjob.Executor, fileItem)
|
||||
}
|
||||
if len(cronjob.User) == 0 {
|
||||
return cmdMgr.Run(cronjob.Executor, cronjob.Script)
|
||||
}
|
||||
if err := cmdMgr.Run("sudo", "-u", cronjob.User, cronjob.Executor, cronjob.Script); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}, nil, int(cronjob.RetryTimes), time.Duration(cronjob.Timeout)*time.Second)
|
||||
}
|
||||
|
||||
func (u *CronjobService) handleCurl(cronjob model.Cronjob, taskItem *task.Task) {
|
||||
taskItem.AddSubTaskWithOps(i18n.GetWithName("HandleShell", cronjob.Name), func(t *task.Task) error {
|
||||
cmdMgr := cmd.NewCommandMgr(cmd.WithTask(*taskItem))
|
||||
return cmdMgr.Run("curl", cronjob.URL)
|
||||
}, nil, int(cronjob.RetryTimes), time.Duration(cronjob.Timeout)*time.Second)
|
||||
}
|
||||
|
||||
func (u *CronjobService) handleNtpSync(cronjob model.Cronjob, taskItem *task.Task) {
|
||||
taskItem.AddSubTaskWithOps(i18n.GetMsgByKey("HandleNtpSync"), func(t *task.Task) error {
|
||||
ntpServer, err := settingRepo.Get(settingRepo.WithByKey("NtpSite"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
taskItem.Logf("ntp server: %s", ntpServer.Value)
|
||||
ntime, err := ntp.GetRemoteTime(ntpServer.Value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ntp.UpdateSystemTime(ntime.Format(constant.DateTimeLayout)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}, nil, int(cronjob.RetryTimes), time.Duration(cronjob.Timeout)*time.Second)
|
||||
}
|
||||
|
||||
func (u *CronjobService) handleCutWebsiteLog(cronjob *model.Cronjob, startTime time.Time, taskItem *task.Task) error {
|
||||
taskItem.AddSubTaskWithOps(i18n.GetWithName("CutWebsiteLog", cronjob.Name), func(t *task.Task) error {
|
||||
websites := loadWebsForJob(*cronjob)
|
||||
fileOp := files.NewFileOp()
|
||||
baseDir := GetOpenrestyDir(SitesRootDir)
|
||||
clientMap, err := NewBackupClientMap([]string{fmt.Sprintf("%v", cronjob.DownloadAccountID)})
|
||||
if err != nil {
|
||||
return fmt.Errorf("load local backup client failed, err: %v", err)
|
||||
}
|
||||
for _, website := range websites {
|
||||
taskItem.Log(website.Alias)
|
||||
var record model.BackupRecord
|
||||
record.From = "cronjob"
|
||||
record.Type = "cut-website-log"
|
||||
record.CronjobID = cronjob.ID
|
||||
record.Name = website.Alias
|
||||
record.DetailName = website.Alias
|
||||
record.DownloadAccountID, record.SourceAccountIDs = cronjob.DownloadAccountID, cronjob.SourceAccountIDs
|
||||
backupDir := pathUtils.Join(global.Dir.LocalBackupDir, "log", "website", website.Alias)
|
||||
if !fileOp.Stat(backupDir) {
|
||||
_ = os.MkdirAll(backupDir, constant.DirPerm)
|
||||
}
|
||||
record.FileDir = strings.TrimPrefix(backupDir, global.Dir.LocalBackupDir+"/")
|
||||
record.FileName = fmt.Sprintf("%s_log_%s.gz", website.PrimaryDomain, startTime.Format(constant.DateTimeSlimLayout))
|
||||
if err := backupRepo.CreateRecord(&record); err != nil {
|
||||
global.LOG.Errorf("save backup record failed, err: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
websiteLogDir := pathUtils.Join(baseDir, website.Alias, "log")
|
||||
srcAccessLogPath := pathUtils.Join(websiteLogDir, "access.log")
|
||||
srcErrorLogPath := pathUtils.Join(websiteLogDir, "error.log")
|
||||
|
||||
dstFilePath := pathUtils.Join(backupDir, record.FileName)
|
||||
if err := backupLogFile(dstFilePath, websiteLogDir, fileOp); err != nil {
|
||||
taskItem.LogFailedWithErr("CutWebsiteLog", err)
|
||||
continue
|
||||
} else {
|
||||
_ = fileOp.WriteFile(srcAccessLogPath, strings.NewReader(""), constant.DirPerm)
|
||||
_ = fileOp.WriteFile(srcErrorLogPath, strings.NewReader(""), constant.DirPerm)
|
||||
}
|
||||
taskItem.Log(i18n.GetMsgWithMap("CutWebsiteLogSuccess", map[string]interface{}{"name": website.PrimaryDomain, "path": dstFilePath}))
|
||||
u.removeExpiredBackup(*cronjob, clientMap, record)
|
||||
}
|
||||
return nil
|
||||
}, nil, int(cronjob.RetryTimes), time.Duration(cronjob.Timeout)*time.Second)
|
||||
return nil
|
||||
}
|
||||
|
||||
func backupLogFile(dstFilePath, websiteLogDir string, fileOp files.FileOp) error {
|
||||
cmdMgr := cmd.NewCommandMgr()
|
||||
if err := cmdMgr.RunBashCf("tar -czf %s -C %s %s", dstFilePath, websiteLogDir, strings.Join([]string{"access.log", "error.log"}, " ")); err != nil {
|
||||
dstDir := pathUtils.Dir(dstFilePath)
|
||||
if err = fileOp.Copy(pathUtils.Join(websiteLogDir, "access.log"), dstDir); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = fileOp.Copy(pathUtils.Join(websiteLogDir, "error.log"), dstDir); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = cmdMgr.RunBashCf("tar -czf %s -C %s %s", dstFilePath, dstDir, strings.Join([]string{"access.log", "error.log"}, " ")); err != nil {
|
||||
return err
|
||||
}
|
||||
_ = fileOp.DeleteFile(pathUtils.Join(dstDir, "access.log"))
|
||||
_ = fileOp.DeleteFile(pathUtils.Join(dstDir, "error.log"))
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *CronjobService) handleSystemClean(cronjob model.Cronjob, taskItem *task.Task) {
|
||||
cleanTask := doSystemClean(taskItem)
|
||||
taskItem.AddSubTaskWithOps(i18n.GetMsgByKey("HandleSystemClean"), cleanTask, nil, int(cronjob.RetryTimes), time.Duration(cronjob.Timeout)*time.Second)
|
||||
}
|
||||
|
||||
func (u *CronjobService) uploadCronjobBackFile(cronjob model.Cronjob, accountMap map[string]backupClientHelper, file string) (string, error) {
|
||||
defer func() {
|
||||
_ = os.Remove(file)
|
||||
}()
|
||||
accounts := strings.Split(cronjob.SourceAccountIDs, ",")
|
||||
cloudSrc := strings.TrimPrefix(file, global.Dir.TmpDir+"/")
|
||||
for _, account := range accounts {
|
||||
if len(account) != 0 {
|
||||
global.LOG.Debugf("start upload file to %s, dir: %s", accountMap[account].name, pathUtils.Join(accountMap[account].backupPath, cloudSrc))
|
||||
if _, err := accountMap[account].client.Upload(file, pathUtils.Join(accountMap[account].backupPath, cloudSrc)); err != nil {
|
||||
global.LOG.Errorf("upload file to %s failed, err: %v", accountMap[account].name, err)
|
||||
continue
|
||||
}
|
||||
global.LOG.Debugf("upload successful!")
|
||||
}
|
||||
}
|
||||
return cloudSrc, nil
|
||||
}
|
||||
|
||||
func (u *CronjobService) removeExpiredBackup(cronjob model.Cronjob, accountMap map[string]backupClientHelper, record model.BackupRecord) {
|
||||
var opts []repo.DBOption
|
||||
opts = append(opts, repo.WithByFrom("cronjob"))
|
||||
opts = append(opts, backupRepo.WithByCronID(cronjob.ID))
|
||||
opts = append(opts, repo.WithOrderBy("created_at desc"))
|
||||
if record.ID != 0 {
|
||||
opts = append(opts, repo.WithByType(record.Type))
|
||||
opts = append(opts, repo.WithByName(record.Name))
|
||||
opts = append(opts, repo.WithByDetailName(record.DetailName))
|
||||
}
|
||||
records, _ := backupRepo.ListRecord(opts...)
|
||||
if len(records) <= int(cronjob.RetainCopies) {
|
||||
return
|
||||
}
|
||||
for i := int(cronjob.RetainCopies); i < len(records); i++ {
|
||||
accounts := strings.Split(cronjob.SourceAccountIDs, ",")
|
||||
if cronjob.Type == "snapshot" {
|
||||
for _, account := range accounts {
|
||||
if len(account) != 0 {
|
||||
if _, ok := accountMap[account]; !ok {
|
||||
continue
|
||||
}
|
||||
_, _ = accountMap[account].client.Delete(pathUtils.Join(accountMap[account].backupPath, "system_snapshot", records[i].FileName))
|
||||
}
|
||||
}
|
||||
_ = snapshotRepo.Delete(repo.WithByName(strings.TrimSuffix(records[i].FileName, ".tar.gz")))
|
||||
} else {
|
||||
for _, account := range accounts {
|
||||
if len(account) != 0 {
|
||||
if _, ok := accountMap[account]; !ok {
|
||||
continue
|
||||
}
|
||||
_, _ = accountMap[account].client.Delete(pathUtils.Join(accountMap[account].backupPath, records[i].FileDir, records[i].FileName))
|
||||
}
|
||||
}
|
||||
}
|
||||
_ = backupRepo.DeleteRecord(context.Background(), repo.WithByID(records[i].ID))
|
||||
}
|
||||
}
|
||||
|
||||
func (u *CronjobService) removeExpiredLog(cronjob model.Cronjob) {
|
||||
records, _ := cronjobRepo.ListRecord(cronjobRepo.WithByJobID(int(cronjob.ID)), repo.WithOrderBy("created_at desc"))
|
||||
if len(records) <= int(cronjob.RetainCopies) {
|
||||
return
|
||||
}
|
||||
for i := int(cronjob.RetainCopies); i < len(records); i++ {
|
||||
if len(records[i].File) != 0 {
|
||||
files := strings.Split(records[i].File, ",")
|
||||
for _, file := range files {
|
||||
_ = os.Remove(file)
|
||||
}
|
||||
}
|
||||
_ = cronjobRepo.DeleteRecord(repo.WithByID(uint(records[i].ID)))
|
||||
_ = taskRepo.Delete(taskRepo.WithByID(records[i].TaskID))
|
||||
_ = os.Remove(path.Join(global.CONF.Base.InstallDir, "1panel/log/task/Cronjob", records[i].TaskID+".log"))
|
||||
}
|
||||
}
|
||||
|
||||
func hasBackup(cronjobType string) bool {
|
||||
return cronjobType == "app" || cronjobType == "database" || cronjobType == "website" || cronjobType == "directory" || cronjobType == "snapshot" || cronjobType == "log" || cronjobType == "cutWebsiteLog"
|
||||
}
|
||||
|
||||
func handleCronJobAlert(cronjob *model.Cronjob) {
|
||||
pushAlert := dto.PushAlert{
|
||||
TaskName: cronjob.Name,
|
||||
AlertType: cronjob.Type,
|
||||
EntryID: cronjob.ID,
|
||||
Param: cronjob.Type,
|
||||
}
|
||||
err := xpack.PushAlert(pushAlert)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("cronjob alert push failed, err: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -1,561 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
network "net"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/xpu"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/copier"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/shirou/gopsutil/v3/cpu"
|
||||
"github.com/shirou/gopsutil/v3/disk"
|
||||
"github.com/shirou/gopsutil/v3/host"
|
||||
"github.com/shirou/gopsutil/v3/load"
|
||||
"github.com/shirou/gopsutil/v3/mem"
|
||||
"github.com/shirou/gopsutil/v3/net"
|
||||
)
|
||||
|
||||
type DashboardService struct{}
|
||||
|
||||
type IDashboardService interface {
|
||||
LoadOsInfo() (*dto.OsInfo, error)
|
||||
LoadBaseInfo(ioOption string, netOption string) (*dto.DashboardBase, error)
|
||||
LoadCurrentInfoForNode() *dto.NodeCurrent
|
||||
LoadCurrentInfo(ioOption string, netOption string) *dto.DashboardCurrent
|
||||
|
||||
LoadAppLauncher(ctx *gin.Context) ([]dto.AppLauncher, error)
|
||||
ChangeShow(req dto.SettingUpdate) error
|
||||
ListLauncherOption(filter string) ([]dto.LauncherOption, error)
|
||||
Restart(operation string) error
|
||||
}
|
||||
|
||||
func NewIDashboardService() IDashboardService {
|
||||
return &DashboardService{}
|
||||
}
|
||||
|
||||
func (u *DashboardService) Restart(operation string) error {
|
||||
if operation != "1panel" && operation != "system" && operation != "1panel-agent" {
|
||||
return fmt.Errorf("handle restart operation %s failed, err: nonsupport such operation", operation)
|
||||
}
|
||||
itemCmd := fmt.Sprintf("%s systemctl restart 1panel-agent.service && %s systemctl restart 1panel-core.service", cmd.SudoHandleCmd(), cmd.SudoHandleCmd())
|
||||
if operation == "system" {
|
||||
itemCmd = fmt.Sprintf("%s reboot", cmd.SudoHandleCmd())
|
||||
}
|
||||
if operation == "1panel-agent" {
|
||||
itemCmd = fmt.Sprintf("%s systemctl restart 1panel-agent.service", cmd.SudoHandleCmd())
|
||||
}
|
||||
go func() {
|
||||
stdout, err := cmd.RunDefaultWithStdoutBashC(itemCmd)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("handle %s failed, err: %v", itemCmd, stdout)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *DashboardService) LoadOsInfo() (*dto.OsInfo, error) {
|
||||
var baseInfo dto.OsInfo
|
||||
hostInfo, err := host.Info()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
baseInfo.OS = hostInfo.OS
|
||||
baseInfo.Platform = hostInfo.Platform
|
||||
baseInfo.PlatformFamily = hostInfo.PlatformFamily
|
||||
baseInfo.KernelArch = hostInfo.KernelArch
|
||||
baseInfo.KernelVersion = hostInfo.KernelVersion
|
||||
|
||||
diskInfo, err := disk.Usage(global.Dir.BaseDir)
|
||||
if err == nil {
|
||||
baseInfo.DiskSize = int64(diskInfo.Free)
|
||||
}
|
||||
|
||||
if baseInfo.KernelArch == "armv7l" {
|
||||
baseInfo.KernelArch = "armv7"
|
||||
}
|
||||
if baseInfo.KernelArch == "x86_64" {
|
||||
baseInfo.KernelArch = "amd64"
|
||||
}
|
||||
return &baseInfo, nil
|
||||
}
|
||||
|
||||
func (u *DashboardService) LoadCurrentInfoForNode() *dto.NodeCurrent {
|
||||
var currentInfo dto.NodeCurrent
|
||||
|
||||
currentInfo.CPUTotal, _ = cpu.Counts(true)
|
||||
totalPercent, _ := cpu.Percent(100*time.Millisecond, false)
|
||||
if len(totalPercent) == 1 {
|
||||
currentInfo.CPUUsedPercent = totalPercent[0]
|
||||
currentInfo.CPUUsed = currentInfo.CPUUsedPercent * 0.01 * float64(currentInfo.CPUTotal)
|
||||
}
|
||||
|
||||
loadInfo, _ := load.Avg()
|
||||
currentInfo.Load1 = loadInfo.Load1
|
||||
currentInfo.Load5 = loadInfo.Load5
|
||||
currentInfo.Load15 = loadInfo.Load15
|
||||
currentInfo.LoadUsagePercent = loadInfo.Load1 / (float64(currentInfo.CPUTotal*2) * 0.75) * 100
|
||||
|
||||
memoryInfo, _ := mem.VirtualMemory()
|
||||
currentInfo.MemoryTotal = memoryInfo.Total
|
||||
currentInfo.MemoryAvailable = memoryInfo.Available
|
||||
currentInfo.MemoryUsed = memoryInfo.Used
|
||||
currentInfo.MemoryUsedPercent = memoryInfo.UsedPercent
|
||||
|
||||
swapInfo, _ := mem.SwapMemory()
|
||||
currentInfo.SwapMemoryTotal = swapInfo.Total
|
||||
currentInfo.SwapMemoryAvailable = swapInfo.Free
|
||||
currentInfo.SwapMemoryUsed = swapInfo.Used
|
||||
currentInfo.SwapMemoryUsedPercent = swapInfo.UsedPercent
|
||||
|
||||
return ¤tInfo
|
||||
}
|
||||
|
||||
func (u *DashboardService) LoadBaseInfo(ioOption string, netOption string) (*dto.DashboardBase, error) {
|
||||
var baseInfo dto.DashboardBase
|
||||
hostInfo, err := host.Info()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
baseInfo.Hostname = hostInfo.Hostname
|
||||
baseInfo.OS = hostInfo.OS
|
||||
baseInfo.Platform = hostInfo.Platform
|
||||
baseInfo.PlatformFamily = hostInfo.PlatformFamily
|
||||
baseInfo.PlatformVersion = hostInfo.PlatformVersion
|
||||
baseInfo.KernelArch = hostInfo.KernelArch
|
||||
baseInfo.KernelVersion = hostInfo.KernelVersion
|
||||
ss, _ := json.Marshal(hostInfo)
|
||||
baseInfo.VirtualizationSystem = string(ss)
|
||||
baseInfo.IpV4Addr = GetOutboundIP()
|
||||
httpProxy := os.Getenv("http_proxy")
|
||||
if httpProxy == "" {
|
||||
httpProxy = os.Getenv("HTTP_PROXY")
|
||||
}
|
||||
if httpProxy != "" {
|
||||
baseInfo.SystemProxy = httpProxy
|
||||
}
|
||||
baseInfo.SystemProxy = "noProxy"
|
||||
|
||||
appInstall, err := appInstallRepo.ListBy(context.Background())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
baseInfo.AppInstalledNumber = len(appInstall)
|
||||
postgresqlDbs, err := postgresqlRepo.List()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mysqlDbs, err := mysqlRepo.List()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
baseInfo.DatabaseNumber = len(mysqlDbs) + len(postgresqlDbs)
|
||||
website, err := websiteRepo.GetBy()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
baseInfo.WebsiteNumber = len(website)
|
||||
cronjobs, err := cronjobRepo.List()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
baseInfo.CronjobNumber = len(cronjobs)
|
||||
|
||||
cpuInfo, err := cpu.Info()
|
||||
if err == nil {
|
||||
baseInfo.CPUModelName = cpuInfo[0].ModelName
|
||||
}
|
||||
|
||||
baseInfo.CPUCores, _ = cpu.Counts(false)
|
||||
baseInfo.CPULogicalCores, _ = cpu.Counts(true)
|
||||
|
||||
baseInfo.CurrentInfo = *u.LoadCurrentInfo(ioOption, netOption)
|
||||
return &baseInfo, nil
|
||||
}
|
||||
|
||||
func (u *DashboardService) LoadCurrentInfo(ioOption string, netOption string) *dto.DashboardCurrent {
|
||||
var currentInfo dto.DashboardCurrent
|
||||
hostInfo, _ := host.Info()
|
||||
currentInfo.Uptime = hostInfo.Uptime
|
||||
currentInfo.TimeSinceUptime = time.Now().Add(-time.Duration(hostInfo.Uptime) * time.Second).Format(constant.DateTimeLayout)
|
||||
currentInfo.Procs = hostInfo.Procs
|
||||
|
||||
currentInfo.CPUTotal, _ = cpu.Counts(true)
|
||||
totalPercent, _ := cpu.Percent(100*time.Millisecond, false)
|
||||
if len(totalPercent) == 1 {
|
||||
currentInfo.CPUUsedPercent = totalPercent[0]
|
||||
currentInfo.CPUUsed = currentInfo.CPUUsedPercent * 0.01 * float64(currentInfo.CPUTotal)
|
||||
}
|
||||
currentInfo.CPUPercent, _ = cpu.Percent(100*time.Millisecond, true)
|
||||
|
||||
loadInfo, _ := load.Avg()
|
||||
currentInfo.Load1 = loadInfo.Load1
|
||||
currentInfo.Load5 = loadInfo.Load5
|
||||
currentInfo.Load15 = loadInfo.Load15
|
||||
currentInfo.LoadUsagePercent = loadInfo.Load1 / (float64(currentInfo.CPUTotal*2) * 0.75) * 100
|
||||
|
||||
memoryInfo, _ := mem.VirtualMemory()
|
||||
currentInfo.MemoryTotal = memoryInfo.Total
|
||||
currentInfo.MemoryAvailable = memoryInfo.Available
|
||||
currentInfo.MemoryUsed = memoryInfo.Used
|
||||
currentInfo.MemoryUsedPercent = memoryInfo.UsedPercent
|
||||
|
||||
swapInfo, _ := mem.SwapMemory()
|
||||
currentInfo.SwapMemoryTotal = swapInfo.Total
|
||||
currentInfo.SwapMemoryAvailable = swapInfo.Free
|
||||
currentInfo.SwapMemoryUsed = swapInfo.Used
|
||||
currentInfo.SwapMemoryUsedPercent = swapInfo.UsedPercent
|
||||
|
||||
currentInfo.DiskData = loadDiskInfo()
|
||||
currentInfo.GPUData = loadGPUInfo()
|
||||
currentInfo.XPUData = loadXpuInfo()
|
||||
|
||||
if ioOption == "all" {
|
||||
diskInfo, _ := disk.IOCounters()
|
||||
for _, state := range diskInfo {
|
||||
currentInfo.IOReadBytes += state.ReadBytes
|
||||
currentInfo.IOWriteBytes += state.WriteBytes
|
||||
currentInfo.IOCount += (state.ReadCount + state.WriteCount)
|
||||
currentInfo.IOReadTime += state.ReadTime
|
||||
currentInfo.IOWriteTime += state.WriteTime
|
||||
}
|
||||
} else {
|
||||
diskInfo, _ := disk.IOCounters(ioOption)
|
||||
for _, state := range diskInfo {
|
||||
currentInfo.IOReadBytes += state.ReadBytes
|
||||
currentInfo.IOWriteBytes += state.WriteBytes
|
||||
currentInfo.IOCount += (state.ReadCount + state.WriteCount)
|
||||
currentInfo.IOReadTime += state.ReadTime
|
||||
currentInfo.IOWriteTime += state.WriteTime
|
||||
}
|
||||
}
|
||||
|
||||
if netOption == "all" {
|
||||
netInfo, _ := net.IOCounters(false)
|
||||
if len(netInfo) != 0 {
|
||||
currentInfo.NetBytesSent = netInfo[0].BytesSent
|
||||
currentInfo.NetBytesRecv = netInfo[0].BytesRecv
|
||||
}
|
||||
} else {
|
||||
netInfo, _ := net.IOCounters(true)
|
||||
for _, state := range netInfo {
|
||||
if state.Name == netOption {
|
||||
currentInfo.NetBytesSent = state.BytesSent
|
||||
currentInfo.NetBytesRecv = state.BytesRecv
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
currentInfo.ShotTime = time.Now()
|
||||
return ¤tInfo
|
||||
}
|
||||
|
||||
func (u *DashboardService) LoadAppLauncher(ctx *gin.Context) ([]dto.AppLauncher, error) {
|
||||
var (
|
||||
data []dto.AppLauncher
|
||||
recommendList []dto.AppLauncher
|
||||
)
|
||||
appInstalls, err := appInstallRepo.ListBy(context.Background())
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
apps, err := appRepo.GetBy()
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
|
||||
showList, _ := launcherRepo.ListName()
|
||||
defaultList := []string{"openresty", "mysql", "halo", "redis", "maxkb", "wordpress"}
|
||||
allList := common.RemoveRepeatStr(append(defaultList, showList...))
|
||||
for _, showItem := range allList {
|
||||
var itemData dto.AppLauncher
|
||||
for _, app := range apps {
|
||||
if showItem == app.Key {
|
||||
itemData.Key = app.Key
|
||||
itemData.Type = app.Type
|
||||
itemData.Name = app.Name
|
||||
itemData.Icon = app.Icon
|
||||
itemData.Limit = app.Limit
|
||||
itemData.Recommend = app.Recommend
|
||||
itemData.Description = app.GetDescription(ctx)
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(itemData.Icon) == 0 {
|
||||
continue
|
||||
}
|
||||
for _, install := range appInstalls {
|
||||
if install.App.Key == showItem {
|
||||
itemData.IsInstall = true
|
||||
itemData.Detail = append(itemData.Detail, dto.InstallDetail{
|
||||
InstallID: install.ID,
|
||||
DetailID: install.AppDetailId,
|
||||
Name: install.Name,
|
||||
Version: install.Version,
|
||||
Status: install.Status,
|
||||
Path: install.GetPath(),
|
||||
WebUI: install.WebUI,
|
||||
HttpPort: install.HttpPort,
|
||||
HttpsPort: install.HttpsPort,
|
||||
})
|
||||
}
|
||||
}
|
||||
if ArryContains(defaultList, showItem) && len(itemData.Detail) == 0 {
|
||||
itemData.IsRecommend = true
|
||||
recommendList = append(recommendList, itemData)
|
||||
continue
|
||||
}
|
||||
if !ArryContains(showList, showItem) && len(itemData.Detail) != 0 {
|
||||
continue
|
||||
}
|
||||
data = append(data, itemData)
|
||||
}
|
||||
|
||||
sort.Slice(recommendList, func(i, j int) bool {
|
||||
return recommendList[i].Recommend < recommendList[j].Recommend
|
||||
})
|
||||
sort.Slice(data, func(i, j int) bool {
|
||||
return data[i].Name < data[j].Name
|
||||
})
|
||||
data = append(data, recommendList...)
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (u *DashboardService) ChangeShow(req dto.SettingUpdate) error {
|
||||
launcher, _ := launcherRepo.Get(repo.WithByKey(req.Key))
|
||||
if req.Value == constant.StatusEnable && launcher.ID == 0 {
|
||||
if err := launcherRepo.Create(&model.AppLauncher{Key: req.Key}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if req.Value == constant.StatusDisable && launcher.ID != 0 {
|
||||
if err := launcherRepo.Delete(repo.WithByKey(req.Key)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *DashboardService) ListLauncherOption(filter string) ([]dto.LauncherOption, error) {
|
||||
showList, _ := launcherRepo.ListName()
|
||||
var data []dto.LauncherOption
|
||||
optionMap := make(map[string]bool)
|
||||
appInstalls, err := appInstallRepo.ListBy(context.Background())
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
|
||||
for _, install := range appInstalls {
|
||||
isShow := false
|
||||
for _, item := range showList {
|
||||
if install.App.Key == item {
|
||||
isShow = true
|
||||
break
|
||||
}
|
||||
}
|
||||
optionMap[install.App.Key] = isShow
|
||||
}
|
||||
for key, val := range optionMap {
|
||||
if len(filter) != 0 && !strings.Contains(key, filter) {
|
||||
continue
|
||||
}
|
||||
data = append(data, dto.LauncherOption{Key: key, IsShow: val})
|
||||
}
|
||||
sort.Slice(data, func(i, j int) bool {
|
||||
return data[i].Key < data[j].Key
|
||||
})
|
||||
return data, nil
|
||||
}
|
||||
|
||||
type diskInfo struct {
|
||||
Type string
|
||||
Mount string
|
||||
Device string
|
||||
}
|
||||
|
||||
func loadDiskInfo() []dto.DiskInfo {
|
||||
var datas []dto.DiskInfo
|
||||
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(2 * time.Second))
|
||||
stdout, err := cmdMgr.RunWithStdoutBashC("df -hT -P|grep '/'|grep -v tmpfs|grep -v 'snap/core'|grep -v udev")
|
||||
if err != nil {
|
||||
cmdMgr2 := cmd.NewCommandMgr(cmd.WithTimeout(1 * time.Second))
|
||||
stdout, err = cmdMgr2.RunWithStdoutBashC("df -lhT -P|grep '/'|grep -v tmpfs|grep -v 'snap/core'|grep -v udev")
|
||||
if err != nil {
|
||||
return datas
|
||||
}
|
||||
}
|
||||
lines := strings.Split(stdout, "\n")
|
||||
|
||||
var mounts []diskInfo
|
||||
var excludes = []string{"/mnt/cdrom", "/boot", "/boot/efi", "/dev", "/dev/shm", "/run/lock", "/run", "/run/shm", "/run/user"}
|
||||
for _, line := range lines {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) < 7 {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(fields[6], "/snap") || len(strings.Split(fields[6], "/")) > 10 {
|
||||
continue
|
||||
}
|
||||
if strings.TrimSpace(fields[1]) == "tmpfs" || strings.TrimSpace(fields[1]) == "overlay" {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(fields[2], "K") {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(fields[6], "docker") || strings.Contains(fields[6], "podman") || strings.Contains(fields[6], "containerd") || strings.HasPrefix(fields[6], "/var/lib/containers") {
|
||||
continue
|
||||
}
|
||||
isExclude := false
|
||||
for _, exclude := range excludes {
|
||||
if exclude == fields[6] {
|
||||
isExclude = true
|
||||
}
|
||||
}
|
||||
if isExclude {
|
||||
continue
|
||||
}
|
||||
mounts = append(mounts, diskInfo{Type: fields[1], Device: fields[0], Mount: strings.Join(fields[6:], " ")})
|
||||
}
|
||||
|
||||
var (
|
||||
wg sync.WaitGroup
|
||||
mu sync.Mutex
|
||||
)
|
||||
wg.Add(len(mounts))
|
||||
for i := 0; i < len(mounts); i++ {
|
||||
go func(timeoutCh <-chan time.Time, mount diskInfo) {
|
||||
defer wg.Done()
|
||||
|
||||
var itemData dto.DiskInfo
|
||||
itemData.Path = mount.Mount
|
||||
itemData.Type = mount.Type
|
||||
itemData.Device = mount.Device
|
||||
select {
|
||||
case <-timeoutCh:
|
||||
mu.Lock()
|
||||
datas = append(datas, itemData)
|
||||
mu.Unlock()
|
||||
global.LOG.Errorf("load disk info from %s failed, err: timeout", mount.Mount)
|
||||
default:
|
||||
state, err := disk.Usage(mount.Mount)
|
||||
if err != nil {
|
||||
mu.Lock()
|
||||
datas = append(datas, itemData)
|
||||
mu.Unlock()
|
||||
global.LOG.Errorf("load disk info from %s failed, err: %v", mount.Mount, err)
|
||||
return
|
||||
}
|
||||
itemData.Total = state.Total
|
||||
itemData.Free = state.Free
|
||||
itemData.Used = state.Used
|
||||
itemData.UsedPercent = state.UsedPercent
|
||||
itemData.InodesTotal = state.InodesTotal
|
||||
itemData.InodesUsed = state.InodesUsed
|
||||
itemData.InodesFree = state.InodesFree
|
||||
itemData.InodesUsedPercent = state.InodesUsedPercent
|
||||
mu.Lock()
|
||||
datas = append(datas, itemData)
|
||||
mu.Unlock()
|
||||
}
|
||||
}(time.After(5*time.Second), mounts[i])
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
sort.Slice(datas, func(i, j int) bool {
|
||||
return datas[i].Path < datas[j].Path
|
||||
})
|
||||
return datas
|
||||
}
|
||||
|
||||
func loadGPUInfo() []dto.GPUInfo {
|
||||
ok, client := gpu.New()
|
||||
var list []interface{}
|
||||
if ok {
|
||||
info, err := client.LoadGpuInfo()
|
||||
if err != nil || len(info.GPUs) == 0 {
|
||||
return nil
|
||||
}
|
||||
for _, item := range info.GPUs {
|
||||
list = append(list, item)
|
||||
}
|
||||
}
|
||||
if len(list) == 0 {
|
||||
return nil
|
||||
}
|
||||
var data []dto.GPUInfo
|
||||
for _, gpu := range list {
|
||||
var dataItem dto.GPUInfo
|
||||
if err := copier.Copy(&dataItem, &gpu); err != nil {
|
||||
continue
|
||||
}
|
||||
dataItem.PowerUsage = dataItem.PowerDraw + " / " + dataItem.MaxPowerLimit
|
||||
dataItem.MemoryUsage = dataItem.MemUsed + " / " + dataItem.MemTotal
|
||||
data = append(data, dataItem)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
type AppLauncher struct {
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
func ArryContains(arr []string, element string) bool {
|
||||
for _, v := range arr {
|
||||
if v == element {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func loadXpuInfo() []dto.XPUInfo {
|
||||
var list []interface{}
|
||||
ok, xpuClient := xpu.New()
|
||||
if ok {
|
||||
xpus, err := xpuClient.LoadDashData()
|
||||
if err != nil || len(xpus) == 0 {
|
||||
return nil
|
||||
}
|
||||
for _, item := range xpus {
|
||||
list = append(list, item)
|
||||
}
|
||||
}
|
||||
if len(list) == 0 {
|
||||
return nil
|
||||
}
|
||||
var data []dto.XPUInfo
|
||||
for _, gpu := range list {
|
||||
var dataItem dto.XPUInfo
|
||||
if err := copier.Copy(&dataItem, &gpu); err != nil {
|
||||
continue
|
||||
}
|
||||
data = append(data, dataItem)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func GetOutboundIP() string {
|
||||
conn, err := network.Dial("udp", "8.8.8.8:80")
|
||||
|
||||
if err != nil {
|
||||
return "IPNotFound"
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
localAddr := conn.LocalAddr().(*network.UDPAddr)
|
||||
return localAddr.IP.String()
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/jinzhu/copier"
|
||||
)
|
||||
|
||||
type GroupService struct{}
|
||||
|
||||
type IGroupService interface {
|
||||
List(req dto.OperateByType) ([]dto.GroupInfo, error)
|
||||
Create(req dto.GroupCreate) error
|
||||
Update(req dto.GroupUpdate) error
|
||||
Delete(id uint) error
|
||||
GetDefault() (model.Group, error)
|
||||
}
|
||||
|
||||
func NewIGroupService() IGroupService {
|
||||
return &GroupService{}
|
||||
}
|
||||
|
||||
func (u *GroupService) List(req dto.OperateByType) ([]dto.GroupInfo, error) {
|
||||
options := []repo.DBOption{
|
||||
repo.WithOrderBy("is_default desc"),
|
||||
repo.WithOrderBy("created_at desc"),
|
||||
}
|
||||
if len(req.Type) != 0 {
|
||||
options = append(options, repo.WithByType(req.Type))
|
||||
}
|
||||
var (
|
||||
groups []model.Group
|
||||
err error
|
||||
)
|
||||
groups, err = groupRepo.GetList(options...)
|
||||
if err != nil {
|
||||
return nil, buserr.New("ErrRecordNotFound")
|
||||
}
|
||||
var dtoUsers []dto.GroupInfo
|
||||
for _, group := range groups {
|
||||
var item dto.GroupInfo
|
||||
if err := copier.Copy(&item, &group); err != nil {
|
||||
return nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
||||
}
|
||||
dtoUsers = append(dtoUsers, item)
|
||||
}
|
||||
return dtoUsers, err
|
||||
}
|
||||
|
||||
func (u *GroupService) Create(req dto.GroupCreate) error {
|
||||
group, _ := groupRepo.Get(repo.WithByName(req.Name), repo.WithByType(req.Type))
|
||||
if group.ID != 0 {
|
||||
return buserr.New("ErrRecordExist")
|
||||
}
|
||||
if err := copier.Copy(&group, &req); err != nil {
|
||||
return buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
||||
}
|
||||
if err := groupRepo.Create(&group); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *GroupService) Delete(id uint) error {
|
||||
group, _ := groupRepo.Get(repo.WithByID(id))
|
||||
if group.ID == 0 {
|
||||
return buserr.New("ErrRecordNotFound")
|
||||
}
|
||||
if group.IsDefault {
|
||||
return buserr.New("ErrGroupIsDefault")
|
||||
}
|
||||
if group.Type == "website" {
|
||||
websites, _ := websiteRepo.List(websiteRepo.WithGroupID(group.ID))
|
||||
if len(websites) > 0 {
|
||||
return buserr.New("ErrGroupIsInWebsiteUse")
|
||||
}
|
||||
}
|
||||
return groupRepo.Delete(repo.WithByID(id))
|
||||
}
|
||||
|
||||
func (u *GroupService) Update(req dto.GroupUpdate) error {
|
||||
if req.IsDefault {
|
||||
if err := groupRepo.CancelDefault(req.Type); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
upMap := make(map[string]interface{})
|
||||
upMap["name"] = req.Name
|
||||
upMap["is_default"] = req.IsDefault
|
||||
return groupRepo.Update(req.ID, upMap)
|
||||
}
|
||||
|
||||
func (u *GroupService) GetDefault() (model.Group, error) {
|
||||
return groupRepo.Get(groupRepo.WithByDefault(true))
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
)
|
||||
|
||||
type LogService struct{}
|
||||
|
||||
type ILogService interface {
|
||||
ListSystemLogFile() ([]string, error)
|
||||
}
|
||||
|
||||
func NewILogService() ILogService {
|
||||
return &LogService{}
|
||||
}
|
||||
|
||||
func (u *LogService) ListSystemLogFile() ([]string, error) {
|
||||
var listFile []string
|
||||
files, err := os.ReadDir(global.Dir.LogDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
listMap := make(map[string]struct{})
|
||||
for _, item := range files {
|
||||
if item.IsDir() || !strings.HasPrefix(item.Name(), "1Panel") {
|
||||
continue
|
||||
}
|
||||
if item.Name() == "1Panel.log" || item.Name() == "1Panel-Core.log" {
|
||||
itemName := time.Now().Format("2006-01-02")
|
||||
if _, ok := listMap[itemName]; ok {
|
||||
continue
|
||||
}
|
||||
listMap[itemName] = struct{}{}
|
||||
listFile = append(listFile, itemName)
|
||||
continue
|
||||
}
|
||||
itemFileName := strings.TrimPrefix(item.Name(), "1Panel-Core-")
|
||||
itemFileName = strings.TrimPrefix(itemFileName, "1Panel-")
|
||||
itemFileName = strings.TrimSuffix(itemFileName, ".gz")
|
||||
itemFileName = strings.TrimSuffix(itemFileName, ".log")
|
||||
if len(itemFileName) == 0 {
|
||||
continue
|
||||
}
|
||||
if _, ok := listMap[itemFileName]; ok {
|
||||
continue
|
||||
}
|
||||
listMap[itemFileName] = struct{}{}
|
||||
listFile = append(listFile, itemFileName)
|
||||
}
|
||||
if len(listFile) < 2 {
|
||||
return listFile, nil
|
||||
}
|
||||
sort.Slice(listFile, func(i, j int) bool {
|
||||
return listFile[i] > listFile[j]
|
||||
})
|
||||
|
||||
return listFile, nil
|
||||
}
|
||||
@@ -1,350 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
cmd2 "github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
"github.com/subosito/gotenv"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/compose"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/response"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/files"
|
||||
)
|
||||
|
||||
type NginxService struct {
|
||||
}
|
||||
|
||||
type INginxService interface {
|
||||
GetNginxConfig() (*response.NginxFile, error)
|
||||
GetConfigByScope(req request.NginxScopeReq) ([]response.NginxParam, error)
|
||||
UpdateConfigByScope(req request.NginxConfigUpdate) error
|
||||
GetStatus() (response.NginxStatus, error)
|
||||
UpdateConfigFile(req request.NginxConfigFileUpdate) error
|
||||
|
||||
Build(req request.NginxBuildReq) error
|
||||
GetModules() (*response.NginxBuildConfig, error)
|
||||
UpdateModule(req request.NginxModuleUpdate) error
|
||||
}
|
||||
|
||||
func NewINginxService() INginxService {
|
||||
return &NginxService{}
|
||||
}
|
||||
|
||||
func (n NginxService) GetNginxConfig() (*response.NginxFile, error) {
|
||||
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
configPath := path.Join(global.Dir.AppInstallDir, constant.AppOpenresty, nginxInstall.Name, "conf", "nginx.conf")
|
||||
byteContent, err := files.NewFileOp().GetContent(configPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &response.NginxFile{Content: string(byteContent)}, nil
|
||||
}
|
||||
|
||||
func (n NginxService) GetConfigByScope(req request.NginxScopeReq) ([]response.NginxParam, error) {
|
||||
keys, ok := dto.ScopeKeyMap[req.Scope]
|
||||
if !ok || len(keys) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return getNginxParamsByKeys(constant.NginxScopeHttp, keys, nil)
|
||||
}
|
||||
|
||||
func (n NginxService) UpdateConfigByScope(req request.NginxConfigUpdate) error {
|
||||
keys, ok := dto.ScopeKeyMap[req.Scope]
|
||||
if !ok || len(keys) == 0 {
|
||||
return nil
|
||||
}
|
||||
return updateNginxConfig(constant.NginxScopeHttp, getNginxParams(req.Params, keys), nil)
|
||||
}
|
||||
|
||||
func (n NginxService) GetStatus() (response.NginxStatus, error) {
|
||||
httpPort, _, err := getAppInstallPort(constant.AppOpenresty)
|
||||
if err != nil {
|
||||
return response.NginxStatus{}, err
|
||||
}
|
||||
url := "http://127.0.0.1/nginx_status"
|
||||
if httpPort != 80 {
|
||||
url = fmt.Sprintf("http://127.0.0.1:%v/nginx_status", httpPort)
|
||||
}
|
||||
res, err := http.Get(url)
|
||||
if err != nil || res.StatusCode > 300 {
|
||||
return response.NginxStatus{}, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
content, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return response.NginxStatus{}, err
|
||||
}
|
||||
var status response.NginxStatus
|
||||
resArray := strings.Split(string(content), " ")
|
||||
active, err := strconv.Atoi(resArray[2])
|
||||
if err == nil {
|
||||
status.Active = active
|
||||
}
|
||||
accepts, err := strconv.Atoi(resArray[7])
|
||||
if err == nil {
|
||||
status.Accepts = accepts
|
||||
}
|
||||
handled, err := strconv.Atoi(resArray[8])
|
||||
if err == nil {
|
||||
status.Handled = handled
|
||||
}
|
||||
requests, err := strconv.Atoi(resArray[9])
|
||||
if err == nil {
|
||||
status.Requests = requests
|
||||
}
|
||||
reading, err := strconv.Atoi(resArray[11])
|
||||
if err == nil {
|
||||
status.Reading = reading
|
||||
}
|
||||
writing, err := strconv.Atoi(resArray[13])
|
||||
if err == nil {
|
||||
status.Writing = writing
|
||||
}
|
||||
waiting, err := strconv.Atoi(resArray[15])
|
||||
if err == nil {
|
||||
status.Waiting = waiting
|
||||
}
|
||||
return status, nil
|
||||
}
|
||||
|
||||
func (n NginxService) UpdateConfigFile(req request.NginxConfigFileUpdate) error {
|
||||
fileOp := files.NewFileOp()
|
||||
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
filePath := path.Join(global.Dir.AppInstallDir, constant.AppOpenresty, nginxInstall.Name, "conf", "nginx.conf")
|
||||
if req.Backup {
|
||||
backupPath := path.Join(path.Dir(filePath), "bak")
|
||||
if !fileOp.Stat(backupPath) {
|
||||
if err := fileOp.CreateDir(backupPath, constant.DirPerm); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
newFile := path.Join(backupPath, "nginx.bak"+"-"+time.Now().Format("2006-01-02-15-04-05"))
|
||||
if err := fileOp.Copy(filePath, backupPath); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fileOp.Rename(path.Join(backupPath, "nginx.conf"), newFile); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
oldContent, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = fileOp.WriteFile(filePath, strings.NewReader(req.Content), constant.DirPerm); err != nil {
|
||||
return err
|
||||
}
|
||||
if status, err := checkContainerStatus(nginxInstall.ContainerName); err == nil && status != "running" {
|
||||
if out, err := compose.DownAndUp(nginxInstall.GetComposePath()); err != nil {
|
||||
_ = fileOp.SaveFile(filePath, string(oldContent), constant.DirPerm)
|
||||
return fmt.Errorf("nginx restart failed: %v", out)
|
||||
} else {
|
||||
return nginxCheckAndReload(string(oldContent), filePath, nginxInstall.ContainerName)
|
||||
}
|
||||
}
|
||||
return nginxCheckAndReload(string(oldContent), filePath, nginxInstall.ContainerName)
|
||||
}
|
||||
|
||||
func (n NginxService) Build(req request.NginxBuildReq) error {
|
||||
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
taskName := task.GetTaskName(nginxInstall.Name, task.TaskBuild, task.TaskScopeApp)
|
||||
if err = task.CheckTaskIsExecuting(taskName); err != nil {
|
||||
return err
|
||||
}
|
||||
fileOp := files.NewFileOp()
|
||||
buildPath := path.Join(nginxInstall.GetPath(), "build")
|
||||
if !fileOp.Stat(buildPath) {
|
||||
return buserr.New("ErrBuildDirNotFound")
|
||||
}
|
||||
moduleConfigPath := path.Join(buildPath, "module.json")
|
||||
moduleContent, err := fileOp.GetContent(moduleConfigPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var (
|
||||
modules []dto.NginxModule
|
||||
addModuleParams []string
|
||||
addPackages []string
|
||||
)
|
||||
if len(moduleContent) > 0 {
|
||||
_ = json.Unmarshal(moduleContent, &modules)
|
||||
bashFile, err := os.OpenFile(path.Join(buildPath, "tmp", "pre.sh"), os.O_CREATE|os.O_WRONLY|os.O_TRUNC, constant.DirPerm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer bashFile.Close()
|
||||
bashFileWriter := bufio.NewWriter(bashFile)
|
||||
for _, module := range modules {
|
||||
if !module.Enable {
|
||||
continue
|
||||
}
|
||||
_, err = bashFileWriter.WriteString(module.Script + "\n")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
addModuleParams = append(addModuleParams, module.Params)
|
||||
addPackages = append(addPackages, module.Packages...)
|
||||
}
|
||||
err = bashFileWriter.Flush()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
envs, err := gotenv.Read(nginxInstall.GetEnvPath())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
envs["CONTAINER_PACKAGE_URL"] = req.Mirror
|
||||
envs["RESTY_CONFIG_OPTIONS_MORE"] = ""
|
||||
envs["RESTY_ADD_PACKAGE_BUILDDEPS"] = ""
|
||||
if len(addModuleParams) > 0 {
|
||||
envs["RESTY_CONFIG_OPTIONS_MORE"] = strings.Join(addModuleParams, " ")
|
||||
}
|
||||
if len(addPackages) > 0 {
|
||||
envs["RESTY_ADD_PACKAGE_BUILDDEPS"] = strings.Join(addPackages, " ")
|
||||
}
|
||||
_ = gotenv.Write(envs, nginxInstall.GetEnvPath())
|
||||
|
||||
buildTask, err := task.NewTaskWithOps(nginxInstall.Name, task.TaskBuild, task.TaskScopeApp, req.TaskID, nginxInstall.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buildTask.AddSubTaskWithOps("", func(t *task.Task) error {
|
||||
cmdMgr := cmd2.NewCommandMgr(cmd.WithTask(*buildTask), cmd.WithTimeout(60*time.Minute))
|
||||
if err = cmdMgr.RunBashCf("docker compose -f %s build", nginxInstall.GetComposePath()); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = compose.DownAndUp(nginxInstall.GetComposePath())
|
||||
return err
|
||||
}, nil, 0, 60*time.Minute)
|
||||
|
||||
go func() {
|
||||
_ = buildTask.Execute()
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n NginxService) GetModules() (*response.NginxBuildConfig, error) {
|
||||
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fileOp := files.NewFileOp()
|
||||
var modules []dto.NginxModule
|
||||
moduleConfigPath := path.Join(nginxInstall.GetPath(), "build", "module.json")
|
||||
if !fileOp.Stat(moduleConfigPath) {
|
||||
return nil, nil
|
||||
}
|
||||
moduleContent, err := fileOp.GetContent(moduleConfigPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(moduleContent) > 0 {
|
||||
_ = json.Unmarshal(moduleContent, &modules)
|
||||
}
|
||||
var resList []response.NginxModule
|
||||
for _, module := range modules {
|
||||
resList = append(resList, response.NginxModule{
|
||||
Name: module.Name,
|
||||
Script: module.Script,
|
||||
Packages: strings.Join(module.Packages, ","),
|
||||
Params: module.Params,
|
||||
Enable: module.Enable,
|
||||
})
|
||||
}
|
||||
envs, err := gotenv.Read(nginxInstall.GetEnvPath())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &response.NginxBuildConfig{
|
||||
Mirror: envs["CONTAINER_PACKAGE_URL"],
|
||||
Modules: resList,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (n NginxService) UpdateModule(req request.NginxModuleUpdate) error {
|
||||
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fileOp := files.NewFileOp()
|
||||
var (
|
||||
modules []dto.NginxModule
|
||||
)
|
||||
moduleConfigPath := path.Join(nginxInstall.GetPath(), "build", "module.json")
|
||||
if !fileOp.Stat(moduleConfigPath) {
|
||||
_ = fileOp.CreateFile(moduleConfigPath)
|
||||
}
|
||||
moduleContent, err := fileOp.GetContent(moduleConfigPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(moduleContent) > 0 {
|
||||
_ = json.Unmarshal(moduleContent, &modules)
|
||||
}
|
||||
|
||||
switch req.Operate {
|
||||
case "create":
|
||||
for _, module := range modules {
|
||||
if module.Name == req.Name {
|
||||
return buserr.New("ErrNameIsExist")
|
||||
}
|
||||
}
|
||||
modules = append(modules, dto.NginxModule{
|
||||
Name: req.Name,
|
||||
Script: req.Script,
|
||||
Packages: strings.Split(req.Packages, ","),
|
||||
Params: req.Params,
|
||||
Enable: true,
|
||||
})
|
||||
case "update":
|
||||
for i, module := range modules {
|
||||
if module.Name == req.Name {
|
||||
modules[i].Script = req.Script
|
||||
modules[i].Packages = strings.Split(req.Packages, ",")
|
||||
modules[i].Params = req.Params
|
||||
modules[i].Enable = req.Enable
|
||||
break
|
||||
}
|
||||
}
|
||||
case "delete":
|
||||
for i, module := range modules {
|
||||
if module.Name == req.Name {
|
||||
modules = append(modules[:i], modules[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
moduleByte, err := json.Marshal(modules)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fileOp.SaveFileWithByte(moduleConfigPath, moduleByte, constant.DirPerm)
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,961 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
cmd2 "github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/response"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/cmd/server/nginx_conf"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/compose"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/docker"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/files"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/subosito/gotenv"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func handleRuntime(create request.RuntimeCreate, runtime *model.Runtime, fileOp files.FileOp, appVersionDir string) (err error) {
|
||||
runtimeDir := path.Join(global.Dir.RuntimeDir, create.Type)
|
||||
if err = fileOp.CopyDir(appVersionDir, runtimeDir); err != nil {
|
||||
return
|
||||
}
|
||||
versionDir := path.Join(runtimeDir, filepath.Base(appVersionDir))
|
||||
projectDir := path.Join(runtimeDir, create.Name)
|
||||
defer func() {
|
||||
if err != nil {
|
||||
_ = fileOp.DeleteDir(projectDir)
|
||||
}
|
||||
}()
|
||||
if err = fileOp.Rename(versionDir, projectDir); err != nil {
|
||||
return
|
||||
}
|
||||
composeContent, envContent, _, err := handleParams(create, projectDir)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
runtime.DockerCompose = string(composeContent)
|
||||
runtime.Env = string(envContent)
|
||||
runtime.Status = constant.StatusCreating
|
||||
runtime.CodeDir = create.CodeDir
|
||||
|
||||
nodeDetail, err := appDetailRepo.GetFirst(repo.WithByID(runtime.AppDetailID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
RequestDownloadCallBack(nodeDetail.DownloadCallBackUrl)
|
||||
}()
|
||||
go startRuntime(runtime)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func handlePHP(create request.RuntimeCreate, runtime *model.Runtime, fileOp files.FileOp, appVersionDir string) (err error) {
|
||||
runtimeDir := path.Join(global.Dir.RuntimeDir, create.Type)
|
||||
if err = fileOp.CopyDirWithNewName(appVersionDir, runtimeDir, create.Name); err != nil {
|
||||
return
|
||||
}
|
||||
projectDir := path.Join(runtimeDir, create.Name)
|
||||
defer func() {
|
||||
if err != nil {
|
||||
_ = fileOp.DeleteDir(projectDir)
|
||||
}
|
||||
}()
|
||||
|
||||
version, ok := create.Params["PHP_VERSION"]
|
||||
if ok {
|
||||
extensionsDir := path.Join(projectDir, "extensions", getExtensionDir(version.(string)))
|
||||
_ = fileOp.CreateDir(extensionsDir, 0644)
|
||||
}
|
||||
|
||||
composeContent, envContent, forms, err := handleParams(create, projectDir)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
runtime.DockerCompose = string(composeContent)
|
||||
runtime.Env = string(envContent)
|
||||
runtime.Params = string(forms)
|
||||
runtime.Status = constant.StatusBuilding
|
||||
|
||||
go buildRuntime(runtime, "", "", false)
|
||||
return
|
||||
}
|
||||
|
||||
func startRuntime(runtime *model.Runtime) {
|
||||
if err := runComposeCmdWithLog("up", runtime.GetComposePath(), runtime.GetLogPath()); err != nil {
|
||||
runtime.Status = constant.StatusError
|
||||
runtime.Message = err.Error()
|
||||
_ = runtimeRepo.Save(runtime)
|
||||
return
|
||||
}
|
||||
|
||||
if err := SyncRuntimeContainerStatus(runtime); err != nil {
|
||||
runtime.Status = constant.StatusError
|
||||
runtime.Message = err.Error()
|
||||
_ = runtimeRepo.Save(runtime)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func reCreateRuntime(runtime *model.Runtime) {
|
||||
var err error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
runtime.Status = constant.StatusError
|
||||
runtime.Message = err.Error()
|
||||
_ = runtimeRepo.Save(runtime)
|
||||
}
|
||||
}()
|
||||
if err = runComposeCmdWithLog("down", runtime.GetComposePath(), runtime.GetLogPath()); err != nil {
|
||||
return
|
||||
}
|
||||
if err = runComposeCmdWithLog("up", runtime.GetComposePath(), runtime.GetLogPath()); err != nil {
|
||||
return
|
||||
}
|
||||
if err := SyncRuntimeContainerStatus(runtime); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func runComposeCmdWithLog(operate string, composePath string, logPath string) error {
|
||||
cmd := exec.Command("docker", "compose", "-f", composePath, operate)
|
||||
if operate == "up" {
|
||||
cmd = exec.Command("docker", "compose", "-f", composePath, operate, "-d")
|
||||
}
|
||||
logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, constant.FilePerm)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Failed to open log file: %v", err)
|
||||
return err
|
||||
}
|
||||
defer logFile.Close()
|
||||
multiWriterStdout := io.MultiWriter(os.Stdout, logFile)
|
||||
cmd.Stdout = multiWriterStdout
|
||||
var stderrBuf bytes.Buffer
|
||||
multiWriterStderr := io.MultiWriter(&stderrBuf, logFile, os.Stderr)
|
||||
cmd.Stderr = multiWriterStderr
|
||||
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return errors.New(buserr.New("ErrRuntimeStart").Error() + ":" + stderrBuf.String())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func SyncRuntimesStatus(runtimes []model.Runtime) error {
|
||||
cli, err := docker.NewClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cli.Close()
|
||||
var containerNames []string
|
||||
runtimeContainer := make(map[string]int)
|
||||
for index, runtime := range runtimes {
|
||||
containerNames = append(containerNames, runtime.ContainerName)
|
||||
runtimeContainer["/"+runtime.ContainerName] = index
|
||||
}
|
||||
containers, err := cli.ListContainersByName(containerNames)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, contain := range containers {
|
||||
if index, ok := runtimeContainer[contain.Names[0]]; ok {
|
||||
if runtimes[index].Status == constant.StatusBuilding || runtimes[index].Status == constant.StatusCreating {
|
||||
delete(runtimeContainer, contain.Names[0])
|
||||
continue
|
||||
}
|
||||
switch contain.State {
|
||||
case "exited":
|
||||
runtimes[index].Status = constant.StatusError
|
||||
case "running":
|
||||
runtimes[index].Status = constant.StatusRunning
|
||||
case "paused":
|
||||
runtimes[index].Status = constant.StatusStopped
|
||||
case "restarting":
|
||||
runtimes[index].Status = constant.StatusRestarting
|
||||
}
|
||||
_ = runtimeRepo.Save(&runtimes[index])
|
||||
delete(runtimeContainer, contain.Names[0])
|
||||
}
|
||||
}
|
||||
for _, index := range runtimeContainer {
|
||||
if runtimes[index].Status != constant.StatusBuilding && runtimes[index].Status != constant.StatusCreating {
|
||||
runtimes[index].Status = constant.StatusStopped
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func SyncRuntimeContainerStatus(runtime *model.Runtime) error {
|
||||
env, err := gotenv.Unmarshal(runtime.Env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var containerNames []string
|
||||
if containerName, ok := env["CONTAINER_NAME"]; !ok {
|
||||
return buserr.New("ErrContainerNameNotFound")
|
||||
} else {
|
||||
containerNames = append(containerNames, containerName)
|
||||
}
|
||||
cli, err := docker.NewClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cli.Close()
|
||||
containers, err := cli.ListContainersByName(containerNames)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(containers) == 0 {
|
||||
return buserr.WithNameAndErr("ErrContainerNotFound", containerNames[0], nil)
|
||||
}
|
||||
container := containers[0]
|
||||
|
||||
switch container.State {
|
||||
case "exited":
|
||||
runtime.Status = constant.StatusError
|
||||
case "running":
|
||||
runtime.Status = constant.StatusRunning
|
||||
case "paused":
|
||||
runtime.Status = constant.StatusStopped
|
||||
default:
|
||||
if runtime.Status != constant.StatusBuilding {
|
||||
runtime.Status = constant.StatusStopped
|
||||
}
|
||||
}
|
||||
|
||||
return runtimeRepo.Save(runtime)
|
||||
}
|
||||
|
||||
func getRuntimeEnv(envStr, key string) string {
|
||||
env, err := gotenv.Unmarshal(envStr)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
if v, ok := env[key]; ok {
|
||||
return v
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func deleteImageByID(oldImageID, imageName string, client docker.Client) {
|
||||
newImageID, err := client.GetImageIDByName(imageName)
|
||||
if err == nil && newImageID != oldImageID {
|
||||
global.LOG.Infof("delete imageID [%s] ", oldImageID)
|
||||
if err := client.DeleteImage(oldImageID); err != nil {
|
||||
global.LOG.Errorf("delete imageID [%s] error %v", oldImageID, err)
|
||||
} else {
|
||||
global.LOG.Infof("delete old image success")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func buildRuntime(runtime *model.Runtime, oldImageID string, oldEnv string, rebuild bool) {
|
||||
runtimePath := runtime.GetPath()
|
||||
composePath := runtime.GetComposePath()
|
||||
logPath := path.Join(runtimePath, "build.log")
|
||||
|
||||
logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, constant.FilePerm)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("failed to open log file: %v", err)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
_ = logFile.Close()
|
||||
}()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Hour)
|
||||
defer cancel()
|
||||
|
||||
cmd := exec.CommandContext(ctx, "docker", "compose", "-f", composePath, "build")
|
||||
cmd.Stdout = logFile
|
||||
var stderrBuf bytes.Buffer
|
||||
multiWriterStderr := io.MultiWriter(&stderrBuf, logFile)
|
||||
cmd.Stderr = multiWriterStderr
|
||||
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
runtime.Status = constant.StatusError
|
||||
runtime.Message = buserr.New("ErrImageBuildErr").Error() + ":" + stderrBuf.String()
|
||||
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
|
||||
runtime.Message = buserr.New("ErrImageBuildErr").Error() + ":" + buserr.New("ErrCmdTimeout").Error()
|
||||
} else {
|
||||
runtime.Message = buserr.New("ErrImageBuildErr").Error() + ":" + stderrBuf.String()
|
||||
}
|
||||
_ = runtimeRepo.Save(runtime)
|
||||
return
|
||||
}
|
||||
if err = runComposeCmdWithLog(constant.RuntimeDown, runtime.GetComposePath(), runtime.GetLogPath()); err != nil {
|
||||
return
|
||||
}
|
||||
client, err := docker.NewClient()
|
||||
if err != nil {
|
||||
_, _ = logFile.WriteString(fmt.Sprintf("failed to connect to docker client: %v", err))
|
||||
return
|
||||
}
|
||||
runtime.Message = ""
|
||||
if rebuild && runtime.ID > 0 {
|
||||
extensionsStr := getRuntimeEnv(runtime.Env, "PHP_EXTENSIONS")
|
||||
extensionsArray := strings.Split(extensionsStr, ",")
|
||||
oldExtensionStr := getRuntimeEnv(oldEnv, "PHP_EXTENSIONS")
|
||||
oldExtensionArray := strings.Split(oldExtensionStr, ",")
|
||||
var delExtensions []string
|
||||
for _, oldExt := range oldExtensionArray {
|
||||
exist := false
|
||||
for _, ext := range extensionsArray {
|
||||
if oldExt == ext {
|
||||
exist = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !exist {
|
||||
delExtensions = append(delExtensions, oldExt)
|
||||
}
|
||||
}
|
||||
if err = unInstallPHPExtension(runtime, delExtensions); err != nil {
|
||||
_, _ = logFile.WriteString(fmt.Sprintf("unInstallPHPExtension error %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = runtimeRepo.Save(runtime)
|
||||
}()
|
||||
|
||||
if out, err := compose.Up(composePath); err != nil {
|
||||
runtime.Status = constant.StatusStartErr
|
||||
runtime.Message = out
|
||||
return
|
||||
}
|
||||
deleteImageID := ""
|
||||
extensions := getRuntimeEnv(runtime.Env, "PHP_EXTENSIONS")
|
||||
if extensions != "" {
|
||||
deleteImageID, _ = client.GetImageIDByName(runtime.Image)
|
||||
cmdMgr := cmd2.NewCommandMgr(cmd2.WithTimeout(60*time.Minute), cmd2.WithOutputFile(logPath))
|
||||
if err = cmdMgr.Run("docker", "exec", "-i", runtime.ContainerName, "install-ext", extensions); err != nil {
|
||||
runtime.Status = constant.StatusError
|
||||
runtime.Message = buserr.New("ErrImageBuildErr").Error() + ":" + err.Error()
|
||||
return
|
||||
}
|
||||
commitMgr := cmd2.NewCommandMgr(cmd2.WithTimeout(10*time.Minute), cmd2.WithOutputFile(logPath))
|
||||
err = commitMgr.Run("docker", "commit", runtime.ContainerName, runtime.Image)
|
||||
if err != nil {
|
||||
runtime.Status = constant.StatusError
|
||||
runtime.Message = buserr.New("ErrImageBuildErr").Error() + ":" + err.Error()
|
||||
return
|
||||
}
|
||||
}
|
||||
if oldImageID != "" {
|
||||
deleteImageByID(oldImageID, runtime.Image, client)
|
||||
}
|
||||
if deleteImageID != "" {
|
||||
deleteImageByID(deleteImageID, runtime.Image, client)
|
||||
}
|
||||
if out, err := compose.DownAndUp(composePath); err != nil {
|
||||
runtime.Status = constant.StatusStartErr
|
||||
runtime.Message = out
|
||||
return
|
||||
}
|
||||
runtime.Status = constant.StatusRunning
|
||||
_ = runtimeRepo.Save(runtime)
|
||||
}
|
||||
|
||||
func handleParams(create request.RuntimeCreate, projectDir string) (composeContent []byte, envContent []byte, forms []byte, err error) {
|
||||
fileOp := files.NewFileOp()
|
||||
composeContent, err = fileOp.GetContent(path.Join(projectDir, "docker-compose.yml"))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
envPath := path.Join(projectDir, ".env")
|
||||
if !fileOp.Stat(envPath) {
|
||||
_ = fileOp.CreateFile(envPath)
|
||||
}
|
||||
env, err := gotenv.Read(envPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for k := range env {
|
||||
if strings.HasPrefix(k, "CONTAINER_PORT_") || strings.HasPrefix(k, "HOST_PORT_") || strings.HasPrefix(k, "HOST_IP_") || strings.Contains(k, "APP_PORT") {
|
||||
delete(env, k)
|
||||
}
|
||||
}
|
||||
switch create.Type {
|
||||
case constant.RuntimePHP:
|
||||
create.Params["IMAGE_NAME"] = create.Image
|
||||
var fromYml []byte
|
||||
fromYml, err = fileOp.GetContent(path.Join(projectDir, "data.yml"))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var data dto.PHPForm
|
||||
err = yaml.Unmarshal(fromYml, &data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
formFields := data.AdditionalProperties.FormFields
|
||||
forms, err = json.MarshalIndent(map[string]interface{}{
|
||||
"formFields": formFields,
|
||||
}, "", " ")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if extends, ok := create.Params["PHP_EXTENSIONS"]; ok {
|
||||
if extendsArray, ok := extends.([]interface{}); ok {
|
||||
strArray := make([]string, len(extendsArray))
|
||||
for i, v := range extendsArray {
|
||||
strArray[i] = strings.ToLower(fmt.Sprintf("%v", v))
|
||||
}
|
||||
create.Params["PHP_EXTENSIONS"] = strings.Join(strArray, ",")
|
||||
}
|
||||
}
|
||||
create.Params["CONTAINER_PACKAGE_URL"] = create.Source
|
||||
siteDir, _ := settingRepo.Get(settingRepo.WithByKey("WEBSITE_DIR"))
|
||||
if siteDir.Value == "" {
|
||||
siteDir.Value = path.Join(global.Dir.BaseDir, "1panel", "www")
|
||||
}
|
||||
create.Params["PANEL_WEBSITE_DIR"] = siteDir.Value
|
||||
composeContent, err = handleEnvironments(composeContent, create, projectDir)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
case constant.RuntimeNode:
|
||||
create.Params["CODE_DIR"] = create.CodeDir
|
||||
create.Params["NODE_VERSION"] = create.Version
|
||||
if create.NodeConfig.Install {
|
||||
create.Params["RUN_INSTALL"] = "1"
|
||||
} else {
|
||||
create.Params["RUN_INSTALL"] = "0"
|
||||
}
|
||||
create.Params["CONTAINER_PACKAGE_URL"] = create.Source
|
||||
composeContent, err = handleCompose(env, composeContent, create, projectDir)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
case constant.RuntimeJava:
|
||||
create.Params["CODE_DIR"] = create.CodeDir
|
||||
create.Params["JAVA_VERSION"] = create.Version
|
||||
composeContent, err = handleCompose(env, composeContent, create, projectDir)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
case constant.RuntimeGo:
|
||||
create.Params["CODE_DIR"] = create.CodeDir
|
||||
create.Params["GO_VERSION"] = create.Version
|
||||
composeContent, err = handleCompose(env, composeContent, create, projectDir)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
case constant.RuntimePython:
|
||||
create.Params["CODE_DIR"] = create.CodeDir
|
||||
create.Params["PYTHON_VERSION"] = create.Version
|
||||
composeContent, err = handleCompose(env, composeContent, create, projectDir)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
case constant.RuntimeDotNet:
|
||||
create.Params["CODE_DIR"] = create.CodeDir
|
||||
create.Params["DOTNET_VERSION"] = create.Version
|
||||
composeContent, err = handleCompose(env, composeContent, create, projectDir)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
newMap := make(map[string]string)
|
||||
handleMap(create.Params, newMap)
|
||||
for k, v := range newMap {
|
||||
env[k] = v
|
||||
}
|
||||
|
||||
envStr, err := gotenv.Marshal(env)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if err = gotenv.Write(env, envPath); err != nil {
|
||||
return
|
||||
}
|
||||
envContent = []byte(envStr)
|
||||
return
|
||||
}
|
||||
|
||||
func handleEnvironments(composeContent []byte, create request.RuntimeCreate, projectDir string) (composeByte []byte, err error) {
|
||||
composeMap := make(map[string]interface{})
|
||||
if err = yaml.Unmarshal(composeContent, &composeMap); err != nil {
|
||||
return
|
||||
}
|
||||
services, serviceValid := composeMap["services"].(map[string]interface{})
|
||||
if !serviceValid {
|
||||
err = buserr.New("ErrFileParse")
|
||||
return
|
||||
}
|
||||
serviceName := ""
|
||||
serviceValue := make(map[string]interface{})
|
||||
for name, service := range services {
|
||||
serviceName = name
|
||||
serviceValue = service.(map[string]interface{})
|
||||
var environments []interface{}
|
||||
for _, e := range create.Environments {
|
||||
environments = append(environments, fmt.Sprintf("%s=%s", e.Key, e.Value))
|
||||
}
|
||||
delete(serviceValue, "environment")
|
||||
if len(environments) > 0 {
|
||||
serviceValue["environment"] = environments
|
||||
}
|
||||
break
|
||||
}
|
||||
services[serviceName] = serviceValue
|
||||
composeMap["services"] = services
|
||||
composeByte, err = yaml.Marshal(composeMap)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
fileOp := files.NewFileOp()
|
||||
_ = fileOp.SaveFile(path.Join(projectDir, "docker-compose.yml"), string(composeByte), constant.DirPerm)
|
||||
return
|
||||
}
|
||||
|
||||
func handleCompose(env gotenv.Env, composeContent []byte, create request.RuntimeCreate, projectDir string) (composeByte []byte, err error) {
|
||||
existMap := make(map[string]interface{})
|
||||
composeMap := make(map[string]interface{})
|
||||
if err = yaml.Unmarshal(composeContent, &composeMap); err != nil {
|
||||
return
|
||||
}
|
||||
services, serviceValid := composeMap["services"].(map[string]interface{})
|
||||
if !serviceValid {
|
||||
err = buserr.New("ErrFileParse")
|
||||
return
|
||||
}
|
||||
serviceName := ""
|
||||
serviceValue := make(map[string]interface{})
|
||||
for name, service := range services {
|
||||
serviceName = name
|
||||
serviceValue = service.(map[string]interface{})
|
||||
delete(serviceValue, "ports")
|
||||
if create.ExposedPorts != nil && len(create.ExposedPorts) > 0 {
|
||||
var ports []interface{}
|
||||
for i, port := range create.ExposedPorts {
|
||||
containerPortStr := fmt.Sprintf("CONTAINER_PORT_%d", i)
|
||||
hostPortStr := fmt.Sprintf("HOST_PORT_%d", i)
|
||||
existMap[containerPortStr] = struct{}{}
|
||||
existMap[hostPortStr] = struct{}{}
|
||||
hostIPStr := fmt.Sprintf("HOST_IP_%d", i)
|
||||
ports = append(ports, fmt.Sprintf("${%s}:${%s}:${%s}", hostIPStr, hostPortStr, containerPortStr))
|
||||
create.Params[containerPortStr] = port.ContainerPort
|
||||
create.Params[hostPortStr] = port.HostPort
|
||||
create.Params[hostIPStr] = port.HostIP
|
||||
}
|
||||
if create.Type == constant.RuntimePHP {
|
||||
ports = append(ports, fmt.Sprintf("127.0.0.1:${PANEL_APP_PORT_HTTP}:9000"))
|
||||
}
|
||||
serviceValue["ports"] = ports
|
||||
} else {
|
||||
if create.Type == constant.RuntimePHP {
|
||||
serviceValue["ports"] = []interface{}{fmt.Sprintf("127.0.0.1:${PANEL_APP_PORT_HTTP}:9000")}
|
||||
}
|
||||
}
|
||||
var environments []interface{}
|
||||
for _, e := range create.Environments {
|
||||
environments = append(environments, fmt.Sprintf("%s=%s", e.Key, e.Value))
|
||||
}
|
||||
delete(serviceValue, "environment")
|
||||
if len(environments) > 0 {
|
||||
serviceValue["environment"] = environments
|
||||
}
|
||||
var volumes []interface{}
|
||||
defaultVolumes := make(map[string]string)
|
||||
switch create.Type {
|
||||
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimePython, constant.RuntimeDotNet:
|
||||
defaultVolumes = constant.RuntimeDefaultVolumes
|
||||
case constant.RuntimeGo:
|
||||
defaultVolumes = constant.GoDefaultVolumes
|
||||
case constant.RuntimePHP:
|
||||
defaultVolumes = constant.PHPDefaultVolumes
|
||||
}
|
||||
for k, v := range defaultVolumes {
|
||||
volumes = append(volumes, fmt.Sprintf("%s:%s", k, v))
|
||||
}
|
||||
for _, volume := range create.Volumes {
|
||||
volumes = append(volumes, fmt.Sprintf("%s:%s", volume.Source, volume.Target))
|
||||
}
|
||||
serviceValue["volumes"] = volumes
|
||||
break
|
||||
}
|
||||
for k := range env {
|
||||
if strings.Contains(k, "CONTAINER_PORT_") || strings.Contains(k, "HOST_PORT_") {
|
||||
if _, ok := existMap[k]; !ok {
|
||||
delete(env, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
services[serviceName] = serviceValue
|
||||
composeMap["services"] = services
|
||||
composeByte, err = yaml.Marshal(composeMap)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
fileOp := files.NewFileOp()
|
||||
_ = fileOp.SaveFile(path.Join(projectDir, "docker-compose.yml"), string(composeByte), constant.DirPerm)
|
||||
return
|
||||
}
|
||||
|
||||
func checkContainerName(name string) error {
|
||||
dockerCli, err := docker.NewClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dockerCli.Close()
|
||||
names, err := dockerCli.ListContainersByName([]string{name})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(names) > 0 {
|
||||
return buserr.New("ErrContainerName")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkContainerStatus(name string) (string, error) {
|
||||
dockerCli, err := docker.NewClient()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer dockerCli.Close()
|
||||
names, err := dockerCli.ListContainersByName([]string{name})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(names) > 0 {
|
||||
return names[0].State, nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func unInstallPHPExtension(runtime *model.Runtime, delExtensions []string) error {
|
||||
dir := runtime.GetPath()
|
||||
fileOP := files.NewFileOp()
|
||||
var phpExtensions []response.SupportExtension
|
||||
if err := json.Unmarshal(nginx_conf.PHPExtensionsJson, &phpExtensions); err != nil {
|
||||
return err
|
||||
}
|
||||
phpVersion := getRuntimeEnv(runtime.Env, "PHP_VERSION")
|
||||
phpExtensionDir := path.Join(dir, "extensions", getExtensionDir(phpVersion))
|
||||
|
||||
delMap := make(map[string]struct{})
|
||||
for _, ext := range phpExtensions {
|
||||
for _, del := range delExtensions {
|
||||
if ext.Name == del {
|
||||
delMap[ext.Check] = struct{}{}
|
||||
_ = fileOP.DeleteFile(path.Join(phpExtensionDir, ext.File))
|
||||
_ = fileOP.DeleteFile(path.Join(dir, "conf", "conf.d", "docker-php-ext-"+ext.Check+".ini"))
|
||||
_ = removePHPIniExt(path.Join(dir, "conf", "php.ini"), ext.File)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
extensions := getRuntimeEnv(runtime.Env, "PHP_EXTENSIONS")
|
||||
var (
|
||||
oldExts []string
|
||||
newExts []string
|
||||
)
|
||||
oldExts = strings.Split(extensions, ",")
|
||||
for _, ext := range oldExts {
|
||||
if _, ok := delMap[ext]; !ok {
|
||||
newExts = append(newExts, ext)
|
||||
}
|
||||
}
|
||||
newExtensions := strings.Join(newExts, ",")
|
||||
envs, err := gotenv.Unmarshal(runtime.Env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
envs["PHP_EXTENSIONS"] = newExtensions
|
||||
if err = gotenv.Write(envs, runtime.GetEnvPath()); err != nil {
|
||||
return err
|
||||
}
|
||||
envContent, err := gotenv.Marshal(envs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
runtime.Env = envContent
|
||||
return nil
|
||||
}
|
||||
|
||||
func removePHPIniExt(filePath, extensionName string) error {
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
targetLine1 := `extension="` + extensionName + `"`
|
||||
targetLine2 := `zend_extension="` + extensionName + `"`
|
||||
|
||||
tempFile, err := os.CreateTemp(path.Dir(filePath), "temp_*.txt")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tempFile.Close()
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
writer := bufio.NewWriter(tempFile)
|
||||
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if !strings.Contains(line, targetLine1) && !strings.Contains(line, targetLine2) {
|
||||
_, err := writer.WriteString(line + "\n")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.Rename(tempFile.Name(), filePath)
|
||||
}
|
||||
|
||||
func restartRuntime(runtime *model.Runtime) (err error) {
|
||||
if err = runComposeCmdWithLog(constant.RuntimeDown, runtime.GetComposePath(), runtime.GetLogPath()); err != nil {
|
||||
return
|
||||
}
|
||||
if err = runComposeCmdWithLog(constant.RuntimeUp, runtime.GetComposePath(), runtime.GetLogPath()); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func getDockerComposeEnvironments(yml []byte) ([]request.Environment, error) {
|
||||
var (
|
||||
composeProject docker.ComposeProject
|
||||
err error
|
||||
)
|
||||
err = yaml.Unmarshal(yml, &composeProject)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var res []request.Environment
|
||||
for _, service := range composeProject.Services {
|
||||
for key, value := range service.Environment.Variables {
|
||||
res = append(res, request.Environment{
|
||||
Key: key,
|
||||
Value: value,
|
||||
})
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func getDockerComposeVolumes(yml []byte) ([]request.Volume, error) {
|
||||
var (
|
||||
composeProject docker.ComposeProject
|
||||
err error
|
||||
)
|
||||
err = yaml.Unmarshal(yml, &composeProject)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var res []request.Volume
|
||||
for _, service := range composeProject.Services {
|
||||
for _, volume := range service.Volumes {
|
||||
envArray := strings.Split(volume, ":")
|
||||
source := envArray[0]
|
||||
target := ""
|
||||
if len(envArray) > 1 {
|
||||
target = envArray[1]
|
||||
}
|
||||
res = append(res, request.Volume{
|
||||
Source: source,
|
||||
Target: target,
|
||||
})
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func checkRuntimePortExist(port int, scanPort bool, runtimeID uint) error {
|
||||
errMap := make(map[string]interface{})
|
||||
errMap["port"] = port
|
||||
appInstall, _ := appInstallRepo.GetFirst(appInstallRepo.WithPort(port))
|
||||
if appInstall.ID > 0 {
|
||||
errMap["type"] = i18n.GetMsgByKey("TYPE_APP")
|
||||
errMap["name"] = appInstall.Name
|
||||
return buserr.WithMap("ErrPortExist", errMap, nil)
|
||||
}
|
||||
opts := []repo.DBOption{runtimeRepo.WithPort(port)}
|
||||
if runtimeID > 0 {
|
||||
opts = append(opts, repo.WithByNOTID(runtimeID))
|
||||
}
|
||||
runtime, _ := runtimeRepo.GetFirst(context.Background(), opts...)
|
||||
if runtime != nil {
|
||||
errMap["type"] = i18n.GetMsgByKey("TYPE_RUNTIME")
|
||||
errMap["name"] = runtime.Name
|
||||
return buserr.WithMap("ErrPortExist", errMap, nil)
|
||||
}
|
||||
domain, _ := websiteDomainRepo.GetFirst(websiteDomainRepo.WithPort(port))
|
||||
if domain.ID > 0 {
|
||||
errMap["type"] = i18n.GetMsgByKey("TYPE_DOMAIN")
|
||||
errMap["name"] = domain.Domain
|
||||
return buserr.WithMap("ErrPortExist", errMap, nil)
|
||||
}
|
||||
if scanPort && common.ScanPort(port) {
|
||||
return buserr.WithDetail("ErrPortInUsed", port, nil)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getExtensionDir(version string) string {
|
||||
if strings.HasPrefix(version, "8.4") {
|
||||
return "no-debug-non-zts-20240924"
|
||||
}
|
||||
if strings.HasPrefix(version, "8.3") {
|
||||
return "no-debug-non-zts-20230831"
|
||||
}
|
||||
if strings.HasPrefix(version, "8.2") {
|
||||
return "no-debug-non-zts-20220829"
|
||||
}
|
||||
if strings.HasPrefix(version, "8.1") {
|
||||
return "no-debug-non-zts-20210902"
|
||||
}
|
||||
if strings.HasPrefix(version, "8.0") {
|
||||
return "no-debug-non-zts-20200930"
|
||||
}
|
||||
if strings.HasPrefix(version, "7.4") {
|
||||
return "no-debug-non-zts-20190902"
|
||||
}
|
||||
if strings.HasPrefix(version, "7.3") {
|
||||
return "no-debug-non-zts-20180731"
|
||||
}
|
||||
if strings.HasPrefix(version, "7.2") {
|
||||
return "no-debug-non-zts-20170718"
|
||||
}
|
||||
if strings.HasPrefix(version, "7.1") {
|
||||
return "no-debug-non-zts-20160303"
|
||||
}
|
||||
if strings.HasPrefix(version, "7.0") {
|
||||
return "no-debug-non-zts-20151012"
|
||||
}
|
||||
if strings.HasPrefix(version, "5.6") {
|
||||
return "no-debug-non-zts-20131226"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func RestartPHPRuntime() {
|
||||
runtimes, err := runtimeRepo.List(repo.WithByType(constant.RuntimePHP))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
websiteDir, _ := settingRepo.GetValueByKey("WEBSITE_DIR")
|
||||
for _, runtime := range runtimes {
|
||||
envs, err := gotenv.Unmarshal(runtime.Env)
|
||||
if err != nil {
|
||||
global.LOG.Warningf("restart php runtime failed %v", err)
|
||||
continue
|
||||
}
|
||||
envs["PANEL_WEBSITE_DIR"] = websiteDir
|
||||
if err = gotenv.Write(envs, runtime.GetEnvPath()); err != nil {
|
||||
global.LOG.Warningf("restart php runtime failed %v", err)
|
||||
continue
|
||||
}
|
||||
go func() {
|
||||
_ = restartRuntime(&runtime)
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func handleRuntimeDTO(res *response.RuntimeDTO, runtime model.Runtime) error {
|
||||
res.Params = make(map[string]interface{})
|
||||
envs, err := gotenv.Unmarshal(runtime.Env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for k, v := range envs {
|
||||
if strings.Contains(k, "CONTAINER_PORT") || strings.Contains(k, "HOST_PORT") {
|
||||
if strings.Contains(k, "CONTAINER_PORT") {
|
||||
r := regexp.MustCompile(`_(\d+)$`)
|
||||
matches := r.FindStringSubmatch(k)
|
||||
containerPort, err := strconv.Atoi(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hostPort, err := strconv.Atoi(envs[fmt.Sprintf("HOST_PORT_%s", matches[1])])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hostIP := envs[fmt.Sprintf("HOST_IP_%s", matches[1])]
|
||||
if hostIP == "" {
|
||||
hostIP = "0.0.0.0"
|
||||
}
|
||||
res.ExposedPorts = append(res.ExposedPorts, request.ExposedPort{
|
||||
ContainerPort: containerPort,
|
||||
HostPort: hostPort,
|
||||
HostIP: hostIP,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
res.Params[k] = v
|
||||
}
|
||||
}
|
||||
if v, ok := envs["CONTAINER_PACKAGE_URL"]; ok {
|
||||
res.Source = v
|
||||
}
|
||||
composeByte, err := files.NewFileOp().GetContent(runtime.GetComposePath())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res.Environments, err = getDockerComposeEnvironments(composeByte)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
volumes, err := getDockerComposeVolumes(composeByte)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defaultVolumes := make(map[string]string)
|
||||
switch runtime.Type {
|
||||
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimePython, constant.RuntimeDotNet:
|
||||
defaultVolumes = constant.RuntimeDefaultVolumes
|
||||
case constant.RuntimeGo:
|
||||
defaultVolumes = constant.GoDefaultVolumes
|
||||
case constant.RuntimePHP:
|
||||
defaultVolumes = constant.PHPDefaultVolumes
|
||||
}
|
||||
for _, volume := range volumes {
|
||||
exist := false
|
||||
for key, value := range defaultVolumes {
|
||||
if key == volume.Source && value == volume.Target {
|
||||
exist = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !exist {
|
||||
res.Volumes = append(res.Volumes, volume)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/encrypt"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/ssh"
|
||||
"github.com/jinzhu/copier"
|
||||
)
|
||||
|
||||
type SettingService struct{}
|
||||
|
||||
type ISettingService interface {
|
||||
GetSettingInfo() (*dto.SettingInfo, error)
|
||||
Update(key, value string) error
|
||||
|
||||
GetSSHInfo() (string, error)
|
||||
TestConnByInfo(req dto.SSHConnData) bool
|
||||
SaveConnInfo(req dto.SSHConnData) error
|
||||
GetSystemProxy() (*dto.SystemProxy, error)
|
||||
GetSettingByKey(key string) string
|
||||
}
|
||||
|
||||
func NewISettingService() ISettingService {
|
||||
return &SettingService{}
|
||||
}
|
||||
|
||||
func (u *SettingService) GetSettingInfo() (*dto.SettingInfo, error) {
|
||||
setting, err := settingRepo.GetList()
|
||||
if err != nil {
|
||||
return nil, buserr.New("ErrRecordNotFound")
|
||||
}
|
||||
settingMap := make(map[string]string)
|
||||
for _, set := range setting {
|
||||
settingMap[set.Key] = set.Value
|
||||
}
|
||||
var info dto.SettingInfo
|
||||
arr, err := json.Marshal(settingMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := json.Unmarshal(arr, &info); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info.LocalTime = time.Now().Format("2006-01-02 15:04:05 MST -0700")
|
||||
return &info, err
|
||||
}
|
||||
|
||||
func (u *SettingService) Update(key, value string) error {
|
||||
return settingRepo.UpdateOrCreate(key, value)
|
||||
}
|
||||
|
||||
func (u *SettingService) GetSSHInfo() (string, error) {
|
||||
conn, err := settingRepo.GetValueByKey("LocalSSHConn")
|
||||
if err != nil || len(conn) == 0 {
|
||||
return "", err
|
||||
}
|
||||
return encrypt.StringDecrypt(conn)
|
||||
}
|
||||
|
||||
func (u *SettingService) TestConnByInfo(req dto.SSHConnData) bool {
|
||||
if req.AuthMode == "password" && len(req.Password) != 0 {
|
||||
password, err := base64.StdEncoding.DecodeString(req.Password)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
req.Password = string(password)
|
||||
}
|
||||
if req.AuthMode == "key" && len(req.PrivateKey) != 0 {
|
||||
privateKey, err := base64.StdEncoding.DecodeString(req.PrivateKey)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
req.PrivateKey = string(privateKey)
|
||||
}
|
||||
|
||||
var connInfo ssh.ConnInfo
|
||||
_ = copier.Copy(&connInfo, &req)
|
||||
connInfo.PrivateKey = []byte(req.PrivateKey)
|
||||
if len(req.PassPhrase) != 0 {
|
||||
connInfo.PassPhrase = []byte(req.PassPhrase)
|
||||
}
|
||||
client, err := ssh.NewClient(connInfo)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer client.Close()
|
||||
return true
|
||||
}
|
||||
|
||||
func (u *SettingService) SaveConnInfo(req dto.SSHConnData) error {
|
||||
if req.AuthMode == "password" && len(req.Password) != 0 {
|
||||
password, err := base64.StdEncoding.DecodeString(req.Password)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Password = string(password)
|
||||
}
|
||||
if req.AuthMode == "key" && len(req.PrivateKey) != 0 {
|
||||
privateKey, err := base64.StdEncoding.DecodeString(req.PrivateKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.PrivateKey = string(privateKey)
|
||||
}
|
||||
|
||||
var connInfo ssh.ConnInfo
|
||||
_ = copier.Copy(&connInfo, &req)
|
||||
connInfo.PrivateKey = []byte(req.PrivateKey)
|
||||
if len(req.PassPhrase) != 0 {
|
||||
connInfo.PassPhrase = []byte(req.PassPhrase)
|
||||
}
|
||||
client, err := ssh.NewClient(connInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
localConn, _ := json.Marshal(&connInfo)
|
||||
connAfterEncrypt, _ := encrypt.StringEncrypt(string(localConn))
|
||||
_ = settingRepo.Update("LocalSSHConn", connAfterEncrypt)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *SettingService) GetSystemProxy() (*dto.SystemProxy, error) {
|
||||
systemProxy := dto.SystemProxy{}
|
||||
systemProxy.Type, _ = settingRepo.GetValueByKey("ProxyType")
|
||||
systemProxy.URL, _ = settingRepo.GetValueByKey("ProxyUrl")
|
||||
systemProxy.Port, _ = settingRepo.GetValueByKey("ProxyPort")
|
||||
systemProxy.User, _ = settingRepo.GetValueByKey("ProxyUser")
|
||||
passwd, _ := settingRepo.GetValueByKey("ProxyPasswd")
|
||||
systemProxy.Password, _ = encrypt.StringDecrypt(passwd)
|
||||
return &systemProxy, nil
|
||||
}
|
||||
|
||||
func (u *SettingService) GetSettingByKey(key string) string {
|
||||
switch key {
|
||||
case "SystemIP":
|
||||
value, _ := settingRepo.GetValueByKey(key)
|
||||
return value
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
@@ -1,405 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/docker"
|
||||
fileUtils "github.com/1Panel-dev/1Panel/agent/utils/files"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
"github.com/google/uuid"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/shirou/gopsutil/v3/host"
|
||||
)
|
||||
|
||||
type SnapshotService struct {
|
||||
OriginalPath string
|
||||
}
|
||||
|
||||
type ISnapshotService interface {
|
||||
SearchWithPage(req dto.PageSnapshot) (int64, interface{}, error)
|
||||
LoadSnapshotData() (dto.SnapshotData, error)
|
||||
SnapshotCreate(parentTask *task.Task, req dto.SnapshotCreate, jobID, retry, timeout uint) error
|
||||
SnapshotReCreate(id uint) error
|
||||
SnapshotRecover(req dto.SnapshotRecover) error
|
||||
SnapshotRollback(req dto.SnapshotRecover) error
|
||||
SnapshotImport(req dto.SnapshotImport) error
|
||||
Delete(req dto.SnapshotBatchDelete) error
|
||||
|
||||
UpdateDescription(req dto.UpdateDescription) error
|
||||
}
|
||||
|
||||
func NewISnapshotService() ISnapshotService {
|
||||
return &SnapshotService{}
|
||||
}
|
||||
|
||||
func (u *SnapshotService) SearchWithPage(req dto.PageSnapshot) (int64, interface{}, error) {
|
||||
total, records, err := snapshotRepo.Page(req.Page, req.PageSize, repo.WithByLikeName(req.Info), repo.WithOrderRuleBy(req.OrderBy, req.Order))
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
var datas []dto.SnapshotInfo
|
||||
for i := 0; i < len(records); i++ {
|
||||
var item dto.SnapshotInfo
|
||||
if err := copier.Copy(&item, &records[i]); err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
item.SourceAccounts, item.DownloadAccount, _ = loadBackupNamesByID(records[i].SourceAccountIDs, records[i].DownloadAccountID)
|
||||
datas = append(datas, item)
|
||||
}
|
||||
return total, datas, err
|
||||
}
|
||||
|
||||
func (u *SnapshotService) SnapshotImport(req dto.SnapshotImport) error {
|
||||
if len(req.Names) == 0 {
|
||||
return fmt.Errorf("incorrect snapshot request body: %v", req.Names)
|
||||
}
|
||||
|
||||
for _, snapName := range req.Names {
|
||||
if !strings.HasPrefix(snapName, "1panel-v2.") && !strings.HasPrefix(snapName, "snapshot-1panel-v2.") {
|
||||
return fmt.Errorf("incorrect snapshot name format of %s", snapName)
|
||||
}
|
||||
snap, _ := snapshotRepo.Get(repo.WithByName(strings.ReplaceAll(snapName, ".tar.gz", "")))
|
||||
if snap.ID != 0 {
|
||||
return buserr.New("ErrRecordExist")
|
||||
}
|
||||
}
|
||||
for _, snap := range req.Names {
|
||||
shortName := strings.TrimPrefix(snap, "snapshot_")
|
||||
nameItems := strings.Split(shortName, "-")
|
||||
if !strings.HasPrefix(shortName, "1panel-v") || !strings.HasSuffix(shortName, ".tar.gz") || len(nameItems) < 3 {
|
||||
return fmt.Errorf("incorrect snapshot name format of %s", shortName)
|
||||
}
|
||||
if strings.HasSuffix(snap, ".tar.gz") {
|
||||
snap = strings.ReplaceAll(snap, ".tar.gz", "")
|
||||
}
|
||||
itemSnap := model.Snapshot{
|
||||
Name: snap,
|
||||
SourceAccountIDs: fmt.Sprintf("%v", req.BackupAccountID),
|
||||
DownloadAccountID: req.BackupAccountID,
|
||||
Version: nameItems[1],
|
||||
Description: req.Description,
|
||||
Status: constant.StatusSuccess,
|
||||
}
|
||||
if err := snapshotRepo.Create(&itemSnap); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *SnapshotService) LoadSnapshotData() (dto.SnapshotData, error) {
|
||||
var (
|
||||
data dto.SnapshotData
|
||||
err error
|
||||
)
|
||||
fileOp := fileUtils.NewFileOp()
|
||||
data.AppData, err = loadApps(fileOp)
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
data.PanelData, err = loadPanelFile(fileOp)
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
itemBackups, err := loadFile(global.Dir.LocalBackupDir, 8, fileOp)
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
i := 0
|
||||
for _, item := range itemBackups {
|
||||
if item.Label != "app" && item.Label != "system_snapshot" {
|
||||
itemBackups[i] = item
|
||||
i++
|
||||
}
|
||||
}
|
||||
data.BackupData = itemBackups[:i]
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (u *SnapshotService) UpdateDescription(req dto.UpdateDescription) error {
|
||||
return snapshotRepo.Update(req.ID, map[string]interface{}{"description": req.Description})
|
||||
}
|
||||
|
||||
type SnapshotJson struct {
|
||||
BaseDir string `json:"baseDir"`
|
||||
OperestyDir string `json:"operestyDir"`
|
||||
BackupDataDir string `json:"backupDataDir"`
|
||||
Size uint64 `json:"size"`
|
||||
}
|
||||
|
||||
func (u *SnapshotService) Delete(req dto.SnapshotBatchDelete) error {
|
||||
snaps, _ := snapshotRepo.GetList(repo.WithByIDs(req.Ids))
|
||||
for _, snap := range snaps {
|
||||
if req.DeleteWithFile {
|
||||
accounts, err := NewBackupClientMap(strings.Split(snap.SourceAccountIDs, ","))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, item := range accounts {
|
||||
global.LOG.Debugf("remove snapshot file %s.tar.gz from %s", snap.Name, item.name)
|
||||
_, _ = item.client.Delete(path.Join(item.backupPath, "system_snapshot", snap.Name+".tar.gz"))
|
||||
}
|
||||
_ = backupRepo.DeleteRecord(context.Background(), repo.WithByType("snapshot"), backupRepo.WithByFileName(snap.Name+".tar.gz"))
|
||||
}
|
||||
|
||||
if err := snapshotRepo.Delete(repo.WithByID(snap.ID)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func hasOs(name string) bool {
|
||||
return strings.Contains(name, "amd64") ||
|
||||
strings.Contains(name, "arm64") ||
|
||||
strings.Contains(name, "armv7") ||
|
||||
strings.Contains(name, "ppc64le") ||
|
||||
strings.Contains(name, "s390x")
|
||||
}
|
||||
|
||||
func loadOs() string {
|
||||
hostInfo, _ := host.Info()
|
||||
switch hostInfo.KernelArch {
|
||||
case "x86_64":
|
||||
return "amd64"
|
||||
case "armv7l":
|
||||
return "armv7"
|
||||
default:
|
||||
return hostInfo.KernelArch
|
||||
}
|
||||
}
|
||||
|
||||
func loadApps(fileOp fileUtils.FileOp) ([]dto.DataTree, error) {
|
||||
var data []dto.DataTree
|
||||
apps, err := appInstallRepo.ListBy(context.Background())
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
openrestyID := 0
|
||||
for _, app := range apps {
|
||||
if app.App.Key == constant.AppOpenresty {
|
||||
openrestyID = int(app.ID)
|
||||
}
|
||||
}
|
||||
websites, err := websiteRepo.List()
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
appRelationMap := make(map[uint]uint)
|
||||
for _, website := range websites {
|
||||
if website.Type == constant.Deployment && website.AppInstallID != 0 {
|
||||
appRelationMap[uint(openrestyID)] = website.AppInstallID
|
||||
}
|
||||
}
|
||||
appRelations, err := appInstallResourceRepo.GetBy()
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
for _, relation := range appRelations {
|
||||
appRelationMap[uint(relation.AppInstallId)] = relation.LinkId
|
||||
}
|
||||
appMap := make(map[uint]string)
|
||||
for _, app := range apps {
|
||||
appMap[app.ID] = fmt.Sprintf("%s-%s", app.App.Key, app.Name)
|
||||
}
|
||||
|
||||
appTreeMap := make(map[string]dto.DataTree)
|
||||
for _, app := range apps {
|
||||
itemApp := dto.DataTree{
|
||||
ID: uuid.NewString(),
|
||||
Label: fmt.Sprintf("%s - %s", app.App.Name, app.Name),
|
||||
Key: app.App.Key,
|
||||
Name: app.Name,
|
||||
}
|
||||
appPath := path.Join(global.Dir.DataDir, "apps", app.App.Key, app.Name)
|
||||
itemAppData := dto.DataTree{ID: uuid.NewString(), Label: "appData", Key: app.App.Key, Name: app.Name, IsCheck: true, Path: appPath}
|
||||
if app.App.Key == constant.AppOpenresty && len(websites) != 0 {
|
||||
itemAppData.IsDisable = true
|
||||
}
|
||||
if val, ok := appRelationMap[app.ID]; ok {
|
||||
itemAppData.RelationItemID = appMap[val]
|
||||
}
|
||||
sizeItem, err := fileOp.GetDirSize(appPath)
|
||||
if err == nil {
|
||||
itemAppData.Size = uint64(sizeItem)
|
||||
}
|
||||
itemApp.Size += itemAppData.Size
|
||||
data = append(data, itemApp)
|
||||
appTreeMap[fmt.Sprintf("%s-%s", itemApp.Key, itemApp.Name)] = itemAppData
|
||||
}
|
||||
|
||||
for key, val := range appTreeMap {
|
||||
if valRelation, ok := appTreeMap[val.RelationItemID]; ok {
|
||||
valRelation.IsDisable = true
|
||||
appTreeMap[val.RelationItemID] = valRelation
|
||||
|
||||
val.RelationItemID = valRelation.ID
|
||||
appTreeMap[key] = val
|
||||
}
|
||||
}
|
||||
for i := 0; i < len(data); i++ {
|
||||
if val, ok := appTreeMap[fmt.Sprintf("%s-%s", data[i].Key, data[i].Name)]; ok {
|
||||
data[i].Children = append(data[i].Children, val)
|
||||
}
|
||||
}
|
||||
data = loadAppBackup(data, fileOp)
|
||||
data = loadAppImage(data)
|
||||
return data, nil
|
||||
}
|
||||
func loadAppBackup(list []dto.DataTree, fileOp fileUtils.FileOp) []dto.DataTree {
|
||||
for i := 0; i < len(list); i++ {
|
||||
appBackupPath := path.Join(global.Dir.LocalBackupDir, "app", list[i].Key, list[i].Name)
|
||||
itemAppBackupTree, err := loadFile(appBackupPath, 8, fileOp)
|
||||
itemAppBackup := dto.DataTree{ID: uuid.NewString(), Label: "appBackup", IsCheck: true, Children: itemAppBackupTree, Path: appBackupPath}
|
||||
if err == nil {
|
||||
backupSizeItem, err := fileOp.GetDirSize(appBackupPath)
|
||||
if err == nil {
|
||||
itemAppBackup.Size = uint64(backupSizeItem)
|
||||
list[i].Size += itemAppBackup.Size
|
||||
}
|
||||
list[i].Children = append(list[i].Children, itemAppBackup)
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
func loadAppImage(list []dto.DataTree) []dto.DataTree {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
global.LOG.Errorf("new docker client failed, err: %v", err)
|
||||
return list
|
||||
}
|
||||
defer client.Close()
|
||||
imageList, err := client.ImageList(context.Background(), image.ListOptions{})
|
||||
if err != nil {
|
||||
global.LOG.Errorf("load image list failed, err: %v", err)
|
||||
return list
|
||||
}
|
||||
|
||||
for i := 0; i < len(list); i++ {
|
||||
itemAppImage := dto.DataTree{ID: uuid.NewString(), Label: "appImage"}
|
||||
stdout, err := cmd.RunDefaultWithStdoutBashCf("cat %s | grep image: ", path.Join(global.Dir.AppDir, list[i].Key, list[i].Name, "docker-compose.yml"))
|
||||
if err != nil {
|
||||
list[i].Children = append(list[i].Children, itemAppImage)
|
||||
continue
|
||||
}
|
||||
itemAppImage.Name = strings.ReplaceAll(strings.ReplaceAll(strings.TrimSpace(stdout), "\n", ""), "image: ", "")
|
||||
for _, imageItem := range imageList {
|
||||
for _, tag := range imageItem.RepoTags {
|
||||
if tag == itemAppImage.Name {
|
||||
itemAppImage.Size = uint64(imageItem.Size)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
list[i].Children = append(list[i].Children, itemAppImage)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
func loadPanelFile(fileOp fileUtils.FileOp) ([]dto.DataTree, error) {
|
||||
var data []dto.DataTree
|
||||
snapFiles, err := os.ReadDir(global.Dir.DataDir)
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
for _, fileItem := range snapFiles {
|
||||
itemData := dto.DataTree{
|
||||
ID: uuid.NewString(),
|
||||
Label: fileItem.Name(),
|
||||
IsCheck: true,
|
||||
Path: path.Join(global.Dir.DataDir, fileItem.Name()),
|
||||
}
|
||||
switch itemData.Label {
|
||||
case "agent", "runtime", "docker", "task", "geo", "secret", "uploads":
|
||||
itemData.IsDisable = true
|
||||
case "clamav", "download":
|
||||
panelPath := path.Join(global.Dir.DataDir, itemData.Label)
|
||||
itemData.Children, _ = loadFile(panelPath, 3, fileOp)
|
||||
case "apps", "backup", "log", "db", "tmp":
|
||||
continue
|
||||
}
|
||||
if fileItem.IsDir() {
|
||||
sizeItem, err := fileOp.GetDirSize(path.Join(global.Dir.DataDir, itemData.Label))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
itemData.Size = uint64(sizeItem)
|
||||
} else {
|
||||
fileInfo, err := fileItem.Info()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
itemData.Size = uint64(fileInfo.Size())
|
||||
}
|
||||
if itemData.IsCheck && itemData.Size == 0 {
|
||||
itemData.IsCheck = false
|
||||
itemData.IsDisable = true
|
||||
}
|
||||
|
||||
data = append(data, itemData)
|
||||
}
|
||||
|
||||
openrestySite, _ := settingRepo.GetValueByKey("WEBSITE_DIR")
|
||||
if len(openrestySite) != 0 && !strings.Contains(openrestySite, global.Dir.DataDir) {
|
||||
sizeItem, _ := fileOp.GetDirSize(openrestySite)
|
||||
data = append(data, dto.DataTree{
|
||||
ID: uuid.NewString(),
|
||||
Label: "www",
|
||||
IsCheck: true,
|
||||
IsDisable: true,
|
||||
Path: openrestySite,
|
||||
Size: uint64(sizeItem),
|
||||
})
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func loadFile(pathItem string, index int, fileOp fileUtils.FileOp) ([]dto.DataTree, error) {
|
||||
var data []dto.DataTree
|
||||
snapFiles, err := os.ReadDir(pathItem)
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
for _, fileItem := range snapFiles {
|
||||
itemData := dto.DataTree{
|
||||
ID: uuid.NewString(),
|
||||
Label: fileItem.Name(),
|
||||
Name: fileItem.Name(),
|
||||
Path: path.Join(pathItem, fileItem.Name()),
|
||||
IsCheck: true,
|
||||
}
|
||||
if fileItem.IsDir() {
|
||||
sizeItem, err := fileOp.GetDirSize(path.Join(pathItem, itemData.Label))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
itemData.Size = uint64(sizeItem)
|
||||
if index > 1 {
|
||||
itemData.Children, _ = loadFile(path.Join(pathItem, itemData.Label), index-1, fileOp)
|
||||
}
|
||||
} else {
|
||||
fileInfo, err := fileItem.Info()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
itemData.Size = uint64(fileInfo.Size())
|
||||
}
|
||||
data = append(data, itemData)
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
@@ -1,554 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/copier"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/files"
|
||||
"github.com/pkg/errors"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func (u *SnapshotService) SnapshotCreate(parentTask *task.Task, req dto.SnapshotCreate, jobID, retry, timeout uint) error {
|
||||
versionItem, _ := settingRepo.Get(settingRepo.WithByKey("SystemVersion"))
|
||||
|
||||
scope := "core"
|
||||
if !global.IsMaster {
|
||||
scope = "agent"
|
||||
}
|
||||
if jobID == 0 {
|
||||
req.Name = fmt.Sprintf("1panel-%s-%s-linux-%s-%s", versionItem.Value, scope, loadOs(), time.Now().Format(constant.DateTimeSlimLayout))
|
||||
}
|
||||
appItem, _ := json.Marshal(req.AppData)
|
||||
panelItem, _ := json.Marshal(req.PanelData)
|
||||
backupItem, _ := json.Marshal(req.BackupData)
|
||||
snap := model.Snapshot{
|
||||
Name: req.Name,
|
||||
TaskID: req.TaskID,
|
||||
Secret: req.Secret,
|
||||
Description: req.Description,
|
||||
SourceAccountIDs: req.SourceAccountIDs,
|
||||
DownloadAccountID: req.DownloadAccountID,
|
||||
|
||||
AppData: string(appItem),
|
||||
PanelData: string(panelItem),
|
||||
BackupData: string(backupItem),
|
||||
WithMonitorData: req.WithMonitorData,
|
||||
WithLoginLog: req.WithLoginLog,
|
||||
WithOperationLog: req.WithOperationLog,
|
||||
WithTaskLog: req.WithTaskLog,
|
||||
WithSystemLog: req.WithSystemLog,
|
||||
|
||||
Version: versionItem.Value,
|
||||
Status: constant.StatusWaiting,
|
||||
}
|
||||
if err := snapshotRepo.Create(&snap); err != nil {
|
||||
global.LOG.Errorf("create snapshot record to db failed, err: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
req.ID = snap.ID
|
||||
var err error
|
||||
taskItem := parentTask
|
||||
if parentTask == nil {
|
||||
taskItem, err = task.NewTaskWithOps(req.Name, task.TaskCreate, task.TaskScopeSnapshot, req.TaskID, req.ID)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("new task for create snapshot failed, err: %v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
if jobID == 0 {
|
||||
go func() {
|
||||
_ = handleSnapshot(req, taskItem, jobID, 3, 0)
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
return handleSnapshot(req, taskItem, jobID, retry, timeout)
|
||||
}
|
||||
|
||||
func (u *SnapshotService) SnapshotReCreate(id uint) error {
|
||||
snap, err := snapshotRepo.Get(repo.WithByID(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
taskModel, err := taskRepo.GetFirst(taskRepo.WithResourceID(snap.ID), repo.WithByType(task.TaskScopeSnapshot))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var req dto.SnapshotCreate
|
||||
_ = copier.Copy(&req, snap)
|
||||
if err := json.Unmarshal([]byte(snap.PanelData), &req.PanelData); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := json.Unmarshal([]byte(snap.AppData), &req.AppData); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := json.Unmarshal([]byte(snap.BackupData), &req.BackupData); err != nil {
|
||||
return err
|
||||
}
|
||||
req.TaskID = taskModel.ID
|
||||
taskItem, err := task.NewTaskWithOps(req.Name, task.TaskCreate, task.TaskScopeSnapshot, req.TaskID, req.ID)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("new task for create snapshot failed, err: %v", err)
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
_ = handleSnapshot(req, taskItem, 0, 0, 0)
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleSnapshot(req dto.SnapshotCreate, taskItem *task.Task, jobID, retry, timeout uint) error {
|
||||
rootDir := path.Join(global.Dir.TmpDir, "system", req.Name)
|
||||
openrestyDir, _ := settingRepo.GetValueByKey("WEBSITE_DIR")
|
||||
itemHelper := snapHelper{SnapID: req.ID, Task: *taskItem, FileOp: files.NewFileOp(), Ctx: context.Background(), OpenrestyDir: openrestyDir}
|
||||
baseDir := path.Join(rootDir, "base")
|
||||
_ = os.MkdirAll(baseDir, os.ModePerm)
|
||||
|
||||
if timeout == 0 {
|
||||
timeout = 1800
|
||||
}
|
||||
taskItem.AddSubTaskWithAliasAndOps(
|
||||
"SnapDBInfo",
|
||||
func(t *task.Task) error {
|
||||
if err := loadDbConn(&itemHelper, rootDir, req); err != nil {
|
||||
return err
|
||||
}
|
||||
_ = itemHelper.snapAgentDB.Where("id = ?", req.ID).Delete(&model.Snapshot{}).Error
|
||||
if jobID != 0 {
|
||||
_ = itemHelper.snapAgentDB.Where("id = ?", jobID).Delete(&model.JobRecords{}).Error
|
||||
}
|
||||
return nil
|
||||
}, nil, int(retry), time.Duration(timeout)*time.Second,
|
||||
)
|
||||
|
||||
if len(req.InterruptStep) == 0 || req.InterruptStep == "SnapBaseInfo" {
|
||||
taskItem.AddSubTaskWithAliasAndOps(
|
||||
"SnapBaseInfo",
|
||||
func(t *task.Task) error { return snapBaseData(itemHelper, baseDir) },
|
||||
nil, int(retry), time.Duration(timeout)*time.Second,
|
||||
)
|
||||
req.InterruptStep = ""
|
||||
}
|
||||
if len(req.InterruptStep) == 0 || req.InterruptStep == "SnapInstallApp" {
|
||||
taskItem.AddSubTaskWithAliasAndOps(
|
||||
"SnapInstallApp",
|
||||
func(t *task.Task) error { return snapAppImage(itemHelper, req, rootDir) },
|
||||
nil, int(retry), time.Duration(timeout)*time.Second,
|
||||
)
|
||||
req.InterruptStep = ""
|
||||
}
|
||||
if len(req.InterruptStep) == 0 || req.InterruptStep == "SnapLocalBackup" {
|
||||
taskItem.AddSubTaskWithAliasAndOps(
|
||||
"SnapLocalBackup",
|
||||
func(t *task.Task) error { return snapBackupData(itemHelper, req, rootDir) },
|
||||
nil, int(retry), time.Duration(timeout)*time.Second,
|
||||
)
|
||||
req.InterruptStep = ""
|
||||
}
|
||||
if len(req.InterruptStep) == 0 || req.InterruptStep == "SnapPanelData" {
|
||||
taskItem.AddSubTaskWithAliasAndOps(
|
||||
"SnapPanelData",
|
||||
func(t *task.Task) error { return snapPanelData(itemHelper, req, rootDir) },
|
||||
nil, int(retry), time.Duration(timeout)*time.Second,
|
||||
)
|
||||
req.InterruptStep = ""
|
||||
}
|
||||
|
||||
taskItem.AddSubTaskWithAliasAndOps(
|
||||
"SnapCloseDBConn",
|
||||
func(t *task.Task) error {
|
||||
taskItem.Log("---------------------- 6 / 8 ----------------------")
|
||||
common.CloseDB(itemHelper.snapAgentDB)
|
||||
common.CloseDB(itemHelper.snapCoreDB)
|
||||
return nil
|
||||
}, nil, int(retry), time.Duration(timeout)*time.Second,
|
||||
)
|
||||
if len(req.InterruptStep) == 0 || req.InterruptStep == "SnapCompress" {
|
||||
taskItem.AddSubTaskWithAliasAndOps(
|
||||
"SnapCompress",
|
||||
func(t *task.Task) error { return snapCompress(itemHelper, rootDir, req.Secret) },
|
||||
nil, int(retry), time.Duration(timeout)*time.Second,
|
||||
)
|
||||
req.InterruptStep = ""
|
||||
}
|
||||
if len(req.InterruptStep) == 0 || req.InterruptStep == "SnapUpload" {
|
||||
taskItem.AddSubTaskWithAliasAndOps(
|
||||
"SnapUpload",
|
||||
func(t *task.Task) error {
|
||||
return snapUpload(itemHelper, req.SourceAccountIDs, fmt.Sprintf("%s.tar.gz", rootDir))
|
||||
}, nil, int(retry), time.Duration(timeout)*time.Second,
|
||||
)
|
||||
req.InterruptStep = ""
|
||||
}
|
||||
if err := taskItem.Execute(); err != nil {
|
||||
_ = snapshotRepo.Update(req.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error(), "interrupt_step": taskItem.Task.CurrentStep})
|
||||
return err
|
||||
}
|
||||
_ = snapshotRepo.Update(req.ID, map[string]interface{}{"status": constant.StatusSuccess, "interrupt_step": ""})
|
||||
_ = os.RemoveAll(rootDir)
|
||||
return nil
|
||||
}
|
||||
|
||||
type snapHelper struct {
|
||||
SnapID uint
|
||||
SnapName string
|
||||
snapAgentDB *gorm.DB
|
||||
snapCoreDB *gorm.DB
|
||||
Ctx context.Context
|
||||
FileOp files.FileOp
|
||||
Wg *sync.WaitGroup
|
||||
Task task.Task
|
||||
|
||||
OpenrestyDir string
|
||||
}
|
||||
|
||||
func loadDbConn(snap *snapHelper, targetDir string, req dto.SnapshotCreate) error {
|
||||
snap.Task.Log("---------------------- 1 / 8 ----------------------")
|
||||
snap.Task.LogStart(i18n.GetMsgByKey("SnapDBInfo"))
|
||||
pathDB := path.Join(global.Dir.DataDir, "db")
|
||||
|
||||
err := snap.FileOp.CopyDir(pathDB, targetDir)
|
||||
snap.Task.LogWithStatus(i18n.GetWithName("SnapCopy", pathDB), err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
agentDb, err := common.LoadDBConnByPathWithErr(path.Join(targetDir, "db/agent.db"), "agent.db")
|
||||
snap.Task.LogWithStatus(i18n.GetWithName("SnapNewDB", "agent"), err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
snap.snapAgentDB = agentDb
|
||||
coreDb, err := common.LoadDBConnByPathWithErr(path.Join(targetDir, "db/core.db"), "core.db")
|
||||
snap.Task.LogWithStatus(i18n.GetWithName("SnapNewDB", "core"), err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
snap.snapCoreDB = coreDb
|
||||
|
||||
if !req.WithMonitorData {
|
||||
err = os.Remove(path.Join(targetDir, "db/monitor.db"))
|
||||
snap.Task.LogWithStatus(i18n.GetMsgByKey("SnapDeleteMonitor"), err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if !req.WithTaskLog {
|
||||
err = os.Remove(path.Join(targetDir, "db/task.db"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
taskDB, err := common.LoadDBConnByPathWithErr(path.Join(targetDir, "db/task.db"), "core.db")
|
||||
snap.Task.LogWithStatus(i18n.GetWithName("SnapNewDB", "task"), err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = taskDB.Where("id = ?", req.TaskID).Delete(&model.Task{}).Error
|
||||
}
|
||||
if !req.WithOperationLog && global.IsMaster {
|
||||
err = snap.snapCoreDB.Exec("DELETE FROM operation_logs").Error
|
||||
snap.Task.LogWithStatus(i18n.GetMsgByKey("SnapDeleteOperationLog"), err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if !req.WithLoginLog && global.IsMaster {
|
||||
err = snap.snapCoreDB.Exec("DELETE FROM login_logs").Error
|
||||
snap.Task.LogWithStatus(i18n.GetMsgByKey("SnapDeleteLoginLog"), err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func snapBaseData(snap snapHelper, targetDir string) error {
|
||||
snap.Task.Log("---------------------- 2 / 8 ----------------------")
|
||||
snap.Task.LogStart(i18n.GetMsgByKey("SnapBaseInfo"))
|
||||
|
||||
if global.IsMaster {
|
||||
err := snap.FileOp.CopyFile("/usr/local/bin/1panel-core", targetDir)
|
||||
snap.Task.LogWithStatus(i18n.GetWithName("SnapCopy", "/usr/local/bin/1panel-core"), err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = snap.FileOp.CopyFile("/usr/local/bin/1pctl", targetDir)
|
||||
snap.Task.LogWithStatus(i18n.GetWithName("SnapCopy", "/usr/local/bin/1pctl"), err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err := snap.FileOp.CopyFile("/usr/local/bin/1panel-agent", targetDir)
|
||||
snap.Task.LogWithStatus(i18n.GetWithName("SnapCopy", "/usr/local/bin/1panel-agent"), err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if global.IsMaster {
|
||||
err = snap.FileOp.CopyFile("/etc/systemd/system/1panel-core.service", targetDir)
|
||||
snap.Task.LogWithStatus(i18n.GetWithName("SnapCopy", "/etc/systemd/system/1panel-core.service"), err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = snap.FileOp.CopyFile("/etc/systemd/system/1panel-agent.service", targetDir)
|
||||
snap.Task.LogWithStatus(i18n.GetWithName("SnapCopy", "/etc/systemd/system/1panel-agent.service"), err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if snap.FileOp.Stat("/etc/docker/daemon.json") {
|
||||
err = snap.FileOp.CopyFile("/etc/docker/daemon.json", targetDir)
|
||||
snap.Task.LogWithStatus(i18n.GetWithName("SnapCopy", "/etc/docker/daemon.json"), err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
remarkInfo, _ := json.MarshalIndent(SnapshotJson{
|
||||
BaseDir: global.Dir.BaseDir,
|
||||
OperestyDir: snap.OpenrestyDir,
|
||||
BackupDataDir: global.Dir.LocalBackupDir,
|
||||
}, "", "\t")
|
||||
err = os.WriteFile(path.Join(targetDir, "snapshot.json"), remarkInfo, 0640)
|
||||
snap.Task.LogWithStatus(i18n.GetWithName("SnapCopy", path.Join(targetDir, "snapshot.json")), err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func snapAppImage(snap snapHelper, req dto.SnapshotCreate, targetDir string) error {
|
||||
snap.Task.Log("---------------------- 3 / 8 ----------------------")
|
||||
snap.Task.LogStart(i18n.GetMsgByKey("SnapInstallApp"))
|
||||
|
||||
var appInstalls []model.AppInstall
|
||||
_ = snap.snapAgentDB.Where("1 = 1").Find(&appInstalls).Error
|
||||
for _, item := range appInstalls {
|
||||
if err := snap.snapAgentDB.
|
||||
Model(&model.AppInstall{}).
|
||||
Where("id = ?", item.ID).
|
||||
Updates(map[string]interface{}{"status": constant.StatusWaitingRestart}).
|
||||
Error; err != nil {
|
||||
global.LOG.Errorf("update app %s status failed, err: %v", item.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
var imageList []string
|
||||
existStr, _ := cmd.RunDefaultWithStdoutBashC("docker images | awk '{print $1\":\"$2}' | grep -v REPOSITORY:TAG")
|
||||
existImages := strings.Split(existStr, "\n")
|
||||
for _, app := range req.AppData {
|
||||
for _, item := range app.Children {
|
||||
if item.Label == "appImage" && item.IsCheck {
|
||||
for _, existImage := range existImages {
|
||||
if len(existImage) == 0 {
|
||||
continue
|
||||
}
|
||||
if existImage == item.Name {
|
||||
imageList = append(imageList, item.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
snap.Task.Log(strings.Join(imageList, " "))
|
||||
if len(imageList) != 0 {
|
||||
snap.Task.Logf("docker save %s | gzip -c > %s", strings.Join(imageList, " "), path.Join(targetDir, "images.tar.gz"))
|
||||
std, err := cmd.NewCommandMgr(cmd.WithTimeout(10*time.Minute)).RunWithStdoutBashCf("docker save %s | gzip -c > %s", strings.Join(imageList, " "), path.Join(targetDir, "images.tar.gz"))
|
||||
if err != nil {
|
||||
snap.Task.LogFailedWithErr(i18n.GetMsgByKey("SnapDockerSave"), errors.New(std))
|
||||
return errors.New(std)
|
||||
}
|
||||
snap.Task.LogSuccess(i18n.GetMsgByKey("SnapDockerSave"))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func snapBackupData(snap snapHelper, req dto.SnapshotCreate, targetDir string) error {
|
||||
snap.Task.Log("---------------------- 4 / 8 ----------------------")
|
||||
snap.Task.LogStart(i18n.GetMsgByKey("SnapLocalBackup"))
|
||||
|
||||
excludes := loadBackupExcludes(snap, req.BackupData)
|
||||
excludes = append(excludes, "./system_snapshot")
|
||||
for _, item := range req.AppData {
|
||||
for _, itemApp := range item.Children {
|
||||
if itemApp.Label == "appBackup" {
|
||||
excludes = append(excludes, loadAppBackupExcludes([]dto.DataTree{itemApp})...)
|
||||
}
|
||||
}
|
||||
}
|
||||
err := snap.FileOp.TarGzCompressPro(false, global.Dir.LocalBackupDir, path.Join(targetDir, "1panel_backup.tar.gz"), "", strings.Join(excludes, ","))
|
||||
snap.Task.LogWithStatus(i18n.GetMsgByKey("SnapCompressBackup"), err)
|
||||
|
||||
return err
|
||||
}
|
||||
func loadBackupExcludes(snap snapHelper, req []dto.DataTree) []string {
|
||||
var excludes []string
|
||||
for _, item := range req {
|
||||
if len(item.Children) == 0 {
|
||||
if item.IsCheck {
|
||||
continue
|
||||
}
|
||||
if err := snap.snapAgentDB.Where("file_dir = ? AND file_name = ?", strings.TrimPrefix(path.Dir(item.Path), global.Dir.LocalBackupDir+"/"), path.Base(item.Path)).Delete(&model.BackupRecord{}).Error; err != nil {
|
||||
snap.Task.LogWithStatus("delete backup file from database", err)
|
||||
}
|
||||
excludes = append(excludes, "."+strings.TrimPrefix(item.Path, global.Dir.LocalBackupDir))
|
||||
} else {
|
||||
excludes = append(excludes, loadBackupExcludes(snap, item.Children)...)
|
||||
}
|
||||
}
|
||||
return excludes
|
||||
}
|
||||
func loadAppBackupExcludes(req []dto.DataTree) []string {
|
||||
var excludes []string
|
||||
for _, item := range req {
|
||||
if len(item.Children) == 0 {
|
||||
if !item.IsCheck {
|
||||
excludes = append(excludes, "."+strings.TrimPrefix(item.Path, path.Join(global.Dir.LocalBackupDir)))
|
||||
}
|
||||
} else {
|
||||
excludes = append(excludes, loadAppBackupExcludes(item.Children)...)
|
||||
}
|
||||
}
|
||||
return excludes
|
||||
}
|
||||
|
||||
func snapPanelData(snap snapHelper, req dto.SnapshotCreate, targetDir string) error {
|
||||
snap.Task.Log("---------------------- 5 / 8 ----------------------")
|
||||
snap.Task.LogStart(i18n.GetMsgByKey("SnapPanelData"))
|
||||
|
||||
excludes := loadPanelExcludes(req.PanelData)
|
||||
for _, item := range req.AppData {
|
||||
for _, itemApp := range item.Children {
|
||||
if itemApp.Label == "appData" {
|
||||
excludes = append(excludes, loadPanelExcludes([]dto.DataTree{itemApp})...)
|
||||
}
|
||||
}
|
||||
}
|
||||
excludes = append(excludes, "./cache")
|
||||
excludes = append(excludes, "./db")
|
||||
excludes = append(excludes, "./tmp")
|
||||
if !req.WithSystemLog {
|
||||
excludes = append(excludes, "./log/1Panel*")
|
||||
}
|
||||
if !req.WithTaskLog {
|
||||
excludes = append(excludes, "./log/task")
|
||||
}
|
||||
|
||||
rootDir := global.Dir.DataDir
|
||||
if strings.Contains(global.Dir.LocalBackupDir, rootDir) {
|
||||
excludes = append(excludes, "."+strings.ReplaceAll(global.Dir.LocalBackupDir, rootDir, ""))
|
||||
}
|
||||
if len(snap.OpenrestyDir) != 0 && strings.Contains(snap.OpenrestyDir, rootDir) {
|
||||
excludes = append(excludes, "."+strings.ReplaceAll(snap.OpenrestyDir, rootDir, ""))
|
||||
}
|
||||
ignoreVal, _ := settingRepo.Get(settingRepo.WithByKey("SnapshotIgnore"))
|
||||
rules := strings.Split(ignoreVal.Value, ",")
|
||||
for _, ignore := range rules {
|
||||
if len(ignore) == 0 || cmd.CheckIllegal(ignore) {
|
||||
continue
|
||||
}
|
||||
excludes = append(excludes, "."+strings.ReplaceAll(ignore, rootDir, ""))
|
||||
}
|
||||
err := snap.FileOp.TarGzCompressPro(false, rootDir, path.Join(targetDir, "1panel_data.tar.gz"), "", strings.Join(excludes, ","))
|
||||
snap.Task.LogWithStatus(i18n.GetMsgByKey("SnapCompressPanel"), err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(snap.OpenrestyDir) != 0 {
|
||||
err := snap.FileOp.TarGzCompressPro(false, snap.OpenrestyDir, path.Join(targetDir, "website.tar.gz"), "", "")
|
||||
snap.Task.LogWithStatus(i18n.GetMsgByKey("SnapWebsite"), err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
func loadPanelExcludes(req []dto.DataTree) []string {
|
||||
var excludes []string
|
||||
for _, item := range req {
|
||||
if len(item.Children) == 0 {
|
||||
if !item.IsCheck {
|
||||
excludes = append(excludes, "."+strings.TrimPrefix(item.Path, path.Join(global.Dir.BaseDir, "1panel")))
|
||||
}
|
||||
} else {
|
||||
excludes = append(excludes, loadPanelExcludes(item.Children)...)
|
||||
}
|
||||
}
|
||||
return excludes
|
||||
}
|
||||
|
||||
func snapCompress(snap snapHelper, rootDir string, secret string) error {
|
||||
snap.Task.Log("---------------------- 7 / 8 ----------------------")
|
||||
snap.Task.LogStart(i18n.GetMsgByKey("SnapCompress"))
|
||||
|
||||
tmpDir := path.Join(global.Dir.TmpDir, "system")
|
||||
fileName := fmt.Sprintf("%s.tar.gz", path.Base(rootDir))
|
||||
err := snap.FileOp.TarGzCompressPro(true, rootDir, path.Join(tmpDir, fileName), secret, "")
|
||||
snap.Task.LogWithStatus(i18n.GetMsgByKey("SnapCompressFile"), err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stat, err := os.Stat(path.Join(tmpDir, fileName))
|
||||
snap.Task.LogWithStatus(i18n.GetMsgByKey("SnapCheckCompress"), err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
size := common.LoadSizeUnit2F(float64(stat.Size()))
|
||||
snap.Task.Log(i18n.GetWithName("SnapCompressSize", size))
|
||||
_ = os.RemoveAll(rootDir)
|
||||
return nil
|
||||
}
|
||||
|
||||
func snapUpload(snap snapHelper, accounts string, file string) error {
|
||||
snap.Task.Log("---------------------- 8 / 8 ----------------------")
|
||||
snap.Task.LogStart(i18n.GetMsgByKey("SnapUpload"))
|
||||
|
||||
source := path.Join(global.Dir.TmpDir, "system", path.Base(file))
|
||||
accountMap, err := NewBackupClientMap(strings.Split(accounts, ","))
|
||||
snap.Task.LogWithStatus(i18n.GetMsgByKey("SnapLoadBackup"), err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
targetAccounts := strings.Split(accounts, ",")
|
||||
for _, item := range targetAccounts {
|
||||
snap.Task.LogStart(i18n.GetWithName("SnapUploadTo", fmt.Sprintf("[%s] %s", accountMap[item].name, path.Join("system_snapshot", path.Base(file)))))
|
||||
_, err := accountMap[item].client.Upload(source, path.Join(accountMap[item].backupPath, "system_snapshot", path.Base(file)))
|
||||
snap.Task.LogWithStatus(i18n.GetWithName("SnapUploadRes", accountMap[item].name), err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
_ = os.Remove(source)
|
||||
return nil
|
||||
}
|
||||
@@ -1,411 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/files"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type snapRecoverHelper struct {
|
||||
FileOp files.FileOp
|
||||
Task *task.Task
|
||||
}
|
||||
|
||||
func (u *SnapshotService) SnapshotRecover(req dto.SnapshotRecover) error {
|
||||
global.LOG.Info("start to recover panel by snapshot now")
|
||||
snap, err := snapshotRepo.Get(repo.WithByID(req.ID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if hasOs(snap.Name) && !strings.Contains(snap.Name, loadOs()) {
|
||||
errInfo := fmt.Sprintf("restoring snapshots(%s) between different server architectures(%s) is not supported", snap.Name, loadOs())
|
||||
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"recover_status": constant.StatusFailed, "recover_message": errInfo})
|
||||
return errors.New(errInfo)
|
||||
}
|
||||
if len(snap.RollbackStatus) != 0 && snap.RollbackStatus != constant.StatusSuccess {
|
||||
req.IsNew = true
|
||||
}
|
||||
if !req.IsNew && (snap.InterruptStep == "RecoverDownload" || snap.InterruptStep == "RecoverDecompress" || snap.InterruptStep == "BackupBeforeRecover") {
|
||||
req.IsNew = true
|
||||
}
|
||||
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"recover_status": constant.StatusWaiting})
|
||||
_ = settingRepo.Update("SystemStatus", "Recovering")
|
||||
|
||||
if len(snap.InterruptStep) == 0 {
|
||||
req.IsNew = true
|
||||
}
|
||||
if len(snap.TaskRecoverID) != 0 {
|
||||
req.TaskID = snap.TaskRecoverID
|
||||
} else {
|
||||
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"task_recover_id": req.TaskID})
|
||||
}
|
||||
taskItem, err := task.NewTaskWithOps(snap.Name, task.TaskRecover, task.TaskScopeSnapshot, req.TaskID, snap.ID)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("new task for create snapshot failed, err: %v", err)
|
||||
return err
|
||||
}
|
||||
rootDir := path.Join(global.Dir.TmpDir, "system", snap.Name)
|
||||
if _, err := os.Stat(rootDir); err != nil && os.IsNotExist(err) {
|
||||
_ = os.MkdirAll(rootDir, os.ModePerm)
|
||||
}
|
||||
itemHelper := snapRecoverHelper{Task: taskItem, FileOp: files.NewFileOp()}
|
||||
|
||||
go func() {
|
||||
_ = global.Cron.Stop()
|
||||
defer func() {
|
||||
global.Cron.Start()
|
||||
}()
|
||||
|
||||
if req.IsNew || snap.InterruptStep == "RecoverDownload" || req.ReDownload {
|
||||
taskItem.AddSubTaskWithAlias(
|
||||
"RecoverDownload",
|
||||
func(t *task.Task) error { return handleDownloadSnapshot(&itemHelper, snap, rootDir) },
|
||||
nil,
|
||||
)
|
||||
req.IsNew = true
|
||||
}
|
||||
if req.IsNew || snap.InterruptStep == "RecoverDecompress" {
|
||||
taskItem.AddSubTaskWithAlias(
|
||||
"RecoverDecompress",
|
||||
func(t *task.Task) error {
|
||||
itemHelper.Task.Log("---------------------- 2 / 10 ----------------------")
|
||||
itemHelper.Task.LogStart(i18n.GetWithName("RecoverDecompress", snap.Name))
|
||||
err := itemHelper.FileOp.TarGzExtractPro(fmt.Sprintf("%s/%s.tar.gz", rootDir, snap.Name), rootDir, req.Secret)
|
||||
itemHelper.Task.LogWithStatus(i18n.GetMsgByKey("Decompress"), err)
|
||||
return err
|
||||
},
|
||||
nil,
|
||||
)
|
||||
req.IsNew = true
|
||||
}
|
||||
if req.IsNew || snap.InterruptStep == "BackupBeforeRecover" {
|
||||
taskItem.AddSubTaskWithAlias(
|
||||
"BackupBeforeRecover",
|
||||
func(t *task.Task) error { return backupBeforeRecover(snap.Name, &itemHelper) },
|
||||
nil,
|
||||
)
|
||||
req.IsNew = true
|
||||
}
|
||||
|
||||
var snapJson SnapshotJson
|
||||
taskItem.AddSubTaskWithAlias(
|
||||
"Readjson",
|
||||
func(t *task.Task) error {
|
||||
snapJson, err = readFromJson(path.Join(rootDir, snap.Name), &itemHelper)
|
||||
return err
|
||||
},
|
||||
nil,
|
||||
)
|
||||
if req.IsNew || snap.InterruptStep == "RecoverApp" {
|
||||
taskItem.AddSubTaskWithAlias(
|
||||
"RecoverApp",
|
||||
func(t *task.Task) error { return recoverAppData(path.Join(rootDir, snap.Name), &itemHelper) },
|
||||
nil,
|
||||
)
|
||||
req.IsNew = true
|
||||
}
|
||||
if req.IsNew || snap.InterruptStep == "RecoverBaseData" {
|
||||
taskItem.AddSubTaskWithAlias(
|
||||
"RecoverBaseData",
|
||||
func(t *task.Task) error { return recoverBaseData(path.Join(rootDir, snap.Name, "base"), &itemHelper) },
|
||||
nil,
|
||||
)
|
||||
req.IsNew = true
|
||||
}
|
||||
if req.IsNew || snap.InterruptStep == "RecoverDBData" {
|
||||
taskItem.AddSubTaskWithAlias(
|
||||
"RecoverDBData",
|
||||
func(t *task.Task) error { return recoverDBData(path.Join(rootDir, snap.Name, "db"), &itemHelper) },
|
||||
nil,
|
||||
)
|
||||
req.IsNew = true
|
||||
}
|
||||
if req.IsNew || snap.InterruptStep == "RecoverBackups" {
|
||||
taskItem.AddSubTaskWithAlias(
|
||||
"RecoverBackups",
|
||||
func(t *task.Task) error {
|
||||
itemHelper.Task.Log("---------------------- 8 / 10 ----------------------")
|
||||
itemHelper.Task.LogStart(i18n.GetWithName("RecoverBackups", snap.Name))
|
||||
err := itemHelper.FileOp.TarGzExtractPro(path.Join(rootDir, snap.Name, "/1panel_backup.tar.gz"), snapJson.BackupDataDir, "")
|
||||
itemHelper.Task.LogWithStatus(i18n.GetMsgByKey("Decompress"), err)
|
||||
return err
|
||||
},
|
||||
nil,
|
||||
)
|
||||
req.IsNew = true
|
||||
}
|
||||
if req.IsNew || snap.InterruptStep == "RecoverPanelData" {
|
||||
taskItem.AddSubTaskWithAlias(
|
||||
"RecoverPanelData",
|
||||
func(t *task.Task) error {
|
||||
itemHelper.Task.Log("---------------------- 9 / 10 ----------------------")
|
||||
itemHelper.Task.LogStart(i18n.GetWithName("RecoverPanelData", snap.Name))
|
||||
err := itemHelper.FileOp.TarGzExtractPro(path.Join(rootDir, snap.Name, "/1panel_data.tar.gz"), path.Join(snapJson.BaseDir, "1panel"), "")
|
||||
itemHelper.Task.LogWithStatus(i18n.GetMsgByKey("Decompress"), err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(snapJson.OperestyDir) != 0 {
|
||||
err := itemHelper.FileOp.TarGzExtractPro(path.Join(rootDir, snap.Name, "/website.tar.gz"), snapJson.OperestyDir, "")
|
||||
itemHelper.Task.LogWithStatus(i18n.GetMsgByKey("RecoverWebsite"), err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return err
|
||||
},
|
||||
nil,
|
||||
)
|
||||
req.IsNew = true
|
||||
}
|
||||
taskItem.AddSubTaskWithAlias(
|
||||
"RecoverDBData",
|
||||
func(t *task.Task) error {
|
||||
return restartCompose(path.Join(snapJson.BaseDir, "1panel/docker/compose"), &itemHelper)
|
||||
},
|
||||
nil,
|
||||
)
|
||||
|
||||
if err := taskItem.Execute(); err != nil {
|
||||
_ = settingRepo.Update("SystemStatus", "Free")
|
||||
_ = snapshotRepo.Update(req.ID, map[string]interface{}{"recover_status": constant.StatusFailed, "recover_message": err.Error(), "interrupt_step": taskItem.Task.CurrentStep})
|
||||
return
|
||||
}
|
||||
_ = os.RemoveAll(rootDir)
|
||||
common.RestartService(true, true, true)
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleDownloadSnapshot(itemHelper *snapRecoverHelper, snap model.Snapshot, targetDir string) error {
|
||||
itemHelper.Task.Log("---------------------- 1 / 10 ----------------------")
|
||||
itemHelper.Task.LogStart(i18n.GetMsgByKey("RecoverDownload"))
|
||||
|
||||
account, client, err := NewBackupClientWithID(snap.DownloadAccountID)
|
||||
itemHelper.Task.LogWithStatus(i18n.GetWithName("RecoverDownloadAccount", fmt.Sprintf("%s - %s", account.Type, account.Name)), err)
|
||||
targetPath := ""
|
||||
if len(account.BackupPath) != 0 {
|
||||
targetPath = path.Join(account.BackupPath, fmt.Sprintf("system_snapshot/%s.tar.gz", snap.Name))
|
||||
} else {
|
||||
targetPath = fmt.Sprintf("system_snapshot/%s.tar.gz", snap.Name)
|
||||
}
|
||||
filePath := fmt.Sprintf("%s/%s.tar.gz", targetDir, snap.Name)
|
||||
_ = os.RemoveAll(filePath)
|
||||
_, err = client.Download(targetPath, filePath)
|
||||
itemHelper.Task.LogWithStatus(i18n.GetMsgByKey("Download"), err)
|
||||
return err
|
||||
}
|
||||
|
||||
func backupBeforeRecover(name string, itemHelper *snapRecoverHelper) error {
|
||||
itemHelper.Task.Log("---------------------- 3 / 10 ----------------------")
|
||||
itemHelper.Task.LogStart(i18n.GetMsgByKey("BackupBeforeRecover"))
|
||||
|
||||
rootDir := fmt.Sprintf("%s/1panel_original/original_%s", global.Dir.BaseDir, name)
|
||||
baseDir := path.Join(rootDir, "base")
|
||||
if _, err := os.Stat(baseDir); err != nil {
|
||||
_ = os.MkdirAll(baseDir, os.ModePerm)
|
||||
}
|
||||
|
||||
err := itemHelper.FileOp.CopyDirWithExclude(global.Dir.DataDir, rootDir, []string{"cache", "tmp"})
|
||||
itemHelper.Task.LogWithStatus(i18n.GetWithName("SnapCopy", global.Dir.DataDir), err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
openrestyDir, _ := settingRepo.GetValueByKey("WEBSITE_DIR")
|
||||
if len(openrestyDir) != 0 && !strings.Contains(openrestyDir, global.Dir.DataDir) {
|
||||
err := itemHelper.FileOp.CopyDirWithExclude(openrestyDir, rootDir, nil)
|
||||
itemHelper.Task.LogWithStatus(i18n.GetWithName("SnapCopy", openrestyDir), err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(global.Dir.LocalBackupDir) != 0 && !strings.Contains(global.Dir.LocalBackupDir, global.Dir.DataDir) {
|
||||
err = itemHelper.FileOp.CopyDirWithExclude(global.Dir.LocalBackupDir, rootDir, []string{"system_snapshot"})
|
||||
itemHelper.Task.LogWithStatus(i18n.GetWithName("SnapCopy", global.Dir.LocalBackupDir), err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if global.IsMaster {
|
||||
err = itemHelper.FileOp.CopyFile("/usr/local/bin/1pctl", baseDir)
|
||||
itemHelper.Task.LogWithStatus(i18n.GetWithName("SnapCopy", "/usr/local/bin/1pctl"), err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = itemHelper.FileOp.CopyFile("/usr/local/bin/1panel-core", baseDir)
|
||||
itemHelper.Task.LogWithStatus(i18n.GetWithName("SnapCopy", "/usr/local/bin/1panel-core"), err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = itemHelper.FileOp.CopyFile("/etc/systemd/system/1panel-core.service", baseDir)
|
||||
itemHelper.Task.LogWithStatus(i18n.GetWithName("SnapCopy", "/etc/systemd/system/1panel-core.service"), err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = itemHelper.FileOp.CopyFile("/usr/local/bin/1panel-agent", baseDir)
|
||||
itemHelper.Task.LogWithStatus(i18n.GetWithName("SnapCopy", "/usr/local/bin/1panel-agent"), err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = itemHelper.FileOp.CopyFile("/etc/systemd/system/1panel-agent.service", baseDir)
|
||||
itemHelper.Task.LogWithStatus(i18n.GetWithName("SnapCopy", "/etc/systemd/system/1panel-agent.service"), err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if itemHelper.FileOp.Stat("/etc/docker/daemon.json") {
|
||||
err = itemHelper.FileOp.CopyFile("/etc/docker/daemon.json", baseDir)
|
||||
itemHelper.Task.LogWithStatus(i18n.GetWithName("SnapCopy", "/etc/docker/daemon.json"), err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func readFromJson(rootDir string, itemHelper *snapRecoverHelper) (SnapshotJson, error) {
|
||||
itemHelper.Task.Log("---------------------- 4 / 10 ----------------------")
|
||||
itemHelper.Task.LogStart(i18n.GetMsgByKey("Readjson"))
|
||||
|
||||
snapJsonPath := path.Join(rootDir, "base/snapshot.json")
|
||||
var snap SnapshotJson
|
||||
_, err := os.Stat(snapJsonPath)
|
||||
itemHelper.Task.LogWithStatus(i18n.GetMsgByKey("ReadjsonPath"), err)
|
||||
if err != nil {
|
||||
return snap, err
|
||||
}
|
||||
fileByte, err := os.ReadFile(snapJsonPath)
|
||||
itemHelper.Task.LogWithStatus(i18n.GetMsgByKey("ReadjsonContent"), err)
|
||||
if err != nil {
|
||||
return snap, err
|
||||
}
|
||||
err = json.Unmarshal(fileByte, &snap)
|
||||
itemHelper.Task.LogWithStatus(i18n.GetMsgByKey("ReadjsonMarshal"), err)
|
||||
if err != nil {
|
||||
return snap, err
|
||||
}
|
||||
return snap, nil
|
||||
}
|
||||
|
||||
func recoverAppData(src string, itemHelper *snapRecoverHelper) error {
|
||||
itemHelper.Task.Log("---------------------- 5 / 10 ----------------------")
|
||||
itemHelper.Task.LogStart(i18n.GetMsgByKey("RecoverApp"))
|
||||
|
||||
if _, err := os.Stat(path.Join(src, "images.tar.gz")); err != nil {
|
||||
itemHelper.Task.Log(i18n.GetMsgByKey("RecoverAppEmpty"))
|
||||
return nil
|
||||
}
|
||||
std, err := cmd.NewCommandMgr(cmd.WithTimeout(10*time.Minute)).RunWithStdoutBashCf("docker load < %s", path.Join(src, "images.tar.gz"))
|
||||
if err != nil {
|
||||
itemHelper.Task.LogFailedWithErr(i18n.GetMsgByKey("RecoverAppImage"), errors.New(std))
|
||||
return fmt.Errorf("docker load images failed, err: %v", err)
|
||||
}
|
||||
itemHelper.Task.LogSuccess(i18n.GetMsgByKey("RecoverAppImage"))
|
||||
return nil
|
||||
}
|
||||
|
||||
func recoverBaseData(src string, itemHelper *snapRecoverHelper) error {
|
||||
itemHelper.Task.Log("---------------------- 6 / 10 ----------------------")
|
||||
itemHelper.Task.LogStart(i18n.GetMsgByKey("SnapBaseInfo"))
|
||||
|
||||
if global.IsMaster {
|
||||
err := itemHelper.FileOp.CopyFile(path.Join(src, "1pctl"), "/usr/local/bin")
|
||||
itemHelper.Task.LogWithStatus(i18n.GetWithName("SnapCopy", "/usr/local/bin/1pctl"), err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = itemHelper.FileOp.CopyFile(path.Join(src, "1panel-core"), "/usr/local/bin")
|
||||
itemHelper.Task.LogWithStatus(i18n.GetWithName("SnapCopy", "/usr/local/bin/1panel-core"), err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = itemHelper.FileOp.CopyFile(path.Join(src, "1panel-core.service"), "/etc/systemd/system")
|
||||
itemHelper.Task.LogWithStatus(i18n.GetWithName("SnapCopy", "/etc/systemd/system/1panel-core.service"), err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err := itemHelper.FileOp.CopyFile(path.Join(src, "1panel-agent"), "/usr/local/bin")
|
||||
itemHelper.Task.LogWithStatus(i18n.GetWithName("SnapCopy", "/usr/local/bin/1panel-agent"), err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = itemHelper.FileOp.CopyFile(path.Join(src, "1panel-agent.service"), "/etc/systemd/system")
|
||||
itemHelper.Task.LogWithStatus(i18n.GetWithName("SnapCopy", "/etc/systemd/system/1panel-agent.service"), err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
daemonJsonPath := "/etc/docker/daemon.json"
|
||||
_, errSrc := os.Stat(path.Join(src, "docker/daemon.json"))
|
||||
_, errPath := os.Stat(daemonJsonPath)
|
||||
if os.IsNotExist(errSrc) && os.IsNotExist(errPath) {
|
||||
itemHelper.Task.Log(i18n.GetMsgByKey("RecoverDaemonJsonEmpty"))
|
||||
return nil
|
||||
}
|
||||
if errSrc == nil {
|
||||
err = itemHelper.FileOp.CopyFile(path.Join(src, "docker/daemon.json"), "/etc/docker")
|
||||
itemHelper.Task.Log(i18n.GetMsgByKey("RecoverDaemonJson"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("recover docker daemon.json failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
_, _ = cmd.RunDefaultWithStdoutBashC("systemctl restart docker")
|
||||
return nil
|
||||
}
|
||||
|
||||
func recoverDBData(src string, itemHelper *snapRecoverHelper) error {
|
||||
itemHelper.Task.Log("---------------------- 7 / 10 ----------------------")
|
||||
itemHelper.Task.LogStart(i18n.GetMsgByKey("RecoverDBData"))
|
||||
err := itemHelper.FileOp.CopyDirWithExclude(src, global.Dir.DataDir, nil)
|
||||
|
||||
itemHelper.Task.LogWithStatus(i18n.GetMsgByKey("RecoverDBData"), err)
|
||||
return err
|
||||
}
|
||||
|
||||
func restartCompose(composePath string, itemHelper *snapRecoverHelper) error {
|
||||
itemHelper.Task.Log("---------------------- 10 / 10 ----------------------")
|
||||
itemHelper.Task.LogStart(i18n.GetMsgByKey("RecoverCompose"))
|
||||
|
||||
composes, err := composeRepo.ListRecord()
|
||||
itemHelper.Task.LogWithStatus(i18n.GetMsgByKey("RecoverComposeList"), err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, compose := range composes {
|
||||
pathItem := path.Join(composePath, compose.Name, "docker-compose.yml")
|
||||
if _, err := os.Stat(pathItem); err != nil {
|
||||
continue
|
||||
}
|
||||
upCmd := fmt.Sprintf("docker compose -f %s up -d", pathItem)
|
||||
stdout, err := cmd.RunDefaultWithStdoutBashC(upCmd)
|
||||
if err != nil {
|
||||
itemHelper.Task.LogFailedWithErr(i18n.GetMsgByKey("RecoverCompose"), errors.New(stdout))
|
||||
continue
|
||||
}
|
||||
itemHelper.Task.LogSuccess(i18n.GetWithName("RecoverComposeItem", pathItem))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/files"
|
||||
)
|
||||
|
||||
func (u *SnapshotService) SnapshotRollback(req dto.SnapshotRecover) error {
|
||||
global.LOG.Info("start to rollback now")
|
||||
snap, err := snapshotRepo.Get(repo.WithByID(req.ID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(snap.TaskRollbackID) != 0 {
|
||||
req.TaskID = snap.TaskRollbackID
|
||||
} else {
|
||||
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"task_rollback_id": req.TaskID})
|
||||
}
|
||||
taskItem, err := task.NewTaskWithOps(snap.Name, task.TaskRollback, task.TaskScopeSnapshot, req.TaskID, snap.ID)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("new task for create snapshot failed, err: %v", err)
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
rootDir := fmt.Sprintf("%s/1panel_original/original_%s", global.Dir.BaseDir, snap.Name)
|
||||
baseDir := path.Join(rootDir, "base")
|
||||
|
||||
FileOp := files.NewFileOp()
|
||||
taskItem.AddSubTask(
|
||||
i18n.GetWithName("SnapCopy", "/usr/local/bin/1pctl"),
|
||||
func(t *task.Task) error {
|
||||
return FileOp.CopyFile(path.Join(baseDir, "1pctl"), "/usr/local/bin")
|
||||
},
|
||||
nil,
|
||||
)
|
||||
if global.IsMaster {
|
||||
taskItem.AddSubTask(
|
||||
i18n.GetWithName("SnapCopy", "/usr/local/bin/1panel-core"),
|
||||
func(t *task.Task) error {
|
||||
return FileOp.CopyFile(path.Join(baseDir, "1panel"), "/usr/local/bin")
|
||||
},
|
||||
nil,
|
||||
)
|
||||
}
|
||||
taskItem.AddSubTask(
|
||||
i18n.GetWithName("SnapCopy", "/usr/local/bin/1panel-agent"),
|
||||
func(t *task.Task) error {
|
||||
return FileOp.CopyFile(path.Join(baseDir, "1panel-agent"), "/usr/local/bin")
|
||||
},
|
||||
nil,
|
||||
)
|
||||
if global.IsMaster {
|
||||
taskItem.AddSubTask(
|
||||
i18n.GetWithName("SnapCopy", "/etc/systemd/system/1panel-core.service"),
|
||||
func(t *task.Task) error {
|
||||
return FileOp.CopyFile(path.Join(baseDir, "1panel-core.service"), "/etc/systemd/system")
|
||||
},
|
||||
nil,
|
||||
)
|
||||
}
|
||||
taskItem.AddSubTask(
|
||||
i18n.GetWithName("SnapCopy", "/etc/systemd/system/1panel-agent.service"),
|
||||
func(t *task.Task) error {
|
||||
return FileOp.CopyFile(path.Join(baseDir, "1panel-agent.service"), "/etc/systemd/system")
|
||||
},
|
||||
nil,
|
||||
)
|
||||
taskItem.AddSubTask(
|
||||
i18n.GetWithName("SnapCopy", "/etc/docker/daemon.json"),
|
||||
func(t *task.Task) error {
|
||||
return FileOp.CopyFile(path.Join(baseDir, "daemon.json"), "/etc/docker")
|
||||
},
|
||||
nil,
|
||||
)
|
||||
taskItem.AddSubTask(
|
||||
i18n.GetWithName("SnapCopy", global.Dir.LocalBackupDir),
|
||||
func(t *task.Task) error {
|
||||
return FileOp.CopyDir(path.Join(rootDir, "backup"), global.Dir.LocalBackupDir)
|
||||
},
|
||||
nil,
|
||||
)
|
||||
openrestyDir, _ := settingRepo.GetValueByKey("WEBSITE_DIR")
|
||||
if len(openrestyDir) != 0 {
|
||||
taskItem.AddSubTask(
|
||||
i18n.GetWithName("SnapCopy", openrestyDir),
|
||||
func(t *task.Task) error {
|
||||
return FileOp.CopyDir(path.Join(rootDir, "www"), openrestyDir)
|
||||
},
|
||||
nil,
|
||||
)
|
||||
}
|
||||
taskItem.AddSubTask(
|
||||
i18n.GetWithName("SnapCopy", global.Dir.BaseDir),
|
||||
func(t *task.Task) error {
|
||||
return FileOp.CopyDir(path.Join(rootDir, "1panel"), global.Dir.BaseDir)
|
||||
},
|
||||
nil,
|
||||
)
|
||||
if err := taskItem.Execute(); err != nil {
|
||||
_ = snapshotRepo.Update(req.ID, map[string]interface{}{"rollback_status": constant.StatusFailed, "rollback_message": err.Error()})
|
||||
return
|
||||
}
|
||||
_ = snapshotRepo.Update(req.ID, map[string]interface{}{
|
||||
"recover_status": "",
|
||||
"recover_message": "",
|
||||
"rollback_status": "",
|
||||
"rollback_message": "",
|
||||
"interrupt_step": "",
|
||||
})
|
||||
_ = os.RemoveAll(rootDir)
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
)
|
||||
|
||||
type TaskLogService struct{}
|
||||
|
||||
type ITaskLogService interface {
|
||||
Page(req dto.SearchTaskLogReq) (int64, []dto.TaskDTO, error)
|
||||
SyncForRestart() error
|
||||
CountExecutingTask() (int64, error)
|
||||
}
|
||||
|
||||
func NewITaskService() ITaskLogService {
|
||||
return &TaskLogService{}
|
||||
}
|
||||
|
||||
func (u *TaskLogService) Page(req dto.SearchTaskLogReq) (int64, []dto.TaskDTO, error) {
|
||||
opts := []repo.DBOption{
|
||||
repo.WithOrderBy("created_at desc"),
|
||||
}
|
||||
if req.Status != "" {
|
||||
opts = append(opts, repo.WithByStatus(req.Status))
|
||||
}
|
||||
if req.Type != "" {
|
||||
opts = append(opts, repo.WithByType(req.Type))
|
||||
}
|
||||
|
||||
total, tasks, err := taskRepo.Page(
|
||||
req.Page,
|
||||
req.PageSize,
|
||||
opts...,
|
||||
)
|
||||
var items []dto.TaskDTO
|
||||
for _, t := range tasks {
|
||||
item := dto.TaskDTO{
|
||||
Task: t,
|
||||
}
|
||||
items = append(items, item)
|
||||
}
|
||||
return total, items, err
|
||||
}
|
||||
|
||||
func (u *TaskLogService) SyncForRestart() error {
|
||||
return taskRepo.UpdateRunningTaskToFailed()
|
||||
}
|
||||
|
||||
func (u *TaskLogService) CountExecutingTask() (int64, error) {
|
||||
return taskRepo.CountExecutingTask()
|
||||
}
|
||||
@@ -1,301 +0,0 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type ActionFunc func(*Task) error
|
||||
type RollbackFunc func(*Task)
|
||||
|
||||
type Task struct {
|
||||
Name string
|
||||
TaskID string
|
||||
Logger *log.Logger
|
||||
Writer *bufio.Writer
|
||||
SubTasks []*SubTask
|
||||
Rollbacks []RollbackFunc
|
||||
logFile *os.File
|
||||
taskRepo repo.ITaskRepo
|
||||
Task *model.Task
|
||||
ParentID string
|
||||
}
|
||||
|
||||
type SubTask struct {
|
||||
RootTask *Task
|
||||
Name string
|
||||
StepAlias string
|
||||
Retry int
|
||||
Timeout time.Duration
|
||||
Action ActionFunc
|
||||
Rollback RollbackFunc
|
||||
Error error
|
||||
IgnoreErr bool
|
||||
}
|
||||
|
||||
const (
|
||||
TaskInstall = "TaskInstall"
|
||||
TaskUninstall = "TaskUninstall"
|
||||
TaskCreate = "TaskCreate"
|
||||
TaskDelete = "TaskDelete"
|
||||
TaskUpgrade = "TaskUpgrade"
|
||||
TaskUpdate = "TaskUpdate"
|
||||
TaskRestart = "TaskRestart"
|
||||
TaskBackup = "TaskBackup"
|
||||
TaskRecover = "TaskRecover"
|
||||
TaskRollback = "TaskRollback"
|
||||
TaskSync = "TaskSync"
|
||||
TaskBuild = "TaskBuild"
|
||||
TaskPull = "TaskPull"
|
||||
TaskPush = "TaskPush"
|
||||
TaskHandle = "TaskHandle"
|
||||
)
|
||||
|
||||
const (
|
||||
TaskScopeWebsite = "Website"
|
||||
TaskScopeAI = "AI"
|
||||
TaskScopeApp = "App"
|
||||
TaskScopeRuntime = "Runtime"
|
||||
TaskScopeDatabase = "Database"
|
||||
TaskScopeCronjob = "Cronjob"
|
||||
TaskScopeSystem = "System"
|
||||
TaskScopeAppStore = "AppStore"
|
||||
TaskScopeSnapshot = "Snapshot"
|
||||
TaskScopeContainer = "Container"
|
||||
TaskScopeCompose = "Compose"
|
||||
TaskScopeImage = "Image"
|
||||
TaskScopeRuntimeExtension = "RuntimeExtension"
|
||||
TaskScopeCustomAppstore = "CustomAppstore"
|
||||
)
|
||||
|
||||
func GetTaskName(resourceName, operate, scope string) string {
|
||||
return fmt.Sprintf("%s%s [%s]", i18n.GetMsgByKey(operate), i18n.GetMsgByKey(scope), resourceName)
|
||||
}
|
||||
|
||||
func NewTaskWithOps(resourceName, operate, scope, taskID string, resourceID uint) (*Task, error) {
|
||||
return NewTask(GetTaskName(resourceName, operate, scope), operate, scope, taskID, resourceID)
|
||||
}
|
||||
|
||||
func CheckTaskIsExecuting(name string) error {
|
||||
taskRepo := repo.NewITaskRepo()
|
||||
task, _ := taskRepo.GetFirst(taskRepo.WithByStatus(constant.StatusExecuting), repo.WithByName(name))
|
||||
if task.ID != "" {
|
||||
return buserr.New("TaskIsExecuting")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func CheckResourceTaskIsExecuting(operate, scope string, resourceID uint) bool {
|
||||
taskRepo := repo.NewITaskRepo()
|
||||
task, _ := taskRepo.GetFirst(
|
||||
taskRepo.WithByStatus(constant.StatusExecuting),
|
||||
taskRepo.WithResourceID(resourceID),
|
||||
taskRepo.WithOperate(operate),
|
||||
repo.WithByType(scope))
|
||||
return task.ID != ""
|
||||
}
|
||||
|
||||
func NewTask(name, operate, taskScope, taskID string, resourceID uint) (*Task, error) {
|
||||
if taskID == "" {
|
||||
taskID = uuid.New().String()
|
||||
}
|
||||
logDir := path.Join(global.Dir.TaskDir, taskScope)
|
||||
if _, err := os.Stat(logDir); os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(logDir, constant.DirPerm); err != nil {
|
||||
return nil, fmt.Errorf("failed to create log directory: %w", err)
|
||||
}
|
||||
}
|
||||
logPath := path.Join(global.Dir.TaskDir, taskScope, taskID+".log")
|
||||
file, err := os.OpenFile(logPath, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, constant.FilePerm)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open log file: %w", err)
|
||||
}
|
||||
writer := bufio.NewWriter(file)
|
||||
logger := log.New(file, "", log.LstdFlags)
|
||||
taskModel := &model.Task{
|
||||
ID: taskID,
|
||||
Name: name,
|
||||
Type: taskScope,
|
||||
LogFile: logPath,
|
||||
Status: constant.StatusExecuting,
|
||||
ResourceID: resourceID,
|
||||
Operate: operate,
|
||||
}
|
||||
taskRepo := repo.NewITaskRepo()
|
||||
task := &Task{Name: name, logFile: file, Logger: logger, taskRepo: taskRepo, Task: taskModel, Writer: writer}
|
||||
return task, nil
|
||||
}
|
||||
|
||||
func (t *Task) AddSubTask(name string, action ActionFunc, rollback RollbackFunc) {
|
||||
subTask := &SubTask{RootTask: t, Name: name, Retry: 0, Timeout: 30 * time.Minute, Action: action, Rollback: rollback}
|
||||
t.SubTasks = append(t.SubTasks, subTask)
|
||||
}
|
||||
|
||||
func (t *Task) AddSubTaskWithAlias(key string, action ActionFunc, rollback RollbackFunc) {
|
||||
subTask := &SubTask{RootTask: t, Name: i18n.GetMsgByKey(key), StepAlias: key, Retry: 0, Timeout: 30 * time.Minute, Action: action, Rollback: rollback}
|
||||
t.SubTasks = append(t.SubTasks, subTask)
|
||||
}
|
||||
|
||||
func (t *Task) AddSubTaskWithOps(name string, action ActionFunc, rollback RollbackFunc, retry int, timeout time.Duration) {
|
||||
subTask := &SubTask{RootTask: t, Name: name, Retry: retry, Timeout: timeout, Action: action, Rollback: rollback}
|
||||
t.SubTasks = append(t.SubTasks, subTask)
|
||||
}
|
||||
|
||||
func (t *Task) AddSubTaskWithAliasAndOps(key string, action ActionFunc, rollback RollbackFunc, retry int, timeout time.Duration) {
|
||||
subTask := &SubTask{RootTask: t, Name: i18n.GetMsgByKey(key), Retry: retry, Timeout: timeout, Action: action, Rollback: rollback}
|
||||
t.SubTasks = append(t.SubTasks, subTask)
|
||||
}
|
||||
|
||||
func (t *Task) AddSubTaskWithIgnoreErr(name string, action ActionFunc) {
|
||||
subTask := &SubTask{RootTask: t, Name: name, Retry: 0, Timeout: 30 * time.Minute, Action: action, Rollback: nil, IgnoreErr: true}
|
||||
t.SubTasks = append(t.SubTasks, subTask)
|
||||
}
|
||||
|
||||
func (s *SubTask) Execute() error {
|
||||
subTaskName := s.Name
|
||||
if s.Name == "" {
|
||||
subTaskName = i18n.GetMsgByKey("SubTask")
|
||||
}
|
||||
var err error
|
||||
for i := 0; i < s.Retry+1; i++ {
|
||||
if i > 0 {
|
||||
s.RootTask.Log(i18n.GetWithName("TaskRetry", strconv.Itoa(i)))
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), s.Timeout)
|
||||
defer cancel()
|
||||
|
||||
done := make(chan error)
|
||||
go func() {
|
||||
done <- s.Action(s.RootTask)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
s.RootTask.Log(i18n.GetWithName("TaskTimeout", subTaskName))
|
||||
err = errors.New("timeout!")
|
||||
case err = <-done:
|
||||
if err != nil {
|
||||
s.RootTask.Log(i18n.GetWithNameAndErr("SubTaskFailed", subTaskName, err))
|
||||
} else {
|
||||
s.RootTask.Log(i18n.GetWithName("SubTaskSuccess", subTaskName))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if i == s.Retry {
|
||||
if s.Rollback != nil {
|
||||
s.Rollback(s.RootTask)
|
||||
}
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (t *Task) updateTask(task *model.Task) {
|
||||
_ = t.taskRepo.Update(context.Background(), task)
|
||||
}
|
||||
|
||||
func (t *Task) Execute() error {
|
||||
if err := t.taskRepo.Save(context.Background(), t.Task); err != nil {
|
||||
return err
|
||||
}
|
||||
var err error
|
||||
t.Log(i18n.GetWithName("TaskStart", t.Name))
|
||||
for _, subTask := range t.SubTasks {
|
||||
t.Task.CurrentStep = subTask.StepAlias
|
||||
t.updateTask(t.Task)
|
||||
if err = subTask.Execute(); err == nil {
|
||||
if subTask.Rollback != nil {
|
||||
t.Rollbacks = append(t.Rollbacks, subTask.Rollback)
|
||||
}
|
||||
} else {
|
||||
if subTask.IgnoreErr {
|
||||
err = nil
|
||||
continue
|
||||
}
|
||||
t.Task.ErrorMsg = err.Error()
|
||||
t.Task.Status = constant.StatusFailed
|
||||
for _, rollback := range t.Rollbacks {
|
||||
rollback(t)
|
||||
}
|
||||
t.updateTask(t.Task)
|
||||
break
|
||||
}
|
||||
}
|
||||
if t.Task.Status == constant.StatusExecuting {
|
||||
t.Task.Status = constant.StatusSuccess
|
||||
}
|
||||
t.Log("[TASK-END]")
|
||||
t.Task.EndAt = time.Now()
|
||||
t.updateTask(t.Task)
|
||||
_ = t.logFile.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
func (t *Task) DeleteLogFile() {
|
||||
_ = os.Remove(t.Task.LogFile)
|
||||
}
|
||||
|
||||
func (t *Task) LogWithStatus(msg string, err error) {
|
||||
if err != nil {
|
||||
t.Logger.Print(i18n.GetWithNameAndErr("FailedStatus", msg, err))
|
||||
} else {
|
||||
t.Logger.Print(i18n.GetWithName("SuccessStatus", msg))
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Task) Log(msg string) {
|
||||
t.Logger.Print(msg)
|
||||
}
|
||||
|
||||
func (t *Task) Logf(format string, v ...any) {
|
||||
t.Logger.Printf(format, v...)
|
||||
}
|
||||
|
||||
func (t *Task) LogFailed(msg string) {
|
||||
t.Logger.Println(msg + i18n.GetMsgByKey("Failed"))
|
||||
}
|
||||
|
||||
func (t *Task) LogFailedWithErr(msg string, err error) {
|
||||
t.Logger.Printf("%s %s : %s\n", msg, i18n.GetMsgByKey("Failed"), err.Error())
|
||||
}
|
||||
|
||||
func (t *Task) LogSuccess(msg string) {
|
||||
t.Logger.Println(msg + i18n.GetMsgByKey("Success"))
|
||||
}
|
||||
func (t *Task) LogSuccessF(format string, v ...any) {
|
||||
t.Logger.Println(fmt.Sprintf(format, v...) + i18n.GetMsgByKey("Success"))
|
||||
}
|
||||
|
||||
func (t *Task) LogStart(msg string) {
|
||||
t.Logger.Printf("%s%s\n", i18n.GetMsgByKey("Start"), msg)
|
||||
}
|
||||
|
||||
func (t *Task) LogWithOps(operate, msg string) {
|
||||
t.Logger.Printf("%s%s", i18n.GetMsgByKey(operate), msg)
|
||||
}
|
||||
|
||||
func (t *Task) LogSuccessWithOps(operate, msg string) {
|
||||
t.Logger.Printf("%s%s%s", i18n.GetMsgByKey(operate), msg, i18n.GetMsgByKey("Success"))
|
||||
}
|
||||
|
||||
func (t *Task) LogFailedWithOps(operate, msg string, err error) {
|
||||
t.Logger.Printf("%s%s%s : %s ", i18n.GetMsgByKey(operate), msg, i18n.GetMsgByKey("Failed"), err.Error())
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
additionalProperties:
|
||||
key: #应用的 key ,仅限英文,用于在 Linux 创建文件夹
|
||||
name: #应用名称
|
||||
tags:
|
||||
- Tool #应用标签,可以有多个,请参照下方的标签列表
|
||||
shortDescZh: #应用中文描述,不要超过30个字
|
||||
shortDescEn: #应用英文描述
|
||||
type: tool #应用类型,区别于应用分类,只能有一个,请参照下方的类型列表
|
||||
crossVersionUpdate: #是否可以跨大版本升级
|
||||
limit: #应用安装数量限制,0 代表无限制
|
||||
website: #官网地址
|
||||
github: #github 地址
|
||||
document: #文档地址
|
||||
@@ -1,15 +0,0 @@
|
||||
base:
|
||||
install_dir: /opt
|
||||
mode: dev
|
||||
|
||||
remote_url:
|
||||
app_repo: https://apps-assets.fit2cloud.com
|
||||
repo_url: https://resource.fit2cloud.com/1panel/package/v2
|
||||
resource_url: https://resource.fit2cloud.com/1panel/resource/v2
|
||||
|
||||
log:
|
||||
level: debug
|
||||
time_zone: Asia/Shanghai
|
||||
log_name: 1Panel
|
||||
log_suffix: .log
|
||||
max_backup: 10
|
||||
@@ -1,19 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "net/http/pprof"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/server"
|
||||
)
|
||||
|
||||
// @title 1Panel
|
||||
// @version 2.0
|
||||
// @description 开源Linux面板
|
||||
// @termsOfService http://swagger.io/terms/
|
||||
// @license.name Apache 2.0
|
||||
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
// @host localhost
|
||||
// @BasePath /api/v2
|
||||
func main() {
|
||||
server.Start()
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user