Compare commits
699
Commits
b4340dcd0f
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2f5dd405c3 | ||
|
|
11413786f5 | ||
|
|
48f5740679 | ||
|
|
487e144e9b | ||
|
|
d4c8c8f49e | ||
|
|
2d8d44c24d | ||
|
|
4ce6baa87e | ||
|
|
403cf51ea4 | ||
|
|
1462ad562a | ||
|
|
7b4a3224a2 | ||
|
|
52fb8d23d2 | ||
|
|
6940c41caf | ||
|
|
5b1beab49f | ||
|
|
e776b99ca7 | ||
|
|
edf9ee7303 | ||
|
|
02cae33668 | ||
|
|
832e1b2a82 | ||
|
|
488da10686 | ||
|
|
139d1b2603 | ||
|
|
2e9d742061 | ||
|
|
e0d97c1889 | ||
|
|
c9906ebe34 | ||
|
|
3673f4553b | ||
|
|
5c4d9dc9f5 | ||
|
|
deda67277f | ||
|
|
82d444ab07 | ||
|
|
b0f333d451 | ||
|
|
9f8ee79c78 | ||
|
|
3c4933826e | ||
|
|
4e79254465 | ||
|
|
e396d95686 | ||
|
|
f447d83812 | ||
|
|
045be42dc4 | ||
|
|
b4b53aa109 | ||
|
|
754e9534dd | ||
|
|
07c6192987 | ||
|
|
f63b829104 | ||
|
|
634dc7db49 | ||
|
|
68cd3d5080 | ||
|
|
4f8892236a | ||
|
|
610d6f9e0d | ||
|
|
7991bcfbf9 | ||
|
|
71971c0037 | ||
|
|
940a21ae7d | ||
|
|
9b2816e22f | ||
|
|
656f938ce3 | ||
|
|
f35a17c12c | ||
|
|
1bc34f9b01 | ||
|
|
84540b68bc | ||
|
|
c85d3d3413 | ||
|
|
92ee02f987 | ||
|
|
cc3bf76a39 | ||
|
|
142622318c | ||
|
|
02697172be | ||
|
|
828897b13f | ||
|
|
062d26e028 | ||
|
|
839949632e | ||
|
|
a25101c7bb | ||
|
|
92835b9b6b | ||
|
|
c6d6ba1382 | ||
|
|
8f6402aeb0 | ||
|
|
558f43062a | ||
|
|
ebbcb7b7f9 | ||
|
|
829f536b8a | ||
|
|
76d4b91fce | ||
|
|
37c2372ab4 | ||
|
|
f8552a0873 | ||
|
|
9a88e876f6 | ||
|
|
8834b077ad | ||
|
|
54d3232700 | ||
|
|
81169d4297 | ||
|
|
6cb5992006 | ||
|
|
6a04a80949 | ||
|
|
b3af9b3a3e | ||
|
|
312b9fc689 | ||
|
|
51282fc99c | ||
|
|
6749cbabdf | ||
|
|
384dc29208 | ||
|
|
6bebe669d4 | ||
|
|
3adcc4eb1e | ||
|
|
c38fdc45a9 | ||
|
|
4e002d3e75 | ||
|
|
dc55dfa34b | ||
|
|
48d88875a1 | ||
|
|
8a36633bc0 | ||
|
|
70328f4713 | ||
|
|
583ddbc608 | ||
|
|
ccf097e0e2 | ||
|
|
fcbc11fa73 | ||
|
|
93f2a53518 | ||
|
|
06df32e9e3 | ||
|
|
0558fd5a2f | ||
|
|
c9fe1faf7b | ||
|
|
889f7d447c | ||
|
|
6cb6b42fe5 | ||
|
|
26cab215b8 | ||
|
|
71a04e81f5 | ||
|
|
492f9e2547 | ||
|
|
f6226d7230 | ||
|
|
834519f419 | ||
|
|
f7cf2de859 | ||
|
|
7c9a2077b1 | ||
|
|
4e6324a3d5 | ||
|
|
f94c5a0257 | ||
|
|
3ba2884d5a | ||
|
|
d36ad9e8ca | ||
|
|
8ce13a37bf | ||
|
|
6a8f994065 | ||
|
|
70db02fe0c | ||
|
|
7733892dae | ||
|
|
55c8f109b2 | ||
|
|
36d2b9b014 | ||
|
|
5b5b76c973 | ||
|
|
ea77020968 | ||
|
|
c20328a93f | ||
|
|
686f14079f | ||
|
|
d5f0e24470 | ||
|
|
4c26ed6b1f | ||
|
|
f1a4d12abb | ||
|
|
c6b08e6361 | ||
|
|
d89e52dd89 | ||
|
|
4bf66e9d07 | ||
|
|
c79c30ef38 | ||
|
|
d8f0da330c | ||
|
|
b30053ba17 | ||
|
|
ed3471dc8f | ||
|
|
bbf06d0165 | ||
|
|
cb92f8311a | ||
|
|
36eb54714d | ||
|
|
7eb667125d | ||
|
|
2f4b66b903 | ||
|
|
e488eddbcc | ||
|
|
3e832e7f45 | ||
|
|
8eb93a3afd | ||
|
|
6eddce493e | ||
|
|
2850fe09d9 | ||
|
|
5a1fb7fb07 | ||
|
|
4bd3a5fab5 | ||
|
|
04d5ff8370 | ||
|
|
2b240df710 | ||
|
|
92926ae3c2 | ||
|
|
01d2bf53d2 | ||
|
|
20d8622d4d | ||
|
|
06ef7b6bc9 | ||
|
|
08eea2d775 | ||
|
|
f9b4919022 | ||
|
|
859e6e8b56 | ||
|
|
2426427bf1 | ||
|
|
79021a0a3a | ||
|
|
0944b8ef69 | ||
|
|
ee1eb4442b | ||
|
|
6ff06f9eb2 | ||
|
|
c254871ef2 | ||
|
|
32ba661db9 | ||
|
|
27f1fc2d36 | ||
|
|
7c8a10a3bf | ||
|
|
9308e2126a | ||
|
|
f463a0c95b | ||
|
|
ff863d3018 | ||
|
|
db19e7055e | ||
|
|
7c192c1e77 | ||
|
|
36ff85e9ff | ||
|
|
34f498505e | ||
|
|
838fdb0359 | ||
|
|
601c8ed2a6 | ||
|
|
a9b7123114 | ||
|
|
3253993a2b | ||
|
|
806c755f56 | ||
|
|
8f57e4e504 | ||
|
|
4633f817d1 | ||
|
|
e9ce6834ee | ||
|
|
d81688acdf | ||
|
|
e322e241e1 | ||
|
|
e1284b8276 | ||
|
|
ba388c796b | ||
|
|
bfa047da3e | ||
|
|
8c0362c002 | ||
|
|
f98b8496d8 | ||
|
|
1d80850ba0 | ||
|
|
17b1b23eb6 | ||
|
|
6f4c3a375a | ||
|
|
01323cb36b | ||
|
|
55466d4b34 | ||
|
|
da80ecffe1 | ||
|
|
543322272b | ||
|
|
c7ce645531 | ||
|
|
a4d7a9535d | ||
|
|
c132d0eb72 | ||
|
|
59b15069a0 | ||
|
|
8e08698b3d | ||
|
|
3eed94f791 | ||
|
|
a7d7453f9f | ||
|
|
a49ddd1593 | ||
|
|
d50ec34ea1 | ||
|
|
69bdceb87b | ||
|
|
75191a8198 | ||
|
|
24e652207b | ||
|
|
ab47bbddbf | ||
|
|
ebe0c83d47 | ||
|
|
0ad121be8e | ||
|
|
b29ff7a325 | ||
|
|
e355ef6783 | ||
|
|
5f8a36d993 | ||
|
|
45b348d897 | ||
|
|
110d258886 | ||
|
|
7d48693230 | ||
|
|
b4a6f23b1a | ||
|
|
a7d0d05c40 | ||
|
|
9c9d5295ec | ||
|
|
3dcc1950ab | ||
|
|
928fccc072 | ||
|
|
bb8a231b7d | ||
|
|
7cd9385876 | ||
|
|
5239100b53 | ||
|
|
c1607024fb | ||
|
|
8a829c23b8 | ||
|
|
029cdac89a | ||
|
|
1d3cb21546 | ||
|
|
2dd10a22c9 | ||
|
|
5bc2dfc51d | ||
|
|
9851701b61 | ||
|
|
3c5cae8d3d | ||
|
|
42ab973153 | ||
|
|
69394a58ab | ||
|
|
0d4caca6e8 | ||
|
|
adc948485b | ||
|
|
ca5bea9821 | ||
|
|
412075196e | ||
|
|
210eb03709 | ||
|
|
91e2cede31 | ||
|
|
918d218f26 | ||
|
|
ec30432a3f | ||
|
|
4f7a329fb2 | ||
|
|
7ddef76ed1 | ||
|
|
f6010ff713 | ||
|
|
83eb9c4735 | ||
|
|
b9fe8924e2 | ||
|
|
0364e4903d | ||
|
|
2b0336454c | ||
|
|
042750b2bd | ||
|
|
5fcffb6904 | ||
|
|
405e232f3e | ||
|
|
d951506d40 | ||
|
|
1ccb1a476b | ||
|
|
942236556a | ||
|
|
790aefc333 | ||
|
|
6d0b5548cf | ||
|
|
9a8b2326a6 | ||
|
|
ea737a28de | ||
|
|
c0887bec97 | ||
|
|
c826bac0c7 | ||
|
|
881acbc6ce | ||
|
|
a90eaf0935 | ||
|
|
2c711ee982 | ||
|
|
439112e23a | ||
|
|
48524c385c | ||
|
|
8d5af43042 | ||
|
|
9e4c10aa2e | ||
|
|
0f6ff4f5e7 | ||
|
|
b75b8508cd | ||
|
|
273b8c966b | ||
|
|
b8f725c34c | ||
|
|
3b9167263d | ||
|
|
744fa87719 | ||
|
|
45ec3d6c9e | ||
|
|
71a819ab17 | ||
|
|
9b9e1d009a | ||
|
|
1d43c0f22e | ||
|
|
cdbb34aed2 | ||
|
|
53daed57c6 | ||
|
|
f42a863c0f | ||
|
|
50fa72f623 | ||
|
|
edaf4ff15f | ||
|
|
bc10de87c3 | ||
|
|
10480524f9 | ||
|
|
7c035c36d5 | ||
|
|
7d29724819 | ||
|
|
4c7eec7b12 | ||
|
|
18e0cb1eea | ||
|
|
6f5d64e0f4 | ||
|
|
1b954f1401 | ||
|
|
895914d3cf | ||
|
|
95a320b8c3 | ||
|
|
e2544b5af0 | ||
|
|
272fae6e92 | ||
|
|
4f777b27f7 | ||
|
|
b7dafe0896 | ||
|
|
b35e5a5b3f | ||
|
|
1b6d87feca | ||
|
|
e28953e5f2 | ||
|
|
e70ff331ed | ||
|
|
eee7713f7f | ||
|
|
9d9f2edd4a | ||
|
|
5da871b11e | ||
|
|
830c9110ca | ||
|
|
ed88181930 | ||
|
|
92b14fba76 | ||
|
|
9125284f09 | ||
|
|
ea6be3f1d8 | ||
|
|
c98666f34b | ||
|
|
c39b0bd699 | ||
|
|
a4e9dfab6f | ||
|
|
6860314215 | ||
|
|
f98c444b40 | ||
|
|
06de99739c | ||
|
|
1e07579d5c | ||
|
|
af9bfa58ab | ||
|
|
6fae4a04b9 | ||
|
|
5d198f7999 | ||
|
|
8627bce58d | ||
|
|
725ac1771c | ||
|
|
3f9ffffd10 | ||
|
|
95d11ab1d2 | ||
|
|
87ca2e5295 | ||
|
|
c2fd991ae9 | ||
|
|
670d6bd5c2 | ||
|
|
5f4833723b | ||
|
|
3cde3db06c | ||
|
|
641c3517f7 | ||
|
|
86bf2a5c9a | ||
|
|
20d62fee12 | ||
|
|
c71dd103a6 | ||
|
|
f908fd4e38 | ||
|
|
4b06177d45 | ||
|
|
c926505894 | ||
|
|
dcfce73c94 | ||
|
|
eb40a1e6ed | ||
|
|
81956f987c | ||
|
|
6dfec20082 | ||
|
|
7476dd82e1 | ||
|
|
41b146f972 | ||
|
|
be57dd72f4 | ||
|
|
513bf0e7ad | ||
|
|
d42d73528f | ||
|
|
6beaf5d409 | ||
|
|
c861ffa860 | ||
|
|
7669276242 | ||
|
|
085b97fd5e | ||
|
|
48e3df1242 | ||
|
|
7b2c6c3383 | ||
|
|
bb7191dff9 | ||
|
|
252ac149c3 | ||
|
|
032211d0bc | ||
|
|
8cbb97226f | ||
|
|
726500dd9a | ||
|
|
aa2917ebe9 | ||
|
|
1447c4bec3 | ||
|
|
cf79c6a1ac | ||
|
|
f173caeb86 | ||
|
|
18ac2ae2c9 | ||
|
|
714efdb738 | ||
|
|
6e786ce152 | ||
|
|
fb1001b43f | ||
|
|
51ba4a2a8d | ||
|
|
714a2759c7 | ||
|
|
6e8658ec5a | ||
|
|
b045383a91 | ||
|
|
e2a37fb6a6 | ||
|
|
da4b6e3335 | ||
|
|
3c751819e6 | ||
|
|
3411e0a14e | ||
|
|
8709c95b4b | ||
|
|
334efe1b70 | ||
|
|
5e5a22a59e | ||
|
|
8eedd41999 | ||
|
|
4006bbdbb4 | ||
|
|
7b84b4b6eb | ||
|
|
f7d9923b00 | ||
|
|
76beca50ac | ||
|
|
2f1455f74a | ||
|
|
0acf1c5b09 | ||
|
|
a5b100ed93 | ||
|
|
bcb99ad567 | ||
|
|
8f79899568 | ||
|
|
12d9e15e7a | ||
|
|
c8c84eb825 | ||
|
|
903c6f5da1 | ||
|
|
73d28e045e | ||
|
|
6ebbd52724 | ||
|
|
5c0d0481d1 | ||
|
|
c6c608ac11 | ||
|
|
d478a7e6af | ||
|
|
6d2b68b500 | ||
|
|
113769434a | ||
|
|
0f701fe2c7 | ||
|
|
483a1184f5 | ||
|
|
d003324bbe | ||
|
|
de55ef0778 | ||
|
|
959d3c6de7 | ||
|
|
71bf859859 | ||
|
|
3a8488242d | ||
|
|
e9f8399985 | ||
|
|
da2d2a0c33 | ||
|
|
a851f07d03 | ||
|
|
a0a82307c8 | ||
|
|
7763dedd43 | ||
|
|
cc93ab013e | ||
|
|
e599f2a19a | ||
|
|
5715572d32 | ||
|
|
6417eed8df | ||
|
|
b446c17039 | ||
|
|
c3c43e0a47 | ||
|
|
8d81e0ba6a | ||
|
|
4f6e561cb0 | ||
|
|
db4f942c36 | ||
|
|
52217db05c | ||
|
|
753483ff24 | ||
|
|
157d2849c0 | ||
|
|
91b8ea0b49 | ||
|
|
25d6002d31 | ||
|
|
1b3b2016ab | ||
|
|
db4377ab96 | ||
|
|
4bf8dc72ab | ||
|
|
d208f7046c | ||
|
|
29cfb3e77e | ||
|
|
01af018bb4 | ||
|
|
45a9a63b22 | ||
|
|
0396ae4f7a | ||
|
|
37fd5775bc | ||
|
|
383290d05b | ||
|
|
fea1c55dbe | ||
|
|
5407ca16d0 | ||
|
|
06858ceab1 | ||
|
|
17c25bfbec | ||
|
|
3e2eac3c90 | ||
|
|
0bbaa42ef1 | ||
|
|
c5a8ff9cab | ||
|
|
2bf9247c1e | ||
|
|
b8c96b0fd9 | ||
|
|
f0a1ae993a | ||
|
|
366eb08ce3 | ||
|
|
6d9955c895 | ||
|
|
15039aa492 | ||
|
|
e286210d86 | ||
|
|
58a600a7f7 | ||
|
|
7cd7a5658c | ||
|
|
b225522405 | ||
|
|
e86034a29e | ||
|
|
5e38c5ca03 | ||
|
|
5010cc181f | ||
|
|
81c50e508c | ||
|
|
7fa19879d9 | ||
|
|
2c313f2643 | ||
|
|
401044c0af | ||
|
|
e6ec16cc3d | ||
|
|
8387fbe769 | ||
|
|
9ded8873fc | ||
|
|
766d91496d | ||
|
|
d6e8c7f4f9 | ||
|
|
3b519187f1 | ||
|
|
701dbe4519 | ||
|
|
64b7a551d6 | ||
|
|
92bca12681 | ||
|
|
6d10a9ebf8 | ||
|
|
f3b1270113 | ||
|
|
e1ad0cfe4e | ||
|
|
e9cdb26dbd | ||
|
|
6996520ff4 | ||
|
|
5a42768727 | ||
|
|
d6ffb5b094 | ||
|
|
c798dfaacd | ||
|
|
29bdb69f55 | ||
|
|
f551063cb1 | ||
|
|
fd9c0b08fd | ||
|
|
ca7f49015a | ||
|
|
2a216ed477 | ||
|
|
2543d966a9 | ||
|
|
e27c361861 | ||
|
|
a1788542bb | ||
|
|
058b08f372 | ||
|
|
5c4b553551 | ||
|
|
786f51ced0 | ||
|
|
352f378943 | ||
|
|
29a1850606 | ||
|
|
5c54208c65 | ||
|
|
8136f1965c | ||
|
|
484bc73e1f | ||
|
|
5ce037e011 | ||
|
|
c65fba1c92 | ||
|
|
2063ef415c | ||
|
|
3955640f5e | ||
|
|
29eab22fa1 | ||
|
|
598db934af | ||
|
|
c16ab08b29 | ||
|
|
2f5d13d11f | ||
|
|
b21371fb99 | ||
|
|
be708cb71a | ||
|
|
fb769d043c | ||
|
|
041987e815 | ||
|
|
b04c93b628 | ||
|
|
ba8e4ddb75 | ||
|
|
679b0d31d3 | ||
|
|
de9c0688ba | ||
|
|
265ea8b3f7 | ||
|
|
e9fa24203d | ||
|
|
0c0da3b6da | ||
|
|
ebd1fa5c3a | ||
|
|
6d6bafab0c | ||
|
|
cc473df774 | ||
|
|
d0c96f321c | ||
|
|
b830ddb5ff | ||
|
|
55efe6adaf | ||
|
|
675bf52e49 | ||
|
|
462cedb6ad | ||
|
|
1588e99b5b | ||
|
|
02e87bd8c8 | ||
|
|
6f5082850e | ||
|
|
5346a6a825 | ||
|
|
eec11ac4e9 | ||
|
|
f1d3f4ddd4 | ||
|
|
eb7b8fbd17 | ||
|
|
341342fdab | ||
|
|
c1edabbaf9 | ||
|
|
863250aa5d | ||
|
|
50de4735bb | ||
|
|
7f8123ca00 | ||
|
|
e83ba792e8 | ||
|
|
667bf827f5 | ||
|
|
aa9ccc6e26 | ||
|
|
4a6a196170 | ||
|
|
cb9a5d6038 | ||
|
|
33d61bc806 | ||
|
|
a223f972bb | ||
|
|
bdc15954bb | ||
|
|
c643c3df2a | ||
|
|
6571dafa2c | ||
|
|
84d7f5364f | ||
|
|
9e7881d644 | ||
|
|
a3a25afaa2 | ||
|
|
3fe43d68bd | ||
|
|
e6f84b8dc9 | ||
|
|
ab3530a189 | ||
|
|
51ff2a3573 | ||
|
|
5ebf08c5df | ||
|
|
ecf8bb5f44 | ||
|
|
1a37060e97 | ||
|
|
b37bcae33e | ||
|
|
58820267c4 | ||
|
|
13a6f8f7b2 | ||
|
|
9cdd2e189e | ||
|
|
b1cd379f0e | ||
|
|
72b5a9ea9e | ||
|
|
e5cff1b9f4 | ||
|
|
f35ba070bc | ||
|
|
a75db6dec1 | ||
|
|
a8f2f730b5 | ||
|
|
b6d47a0c7e | ||
|
|
220ae61141 | ||
|
|
821fef6816 | ||
|
|
0f7f66589e | ||
|
|
c49ca6c73d | ||
|
|
b884ab9db2 | ||
|
|
4940f4ddd6 | ||
|
|
1de492c41a | ||
|
|
497696e927 | ||
|
|
e70fa407a9 | ||
|
|
8e50cd3c63 | ||
|
|
541710dddc | ||
|
|
8d98a5bd19 | ||
|
|
d7e2bb39c5 | ||
|
|
149e0421b2 | ||
|
|
681a8bd327 | ||
|
|
779d726950 | ||
|
|
72f1a8b39d | ||
|
|
2970895e96 | ||
|
|
f777b2625c | ||
|
|
e7cf52f787 | ||
|
|
55c66b2a37 | ||
|
|
3725fe03da | ||
|
|
9100762122 | ||
|
|
47553bdd78 | ||
|
|
cc06487f1e | ||
|
|
e5371da2f6 | ||
|
|
5b207adc77 | ||
|
|
40cdf5f373 | ||
|
|
47866cca42 | ||
|
|
e6f9ba78dd | ||
|
|
dd72f1f7da | ||
|
|
dc0f23a717 | ||
|
|
285a2c0b65 | ||
|
|
1fd72e6e63 | ||
|
|
f25b8bf484 | ||
|
|
6099434936 | ||
|
|
559ae112a1 | ||
|
|
3aa49e6bcc | ||
|
|
b030e53a8e | ||
|
|
5208d88f8f | ||
|
|
5e736c71e4 | ||
|
|
bc54aad7e6 | ||
|
|
2f20e73051 | ||
|
|
b238fa9ea5 | ||
|
|
5816e88a36 | ||
|
|
c34d7cef9e | ||
|
|
b20c6abd8b | ||
|
|
d8a5abc84d | ||
|
|
1ea7307784 | ||
|
|
476ce2f515 | ||
|
|
2e0928d3aa | ||
|
|
3ee4c302f7 | ||
|
|
84b8eda7dc | ||
|
|
7d62ce0aea | ||
|
|
f7709be2b6 | ||
|
|
6889090fe2 | ||
|
|
5753672906 | ||
|
|
757838d70d | ||
|
|
38b053c124 | ||
|
|
380d17ebe6 | ||
|
|
18a04f1b94 | ||
|
|
880f2a58f1 | ||
|
|
23931443b6 | ||
|
|
2a3715ab54 | ||
|
|
c3fede03f0 | ||
|
|
11cf18bef9 | ||
|
|
b9095b65f7 | ||
|
|
5cbfaaed08 | ||
|
|
c70da06b15 | ||
|
|
15be476776 | ||
|
|
0f37a00e7f | ||
|
|
38b980c847 | ||
|
|
529ebbd4c8 | ||
|
|
c5281c1b6b | ||
|
|
e91370ac03 | ||
|
|
0faee4ed04 | ||
|
|
09d52890f4 | ||
|
|
19a1636fbe | ||
|
|
5b5252a02c | ||
|
|
100e47a220 | ||
|
|
218574d2d2 | ||
|
|
d085d4fd2c | ||
|
|
023ecc4e7d | ||
|
|
3bcbcfbfd0 | ||
|
|
d1a5ea108d | ||
|
|
efcbbe01cd | ||
|
|
7724de279f | ||
|
|
bf5b6f688e | ||
|
|
c058130601 | ||
|
|
cd57311377 | ||
|
|
ff421776d3 | ||
|
|
7df9fbafe2 | ||
|
|
ced38947a8 | ||
|
|
87b8f30d19 | ||
|
|
659fca24c7 | ||
|
|
0f7acd11a9 | ||
|
|
f4b16183af | ||
|
|
a9db0189ad | ||
|
|
acb231744b | ||
|
|
18026c18d2 | ||
|
|
4a1bea34a3 | ||
|
|
6205e52e5e | ||
|
|
41b1ad2607 | ||
|
|
3bfb0d95f4 | ||
|
|
bc4745f528 | ||
|
|
a11665f9b8 | ||
|
|
6122aa3173 | ||
|
|
f6c7f39adc | ||
|
|
e5b98afbf0 | ||
|
|
1727ab659c | ||
|
|
a7bd78fbc5 | ||
|
|
0176bf58bd | ||
|
|
7bc9745bc6 | ||
|
|
66ec912298 | ||
|
|
d8b4ca0716 | ||
|
|
4a429cbc1a | ||
|
|
6c8e096a2c | ||
|
|
df7f4b8441 | ||
|
|
1dc63f5a36 | ||
|
|
b1441242da | ||
|
|
31ddff6a34 | ||
|
|
fe221f5b99 | ||
|
|
33de63b6f6 | ||
|
|
da17e91a93 | ||
|
|
e0eb093271 | ||
|
|
cc677b35c9 | ||
|
|
21ed653951 | ||
|
|
4d482912f4 | ||
|
|
60ee653940 | ||
|
|
fcd5b676e4 | ||
|
|
7efb4d6b2e | ||
|
|
e2879cba6a | ||
|
|
2308931d86 | ||
|
|
3ba5d4ba7c | ||
|
|
e7984ba7da | ||
|
|
394aab109c | ||
|
|
e821f0993e | ||
|
|
33fe1ca697 | ||
|
|
ed17206fb2 | ||
|
|
b2242267b8 | ||
|
|
b2486744e7 | ||
|
|
f78b8424a8 | ||
|
|
3508567ab2 | ||
|
|
d7eb69af85 | ||
|
|
ac1656c378 | ||
|
|
167e43e681 | ||
|
|
197be0a964 | ||
|
|
618d8bce68 | ||
|
|
499a7bacd9 | ||
|
|
18b3bcb662 | ||
|
|
6b33b38f91 | ||
|
|
83aa09c9a1 |
@@ -1,4 +1,4 @@
|
||||
FROM debian:bookworm-slim
|
||||
FROM docker.m.daocloud.io/library/debian:bookworm-slim@sha256:88200866dfff7ea7f5cbcb6ec7c8a701889efe6fe859fe64d6990e4b07ea4171
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends tini openjdk-17-jre-headless fuse3 zstd curl ca-certificates bash procps && rm -rf /var/lib/apt/lists/*
|
||||
WORKDIR /app
|
||||
|
||||
@@ -1,7 +1,211 @@
|
||||
# XyMediaVault public installer
|
||||
# XyMediaVault 公开发布与安装
|
||||
|
||||
Run `curl -fsSL https://git.keeper.work/admin/xymediavault-releases/raw/branch/main/install.sh | sudo bash` from the directory where you want XyMediaVault installed. The default install directory is the command's current directory; use `--install-dir /absolute/path` to override it. The installer downloads `catalog-v1.json` and the public Generic Package assets over HTTPS, selects `linux-amd64` or `linux-arm64`, safely extracts the app archive, and stores TMM and Title archives in the local component directory.
|
||||
> 本仓库提供 XyMediaVault 的安装入口、部署模板与公开组件目录。
|
||||
>
|
||||
> 日常安装、升级、状态查看和本地媒体 FUSE 挂载维护,均从 `install.sh` 进入。本仓库不包含应用源码。
|
||||
|
||||
The app archive must contain `bin/xymediavault`, `bin/xymedia-supervisor`, `web/dist`, and `release.json`. The installer rejects legacy app packages missing the supervisor and waits for the current public catalog entry containing this complete layout.
|
||||
---
|
||||
|
||||
Options include `--install-dir`, PostgreSQL connection options, `--postgres-password-file`, `--skip-components`, and `--existing-db`. An external database requires `--existing-db`; the migration command is explicit and never runs against an unconfirmed external database. No source repository, token, signature, or release hash is used by this v1 installer.
|
||||
## 安装前确认
|
||||
|
||||
| 项目 | 说明 |
|
||||
| --- | --- |
|
||||
| 执行环境 | 在 Linux 主机上执行,并使用具备 Docker 管理权限的管理员账户。 |
|
||||
| 基础依赖 | 需要 Docker 及其编排工具;本地媒体 FUSE 维护额外需要第二代编排命令。 |
|
||||
| 安装目录 | 脚本会创建应用配置、数据库目录、密钥文件和容器。请选择你拥有管理权限的目录。 |
|
||||
| 已有小雅 | 已有 Alist 或 Xiaoya 时,先确认数据目录和当前用途;安装器可复用已有服务,也可单独创建一套受管理的小雅。 |
|
||||
| 敏感信息 | 不要公开 `.env`、`secrets/`、控制器密钥、数据库密码、阿里云授权信息或完整安装日志。 |
|
||||
|
||||
> **操作提醒**
|
||||
>
|
||||
> 清理、重建、挂载变更和数据库维护都可能影响正在使用的服务。执行前请确认影响范围,并为重要数据准备备份。
|
||||
|
||||
---
|
||||
|
||||
## 开始安装
|
||||
|
||||
进入希望作为安装目录的位置后执行。默认安装目录就是当前工作目录,脚本会显示交互菜单。
|
||||
|
||||
```bash
|
||||
curl -fsSL --proto '=https' --proto-redir '=https' \
|
||||
https://git.keeper.work/admin/xymediavault-releases/raw/branch/main/install.sh \
|
||||
| sudo bash
|
||||
```
|
||||
|
||||
### 指定安装目录
|
||||
|
||||
```bash
|
||||
curl -fsSL --proto '=https' --proto-redir '=https' \
|
||||
https://git.keeper.work/admin/xymediavault-releases/raw/branch/main/install.sh \
|
||||
| sudo bash -s -- --install-dir /opt/xymedia
|
||||
```
|
||||
|
||||
安装器会从当前公开 `catalog-v1.json` 下载与主机架构匹配的应用、媒体文件解析服务、媒体管理服务和控制器制品。不要手工编辑目录文件,也不要填写未发布的组件地址。
|
||||
|
||||
---
|
||||
|
||||
## 选择安装方式
|
||||
|
||||
| 菜单 | 用途 | 适合场景 |
|
||||
| --- | --- | --- |
|
||||
| `1` | 安装或升级:管理平台和本机小雅 | 新用户、单机部署、默认推荐;已有部署可直接升级 |
|
||||
| `2` | 仅安装管理平台 | 只需要平台,暂不接入小雅 |
|
||||
| `3` | 安装管理平台并连接远程小雅控制器 | 小雅部署在另一台主机 |
|
||||
| `4` | 仅安装小雅控制器 | 在小雅主机上部署控制器,供另一台管理平台连接 |
|
||||
| `5` | 状态、诊断与维护 | 查看状态、管理本地 FUSE 挂载、执行清理 |
|
||||
| `6` | 查看小雅控制器地址和密钥 | 仅限可信管理员查看 |
|
||||
| `7` | 查看数据库连接信息 | 仅限可信管理员查看 |
|
||||
| `0` | 退出 | 不执行安装或维护操作 |
|
||||
|
||||
### 已有小雅:复用或独立安装
|
||||
|
||||
选择模式 `1` 后,若检测到本机已有 Alist 或 Xiaoya 容器,安装器会让你选择:
|
||||
|
||||
| 选择 | 行为 |
|
||||
| --- | --- |
|
||||
| 复用已有容器 | 不会接管、重命名或删除该容器。请确认你有管理权限,并按提示提供与容器数据挂载一致的宿主机目录。同机已有 Alist/Xiaoya 且希望由平台管理时,请选择模式 `1`。 |
|
||||
| 独立安装小雅 | 创建由 XyMediaVault 管理的 `xymedia-xiaoya` 容器和独立数据目录。默认端口被占用时,按提示选择其他宿主机端口。 |
|
||||
| 取消 | 不修改已有容器或安装配置。 |
|
||||
|
||||
复用已有服务前,请先确认它不是其他业务正在依赖的生产实例。独立安装不会主动修改已有 Alist/Xiaoya,但端口和目录不能冲突。名称或镜像中不含 `alist` 或 `xiaoya` 的服务不会被安装器自动识别,请勿假定它会被复用。
|
||||
|
||||
### 远程小雅控制器
|
||||
|
||||
远程部署分两步:
|
||||
|
||||
1. 在小雅所在主机选择模式 `4`,配置真实的小雅数据目录和容器名。
|
||||
2. 在管理平台主机选择模式 `3`,填写控制器地址、Token 和显示名称。
|
||||
|
||||
> 两台主机之间必须具备网络连通性,并应使用防火墙限制控制器端口的访问来源。
|
||||
|
||||
---
|
||||
|
||||
## 服务端口
|
||||
|
||||
默认端口如下。应用、WebDAV、TVBox 和控制器端口不会在交互菜单中逐项询问;首次安装需要改端口时,请在运行安装器前通过环境变量传入。端口冲突时不要直接修改正在运行的部署文件。
|
||||
|
||||
| 服务 | 宿主机默认端口 | 访问方式 |
|
||||
| --- | ---: | --- |
|
||||
| 管理后台/API | `18080` | `http://服务器IP:18080` |
|
||||
| WebDAV | `18081` | `http://服务器IP:18081/dav` |
|
||||
| TVBox | `18082` | `http://服务器IP:18082` |
|
||||
| Emby 反向代理 | `18086` | `http://服务器IP:18086` |
|
||||
| 小雅 Web | `5678` | `http://服务器IP:5678` |
|
||||
| 小雅管理 | `2345` | 由小雅使用 |
|
||||
| 小雅代理 | `2346` | 由小雅使用 |
|
||||
| 小雅控制器 | `19090` | 远程管理平台连接时使用 |
|
||||
|
||||
例如,将应用、WebDAV、TVBox、Emby 反向代理和控制器端口改为其他可用端口后再安装或升级:
|
||||
|
||||
```bash
|
||||
curl -fsSL --proto '=https' --proto-redir '=https' \
|
||||
https://git.keeper.work/admin/xymediavault-releases/raw/branch/main/install.sh \
|
||||
| sudo env \
|
||||
XYMEDIA_API_PORT=28080 \
|
||||
XYMEDIA_WEBDAV_PORT=28081 \
|
||||
XYMEDIA_TVBOX_PORT=28082 \
|
||||
XYMEDIA_EMBY_PROXY_PORT=28086 \
|
||||
XYMEDIA_CONTROLLER_PORT=29090 \
|
||||
bash
|
||||
```
|
||||
|
||||
> 小雅 Web、管理和代理端口会在选择独立安装小雅时由安装器提示确认;也可通过 `XYMEDIA_XIAOYA_PORT`、`XYMEDIA_XIAOYA_ADMIN_PORT`、`XYMEDIA_XIAOYA_PROXY_PORT` 预先指定。已有实例需要变更端口时,请先备份安装目录中的 `.env`,确认依赖方已停止或已调整,再使用对应端口变量重新运行安装器。
|
||||
|
||||
Emby 反向代理默认使用 `18086`,可通过 `XYMEDIA_EMBY_PROXY_PORT` 自定义;该端口必须与管理后台、WebDAV 和 TVBox 主机端口不同。
|
||||
|
||||
Dashboard 的服务地址使用当前浏览器 hostname 和协议,并读取 Compose 注入的 `XYMEDIA_DASHBOARD_*_PORT` 元数据。外部 PostgreSQL 模板保留 `XYMEDIA_API_BIND`、`XYMEDIA_WEBDAV_BIND` 和 `XYMEDIA_TVBOX_BIND` 的兼容映射;如这些映射被自定义,请同时设置对应的纯数字 Dashboard 端口变量,否则 Dashboard 不显示该地址。
|
||||
|
||||
---
|
||||
|
||||
## 安装后检查
|
||||
|
||||
安装完成后,建议先在安装器中选择菜单 `5` 的“查看状态”。也可在安装目录中检查应用健康状态:
|
||||
|
||||
```bash
|
||||
curl -fsS http://127.0.0.1:18080/api/health
|
||||
```
|
||||
|
||||
查看容器:
|
||||
|
||||
```bash
|
||||
docker ps --filter name=xymedia
|
||||
```
|
||||
|
||||
查看应用日志:
|
||||
|
||||
```bash
|
||||
docker logs --tail=200 xymedia-app
|
||||
```
|
||||
|
||||
如果安装目录不是当前目录,请先进入实际安装目录,或在维护命令中使用 `--install-dir`。
|
||||
|
||||
---
|
||||
|
||||
## 本地媒体 FUSE 挂载
|
||||
|
||||
菜单 `5` 提供以下维护项:
|
||||
|
||||
| 维护项 | 作用 |
|
||||
| --- | --- |
|
||||
| 查看状态 | 查看服务、数据库和当前挂载状态 |
|
||||
| 一键挂载本地媒体库 | 首次配置宿主机媒体目录并启动 FUSE |
|
||||
| 启动/重启已配置挂载 | 使用已保存的挂载路径重新启动 |
|
||||
| 停用本地媒体库挂载 | 停止受管理的 FUSE 挂载并恢复普通应用模式 |
|
||||
| 清理 XyMediaVault 容器及数据 | 需多次确认的破坏性维护操作 |
|
||||
|
||||
启用 FUSE 前,请逐项确认:
|
||||
|
||||
- 宿主机存在字符设备 `/dev/fuse`;
|
||||
- 使用 `root` 执行 FUSE 维护;
|
||||
- 目标目录是已存在、绝对、非符号链接的目录;
|
||||
- 首次挂载时目标目录必须为空;
|
||||
- 目标目录不能是其他应用正在读写的普通数据目录;
|
||||
- 部署配置能够向 `xymedia-app` 映射 `/dev/fuse` 和 `SYS_ADMIN` 能力。
|
||||
|
||||
挂载动作由管理员执行一次即可。挂载成功后,媒体服务应读取挂载后的目录;是否允许普通用户读取仍取决于媒体服务容器的目录映射、挂载传播和自身用户权限。
|
||||
|
||||
如果挂载失败,安装器会输出预检、容器设备、应用健康和挂载状态诊断。请保留脱敏后的诊断结果,不要粘贴 `.env` 或密钥文件内容。
|
||||
|
||||
---
|
||||
|
||||
## 清理与数据安全
|
||||
|
||||
维护菜单中的“清理 XyMediaVault 容器及数据”是破坏性操作:
|
||||
|
||||
- 它只处理安装器能够确认属于当前实例的受管理资源;
|
||||
- 不会使用 Docker prune,也不会按名称猜测删除其他容器;
|
||||
- 选择清理本机小雅时,只会处理明确标记为本安装实例管理的小雅;
|
||||
- 仍应在执行前自行确认数据库、媒体目录和小雅数据的备份;
|
||||
- 不要使用 `docker compose down -v` 代替安装器维护流程,除非你明确要永久删除对应数据卷。
|
||||
|
||||
---
|
||||
|
||||
## 仓库文件说明
|
||||
|
||||
| 文件 | 用途 |
|
||||
| --- | --- |
|
||||
| `install.sh` | 交互式安装、升级和维护入口 |
|
||||
| `catalog-v1.json` | 当前公开应用、媒体文件解析服务、媒体管理服务和控制器制品目录 |
|
||||
| `compose.yaml` | 平台、本地数据库和本机小雅模板 |
|
||||
| `compose.fuse.yaml` | FUSE 启用时合并的部署覆盖配置 |
|
||||
| `compose-controller.yaml` | 仅部署控制器时使用的模板 |
|
||||
| `config.yaml` | 应用默认配置模板 |
|
||||
| `remount-fuse.sh` | 本地媒体 FUSE 维护脚本,由安装器自动刷新 |
|
||||
| `Dockerfile.bootstrap` | 应用与控制器启动镜像定义 |
|
||||
|
||||
这些文件由发布流程同步。安装实例中的 `.env`、`config.yaml`、`secrets/`、`data/`、`releases/` 和组件目录属于本机状态,不应直接提交回公开仓库。
|
||||
|
||||
---
|
||||
|
||||
## 获取帮助
|
||||
|
||||
排障时请提供以下脱敏信息:
|
||||
|
||||
- 使用的安装模式和安装目录;
|
||||
- 操作系统、CPU 架构、Docker 和 Compose 版本;
|
||||
- 菜单操作步骤;
|
||||
- `docker ps --filter name=xymedia` 输出;
|
||||
- `docker logs --tail=200 xymedia-app` 中去除凭据后的相关部分;
|
||||
- FUSE 维护输出中的 `preflight diagnostics` 和 `timeout diagnostics`。
|
||||
|
||||
请勿提交控制器 Token、数据库密码、阿里云授权信息、Cookie、完整 `.env`、完整 `config.yaml` 或包含内部地址和凭据的截图。
|
||||
|
||||
+31
-10
@@ -1,18 +1,20 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"generated_at": "2026-08-30T10:23:00Z",
|
||||
"generated_at": "2026-09-10T03:05:21Z",
|
||||
"components": {
|
||||
"title": {
|
||||
"artifacts": {
|
||||
"linux-amd64": {
|
||||
"version": "1.0.6",
|
||||
"version": "2.0.0",
|
||||
"platform": "linux-amd64",
|
||||
"asset_url": "https://git.keeper.work/api/packages/admin/generic/xymediavault-title/1.0.6/xymedia-title-1.0.6-linux-amd64.tar.zst"
|
||||
"asset_url": "https://git.keeper.work/api/packages/admin/generic/xymediavault-title/2.0.0/xymedia-title-2.0.0-linux-amd64.tar.zst",
|
||||
"sha256": "67d7da24e8d52ec1764fa626cffc3aa1fb48fb652d86bb7f0e908f7be2d4b5a3"
|
||||
},
|
||||
"linux-arm64": {
|
||||
"version": "1.0.6",
|
||||
"version": "2.0.0",
|
||||
"platform": "linux-arm64",
|
||||
"asset_url": "https://git.keeper.work/api/packages/admin/generic/xymediavault-title/1.0.6/xymedia-title-1.0.6-linux-arm64.tar.zst"
|
||||
"asset_url": "https://git.keeper.work/api/packages/admin/generic/xymediavault-title/2.0.0/xymedia-title-2.0.0-linux-arm64.tar.zst",
|
||||
"sha256": "dfc7e77c0e34ecc995745a38a4195e36e543ab1e37bc7a2b9e6c36d7013bae92"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -21,21 +23,40 @@
|
||||
"linux-any": {
|
||||
"version": "1.0.0",
|
||||
"platform": "linux-any",
|
||||
"asset_url": "https://git.keeper.work/api/packages/admin/generic/xymediavault-tmm/1.0.0/xymedia-tmm-1.0.0-linux-any.tar.zst"
|
||||
"asset_url": "https://git.keeper.work/api/packages/admin/generic/xymediavault-tmm/1.0.0/xymedia-tmm-1.0.0-linux-any.tar.zst",
|
||||
"sha256": "d648eeb8a944efe013d37e1655ef17671bb66959249494768255d6c882e803f1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"artifacts": {
|
||||
"linux-amd64": {
|
||||
"version": "1.4.15",
|
||||
"version": "2.1.0",
|
||||
"platform": "linux-amd64",
|
||||
"asset_url": "https://git.keeper.work/api/packages/admin/generic/xymediavault-app/1.4.15/xymediavault-1.4.15-linux-amd64.tar.gz"
|
||||
"asset_url": "https://git.keeper.work/api/packages/admin/generic/xymediavault-app/2.1.0-e99396b7652b-20260910030408-325-1/xymediavault-2.1.0-linux-amd64.tar.gz",
|
||||
"sha256": "271019d3160cd92c5e2413796db25bab6caf6a023e9574f787312c9050ce3202"
|
||||
},
|
||||
"linux-arm64": {
|
||||
"version": "1.4.15",
|
||||
"version": "2.1.0",
|
||||
"platform": "linux-arm64",
|
||||
"asset_url": "https://git.keeper.work/api/packages/admin/generic/xymediavault-app/1.4.15/xymediavault-1.4.15-linux-arm64.tar.gz"
|
||||
"asset_url": "https://git.keeper.work/api/packages/admin/generic/xymediavault-app/2.1.0-e99396b7652b-20260910030408-325-1/xymediavault-2.1.0-linux-arm64.tar.gz",
|
||||
"sha256": "5f5f3974f8abdcf87026652f8c40ade2c939c857c41d392a3fa797779d6133ce"
|
||||
}
|
||||
}
|
||||
},
|
||||
"controller": {
|
||||
"artifacts": {
|
||||
"linux-amd64": {
|
||||
"version": "2.1.0",
|
||||
"platform": "linux-amd64",
|
||||
"asset_url": "https://git.keeper.work/api/packages/admin/generic/xymedia-controller/2.1.0-e99396b7652b-20260910030408-325-1/xymedia-controller-2.1.0-linux-amd64.tar.gz",
|
||||
"sha256": "bcb82e059e22e8d12612f417e1202a54971e36615d04bf5e6fb8868d6f306602"
|
||||
},
|
||||
"linux-arm64": {
|
||||
"version": "2.1.0",
|
||||
"platform": "linux-arm64",
|
||||
"asset_url": "https://git.keeper.work/api/packages/admin/generic/xymedia-controller/2.1.0-e99396b7652b-20260910030408-325-1/xymedia-controller-2.1.0-linux-arm64.tar.gz",
|
||||
"sha256": "105e748667af3978d442bf7a62ce434f2f39d2f05271fc6b0595599ea765b4f2"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
services:
|
||||
xiaoya-control:
|
||||
image: ${XYMEDIA_BOOTSTRAP_IMAGE:-git.keeper.work/admin/xymediavault-bootstrap:1}
|
||||
container_name: xymedia-controller
|
||||
labels:
|
||||
com.xymediavault.managed: "true"
|
||||
com.xymediavault.instance_id: "${XYMEDIA_INSTANCE_ID}"
|
||||
com.xymediavault.install_dir: "${XYMEDIA_INSTALL_DIR}"
|
||||
entrypoint: [/controller/xymedia-edge]
|
||||
command: [controller]
|
||||
environment:
|
||||
XYMEDIA_CONTROLLER_TOKEN_FILE: /run/secrets/xiaoya-controller-token
|
||||
XYMEDIA_XIAOYA_CONTAINER: ${XYMEDIA_XIAOYA_CONTAINER:-xymedia-xiaoya}
|
||||
XYMEDIA_XIAOYA_SERVICE_PORTS: "80,2345,2346"
|
||||
XYMEDIA_XIAOYA_DATA_DIR: /xiaoya
|
||||
XYMEDIA_ALIYUN_QR_URL: https://passport.aliyundrive.com/newlogin/qrcode
|
||||
XYMEDIA_ALIYUN_TV_BASE_URL: ${XYMEDIA_ALIYUN_TV_BASE_URL:-http://api.extscreen.com}
|
||||
XYMEDIA_ALIYUN_TV_OPENAPI_BASE_URL: ${XYMEDIA_ALIYUN_TV_OPENAPI_BASE_URL:-https://openapi.alipan.com}
|
||||
XYMEDIA_INSECURE_SKIP_TLS_VERIFY: "true"
|
||||
XYMEDIA_CONTROLLER_ADDR: "${XYMEDIA_CONTROLLER_BIND_ADDRESS:-0.0.0.0}:${XYMEDIA_CONTROLLER_PORT:-19090}"
|
||||
XYMEDIA_HOST_ADDRESSES_FILE: /etc/xymedia/config/XYMEDIA_HOST_ADDRESSES
|
||||
XYMEDIA_HOST_ADDRESSES: ${XYMEDIA_HOST_ADDRESSES:-}
|
||||
ports:
|
||||
- "${XYMEDIA_CONTROLLER_BIND_ADDRESS:-0.0.0.0}:${XYMEDIA_CONTROLLER_PORT:-19090}:${XYMEDIA_CONTROLLER_PORT:-19090}"
|
||||
volumes:
|
||||
- "${XYMEDIA_XIAOYA_DATA_DIR:-${XYMEDIA_INSTALL_DIR}/xiaoya}:/xiaoya"
|
||||
- "${XYMEDIA_INSTALL_DIR}/config:/etc/xymedia/config:ro"
|
||||
- "${XYMEDIA_INSTALL_DIR}/controller:/controller:ro"
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
secrets: [xiaoya-controller-token]
|
||||
restart: unless-stopped
|
||||
secrets:
|
||||
xiaoya-controller-token:
|
||||
file: ${XYMEDIA_INSTALL_DIR}/secrets/xiaoya-controller-token
|
||||
@@ -0,0 +1,12 @@
|
||||
services:
|
||||
app:
|
||||
devices:
|
||||
- /dev/fuse:/dev/fuse
|
||||
cap_add:
|
||||
- SYS_ADMIN
|
||||
security_opt:
|
||||
- apparmor:unconfined
|
||||
environment:
|
||||
XYMEDIAVAULT_FUSE_HOST_PATH: ${XYMEDIA_FUSE_HOST_PATH}
|
||||
volumes:
|
||||
- ${XYMEDIA_FUSE_HOST_PATH}:/mnt/xymediavault:rshared
|
||||
+79
-4
@@ -1,12 +1,19 @@
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:17-bookworm
|
||||
container_name: xymedia-postgres
|
||||
labels: &xymedia-managed-labels
|
||||
com.xymediavault.managed: "true"
|
||||
com.xymediavault.instance_id: "${XYMEDIA_INSTANCE_ID}"
|
||||
com.xymediavault.install_dir: "${XYMEDIA_INSTALL_DIR}"
|
||||
profiles: [local-db]
|
||||
environment:
|
||||
POSTGRES_DB: ${XYMEDIA_POSTGRES_DATABASE}
|
||||
POSTGRES_USER: ${XYMEDIA_POSTGRES_USER}
|
||||
POSTGRES_PASSWORD_FILE: /run/secrets/xymedia-postgres-password
|
||||
secrets: [xymedia-postgres-password]
|
||||
ports:
|
||||
- "${XYMEDIA_POSTGRES_BIND_ADDRESS:-0.0.0.0}:${XYMEDIA_POSTGRES_PUBLIC_PORT}:5432"
|
||||
volumes: ["${XYMEDIA_INSTALL_DIR}/data/postgres:/var/lib/postgresql/data"]
|
||||
healthcheck:
|
||||
test: [CMD-SHELL, "pg_isready -U ${XYMEDIA_POSTGRES_USER} -d ${XYMEDIA_POSTGRES_DATABASE}"]
|
||||
@@ -14,38 +21,106 @@ services:
|
||||
timeout: 5s
|
||||
retries: 24
|
||||
app:
|
||||
# 应用容器通过宿主机公开端口连接 PostgreSQL;Docker/NAS 网络和防火墙必须允许局域网/回环访问。
|
||||
image: ${XYMEDIA_BOOTSTRAP_IMAGE:-git.keeper.work/admin/xymediavault-bootstrap:1}
|
||||
container_name: xymedia-app
|
||||
labels: *xymedia-managed-labels
|
||||
entrypoint: [/usr/bin/tini, --]
|
||||
command: [/releases/current/bin/xymedia-supervisor, --releases-dir, /releases, --config, /app/config.yaml]
|
||||
environment:
|
||||
XYMEDIA_POSTGRES_MODE: external
|
||||
XYMEDIA_POSTGRES_HOST: ${XYMEDIA_POSTGRES_HOST}
|
||||
XYMEDIA_POSTGRES_PORT: ${XYMEDIA_POSTGRES_PORT}
|
||||
XYMEDIA_POSTGRES_DATABASE: ${XYMEDIA_POSTGRES_DATABASE}
|
||||
XYMEDIA_POSTGRES_USER: ${XYMEDIA_POSTGRES_USER}
|
||||
XYMEDIA_COMPONENT_RUNTIME: local
|
||||
XYMEDIA_WEB_DIR: /releases/current/web/dist
|
||||
XYMEDIA_XIAOYA_CONTROLLER_KEY_FILE: /run/secrets/xiaoya-controller-token
|
||||
XYMEDIA_DASHBOARD_API_PORT: ${XYMEDIA_API_PORT:-18080}
|
||||
XYMEDIA_DASHBOARD_WEBDAV_PORT: ${XYMEDIA_WEBDAV_PORT:-18081}
|
||||
XYMEDIA_DASHBOARD_TVBOX_PORT: ${XYMEDIA_TVBOX_PORT:-18082}
|
||||
XYMEDIA_DASHBOARD_EMBY_PROXY_PORT: ${XYMEDIA_EMBY_PROXY_PORT:-18086}
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
ports:
|
||||
- "${XYMEDIA_API_PORT:-18080}:8080"
|
||||
- "${XYMEDIA_WEBDAV_PORT:-18081}:8081"
|
||||
- "${XYMEDIA_TVBOX_PORT:-18082}:8082"
|
||||
- "${XYMEDIA_EMBY_PROXY_PORT:-18086}:8083"
|
||||
volumes:
|
||||
- "${XYMEDIA_INSTALL_DIR}/data:/app/data"
|
||||
- "${XYMEDIA_INSTALL_DIR}/components:/app/components"
|
||||
- "${XYMEDIA_INSTALL_DIR}/releases:/releases"
|
||||
- "${XYMEDIA_INSTALL_DIR}/config.yaml:/app/config.yaml:ro"
|
||||
secrets: [xymedia-postgres-password]
|
||||
secrets: [xymedia-postgres-password, xiaoya-controller-token]
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: [CMD, /releases/current/bin/xymediavault, healthcheck, --url, http://127.0.0.1:8080/api/health]
|
||||
app-migrate:
|
||||
xiaoya-alist:
|
||||
image: xiaoyaliu/alist:latest
|
||||
profiles: [xiaoya]
|
||||
container_name: ${XYMEDIA_XIAOYA_CONTAINER:-xymedia-xiaoya}
|
||||
labels:
|
||||
com.xymediavault.managed: "true"
|
||||
com.xymediavault.instance_id: "${XYMEDIA_INSTANCE_ID}"
|
||||
com.xymediavault.install_dir: "${XYMEDIA_INSTALL_DIR}"
|
||||
ports: ["${XYMEDIA_XIAOYA_PORT:-5678}:80", "${XYMEDIA_XIAOYA_ADMIN_PORT:-2345}:2345", "${XYMEDIA_XIAOYA_PROXY_PORT:-2346}:2346"]
|
||||
volumes:
|
||||
- "${XYMEDIA_XIAOYA_DATA_DIR:-${XYMEDIA_INSTALL_DIR}/xiaoya}:/data"
|
||||
- "${XYMEDIA_XIAOYA_DATA_DIR:-${XYMEDIA_INSTALL_DIR}/xiaoya}/data:/www/data"
|
||||
restart: unless-stopped
|
||||
xiaoya-control:
|
||||
image: ${XYMEDIA_BOOTSTRAP_IMAGE:-git.keeper.work/admin/xymediavault-bootstrap:1}
|
||||
profiles: [xiaoya-control]
|
||||
container_name: xymedia-controller
|
||||
labels: *xymedia-managed-labels
|
||||
entrypoint: [/controller/xymedia-edge]
|
||||
command: [controller]
|
||||
environment:
|
||||
XYMEDIA_CONTROLLER_TOKEN_FILE: /run/secrets/xiaoya-controller-token
|
||||
XYMEDIA_XIAOYA_CONTAINER: ${XYMEDIA_XIAOYA_CONTAINER:-xymedia-xiaoya}
|
||||
XYMEDIA_XIAOYA_SERVICE_PORTS: "80,2345,2346"
|
||||
XYMEDIA_XIAOYA_DATA_DIR: /xiaoya
|
||||
XYMEDIA_ALIYUN_QR_URL: https://passport.aliyundrive.com/newlogin/qrcode
|
||||
XYMEDIA_ALIYUN_OPEN_TOKEN_URL: https://openapi.alipan.com
|
||||
XYMEDIA_ALIYUN_TV_BASE_URL: ${XYMEDIA_ALIYUN_TV_BASE_URL:-http://api.extscreen.com}
|
||||
XYMEDIA_ALIYUN_TV_OPENAPI_BASE_URL: ${XYMEDIA_ALIYUN_TV_OPENAPI_BASE_URL:-https://openapi.alipan.com}
|
||||
XYMEDIA_INSECURE_SKIP_TLS_VERIFY: "true"
|
||||
XYMEDIA_CONTROLLER_ADDR: "${XYMEDIA_CONTROLLER_BIND_ADDRESS:-0.0.0.0}:${XYMEDIA_CONTROLLER_PORT:-19090}"
|
||||
XYMEDIA_HOST_ADDRESSES_FILE: /etc/xymedia/config/XYMEDIA_HOST_ADDRESSES
|
||||
XYMEDIA_HOST_ADDRESSES: ${XYMEDIA_HOST_ADDRESSES:-}
|
||||
ports: ["${XYMEDIA_CONTROLLER_BIND_ADDRESS:-0.0.0.0}:${XYMEDIA_CONTROLLER_PORT:-19090}:${XYMEDIA_CONTROLLER_PORT:-19090}"]
|
||||
volumes:
|
||||
- "${XYMEDIA_XIAOYA_DATA_DIR:-${XYMEDIA_INSTALL_DIR}/xiaoya}:/xiaoya"
|
||||
- "${XYMEDIA_INSTALL_DIR}/config:/etc/xymedia/config:ro"
|
||||
- "${XYMEDIA_INSTALL_DIR}/controller:/controller:ro"
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
secrets: [xiaoya-controller-token]
|
||||
restart: unless-stopped
|
||||
app-migrate:
|
||||
# 迁移容器同样通过宿主机公开端口连接;Docker/NAS 网络和防火墙必须允许局域网/回环访问。
|
||||
image: ${XYMEDIA_BOOTSTRAP_IMAGE:-git.keeper.work/admin/xymediavault-bootstrap:1}
|
||||
labels: *xymedia-managed-labels
|
||||
entrypoint: [/releases/current/bin/xymediavault]
|
||||
command: [migrate, --new, --config, /app/config.yaml]
|
||||
environment:
|
||||
XYMEDIA_POSTGRES_MODE: external
|
||||
XYMEDIA_POSTGRES_HOST: ${XYMEDIA_POSTGRES_HOST}
|
||||
XYMEDIA_POSTGRES_PORT: ${XYMEDIA_POSTGRES_PORT}
|
||||
XYMEDIA_POSTGRES_DATABASE: ${XYMEDIA_POSTGRES_DATABASE}
|
||||
XYMEDIA_POSTGRES_USER: ${XYMEDIA_POSTGRES_USER}
|
||||
XYMEDIA_EXTERNAL_MIGRATION_CONFIRM: ${XYMEDIA_EXTERNAL_MIGRATION_CONFIRM:-YES}
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
volumes:
|
||||
- "${XYMEDIA_INSTALL_DIR}/data:/app/data"
|
||||
- "${XYMEDIA_INSTALL_DIR}/releases:/releases:ro"
|
||||
- "${XYMEDIA_INSTALL_DIR}/config.yaml:/app/config.yaml:ro"
|
||||
secrets: [xymedia-postgres-password]
|
||||
secrets: [xymedia-postgres-password, xiaoya-controller-token]
|
||||
profiles: [migration]
|
||||
restart: "no"
|
||||
secrets:
|
||||
xymedia-postgres-password:
|
||||
file: ${XYMEDIA_INSTALL_DIR}/secrets/postgres-password
|
||||
file: ${XYMEDIA_INSTALL_DIR}/secrets/xymedia-postgres-password
|
||||
xiaoya-controller-token:
|
||||
file: ${XYMEDIA_INSTALL_DIR}/secrets/xiaoya-controller-token
|
||||
|
||||
+8
-1
@@ -30,4 +30,11 @@ fuse:
|
||||
auto_mount: false
|
||||
mount_path: /mnt/xymediavault
|
||||
cache:
|
||||
directory: /app/data/cache
|
||||
directory: /app/data/cache/media-map
|
||||
xiaoya:
|
||||
config_dir: /app/xiaoya
|
||||
container_name: ${XYMEDIA_XIAOYA_CONTAINER}
|
||||
internal_url: http://${XYMEDIA_XIAOYA_CONTAINER}:80
|
||||
public_url: http://127.0.0.1:5678
|
||||
controller_url: ${XYMEDIA_XIAOYA_CONTROLLER_URL}
|
||||
controller_key_file: /run/secrets/xiaoya-controller-token
|
||||
|
||||
+1555
-77
File diff suppressed because it is too large
Load Diff
+284
@@ -0,0 +1,284 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
die() { printf 'xymediavault FUSE: ERROR: %s\n' "$*" >&2; exit 1; }
|
||||
log() { printf '%s xymediavault FUSE: %s\n' "$(date '+%Y-%m-%dT%H:%M:%S%z')" "$*" >>"$LOG_FILE"; }
|
||||
|
||||
install_dir=${XYMEDIA_INSTALL_DIR:-}
|
||||
action=restart
|
||||
path_arg=
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
--configure) [ "$#" -ge 2 ] || die '--configure requires a path'; action=configure; path_arg=$2; shift 2;;
|
||||
--restart) action=restart; shift;;
|
||||
--disable) action=disable; shift;;
|
||||
--install-dir) [ "$#" -ge 2 ] || die '--install-dir requires a path'; install_dir=$2; shift 2;;
|
||||
*) die "unknown option: $1";;
|
||||
esac
|
||||
done
|
||||
[ -n "$install_dir" ] || install_dir=$(CDPATH='' pwd -P)
|
||||
case "$install_dir" in ''|/|*[![:print:]]*) die 'installation directory is invalid';; esac
|
||||
[ -d "$install_dir" ] || die "installation directory does not exist: $install_dir"
|
||||
install_dir=$(CDPATH='' cd -- "$install_dir" && pwd -P) || die 'cannot canonicalize installation directory'
|
||||
[ "$(id -u)" -eq 0 ] || die 'FUSE maintenance requires root (euid 0)'
|
||||
[ -f "$install_dir/.env" ] || die "missing $install_dir/.env"
|
||||
[ -f "$install_dir/compose.yaml" ] || die "missing $install_dir/compose.yaml"
|
||||
[ -f "$install_dir/config.yaml" ] || die "missing $install_dir/config.yaml"
|
||||
[ -f "$install_dir/compose.fuse.yaml" ] || die "missing $install_dir/compose.fuse.yaml"
|
||||
[ ! -L "$install_dir/.env" ] || die '.env must not be a symlink'
|
||||
[ ! -L "$install_dir/config.yaml" ] || die 'config.yaml must not be a symlink'
|
||||
for protected in "$install_dir/.env" "$install_dir/config.yaml"; do
|
||||
[ "$(stat -c '%u' "$protected")" -eq 0 ] || die "protected file must be owned by root: $protected"
|
||||
protected_mode=$(stat -c '%a' "$protected")
|
||||
[ $(( 0$protected_mode & 022 )) -eq 0 ] || die "protected file must not be group/world writable: $protected"
|
||||
done
|
||||
state_dir=$install_dir/state
|
||||
if [ -e "$state_dir" ] && [ -L "$state_dir" ]; then die 'state directory must not be a symlink'; fi
|
||||
if [ ! -e "$state_dir" ]; then umask 077; mkdir "$state_dir" || die 'cannot create state directory'; fi
|
||||
[ -d "$state_dir" ] || die 'state path must be a directory'
|
||||
[ "$(stat -c '%u' "$state_dir")" -eq 0 ] || die 'state directory must be owned by root'
|
||||
state_mode=$(stat -c '%a' "$state_dir")
|
||||
[ $(( 0$state_mode & 022 )) -eq 0 ] || die 'state directory must not be group/world writable'
|
||||
command -v docker >/dev/null 2>&1 || die 'docker is required'
|
||||
docker compose version >/dev/null 2>&1 || die 'Docker Compose V2 is required'
|
||||
command -v findmnt >/dev/null 2>&1 || die 'findmnt is required'
|
||||
command -v mountpoint >/dev/null 2>&1 || die 'mountpoint is required'
|
||||
case "${DOCKER_HOST:-}" in ''|unix://*|unix:/*) ;; *) die 'remote Docker contexts are not allowed';; esac
|
||||
|
||||
read_env() { awk -F= -v wanted="$1" '$1 == wanted {sub(/^[^=]*=/, ""); print; exit}' "$install_dir/.env"; }
|
||||
old_enabled=$(read_env XYMEDIA_FUSE_ENABLED || true)
|
||||
old_path=$(read_env XYMEDIA_FUSE_HOST_PATH || true)
|
||||
case "$old_enabled" in true|false) ;; *) old_enabled=false;; esac
|
||||
mount_id= mount_source= mount_fstype=
|
||||
inspect_mount() {
|
||||
mount_id=; mount_source=; mount_fstype=
|
||||
if mountpoint -q "$1" 2>/dev/null; then
|
||||
mount_id=$(findmnt -rn -M "$1" -o ID 2>/dev/null || true)
|
||||
mount_source=$(findmnt -rn -M "$1" -o SOURCE 2>/dev/null || true)
|
||||
mount_fstype=$(findmnt -rn -M "$1" -o FSTYPE 2>/dev/null || true)
|
||||
[ -n "$mount_id" ] && [ -n "$mount_source" ] && [ -n "$mount_fstype" ] || return 1
|
||||
[ "$mount_id" = "$(findmnt -rn -M "$1" -o ID 2>/dev/null)" ] || return 1
|
||||
fi
|
||||
}
|
||||
owned_mount() {
|
||||
inspect_mount "$1" || die "mount identity is unstable: $1"
|
||||
[ -z "$mount_id" ] || { [ "$mount_source" = xymediavault ] && { [ "$mount_fstype" = fuse.xymediavault ] || [ "$mount_fstype" = fuse ]; }; } || die "refusing foreign or unknown mount at $1"
|
||||
}
|
||||
valid_path() {
|
||||
PATH_TO_CHECK=$1 python3 - <<'PY'
|
||||
import os, pathlib
|
||||
p = pathlib.Path(os.environ['PATH_TO_CHECK'])
|
||||
if not p.is_absolute() or str(p) == '/' or any(ord(c) < 32 or not c.isprintable() for c in str(p)):
|
||||
raise SystemExit(1)
|
||||
if not p.is_dir(): raise SystemExit(1)
|
||||
cur = pathlib.Path(p.root)
|
||||
for part in p.parts[1:]:
|
||||
cur /= part
|
||||
if cur.is_symlink(): raise SystemExit(1)
|
||||
print(p.resolve(strict=True))
|
||||
PY
|
||||
}
|
||||
if [ "$action" = configure ]; then
|
||||
new_path=$(valid_path "$path_arg") || die '目标路径必须是存在的、非根、绝对且不含符号链接的目录'
|
||||
inspect_mount "$new_path" || die '无法稳定识别目标路径挂载状态'
|
||||
if [ -n "$mount_id" ]; then owned_mount "$new_path"; else [ -z "$(find "$new_path" -mindepth 1 -maxdepth 1 -print -quit)" ] || die '目标目录必须为空'; fi
|
||||
else
|
||||
if [ -n "$old_path" ]; then new_path=$(valid_path "$old_path") || die '已配置的 FUSE 主机路径无效'; else new_path=; fi
|
||||
fi
|
||||
if [ -n "$old_path" ]; then
|
||||
old_path=$(valid_path "$old_path" 2>/dev/null) || die '已配置的 FUSE 主机路径无效'
|
||||
elif [ "$old_enabled" = true ]; then
|
||||
die 'FUSE 已启用但未配置主机路径'
|
||||
fi
|
||||
if [ "$action" != disable ] && [ -z "$new_path" ]; then die '未配置 FUSE 主机路径'; fi
|
||||
|
||||
print_preflight() {
|
||||
printf 'xymediavault FUSE: preflight diagnostics\n' >&2
|
||||
printf ' install_dir: %s\n' "$install_dir" >&2
|
||||
printf ' configured host path: %s\n' "${new_path:-${old_path:-<none>}}" >&2
|
||||
if [ -e /dev/fuse ]; then
|
||||
printf ' host /dev/fuse: %s (%s)\n' "$( [ -c /dev/fuse ] && printf present || printf 'wrong type' )" "$(stat -c '%F' /dev/fuse 2>/dev/null || printf 'unreadable')" >&2
|
||||
else
|
||||
printf ' host /dev/fuse: missing\n' >&2
|
||||
fi
|
||||
printf ' compose.fuse.yaml: %s\n' "$( [ -f "$install_dir/compose.fuse.yaml" ] && printf present || printf missing )" >&2
|
||||
}
|
||||
print_preflight
|
||||
[ -e /dev/fuse ] && [ -c /dev/fuse ] || die '/dev/fuse character device is required'
|
||||
|
||||
compose_check_env=${new_path:-$old_path}
|
||||
if [ "$action" = disable ] || [ -z "$compose_check_env" ]; then
|
||||
docker compose --project-directory "$install_dir" --env-file "$install_dir/.env" -f "$install_dir/compose.yaml" config -q || die 'compose configuration is invalid'
|
||||
else
|
||||
XYMEDIA_FUSE_HOST_PATH=$compose_check_env docker compose --project-directory "$install_dir" --env-file "$install_dir/.env" -f "$install_dir/compose.yaml" -f "$install_dir/compose.fuse.yaml" config -q || die 'compose configuration is invalid'
|
||||
fi
|
||||
|
||||
lock_dir=$install_dir/.install.lock
|
||||
mkdir "$lock_dir" 2>/dev/null || die '已有安装或 FUSE 维护正在进行'
|
||||
umask 077
|
||||
tmp_parent=${TMPDIR:-/tmp}
|
||||
[ -d "$tmp_parent" ] && [ ! -L "$tmp_parent" ] || die 'TMPDIR must be an existing non-symlink directory'
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
[ "$(stat -c '%u' "$tmp_parent")" -eq 0 ] || die 'TMPDIR must be owned by root when running as root'
|
||||
[ $(( $(stat -c '%a' "$tmp_parent") & 022 )) -eq 0 ] || [ $(( $(stat -c '%a' "$tmp_parent") & 1000 )) -ne 0 ] || die 'TMPDIR must not be group/world writable unless sticky'
|
||||
fi
|
||||
private_tmp=$(mktemp -d "$tmp_parent/xymedia-fuse.XXXXXX") || { rmdir "$lock_dir" 2>/dev/null || true; die 'cannot create private temporary directory'; }
|
||||
chmod 700 "$private_tmp"
|
||||
log_file=$(mktemp "$private_tmp/remount.log.XXXXXX") || { rm -rf "$private_tmp"; rmdir "$lock_dir" 2>/dev/null || true; die 'cannot create private log'; }
|
||||
chmod 600 "$log_file"
|
||||
env_snapshot=$(mktemp "$private_tmp/env.snapshot.XXXXXX") || { rm -rf "$private_tmp"; rmdir "$lock_dir" 2>/dev/null || true; die 'cannot create private environment snapshot'; }
|
||||
config_snapshot=$(mktemp "$private_tmp/config.snapshot.XXXXXX") || { rm -rf "$private_tmp"; rmdir "$lock_dir" 2>/dev/null || true; die 'cannot create private config snapshot'; }
|
||||
cp "$install_dir/.env" "$env_snapshot"; cp "$install_dir/config.yaml" "$config_snapshot"
|
||||
LOG_FILE=$log_file
|
||||
env_tmp= config_tmp=
|
||||
log_pid=
|
||||
cleanup() {
|
||||
status=$?
|
||||
trap - 0 1 2 3 15
|
||||
if [ -n "$log_pid" ]; then
|
||||
kill "$log_pid" 2>/dev/null || true
|
||||
wait "$log_pid" 2>/dev/null || true
|
||||
fi
|
||||
rm -f "$env_tmp" "$config_tmp"
|
||||
rm -rf "$private_tmp"
|
||||
rmdir "$lock_dir" 2>/dev/null || true
|
||||
exit "$status"
|
||||
}
|
||||
trap cleanup 0 1 2 3 15
|
||||
|
||||
compose() { docker compose --project-directory "$install_dir" --env-file "$install_dir/.env" -f "$install_dir/compose.yaml" "$@"; }
|
||||
compose_old() { if [ "$old_enabled" = true ]; then compose -f "$install_dir/compose.fuse.yaml" "$@"; else compose "$@"; fi; }
|
||||
restore_old() {
|
||||
cp "$env_snapshot" "$install_dir/.env"; cp "$config_snapshot" "$install_dir/config.yaml"
|
||||
compose_old up -d app >/dev/null 2>&1 || true
|
||||
}
|
||||
fail_restore() { log "operation failed: $*"; restore_old; die "$*; configuration and app recovery attempted (log: $log_file)"; }
|
||||
if [ "$action" = disable ]; then enabled=false; else enabled=true; fi
|
||||
inspect_mount "${old_path:-$new_path}" || fail_restore 'old mount identity is unstable'
|
||||
old_mount_id=$mount_id; old_mount_source=$mount_source; old_mount_fstype=$mount_fstype
|
||||
if [ -n "$old_mount_id" ]; then owned_mount "${old_path:-$new_path}"; fi
|
||||
compose_old stop app >/dev/null 2>&1 || fail_restore 'failed to stop app'
|
||||
if [ -n "$old_mount_id" ]; then
|
||||
inspect_mount "${old_path:-$new_path}" || fail_restore 'mount changed before unmount'
|
||||
[ "$mount_id" = "$old_mount_id" ] && [ "$mount_source" = "$old_mount_source" ] && [ "$mount_fstype" = "$old_mount_fstype" ] || fail_restore 'mount identity changed before unmount'
|
||||
if command -v fusermount3 >/dev/null 2>&1; then fusermount3 -u "${old_path:-$new_path}" || fail_restore 'unable to unmount owned FUSE mount'
|
||||
elif command -v fusermount >/dev/null 2>&1; then fusermount -u "${old_path:-$new_path}" || fail_restore 'unable to unmount owned FUSE mount'
|
||||
else umount "${old_path:-$new_path}" || fail_restore 'fusermount is required to unmount FUSE safely'; fi
|
||||
inspect_mount "${old_path:-$new_path}" || fail_restore 'mount identity is unstable after unmount'
|
||||
[ -z "$mount_id" ] || fail_restore 'FUSE path is still mounted'
|
||||
fi
|
||||
env_tmp=$(mktemp "$install_dir/.env.write.XXXXXX") || fail_restore 'cannot create environment write file'
|
||||
config_tmp=$(mktemp "$install_dir/.config.write.XXXXXX") || fail_restore 'cannot create config write file'
|
||||
chmod 600 "$env_tmp" "$config_tmp"
|
||||
if ! FUSE_ENABLED=$enabled FUSE_PATH=${new_path:-${old_path:-}} python3 - "$install_dir/.env" "$install_dir/config.yaml" "$env_tmp" "$config_tmp" <<'PY'
|
||||
import os, pathlib
|
||||
import stat, sys
|
||||
env = pathlib.Path(sys.argv[1]); config = pathlib.Path(sys.argv[2])
|
||||
env_tmp = pathlib.Path(sys.argv[3]); config_tmp = pathlib.Path(sys.argv[4])
|
||||
values = {'XYMEDIA_FUSE_ENABLED': os.environ['FUSE_ENABLED'], 'XYMEDIA_FUSE_HOST_PATH': os.environ['FUSE_PATH']}
|
||||
lines = env.read_text().splitlines(); seen=set(); out=[]
|
||||
for line in lines:
|
||||
key=line.split('=',1)[0] if '=' in line else ''
|
||||
if key in values: out.append(key+'='+values[key]); seen.add(key)
|
||||
else: out.append(line)
|
||||
out += [k+'='+v for k,v in values.items() if k not in seen]
|
||||
if env.is_symlink() or config.is_symlink(): raise SystemExit('protected config file is a symlink')
|
||||
if not stat.S_ISREG(env.stat().st_mode) or not stat.S_ISREG(config.stat().st_mode): raise SystemExit('protected config file is not regular')
|
||||
env_tmp.write_text('\n'.join(out)+'\n'); os.chmod(env_tmp, 0o600); os.replace(env_tmp, env)
|
||||
text=config.read_text(); import re
|
||||
text=re.sub(r'(?m)^( auto_mount:)\s*.*$', r'\1 '+('true' if values['XYMEDIA_FUSE_ENABLED']=='true' else 'false'), text)
|
||||
text=re.sub(r'(?m)^( mount_path:)\s*.*$', r'\1 /mnt/xymediavault', text)
|
||||
config_tmp.write_text(text); os.chmod(config_tmp, 0o600); os.replace(config_tmp, config)
|
||||
PY
|
||||
then
|
||||
fail_restore 'failed to update environment or config'
|
||||
fi
|
||||
startup_since=$(date -u '+%Y-%m-%dT%H:%M:%SZ')
|
||||
if [ "$enabled" = true ]; then
|
||||
if ! compose -f "$install_dir/compose.fuse.yaml" up -d app >/dev/null; then
|
||||
fail_restore 'failed to start app: docker compose up failed'
|
||||
fi
|
||||
else
|
||||
if ! compose up -d app >/dev/null; then
|
||||
fail_restore 'failed to start app: docker compose up failed'
|
||||
fi
|
||||
fi
|
||||
docker logs -f --since "$startup_since" xymedia-app >&2 &
|
||||
log_pid=$!
|
||||
health_result=unavailable
|
||||
container_status=unknown
|
||||
container_fuse=unknown
|
||||
status_mount_source=
|
||||
status_mount_fstype=
|
||||
print_status() {
|
||||
inspect_mount "${new_path:-${old_path:-/nonexistent}}" || true
|
||||
status_mount_source=${mount_source:-absent}
|
||||
status_mount_fstype=${mount_fstype:-absent}
|
||||
container_status=unknown
|
||||
container_fuse=unknown
|
||||
inspected_status=$(docker inspect --format '{{.State.Status}}' xymedia-app 2>/dev/null || true)
|
||||
case "$inspected_status" in
|
||||
created|restarting|running|paused|exited|dead) container_status=$inspected_status;;
|
||||
*) container_status=unavailable;;
|
||||
esac
|
||||
if [ "$container_status" = running ]; then
|
||||
if docker exec xymedia-app test -c /dev/fuse >/dev/null 2>&1; then container_fuse=present; else container_fuse=missing; fi
|
||||
fi
|
||||
printf 'xymediavault FUSE: status attempt %s/60: health=%s, host mount source=%s, fstype=%s, container status=%s, container /dev/fuse=%s\n' \
|
||||
"$attempt" "$health_result" "$status_mount_source" "$status_mount_fstype" "$container_status" "$container_fuse" >&2
|
||||
}
|
||||
print_timeout_diagnostics() {
|
||||
printf 'xymediavault FUSE: timeout diagnostics\n' >&2
|
||||
if [ ! -e /dev/fuse ] || [ ! -c /dev/fuse ]; then
|
||||
printf ' likely cause: host /dev/fuse is missing or is not a character device\n' >&2
|
||||
elif [ "$enabled" = true ] && [ "$container_fuse" = missing ]; then
|
||||
printf ' likely cause: compose/container is missing /dev/fuse\n' >&2
|
||||
elif [ "$enabled" = true ] && [ "$container_fuse" = unknown ]; then
|
||||
printf ' likely cause: app container is unavailable; container /dev/fuse could not be confirmed\n' >&2
|
||||
elif [ "$health_result" = unavailable ]; then
|
||||
printf ' likely cause: app health unavailable\n' >&2
|
||||
elif [ -z "$status_mount_source" ] || [ "$status_mount_source" = absent ]; then
|
||||
printf ' likely cause: app is healthy but host FUSE mount is absent\n' >&2
|
||||
else
|
||||
printf ' likely cause: host mount exists with unexpected source/fstype (source=%s, fstype=%s)\n' "$status_mount_source" "$status_mount_fstype" >&2
|
||||
fi
|
||||
printf ' recent xymedia-app logs (bounded):\n' >&2
|
||||
docker logs --tail 80 xymedia-app 2>&1 | while IFS= read -r line; do printf ' %s\n' "$line"; done >&2 || true
|
||||
printf ' application mount config:\n' >&2
|
||||
if mount_config=$(curl -fsS --max-time 5 "http://127.0.0.1:${XYMEDIA_API_PORT:-18080}/api/mount/config" 2>&1); then
|
||||
printf '%s\n' "$mount_config" | while IFS= read -r line; do printf ' %s\n' "$line"; done >&2
|
||||
else
|
||||
printf ' unavailable\n' >&2
|
||||
fi
|
||||
printf ' application mount status:\n' >&2
|
||||
if mount_status=$(curl -fsS --max-time 5 "http://127.0.0.1:${XYMEDIA_API_PORT:-18080}/api/mount/status" 2>&1); then
|
||||
printf '%s\n' "$mount_status" | while IFS= read -r line; do printf ' %s\n' "$line"; done >&2
|
||||
else
|
||||
printf ' unavailable\n' >&2
|
||||
fi
|
||||
printf ' container diagnostics (status and device mapping only):\n' >&2
|
||||
docker inspect --format '{{.State.Status}} health={{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}} devices={{range .HostConfig.Devices}}{{.PathOnHost}}:{{.PathInContainer}} {{end}}' xymedia-app 2>&1 >&2 || true
|
||||
printf ' compose configuration validation: ' >&2
|
||||
if [ "$enabled" = true ]; then
|
||||
compose -f "$install_dir/compose.fuse.yaml" config -q >/dev/null 2>&1 && printf 'passed\n' >&2 || printf 'failed\n' >&2
|
||||
else
|
||||
compose config -q >/dev/null 2>&1 && printf 'passed\n' >&2 || printf 'failed\n' >&2
|
||||
fi
|
||||
}
|
||||
attempt=0
|
||||
while [ "$attempt" -lt 60 ]; do
|
||||
attempt=$((attempt+1))
|
||||
if curl -fsS --max-time 5 "http://127.0.0.1:${XYMEDIA_API_PORT:-18080}/api/health" >/dev/null 2>&1; then
|
||||
health_result=healthy
|
||||
if [ "$enabled" = false ]; then log 'FUSE disabled and base app is healthy'; exit 0; fi
|
||||
inspect_mount "$new_path" || fail_restore 'mount identity is unstable after start'
|
||||
[ "$mount_source" = xymediavault ] && { [ "$mount_fstype" = fuse.xymediavault ] || [ "$mount_fstype" = fuse ]; } && log "FUSE mounted: $new_path" && exit 0
|
||||
else
|
||||
health_result=unavailable
|
||||
fi
|
||||
if [ "$attempt" -eq 1 ] || [ $((attempt % 5)) -eq 0 ]; then print_status; fi
|
||||
sleep 2
|
||||
done
|
||||
print_status
|
||||
print_timeout_diagnostics
|
||||
fail_restore 'app health or FUSE mount timed out'
|
||||
Reference in New Issue
Block a user