mirror of
https://github.com/vdsm/virtual-dsm.git
synced 2026-08-29 04:07:02 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
63e4529eff | ||
|
|
afd848cebb | ||
|
|
695b075130 | ||
|
|
7eff53e722 | ||
|
|
bcaf0e5980 | ||
|
|
faa820c2cc | ||
|
|
81e477fcc4 | ||
|
|
abd3a1c3df | ||
|
|
3b59bcd284 | ||
|
|
f1b56f394a | ||
|
|
1a983ebcd1 | ||
|
|
41c76198fa | ||
|
|
222b5649b0 | ||
|
|
e14fd4b711 | ||
|
|
6fe19a8af4 | ||
|
|
072b5c3070 | ||
|
|
89dbaeb2a4 | ||
|
|
02f76b44cd | ||
|
|
99d6b8f830 | ||
|
|
e6b68b2f08 | ||
|
|
7a8f0041e7 | ||
|
|
64809bb4d4 | ||
|
|
f08b2cbd99 | ||
|
|
18d9ecd434 | ||
|
|
6944293a32 | ||
|
|
d807a365ff | ||
|
|
066af83d07 | ||
|
|
81536818df | ||
|
|
949c288565 | ||
|
|
5d5bbbcf4d | ||
|
|
82a62c0240 | ||
|
|
5399e1d463 | ||
|
|
c7e7b60f01 | ||
|
|
f1defa6890 | ||
|
|
a7fb161974 | ||
|
|
dcdf5e5293 | ||
|
|
1eb0db66ba | ||
|
|
3eec8e03ed | ||
|
|
5483981ed4 | ||
|
|
62330a5bf4 | ||
|
|
2b0b59dfba | ||
|
|
b680ff9dd8 | ||
|
|
5c889b272d | ||
|
|
585ebb3f53 | ||
|
|
401307b981 | ||
|
|
46820fe6eb | ||
|
|
9452d48694 | ||
|
|
5c3fed1387 | ||
|
|
797411ae7f | ||
|
|
e81c509208 | ||
|
|
ace8614a50 | ||
|
|
8263b3a737 | ||
|
|
61c6142988 | ||
|
|
5d26338bb3 | ||
|
|
084d475cde | ||
|
|
c36b0dcb00 | ||
|
|
6e9993742b | ||
|
|
6946af0212 | ||
|
|
0a9361d62f | ||
|
|
7cbfcd44a6 | ||
|
|
6bcacf5e59 | ||
|
|
3bea87f087 | ||
|
|
f01e1564f7 | ||
|
|
cf8cdbf0c1 | ||
|
|
0813494ee0 | ||
|
|
0d71e30f50 | ||
|
|
c203deab9a | ||
|
|
9cf31df266 | ||
|
|
0cd08f2ee1 | ||
|
|
a32d215e90 | ||
|
|
c1f9a690f9 | ||
|
|
5de504deb3 | ||
|
|
250eb18238 | ||
|
|
ca80da59bd | ||
|
|
9cd86dfe82 | ||
|
|
5bb93651f7 | ||
|
|
0c268308a4 | ||
|
|
7b741d3e4b | ||
|
|
f1d29212ae | ||
|
|
fdee9f89a3 | ||
|
|
210a360797 | ||
|
|
991bb2627a | ||
|
|
764a186836 | ||
|
|
726786a98a | ||
|
|
2caf97d407 | ||
|
|
0e67286599 | ||
|
|
2b9fc80275 | ||
|
|
0d88774bd3 | ||
|
|
fa23052ed4 | ||
|
|
cc3267f258 | ||
|
|
fd002564a6 | ||
|
|
2703a97cfa | ||
|
|
29e2d034bf | ||
|
|
cbe3644335 | ||
|
|
c9a2a6966b | ||
|
|
498248f43e | ||
|
|
8963228b1c | ||
|
|
827d6d9e10 | ||
|
|
1c577abef4 | ||
|
|
20fd1a975c | ||
|
|
770774680c | ||
|
|
0c75aa81fe | ||
|
|
36ec57ac15 | ||
|
|
9c488c52b9 | ||
|
|
2ba756260f | ||
|
|
b306012743 | ||
|
|
61b3ee7e01 | ||
|
|
245036f844 | ||
|
|
68cadfe035 | ||
|
|
96102832b1 | ||
|
|
211a56bf73 | ||
|
|
506fb7b114 | ||
|
|
bcc3393b7f | ||
|
|
45c0f91d6e | ||
|
|
a1910546a6 | ||
|
|
637ed0e3a6 | ||
|
|
954a6076ad | ||
|
|
415aaa3038 | ||
|
|
c87e45719b | ||
|
|
102945ab48 | ||
|
|
1c3d490303 | ||
|
|
73fa70f52f | ||
|
|
7b095702fc | ||
|
|
f82b2769d3 | ||
|
|
cafe619028 | ||
|
|
e94a0c4a52 | ||
|
|
d6a353df0e | ||
|
|
050812e996 | ||
|
|
75280e9aa9 | ||
|
|
8dcc8ccee1 | ||
|
|
ba866e9e75 | ||
|
|
06e7a8edb9 | ||
|
|
495c67734a | ||
|
|
1557735331 | ||
|
|
f45b308c78 | ||
|
|
d5821cd782 | ||
|
|
312816b18e | ||
|
|
f42a3f1662 | ||
|
|
558e5022ca | ||
|
|
e03822f26e | ||
|
|
7b90ca30ba | ||
|
|
22c01bf2f1 | ||
|
|
1f601db8b0 | ||
|
|
e7e2c35cb7 | ||
|
|
50089112c0 | ||
|
|
f9538ce53b | ||
|
|
49d25a13d0 | ||
|
|
7c9d2b1fd5 | ||
|
|
b68d243b6a | ||
|
|
79e85f9fd1 | ||
|
|
476048a4ee | ||
|
|
f983dc3e46 | ||
|
|
38f9c49200 | ||
|
|
7157717a85 | ||
|
|
031674424d | ||
|
|
28e6f8da78 | ||
|
|
d5802607b5 | ||
|
|
7168f2f843 | ||
|
|
819dd29025 | ||
|
|
48ef47b85b | ||
|
|
10c902bc9b | ||
|
|
d6de10efe1 | ||
|
|
6d1d9d92b4 | ||
|
|
799649e78c | ||
|
|
12060c72b2 | ||
|
|
6eadd1b953 | ||
|
|
342ca4da03 | ||
|
|
b0b6190560 | ||
|
|
075410e4cf | ||
|
|
2a388b434b | ||
|
|
280c4037f3 | ||
|
|
007bdc735d | ||
|
|
41fc0a6a10 | ||
|
|
7753e4ac74 | ||
|
|
f74bf35202 | ||
|
|
aa472c96c6 | ||
|
|
0dcb880800 | ||
|
|
8daa6973b7 | ||
|
|
3884befa56 | ||
|
|
85b83f8eb1 | ||
|
|
f395dfaa66 | ||
|
|
b3124075ea | ||
|
|
aed909bdee | ||
|
|
d4cf5778e2 | ||
|
|
48de7dd00b | ||
|
|
686e64da3f | ||
|
|
7517dc8b3e | ||
|
|
660fe715e4 | ||
|
|
04ea29ec77 | ||
|
|
02c891cb16 | ||
|
|
0b5d21357e | ||
|
|
e0545b37d7 | ||
|
|
4161c21082 | ||
|
|
48d9a1771d | ||
|
|
471cdbb338 | ||
|
|
e77bca202b | ||
|
|
2e6c01e934 | ||
|
|
302c991c0c | ||
|
|
a89007ee03 | ||
|
|
8a89149d58 | ||
|
|
5e8bbc2868 | ||
|
|
4e48920309 | ||
|
|
8b145924b9 | ||
|
|
a0328e1e9c | ||
|
|
b7f5214a7b | ||
|
|
b0e4c4ac5f | ||
|
|
bbb67aac93 | ||
|
|
433c83b393 | ||
|
|
5577178eeb | ||
|
|
221b0242fa | ||
|
|
c05623f8af | ||
|
|
eb9884cc96 | ||
|
|
8e2490e6bc | ||
|
|
399243886e | ||
|
|
b0dbfcb805 | ||
|
|
dec2dc9230 | ||
|
|
2c44669d91 | ||
|
|
175d90aad7 | ||
|
|
e1e5f8f91d | ||
|
|
dff8abbe1e | ||
|
|
0f97091e61 | ||
|
|
ea0d7ee6cd | ||
|
|
75daa31d36 | ||
|
|
8c44319c45 | ||
|
|
dac574d933 | ||
|
|
fd4c5001e8 | ||
|
|
dfe0b23995 | ||
|
|
6158372eaa | ||
|
|
b1a778d7b2 | ||
|
|
bc0defd813 | ||
|
|
48e7a9fff0 | ||
|
|
c2fa58ef27 | ||
|
|
ea49cb144b | ||
|
|
b8e778a79d | ||
|
|
c70e12f0a2 | ||
|
|
6281205912 | ||
|
|
1ffc5c55b2 | ||
|
|
c3302e1720 | ||
|
|
25227944b5 | ||
|
|
06650e916a | ||
|
|
2e34dffed5 | ||
|
|
3db91f077f | ||
|
|
fae14f4dd9 | ||
|
|
d190b3ba87 | ||
|
|
554f3295cb | ||
|
|
cdc4689d6a | ||
|
|
21d18fd439 | ||
|
|
fd5ec52226 | ||
|
|
fc7ab22741 | ||
|
|
59cf59faa4 | ||
|
|
705c6ce7f1 | ||
|
|
f2937ab507 | ||
|
|
399829cf3c | ||
|
|
b694d6faf8 | ||
|
|
7acd1f6cdb | ||
|
|
09ca3bf118 | ||
|
|
6cac45c397 | ||
|
|
f18663d840 | ||
|
|
fefe1af9e6 | ||
|
|
87a8cf7513 | ||
|
|
7f31cb6023 | ||
|
|
138742c953 | ||
|
|
2c6efc45f2 | ||
|
|
24d795fbe3 | ||
|
|
7fae62d286 | ||
|
|
2135df07ea | ||
|
|
521beedf1c | ||
|
|
e362c9a8a9 | ||
|
|
78817ecfc1 | ||
|
|
8de7f56ff8 | ||
|
|
2e2017470e | ||
|
|
0a0cd98de3 | ||
|
|
17187dd23a | ||
|
|
10237b31cf | ||
|
|
6883efb857 | ||
|
|
1ca55cad8f | ||
|
|
f841eb1ef1 | ||
|
|
1ee49332f3 | ||
|
|
095e618785 | ||
|
|
57a902ad9f | ||
|
|
a7e229aaae | ||
|
|
10466d7851 | ||
|
|
238ebd273b | ||
|
|
0b08f4212e | ||
|
|
9aec5d20e3 | ||
|
|
a19bf2f066 | ||
|
|
bee68ee548 | ||
|
|
520c70ab22 | ||
|
|
67a7fbc94b | ||
|
|
1b8054e847 | ||
|
|
631a558a9f | ||
|
|
d5805b4e08 | ||
|
|
1dc15ce2e0 | ||
|
|
4145e811df | ||
|
|
fe8615d7d3 | ||
|
|
27ea5506bc | ||
|
|
394131a0d7 | ||
|
|
4d0d16023f | ||
|
|
60dd794b29 | ||
|
|
f13d104968 | ||
|
|
914099fd12 | ||
|
|
639725957d | ||
|
|
64bbe82c4a | ||
|
|
73a13e5697 | ||
|
|
e8ec5fb8ed | ||
|
|
0b500ca377 | ||
|
|
df58745ed8 | ||
|
|
55e8ab4930 | ||
|
|
4882fd3bf6 | ||
|
|
3e69f4bcc9 | ||
|
|
39428909d6 | ||
|
|
71d2ed40db | ||
|
|
9ad21a28a6 | ||
|
|
0c7acd6e71 | ||
|
|
ad26129375 | ||
|
|
4e7822fedd | ||
|
|
4dfcbe4ab1 | ||
|
|
8fb6f1f9ad | ||
|
|
11d4fafa6d | ||
|
|
be15557798 | ||
|
|
1e83757494 | ||
|
|
fad463a439 | ||
|
|
608563d029 | ||
|
|
5fcd22b09f | ||
|
|
65548da2ef | ||
|
|
111e513dac | ||
|
|
7fce3a98fe | ||
|
|
4018eeadb4 | ||
|
|
a8e4647fa6 | ||
|
|
36d3fca4fc | ||
|
|
89b28f36d3 | ||
|
|
b4f7d70a7a | ||
|
|
902bafbd0c | ||
|
|
7ad5c7fa70 | ||
|
|
4d29f056b7 | ||
|
|
36af9a3483 | ||
|
|
5e6f931433 | ||
|
|
beadfe720c | ||
|
|
92b4bfc383 | ||
|
|
092ed23085 | ||
|
|
c70fa3d00a | ||
|
|
fea0ba09f6 | ||
|
|
7d2af63eac | ||
|
|
2e73bf560e | ||
|
|
bfc8d7a9c6 | ||
|
|
a1e9936572 | ||
|
|
3675a50129 | ||
|
|
91229152bd | ||
|
|
a1993e590a | ||
|
|
52fe712b6f | ||
|
|
7f400b6b59 | ||
|
|
9cb88a99e6 | ||
|
|
b967a471b5 | ||
|
|
f40127df01 | ||
|
|
8c5e0ee274 | ||
|
|
2adf0b292b | ||
|
|
1c9b793c75 | ||
|
|
00c4ef7795 | ||
|
|
619657adf2 | ||
|
|
41db7c1035 | ||
|
|
d71834a777 | ||
|
|
d078af0397 | ||
|
|
2827d1d375 | ||
|
|
898499a4e3 | ||
|
|
eb010cc215 | ||
|
|
84440d5159 | ||
|
|
ff3744ead9 | ||
|
|
d00fe4b3eb | ||
|
|
72a86a5d7f | ||
|
|
811ab622df | ||
|
|
e76b72cddf | ||
|
|
e9edacc9c3 | ||
|
|
a6694a6b29 | ||
|
|
59323cd375 | ||
|
|
6dc714e449 | ||
|
|
5d75a9b039 | ||
|
|
92b4cf5997 | ||
|
|
906e61b1b2 | ||
|
|
dab230f9d5 | ||
|
|
5e8bcda9fc | ||
|
|
d4bf83ae86 | ||
|
|
8244a48511 | ||
|
|
43ffa18a5f | ||
|
|
b131a32d0c | ||
|
|
c81787b837 | ||
|
|
f9df3c6db6 | ||
|
|
e383ec30e3 | ||
|
|
1197c4791e | ||
|
|
106c684389 | ||
|
|
a199ced77b | ||
|
|
77ac73666e | ||
|
|
64975557c2 | ||
|
|
b62321806a | ||
|
|
7c392082b1 | ||
|
|
392e9a6417 | ||
|
|
fb5f684e7e | ||
|
|
0dbc794223 | ||
|
|
6295ed3b7b | ||
|
|
0f2d889858 | ||
|
|
60e6b01982 | ||
|
|
1dc69f9e22 | ||
|
|
fcca41ad93 | ||
|
|
9897747425 | ||
|
|
4dbe9dcefd | ||
|
|
be8bee90f2 | ||
|
|
f8879029ec | ||
|
|
7b491b3cee | ||
|
|
2ece865417 | ||
|
|
0d8c693c65 | ||
|
|
9f7d1396b6 | ||
|
|
5ad323486e | ||
|
|
19b4248929 | ||
|
|
c135c4cac3 | ||
|
|
fb1751ff26 | ||
|
|
34f32d4ac6 | ||
|
|
36f1e47c0a | ||
|
|
c5dd5c2e46 | ||
|
|
d1d920372a | ||
|
|
12d8fd3ed0 | ||
|
|
729aed536e | ||
|
|
b588f8c90d | ||
|
|
1f1007a0f1 | ||
|
|
2ec37e2802 | ||
|
|
3126b3847b | ||
|
|
41e0157e9d | ||
|
|
1d64410849 | ||
|
|
55034b0f40 | ||
|
|
acffcf3774 | ||
|
|
fba0eb527b | ||
|
|
d9fc2714a6 | ||
|
|
d1f1772d74 | ||
|
|
bf1d47e4f3 | ||
|
|
3da564dfd1 | ||
|
|
abd30b9d91 | ||
|
|
c86408cbd6 | ||
|
|
1f51974c48 | ||
|
|
29f4cde296 | ||
|
|
c4a0035062 | ||
|
|
6724ddbd7d | ||
|
|
8d8ed63122 | ||
|
|
6f4ea81907 | ||
|
|
6d162744ec | ||
|
|
145b4aab5b | ||
|
|
2e4eb56d0b | ||
|
|
39c019193e | ||
|
|
faec563b4a | ||
|
|
9840f8e07a | ||
|
|
3a5895fa0f | ||
|
|
1bac5c8a7f | ||
|
|
dd76c60e2a | ||
|
|
ac78cc7dc5 | ||
|
|
66ac2d2002 | ||
|
|
0bd099d704 | ||
|
|
d2dac3cfb4 | ||
|
|
e81dc0f31d | ||
|
|
5015597183 | ||
|
|
64e2af9fa2 | ||
|
|
debb4b69fc | ||
|
|
c6d3dda171 | ||
|
|
7c0693c2ff | ||
|
|
76355d4857 | ||
|
|
404aaadefc | ||
|
|
be027e10be | ||
|
|
1c8cad92f8 | ||
|
|
fabb8ea3b7 | ||
|
|
2ee4abca54 | ||
|
|
5896928030 | ||
|
|
8652544982 | ||
|
|
a70338ec3c | ||
|
|
a84878abfc | ||
|
|
8421a391b7 | ||
|
|
f9340ec3d6 | ||
|
|
0cca9c5f83 | ||
|
|
13d60b7f47 | ||
|
|
f74771a9cc | ||
|
|
f24ba41930 | ||
|
|
f412580a4a | ||
|
|
5cde1b4438 | ||
|
|
7cfb57b1bc | ||
|
|
a478b58f97 | ||
|
|
8297f4f880 | ||
|
|
4c67343d33 | ||
|
|
53cc6998f0 | ||
|
|
d857d71e0d | ||
|
|
003c2766ce | ||
|
|
78594098cc | ||
|
|
3c31bc91e4 | ||
|
|
72141bab7a | ||
|
|
bc52463aa4 | ||
|
|
9fa68908a9 | ||
|
|
740dbec1b1 | ||
|
|
440d203730 | ||
|
|
1a83c67e2c | ||
|
|
34a707a2a5 | ||
|
|
cabb2cdfc9 | ||
|
|
dc52ccf172 | ||
|
|
bdd7fec3c3 | ||
|
|
bd8b03d089 | ||
|
|
a10588b0ce | ||
|
|
3503b86e12 | ||
|
|
9e124980cd | ||
|
|
675aa5e122 | ||
|
|
2a62d4e938 | ||
|
|
9cbb51cc86 | ||
|
|
7790f81d15 | ||
|
|
fdbff4879b | ||
|
|
739e679a66 | ||
|
|
0dea507d85 | ||
|
|
4f524c47d8 | ||
|
|
0c74201eb4 | ||
|
|
1e13258bc9 | ||
|
|
2c7cea042f | ||
|
|
fc92b66ff4 | ||
|
|
89ae24a2ac | ||
|
|
bd4a23b287 | ||
|
|
1b8c4d9f08 | ||
|
|
a1187decb5 | ||
|
|
b9d7aa182d | ||
|
|
b908c1118d | ||
|
|
fab776764f | ||
|
|
f3e17e399d | ||
|
|
3706ca873b | ||
|
|
10c565f32b | ||
|
|
d237e5b9e6 | ||
|
|
3c7e1ce12f | ||
|
|
f422f64325 | ||
|
|
df0656b345 | ||
|
|
1fc9c56c8f | ||
|
|
893a013ae9 | ||
|
|
6d3812d1d0 | ||
|
|
6422aec780 | ||
|
|
fcd7b8a825 | ||
|
|
575da1f574 | ||
|
|
3a507f5bf6 | ||
|
|
a3d6e3740c | ||
|
|
53e0330e21 | ||
|
|
f935c1e28a | ||
|
|
6a0c708224 | ||
|
|
a407d2d94d | ||
|
|
53605bd6e8 | ||
|
|
944faaa927 | ||
|
|
fb7cfc09de | ||
|
|
b9ae73e322 | ||
|
|
c28dbb6b9b | ||
|
|
dcc26b67a6 | ||
|
|
2627d320f3 | ||
|
|
fd4bc28835 | ||
|
|
63e4d588a2 | ||
|
|
feb1680909 | ||
|
|
9d21489b8e | ||
|
|
e70ed1900f | ||
|
|
d65b5a089a | ||
|
|
84643647cc | ||
|
|
57f487db6d | ||
|
|
0862cad2ce | ||
|
|
5ae4f59315 | ||
|
|
3fc3005ba5 | ||
|
|
20f48edd00 | ||
|
|
b854aad830 | ||
|
|
6cbe03f656 | ||
|
|
63cac9a75e | ||
|
|
08616f1057 | ||
|
|
e6193b1020 | ||
|
|
f28b9903f3 | ||
|
|
7bf2d119ea | ||
|
|
527bded1b2 | ||
|
|
1208c53ebb | ||
|
|
973efa2d27 | ||
|
|
d09588b915 | ||
|
|
19aa313753 | ||
|
|
9db12cd25f | ||
|
|
69e785e6ee | ||
|
|
159fce6839 | ||
|
|
08e4084458 | ||
|
|
06f210846c | ||
|
|
74629e4b55 | ||
|
|
6e8af6e52f | ||
|
|
38611a7af2 | ||
|
|
f089acc01a | ||
|
|
5a7ecb48d6 | ||
|
|
5b3880aa5e | ||
|
|
4653aafbee | ||
|
|
281f2992ff | ||
|
|
4bdcf8bfe1 | ||
|
|
62acaa95bf | ||
|
|
369bff339d | ||
|
|
5332d387f4 | ||
|
|
f0d08ef263 | ||
|
|
e4334f9499 | ||
|
|
627ec56262 | ||
|
|
251cf8121e | ||
|
|
87fad1b0e9 | ||
|
|
698516ac8c | ||
|
|
dae5d75674 | ||
|
|
95facffa9b | ||
|
|
682e0a9952 | ||
|
|
9a97dfdc70 | ||
|
|
2f383699f9 | ||
|
|
8137a137b3 | ||
|
|
1339d51796 | ||
|
|
dce447c974 | ||
|
|
ef5b650991 | ||
|
|
fd19c7b4f3 | ||
|
|
727297642c | ||
|
|
cd457801e7 | ||
|
|
392e7afdfe | ||
|
|
b425e34907 | ||
|
|
7c2e2fc7b1 | ||
|
|
9bac0c94a8 | ||
|
|
631568681e | ||
|
|
6599861dbb | ||
|
|
3bcd831531 | ||
|
|
fafd4a4fca | ||
|
|
5b69178f08 | ||
|
|
479a30369d | ||
|
|
ba522a4acb | ||
|
|
3ab6d2c418 | ||
|
|
b71b2245cb | ||
|
|
bbea0eb429 | ||
|
|
c28c5cfbaa | ||
|
|
be3fd30cb2 | ||
|
|
e3942da906 | ||
|
|
d66be1a228 | ||
|
|
72085d3711 | ||
|
|
e7cdbb1db5 | ||
|
|
7a592e0cea | ||
|
|
107a4b87d5 | ||
|
|
8b0ec3bef7 | ||
|
|
aaded40a4f | ||
|
|
7f77bb88ab | ||
|
|
95c3b2caad | ||
|
|
150c450bf6 | ||
|
|
932c23afba | ||
|
|
2e0107e46f | ||
|
|
d22a3a4c7d | ||
|
|
f93f870626 | ||
|
|
a2e55c5dda | ||
|
|
32748509ea | ||
|
|
970a662170 | ||
|
|
8925323a6e | ||
|
|
10915a601c | ||
|
|
2cc1af19b1 | ||
|
|
6670ca4fe1 | ||
|
|
d3f77c848c | ||
|
|
3f2ca67051 | ||
|
|
3812101366 | ||
|
|
db72acfc4f | ||
|
|
1b3d760f5f | ||
|
|
539f5de6d9 | ||
|
|
fe2d072056 | ||
|
|
469ee67942 | ||
|
|
95991d8f5d | ||
|
|
7e12585429 | ||
|
|
c335078aac | ||
|
|
f1fbbb5623 | ||
|
|
b6502e0a38 | ||
|
|
2fab3e5897 | ||
|
|
a4ea89d6e7 | ||
|
|
c451f253fa | ||
|
|
03121b6c6d | ||
|
|
007d20c315 | ||
|
|
26d6fa9fcc | ||
|
|
b9f3e52ba4 | ||
|
|
03d2665725 | ||
|
|
ba7fd2fe4a | ||
|
|
a8bcae16a4 | ||
|
|
2f19d31a81 | ||
|
|
54692e3a75 | ||
|
|
029235a34d | ||
|
|
180573d69f | ||
|
|
8fa900335a | ||
|
|
a527080ccd | ||
|
|
ce6d60c611 | ||
|
|
ff9fd9b377 | ||
|
|
9e61be15e6 | ||
|
|
b1d53b42ca | ||
|
|
143a2151fb | ||
|
|
7fd29e30b3 | ||
|
|
efe46e1fdc | ||
|
|
2cf4ca07f4 | ||
|
|
b88207f0dd | ||
|
|
70e10b1d56 | ||
|
|
ced994d94a | ||
|
|
354bd2429b | ||
|
|
c1d3d15d4e | ||
|
|
95b2b83ac6 | ||
|
|
c3c4d966b4 | ||
|
|
a768fecfde | ||
|
|
01e41a4014 | ||
|
|
eb4852683b | ||
|
|
6218333fec | ||
|
|
f32d8cbefc | ||
|
|
3e985502c2 | ||
|
|
ad3132e8c2 | ||
|
|
830ace0e47 | ||
|
|
1c36893729 | ||
|
|
a87aaab6f7 | ||
|
|
21699b8960 | ||
|
|
87ee25d404 | ||
|
|
c1714f9e6b | ||
|
|
754765b766 | ||
|
|
419f0cf571 | ||
|
|
55d9ac521f | ||
|
|
3406b3b471 | ||
|
|
f067ad2458 | ||
|
|
7eafd0a969 | ||
|
|
116f30bc0a | ||
|
|
04aa20e836 | ||
|
|
3bf4cc861b | ||
|
|
3c6620a3f9 | ||
|
|
ab0ea5a1d8 | ||
|
|
f894ad2686 | ||
|
|
570340d4e5 | ||
|
|
7dbe706282 | ||
|
|
0c9559f695 | ||
|
|
3113e2b64e | ||
|
|
f0ce992a27 | ||
|
|
6334cfc8bc | ||
|
|
451a569617 | ||
|
|
44d82d6544 | ||
|
|
618ec66401 | ||
|
|
d24ae86c12 | ||
|
|
32db74e50d | ||
|
|
503c89f08c | ||
|
|
c9e6e65991 | ||
|
|
04bd8a1639 | ||
|
|
a024294e19 |
@@ -0,0 +1,19 @@
|
|||||||
|
services:
|
||||||
|
dsm:
|
||||||
|
container_name: dsm
|
||||||
|
image: ghcr.io/vdsm/virtual-dsm
|
||||||
|
environment:
|
||||||
|
RAM_SIZE: "half"
|
||||||
|
DISK_SIZE: "max"
|
||||||
|
CPU_CORES: "max"
|
||||||
|
devices:
|
||||||
|
- /dev/kvm
|
||||||
|
- /dev/net/tun
|
||||||
|
cap_add:
|
||||||
|
- NET_ADMIN
|
||||||
|
ports:
|
||||||
|
- 5000:5000
|
||||||
|
volumes:
|
||||||
|
- ./dsm:/storage
|
||||||
|
restart: on-failure
|
||||||
|
stop_grace_period: 2m
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"name": "Virtual DSM",
|
||||||
|
"service": "dsm",
|
||||||
|
"forwardPorts": [5000],
|
||||||
|
"portsAttributes": {
|
||||||
|
"5000": {
|
||||||
|
"label": "Web",
|
||||||
|
"onAutoForward": "notify"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"otherPortsAttributes": {
|
||||||
|
"onAutoForward": "ignore"
|
||||||
|
},
|
||||||
|
"dockerComposeFile": "codespaces.yml"
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
.dockerignore
|
.dockerignore
|
||||||
|
.devcontainer
|
||||||
.git
|
.git
|
||||||
.github
|
.github
|
||||||
.gitignore
|
.gitignore
|
||||||
@@ -6,7 +7,10 @@
|
|||||||
.gitmodules
|
.gitmodules
|
||||||
Dockerfile
|
Dockerfile
|
||||||
Dockerfile.archive
|
Dockerfile.archive
|
||||||
|
compose.yml
|
||||||
|
compose.yaml
|
||||||
docker-compose.yml
|
docker-compose.yml
|
||||||
|
docker-compose.yaml
|
||||||
|
|
||||||
*.md
|
*.md
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
name: "\U0001F6A8 Technical issue"
|
||||||
|
description: When you're experiencing problems using the container
|
||||||
|
body:
|
||||||
|
- type: input
|
||||||
|
id: os
|
||||||
|
attributes:
|
||||||
|
label: Operating system
|
||||||
|
description: Your Linux distribution (can be shown by `lsb_release -a`).
|
||||||
|
placeholder: e.g. Ubuntu 24.04
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: summary
|
||||||
|
attributes:
|
||||||
|
label: Description
|
||||||
|
description: A clear and concise description of your issue.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: compose
|
||||||
|
attributes:
|
||||||
|
label: Docker compose
|
||||||
|
description: The compose file (or otherwise the `docker run` command used).
|
||||||
|
render: yaml
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: log
|
||||||
|
attributes:
|
||||||
|
label: Docker log
|
||||||
|
description: The logfile of the container (as shown by `docker logs dsm`).
|
||||||
|
render: shell
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: screenshot
|
||||||
|
attributes:
|
||||||
|
label: Screenshots (optional)
|
||||||
|
description: Screenshots that might help to make the problem more clear.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
name: "\U0001F680 Feature request"
|
||||||
|
description: Suggest an idea for improving the container
|
||||||
|
title: "[Feature]: "
|
||||||
|
labels: ["enhancement"]
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
id: problem
|
||||||
|
attributes:
|
||||||
|
label: Is your proposal related to a problem?
|
||||||
|
description: |
|
||||||
|
Provide a clear and concise description of what the problem is.
|
||||||
|
For example, "I'm always frustrated when..."
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: solution
|
||||||
|
attributes:
|
||||||
|
label: Describe the solution you'd like.
|
||||||
|
description: |
|
||||||
|
Provide a clear and concise description of what you want to happen.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: alternatives
|
||||||
|
attributes:
|
||||||
|
label: Describe alternatives you've considered.
|
||||||
|
description: |
|
||||||
|
Let us know about other solutions you've tried or researched.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: context
|
||||||
|
attributes:
|
||||||
|
label: Additional context
|
||||||
|
description: |
|
||||||
|
Is there anything else you can add about the proposal?
|
||||||
|
You might want to link to related issues here, if you haven't already.
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
name: "\U0001F41E Bug report"
|
||||||
|
description: Create a report to help us improve the container
|
||||||
|
title: "[Bug]: "
|
||||||
|
labels: ["bug"]
|
||||||
|
body:
|
||||||
|
- type: input
|
||||||
|
id: os
|
||||||
|
attributes:
|
||||||
|
label: Operating system
|
||||||
|
description: Your Linux distribution (can be shown by `lsb_release -a`).
|
||||||
|
placeholder: e.g. Ubuntu 24.04
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: summary
|
||||||
|
attributes:
|
||||||
|
label: Description
|
||||||
|
description: Describe the expected behaviour, the actual behaviour, and the steps to reproduce.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: compose
|
||||||
|
attributes:
|
||||||
|
label: Docker compose
|
||||||
|
description: The compose file (or otherwise the `docker run` command used).
|
||||||
|
render: yaml
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: log
|
||||||
|
attributes:
|
||||||
|
label: Docker log
|
||||||
|
description: The logfile of the container (as shown by `docker logs dsm`).
|
||||||
|
render: shell
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: screenshot
|
||||||
|
attributes:
|
||||||
|
label: Screenshots (optional)
|
||||||
|
description: Screenshots that might help to make the problem more clear.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
name: "\U00002753 General question"
|
||||||
|
description: Questions about the container not related to an issue
|
||||||
|
title: "[Question]: "
|
||||||
|
labels: ["question"]
|
||||||
|
body:
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: Is your question not already answered in the FAQ?
|
||||||
|
description: Please read the [FAQ](https://github.com/vdsm/virtual-dsm/blob/master/readme.md) carefully to avoid asking duplicate questions.
|
||||||
|
options:
|
||||||
|
- label: I made sure the question is not listed in the [FAQ](https://github.com/vdsm/virtual-dsm/blob/master/readme.md).
|
||||||
|
required: true
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: Is this a general question and not a technical issue?
|
||||||
|
description: For questions related to issues you must use the [technical issue](https://github.com/vdsm/virtual-dsm/issues/new?assignees=&labels=&projects=&template=1-issue.yml) form instead. It contains all the right fields (system info, logfiles, etc.) we need in order to be able to help you.
|
||||||
|
options:
|
||||||
|
- label: I am sure my question is not about a technical issue.
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: question
|
||||||
|
attributes:
|
||||||
|
label: Question
|
||||||
|
description: What's the question you have about the container?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
@@ -4,7 +4,11 @@ updates:
|
|||||||
directory: /
|
directory: /
|
||||||
schedule:
|
schedule:
|
||||||
interval: weekly
|
interval: weekly
|
||||||
|
cooldown:
|
||||||
|
default-days: 7
|
||||||
- package-ecosystem: github-actions
|
- package-ecosystem: github-actions
|
||||||
directory: /
|
directory: /
|
||||||
schedule:
|
schedule:
|
||||||
interval: weekly
|
interval: weekly
|
||||||
|
cooldown:
|
||||||
|
default-days: 7
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
{
|
{
|
||||||
"extends": [
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
"config:base",
|
"extends": ["config:recommended", ":disableDependencyDashboard"]
|
||||||
":disableDependencyDashboard"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
+52
-59
@@ -2,16 +2,10 @@ name: Build
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
|
||||||
branches:
|
concurrency:
|
||||||
- master
|
group: build
|
||||||
paths-ignore:
|
cancel-in-progress: false
|
||||||
- '**/*.md'
|
|
||||||
- '**/*.yml'
|
|
||||||
- '.gitignore'
|
|
||||||
- '.dockerignore'
|
|
||||||
- '.github/**'
|
|
||||||
- '.github/workflows/**'
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
shellcheck:
|
shellcheck:
|
||||||
@@ -22,80 +16,67 @@ jobs:
|
|||||||
needs: shellcheck
|
needs: shellcheck
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
|
actions: write
|
||||||
packages: write
|
packages: write
|
||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
-
|
|
||||||
name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
-
|
-
|
||||||
name: Checkout
|
name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
-
|
-
|
||||||
name: Prepare Docker build
|
name: Docker metadata
|
||||||
id: prepare
|
id: meta
|
||||||
run: |
|
uses: docker/metadata-action@v6
|
||||||
|
with:
|
||||||
PLATFORMS="linux/amd64,linux/arm64"
|
context: git
|
||||||
VERSION="${{ vars.MAJOR }}.${{ vars.MINOR }}"
|
images: |
|
||||||
|
${{ secrets.DOCKERHUB_REPO }}
|
||||||
TAGS=()
|
ghcr.io/${{ github.repository }}
|
||||||
TAGS=("${{ github.repository }}:latest")
|
tags: |
|
||||||
TAGS+=("${{ github.repository }}:${VERSION}")
|
type=raw,value=latest,priority=100
|
||||||
#TAGS+=("${{ secrets.DOCKERHUB_MIRROR }}:latest")
|
type=raw,value=${{ vars.MAJOR }}.${{ vars.MINOR }}
|
||||||
#TAGS+=("${{ secrets.DOCKERHUB_MIRROR }}:${VERSION}")
|
labels: |
|
||||||
TAGS+=("ghcr.io/${{ github.repository }}:latest")
|
org.opencontainers.image.title=${{ vars.NAME }}
|
||||||
TAGS+=("ghcr.io/${{ github.repository }}:${VERSION}")
|
env:
|
||||||
|
DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index
|
||||||
echo "tags=${TAGS[@]}" >> $GITHUB_OUTPUT
|
-
|
||||||
echo "version=${VERSION}" >> $GITHUB_OUTPUT
|
name: Set up Docker Buildx
|
||||||
echo "docker_platforms=${PLATFORMS}" >> $GITHUB_OUTPUT
|
uses: docker/setup-buildx-action@v4
|
||||||
echo "build_date=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT
|
|
||||||
-
|
-
|
||||||
name: Login into Docker Hub
|
name: Login into Docker Hub
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v4
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
-
|
-
|
||||||
name: Login to GitHub Container Registry
|
name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v4
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
-
|
-
|
||||||
name: Build Docker image
|
name: Build Docker image
|
||||||
run: |
|
uses: docker/build-push-action@v7
|
||||||
|
with:
|
||||||
TAGS=(${{ steps.prepare.outputs.tags }})
|
context: .
|
||||||
|
push: true
|
||||||
echo "Build date: ${{ steps.prepare.outputs.build_date }}"
|
provenance: false
|
||||||
echo "Docker platform: ${{ steps.prepare.outputs.docker_platforms }}"
|
platforms: linux/amd64,linux/arm64
|
||||||
echo "Tags: ${{ steps.prepare.outputs.tags }}"
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
docker buildx build --platform ${{ steps.prepare.outputs.docker_platforms }} \
|
annotations: ${{ steps.meta.outputs.annotations }}
|
||||||
--output "type=image,push=true" \
|
build-args: |
|
||||||
--progress=plain \
|
VERSION_ARG=${{ steps.meta.outputs.version }}
|
||||||
--build-arg "BUILD_ARG=${GITHUB_RUN_ID}" \
|
|
||||||
--build-arg "VERSION_ARG=${{ steps.prepare.outputs.version }}" \
|
|
||||||
--build-arg "DATE_ARG=${{ steps.prepare.outputs.build_date }}" \
|
|
||||||
--build-arg "VCS_REF=${GITHUB_SHA::8}" \
|
|
||||||
$(printf "%s" "${TAGS[@]/#/ --tag }" ) .
|
|
||||||
-
|
|
||||||
name: Clear Docker credentials
|
|
||||||
run: |
|
|
||||||
rm -f ${HOME}/.docker/config.json
|
|
||||||
-
|
-
|
||||||
name: Create a release
|
name: Create a release
|
||||||
uses: action-pack/github-release@v2
|
uses: action-pack/github-release@v2
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.REPO_ACCESS_TOKEN }}
|
|
||||||
with:
|
with:
|
||||||
tag: "v${{ steps.prepare.outputs.version }}"
|
tag: "v${{ steps.meta.outputs.version }}"
|
||||||
title: "v${{ steps.prepare.outputs.version }}"
|
title: "v${{ steps.meta.outputs.version }}"
|
||||||
|
token: ${{ secrets.REPO_ACCESS_TOKEN }}
|
||||||
-
|
-
|
||||||
name: Increment version variable
|
name: Increment version variable
|
||||||
uses: action-pack/bump@v2
|
uses: action-pack/bump@v2
|
||||||
@@ -108,3 +89,15 @@ jobs:
|
|||||||
url: ${{ secrets.GITLAB_URL }}
|
url: ${{ secrets.GITLAB_URL }}
|
||||||
token: ${{ secrets.GITLAB_TOKEN }}
|
token: ${{ secrets.GITLAB_TOKEN }}
|
||||||
username: ${{ secrets.GITLAB_USERNAME }}
|
username: ${{ secrets.GITLAB_USERNAME }}
|
||||||
|
-
|
||||||
|
name: Send mail
|
||||||
|
uses: action-pack/send-mail@v1
|
||||||
|
with:
|
||||||
|
to: ${{secrets.MAILTO}}
|
||||||
|
from: Github Actions <${{secrets.MAILTO}}>
|
||||||
|
connection_url: ${{secrets.MAIL_CONNECTION}}
|
||||||
|
subject: Build of ${{ github.event.repository.name }} v${{ steps.meta.outputs.version }} completed
|
||||||
|
body: |
|
||||||
|
The build job of ${{ github.event.repository.name }} v${{ steps.meta.outputs.version }} was completed successfully!
|
||||||
|
|
||||||
|
See https://github.com/${{ github.repository }}/actions for more information.
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
on: [workflow_call]
|
on: [workflow_call]
|
||||||
|
|
||||||
name: "Check"
|
name: "Check"
|
||||||
|
|
||||||
permissions: {}
|
permissions: {}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -7,8 +9,29 @@ jobs:
|
|||||||
name: shellcheck
|
name: shellcheck
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
-
|
||||||
- name: Run ShellCheck
|
name: Checkout
|
||||||
uses: ludeeus/action-shellcheck@master
|
uses: actions/checkout@v7
|
||||||
env:
|
- parallel:
|
||||||
SHELLCHECK_OPTS: -x -e SC2001 -e SC2002 -e SC2223 -e SC2034 -e SC2064 -e SC2317 -e SC2028 -e SC2153 -e SC2004
|
-
|
||||||
|
name: Run ShellCheck
|
||||||
|
uses: ludeeus/action-shellcheck@master
|
||||||
|
env:
|
||||||
|
SHELLCHECK_OPTS: >-
|
||||||
|
-x
|
||||||
|
--source-path=src
|
||||||
|
-e SC1091
|
||||||
|
-e SC2001
|
||||||
|
-e SC2034
|
||||||
|
-e SC2317
|
||||||
|
-e SC2153
|
||||||
|
-
|
||||||
|
name: Lint Dockerfile
|
||||||
|
uses: hadolint/hadolint-action@v3.3.0
|
||||||
|
with:
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
ignore: DL3008
|
||||||
|
failure-threshold: warning
|
||||||
|
-
|
||||||
|
name: Validate JSON and YML files
|
||||||
|
uses: GrantBirki/json-yaml-validate@v5
|
||||||
|
|||||||
+12
-10
@@ -12,13 +12,15 @@ jobs:
|
|||||||
dockerHubDescription:
|
dockerHubDescription:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
-
|
||||||
-
|
name: Checkout repo
|
||||||
name: Docker Hub Description
|
uses: actions/checkout@v7
|
||||||
uses: peter-evans/dockerhub-description@v3
|
-
|
||||||
with:
|
name: Docker Hub Description
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
uses: peter-evans/dockerhub-description@v5
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
with:
|
||||||
repository: ${{ secrets.DOCKERHUB_REPO }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
short-description: ${{ github.event.repository.description }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
readme-filepath: ./readme.md
|
repository: ${{ secrets.DOCKERHUB_REPO }}
|
||||||
|
short-description: ${{ github.event.repository.description }}
|
||||||
|
readme-filepath: ./readme.md
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
name: "Review"
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
|
checks: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
review:
|
||||||
|
name: review
|
||||||
|
uses: action-pack/.github/.github/workflows/review.yml@master
|
||||||
@@ -1,10 +1,6 @@
|
|||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
|
||||||
- '**/*.sh'
|
|
||||||
- '.github/workflows/test.yml'
|
|
||||||
- '.github/workflows/check.yml'
|
|
||||||
|
|
||||||
name: "Test"
|
name: "Test"
|
||||||
permissions: {}
|
permissions: {}
|
||||||
|
|||||||
+268
-1
@@ -1 +1,268 @@
|
|||||||
build.sh
|
##############################
|
||||||
|
# Operating System Files
|
||||||
|
##############################
|
||||||
|
|
||||||
|
.DS_Store
|
||||||
|
.AppleDouble
|
||||||
|
.LSOverride
|
||||||
|
Thumbs.db
|
||||||
|
ehthumbs.db
|
||||||
|
Desktop.ini
|
||||||
|
Icon?
|
||||||
|
$RECYCLE.BIN/
|
||||||
|
.Spotlight-V100/
|
||||||
|
.Trashes/
|
||||||
|
.fseventsd
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# IDEs
|
||||||
|
##############################
|
||||||
|
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# VS Code
|
||||||
|
##############################
|
||||||
|
|
||||||
|
.history/
|
||||||
|
*.code-workspace
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# Vim
|
||||||
|
##############################
|
||||||
|
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
Session.vim
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# Sublime
|
||||||
|
##############################
|
||||||
|
|
||||||
|
*.sublime-workspace
|
||||||
|
*.sublime-project
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# Temporary Files
|
||||||
|
##############################
|
||||||
|
|
||||||
|
*.tmp
|
||||||
|
*.temp
|
||||||
|
*.bak
|
||||||
|
*.old
|
||||||
|
*.orig
|
||||||
|
*.rej
|
||||||
|
*.save
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# Logs
|
||||||
|
##############################
|
||||||
|
|
||||||
|
*.log
|
||||||
|
logs/
|
||||||
|
log/
|
||||||
|
*.out
|
||||||
|
*.err
|
||||||
|
*.trace
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# Runtime
|
||||||
|
##############################
|
||||||
|
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# Secrets
|
||||||
|
##############################
|
||||||
|
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
*.pem
|
||||||
|
*.key
|
||||||
|
*.crt
|
||||||
|
*.cer
|
||||||
|
*.p12
|
||||||
|
*.pfx
|
||||||
|
*.kdbx
|
||||||
|
*.secret
|
||||||
|
*.token
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# SSH
|
||||||
|
##############################
|
||||||
|
|
||||||
|
.ssh/
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# Docker
|
||||||
|
##############################
|
||||||
|
|
||||||
|
docker-compose.override.yml
|
||||||
|
docker-compose.local.yml
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# VM Storage
|
||||||
|
##############################
|
||||||
|
|
||||||
|
storage/
|
||||||
|
windows/
|
||||||
|
downloads/
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# Disk Images
|
||||||
|
##############################
|
||||||
|
|
||||||
|
*.qcow2
|
||||||
|
*.qcow
|
||||||
|
*.vhd
|
||||||
|
*.vhdx
|
||||||
|
*.vdi
|
||||||
|
*.raw
|
||||||
|
*.img
|
||||||
|
*.iso
|
||||||
|
*.bin
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# QEMU
|
||||||
|
##############################
|
||||||
|
|
||||||
|
*.nvram
|
||||||
|
*.fd
|
||||||
|
*.efi
|
||||||
|
*.sock
|
||||||
|
*.monitor
|
||||||
|
*.serial
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# Samba
|
||||||
|
##############################
|
||||||
|
|
||||||
|
shared/
|
||||||
|
share/
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# Backups
|
||||||
|
##############################
|
||||||
|
|
||||||
|
backup/
|
||||||
|
backups/
|
||||||
|
*.backup
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# Cache
|
||||||
|
##############################
|
||||||
|
|
||||||
|
.cache/
|
||||||
|
.cache-loader/
|
||||||
|
.tmp/
|
||||||
|
temp/
|
||||||
|
tmp/
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# Python
|
||||||
|
##############################
|
||||||
|
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
.pytest_cache/
|
||||||
|
.mypy_cache/
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# Node
|
||||||
|
##############################
|
||||||
|
|
||||||
|
node_modules/
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# Build
|
||||||
|
##############################
|
||||||
|
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
out/
|
||||||
|
release/
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# Coverage
|
||||||
|
##############################
|
||||||
|
|
||||||
|
coverage/
|
||||||
|
.coverage
|
||||||
|
coverage.xml
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# Archives
|
||||||
|
##############################
|
||||||
|
|
||||||
|
*.zip
|
||||||
|
*.tar
|
||||||
|
*.tar.gz
|
||||||
|
*.tgz
|
||||||
|
*.7z
|
||||||
|
*.rar
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# Generated Config
|
||||||
|
##############################
|
||||||
|
|
||||||
|
config.local.*
|
||||||
|
settings.local.*
|
||||||
|
local.env
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# Test Files
|
||||||
|
##############################
|
||||||
|
|
||||||
|
test-output/
|
||||||
|
playwright-report/
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# macOS
|
||||||
|
##############################
|
||||||
|
|
||||||
|
.AppleDB
|
||||||
|
.AppleDesktop
|
||||||
|
Network Trash Folder
|
||||||
|
Temporary Items
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# Linux
|
||||||
|
##############################
|
||||||
|
|
||||||
|
*~
|
||||||
|
.nfs*
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# Windows
|
||||||
|
##############################
|
||||||
|
|
||||||
|
*.stackdump
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# Misc
|
||||||
|
##############################
|
||||||
|
|
||||||
|
*.cache
|
||||||
|
*.lock
|
||||||
|
*.lock.json
|
||||||
|
*.bak.*
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# Keep Examples
|
||||||
|
##############################
|
||||||
|
|
||||||
|
!.gitkeep
|
||||||
|
!.env.example
|
||||||
|
|||||||
+79
-57
@@ -1,69 +1,91 @@
|
|||||||
FROM qemux/qemu-host as builder
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
# FROM golang as builder
|
|
||||||
# WORKDIR /
|
|
||||||
# RUN git clone https://github.com/qemu-tools/qemu-host.git
|
|
||||||
# WORKDIR /qemu-host/src
|
|
||||||
# RUN go mod download
|
|
||||||
# RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o /qemu-host.bin .
|
|
||||||
|
|
||||||
|
FROM qemux/qemu-host:2.06 AS builder
|
||||||
FROM debian:trixie-slim
|
FROM debian:trixie-slim
|
||||||
|
|
||||||
|
ARG TARGETARCH
|
||||||
|
ARG TARGETPLATFORM
|
||||||
|
|
||||||
|
ARG VERSION_ARG="0.0"
|
||||||
|
ARG VERSION_CSTRUCT="4.7"
|
||||||
|
ARG VERSION_PASST="2026_07_16"
|
||||||
|
|
||||||
ARG DEBCONF_NOWARNINGS="yes"
|
ARG DEBCONF_NOWARNINGS="yes"
|
||||||
ARG DEBIAN_FRONTEND noninteractive
|
ARG DEBIAN_FRONTEND="noninteractive"
|
||||||
|
ARG DEBCONF_NONINTERACTIVE_SEEN="true"
|
||||||
|
|
||||||
RUN apt-get update && apt-get -y upgrade && \
|
RUN <<EOF
|
||||||
apt-get --no-install-recommends -y install \
|
set -eu
|
||||||
tini \
|
|
||||||
curl \
|
|
||||||
cpio \
|
|
||||||
wget \
|
|
||||||
fdisk \
|
|
||||||
unzip \
|
|
||||||
socat \
|
|
||||||
procps \
|
|
||||||
xz-utils \
|
|
||||||
iptables \
|
|
||||||
iproute2 \
|
|
||||||
dnsmasq \
|
|
||||||
net-tools \
|
|
||||||
ca-certificates \
|
|
||||||
netcat-openbsd \
|
|
||||||
qemu-system-x86 \
|
|
||||||
&& apt-get clean \
|
|
||||||
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
|
||||||
|
|
||||||
COPY run/*.sh /run/
|
apt-get update
|
||||||
COPY --from=builder /qemu-host.bin /run/host.bin
|
apt-get --no-install-recommends -y install \
|
||||||
|
jq \
|
||||||
|
tini \
|
||||||
|
curl \
|
||||||
|
wget \
|
||||||
|
fdisk \
|
||||||
|
unzip \
|
||||||
|
nginx \
|
||||||
|
procps \
|
||||||
|
ipcalc \
|
||||||
|
ethtool \
|
||||||
|
python3 \
|
||||||
|
python3-pip \
|
||||||
|
python3-msgpack \
|
||||||
|
python3-pysodium \
|
||||||
|
xz-utils \
|
||||||
|
iptables \
|
||||||
|
iproute2 \
|
||||||
|
dnsmasq \
|
||||||
|
fakeroot \
|
||||||
|
apt-utils \
|
||||||
|
net-tools \
|
||||||
|
e2fsprogs \
|
||||||
|
diffutils \
|
||||||
|
qemu-utils \
|
||||||
|
websocketd \
|
||||||
|
iputils-ping \
|
||||||
|
inotify-tools \
|
||||||
|
ca-certificates \
|
||||||
|
netcat-openbsd \
|
||||||
|
qemu-system-x86
|
||||||
|
|
||||||
RUN chmod +x /run/*.sh && chmod +x /run/*.bin
|
# Install Passt package
|
||||||
|
wget "https://github.com/qemus/passt/releases/download/v${VERSION_PASST}/passt_${VERSION_PASST}_${TARGETARCH}.deb" -O /tmp/passt.deb -q --timeout=10
|
||||||
|
dpkg -i /tmp/passt.deb
|
||||||
|
|
||||||
|
apt-get clean
|
||||||
|
|
||||||
|
# Install Python dependencies
|
||||||
|
pip3 install --no-cache-dir --break-system-packages --root-user-action=ignore "dissect.cstruct==$VERSION_CSTRUCT"
|
||||||
|
|
||||||
|
# Configure QEMU
|
||||||
|
mkdir -p /etc/qemu
|
||||||
|
echo "allow br0" > /etc/qemu/bridge.conf
|
||||||
|
|
||||||
|
# Configure nginx
|
||||||
|
unlink /etc/nginx/sites-enabled/default
|
||||||
|
sed -i 's/^worker_processes.*/worker_processes 1;/' /etc/nginx/nginx.conf
|
||||||
|
|
||||||
|
# Set version file
|
||||||
|
echo "$VERSION_ARG" > /etc/version
|
||||||
|
|
||||||
|
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||||
|
EOF
|
||||||
|
|
||||||
|
COPY --chmod=755 ./src /run/
|
||||||
|
COPY --chmod=755 ./web /var/www/
|
||||||
|
COPY --chmod=755 --from=builder /qemu-host.bin /run/host.bin
|
||||||
|
COPY --chmod=744 ./web/conf/nginx.conf /etc/nginx/default.conf
|
||||||
|
ADD --chmod=775 https://raw.githubusercontent.com/sud0woodo/patology/refs/heads/main/patology.py /run/extract.py
|
||||||
|
|
||||||
VOLUME /storage
|
VOLUME /storage
|
||||||
|
EXPOSE 22 139 445 5000
|
||||||
|
|
||||||
EXPOSE 22
|
ENV RAM_SIZE="2G"
|
||||||
EXPOSE 80
|
ENV CPU_CORES="2"
|
||||||
EXPOSE 139
|
ENV DISK_SIZE="256G"
|
||||||
EXPOSE 445
|
|
||||||
EXPOSE 5000
|
|
||||||
|
|
||||||
ENV CPU_CORES "1"
|
HEALTHCHECK --interval=60s --start-period=45s --retries=2 CMD ["/run/check.sh"]
|
||||||
ENV DISK_SIZE "16G"
|
|
||||||
ENV RAM_SIZE "512M"
|
|
||||||
|
|
||||||
ARG DATE_ARG=""
|
ENTRYPOINT ["/usr/bin/tini", "-s", "/run/entry.sh"]
|
||||||
ARG BUILD_ARG=0
|
|
||||||
ARG VERSION_ARG="0.0"
|
|
||||||
ENV VERSION=$VERSION_ARG
|
|
||||||
|
|
||||||
LABEL org.opencontainers.image.licenses="MIT"
|
|
||||||
LABEL org.opencontainers.image.title="Virtual DSM"
|
|
||||||
LABEL org.opencontainers.image.created=${DATE_ARG}
|
|
||||||
LABEL org.opencontainers.image.revision=${BUILD_ARG}
|
|
||||||
LABEL org.opencontainers.image.version=${VERSION_ARG}
|
|
||||||
LABEL org.opencontainers.image.source="https://github.com/vdsm/virtual-dsm/"
|
|
||||||
LABEL org.opencontainers.image.url="https://hub.docker.com/r/vdsm/virtual-dsm/"
|
|
||||||
LABEL org.opencontainers.image.description="Virtual DSM in a docker container"
|
|
||||||
|
|
||||||
HEALTHCHECK --interval=60s --retries=2 CMD /run/check.sh
|
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/bin/tini", "-s", "/run/run.sh"]
|
|
||||||
|
|||||||
-140
@@ -1,140 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -u
|
|
||||||
|
|
||||||
VERSION="9"
|
|
||||||
HEADER="VirtualDSM Agent"
|
|
||||||
|
|
||||||
# Functions
|
|
||||||
|
|
||||||
error () { echo -e "\E[1;31m❯ ERROR: $1\E[0m" ; }
|
|
||||||
info () { echo -e "\E[1;34m❯\E[1;36m $1\E[0m" ; }
|
|
||||||
|
|
||||||
finish() {
|
|
||||||
|
|
||||||
echo "❯ $HEADER: Shutting down.."
|
|
||||||
exit
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkNMI {
|
|
||||||
|
|
||||||
local nmi
|
|
||||||
nmi=$(cat /proc/interrupts | grep NMI | sed 's/[^1-9]*//g')
|
|
||||||
|
|
||||||
if [ "$nmi" != "" ]; then
|
|
||||||
|
|
||||||
info "Received shutdown request through NMI.."
|
|
||||||
|
|
||||||
/usr/syno/sbin/synoshutdown -s > /dev/null
|
|
||||||
finish
|
|
||||||
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
function downloadUpdate {
|
|
||||||
|
|
||||||
TMP="/tmp/agent.sh"
|
|
||||||
rm -f "${TMP}"
|
|
||||||
|
|
||||||
# Auto update the agent
|
|
||||||
|
|
||||||
URL="https://raw.githubusercontent.com/vdsm/virtual-dsm/master/agent/agent.sh"
|
|
||||||
|
|
||||||
remote_size=$(curl -sIk -m 4 "${URL}" | grep -i "content-length:" | tr -d " \t" | cut -d ':' -f 2)
|
|
||||||
remote_size=${remote_size//$'\r'}
|
|
||||||
|
|
||||||
[[ "$remote_size" == "" || "$remote_size" == "0" ]] && return
|
|
||||||
|
|
||||||
remote_size=$(($remote_size+0))
|
|
||||||
((remote_size<100)) && return
|
|
||||||
|
|
||||||
SCRIPT=$(readlink -f "${BASH_SOURCE[0]}")
|
|
||||||
local_size=$(stat -c%s "$SCRIPT")
|
|
||||||
local_size=$(($local_size+0))
|
|
||||||
|
|
||||||
[[ remote_size -eq local_size ]] && return
|
|
||||||
|
|
||||||
if ! curl -sfk -m 10 -o "${TMP}" "${URL}"; then
|
|
||||||
error "$HEADER: curl error ($?)" && return
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ! -f "${TMP}" ]; then
|
|
||||||
error "$HEADER: update error, file not found.." && return
|
|
||||||
fi
|
|
||||||
|
|
||||||
line=$(head -1 "${TMP}")
|
|
||||||
|
|
||||||
if [[ "$line" != "#!/usr/bin/env bash" ]]; then
|
|
||||||
error "$HEADER: update error, invalid header: $line" && return
|
|
||||||
fi
|
|
||||||
|
|
||||||
if cmp --silent -- "${TMP}" "${SCRIPT}"; then
|
|
||||||
error "$HEADER: update file is already equal? (${local_size} / ${remote_size})" && return
|
|
||||||
fi
|
|
||||||
|
|
||||||
mv -f "${TMP}" "${SCRIPT}"
|
|
||||||
chmod 755 "${SCRIPT}"
|
|
||||||
|
|
||||||
info "$HEADER: succesfully installed update..."
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function installPackages {
|
|
||||||
|
|
||||||
for filename in /usr/local/packages/*.spk; do
|
|
||||||
if [ -f "$filename" ]; then
|
|
||||||
|
|
||||||
BASE=$(basename "$filename" .spk)
|
|
||||||
BASE="${BASE%%-*}"
|
|
||||||
|
|
||||||
[[ $BASE == "ActiveInsight" ]] && continue
|
|
||||||
|
|
||||||
info "Installing package ${BASE}.."
|
|
||||||
|
|
||||||
/usr/syno/bin/synopkg install "$filename" > /dev/null
|
|
||||||
/usr/syno/bin/synopkg start "$BASE" > /dev/null &
|
|
||||||
|
|
||||||
rm "$filename"
|
|
||||||
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
trap finish SIGINT SIGTERM
|
|
||||||
|
|
||||||
ts=$(date +%s%N)
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "❯ Started $HEADER v$VERSION..."
|
|
||||||
|
|
||||||
checkNMI
|
|
||||||
|
|
||||||
# Install packages
|
|
||||||
|
|
||||||
first_run=false
|
|
||||||
|
|
||||||
for filename in /usr/local/packages/*.spk; do
|
|
||||||
if [ -f "$filename" ]; then
|
|
||||||
first_run=true
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ "$first_run" = true ]; then
|
|
||||||
|
|
||||||
installPackages
|
|
||||||
|
|
||||||
else
|
|
||||||
|
|
||||||
downloadUpdate
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Wait for NMI interrupt as a shutdown signal
|
|
||||||
|
|
||||||
while true; do
|
|
||||||
|
|
||||||
checkNMI
|
|
||||||
sleep 2 & wait $!
|
|
||||||
|
|
||||||
done
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
PIDFILE="/var/run/agent.pid"
|
|
||||||
SCRIPT="/usr/local/bin/agent.sh"
|
|
||||||
|
|
||||||
error () { echo -e "\E[1;31m❯ ERROR: $1\E[0m" ; }
|
|
||||||
info () { echo -e "\E[1;34m❯\E[1;36m $1\E[0m" ; }
|
|
||||||
|
|
||||||
status() {
|
|
||||||
|
|
||||||
if [ -f "$PIDFILE" ] && kill -0 "$(cat "$PIDFILE")"; then
|
|
||||||
echo 'Service running'
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
start() {
|
|
||||||
|
|
||||||
if [ -f "$PIDFILE" ] && kill -0 "$(cat "$PIDFILE")"; then
|
|
||||||
echo 'Service already running'
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo 'Starting agent service...'
|
|
||||||
chmod 666 /dev/ttyS0
|
|
||||||
|
|
||||||
if [ ! -f "$SCRIPT" ]; then
|
|
||||||
|
|
||||||
URL="https://raw.githubusercontent.com/vdsm/virtual-dsm/master/agent/agent.sh"
|
|
||||||
|
|
||||||
if ! curl -sfk -m 10 -o "${SCRIPT}" "${URL}"; then
|
|
||||||
error 'Failed to download agent script.' > /dev/ttyS0
|
|
||||||
rm -f "${SCRIPT}"
|
|
||||||
return 1
|
|
||||||
else
|
|
||||||
info 'Agent script was missing?' > /dev/ttyS0
|
|
||||||
fi
|
|
||||||
|
|
||||||
chmod 755 "${SCRIPT}"
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "-" > /var/lock/subsys/agent.sh
|
|
||||||
"$SCRIPT" &> /dev/ttyS0 & echo $! > "$PIDFILE"
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
stop() {
|
|
||||||
|
|
||||||
if [ ! -f "$PIDFILE" ] || ! kill -0 "$(cat "$PIDFILE")"; then
|
|
||||||
echo 'Service not running'
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo 'Stopping agent service...'
|
|
||||||
|
|
||||||
chmod 666 /dev/ttyS0
|
|
||||||
info 'Stopping agent service...' > /dev/ttyS0
|
|
||||||
|
|
||||||
kill -15 "$(cat "$PIDFILE")" && rm -f "$PIDFILE"
|
|
||||||
rm -f /var/lock/subsys/agent.sh
|
|
||||||
|
|
||||||
echo 'Service stopped'
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
case "$1" in
|
|
||||||
start)
|
|
||||||
start
|
|
||||||
;;
|
|
||||||
stop)
|
|
||||||
stop
|
|
||||||
;;
|
|
||||||
status)
|
|
||||||
status
|
|
||||||
;;
|
|
||||||
restart)
|
|
||||||
stop
|
|
||||||
start
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "Usage: $0 {start|stop|restart}"
|
|
||||||
exit 1
|
|
||||||
esac
|
|
||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
services:
|
||||||
|
dsm:
|
||||||
|
container_name: dsm
|
||||||
|
image: vdsm/virtual-dsm
|
||||||
|
environment:
|
||||||
|
DISK_SIZE: "256G"
|
||||||
|
devices:
|
||||||
|
- /dev/kvm
|
||||||
|
- /dev/net/tun
|
||||||
|
cap_add:
|
||||||
|
- NET_ADMIN
|
||||||
|
ports:
|
||||||
|
- 5000:5000
|
||||||
|
volumes:
|
||||||
|
- ./dsm:/storage
|
||||||
|
restart: always
|
||||||
|
stop_grace_period: 2m
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
version: "3"
|
|
||||||
services:
|
|
||||||
dsm:
|
|
||||||
container_name: dsm
|
|
||||||
image: vdsm/virtual-dsm:latest
|
|
||||||
environment:
|
|
||||||
CPU_CORES: "1"
|
|
||||||
DISK_SIZE: "16G"
|
|
||||||
RAM_SIZE: "512M"
|
|
||||||
devices:
|
|
||||||
- /dev/kvm
|
|
||||||
- /dev/vhost-net
|
|
||||||
device_cgroup_rules:
|
|
||||||
- 'c *:* rwm'
|
|
||||||
cap_add:
|
|
||||||
- NET_ADMIN
|
|
||||||
ports:
|
|
||||||
- 5000:5000
|
|
||||||
volumes:
|
|
||||||
- /opt/dsm:/storage
|
|
||||||
restart: on-failure
|
|
||||||
stop_grace_period: 1m
|
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
# Environment Variables
|
||||||
|
|
||||||
|
This page lists all the environment variables that can be used to configure the container.
|
||||||
|
|
||||||
|
An empty default means the variable is unset and its value is determined automatically when applicable.
|
||||||
|
|
||||||
|
## 💽 Virtual DSM
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `URL` | | URL or local path to a custom `.pat` installation file. |
|
||||||
|
| `COUNTRY` | | Country code used to select the Synology download mirror. |
|
||||||
|
| `HOST_MAC` | | MAC address reported to DSM. |
|
||||||
|
| `HOST_MODEL` | | Synology host model reported to DSM. |
|
||||||
|
| `HOST_SERIAL` | | Synology host serial number reported to DSM. |
|
||||||
|
| `GUEST_SERIAL` | | Synology guest serial number reported to DSM. |
|
||||||
|
|
||||||
|
## 🧠 CPU and Memory
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `CPU_CORES` | `2` | Number of virtual CPU cores, such as `4`, `half`, or `max`. |
|
||||||
|
| `CPU_MODEL` | `host` | QEMU CPU model. |
|
||||||
|
| `CPU_FLAGS` | | Additional QEMU CPU flags. |
|
||||||
|
| `HOST_CPU` | | CPU name reported to DSM. Selected automatically when unset. |
|
||||||
|
| `KVM` | `Y` | Enables KVM hardware acceleration. |
|
||||||
|
| `RAM_SIZE` | `2G` | Amount of RAM assigned to DSM, such as `2G`, `4G`, `half`, or `max`. |
|
||||||
|
| `RAM_CHECK` | `Y` | Checks whether enough host memory is available before starting DSM. |
|
||||||
|
|
||||||
|
## 💾 Storage
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `DISK_SIZE` | `256G` | Size of the main data disk. |
|
||||||
|
| `DISK_FMT` | `raw` | Disk image format: `raw` or `qcow2`. |
|
||||||
|
| `DISK_TYPE` | `scsi` | Disk device type, such as `sata`, `scsi`, `nvme`, or `blk`. |
|
||||||
|
| `DISK_CACHE` | `none` | Disk cache mode, such as `none` or `writeback`. |
|
||||||
|
| `DISK_IO` | `native` | Disk I/O mode, such as `native`, `threads`, or `io_uring`. |
|
||||||
|
| `DISK_DISCARD` | `unmap` | Discard/TRIM mode for the primary disk. |
|
||||||
|
| `DISK_ROTATION` | `1` | Rotation rate reported to the guest. Use `1` to identify the disk as an SSD. |
|
||||||
|
| `DISK_FLAGS` | | Additional options used when creating `qcow2` disks. |
|
||||||
|
| `ALLOCATE` | `N` | Preallocates space for the data disks. |
|
||||||
|
| `STORAGE` | `/storage` | Storage directory used for disks, settings, and downloads. |
|
||||||
|
|
||||||
|
## 🌐 Networking
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `NETWORK` | | Network mode, such as `nat`, `user`, or `N` to disable networking. |
|
||||||
|
| `DHCP` | `N` | Enables macvtap networking so DSM receives a DHCP address. |
|
||||||
|
| `HOST` | | Hostname assigned to the machine on the network. |
|
||||||
|
| `IP` | | Overrides the automatically selected guest IPv4 address. |
|
||||||
|
| `MAC` | | Guest network adapter MAC address. |
|
||||||
|
| `ADAPTER` | `virtio-net-pci` | QEMU network adapter model. |
|
||||||
|
| `DEV` | `eth0` | Container network interface used as the uplink. |
|
||||||
|
| `MTU` | | MTU assigned to the guest network interface. |
|
||||||
|
| `MASK` | `255.255.255.0` | IPv4 netmask for guest network. |
|
||||||
|
| `TAP` | `dsm` | TAP or macvtap interface name. |
|
||||||
|
| `BRIDGE` | `docker` | Bridge name used for NAT networking. |
|
||||||
|
| `HOST_PORTS` | | Ports excluded from guest forwarding. |
|
||||||
|
| `USER_PORTS` | | Additional ports to forward to DSM when using user-mode networking. |
|
||||||
|
| `DNSMASQ_OPTS` | | Additional options passed to dnsmasq. |
|
||||||
|
| `DNSMASQ_DEBUG` | `N` | Enables dnsmasq debug output. |
|
||||||
|
| `DNSMASQ_DISABLE` | `N` | Disables the internal dnsmasq resolver. |
|
||||||
|
| `PASST_OPTS` | | Additional options passed to passt. |
|
||||||
|
| `PASST_DEBUG` | `N` | Enables passt debug output. |
|
||||||
|
|
||||||
|
## 🖥️ Display
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `DISPLAY` | `none` | Display backend, such as `vnc`, `disabled`, or `none`. |
|
||||||
|
| `LOSSY` | `N` | Enables lossy VNC compression to reduce bandwidth usage. |
|
||||||
|
| `VGA` | `none` | QEMU video adapter model. |
|
||||||
|
| `GPU` | `N` | Enables Intel iGPU acceleration. |
|
||||||
|
| `RENDERNODE` | `/dev/dri/renderD128` | Render node used for GPU acceleration. |
|
||||||
|
|
||||||
|
## ⚙️ System
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `MACHINE` | `q35` | QEMU machine type. |
|
||||||
|
| `ARGUMENTS` | | Additional raw arguments appended to the QEMU command line. |
|
||||||
|
|
||||||
|
## 🔌 Shutdown
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `SHUTDOWN` | `Y` | Enables graceful shutdown. |
|
||||||
|
| `TIMEOUT` | `115` | Maximum time, in seconds, to wait before forcing DSM to stop. |
|
||||||
|
| `API_TIMEOUT` | `90` | Maximum time, in seconds, to wait for the shutdown API call. |
|
||||||
|
|
||||||
|
## 🐞 Debugging
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `DEBUG` | `N` | Enables verbose debug output. |
|
||||||
|
| `TRACE` | `N` | Enables shell command tracing. |
|
||||||
|
| `HOST_DEBUG` | `N` | Enables debug output for the DSM host helper. |
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: dsm-pvc
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 256Gi
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: dsm
|
||||||
|
labels:
|
||||||
|
name: dsm
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
strategy:
|
||||||
|
type: Recreate
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: dsm
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: dsm
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: dsm
|
||||||
|
image: vdsm/virtual-dsm
|
||||||
|
env:
|
||||||
|
- name: DISK_SIZE
|
||||||
|
value: "256G"
|
||||||
|
ports:
|
||||||
|
- containerPort: 5000
|
||||||
|
name: http
|
||||||
|
protocol: TCP
|
||||||
|
securityContext:
|
||||||
|
capabilities:
|
||||||
|
add:
|
||||||
|
- NET_ADMIN
|
||||||
|
privileged: true
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: /storage
|
||||||
|
name: storage
|
||||||
|
- mountPath: /dev/kvm
|
||||||
|
name: dev-kvm
|
||||||
|
- mountPath: /dev/net/tun
|
||||||
|
name: dev-tun
|
||||||
|
terminationGracePeriodSeconds: 120
|
||||||
|
volumes:
|
||||||
|
- name: storage
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: dsm-pvc
|
||||||
|
- hostPath:
|
||||||
|
path: /dev/kvm
|
||||||
|
name: dev-kvm
|
||||||
|
- hostPath:
|
||||||
|
path: /dev/net/tun
|
||||||
|
type: CharDevice
|
||||||
|
name: dev-tun
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: dsm
|
||||||
|
spec:
|
||||||
|
internalTrafficPolicy: Cluster
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 5000
|
||||||
|
protocol: TCP
|
||||||
|
targetPort: 5000
|
||||||
|
selector:
|
||||||
|
app: dsm
|
||||||
|
type: ClusterIP
|
||||||
@@ -1,233 +1,298 @@
|
|||||||
<h1 align="center">Virtual DSM for Docker<br />
|
<h1 align="center">Virtual DSM<br />
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<img src="https://github.com/vdsm/virtual-dsm/raw/master/.github/screen.jpg" title="Screenshot" style="max-width:100%;" width="432" />
|
<a href="https://github.com/vdsm/virtual-dsm"><img src="https://github.com/vdsm/virtual-dsm/raw/master/.github/screen.jpg" title="Screenshot" style="max-width:100%;" width="432" /></a>
|
||||||
</div>
|
</div>
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
[![Build]][build_url]
|
[![Build]][build_url]
|
||||||
[![Version]][tag_url]
|
[![Version]][tag_url]
|
||||||
[![Size]][tag_url]
|
[![Size]][tag_url]
|
||||||
|
[![Package]][pkg_url]
|
||||||
[![Pulls]][hub_url]
|
[![Pulls]][hub_url]
|
||||||
|
|
||||||
</div></h1>
|
</div></h1>
|
||||||
Virtual DSM in a docker container.
|
|
||||||
|
|
||||||
## Features
|
Virtual DSM in a Docker container.
|
||||||
|
|
||||||
- Multi-platform
|
## Features ✨
|
||||||
- KVM acceleration
|
|
||||||
- GPU passthrough
|
|
||||||
- Upgrades supported
|
|
||||||
|
|
||||||
## Usage
|
- Runs Virtual DSM inside a Docker container
|
||||||
|
- Automatic download of the installation files
|
||||||
|
- Web-based access to the DSM desktop
|
||||||
|
- Near-native performance with KVM acceleration
|
||||||
|
- Customizable CPU, memory, and storage allocation
|
||||||
|
- Supports multiple disks and physical disk passthrough
|
||||||
|
- Supports NAT, user-mode, macvlan, and macvtap networking
|
||||||
|
|
||||||
Via `docker-compose.yml`
|
## Usage 🐳
|
||||||
|
|
||||||
|
##### Docker Compose:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
version: "3"
|
|
||||||
services:
|
services:
|
||||||
dsm:
|
dsm:
|
||||||
container_name: dsm
|
container_name: dsm
|
||||||
image: vdsm/virtual-dsm:latest
|
image: vdsm/virtual-dsm
|
||||||
environment:
|
environment:
|
||||||
DISK_SIZE: "16G"
|
DISK_SIZE: "256G"
|
||||||
devices:
|
devices:
|
||||||
- /dev/kvm
|
- /dev/kvm
|
||||||
- /dev/vhost-net
|
- /dev/net/tun
|
||||||
cap_add:
|
cap_add:
|
||||||
- NET_ADMIN
|
- NET_ADMIN
|
||||||
ports:
|
ports:
|
||||||
- 5000:5000
|
- 5000:5000
|
||||||
volumes:
|
volumes:
|
||||||
- /opt/dsm:/storage
|
- ./dsm:/storage
|
||||||
restart: on-failure
|
restart: always
|
||||||
stop_grace_period: 1m
|
stop_grace_period: 2m
|
||||||
```
|
```
|
||||||
|
|
||||||
Via `docker run`
|
##### Docker CLI:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run -it --rm -p 5000:5000 --device=/dev/kvm --cap-add NET_ADMIN --stop-timeout 60 vdsm/virtual-dsm:latest
|
docker run -it --rm --name dsm -e "DISK_SIZE=256G" -p 5000:5000 --device=/dev/kvm --device=/dev/net/tun --cap-add NET_ADMIN -v "${PWD:-.}/dsm:/storage" --stop-timeout 120 docker.io/vdsm/virtual-dsm
|
||||||
```
|
```
|
||||||
|
|
||||||
## FAQ
|
##### Kubernetes:
|
||||||
|
|
||||||
* ### How do I change the size of the virtual disk?
|
```shell
|
||||||
|
kubectl apply -f https://raw.githubusercontent.com/vdsm/virtual-dsm/refs/heads/master/kubernetes.yml
|
||||||
|
```
|
||||||
|
|
||||||
To expand the default size of 16 GB, locate the `DISK_SIZE` setting in your compose file and modify it to your preferred capacity:
|
##### GitHub Codespaces:
|
||||||
|
|
||||||
```yaml
|
[](https://codespaces.new/vdsm/virtual-dsm)
|
||||||
environment:
|
|
||||||
DISK_SIZE: "256G"
|
|
||||||
```
|
|
||||||
|
|
||||||
This can also be used to resize the existing disk to a larger capacity without any data loss.
|
## Requirements ⚙️
|
||||||
|
|
||||||
* ### How do I change the location of the virtual disk?
|
- Docker or Podman on a Linux host with KVM support.
|
||||||
|
- Docker Desktop or Podman (Desktop) on Windows 11 with nested virtualization enabled.
|
||||||
|
- At least 2 GB of available RAM.
|
||||||
|
- At least 32 GB of free disk space.
|
||||||
|
|
||||||
To change the virtual disk's location from the default Docker volume, include the following bind mount in your compose file:
|
> [!NOTE]
|
||||||
|
> Docker Desktop on Linux, macOS, and Windows 10 does not currently provide KVM access to containers and is therefore not supported.
|
||||||
|
|
||||||
```yaml
|
## FAQ 💬
|
||||||
volumes:
|
|
||||||
- /home/user/data:/storage
|
|
||||||
```
|
|
||||||
|
|
||||||
Replace the example path `/home/user/data` with the desired storage folder.
|
### How do I use it?
|
||||||
|
|
||||||
* ### How do I change the space reserved by the virtual disk?
|
Very simple! These are the steps:
|
||||||
|
|
||||||
By default, the entire disk space is reserved in advance. To create a growable disk that only reserves the space that is actually used, add the following environment variable:
|
- Start the container and connect to [port 5000](http://127.0.0.1:5000/) using your web browser.
|
||||||
|
|
||||||
```yaml
|
- Wait until DSM finishes its installation
|
||||||
environment:
|
|
||||||
ALLOCATE: "N"
|
|
||||||
```
|
|
||||||
|
|
||||||
Keep in mind that this will not affect any of your existing disks, it only applies to newly created disks.
|
- Choose a username and password, and you will be taken to the desktop.
|
||||||
|
|
||||||
* ### How do I add multiple disks?
|
Enjoy your brand new NAS, and don't forget to star this repo!
|
||||||
|
|
||||||
To mount extra volumes, modify your compose file like this:
|
### How do I change the storage location?
|
||||||
|
|
||||||
```yaml
|
To change the storage location, include the following bind mount in your compose file:
|
||||||
environment:
|
|
||||||
DISK2_SIZE: "32G"
|
|
||||||
DISK3_SIZE: "64G"
|
|
||||||
volumes:
|
|
||||||
- /home/example2:/storage2
|
|
||||||
- /home/example3:/storage3
|
|
||||||
```
|
|
||||||
|
|
||||||
Additionally, it's also possible to passthrough raw disk devices like this:
|
```yaml
|
||||||
|
volumes:
|
||||||
|
- ./dsm:/storage
|
||||||
|
```
|
||||||
|
|
||||||
```yaml
|
Replace the example path `./dsm` with the desired storage folder or named volume.
|
||||||
environment:
|
|
||||||
DEVICE2: "/dev/vdc1"
|
|
||||||
DEVICE3: "/dev/vdc2"
|
|
||||||
devices:
|
|
||||||
- /dev/vdc1
|
|
||||||
- /dev/vdc2
|
|
||||||
```
|
|
||||||
|
|
||||||
Please beware that any pre-existing partitions and data on those devices will be wiped.
|
### How do I change the size of the disk?
|
||||||
|
|
||||||
* ### How do I increase the amount of CPU or RAM?
|
To expand the default size of 256 GB, locate the `DISK_SIZE` setting in your compose file and modify it to your preferred capacity:
|
||||||
|
|
||||||
By default, a single core and 512 MB of RAM are allocated to the container. To increase this, add the following environment variables:
|
```yaml
|
||||||
|
environment:
|
||||||
|
DISK_SIZE: "512G"
|
||||||
|
```
|
||||||
|
|
||||||
```yaml
|
> [!TIP]
|
||||||
environment:
|
> This can also be used to resize an existing disk to a larger capacity without any data loss.
|
||||||
CPU_CORES: "4"
|
|
||||||
RAM_SIZE: "2048M"
|
|
||||||
```
|
|
||||||
|
|
||||||
* ### How do I verify if my system supports KVM?
|
### How do I add multiple disks?
|
||||||
|
|
||||||
To verify if your system supports KVM, run the following commands:
|
To create additional disks, modify your compose file like this:
|
||||||
|
|
||||||
```bash
|
```yaml
|
||||||
sudo apt install cpu-checker
|
environment:
|
||||||
sudo kvm-ok
|
DISK2_SIZE: "500G"
|
||||||
```
|
DISK3_SIZE: "750G"
|
||||||
|
volumes:
|
||||||
|
- ./example2:/storage2
|
||||||
|
- ./example3:/storage3
|
||||||
|
```
|
||||||
|
|
||||||
If you receive an error from `kvm-ok` indicating that KVM acceleration can't be used, check your BIOS settings.
|
### How do I pass through a disk?
|
||||||
|
|
||||||
* ### How do I assign an individual IP address to the container?
|
You can pass through disk devices or partitions directly by adding them to your compose file in this way:
|
||||||
|
|
||||||
By default, the container uses bridge networking, which shares the IP address with the host.
|
```yaml
|
||||||
|
devices:
|
||||||
|
- /dev/sdb:/disk1
|
||||||
|
- /dev/sdc1:/disk2
|
||||||
|
```
|
||||||
|
|
||||||
If you want to assign an individual IP address to the container, you can create a macvlan network as follows:
|
Make sure it is totally empty (without any filesystem), otherwise DSM may not format it as a volume.
|
||||||
|
|
||||||
```bash
|
### How do I change the amount of CPU or RAM?
|
||||||
docker network create -d macvlan \
|
|
||||||
--subnet=192.168.0.0/24 \
|
|
||||||
--gateway=192.168.0.1 \
|
|
||||||
--ip-range=192.168.0.100/28 \
|
|
||||||
-o parent=eth0 vdsm
|
|
||||||
```
|
|
||||||
|
|
||||||
Be sure to modify these values to match your local subnet.
|
By default, Virtual DSM will be allowed to use 2 CPU cores and 2 GB of RAM.
|
||||||
|
|
||||||
Once you have created the network, change your compose file to look as follows:
|
If you want to adjust this, you can specify the desired amount using the following environment variables:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
services:
|
environment:
|
||||||
dsm:
|
RAM_SIZE: "4G"
|
||||||
container_name: dsm
|
CPU_CORES: "4"
|
||||||
..<snip>..
|
```
|
||||||
networks:
|
|
||||||
vdsm:
|
|
||||||
ipv4_address: 192.168.0.100
|
|
||||||
|
|
||||||
networks:
|
### How do I assign an individual IP address to the container?
|
||||||
vdsm:
|
|
||||||
external: true
|
|
||||||
```
|
|
||||||
|
|
||||||
An added benefit of this approach is that you won't have to perform any port mapping anymore since all ports will be exposed by default.
|
By default, the container uses bridge networking, which shares the IP address with the host.
|
||||||
|
|
||||||
Please note that this IP address won't be accessible from the Docker host due to the design of macvlan, which doesn't permit communication between the two. If this is a concern, you need to create a [second macvlan](https://blog.oddbit.com/post/2018-03-12-using-docker-macvlan-networks/#host-access) as a workaround.
|
If you want to assign an individual IP address to the container, you can create a macvlan network as follows:
|
||||||
|
|
||||||
* ### How can the container acquire an IP address from my router?
|
```bash
|
||||||
|
docker network create -d macvlan \
|
||||||
|
--subnet=192.168.0.0/24 \
|
||||||
|
--gateway=192.168.0.1 \
|
||||||
|
--ip-range=192.168.0.100/28 \
|
||||||
|
-o parent=eth0 vdsm
|
||||||
|
```
|
||||||
|
|
||||||
After configuring the container for macvlan (see above), it is possible for DSM to become part of your home network by requesting an IP from your router, just like your other devices.
|
Be sure to modify these values to match your local subnet.
|
||||||
|
|
||||||
To enable this feature, add the following lines to your compose file:
|
Once you have created the network, change your compose file to look as follows:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
environment:
|
services:
|
||||||
DHCP: "Y"
|
dsm:
|
||||||
devices:
|
container_name: dsm
|
||||||
- /dev/vhost-net
|
..<snip>..
|
||||||
device_cgroup_rules:
|
networks:
|
||||||
- 'c *:* rwm'
|
vdsm:
|
||||||
```
|
ipv4_address: 192.168.0.100
|
||||||
|
|
||||||
Please note that even if you don't need DHCP, it's still recommended to enable this feature as it prevents NAT issues and increases performance by using a `macvtap` interface.
|
networks:
|
||||||
|
vdsm:
|
||||||
|
external: true
|
||||||
|
```
|
||||||
|
|
||||||
* ### How do I install a specific version of vDSM?
|
An added benefit of this approach is that you won't have to perform any port mapping anymore, since all ports will be exposed by default.
|
||||||
|
|
||||||
By default, version 7.2.1 will be installed, but if you prefer an older version, you can add its download URL to your compose file as follows:
|
> [!IMPORTANT]
|
||||||
|
> This IP address won't be accessible from the Docker host due to the design of macvlan, which doesn't permit communication between the two. If this is a concern, you need to create a [second macvlan](https://blog.oddbit.com/post/2018-03-12-using-docker-macvlan-networks/#host-access) as a workaround.
|
||||||
|
|
||||||
```yaml
|
### How can DSM acquire an IP address from my router?
|
||||||
environment:
|
|
||||||
URL: "https://global.synologydownload.com/download/DSM/release/7.0.1/42218/DSM_VirtualDSM_42218.pat"
|
|
||||||
```
|
|
||||||
|
|
||||||
With this method, it is even possible to switch between different versions while keeping all your file data intact.
|
After configuring the container for [macvlan](#how-do-i-assign-an-individual-ip-address-to-the-container), it is possible for DSM to become part of your home network by requesting an IP from your router, just like your other devices.
|
||||||
|
|
||||||
* ### How do I passthrough my GPU?
|
To enable this mode, in which the container and DSM will have separate IP addresses, add the following lines to your compose file:
|
||||||
|
|
||||||
To passthrough your Intel GPU, add the following lines to your compose file:
|
```yaml
|
||||||
|
environment:
|
||||||
|
DHCP: "Y"
|
||||||
|
devices:
|
||||||
|
- /dev/vhost-net
|
||||||
|
device_cgroup_rules:
|
||||||
|
- 'c *:* rwm'
|
||||||
|
```
|
||||||
|
|
||||||
```yaml
|
### How do I pass through the GPU?
|
||||||
environment:
|
|
||||||
GPU: "Y"
|
|
||||||
devices:
|
|
||||||
- /dev/dri
|
|
||||||
```
|
|
||||||
|
|
||||||
This can be used to enable the facial recognition function in Synology Photos for example.
|
To pass through your Intel GPU, add the following lines to your compose file:
|
||||||
|
|
||||||
* ### What are the differences compared to the standard DSM?
|
```yaml
|
||||||
|
environment:
|
||||||
|
GPU: "Y"
|
||||||
|
devices:
|
||||||
|
- /dev/dri
|
||||||
|
```
|
||||||
|
|
||||||
There are only two minor differences: the Virtual Machine Manager package is not available, and Surveillance Station will not include any free licenses.
|
> [!NOTE]
|
||||||
|
> This can be used to enable the facial recognition function in Synology Photos, but does not provide hardware transcoding for video.
|
||||||
|
|
||||||
* ### Is this project legal?
|
### How do I install a specific version of vDSM?
|
||||||
|
|
||||||
Yes, this project contains only open-source code and does not distribute any copyrighted material. Neither does it try to circumvent any copyright protection measures. So under all applicable laws, this project would be considered legal.
|
By default, version 7.2 will be installed, but if you prefer an older version, you can add the download URL of the `.pat` file to your compose file as follows:
|
||||||
|
|
||||||
However, by installing Synology's Virtual DSM, you must accept their end-user license agreement, which does not permit installation on non-Synology hardware. So only run this project on an official Synology NAS, as any other use will be a violation of their terms and conditions.
|
```yaml
|
||||||
|
environment:
|
||||||
|
URL: "https://global.synologydownload.com/download/DSM/release/7.0.1/42218/DSM_VirtualDSM_42218.pat"
|
||||||
|
```
|
||||||
|
|
||||||
## Disclaimer
|
With this method, it is even possible to switch back and forth between versions while keeping your file data intact.
|
||||||
|
|
||||||
Only run this container on Synology hardware, any other use is not permitted by their EULA. The product names, logos, brands, and other trademarks referred to within this project are the property of their respective trademark holders. This project is not affiliated, sponsored, or endorsed by Synology, Inc.
|
Alternatively, you can also skip the download and use a local file instead, by binding it in your compose file in this way:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
volumes:
|
||||||
|
- ./DSM_VirtualDSM_42218.pat:/boot.pat
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace the example path `./DSM_VirtualDSM_42218.pat` with the filename of your desired `.pat` file. The value of `URL` will be ignored in this case.
|
||||||
|
|
||||||
|
### Are these all available options?
|
||||||
|
|
||||||
|
No. For a complete overview of all supported settings, see the [environment variables](docs/environment.md) page.
|
||||||
|
|
||||||
|
### How do I verify that KVM is available?
|
||||||
|
|
||||||
|
First, make sure your platform and container runtime meet the [requirements](#requirements-️) listed above.
|
||||||
|
|
||||||
|
On a Linux host, install `cpu-checker` and run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt install cpu-checker
|
||||||
|
sudo kvm-ok
|
||||||
|
```
|
||||||
|
|
||||||
|
A working configuration should report:
|
||||||
|
|
||||||
|
```text
|
||||||
|
KVM acceleration can be used
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also verify that the KVM device exists:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ls -l /dev/kvm
|
||||||
|
```
|
||||||
|
|
||||||
|
If KVM is unavailable, check whether:
|
||||||
|
|
||||||
|
- Hardware virtualization (`Intel VT-x` or `AMD-V`) is enabled in your BIOS or UEFI.
|
||||||
|
- Nested virtualization is enabled when the host itself is a virtual machine.
|
||||||
|
- Your VPS or cloud provider supports nested virtualization.
|
||||||
|
|
||||||
|
If `kvm-ok` succeeds but the container still reports that KVM is unavailable, you can temporarily add `privileged: true` to your Compose file to rule out a permission or device-access issue.
|
||||||
|
|
||||||
|
### What are the differences compared to the standard DSM?
|
||||||
|
|
||||||
|
There are only two minor differences: the Virtual Machine Manager package is not available, and Surveillance Station will not include any free licenses.
|
||||||
|
|
||||||
|
### Is this project legal?
|
||||||
|
|
||||||
|
Yes, this project contains only open-source code and does not distribute any material owned by Synology. Neither does it try to circumvent any copyright protection measures.
|
||||||
|
|
||||||
|
However, by installing Synology's Virtual DSM, you must accept their end-user license agreement, which does not permit installation on non-Synology hardware. So only run this container on an official Synology NAS, as any other use will be a violation of their terms and conditions.
|
||||||
|
|
||||||
|
## Stars 🌟
|
||||||
|
[](https://github.com/vdsm/virtual-dsm/stargazers)
|
||||||
|
|
||||||
|
## Disclaimer ⚖️
|
||||||
|
|
||||||
|
*Only run this container on Synology hardware, any other use is not permitted by their EULA. The product names, logos, brands, and other trademarks referred to within this project are the property of their respective trademark holders. This project is not affiliated, sponsored, or endorsed by Synology, Inc.*
|
||||||
|
|
||||||
[build_url]: https://github.com/vdsm/virtual-dsm/
|
[build_url]: https://github.com/vdsm/virtual-dsm/
|
||||||
[hub_url]: https://hub.docker.com/r/vdsm/virtual-dsm
|
[hub_url]: https://hub.docker.com/r/vdsm/virtual-dsm
|
||||||
[tag_url]: https://hub.docker.com/r/vdsm/virtual-dsm/tags
|
[tag_url]: https://hub.docker.com/r/vdsm/virtual-dsm/tags
|
||||||
|
[pkg_url]: https://github.com/vdsm/virtual-dsm/pkgs/container/virtual-dsm
|
||||||
|
|
||||||
[Build]: https://github.com/vdsm/virtual-dsm/actions/workflows/build.yml/badge.svg
|
[Build]: https://github.com/vdsm/virtual-dsm/actions/workflows/build.yml/badge.svg
|
||||||
[Size]: https://img.shields.io/docker/image-size/vdsm/virtual-dsm/latest?color=066da5&label=size
|
[Size]: https://img.shields.io/docker/image-size/vdsm/virtual-dsm/latest?color=066da5&label=size
|
||||||
[Pulls]: https://img.shields.io/docker/pulls/kroese/virtual-dsm.svg?style=flat&label=pulls&logo=docker
|
[Pulls]: https://img.shields.io/docker/pulls/vdsm/virtual-dsm.svg?style=flat&label=pulls&logo=docker
|
||||||
[Version]: https://img.shields.io/docker/v/vdsm/virtual-dsm/latest?arch=amd64&sort=semver&color=066da5
|
[Version]: https://img.shields.io/docker/v/vdsm/virtual-dsm/latest?arch=amd64&sort=semver&color=066da5
|
||||||
|
[Package]: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fipitio.github.io%2Fbackage%2Fvdsm%2Fvirtual-dsm%2Fvirtual-dsm.json&query=%24.downloads&logo=github&style=flat&color=066da5&label=pulls
|
||||||
|
|||||||
@@ -1,61 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -u
|
|
||||||
|
|
||||||
[ ! -f "/run/qemu.pid" ] && echo "QEMU not running yet.." && exit 0
|
|
||||||
[ -f "/run/qemu.count" ] && echo "QEMU is shutting down.." && exit 1
|
|
||||||
|
|
||||||
file="/run/dsm.url"
|
|
||||||
|
|
||||||
if [ ! -f "$file" ]; then
|
|
||||||
|
|
||||||
# Retrieve IP from guest VM for Docker healthcheck
|
|
||||||
RESPONSE=$(curl -s -m 16 -S http://127.0.0.1:2210/read?command=10 2>&1)
|
|
||||||
|
|
||||||
if [[ ! "${RESPONSE}" =~ "\"success\"" ]] ; then
|
|
||||||
echo "Failed to connect to guest: $RESPONSE" && exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Retrieve the HTTP port number
|
|
||||||
if [[ ! "${RESPONSE}" =~ "\"http_port\"" ]] ; then
|
|
||||||
echo "Failed to parse response from guest: $RESPONSE" && exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
rest=${RESPONSE#*http_port}
|
|
||||||
rest=${rest#*:}
|
|
||||||
rest=${rest%%,*}
|
|
||||||
PORT=${rest%%\"*}
|
|
||||||
|
|
||||||
[ -z "${PORT}" ] && echo "Guest has not set a portnumber yet.." && exit 1
|
|
||||||
|
|
||||||
# Retrieve the IP address
|
|
||||||
if [[ ! "${RESPONSE}" =~ "eth0" ]] ; then
|
|
||||||
echo "Failed to parse response from guest: $RESPONSE" && exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
rest=${RESPONSE#*eth0}
|
|
||||||
rest=${rest#*ip}
|
|
||||||
rest=${rest#*:}
|
|
||||||
rest=${rest#*\"}
|
|
||||||
IP=${rest%%\"*}
|
|
||||||
|
|
||||||
[ -z "${IP}" ] && echo "Guest has not received an IP yet.." && exit 1
|
|
||||||
|
|
||||||
echo "${IP}:${PORT}" > $file
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
LOCATION=$(cat "$file")
|
|
||||||
|
|
||||||
if ! curl -m 20 -ILfSs "http://${LOCATION}/" > /dev/null; then
|
|
||||||
rm -f $file
|
|
||||||
echo "Failed to reach http://${LOCATION}"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "$LOCATION" == "20.20"* ]]; then
|
|
||||||
echo "Healthcheck OK"
|
|
||||||
else
|
|
||||||
echo "Healthcheck OK ( ${LOCATION%:*} )"
|
|
||||||
fi
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
set -Eeuo pipefail
|
|
||||||
|
|
||||||
KVM_ERR=""
|
|
||||||
KVM_OPTS=""
|
|
||||||
|
|
||||||
if [ -e /dev/kvm ] && sh -c 'echo -n > /dev/kvm' &> /dev/null; then
|
|
||||||
if ! grep -q -e vmx -e svm /proc/cpuinfo; then
|
|
||||||
KVM_ERR="(vmx/svm disabled)"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
[ -e /dev/kvm ] && KVM_ERR="(no write access)" || KVM_ERR="(device file missing)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "${KVM_ERR}" ]; then
|
|
||||||
if [ "$ARCH" == "amd64" ]; then
|
|
||||||
error "KVM acceleration not detected ${KVM_ERR}, see the FAQ about this."
|
|
||||||
[[ "${DEBUG}" != [Yy1]* ]] && exit 88
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
KVM_OPTS=",accel=kvm -enable-kvm -cpu host"
|
|
||||||
fi
|
|
||||||
|
|
||||||
DEF_OPTS="-nographic -nodefaults -boot strict=on -display none"
|
|
||||||
RAM_OPTS=$(echo "-m ${RAM_SIZE}" | sed 's/MB/M/g;s/GB/G/g;s/TB/T/g')
|
|
||||||
CPU_OPTS="-smp ${CPU_CORES},sockets=1,dies=1,cores=${CPU_CORES},threads=1"
|
|
||||||
MAC_OPTS="-machine type=q35,usb=off,dump-guest-core=off,hpet=off${KVM_OPTS}"
|
|
||||||
EXTRA_OPTS="-device virtio-balloon-pci,id=balloon0,bus=pcie.0,addr=0x4"
|
|
||||||
EXTRA_OPTS="$EXTRA_OPTS -object rng-random,id=objrng0,filename=/dev/urandom"
|
|
||||||
EXTRA_OPTS="$EXTRA_OPTS -device virtio-rng-pci,rng=objrng0,id=rng0,bus=pcie.0,addr=0x1c"
|
|
||||||
|
|
||||||
if [[ "${GPU}" == [Yy1]* ]] && [[ "$ARCH" == "amd64" ]]; then
|
|
||||||
DEF_OPTS="-nodefaults -boot strict=on -display egl-headless,rendernode=/dev/dri/renderD128"
|
|
||||||
DEF_OPTS="${DEF_OPTS} -device virtio-vga,id=video0,max_outputs=1,bus=pcie.0,addr=0x1"
|
|
||||||
fi
|
|
||||||
|
|
||||||
ARGS="${DEF_OPTS} ${CPU_OPTS} ${RAM_OPTS} ${MAC_OPTS} ${MON_OPTS} ${SERIAL_OPTS} ${NET_OPTS} ${DISK_OPTS} ${EXTRA_OPTS} ${ARGUMENTS}"
|
|
||||||
ARGS=$(echo "$ARGS" | sed 's/\t/ /g' | tr -s ' ')
|
|
||||||
|
|
||||||
return 0
|
|
||||||
-241
@@ -1,241 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -Eeuo pipefail
|
|
||||||
|
|
||||||
# Docker environment variables
|
|
||||||
|
|
||||||
: ${DISK_IO:='native'} # I/O Mode, can be set to 'native', 'threads' or 'io_turing'
|
|
||||||
: ${DISK_CACHE:='none'} # Caching mode, can be set to 'writeback' for better performance
|
|
||||||
: ${DISK_DISCARD:='on'} # Controls whether unmap (TRIM) commands are passed to the host.
|
|
||||||
: ${DISK_ROTATION:='1'} # Rotation rate, set to 1 for SSD storage and increase for HDD
|
|
||||||
|
|
||||||
BOOT="$STORAGE/$BASE.boot.img"
|
|
||||||
SYSTEM="$STORAGE/$BASE.system.img"
|
|
||||||
|
|
||||||
[ ! -f "$BOOT" ] && error "Virtual DSM boot-image does not exist ($BOOT)" && exit 81
|
|
||||||
[ ! -f "$SYSTEM" ] && error "Virtual DSM system-image does not exist ($SYSTEM)" && exit 82
|
|
||||||
|
|
||||||
DATA="${STORAGE}/data.img"
|
|
||||||
|
|
||||||
if [[ ! -f "${DATA}" ]] && [[ -f "$STORAGE/data$DISK_SIZE.img" ]]; then
|
|
||||||
# Fallback for legacy installs
|
|
||||||
DATA="$STORAGE/data$DISK_SIZE.img"
|
|
||||||
fi
|
|
||||||
|
|
||||||
MIN_SIZE=6442450944
|
|
||||||
DISK_SIZE=$(echo "${DISK_SIZE}" | sed 's/MB/M/g;s/GB/G/g;s/TB/T/g')
|
|
||||||
DATA_SIZE=$(numfmt --from=iec "${DISK_SIZE}")
|
|
||||||
|
|
||||||
if (( DATA_SIZE < MIN_SIZE )); then
|
|
||||||
error "Please increase DISK_SIZE to at least 6 GB." && exit 83
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -f "${DATA}" ]; then
|
|
||||||
|
|
||||||
OLD_SIZE=$(stat -c%s "${DATA}")
|
|
||||||
|
|
||||||
if [ "$DATA_SIZE" -gt "$OLD_SIZE" ]; then
|
|
||||||
|
|
||||||
info "Resizing data disk from $OLD_SIZE to $DATA_SIZE bytes.."
|
|
||||||
|
|
||||||
if [[ "${ALLOCATE}" == [Nn]* ]]; then
|
|
||||||
|
|
||||||
# Resize file by changing its length
|
|
||||||
if ! truncate -s "${DATA_SIZE}" "${DATA}"; then
|
|
||||||
error "Could not resize the file for the virtual disk." && exit 85
|
|
||||||
fi
|
|
||||||
|
|
||||||
else
|
|
||||||
|
|
||||||
REQ=$((DATA_SIZE-OLD_SIZE))
|
|
||||||
|
|
||||||
# Check free diskspace
|
|
||||||
SPACE=$(df --output=avail -B 1 "${STORAGE}" | tail -n 1)
|
|
||||||
|
|
||||||
if (( REQ > SPACE )); then
|
|
||||||
error "Not enough free space to resize virtual disk to ${DISK_SIZE}."
|
|
||||||
error "Specify a smaller size or disable preallocation with ALLOCATE=N." && exit 84
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Resize file by allocating more space
|
|
||||||
if ! fallocate -l "${DATA_SIZE}" "${DATA}"; then
|
|
||||||
if ! truncate -s "${DATA_SIZE}" "${DATA}"; then
|
|
||||||
error "Could not resize the file for the virtual disk." && exit 85
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "${ALLOCATE}" == [Zz]* ]]; then
|
|
||||||
|
|
||||||
GB=$(( (REQ + 1073741823)/1073741824 ))
|
|
||||||
|
|
||||||
info "Preallocating ${GB} GB of diskspace, please wait..."
|
|
||||||
dd if=/dev/urandom of="${DATA}" seek="${OLD_SIZE}" count="${REQ}" bs=1M iflag=count_bytes oflag=seek_bytes status=none
|
|
||||||
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$DATA_SIZE" -lt "$OLD_SIZE" ]; then
|
|
||||||
|
|
||||||
info "Shrinking existing disks is not supported yet!"
|
|
||||||
info "Creating backup of old drive in storage folder..."
|
|
||||||
|
|
||||||
mv -f "${DATA}" "${DATA}.bak"
|
|
||||||
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ! -f "${DATA}" ]; then
|
|
||||||
|
|
||||||
if [[ "${ALLOCATE}" == [Nn]* ]]; then
|
|
||||||
|
|
||||||
# Create an empty file
|
|
||||||
if ! truncate -s "${DATA_SIZE}" "${DATA}"; then
|
|
||||||
rm -f "${DATA}"
|
|
||||||
error "Could not create a file for the virtual disk." && exit 87
|
|
||||||
fi
|
|
||||||
|
|
||||||
else
|
|
||||||
|
|
||||||
# Check free diskspace
|
|
||||||
SPACE=$(df --output=avail -B 1 "${STORAGE}" | tail -n 1)
|
|
||||||
|
|
||||||
if (( DATA_SIZE > SPACE )); then
|
|
||||||
error "Not enough free space to create a virtual disk of ${DISK_SIZE}."
|
|
||||||
error "Specify a smaller size or disable preallocation with ALLOCATE=N." && exit 86
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Create an empty file
|
|
||||||
if ! fallocate -l "${DATA_SIZE}" "${DATA}"; then
|
|
||||||
if ! truncate -s "${DATA_SIZE}" "${DATA}"; then
|
|
||||||
rm -f "${DATA}"
|
|
||||||
error "Could not create a file for the virtual disk." && exit 87
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "${ALLOCATE}" == [Zz]* ]]; then
|
|
||||||
|
|
||||||
info "Preallocating ${DISK_SIZE} of diskspace, please wait..."
|
|
||||||
dd if=/dev/urandom of="${DATA}" count="${DATA_SIZE}" bs=1M iflag=count_bytes status=none
|
|
||||||
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check if file exists
|
|
||||||
if [ ! -f "${DATA}" ]; then
|
|
||||||
error "Virtual disk does not exist ($DATA)" && exit 88
|
|
||||||
fi
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check the filesize
|
|
||||||
SIZE=$(stat -c%s "${DATA}")
|
|
||||||
|
|
||||||
if [[ SIZE -ne DATA_SIZE ]]; then
|
|
||||||
error "Virtual disk has the wrong size: ${SIZE}" && exit 89
|
|
||||||
fi
|
|
||||||
|
|
||||||
DISK_OPTS="\
|
|
||||||
-device virtio-scsi-pci,id=hw-synoboot,bus=pcie.0,addr=0xa \
|
|
||||||
-drive file=${BOOT},if=none,id=drive-synoboot,format=raw,cache=${DISK_CACHE},aio=${DISK_IO},discard=${DISK_DISCARD},detect-zeroes=on \
|
|
||||||
-device scsi-hd,bus=hw-synoboot.0,channel=0,scsi-id=0,lun=0,drive=drive-synoboot,id=synoboot0,rotation_rate=${DISK_ROTATION},bootindex=1 \
|
|
||||||
-device virtio-scsi-pci,id=hw-synosys,bus=pcie.0,addr=0xb \
|
|
||||||
-drive file=${SYSTEM},if=none,id=drive-synosys,format=raw,cache=${DISK_CACHE},aio=${DISK_IO},discard=${DISK_DISCARD},detect-zeroes=on \
|
|
||||||
-device scsi-hd,bus=hw-synosys.0,channel=0,scsi-id=0,lun=0,drive=drive-synosys,id=synosys0,rotation_rate=${DISK_ROTATION},bootindex=2 \
|
|
||||||
-device virtio-scsi-pci,id=hw-userdata,bus=pcie.0,addr=0xc \
|
|
||||||
-drive file=${DATA},if=none,id=drive-userdata,format=raw,cache=${DISK_CACHE},aio=${DISK_IO},discard=${DISK_DISCARD},detect-zeroes=on \
|
|
||||||
-device scsi-hd,bus=hw-userdata.0,channel=0,scsi-id=0,lun=0,drive=drive-userdata,id=userdata0,rotation_rate=${DISK_ROTATION},bootindex=3"
|
|
||||||
|
|
||||||
: ${DISK2_SIZE:=''}
|
|
||||||
EXTRA_SIZE=DISK2_SIZE
|
|
||||||
EXTRA_DISK="/storage2/data.img"
|
|
||||||
|
|
||||||
if [ -d "$(dirname "${EXTRA_DISK}")" ]; then
|
|
||||||
|
|
||||||
if [ ! -f "${EXTRA_DISK}" ]; then
|
|
||||||
[ -z "$EXTRA_SIZE" ] && EXTRA_SIZE="16G"
|
|
||||||
if ! truncate -s "${EXTRA_SIZE}" "${EXTRA_DISK}"; then
|
|
||||||
error "Could not create the file for the second disk." && exit 53
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "$EXTRA_SIZE" ]; then
|
|
||||||
CUR_SIZE=$(stat -c%s "${EXTRA_DISK}")
|
|
||||||
DATA_SIZE=$(numfmt --from=iec "${EXTRA_SIZE}")
|
|
||||||
if [ "$DATA_SIZE" -gt "$CUR_SIZE" ]; then
|
|
||||||
truncate -s "${EXTRA_SIZE}" "${EXTRA_DISK}"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
DISK_OPTS="${DISK_OPTS} \
|
|
||||||
-device virtio-scsi-pci,id=hw-userdata2,bus=pcie.0,addr=0xd \
|
|
||||||
-drive file=${EXTRA_DISK},if=none,id=drive-userdata2,format=raw,cache=${DISK_CACHE},aio=${DISK_IO},discard=${DISK_DISCARD},detect-zeroes=on \
|
|
||||||
-device scsi-hd,bus=hw-userdata2.0,channel=0,scsi-id=0,lun=0,drive=drive-userdata2,id=userdata2,rotation_rate=${DISK_ROTATION},bootindex=4"
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
: ${DISK3_SIZE:=''}
|
|
||||||
EXTRA_SIZE=DISK3_SIZE
|
|
||||||
EXTRA_DISK="/storage3/data.img"
|
|
||||||
|
|
||||||
if [ -d "$(dirname "${EXTRA_DISK}")" ]; then
|
|
||||||
|
|
||||||
if [ ! -f "${EXTRA_DISK}" ]; then
|
|
||||||
[ -z "$EXTRA_SIZE" ] && EXTRA_SIZE="16G"
|
|
||||||
if ! truncate -s "${EXTRA_SIZE}" "${EXTRA_DISK}"; then
|
|
||||||
error "Could not create the file for the third disk." && exit 54
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "$EXTRA_SIZE" ]; then
|
|
||||||
CUR_SIZE=$(stat -c%s "${EXTRA_DISK}")
|
|
||||||
DATA_SIZE=$(numfmt --from=iec "${EXTRA_SIZE}")
|
|
||||||
if [ "$DATA_SIZE" -gt "$CUR_SIZE" ]; then
|
|
||||||
truncate -s "${EXTRA_SIZE}" "${EXTRA_DISK}"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
DISK_OPTS="${DISK_OPTS} \
|
|
||||||
-device virtio-scsi-pci,id=hw-userdata3,bus=pcie.0,addr=0xe \
|
|
||||||
-drive file=${EXTRA_DISK},if=none,id=drive-userdata3,format=raw,cache=${DISK_CACHE},aio=${DISK_IO},discard=${DISK_DISCARD},detect-zeroes=on \
|
|
||||||
-device scsi-hd,bus=hw-userdata3.0,channel=0,scsi-id=0,lun=0,drive=drive-userdata3,id=userdata3,rotation_rate=${DISK_ROTATION},bootindex=5"
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
: ${DEVICE:=''} # Docker variable to passthrough a block device, like /dev/vdc1.
|
|
||||||
: ${DEVICE2:=''}
|
|
||||||
: ${DEVICE3:=''}
|
|
||||||
|
|
||||||
if [ -n "${DEVICE}" ]; then
|
|
||||||
|
|
||||||
[ ! -b "${DEVICE}" ] && error "Device ${DEVICE} cannot be found! Please add it to the 'devices' section of your compose file." && exit 55
|
|
||||||
|
|
||||||
DISK_OPTS="${DISK_OPTS} \
|
|
||||||
-device virtio-scsi-pci,id=hw-userdata4,bus=pcie.0,addr=0xf \
|
|
||||||
-drive file=${DEVICE},if=none,id=drive-userdata4,format=raw,cache=${DISK_CACHE},aio=${DISK_IO},discard=${DISK_DISCARD},detect-zeroes=on \
|
|
||||||
-device scsi-hd,bus=hw-userdata4.0,channel=0,scsi-id=0,lun=0,drive=drive-userdata4,id=userdata4,rotation_rate=${DISK_ROTATION},bootindex=6"
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "${DEVICE2}" ]; then
|
|
||||||
|
|
||||||
[ ! -b "${DEVICE2}" ] && error "Device ${DEVICE2} cannot be found! Please add it to the 'devices' section of your compose file." && exit 56
|
|
||||||
|
|
||||||
DISK_OPTS="${DISK_OPTS} \
|
|
||||||
-device virtio-scsi-pci,id=hw-userdata5,bus=pcie.0,addr=0x5 \
|
|
||||||
-drive file=${DEVICE2},if=none,id=drive-userdata5,format=raw,cache=${DISK_CACHE},aio=${DISK_IO},discard=${DISK_DISCARD},detect-zeroes=on \
|
|
||||||
-device scsi-hd,bus=hw-userdata5.0,channel=0,scsi-id=0,lun=0,drive=drive-userdata5,id=userdata5,rotation_rate=${DISK_ROTATION},bootindex=7"
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "${DEVICE3}" ]; then
|
|
||||||
|
|
||||||
[ ! -b "${DEVICE3}" ] && error "Device ${DEVICE3} cannot be found! Please add it to the 'devices' section of your compose file." && exit 57
|
|
||||||
|
|
||||||
DISK_OPTS="${DISK_OPTS} \
|
|
||||||
-device virtio-scsi-pci,id=hw-userdata6,bus=pcie.0,addr=0x6 \
|
|
||||||
-drive file=${DEVICE3},if=none,id=drive-userdata6,format=raw,cache=${DISK_CACHE},aio=${DISK_IO},discard=${DISK_DISCARD},detect-zeroes=on \
|
|
||||||
-device scsi-hd,bus=hw-userdata6.0,channel=0,scsi-id=0,lun=0,drive=drive-userdata6,id=userdata6,rotation_rate=${DISK_ROTATION},bootindex=8"
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
return 0
|
|
||||||
-45
@@ -1,45 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
set -Eeuo pipefail
|
|
||||||
|
|
||||||
if [[ "${GPU}" != [Yy1]* ]] || [[ "$ARCH" != "amd64" ]]; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
[ ! -d /dev/dri ] && mkdir -m 755 /dev/dri
|
|
||||||
|
|
||||||
if [ ! -c /dev/dri/card0 ]; then
|
|
||||||
mknod /dev/dri/card0 c 226 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ! -c /dev/dri/renderD128 ]; then
|
|
||||||
mknod /dev/dri/renderD128 c 226 128
|
|
||||||
fi
|
|
||||||
|
|
||||||
chmod 666 /dev/dri/card0
|
|
||||||
chmod 666 /dev/dri/renderD128
|
|
||||||
|
|
||||||
if ! apt-mark showinstall | grep -q "xserver-xorg-video-intel"; then
|
|
||||||
|
|
||||||
info "Installing Intel GPU drivers..."
|
|
||||||
|
|
||||||
export DEBCONF_NOWARNINGS="yes"
|
|
||||||
export DEBIAN_FRONTEND="noninteractive"
|
|
||||||
|
|
||||||
apt-get -qq update
|
|
||||||
apt-get -qq --no-install-recommends -y install xserver-xorg-video-intel > /dev/null
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! apt-mark showinstall | grep -q "qemu-system-modules-opengl"; then
|
|
||||||
|
|
||||||
info "Installing OpenGL module..."
|
|
||||||
|
|
||||||
export DEBCONF_NOWARNINGS="yes"
|
|
||||||
export DEBIAN_FRONTEND="noninteractive"
|
|
||||||
|
|
||||||
apt-get -qq update
|
|
||||||
apt-get -qq --no-install-recommends -y install qemu-system-modules-opengl > /dev/null
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
return 0
|
|
||||||
-287
@@ -1,287 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -Eeuo pipefail
|
|
||||||
|
|
||||||
STORAGE="/storage"
|
|
||||||
[ ! -d "$STORAGE" ] && error "Storage folder (${STORAGE}) not found!" && exit 13
|
|
||||||
|
|
||||||
if [ -f "$STORAGE"/dsm.ver ]; then
|
|
||||||
BASE=$(cat "${STORAGE}/dsm.ver")
|
|
||||||
else
|
|
||||||
# Fallback for old installs
|
|
||||||
BASE="DSM_VirtualDSM_42962"
|
|
||||||
fi
|
|
||||||
|
|
||||||
[ -n "$URL" ] && BASE=$(basename "$URL" .pat)
|
|
||||||
|
|
||||||
if [[ -f "$STORAGE/$BASE.boot.img" ]] && [[ -f "$STORAGE/$BASE.system.img" ]]; then
|
|
||||||
# Previous installation found
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Display wait message
|
|
||||||
/run/server.sh 5000 install &
|
|
||||||
|
|
||||||
# Download the required files from the Synology website
|
|
||||||
DL="https://global.synologydownload.com/download/DSM"
|
|
||||||
|
|
||||||
if [ -z "$URL" ]; then
|
|
||||||
|
|
||||||
if [ "$ARCH" == "amd64" ]; then
|
|
||||||
URL="$DL/release/7.2.1/69057-1/DSM_VirtualDSM_69057.pat"
|
|
||||||
else
|
|
||||||
URL="$DL/release/7.0.1/42218/DSM_VirtualDSM_42218.pat"
|
|
||||||
fi
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check if output is to interactive TTY
|
|
||||||
if [ -t 1 ]; then
|
|
||||||
PROGRESS="--progress=bar:noscroll"
|
|
||||||
else
|
|
||||||
PROGRESS="--progress=dot:giga"
|
|
||||||
fi
|
|
||||||
|
|
||||||
BASE=$(basename "$URL" .pat)
|
|
||||||
|
|
||||||
rm -f "$STORAGE"/"$BASE".pat
|
|
||||||
rm -f "$STORAGE"/"$BASE".agent
|
|
||||||
rm -f "$STORAGE"/"$BASE".boot.img
|
|
||||||
rm -f "$STORAGE"/"$BASE".system.img
|
|
||||||
|
|
||||||
MIN_SPACE=6442450944
|
|
||||||
FS=$(stat -f -c %T "$STORAGE")
|
|
||||||
|
|
||||||
if [[ "$FS" != "fat"* && "$FS" != "vfat"* && "$FS" != "exfat"* && \
|
|
||||||
"$FS" != "ntfs"* && "$FS" != "fuse"* && "$FS" != "msdos"* ]]; then
|
|
||||||
TMP="$STORAGE/tmp"
|
|
||||||
else
|
|
||||||
TMP="/tmp/dsm"
|
|
||||||
SPACE=$(df --output=avail -B 1 /tmp | tail -n 1)
|
|
||||||
(( MIN_SPACE > SPACE )) && TMP="$STORAGE/tmp"
|
|
||||||
fi
|
|
||||||
|
|
||||||
rm -rf /tmp/dsm
|
|
||||||
rm -rf "$STORAGE/tmp"
|
|
||||||
rm -rf "$TMP" && mkdir -p "$TMP"
|
|
||||||
|
|
||||||
# Check free diskspace
|
|
||||||
SPACE=$(df --output=avail -B 1 "$TMP" | tail -n 1)
|
|
||||||
(( MIN_SPACE > SPACE )) && error "Not enough free space for installation, need at least 6 GB." && exit 95
|
|
||||||
|
|
||||||
[[ "${DEBUG}" == [Yy1]* ]] && set -x
|
|
||||||
|
|
||||||
RDC="$STORAGE/dsm.rd"
|
|
||||||
|
|
||||||
if [ ! -f "${RDC}" ]; then
|
|
||||||
|
|
||||||
info "Install: Downloading installer..."
|
|
||||||
|
|
||||||
RD="$TMP/rd.gz"
|
|
||||||
POS="65627648-71021835"
|
|
||||||
VERIFY="b4215a4b213ff5154db0488f92c87864"
|
|
||||||
LOC="$DL/release/7.0.1/42218/DSM_VirtualDSM_42218.pat"
|
|
||||||
|
|
||||||
{ curl -r "$POS" -sfk -o "$RD" "$LOC"; rc=$?; } || :
|
|
||||||
(( rc != 0 )) && error "Failed to download $LOC, reason: $rc" && exit 60
|
|
||||||
|
|
||||||
SUM=$(md5sum "$RD" | cut -f 1 -d " ")
|
|
||||||
|
|
||||||
if [ "$SUM" != "$VERIFY" ]; then
|
|
||||||
|
|
||||||
PAT="/install.pat"
|
|
||||||
rm "$RD"
|
|
||||||
rm -f "$PAT"
|
|
||||||
|
|
||||||
{ wget "$LOC" -O "$PAT" -q --no-check-certificate --show-progress "$PROGRESS"; rc=$?; } || :
|
|
||||||
(( rc != 0 )) && error "Failed to download $LOC, reason: $rc" && exit 60
|
|
||||||
|
|
||||||
tar --extract --file="$PAT" --directory="$(dirname "${RD}")"/. "$(basename "${RD}")"
|
|
||||||
rm "$PAT"
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
cp "$RD" "$RDC"
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -f "${RDC}" ]; then
|
|
||||||
|
|
||||||
{ xz -dc <"$RDC" >"$TMP/rd" 2>/dev/null; rc=$?; } || :
|
|
||||||
(( rc != 1 )) && error "Failed to unxz $RDC, reason $rc" && exit 91
|
|
||||||
|
|
||||||
{ (cd "$TMP" && cpio -idm <"$TMP/rd" 2>/dev/null); rc=$?; } || :
|
|
||||||
(( rc != 0 )) && error "Failed to cpio $RDC, reason $rc" && exit 92
|
|
||||||
|
|
||||||
mkdir -p /run/extract
|
|
||||||
for file in $TMP/usr/lib/libcurl.so.4 \
|
|
||||||
$TMP/usr/lib/libmbedcrypto.so.5 \
|
|
||||||
$TMP/usr/lib/libmbedtls.so.13 \
|
|
||||||
$TMP/usr/lib/libmbedx509.so.1 \
|
|
||||||
$TMP/usr/lib/libmsgpackc.so.2 \
|
|
||||||
$TMP/usr/lib/libsodium.so \
|
|
||||||
$TMP/usr/lib/libsynocodesign-ng-virtual-junior-wins.so.7 \
|
|
||||||
$TMP/usr/syno/bin/scemd; do
|
|
||||||
cp "$file" /run/extract/
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ "$ARCH" != "amd64" ]; then
|
|
||||||
mkdir -p /lib64/
|
|
||||||
cp "$TMP/usr/lib/libc.so.6" /lib64/
|
|
||||||
cp "$TMP/usr/lib/libpthread.so.0" /lib64/
|
|
||||||
cp "$TMP/usr/lib/ld-linux-x86-64.so.2" /lib64/
|
|
||||||
fi
|
|
||||||
|
|
||||||
mv /run/extract/scemd /run/extract/syno_extract_system_patch
|
|
||||||
chmod +x /run/extract/syno_extract_system_patch
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
rm -rf "$TMP" && mkdir -p "$TMP"
|
|
||||||
|
|
||||||
info "Install: Downloading $(basename "$URL")..."
|
|
||||||
|
|
||||||
PAT="/$BASE.pat"
|
|
||||||
rm -f "$PAT"
|
|
||||||
|
|
||||||
{ wget "$URL" -O "$PAT" -q --no-check-certificate --show-progress "$PROGRESS"; rc=$?; } || :
|
|
||||||
|
|
||||||
(( rc != 0 )) && error "Failed to download $URL, reason: $rc" && exit 69
|
|
||||||
[ ! -f "$PAT" ] && error "Failed to download $URL" && exit 69
|
|
||||||
|
|
||||||
SIZE=$(stat -c%s "$PAT")
|
|
||||||
|
|
||||||
if ((SIZE<250000000)); then
|
|
||||||
error "The specified PAT file is probably an update pack as it's too small." && exit 62
|
|
||||||
fi
|
|
||||||
|
|
||||||
if { tar tf "$PAT"; } >/dev/null 2>&1; then
|
|
||||||
|
|
||||||
info "Install: Extracting downloaded image..."
|
|
||||||
tar xpf "$PAT" -C "$TMP/."
|
|
||||||
|
|
||||||
else
|
|
||||||
|
|
||||||
if [ "$ARCH" != "amd64" ]; then
|
|
||||||
|
|
||||||
info "Install: Installing QEMU..."
|
|
||||||
|
|
||||||
export DEBCONF_NOWARNINGS="yes"
|
|
||||||
export DEBIAN_FRONTEND="noninteractive"
|
|
||||||
|
|
||||||
apt-get -qq update
|
|
||||||
apt-get -qq --no-install-recommends -y install qemu-user > /dev/null
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
info "Install: Extracting downloaded image..."
|
|
||||||
|
|
||||||
export LD_LIBRARY_PATH="/run/extract"
|
|
||||||
|
|
||||||
if [ "$ARCH" == "amd64" ]; then
|
|
||||||
{ /run/extract/syno_extract_system_patch "$PAT" "$TMP/."; rc=$?; } || :
|
|
||||||
else
|
|
||||||
{ qemu-x86_64 /run/extract/syno_extract_system_patch "$PAT" "$TMP/."; rc=$?; } || :
|
|
||||||
fi
|
|
||||||
|
|
||||||
export LD_LIBRARY_PATH=""
|
|
||||||
|
|
||||||
(( rc != 0 )) && error "Failed to extract PAT file, reason $rc" && exit 63
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
HDA="$TMP/hda1"
|
|
||||||
IDB="$TMP/indexdb"
|
|
||||||
PKG="$TMP/packages"
|
|
||||||
HDP="$TMP/synohdpack_img"
|
|
||||||
|
|
||||||
[ ! -f "$HDA.tgz" ] && error "The PAT file contains no OS image." && exit 64
|
|
||||||
|
|
||||||
BOOT=$(find "$TMP" -name "*.bin.zip")
|
|
||||||
[ ! -f "$BOOT" ] && error "The PAT file contains no boot image." && exit 67
|
|
||||||
|
|
||||||
BOOT=$(echo "$BOOT" | head -c -5)
|
|
||||||
unzip -q -o "$BOOT".zip -d "$TMP"
|
|
||||||
|
|
||||||
[[ "${ALLOCATE}" == [Zz]* ]] && info "Install: Allocating diskspace..."
|
|
||||||
|
|
||||||
SYSTEM="$TMP/sys.img"
|
|
||||||
SYSTEM_SIZE=4954537983
|
|
||||||
|
|
||||||
# Check free diskspace
|
|
||||||
SPACE=$(df --output=avail -B 1 "$TMP" | tail -n 1)
|
|
||||||
(( SYSTEM_SIZE > SPACE )) && error "Not enough free space to create a 4 GB system disk." && exit 87
|
|
||||||
|
|
||||||
if ! fallocate -l "${SYSTEM_SIZE}" "${SYSTEM}"; then
|
|
||||||
if ! truncate -s "${SYSTEM_SIZE}" "${SYSTEM}"; then
|
|
||||||
rm -f "${SYSTEM}" && error "Could not allocate a file for the system disk." && exit 88
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "${ALLOCATE}" == [Zz]* ]]; then
|
|
||||||
info "Install: Preallocating 4 GB of diskspace..."
|
|
||||||
dd if=/dev/urandom of="${SYSTEM}" count="${SYSTEM_SIZE}" bs=1M iflag=count_bytes status=none
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check if file exists
|
|
||||||
[ ! -f "${SYSTEM}" ] && error "System disk does not exist ($SYSTEM)" && exit 89
|
|
||||||
|
|
||||||
# Check the filesize
|
|
||||||
SIZE=$(stat -c%s "${SYSTEM}")
|
|
||||||
[[ SIZE -ne SYSTEM_SIZE ]] && rm -f "${SYSTEM}" && error "System disk has the wrong size: ${SIZE}" && exit 90
|
|
||||||
|
|
||||||
PART="$TMP/partition.fdisk"
|
|
||||||
|
|
||||||
{ echo "label: dos"
|
|
||||||
echo "label-id: 0x6f9ee2e9"
|
|
||||||
echo "device: ${SYSTEM}"
|
|
||||||
echo "unit: sectors"
|
|
||||||
echo "sector-size: 512"
|
|
||||||
echo ""
|
|
||||||
echo "${SYSTEM}1 : start= 2048, size= 4980480, type=83"
|
|
||||||
echo "${SYSTEM}2 : start= 4982528, size= 4194304, type=82"
|
|
||||||
} > "$PART"
|
|
||||||
|
|
||||||
sfdisk -q "$SYSTEM" < "$PART"
|
|
||||||
|
|
||||||
info "Install: Extracting system partition..."
|
|
||||||
|
|
||||||
MOUNT="$TMP/system"
|
|
||||||
rm -rf "$MOUNT" && mkdir -p "$MOUNT"
|
|
||||||
|
|
||||||
mv "$HDA.tgz" "$HDA.txz"
|
|
||||||
tar xpfJ "$HDA.txz" --absolute-names -C "$MOUNT/"
|
|
||||||
|
|
||||||
[ -d "$PKG" ] && mv "$PKG/" "$MOUNT/.SynoUpgradePackages/"
|
|
||||||
rm -f "$MOUNT/.SynoUpgradePackages/ActiveInsight-"*
|
|
||||||
|
|
||||||
[ -f "$HDP.txz" ] && tar xpfJ "$HDP.txz" --absolute-names -C "$MOUNT/"
|
|
||||||
[ -f "$IDB.txz" ] && tar xpfJ "$IDB.txz" --absolute-names -C "$MOUNT/usr/syno/synoman/indexdb/"
|
|
||||||
|
|
||||||
info "Install: Installing system partition..."
|
|
||||||
|
|
||||||
LABEL="1.44.1-42218"
|
|
||||||
OFFSET="1048576" # 2048 * 512
|
|
||||||
NUMBLOCKS="622560" # (4980480 * 512) / 4096
|
|
||||||
|
|
||||||
mke2fs -q -t ext4 -b 4096 -d "$MOUNT/" -L "$LABEL" -F -E "offset=$OFFSET" "$SYSTEM" "$NUMBLOCKS"
|
|
||||||
|
|
||||||
rm -rf "$MOUNT"
|
|
||||||
|
|
||||||
echo "$BASE" > "$STORAGE"/dsm.ver
|
|
||||||
|
|
||||||
if [[ "$TMP" != "$STORAGE/tmp" ]]; then
|
|
||||||
# Check free diskspace
|
|
||||||
SPACE=$(df --output=avail -B 1 "$STORAGE" | tail -n 1)
|
|
||||||
(( MIN_SPACE > SPACE )) && error "Not enough free space in storage folder, need at least 6 GB." && exit 94
|
|
||||||
fi
|
|
||||||
|
|
||||||
mv -f "$PAT" "$STORAGE"/"$BASE".pat
|
|
||||||
mv -f "$BOOT" "$STORAGE"/"$BASE".boot.img
|
|
||||||
mv -f "$SYSTEM" "$STORAGE"/"$BASE".system.img
|
|
||||||
|
|
||||||
rm -rf "$TMP"
|
|
||||||
|
|
||||||
{ set +x; } 2>/dev/null
|
|
||||||
[[ "${DEBUG}" == [Yy1]* ]] && echo
|
|
||||||
|
|
||||||
return 0
|
|
||||||
-259
@@ -1,259 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -Eeuo pipefail
|
|
||||||
|
|
||||||
# Docker environment variables
|
|
||||||
|
|
||||||
: ${DHCP:='N'}
|
|
||||||
: ${MAC:='02:11:32:AA:BB:CC'}
|
|
||||||
|
|
||||||
: ${VM_NET_TAP:='dsm'}
|
|
||||||
: ${VM_NET_DEV:='eth0'}
|
|
||||||
: ${VM_NET_MAC:="$MAC"}
|
|
||||||
: ${VM_NET_HOST:='VirtualDSM'}
|
|
||||||
|
|
||||||
: ${DNS_SERVERS:=''}
|
|
||||||
: ${DNSMASQ_OPTS:=''}
|
|
||||||
: ${DNSMASQ:='/usr/sbin/dnsmasq'}
|
|
||||||
: ${DNSMASQ_CONF_DIR:='/etc/dnsmasq.d'}
|
|
||||||
|
|
||||||
# ######################################
|
|
||||||
# Functions
|
|
||||||
# ######################################
|
|
||||||
|
|
||||||
configureDHCP() {
|
|
||||||
|
|
||||||
# Create a macvtap network for the VM guest
|
|
||||||
|
|
||||||
{ ip link add link "${VM_NET_DEV}" name "${VM_NET_TAP}" address "${VM_NET_MAC}" type macvtap mode bridge ; rc=$?; } || :
|
|
||||||
|
|
||||||
if (( rc != 0 )); then
|
|
||||||
error "Cannot create macvtap interface. Please make sure the network type is 'macvlan' and not 'ipvlan',"
|
|
||||||
error "and that the NET_ADMIN capability has been added to the container config: --cap-add NET_ADMIN" && exit 16
|
|
||||||
fi
|
|
||||||
|
|
||||||
while ! ip link set "${VM_NET_TAP}" up; do
|
|
||||||
info "Waiting for address to become available..."
|
|
||||||
sleep 2
|
|
||||||
done
|
|
||||||
|
|
||||||
TAP_NR=$(</sys/class/net/"${VM_NET_TAP}"/ifindex)
|
|
||||||
TAP_PATH="/dev/tap${TAP_NR}"
|
|
||||||
|
|
||||||
# Create dev file (there is no udev in container: need to be done manually)
|
|
||||||
IFS=: read -r MAJOR MINOR < <(cat /sys/devices/virtual/net/"${VM_NET_TAP}"/tap*/dev)
|
|
||||||
(( MAJOR < 1)) && error "Cannot find: sys/devices/virtual/net/${VM_NET_TAP}" && exit 18
|
|
||||||
|
|
||||||
[[ ! -e "${TAP_PATH}" ]] && [[ -e "/dev0/${TAP_PATH##*/}" ]] && ln -s "/dev0/${TAP_PATH##*/}" "${TAP_PATH}"
|
|
||||||
|
|
||||||
if [[ ! -e "${TAP_PATH}" ]]; then
|
|
||||||
{ mknod "${TAP_PATH}" c "$MAJOR" "$MINOR" ; rc=$?; } || :
|
|
||||||
(( rc != 0 )) && error "Cannot mknod: ${TAP_PATH} ($rc)" && exit 20
|
|
||||||
fi
|
|
||||||
|
|
||||||
{ exec 30>>"$TAP_PATH"; rc=$?; } 2>/dev/null || :
|
|
||||||
|
|
||||||
if (( rc != 0 )); then
|
|
||||||
error "Cannot create TAP interface ($rc). Please add the following docker settings to your "
|
|
||||||
error "container: --device-cgroup-rule='c ${MAJOR}:* rwm' --device=/dev/vhost-net" && exit 21
|
|
||||||
fi
|
|
||||||
|
|
||||||
{ exec 40>>/dev/vhost-net; rc=$?; } 2>/dev/null || :
|
|
||||||
|
|
||||||
if (( rc != 0 )); then
|
|
||||||
error "VHOST can not be found ($rc). Please add the following "
|
|
||||||
error "docker setting to your container: --device=/dev/vhost-net" && exit 22
|
|
||||||
fi
|
|
||||||
|
|
||||||
NET_OPTS="-netdev tap,id=hostnet0,vhost=on,vhostfd=40,fd=30"
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
configureDNS () {
|
|
||||||
|
|
||||||
# dnsmasq configuration:
|
|
||||||
DNSMASQ_OPTS="$DNSMASQ_OPTS --dhcp-range=$VM_NET_IP,$VM_NET_IP --dhcp-host=$VM_NET_MAC,,$VM_NET_IP,$VM_NET_HOST,infinite --dhcp-option=option:netmask,255.255.255.0"
|
|
||||||
|
|
||||||
# Create lease file for faster resolve
|
|
||||||
echo "0 $VM_NET_MAC $VM_NET_IP $VM_NET_HOST 01:${VM_NET_MAC}" > /var/lib/misc/dnsmasq.leases
|
|
||||||
chmod 644 /var/lib/misc/dnsmasq.leases
|
|
||||||
|
|
||||||
# Build DNS options from container /etc/resolv.conf
|
|
||||||
|
|
||||||
if [[ "${DEBUG}" == [Yy1]* ]]; then
|
|
||||||
echo "/etc/resolv.conf:" && echo && cat /etc/resolv.conf && echo
|
|
||||||
fi
|
|
||||||
|
|
||||||
mapfile -t nameservers < <( { grep '^nameserver' /etc/resolv.conf || true; } | sed 's/\t/ /g' | sed 's/nameserver //' | sed 's/ //g')
|
|
||||||
searchdomains=$( { grep '^search' /etc/resolv.conf || true; } | sed 's/\t/ /g' | sed 's/search //' | sed 's/#.*//' | sed 's/\s*$//g' | sed 's/ /,/g')
|
|
||||||
domainname=$(echo "$searchdomains" | awk -F"," '{print $1}')
|
|
||||||
|
|
||||||
for nameserver in "${nameservers[@]}"; do
|
|
||||||
nameserver=$(echo "$nameserver" | sed 's/#.*//' )
|
|
||||||
if ! [[ "$nameserver" =~ .*:.* ]]; then
|
|
||||||
[[ -z "$DNS_SERVERS" ]] && DNS_SERVERS="$nameserver" || DNS_SERVERS="$DNS_SERVERS,$nameserver"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
[[ -z "$DNS_SERVERS" ]] && DNS_SERVERS="1.1.1.1"
|
|
||||||
|
|
||||||
DNSMASQ_OPTS="$DNSMASQ_OPTS --dhcp-option=option:dns-server,$DNS_SERVERS --dhcp-option=option:router,${VM_NET_IP%.*}.1"
|
|
||||||
|
|
||||||
if [ -n "$searchdomains" ] && [ "$searchdomains" != "." ]; then
|
|
||||||
DNSMASQ_OPTS="$DNSMASQ_OPTS --dhcp-option=option:domain-search,$searchdomains --dhcp-option=option:domain-name,$domainname"
|
|
||||||
else
|
|
||||||
[[ -z $(hostname -d) ]] || DNSMASQ_OPTS="$DNSMASQ_OPTS --dhcp-option=option:domain-name,$(hostname -d)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
DNSMASQ_OPTS=$(echo "$DNSMASQ_OPTS" | sed 's/\t/ /g' | tr -s ' ' | sed 's/^ *//')
|
|
||||||
|
|
||||||
[[ "${DEBUG}" == [Yy1]* ]] && set -x
|
|
||||||
$DNSMASQ ${DNSMASQ_OPTS:+ $DNSMASQ_OPTS}
|
|
||||||
{ set +x; } 2>/dev/null
|
|
||||||
[[ "${DEBUG}" == [Yy1]* ]] && echo
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
configureNAT () {
|
|
||||||
|
|
||||||
# Create a bridge with a static IP for the VM guest
|
|
||||||
|
|
||||||
VM_NET_IP='20.20.20.21'
|
|
||||||
[[ "${DEBUG}" == [Yy1]* ]] && set -x
|
|
||||||
|
|
||||||
{ ip link add dev dockerbridge type bridge ; rc=$?; } || :
|
|
||||||
|
|
||||||
if (( rc != 0 )); then
|
|
||||||
error "Capability NET_ADMIN has not been set most likely. Please add the "
|
|
||||||
error "following docker setting to your container: --cap-add NET_ADMIN" && exit 23
|
|
||||||
fi
|
|
||||||
|
|
||||||
ip address add ${VM_NET_IP%.*}.1/24 broadcast ${VM_NET_IP%.*}.255 dev dockerbridge
|
|
||||||
|
|
||||||
while ! ip link set dockerbridge up; do
|
|
||||||
info "Waiting for address to become available..."
|
|
||||||
sleep 2
|
|
||||||
done
|
|
||||||
|
|
||||||
# QEMU Works with taps, set tap to the bridge created
|
|
||||||
ip tuntap add dev "${VM_NET_TAP}" mode tap
|
|
||||||
|
|
||||||
while ! ip link set "${VM_NET_TAP}" up promisc on; do
|
|
||||||
info "Waiting for tap to become available..."
|
|
||||||
sleep 2
|
|
||||||
done
|
|
||||||
|
|
||||||
ip link set dev "${VM_NET_TAP}" master dockerbridge
|
|
||||||
|
|
||||||
# Add internet connection to the VM
|
|
||||||
IP=$(ip address show dev "${VM_NET_DEV}" | grep inet | awk '/inet / { print $2 }' | cut -f1 -d/)
|
|
||||||
|
|
||||||
iptables -t nat -A POSTROUTING -o "${VM_NET_DEV}" -j MASQUERADE
|
|
||||||
iptables -t nat -A PREROUTING -i "${VM_NET_DEV}" -d "${IP}" -p tcp -j DNAT --to $VM_NET_IP
|
|
||||||
iptables -t nat -A PREROUTING -i "${VM_NET_DEV}" -d "${IP}" -p udp -j DNAT --to $VM_NET_IP
|
|
||||||
|
|
||||||
if (( KERNEL > 4 )); then
|
|
||||||
# Hack for guest VMs complaining about "bad udp checksums in 5 packets"
|
|
||||||
iptables -A POSTROUTING -t mangle -p udp --dport bootpc -j CHECKSUM --checksum-fill || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
{ set +x; } 2>/dev/null
|
|
||||||
[[ "${DEBUG}" == [Yy1]* ]] && echo
|
|
||||||
|
|
||||||
# Check port forwarding flag
|
|
||||||
if [[ $(< /proc/sys/net/ipv4/ip_forward) -eq 0 ]]; then
|
|
||||||
{ sysctl -w net.ipv4.ip_forward=1 ; rc=$?; } || :
|
|
||||||
if (( rc != 0 )); then
|
|
||||||
error "Please add the following docker setting to your container: --sysctl net.ipv4.ip_forward=1" && exit 24
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
NET_OPTS="-netdev tap,ifname=${VM_NET_TAP},script=no,downscript=no,id=hostnet0"
|
|
||||||
|
|
||||||
{ exec 40>>/dev/vhost-net; rc=$?; } 2>/dev/null || :
|
|
||||||
(( rc == 0 )) && NET_OPTS="$NET_OPTS,vhost=on,vhostfd=40"
|
|
||||||
|
|
||||||
configureDNS
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
closeNetwork () {
|
|
||||||
|
|
||||||
if [[ "${DHCP}" == [Yy1]* ]]; then
|
|
||||||
|
|
||||||
ip link set "${VM_NET_TAP}" down || true
|
|
||||||
ip link delete "${VM_NET_TAP}" || true
|
|
||||||
|
|
||||||
else
|
|
||||||
|
|
||||||
ip link set "${VM_NET_TAP}" down promisc off || true
|
|
||||||
ip link delete "${VM_NET_TAP}" || true
|
|
||||||
|
|
||||||
ip link set dockerbridge down || true
|
|
||||||
ip link delete dockerbridge || true
|
|
||||||
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# ######################################
|
|
||||||
# Configure Network
|
|
||||||
# ######################################
|
|
||||||
|
|
||||||
{ pkill -f server.sh || true; } 2>/dev/null
|
|
||||||
|
|
||||||
# Create the necessary file structure for /dev/net/tun
|
|
||||||
if [ ! -c /dev/net/tun ]; then
|
|
||||||
[ ! -d /dev/net ] && mkdir -m 755 /dev/net
|
|
||||||
mknod /dev/net/tun c 10 200
|
|
||||||
chmod 666 /dev/net/tun
|
|
||||||
fi
|
|
||||||
|
|
||||||
[ ! -c /dev/net/tun ] && error "TUN network interface not available..." && exit 85
|
|
||||||
|
|
||||||
# Create the necessary file structure for /dev/vhost-net
|
|
||||||
if [ ! -c /dev/vhost-net ]; then
|
|
||||||
mknod /dev/vhost-net c 10 238
|
|
||||||
chmod 660 /dev/vhost-net
|
|
||||||
fi
|
|
||||||
|
|
||||||
update-alternatives --set iptables /usr/sbin/iptables-legacy > /dev/null
|
|
||||||
update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy > /dev/null
|
|
||||||
|
|
||||||
VM_NET_MAC="${VM_NET_MAC//-/:}"
|
|
||||||
GATEWAY=$(ip r | grep default | awk '{print $3}')
|
|
||||||
|
|
||||||
if [[ "${DEBUG}" == [Yy1]* ]]; then
|
|
||||||
|
|
||||||
IP=$(ip address show dev "${VM_NET_DEV}" | grep inet | awk '/inet / { print $2 }' | cut -f1 -d/)
|
|
||||||
info "Container IP is ${IP} with gateway ${GATEWAY}" && echo
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "${DHCP}" == [Yy1]* ]]; then
|
|
||||||
|
|
||||||
if [[ "$GATEWAY" == "172."* ]]; then
|
|
||||||
if [[ "${DEBUG}" == [Yy1]* ]]; then
|
|
||||||
info "Warning: Are you sure the container is on a macvlan network?"
|
|
||||||
else
|
|
||||||
error "You can only enable DHCP while the container is on a macvlan network!" && exit 86
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Configuration for DHCP IP
|
|
||||||
configureDHCP
|
|
||||||
|
|
||||||
# Display IP on port 80 and 5000
|
|
||||||
/run/server.sh 5000 /run/ip.sh &
|
|
||||||
|
|
||||||
else
|
|
||||||
|
|
||||||
# Configuration for static IP
|
|
||||||
configureNAT
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
NET_OPTS="${NET_OPTS} -device virtio-net-pci,romfile=,netdev=hostnet0,mac=${VM_NET_MAC},id=net0"
|
|
||||||
|
|
||||||
return 0
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -Eeuo pipefail
|
|
||||||
|
|
||||||
# Configure QEMU for graceful shutdown
|
|
||||||
|
|
||||||
QEMU_PORT=7100
|
|
||||||
QEMU_TIMEOUT=50
|
|
||||||
|
|
||||||
QEMU_PID=/run/qemu.pid
|
|
||||||
QEMU_COUNT=/run/qemu.count
|
|
||||||
|
|
||||||
rm -f "${QEMU_PID}"
|
|
||||||
rm -f "${QEMU_COUNT}"
|
|
||||||
|
|
||||||
_trap(){
|
|
||||||
func="$1" ; shift
|
|
||||||
for sig ; do
|
|
||||||
trap "$func $sig" "$sig"
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
_graceful_shutdown() {
|
|
||||||
|
|
||||||
set +e
|
|
||||||
|
|
||||||
[ ! -f "${QEMU_PID}" ] && exit 130
|
|
||||||
[ -f "${QEMU_COUNT}" ] && return
|
|
||||||
|
|
||||||
echo && info "Received $1 signal, shutting down..."
|
|
||||||
echo 0 > "${QEMU_COUNT}"
|
|
||||||
|
|
||||||
# Don't send the powerdown signal because vDSM ignores ACPI signals
|
|
||||||
# echo 'system_powerdown' | nc -q 1 -w 1 localhost "${QEMU_PORT}" > /dev/null
|
|
||||||
|
|
||||||
# Send shutdown command to guest agent via serial port
|
|
||||||
RESPONSE=$(curl -s -m 5 -S http://127.0.0.1:2210/read?command=6 2>&1)
|
|
||||||
|
|
||||||
if [[ ! "${RESPONSE}" =~ "\"success\"" ]] ; then
|
|
||||||
|
|
||||||
echo && error "Could not send shutdown command to the guest ($RESPONSE)"
|
|
||||||
|
|
||||||
kill -15 "$(cat "${QEMU_PID}")"
|
|
||||||
pkill -f qemu-system-x86_64 || true
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
while [ "$(cat ${QEMU_COUNT})" -lt "${QEMU_TIMEOUT}" ]; do
|
|
||||||
|
|
||||||
# Increase the counter
|
|
||||||
echo $(($(cat ${QEMU_COUNT})+1)) > ${QEMU_COUNT}
|
|
||||||
|
|
||||||
# Try to connect to qemu
|
|
||||||
if echo 'info version'| nc -q 1 -w 1 localhost "${QEMU_PORT}" >/dev/null 2>&1 ; then
|
|
||||||
|
|
||||||
sleep 1
|
|
||||||
|
|
||||||
CNT="$(cat ${QEMU_COUNT})/${QEMU_TIMEOUT}"
|
|
||||||
[[ "${DEBUG}" == [Yy1]* ]] && info "Shutting down, waiting... (${CNT})"
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
done
|
|
||||||
|
|
||||||
echo && echo "❯ Quitting..."
|
|
||||||
echo 'quit' | nc -q 1 -w 1 localhost "${QEMU_PORT}" >/dev/null 2>&1 || true
|
|
||||||
|
|
||||||
closeNetwork
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_trap _graceful_shutdown SIGTERM SIGHUP SIGINT SIGABRT SIGQUIT
|
|
||||||
|
|
||||||
MON_OPTS="-monitor telnet:localhost:${QEMU_PORT},server,nowait,nodelay"
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -Eeuo pipefail
|
|
||||||
|
|
||||||
info () { echo -e >&2 "\E[1;34m❯\E[1;36m $1\E[0m" ; }
|
|
||||||
error () { echo -e >&2 "\E[1;31m❯ ERROR: $1\E[0m" ; }
|
|
||||||
|
|
||||||
file="/run/dsm.url"
|
|
||||||
|
|
||||||
while [ ! -f "$file" ]
|
|
||||||
do
|
|
||||||
|
|
||||||
sleep 3
|
|
||||||
[ -f "$file" ] && continue
|
|
||||||
|
|
||||||
# Retrieve IP from guest VM
|
|
||||||
|
|
||||||
set +e
|
|
||||||
RESPONSE=$(curl -s -m 16 -S http://127.0.0.1:2210/read?command=10 2>&1)
|
|
||||||
set -e
|
|
||||||
|
|
||||||
if [[ ! "${RESPONSE}" =~ "\"success\"" ]] ; then
|
|
||||||
error "Failed to connect to guest: $RESPONSE" && continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Retrieve the HTTP port number
|
|
||||||
if [[ ! "${RESPONSE}" =~ "\"http_port\"" ]] ; then
|
|
||||||
error "Failed to parse response from guest: $RESPONSE" && continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
rest=${RESPONSE#*http_port}
|
|
||||||
rest=${rest#*:}
|
|
||||||
rest=${rest%%,*}
|
|
||||||
PORT=${rest%%\"*}
|
|
||||||
|
|
||||||
[ -z "${PORT}" ] && continue
|
|
||||||
|
|
||||||
# Retrieve the IP address
|
|
||||||
if [[ ! "${RESPONSE}" =~ "eth0" ]] ; then
|
|
||||||
error "Failed to parse response from guest: $RESPONSE" && continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
rest=${RESPONSE#*eth0}
|
|
||||||
rest=${rest#*ip}
|
|
||||||
rest=${rest#*:}
|
|
||||||
rest=${rest#*\"}
|
|
||||||
IP=${rest%%\"*}
|
|
||||||
|
|
||||||
[ -z "${IP}" ] && continue
|
|
||||||
|
|
||||||
echo "${IP}:${PORT}" > $file
|
|
||||||
|
|
||||||
done
|
|
||||||
|
|
||||||
LOCATION=$(cat "$file")
|
|
||||||
|
|
||||||
if [[ "$LOCATION" == "20.20"* ]]; then
|
|
||||||
MSG="port ${LOCATION##*:}"
|
|
||||||
else
|
|
||||||
MSG="http://${LOCATION}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "" >&2
|
|
||||||
info "--------------------------------------------------------"
|
|
||||||
info " You can now login to DSM at ${MSG}"
|
|
||||||
info "--------------------------------------------------------"
|
|
||||||
echo "" >&2
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -Eeuo pipefail
|
|
||||||
|
|
||||||
info () { echo -e "\E[1;34m❯ \E[1;36m$1\E[0m" ; }
|
|
||||||
error () { echo -e >&2 "\E[1;31m❯ ERROR: $1\E[0m" ; }
|
|
||||||
trap 'error "Status $? while: ${BASH_COMMAND} (line $LINENO/$BASH_LINENO)"' ERR
|
|
||||||
|
|
||||||
[ ! -f "/run/run.sh" ] && error "Script must run inside Docker container!" && exit 11
|
|
||||||
[ "$(id -u)" -ne "0" ] && error "Script must be executed with root privileges." && exit 12
|
|
||||||
|
|
||||||
# Docker environment variables
|
|
||||||
|
|
||||||
: ${URL:=''} # URL of the PAT file
|
|
||||||
: ${GPU:='N'} # Enable GPU passthrough
|
|
||||||
: ${DEBUG:='N'} # Enable debugging mode
|
|
||||||
: ${ALLOCATE:='Y'} # Preallocate diskspace
|
|
||||||
: ${ARGUMENTS:=''} # Extra QEMU parameters
|
|
||||||
: ${CPU_CORES:='1'} # Amount of CPU cores
|
|
||||||
: ${DISK_SIZE:='16G'} # Initial data disk size
|
|
||||||
: ${RAM_SIZE:='512M'} # Maximum RAM amount
|
|
||||||
|
|
||||||
# Helper variables
|
|
||||||
|
|
||||||
KERNEL=$(uname -r | cut -b 1)
|
|
||||||
MINOR=$(uname -r | cut -d '.' -f2)
|
|
||||||
ARCH=$(dpkg --print-architecture)
|
|
||||||
VERS=$(qemu-system-x86_64 --version | head -n 1 | cut -d '(' -f 1)
|
|
||||||
|
|
||||||
# Cleanup files
|
|
||||||
|
|
||||||
rm -f /run/dsm.url
|
|
||||||
rm -f /run/qemu.pid
|
|
||||||
rm -f /run/qemu.count
|
|
||||||
|
|
||||||
return 0
|
|
||||||
-26
@@ -1,26 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -Eeuo pipefail
|
|
||||||
|
|
||||||
echo "❯ Starting Virtual DSM for Docker v${VERSION}..."
|
|
||||||
echo "❯ For support visit https://github.com/vdsm/virtual-dsm/"
|
|
||||||
|
|
||||||
. /run/reset.sh # Initialize system
|
|
||||||
. /run/install.sh # Run installation
|
|
||||||
. /run/disk.sh # Initialize disks
|
|
||||||
. /run/network.sh # Initialize network
|
|
||||||
. /run/gpu.sh # Initialize graphics
|
|
||||||
. /run/serial.sh # Initialize serialport
|
|
||||||
. /run/power.sh # Configure shutdown
|
|
||||||
. /run/config.sh # Configure arguments
|
|
||||||
|
|
||||||
trap - ERR
|
|
||||||
|
|
||||||
set -m
|
|
||||||
(
|
|
||||||
[[ "${DEBUG}" == [Yy1]* ]] && info "$VERS" && set -x
|
|
||||||
qemu-system-x86_64 ${ARGS:+ $ARGS} & echo $! > "${QEMU_PID}"
|
|
||||||
{ set +x; } 2>/dev/null
|
|
||||||
)
|
|
||||||
set +m
|
|
||||||
|
|
||||||
tail --pid "$(cat "${QEMU_PID}")" --follow /dev/null & wait $!
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
set -Eeuo pipefail
|
|
||||||
|
|
||||||
# Docker environment variables
|
|
||||||
|
|
||||||
: ${HOST_CPU:=''}
|
|
||||||
: ${HOST_MAC:=''}
|
|
||||||
: ${HOST_DEBUG:=''}
|
|
||||||
: ${HOST_SERIAL:=''}
|
|
||||||
: ${HOST_MODEL:=''}
|
|
||||||
: ${GUEST_SERIAL:=''}
|
|
||||||
|
|
||||||
if [ -z "$HOST_CPU" ]; then
|
|
||||||
HOST_CPU=$(lscpu | grep 'Model name' | cut -f 2 -d ":" | awk '{$1=$1}1' | sed 's# @.*##g' | sed s/"(R)"//g | sed 's/[^[:alnum:] ]\+/ /g' | sed 's/ */ /g')
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "$HOST_CPU" ]; then
|
|
||||||
HOST_CPU="$HOST_CPU,,"
|
|
||||||
else
|
|
||||||
if [ "$ARCH" == "amd64" ]; then
|
|
||||||
HOST_CPU="QEMU, Virtual CPU, X86_64"
|
|
||||||
else
|
|
||||||
HOST_CPU="QEMU, Virtual CPU, $ARCH"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
HOST_ARGS=()
|
|
||||||
HOST_ARGS+=("-cpu=${CPU_CORES}")
|
|
||||||
HOST_ARGS+=("-cpu_arch=${HOST_CPU}")
|
|
||||||
|
|
||||||
[ -n "$HOST_MAC" ] && HOST_ARGS+=("-mac=${HOST_MAC}")
|
|
||||||
[ -n "$HOST_MODEL" ] && HOST_ARGS+=("-model=${HOST_MODEL}")
|
|
||||||
[ -n "$HOST_SERIAL" ] && HOST_ARGS+=("-hostsn=${HOST_SERIAL}")
|
|
||||||
[ -n "$GUEST_SERIAL" ] && HOST_ARGS+=("-guestsn=${GUEST_SERIAL}")
|
|
||||||
|
|
||||||
if [[ "${HOST_DEBUG}" == [Yy1]* ]]; then
|
|
||||||
set -x
|
|
||||||
./run/host.bin "${HOST_ARGS[@]}" &
|
|
||||||
{ set +x; } 2>/dev/null
|
|
||||||
echo
|
|
||||||
else
|
|
||||||
./run/host.bin "${HOST_ARGS[@]}" >/dev/null &
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Configure serial ports
|
|
||||||
|
|
||||||
SERIAL_OPTS="\
|
|
||||||
-serial mon:stdio \
|
|
||||||
-device virtio-serial-pci,id=virtio-serial0,bus=pcie.0,addr=0x3 \
|
|
||||||
-chardev pty,id=charserial0 \
|
|
||||||
-device isa-serial,chardev=charserial0,id=serial0 \
|
|
||||||
-chardev socket,id=charchannel0,host=127.0.0.1,port=12345,reconnect=10 \
|
|
||||||
-device virtserialport,bus=virtio-serial0.0,nr=1,chardev=charchannel0,id=channel0,name=vchannel"
|
|
||||||
|
|
||||||
return 0
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
TMP_FILE=$(mktemp -q /tmp/server.XXXXXX)
|
|
||||||
|
|
||||||
stop() {
|
|
||||||
trap - SIGINT EXIT
|
|
||||||
{ pkill -f socat || true; } 2>/dev/null
|
|
||||||
[ -f "$TMP_FILE" ] && rm -f "$TMP_FILE"
|
|
||||||
}
|
|
||||||
|
|
||||||
trap 'stop' EXIT SIGINT SIGTERM SIGHUP
|
|
||||||
|
|
||||||
html()
|
|
||||||
{
|
|
||||||
local h="<!DOCTYPE html><HTML><HEAD><TITLE>VirtualDSM</TITLE>"
|
|
||||||
h="${h} <STYLE>body { color: white; background-color: #125bdb; font-family: Verdana,"
|
|
||||||
h="${h} Arial,sans-serif; } a, a:hover, a:active, a:visited { color: white; }</STYLE></HEAD>"
|
|
||||||
h="${h}<BODY><BR><BR><H1><CENTER>$1</CENTER></H1></BODY></HTML>"
|
|
||||||
|
|
||||||
echo "$h"
|
|
||||||
}
|
|
||||||
|
|
||||||
if [[ "$2" != "/"* ]]; then
|
|
||||||
|
|
||||||
BODY="$2"
|
|
||||||
|
|
||||||
if [[ "$BODY" == "install" ]]; then
|
|
||||||
BODY="Please wait while Virtual DSM is being installed..."
|
|
||||||
BODY="$BODY<script>setTimeout(() => { document.location.reload(); }, 9999);</script>"
|
|
||||||
fi
|
|
||||||
|
|
||||||
HTML=$(html "$BODY")
|
|
||||||
printf '%b' "HTTP/1.1 200 OK\nContent-Length: ${#HTML}\nConnection: close\n\n$HTML" > "$TMP_FILE"
|
|
||||||
|
|
||||||
socat TCP4-LISTEN:80,reuseaddr,fork,crlf SYSTEM:"cat ${TMP_FILE}" 2> /dev/null &
|
|
||||||
socat TCP4-LISTEN:"${1:-5000}",reuseaddr,fork,crlf SYSTEM:"cat ${TMP_FILE}" 2> /dev/null & wait $!
|
|
||||||
|
|
||||||
exit
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "$2" != "/run/ip.sh" ]]; then
|
|
||||||
|
|
||||||
cp "$2" "$TMP_FILE"
|
|
||||||
|
|
||||||
else
|
|
||||||
|
|
||||||
BODY="The location of DSM is <a href='http://\${LOCATION}'>http://\${LOCATION}</a><script>"
|
|
||||||
BODY="${BODY}setTimeout(function(){ window.location.assign('http://\${LOCATION}'); }, 3000);</script>"
|
|
||||||
WAIT="Please wait while discovering IP...<script>setTimeout(() => { document.location.reload(); }, 4999);</script>"
|
|
||||||
|
|
||||||
HTML=$(html "xxx")
|
|
||||||
|
|
||||||
{ echo "#!/bin/bash"
|
|
||||||
echo "[ -f \"/run/dsm.url\" ] && LOCATION=\$(cat \"/run/dsm.url\")"
|
|
||||||
echo "HTML=\"$HTML\"; [ -z \"\${LOCATION}\" ] && BODY=\"$WAIT\" || BODY=\"$BODY\"; HTML=\${HTML/xxx/\$BODY}"
|
|
||||||
echo "printf '%b' \"HTTP/1.1 200 OK\\nContent-Length: \${#HTML}\\nConnection: close\\n\\n\$HTML\""
|
|
||||||
} > "$TMP_FILE"
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
chmod +x "$TMP_FILE"
|
|
||||||
|
|
||||||
socat TCP4-LISTEN:80,reuseaddr,fork,crlf SYSTEM:"$TMP_FILE" 2> /dev/null &
|
|
||||||
socat TCP4-LISTEN:"${1:-5000}",reuseaddr,fork,crlf SYSTEM:"$TMP_FILE" 2> /dev/null & wait $!
|
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
cd /run
|
||||||
|
. utils.sh # Load functions
|
||||||
|
|
||||||
|
: "${DHCP:="N"}"
|
||||||
|
: "${NETWORK:="Y"}"
|
||||||
|
|
||||||
|
[ -f "/run/shm/qemu.end" ] && echo "QEMU is shutting down..." && exit 1
|
||||||
|
[ ! -s "/run/shm/qemu.pid" ] && echo "QEMU is not running yet..." && exit 0
|
||||||
|
disabled "$NETWORK" && echo "Networking is disabled." && exit 0
|
||||||
|
|
||||||
|
file="/run/shm/dsm.url"
|
||||||
|
address="/run/shm/qemu.ip"
|
||||||
|
gateway="/run/shm/qemu.gw"
|
||||||
|
|
||||||
|
[ ! -s "$file" ] && echo "DSM has not enabled networking yet..." && exit 0
|
||||||
|
|
||||||
|
location=$(<"$file")
|
||||||
|
|
||||||
|
if ! curl -m 20 -ILfSs "http://$location/" > /dev/null; then
|
||||||
|
|
||||||
|
if enabled "$DHCP"; then
|
||||||
|
ip=$(<"$address")
|
||||||
|
echo "Failed to reach DSM at http://$location"
|
||||||
|
else
|
||||||
|
ip=$(<"$gateway")
|
||||||
|
port="${location##*:}"
|
||||||
|
echo "Failed to reach DSM at port $port"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "You might need to whitelist IP $ip in the DSM firewall." && exit 1
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Healthcheck OK"
|
||||||
|
exit 0
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
DEF_OPTS="-nodefaults -boot strict=on"
|
||||||
|
DEV_OPTS=""
|
||||||
|
|
||||||
|
configureProcessor() {
|
||||||
|
|
||||||
|
CPU_OPTS="-cpu $CPU_FLAGS"
|
||||||
|
CPU_OPTS+=" -smp $CPU_CORES,sockets=1,dies=1,cores=$CPU_CORES,threads=1"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
configureMemory() {
|
||||||
|
|
||||||
|
RAM_OPTS=$(echo "-m ${RAM_SIZE^^}" | sed 's/MB/M/g;s/GB/G/g;s/TB/T/g')
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
configureMonitor() {
|
||||||
|
|
||||||
|
MON_OPTS="-name $PROCESS,process=$PROCESS,debug-threads=on"
|
||||||
|
MON_OPTS+=" -pidfile $QEMU_PID"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
configureMachine() {
|
||||||
|
|
||||||
|
MAC_OPTS="-machine type=$MACHINE,smm=off,usb=off"
|
||||||
|
MAC_OPTS+=",vmport=off,dump-guest-core=off,hpet=off${KVM_OPTS}"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
configureVirtioDevices() {
|
||||||
|
|
||||||
|
DEV_OPTS="-device virtio-balloon-pci,id=balloon0,bus=pcie.0,addr=0x4"
|
||||||
|
DEV_OPTS+=" -object rng-random,id=objrng0,filename=/dev/urandom"
|
||||||
|
DEV_OPTS+=" -device virtio-rng-pci,rng=objrng0,id=rng0,bus=pcie.0,addr=0x1c"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
buildArguments() {
|
||||||
|
|
||||||
|
ARGS="$DEF_OPTS $CPU_OPTS $RAM_OPTS $MAC_OPTS $DISPLAY_OPTS $MON_OPTS $SERIAL_OPTS $NET_OPTS $DISK_OPTS $DEV_OPTS $ARGUMENTS"
|
||||||
|
ARGS=$(echo "$ARGS" | sed 's/\t/ /g' | tr -s ' ')
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
configureMemory
|
||||||
|
configureMonitor
|
||||||
|
configureMachine
|
||||||
|
configureProcessor
|
||||||
|
configureVirtioDevices
|
||||||
|
|
||||||
|
buildArguments
|
||||||
|
|
||||||
|
return 0
|
||||||
+837
@@ -0,0 +1,837 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
# Docker environment variables
|
||||||
|
|
||||||
|
: "${DISK_IO:="native"}" # I/O Mode, can be set to 'native', 'threads' or 'io_uring'
|
||||||
|
: "${DISK_FMT:="raw"}" # Disk file format, 'raw' by default for best performance
|
||||||
|
: "${DISK_TYPE:=""}" # Device type to be used, "sata", "nvme", "blk" or "scsi"
|
||||||
|
: "${DISK_FLAGS:=""}" # Specifies the options for use with the qcow2 disk format
|
||||||
|
: "${DISK_CACHE:="none"}" # Caching mode, can be set to 'writeback' for better performance
|
||||||
|
: "${DISK_DISCARD:="unmap"}" # Controls whether unmap (TRIM) commands are passed to the host.
|
||||||
|
: "${DISK_ROTATION:="1"}" # Rotation rate, set to 1 for SSD storage and increase for HDD
|
||||||
|
|
||||||
|
# Sanitize all variables
|
||||||
|
DISK_IO=$(strip "$DISK_IO")
|
||||||
|
DISK_FMT=$(strip "$DISK_FMT")
|
||||||
|
DISK_TYPE=$(strip "$DISK_TYPE")
|
||||||
|
DISK_FLAGS=$(strip "$DISK_FLAGS")
|
||||||
|
DISK_CACHE=$(strip "$DISK_CACHE")
|
||||||
|
DISK_DISCARD=$(strip "$DISK_DISCARD")
|
||||||
|
DISK_ROTATION=$(strip "$DISK_ROTATION")
|
||||||
|
|
||||||
|
BOOT="$STORAGE/$BASE.boot.img"
|
||||||
|
SYSTEM="$STORAGE/$BASE.system.img"
|
||||||
|
|
||||||
|
[ ! -s "$BOOT" ] && error "Virtual DSM boot-image does not exist ($BOOT)" && exit 81
|
||||||
|
[ ! -s "$SYSTEM" ] && error "Virtual DSM system-image does not exist ($SYSTEM)" && exit 82
|
||||||
|
|
||||||
|
if ! setOwner "$BOOT"; then
|
||||||
|
warn "failed to set the owner for \"$BOOT\" !"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! setOwner "$SYSTEM"; then
|
||||||
|
warn "failed to set the owner for \"$SYSTEM\" !"
|
||||||
|
fi
|
||||||
|
|
||||||
|
fmt2ext() {
|
||||||
|
local diskFmt="$1"
|
||||||
|
|
||||||
|
case "${diskFmt,,}" in
|
||||||
|
qcow2) echo "qcow2" ;;
|
||||||
|
raw) echo "img" ;;
|
||||||
|
*) error "Unrecognized disk format: $diskFmt" && exit 78 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
ext2fmt() {
|
||||||
|
local diskExt="$1"
|
||||||
|
|
||||||
|
case "${diskExt,,}" in
|
||||||
|
qcow2) echo "qcow2" ;;
|
||||||
|
img) echo "raw" ;;
|
||||||
|
*) error "Unrecognized file extension: .$diskExt" && exit 78 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
getSize() {
|
||||||
|
|
||||||
|
local diskFile="$1"
|
||||||
|
local diskExt diskFmt size
|
||||||
|
|
||||||
|
diskExt=$(echo "${diskFile//*./}" | sed 's/^.*\.//')
|
||||||
|
diskFmt=$(ext2fmt "$diskExt")
|
||||||
|
|
||||||
|
case "${diskFmt,,}" in
|
||||||
|
raw)
|
||||||
|
stat -c%s "$diskFile"
|
||||||
|
;;
|
||||||
|
|
||||||
|
qcow2)
|
||||||
|
size=$(qemu-img info --output=json -f "$diskFmt" "$diskFile" | jq -r '."virtual-size" // empty')
|
||||||
|
if [[ ! "$size" =~ ^[0-9]+$ ]]; then
|
||||||
|
error "Failed to determine virtual size of $diskFile"
|
||||||
|
exit 78
|
||||||
|
fi
|
||||||
|
echo "$size"
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
error "Unrecognized disk format: $diskFmt"
|
||||||
|
exit 78
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
isCow() {
|
||||||
|
local fs="$1"
|
||||||
|
|
||||||
|
if [[ "${fs,,}" == "btrfs" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
supportsDirect() {
|
||||||
|
local fs="$1"
|
||||||
|
|
||||||
|
if [[ "${fs,,}" == "ecryptfs" || "${fs,,}" == "tmpfs" ]]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
validDiskType() {
|
||||||
|
|
||||||
|
case "${1,,}" in
|
||||||
|
"ide" | "sata" | "nvme" | "usb" | "scsi" | "blk" | \
|
||||||
|
"virtio-blk" | "virtio-scsi" | "auto" | "none" )
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
allocateRaw() {
|
||||||
|
|
||||||
|
local diskFile="$1"
|
||||||
|
local dataSize="$2"
|
||||||
|
|
||||||
|
if disabled "$ALLOCATE"; then
|
||||||
|
truncate -s "$dataSize" "$diskFile"
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
fallocate -l "$dataSize" "$diskFile" &>/dev/null && return 0
|
||||||
|
fallocate -l -x "$dataSize" "$diskFile" && return 0
|
||||||
|
truncate -s "$dataSize" "$diskFile" || return 1
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
getDiskOptions() {
|
||||||
|
|
||||||
|
local fs="$1"
|
||||||
|
local diskFmt="$2"
|
||||||
|
local diskParam="$DISK_ALLOC"
|
||||||
|
|
||||||
|
isCow "$fs" && diskParam+=",nocow=on"
|
||||||
|
|
||||||
|
if [[ "${diskFmt,,}" != "raw" ]]; then
|
||||||
|
[ -n "$DISK_FLAGS" ] && diskParam+=",$DISK_FLAGS"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$diskParam"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
normalizeSize() {
|
||||||
|
|
||||||
|
local diskSpace="$1"
|
||||||
|
local diskDesc="$2"
|
||||||
|
local dir="$3"
|
||||||
|
|
||||||
|
local free dataSize
|
||||||
|
local spare=1073741824
|
||||||
|
|
||||||
|
if [[ "${diskSpace,,}" == "max" || "${diskSpace,,}" == "half" ]]; then
|
||||||
|
|
||||||
|
free=$(df --output=avail -B 1 "$dir" | tail -n 1)
|
||||||
|
|
||||||
|
if [[ "${diskSpace,,}" == "max" ]]; then
|
||||||
|
free=$(( free - spare ))
|
||||||
|
else
|
||||||
|
free=$(( free / 2 ))
|
||||||
|
fi
|
||||||
|
|
||||||
|
(( free < spare )) && free="$spare"
|
||||||
|
local gb=$(( free / 1073741825 ))
|
||||||
|
diskSpace="${gb}G"
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
local space="${diskSpace// /}"
|
||||||
|
[ -z "$space" ] && space="256G"
|
||||||
|
[ -z "${space//[0-9. ]}" ] && space="${space}G"
|
||||||
|
space=$(echo "${space^^}" | sed 's/MB/M/g;s/GB/G/g;s/TB/T/g')
|
||||||
|
|
||||||
|
if ! numfmt --from=iec "$space" &>/dev/null; then
|
||||||
|
error "Invalid value for ${diskDesc^^}_SIZE: $diskSpace" && exit 73
|
||||||
|
fi
|
||||||
|
|
||||||
|
dataSize=$(numfmt --from=iec "$space")
|
||||||
|
|
||||||
|
if (( dataSize < 6442450944 )); then
|
||||||
|
error "Please increase the ${diskDesc^^}_SIZE variable to at least 6 GB." && exit 73
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$space"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
baseDir() {
|
||||||
|
|
||||||
|
local path="${1%/}"
|
||||||
|
|
||||||
|
[[ -z "$path" || "$path" == "/" ]] && {
|
||||||
|
echo "/"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
path="${path#/}"
|
||||||
|
path="${path%%/*}"
|
||||||
|
|
||||||
|
echo "/$path"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
freeSpace() {
|
||||||
|
|
||||||
|
local path="$1"
|
||||||
|
|
||||||
|
local base
|
||||||
|
base=$(baseDir "$path")
|
||||||
|
|
||||||
|
if ! available=$(df --output=avail -B 1 "$path" | tail -n 1); then
|
||||||
|
error "Failed to check free space in $base."
|
||||||
|
exit 76
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! "$available" =~ ^[0-9]+$ ]]; then
|
||||||
|
error "Failed to check free space in $base."
|
||||||
|
exit 76
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
createDisk() {
|
||||||
|
|
||||||
|
local diskFile="$1"
|
||||||
|
local diskSpace="$2"
|
||||||
|
local diskDesc="$3"
|
||||||
|
local diskFmt="$4"
|
||||||
|
local fs="$5"
|
||||||
|
|
||||||
|
local gb dir base
|
||||||
|
local attributes available
|
||||||
|
|
||||||
|
rm -f "$diskFile"
|
||||||
|
|
||||||
|
local dataSize
|
||||||
|
dataSize=$(numfmt --from=iec "$diskSpace")
|
||||||
|
|
||||||
|
if ! disabled "$ALLOCATE"; then
|
||||||
|
|
||||||
|
# Check free diskspace
|
||||||
|
dir=$(dirname "$diskFile")
|
||||||
|
base=$(baseDir "$dir")
|
||||||
|
|
||||||
|
freeSpace "$dir"
|
||||||
|
|
||||||
|
if (( dataSize > available )); then
|
||||||
|
gb=$(formatBytes "$available")
|
||||||
|
error "Not enough free space to create a $diskDesc of ${diskSpace/G/ GB} in $base, it has only $gb available..."
|
||||||
|
error "Please specify a smaller ${diskDesc^^}_SIZE or disable preallocation by setting ALLOCATE=N." && exit 76
|
||||||
|
fi
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
html "Creating a $diskDesc image..."
|
||||||
|
info "Creating a ${diskSpace/G/ GB} $DISK_STYLE $diskDesc image in $diskFmt format..."
|
||||||
|
|
||||||
|
local failure="Could not create a $DISK_STYLE $diskFmt $diskDesc image of ${diskSpace/G/ GB} ($diskFile)"
|
||||||
|
|
||||||
|
case "${diskFmt,,}" in
|
||||||
|
raw)
|
||||||
|
|
||||||
|
if isCow "$fs"; then
|
||||||
|
if ! touch "$diskFile"; then
|
||||||
|
error "$failure" && exit 77
|
||||||
|
fi
|
||||||
|
{ chattr +C "$diskFile"; } || :
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! allocateRaw "$diskFile" "$dataSize"; then
|
||||||
|
rm -f "$diskFile"
|
||||||
|
error "$failure" && exit 77
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
qcow2)
|
||||||
|
|
||||||
|
local diskParam
|
||||||
|
diskParam=$(getDiskOptions "$fs" "$diskFmt")
|
||||||
|
|
||||||
|
if ! qemu-img create -f "$diskFmt" -o "$diskParam" -- "$diskFile" "$dataSize" ; then
|
||||||
|
rm -f "$diskFile"
|
||||||
|
error "$failure" && exit 70
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if isCow "$fs"; then
|
||||||
|
attributes=$(lsattr "$diskFile")
|
||||||
|
if [[ "$attributes" != *"C"* ]]; then
|
||||||
|
error "Failed to disable COW for $diskDesc image $diskFile on ${fs^^} filesystem (returned $attributes)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
resizeDisk() {
|
||||||
|
|
||||||
|
local diskFile="$1"
|
||||||
|
local diskSpace="$2"
|
||||||
|
local diskDesc="$3"
|
||||||
|
local diskFmt="$4"
|
||||||
|
local fs="$5"
|
||||||
|
|
||||||
|
local gb dir base dataSize
|
||||||
|
local available currentSize
|
||||||
|
|
||||||
|
currentSize=$(getSize "$diskFile") || exit 71
|
||||||
|
dataSize=$(numfmt --from=iec "$diskSpace")
|
||||||
|
local required=$(( dataSize - currentSize ))
|
||||||
|
(( required < 1 )) && error "Shrinking disks is not supported yet, please increase ${diskDesc^^}_SIZE." && exit 71
|
||||||
|
|
||||||
|
if ! disabled "$ALLOCATE"; then
|
||||||
|
|
||||||
|
# Check free diskspace
|
||||||
|
dir=$(dirname "$diskFile")
|
||||||
|
base=$(baseDir "$dir")
|
||||||
|
|
||||||
|
freeSpace "$dir"
|
||||||
|
|
||||||
|
if (( required > available )); then
|
||||||
|
gb=$(formatBytes "$available")
|
||||||
|
error "Not enough free space to resize $diskDesc to ${diskSpace/G/ GB} in $base, it has only $gb available.."
|
||||||
|
error "Please specify a smaller ${diskDesc^^}_SIZE or disable preallocation by setting ALLOCATE=N." && exit 74
|
||||||
|
fi
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
gb=$(formatBytes "$currentSize")
|
||||||
|
MSG="Resizing $diskDesc from $gb to ${diskSpace/G/ GB}..."
|
||||||
|
info "$MSG" && html "$MSG"
|
||||||
|
|
||||||
|
local failure="Could not resize the $DISK_STYLE $diskFmt $diskDesc image from ${gb} to ${diskSpace/G/ GB} ($diskFile)"
|
||||||
|
|
||||||
|
case "${diskFmt,,}" in
|
||||||
|
raw)
|
||||||
|
|
||||||
|
if ! allocateRaw "$diskFile" "$dataSize"; then
|
||||||
|
error "$failure" && exit 75
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
qcow2)
|
||||||
|
|
||||||
|
if ! qemu-img resize -f "$diskFmt" "--$DISK_ALLOC" "$diskFile" "$dataSize" ; then
|
||||||
|
error "$failure" && exit 72
|
||||||
|
fi
|
||||||
|
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
convertDisk() {
|
||||||
|
|
||||||
|
local sourceFile="$1"
|
||||||
|
local sourceFmt="$2"
|
||||||
|
local destinationFile="$3"
|
||||||
|
local destinationFmt="$4"
|
||||||
|
local diskBase="$5"
|
||||||
|
local diskDesc="$6"
|
||||||
|
local fs="$7"
|
||||||
|
local tmpFile="$diskBase.tmp"
|
||||||
|
|
||||||
|
local gb dir base attributes
|
||||||
|
local available currentSize
|
||||||
|
|
||||||
|
[ -f "$destinationFile" ] && error "Conversion failed, destination file $destinationFile already exists?" && exit 79
|
||||||
|
[ ! -f "$sourceFile" ] && error "Conversion failed, source file $sourceFile does not exist?" && exit 79
|
||||||
|
|
||||||
|
rm -f "$tmpFile"
|
||||||
|
|
||||||
|
dir=$(dirname "$tmpFile")
|
||||||
|
base=$(baseDir "$dir")
|
||||||
|
|
||||||
|
if ! disabled "$ALLOCATE"; then
|
||||||
|
|
||||||
|
# Check free diskspace
|
||||||
|
currentSize=$(getSize "$sourceFile") || exit 79
|
||||||
|
|
||||||
|
freeSpace "$dir"
|
||||||
|
|
||||||
|
if (( currentSize > available )); then
|
||||||
|
gb=$(formatBytes "$available")
|
||||||
|
error "Not enough free space to convert $diskDesc to $destinationFmt in $base, it has only $gb available..."
|
||||||
|
error "Please free up some disk space or disable preallocation by setting ALLOCATE=N." && exit 76
|
||||||
|
fi
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
local msg="Converting $diskDesc to $destinationFmt"
|
||||||
|
html "$msg..."
|
||||||
|
info "$msg, please wait until completed..."
|
||||||
|
|
||||||
|
local convertFlags="-p"
|
||||||
|
local diskParam
|
||||||
|
diskParam=$(getDiskOptions "$fs" "$destinationFmt")
|
||||||
|
|
||||||
|
if [[ "$destinationFmt" != "raw" ]]; then
|
||||||
|
if disabled "$ALLOCATE"; then
|
||||||
|
convertFlags+=" -c"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
if ! qemu-img convert -f "$sourceFmt" $convertFlags -o "$diskParam" -O "$destinationFmt" -- "$sourceFile" "$tmpFile"; then
|
||||||
|
rm -f "$tmpFile"
|
||||||
|
error "Failed to convert $DISK_STYLE $diskDesc image to $destinationFmt format in $base, is there enough space available?" && exit 79
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$destinationFmt" == "raw" ]]; then
|
||||||
|
if ! disabled "$ALLOCATE"; then
|
||||||
|
|
||||||
|
# Work around qemu-img bug
|
||||||
|
if ! currentSize=$(stat -c%s "$tmpFile"); then
|
||||||
|
error "Failed to determine converted image size: $tmpFile"
|
||||||
|
exit 79
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! fallocate -l "$currentSize" "$tmpFile" &>/dev/null; then
|
||||||
|
if ! fallocate -l -x "$currentSize" "$tmpFile"; then
|
||||||
|
error "Failed to allocate $currentSize bytes for $diskDesc image $tmpFile"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! mv "$tmpFile" "$destinationFile"; then
|
||||||
|
error "Failed to move converted $diskDesc image to $destinationFile."
|
||||||
|
exit 79
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! rm -f "$sourceFile"; then
|
||||||
|
error "Failed to remove old $diskDesc image $sourceFile."
|
||||||
|
exit 79
|
||||||
|
fi
|
||||||
|
|
||||||
|
if isCow "$fs"; then
|
||||||
|
attributes=$(lsattr "$destinationFile")
|
||||||
|
if [[ "$attributes" != *"C"* ]]; then
|
||||||
|
error "Failed to disable COW for $diskDesc image $destinationFile on ${fs^^} filesystem (returned $attributes)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
msg="Conversion of $diskDesc"
|
||||||
|
info "$msg to $destinationFmt completed successfully!"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
checkFS () {
|
||||||
|
|
||||||
|
local fs="$1"
|
||||||
|
local diskFile="$2"
|
||||||
|
local diskDesc="$3"
|
||||||
|
|
||||||
|
local dir base
|
||||||
|
local attributes
|
||||||
|
|
||||||
|
dir=$(dirname "$diskFile")
|
||||||
|
base=$(baseDir "$dir")
|
||||||
|
[ ! -d "$dir" ] && return 0
|
||||||
|
|
||||||
|
if [[ "${fs,,}" == "overlay"* && "${ENGINE,,}" == "docker" ]]; then
|
||||||
|
warn "the filesystem of $base is OverlayFS, this usually means it was binded to an invalid path!"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${fs,,}" == "fuse"* ]]; then
|
||||||
|
warn "the filesystem of $base is FUSE, this extra layer will negatively affect performance!"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! supportsDirect "$fs"; then
|
||||||
|
warn "the filesystem of $base is $fs, which does not support O_DIRECT mode, adjusting settings..."
|
||||||
|
fi
|
||||||
|
|
||||||
|
if isCow "$fs"; then
|
||||||
|
if [ -f "$diskFile" ]; then
|
||||||
|
attributes=$(lsattr "$diskFile")
|
||||||
|
if [[ "$attributes" != *"C"* ]]; then
|
||||||
|
warn "COW (copy on write) is not disabled for $diskDesc image file $diskFile, this is recommended on ${fs^^} filesystems!"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
createDevice () {
|
||||||
|
|
||||||
|
local diskFile="$1"
|
||||||
|
local diskType="$2"
|
||||||
|
local diskIndex="$3"
|
||||||
|
local diskAddress="$4"
|
||||||
|
local diskFmt="$5"
|
||||||
|
local diskIo="$6"
|
||||||
|
local diskCache="$7"
|
||||||
|
local diskSerial="$8"
|
||||||
|
local diskSectors="$9"
|
||||||
|
local bus="${PCI_BUS:-pcie.0}"
|
||||||
|
|
||||||
|
[[ -z "${PCI_BUS:-}" && ( "${MACHINE,,}" == pc || "${MACHINE,,}" == pc-i440fx* ) ]] && bus="pci.0"
|
||||||
|
|
||||||
|
local bootIndex=""
|
||||||
|
local diskId="data$diskIndex"
|
||||||
|
[ -n "$diskIndex" ] && bootIndex=",bootindex=$diskIndex"
|
||||||
|
local result=" -drive file=$diskFile,id=$diskId,format=$diskFmt,cache=$diskCache,aio=$diskIo,discard=$DISK_DISCARD,detect-zeroes=on"
|
||||||
|
|
||||||
|
case "${diskType,,}" in
|
||||||
|
"none" ) ;;
|
||||||
|
"auto" )
|
||||||
|
echo "$result"
|
||||||
|
;;
|
||||||
|
"usb" )
|
||||||
|
result+=",if=none \
|
||||||
|
-device usb-storage,drive=${diskId}${bootIndex}${diskSerial}${diskSectors}"
|
||||||
|
echo "$result"
|
||||||
|
;;
|
||||||
|
"nvme" )
|
||||||
|
result+=",if=none \
|
||||||
|
-device nvme,drive=${diskId}${bootIndex},serial=deadbeaf${diskIndex}${diskSerial}${diskSectors}"
|
||||||
|
echo "$result"
|
||||||
|
;;
|
||||||
|
"ide" | "sata" )
|
||||||
|
result+=",if=none \
|
||||||
|
-device ich9-ahci,id=ahci${diskIndex},addr=$diskAddress \
|
||||||
|
-device ide-hd,drive=${diskId},bus=ahci$diskIndex.0,rotation_rate=$DISK_ROTATION${bootIndex}${diskSerial}${diskSectors}"
|
||||||
|
echo "$result"
|
||||||
|
;;
|
||||||
|
"blk" | "virtio-blk" )
|
||||||
|
result+=",if=none \
|
||||||
|
-device virtio-blk-pci,drive=${diskId},bus=$bus,addr=$diskAddress,iothread=io2${bootIndex}${diskSerial}${diskSectors}"
|
||||||
|
echo "$result"
|
||||||
|
;;
|
||||||
|
"scsi" | "virtio-scsi" )
|
||||||
|
result+=",if=none \
|
||||||
|
-device virtio-scsi-pci,id=${diskId}b,bus=$bus,addr=$diskAddress,iothread=io2,hotplug=off \
|
||||||
|
-device scsi-hd,drive=${diskId},bus=${diskId}b.0,channel=0,scsi-id=0,lun=0,rotation_rate=$DISK_ROTATION${bootIndex}${diskSerial}${diskSectors}"
|
||||||
|
echo "$result"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
finishDisks () {
|
||||||
|
|
||||||
|
case "${DISK_TYPE,,}" in
|
||||||
|
"blk" | "scsi" | "virtio-blk" | "virtio-scsi" )
|
||||||
|
[[ "$DISK_OPTS" != *" -object iothread,id=io2"* ]] && DISK_OPTS+=" -object iothread,id=io2" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
addDisk () {
|
||||||
|
|
||||||
|
local diskBase="$1"
|
||||||
|
local diskType="$2"
|
||||||
|
local diskDesc="$3"
|
||||||
|
local diskSpace="$4"
|
||||||
|
local diskIndex="$5"
|
||||||
|
local diskAddress="$6"
|
||||||
|
local diskFmt="$7"
|
||||||
|
local diskIo="$8"
|
||||||
|
local diskCache="$9"
|
||||||
|
|
||||||
|
local fs dir used space
|
||||||
|
local diskExt dataSize
|
||||||
|
local available currentSize
|
||||||
|
local previousExt
|
||||||
|
|
||||||
|
diskExt=$(fmt2ext "$diskFmt")
|
||||||
|
local diskFile="$diskBase.$diskExt"
|
||||||
|
|
||||||
|
dir=$(dirname "$diskFile")
|
||||||
|
[ ! -d "$dir" ] && return 0
|
||||||
|
|
||||||
|
space=$(normalizeSize "$diskSpace" "$diskDesc" "$dir")
|
||||||
|
dataSize=$(numfmt --from=iec "$space")
|
||||||
|
|
||||||
|
if ! fs=$(stat -f -c %T "$dir"); then
|
||||||
|
error "Failed to determine filesystem type of \"$dir\" !"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
checkFS "$fs" "$diskFile" "$diskDesc" || exit $?
|
||||||
|
|
||||||
|
if ! supportsDirect "$fs"; then
|
||||||
|
diskIo="threads"
|
||||||
|
diskCache="writeback"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f "$diskFile" ] || [ ! -s "$diskFile" ]; then
|
||||||
|
|
||||||
|
if [[ "${diskFmt,,}" != "raw" ]]; then
|
||||||
|
local previousFmt="raw"
|
||||||
|
else
|
||||||
|
local previousFmt="qcow2"
|
||||||
|
fi
|
||||||
|
|
||||||
|
previousExt=$(fmt2ext "$previousFmt")
|
||||||
|
|
||||||
|
if [ -f "$diskBase.$previousExt" ] &&
|
||||||
|
[ -s "$diskBase.$previousExt" ]; then
|
||||||
|
convertDisk "$diskBase.$previousExt" "$previousFmt" "$diskFile" "$diskFmt" "$diskBase" "$diskDesc" "$fs" || exit $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f "$diskFile" ] && [ -s "$diskFile" ]; then
|
||||||
|
|
||||||
|
currentSize=$(getSize "$diskFile") || exit 71
|
||||||
|
|
||||||
|
if (( dataSize > currentSize )); then
|
||||||
|
|
||||||
|
resizeDisk "$diskFile" "$space" "$diskDesc" "$diskFmt" "$fs" || exit $?
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
|
if (( dataSize < currentSize )); then
|
||||||
|
|
||||||
|
if [[ "${diskSpace,,}" != "max" && "${diskSpace,,}" != "half" ]]; then
|
||||||
|
info "You decreased the ${diskDesc^^}_SIZE variable to ${diskSpace/G/ GB} but shrinking disks is not supported, will be ignored..."
|
||||||
|
fi
|
||||||
|
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
|
createDisk "$diskFile" "$space" "$diskDesc" "$diskFmt" "$fs" || exit $?
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f "$diskFile" ] && disabled "$ALLOCATE"; then
|
||||||
|
|
||||||
|
currentSize=$(getSize "$diskFile") || exit 73
|
||||||
|
used=$(du -sB 1 "$diskFile" | cut -f1)
|
||||||
|
available=$(df --output=avail -B 1 "$dir" | tail -n 1)
|
||||||
|
local missing=$(( currentSize - used - available ))
|
||||||
|
(( missing < 0 )) && missing=0
|
||||||
|
|
||||||
|
if (( missing > 0 )); then
|
||||||
|
|
||||||
|
local gb base
|
||||||
|
|
||||||
|
gb=$(formatBytes "$available")
|
||||||
|
base=$(baseDir "$dir")
|
||||||
|
missing=$(formatBytes "$missing")
|
||||||
|
currentSize=$(formatBytes "$currentSize")
|
||||||
|
local msg="The virtual size of the ${diskDesc,,} is $currentSize"
|
||||||
|
|
||||||
|
if [ -n "$used" ] && [[ "$used" != "0" ]]; then
|
||||||
|
used=$(formatBytes "$used")
|
||||||
|
msg+=" (of which $used is used)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
info "$msg, but there is only $gb of free space remaining in $base now."
|
||||||
|
info "Please consider making at least $missing more space available in $base for future expansions."
|
||||||
|
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f "$diskFile" ]; then
|
||||||
|
if ! setOwner "$diskFile"; then
|
||||||
|
warn "failed to set the owner for \"$diskFile\" !"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
DISK_OPTS+=$(createDevice "$diskFile" "$diskType" "$diskIndex" "$diskAddress" "$diskFmt" "$diskIo" "$diskCache" "" "")
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
addDevice () {
|
||||||
|
|
||||||
|
local diskDev="$1"
|
||||||
|
local diskType="$2"
|
||||||
|
local diskIndex="$3"
|
||||||
|
local diskAddress="$4"
|
||||||
|
|
||||||
|
local sectors=""
|
||||||
|
local devType
|
||||||
|
devType=$(lsblk -no TYPE "$diskDev" 2>/dev/null | head -n1)
|
||||||
|
|
||||||
|
[ -z "$diskDev" ] && return 0
|
||||||
|
[ ! -b "$diskDev" ] && error "Device $diskDev cannot be found! Please add it to the 'devices' section of your compose file." && exit 55
|
||||||
|
|
||||||
|
# Only detect and apply sector sizes for partitions, not whole disks.
|
||||||
|
# Whole disk passthrough with explicit sector sizes causes DSM not to recognize the disk.
|
||||||
|
if [[ "$devType" == "part" ]]; then
|
||||||
|
|
||||||
|
local result
|
||||||
|
local logical="" physical=""
|
||||||
|
|
||||||
|
result=$(fdisk -l "$diskDev" 2>/dev/null | grep -m 1 -o "(logical/physical): .*" | cut -c 21- || true)
|
||||||
|
|
||||||
|
if [ -n "$result" ]; then
|
||||||
|
logical="${result%% *}"
|
||||||
|
physical=$(echo "$result" | grep -m 1 -o "/ .*" | cut -c 3- || true)
|
||||||
|
physical="${physical%% *}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$logical" ] || [ -z "$physical" ]; then
|
||||||
|
warn "Failed to determine the sector size for $diskDev"
|
||||||
|
elif [[ "$physical" != "512" ]]; then
|
||||||
|
sectors=",logical_block_size=$logical,physical_block_size=$physical"
|
||||||
|
fi
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
DISK_OPTS+=$(createDevice "$diskDev" "$diskType" "$diskIndex" "$diskAddress" "raw" "$DISK_IO" "$DISK_CACHE" "" "$sectors")
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
[ -z "${DISK_OPTS:-}" ] && DISK_OPTS=""
|
||||||
|
[ -z "${DISK_TYPE:-}" ] && DISK_TYPE="scsi"
|
||||||
|
[ -z "${DISK_NAME:-}" ] && DISK_NAME="data"
|
||||||
|
[ -z "${DISK_DISABLE:-}" ] && DISK_DISABLE=""
|
||||||
|
|
||||||
|
if ! enabled "$DISK_DISABLE"; then
|
||||||
|
msg="Initializing disks..."
|
||||||
|
enabled "$DEBUG" && echo "$msg"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${DISK_IO,,}" == "native" && "${DISK_CACHE,,}" != "none" && "${DISK_CACHE,,}" != "directsync" ]]; then
|
||||||
|
warn "DISK_IO=native requires direct I/O caching, using DISK_IO=threads with DISK_CACHE=$DISK_CACHE."
|
||||||
|
DISK_IO="threads"
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "${DISK_DISCARD,,}" in
|
||||||
|
"y" | "yes" | "true" | "1" | "on" | "unmap" )
|
||||||
|
DISK_DISCARD="unmap" ;;
|
||||||
|
|
||||||
|
"n" | "no" | "false" | "0" | "off" | "ignore" )
|
||||||
|
DISK_DISCARD="ignore" ;;
|
||||||
|
|
||||||
|
* )
|
||||||
|
warn "Invalid DISK_DISCARD value '$DISK_DISCARD', using 'unmap'."
|
||||||
|
DISK_DISCARD="unmap" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [[ ! "$DISK_ROTATION" =~ ^[0-9]+$ ]]; then
|
||||||
|
warn "Invalid DISK_ROTATION value '$DISK_ROTATION', using 1."
|
||||||
|
DISK_ROTATION="1"
|
||||||
|
fi
|
||||||
|
|
||||||
|
DISK_FMT="${DISK_FMT,,}"
|
||||||
|
|
||||||
|
case "$DISK_FMT" in
|
||||||
|
"raw" | "qcow2" ) ;;
|
||||||
|
* ) error "Invalid DISK_FMT specified, value \"$DISK_FMT\" is not recognized!" && exit 78 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if ! validDiskType "$DISK_TYPE"; then
|
||||||
|
error "Invalid DISK_TYPE specified, value \"$DISK_TYPE\" is not recognized!"
|
||||||
|
exit 80
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$DISK_FLAGS" =~ [[:space:]] ]]; then
|
||||||
|
error "Invalid DISK_FLAGS value '$DISK_FLAGS', spaces are not allowed."
|
||||||
|
exit 78
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$ALLOCATE" ]; then
|
||||||
|
ALLOCATE="N"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if disabled "$ALLOCATE"; then
|
||||||
|
DISK_STYLE="growable"
|
||||||
|
DISK_ALLOC="preallocation=off"
|
||||||
|
else
|
||||||
|
DISK_STYLE="preallocated"
|
||||||
|
DISK_ALLOC="preallocation=falloc"
|
||||||
|
fi
|
||||||
|
|
||||||
|
DISK_OPTS+=$(createDevice "$BOOT" "$DISK_TYPE" "1" "0xa" "raw" "$DISK_IO" "$DISK_CACHE" "" "")
|
||||||
|
DISK_OPTS+=$(createDevice "$SYSTEM" "$DISK_TYPE" "2" "0xb" "raw" "$DISK_IO" "$DISK_CACHE" "" "")
|
||||||
|
|
||||||
|
if enabled "$DISK_DISABLE"; then
|
||||||
|
finishDisks && return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
DISK1_FILE="$STORAGE/${DISK_NAME}"
|
||||||
|
DISK2_FILE="/storage2/${DISK_NAME}2"
|
||||||
|
DISK3_FILE="/storage3/${DISK_NAME}3"
|
||||||
|
DISK4_FILE="/storage4/${DISK_NAME}4"
|
||||||
|
|
||||||
|
: "${DISK2_SIZE:=""}"
|
||||||
|
: "${DISK3_SIZE:=""}"
|
||||||
|
: "${DISK4_SIZE:=""}"
|
||||||
|
|
||||||
|
: "${DEVICE:=""}" # Docker variables to passthrough a block device, like /dev/vdc1.
|
||||||
|
: "${DEVICE2:=""}"
|
||||||
|
: "${DEVICE3:=""}"
|
||||||
|
: "${DEVICE4:=""}"
|
||||||
|
|
||||||
|
[ -z "$DEVICE" ] && [ -b "/disk" ] && DEVICE="/disk"
|
||||||
|
[ -z "$DEVICE" ] && [ -b "/disk1" ] && DEVICE="/disk1"
|
||||||
|
[ -z "$DEVICE2" ] && [ -b "/disk2" ] && DEVICE2="/disk2"
|
||||||
|
[ -z "$DEVICE3" ] && [ -b "/disk3" ] && DEVICE3="/disk3"
|
||||||
|
[ -z "$DEVICE4" ] && [ -b "/disk4" ] && DEVICE4="/disk4"
|
||||||
|
|
||||||
|
[ -z "$DEVICE" ] && [ -b "/dev/disk1" ] && DEVICE="/dev/disk1"
|
||||||
|
[ -z "$DEVICE2" ] && [ -b "/dev/disk2" ] && DEVICE2="/dev/disk2"
|
||||||
|
[ -z "$DEVICE3" ] && [ -b "/dev/disk3" ] && DEVICE3="/dev/disk3"
|
||||||
|
[ -z "$DEVICE4" ] && [ -b "/dev/disk4" ] && DEVICE4="/dev/disk4"
|
||||||
|
|
||||||
|
DISK_FILES=( "$DISK1_FILE" "$DISK2_FILE" "$DISK3_FILE" "$DISK4_FILE" )
|
||||||
|
DISK_DESCS=( "disk" "disk2" "disk3" "disk4" )
|
||||||
|
DISK_SIZES=( "$DISK_SIZE" "$DISK2_SIZE" "$DISK3_SIZE" "$DISK4_SIZE" )
|
||||||
|
DISK_DEVICES=( "$DEVICE" "$DEVICE2" "$DEVICE3" "$DEVICE4" )
|
||||||
|
DISK_INDEXES=( "3" "4" "5" "6" )
|
||||||
|
DISK_ADDRESSES=( "0xc" "0xd" "0xe" "0xf" )
|
||||||
|
|
||||||
|
for i in "${!DISK_FILES[@]}"; do
|
||||||
|
|
||||||
|
if [ -n "${DISK_DEVICES[i]}" ]; then
|
||||||
|
addDevice "${DISK_DEVICES[i]}" "$DISK_TYPE" "${DISK_INDEXES[i]}" "${DISK_ADDRESSES[i]}" || exit $?
|
||||||
|
else
|
||||||
|
addDisk "${DISK_FILES[i]}" "$DISK_TYPE" "${DISK_DESCS[i]}" "${DISK_SIZES[i]}" "${DISK_INDEXES[i]}" "${DISK_ADDRESSES[i]}" "$DISK_FMT" "$DISK_IO" "$DISK_CACHE" || exit $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
done
|
||||||
|
|
||||||
|
finishDisks
|
||||||
|
|
||||||
|
return 0
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
# Docker environment variables
|
||||||
|
|
||||||
|
: "${GPU:="N"}" # GPU passthrough
|
||||||
|
: "${VGA:="virtio"}" # VGA adaptor
|
||||||
|
: "${DISPLAY:="none"}" # Display type
|
||||||
|
: "${LOSSY:="N"}" # Lossy VNC compression
|
||||||
|
: "${RENDERNODE:="/dev/dri/renderD128"}" # Render node
|
||||||
|
|
||||||
|
# Sanitize variables
|
||||||
|
VGA=$(strip "$VGA")
|
||||||
|
LOSSY=$(strip "$LOSSY")
|
||||||
|
DISPLAY=$(strip "$DISPLAY")
|
||||||
|
RENDERNODE=$(strip "$RENDERNODE")
|
||||||
|
|
||||||
|
CPU_VENDOR=$(lscpu | awk '/Vendor ID/{print $3}')
|
||||||
|
|
||||||
|
if ! enabled "$GPU" || isAmdCpu || [[ "$ARCH" != "amd64" ]]; then
|
||||||
|
|
||||||
|
[[ "${DISPLAY,,}" == "none" ]] && VGA="none"
|
||||||
|
|
||||||
|
if enabled "$LOSSY" && [[ "${DISPLAY,,}" == vnc=* ]]; then
|
||||||
|
DISPLAY+=",lossy=on"
|
||||||
|
fi
|
||||||
|
|
||||||
|
DISPLAY_OPTS="-display ${DISPLAY} -vga ${VGA}"
|
||||||
|
return 0
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
msg="Configuring display drivers..."
|
||||||
|
html "$msg"
|
||||||
|
enabled "$DEBUG" && echo "$msg"
|
||||||
|
|
||||||
|
DISPLAY_OPTS="-display egl-headless,rendernode=${RENDERNODE}"
|
||||||
|
DISPLAY_OPTS+=" -vga $VGA"
|
||||||
|
|
||||||
|
[ ! -d /dev/dri ] && mkdir -m 755 /dev/dri
|
||||||
|
|
||||||
|
# Extract the card number from the render node
|
||||||
|
CARD_NUMBER=$(echo "$RENDERNODE" | grep -oP '(?<=renderD)\d+')
|
||||||
|
CARD_DEVICE="/dev/dri/card$((CARD_NUMBER - 128))"
|
||||||
|
|
||||||
|
if [ ! -c "$CARD_DEVICE" ]; then
|
||||||
|
if mknod "$CARD_DEVICE" c 226 $((CARD_NUMBER - 128)); then
|
||||||
|
chmod 666 "$CARD_DEVICE"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -c "$RENDERNODE" ]; then
|
||||||
|
if mknod "$RENDERNODE" c 226 "$CARD_NUMBER"; then
|
||||||
|
chmod 666 "$RENDERNODE"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -c "$RENDERNODE" ] || [ ! -r "$RENDERNODE" ] || [ ! -w "$RENDERNODE" ]; then
|
||||||
|
warn "render device '${RENDERNODE}' is unavailable or inaccessible."
|
||||||
|
fi
|
||||||
|
|
||||||
|
addPackage "xserver-xorg-video-intel" "Intel GPU drivers"
|
||||||
|
addPackage "qemu-system-modules-opengl" "OpenGL module"
|
||||||
|
|
||||||
|
return 0
|
||||||
Executable
+49
@@ -0,0 +1,49 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
: "${PLATFORM:="x64"}"
|
||||||
|
: "${APP:="Virtual DSM"}"
|
||||||
|
: "${SUPPORT:="https://github.com/vdsm/virtual-dsm"}"
|
||||||
|
|
||||||
|
cd /run
|
||||||
|
|
||||||
|
. start.sh # Startup hook
|
||||||
|
. utils.sh # Load functions
|
||||||
|
. reset.sh # Initialize system
|
||||||
|
. server.sh # Start webserver
|
||||||
|
. install.sh # Run installation
|
||||||
|
. disk.sh # Initialize disks
|
||||||
|
. display.sh # Initialize graphics
|
||||||
|
. network.sh # Initialize network
|
||||||
|
. proc.sh # Initialize processor
|
||||||
|
. serial.sh # Initialize serialport
|
||||||
|
. power.sh # Configure shutdown
|
||||||
|
. memory.sh # Check available memory
|
||||||
|
. config.sh # Configure arguments
|
||||||
|
. finish.sh # Finish initialization
|
||||||
|
|
||||||
|
trap - ERR
|
||||||
|
|
||||||
|
cmd=(qemu-system-x86_64)
|
||||||
|
version=$("${cmd[@]}" --version | awk 'NR==1 { print $4 }')
|
||||||
|
info "Booting $APP using QEMU v$version..." && echo
|
||||||
|
|
||||||
|
if ! enabled "$SHUTDOWN"; then
|
||||||
|
exec "${cmd[@]}" ${ARGS:+ $ARGS}
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! interactive; then
|
||||||
|
"${cmd[@]}" ${ARGS:+ $ARGS} &
|
||||||
|
else
|
||||||
|
startConsole
|
||||||
|
startQemu "${cmd[@]}" ${ARGS:+ $ARGS}
|
||||||
|
fi
|
||||||
|
|
||||||
|
pid=$!
|
||||||
|
rc=0
|
||||||
|
|
||||||
|
wait "$pid" || rc=$?
|
||||||
|
[ -f "$QEMU_END" ] && exit "$rc"
|
||||||
|
|
||||||
|
sleep 1 & wait $!
|
||||||
|
finish "$rc"
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
if enabled "$DEBUG"; then
|
||||||
|
printf "QEMU arguments:\n\n%s\n\n" "${ARGS// -/$'\n-'}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
+349
@@ -0,0 +1,349 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
: "${URL:=""}" # URL of the PAT file to be downloaded.
|
||||||
|
|
||||||
|
if [ -f "$STORAGE/dsm.ver" ]; then
|
||||||
|
BASE=$(<"$STORAGE/dsm.ver")
|
||||||
|
BASE="${BASE//[![:print:]]/}"
|
||||||
|
[ -z "$BASE" ] && BASE="DSM_VirtualDSM_69057"
|
||||||
|
else
|
||||||
|
# Fallback for old installs
|
||||||
|
BASE="DSM_VirtualDSM_42962"
|
||||||
|
fi
|
||||||
|
|
||||||
|
FN="boot.pat"
|
||||||
|
DIR=$(find / -maxdepth 1 -type d -iname "$FN" -print -quit)
|
||||||
|
[ ! -d "$DIR" ] && DIR=$(find "$STORAGE" -maxdepth 1 -type d -iname "$FN" -print -quit)
|
||||||
|
|
||||||
|
if [ -d "$DIR" ]; then
|
||||||
|
BASE="DSM_VirtualDSM" && URL="file://$DIR"
|
||||||
|
if [[ ! -s "$STORAGE/$BASE.boot.img" || ! -s "$STORAGE/$BASE.system.img" ]]; then
|
||||||
|
error "The bind $DIR maps to a file that does not exist!" && exit 65
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
FILE=$(find / -maxdepth 1 -type f -iname "$FN" -print -quit)
|
||||||
|
[ ! -s "$FILE" ] && FILE=$(find "$STORAGE" -maxdepth 1 -type f -iname "$FN" -print -quit)
|
||||||
|
[ -s "$FILE" ] && BASE="DSM_VirtualDSM" && URL="file://$FILE"
|
||||||
|
|
||||||
|
URL=$(strip "$URL")
|
||||||
|
|
||||||
|
if [ -n "$URL" ] && [ ! -s "$FILE" ] && [ ! -d "$DIR" ]; then
|
||||||
|
BASE=$(basename "$URL" .pat)
|
||||||
|
if [ ! -s "$STORAGE/$BASE.system.img" ]; then
|
||||||
|
BASE=$(basename "${URL%%\?*}" .pat)
|
||||||
|
printf -v BASE '%b' "${BASE//%/\\x}"
|
||||||
|
BASE="${BASE//[!A-Za-z0-9._-]/_}"
|
||||||
|
fi
|
||||||
|
if [[ "${URL,,}" != "http"* && "${URL,,}" != "file:"* ]]; then
|
||||||
|
[ ! -s "$STORAGE/$BASE.pat" ] && error "Invalid URL: $URL" && exit 65
|
||||||
|
URL="file://$STORAGE/$BASE.pat"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -s "$STORAGE/$BASE.boot.img" && -s "$STORAGE/$BASE.system.img" ]]; then
|
||||||
|
return 0 # Previous installation found
|
||||||
|
fi
|
||||||
|
|
||||||
|
html "Please wait while Virtual DSM is being installed..."
|
||||||
|
|
||||||
|
DL=""
|
||||||
|
DL_CHINA="https://cndl.synology.cn/download/DSM"
|
||||||
|
DL_GLOBAL="https://global.synologydownload.com/download/DSM"
|
||||||
|
|
||||||
|
[[ "${URL,,}" == *"cndl.synology"* ]] && DL="$DL_CHINA"
|
||||||
|
[[ "${URL,,}" == *"global.synology"* ]] && DL="$DL_GLOBAL"
|
||||||
|
|
||||||
|
if [ -z "$DL" ]; then
|
||||||
|
[ -z "$COUNTRY" ] && setCountry
|
||||||
|
[ -z "$COUNTRY" ] && info "Warning: could not detect country to select mirror!"
|
||||||
|
[[ "${COUNTRY^^}" == "CN" ]] && DL="$DL_CHINA" || DL="$DL_GLOBAL"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$URL" ]; then
|
||||||
|
URL="$DL/release/7.2.2/72806/DSM_VirtualDSM_72806.pat"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -s "$FILE" ]; then
|
||||||
|
BASE=$(basename "${URL%%\?*}" .pat)
|
||||||
|
printf -v BASE '%b' "${BASE//%/\\x}"
|
||||||
|
BASE="${BASE//[!A-Za-z0-9._-]/_}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$URL" != "file://$STORAGE/$BASE.pat" ]]; then
|
||||||
|
rm -f "$STORAGE/$BASE.pat"
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -f "$STORAGE/$BASE.agent"
|
||||||
|
rm -f "$STORAGE/$BASE.boot.img"
|
||||||
|
rm -f "$STORAGE/$BASE.system.img"
|
||||||
|
|
||||||
|
# Check filesystem
|
||||||
|
FS=$(stat -f -c %T "$STORAGE")
|
||||||
|
|
||||||
|
if [[ "${FS,,}" == "overlay"* && "${ENGINE,,}" == "docker" ]]; then
|
||||||
|
warn "the filesystem of $STORAGE is OverlayFS, this usually means it was binded to an invalid path!"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${FS,,}" == "fuse"* ]]; then
|
||||||
|
warn "the filesystem of $STORAGE is FUSE, this extra layer will negatively affect performance!"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${FS,,}" == "ecryptfs" || "${FS,,}" == "tmpfs" ]]; then
|
||||||
|
warn "the filesystem of $STORAGE is $FS, which does not support O_DIRECT mode, adjusting settings..."
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${FS,,}" == "fat"* || "${FS,,}" == "vfat"* || "${FS,,}" == "msdos"* ]]; then
|
||||||
|
error "Unable to install on $FS filesystems, please use a different filesystem for /storage." && exit 61
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${FS,,}" != "exfat"* && "${FS,,}" != "ntfs"* && "${FS,,}" != "unknown"* ]]; then
|
||||||
|
TMP="$STORAGE/tmp"
|
||||||
|
rm -rf "$TMP"
|
||||||
|
if ! makeDir "$TMP"; then
|
||||||
|
error "Failed to create directory \"$TMP\" !" && exit 93
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
TMP="/tmp/dsm"
|
||||||
|
TMP_SPACE=2147483648
|
||||||
|
SPACE=$(df --output=avail -B 1 /tmp | tail -n 1)
|
||||||
|
SPACE_MB=$(formatBytes "$SPACE")
|
||||||
|
if (( TMP_SPACE > SPACE )); then
|
||||||
|
error "Not enough free space inside the container, have $SPACE_MB available but need at least 2 GB." && exit 93
|
||||||
|
fi
|
||||||
|
rm -rf "$TMP" && mkdir -p "$TMP"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check free diskspace
|
||||||
|
ROOT_SPACE=536870912
|
||||||
|
SPACE=$(df --output=avail -B 1 / | tail -n 1)
|
||||||
|
SPACE_MB=$(formatBytes "$SPACE" "down")
|
||||||
|
(( ROOT_SPACE > SPACE )) && error "Not enough free space inside the container, have $SPACE_MB available but need at least 500 MB." && exit 96
|
||||||
|
|
||||||
|
MIN_SPACE=15032385536
|
||||||
|
SPACE=$(df --output=avail -B 1 "$STORAGE" | tail -n 1)
|
||||||
|
SPACE_GB=$(formatBytes "$SPACE")
|
||||||
|
(( MIN_SPACE > SPACE )) && error "Not enough free space for installation in $STORAGE, have $SPACE_GB available but need at least 14 GB." && exit 94
|
||||||
|
|
||||||
|
if [[ "$URL" == "file://"* ]]; then
|
||||||
|
MSG="Copying DSM"
|
||||||
|
ERR="Failed to copy ${URL:7}"
|
||||||
|
info "Install: Copying installation image..."
|
||||||
|
else
|
||||||
|
MSG="Downloading DSM"
|
||||||
|
ERR="Failed to download $URL"
|
||||||
|
info "Install: Downloading $BASE.pat..."
|
||||||
|
fi
|
||||||
|
|
||||||
|
html "$MSG..."
|
||||||
|
|
||||||
|
PAT="/$BASE.pat"
|
||||||
|
rm -f "$PAT"
|
||||||
|
|
||||||
|
if [[ "$URL" == "file://"* ]]; then
|
||||||
|
|
||||||
|
if [ ! -f "${URL:7}" ]; then
|
||||||
|
error "File '${URL:7}' does not exist!" && exit 65
|
||||||
|
fi
|
||||||
|
|
||||||
|
cp "${URL:7}" "$PAT"
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
|
SIZE=0
|
||||||
|
REASON=""
|
||||||
|
PROGRESS=()
|
||||||
|
OUTPUT=""
|
||||||
|
LOG=$(mktemp)
|
||||||
|
|
||||||
|
[[ "${URL,,}" == *"_72806.pat" ]] && SIZE=361010261
|
||||||
|
[[ "${URL,,}" == *"_69057.pat" ]] && SIZE=363837333
|
||||||
|
[[ "${URL,,}" == *"_42218.pat" ]] && SIZE=379637760
|
||||||
|
|
||||||
|
# Use Wget's progress bar in a terminal and progress.sh in container logs.
|
||||||
|
if [ -t 1 ]; then
|
||||||
|
PROGRESS=( --show-progress --progress=bar:noscroll )
|
||||||
|
else
|
||||||
|
OUTPUT="log"
|
||||||
|
fi
|
||||||
|
|
||||||
|
/run/progress.sh "$PAT" "$SIZE" "$MSG ([P])..." "$OUTPUT" 52428800 &
|
||||||
|
|
||||||
|
{
|
||||||
|
LC_ALL=C wget "$URL" -O "$PAT" --no-verbose --no-check-certificate \
|
||||||
|
--timeout=30 --no-http-keep-alive "${PROGRESS[@]}" \
|
||||||
|
--output-file="$LOG"
|
||||||
|
rc=$?
|
||||||
|
} || :
|
||||||
|
|
||||||
|
fKill "progress.sh"
|
||||||
|
|
||||||
|
if (( rc != 0 )); then
|
||||||
|
REASON=$(sed -n \
|
||||||
|
-e 's/^wget: //p' \
|
||||||
|
-e 's/^[0-9-]\{10\} [0-9:]\{8\} ERROR //p' \
|
||||||
|
"$LOG" | tail -n 1)
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -f "$LOG"
|
||||||
|
|
||||||
|
if (( rc == 3 )); then
|
||||||
|
error "$ERR because the file could not be written (disk full?)."
|
||||||
|
exit 69
|
||||||
|
elif (( rc != 0 )); then
|
||||||
|
if [ -n "$REASON" ]; then
|
||||||
|
error "$ERR: ${REASON%.}."
|
||||||
|
else
|
||||||
|
error "$ERR with exit status $rc."
|
||||||
|
fi
|
||||||
|
exit 69
|
||||||
|
fi
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
[ ! -s "$PAT" ] && error "$ERR" && exit 69
|
||||||
|
|
||||||
|
SIZE=$(stat -c%s "$PAT")
|
||||||
|
|
||||||
|
if ((SIZE<250000000)); then
|
||||||
|
error "The specified PAT file is probably an update pack as it's too small." && exit 62
|
||||||
|
fi
|
||||||
|
|
||||||
|
MSG="Extracting installation image..."
|
||||||
|
info "Install: $MSG" && html "$MSG"
|
||||||
|
|
||||||
|
if { tar tf "$PAT"; } >/dev/null 2>&1; then
|
||||||
|
|
||||||
|
tar xpf "$PAT" -C "$TMP/."
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
|
{ (cd "$TMP" && python3 /run/extract.py -i "$PAT" -d 2>/run/extract.log); rc=$?; } || :
|
||||||
|
|
||||||
|
if (( rc != 0 )); then
|
||||||
|
cat /run/extract.log
|
||||||
|
error "Failed to extract PAT file, reason $rc" && exit 63
|
||||||
|
fi
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
MSG="Preparing system partition..."
|
||||||
|
info "Install: $MSG" && html "$MSG"
|
||||||
|
|
||||||
|
BOOT=$(find "$TMP" -name "*.bin.zip" -print -quit)
|
||||||
|
[ -z "$BOOT" ] && error "The PAT file contains no boot image." && exit 67
|
||||||
|
[ ! -s "$BOOT" ] && error "The PAT boot image archive is empty." && exit 67
|
||||||
|
|
||||||
|
unzip -q -o "$BOOT" -d "$TMP"
|
||||||
|
BOOT="${BOOT%.zip}"
|
||||||
|
|
||||||
|
SYSTEM="$STORAGE/$BASE.system.img"
|
||||||
|
rm -f "$SYSTEM"
|
||||||
|
|
||||||
|
# Check free diskspace
|
||||||
|
SYSTEM_SIZE=10738466816
|
||||||
|
SPACE=$(df --output=avail -B 1 "$STORAGE" | tail -n 1)
|
||||||
|
SPACE_MB=$(formatBytes "$SPACE")
|
||||||
|
|
||||||
|
if (( SYSTEM_SIZE > SPACE )); then
|
||||||
|
error "Not enough free space in $STORAGE to create a 10 GB system disk, have only $SPACE_MB available." && exit 97
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! touch "$SYSTEM"; then
|
||||||
|
error "Could not create file $SYSTEM for the system disk." && exit 98
|
||||||
|
fi
|
||||||
|
|
||||||
|
! setOwner "$SYSTEM" && warn "failed to set the owner for \"$SYSTEM\" !"
|
||||||
|
|
||||||
|
if [[ "${FS,,}" == "btrfs" ]]; then
|
||||||
|
{ chattr +C "$SYSTEM"; } || :
|
||||||
|
FA=$(lsattr "$SYSTEM")
|
||||||
|
if [[ "$FA" != *"C"* ]]; then
|
||||||
|
error "Failed to disable COW for system image $SYSTEM on ${FS^^} filesystem."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! fallocate -l "$SYSTEM_SIZE" "$SYSTEM" &>/dev/null; then
|
||||||
|
if ! fallocate -l -x "$SYSTEM_SIZE" "$SYSTEM"; then
|
||||||
|
if ! truncate -s "$SYSTEM_SIZE" "$SYSTEM"; then
|
||||||
|
rm -f "$SYSTEM"
|
||||||
|
error "Could not allocate file $SYSTEM for the system disk." && exit 98
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
PART="$TMP/partition.fdisk"
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "label: dos"
|
||||||
|
echo "label-id: 0x6f9ee2e9"
|
||||||
|
echo "device: $SYSTEM"
|
||||||
|
echo "unit: sectors"
|
||||||
|
echo "sector-size: 512"
|
||||||
|
echo ""
|
||||||
|
echo "${SYSTEM}1 : start= 2048, size= 16777216, type=83"
|
||||||
|
echo "${SYSTEM}2 : start= 16779264, size= 4194304, type=82"
|
||||||
|
} > "$PART"
|
||||||
|
|
||||||
|
sfdisk -q "$SYSTEM" < "$PART"
|
||||||
|
|
||||||
|
MOUNT="$TMP/system"
|
||||||
|
rm -rf "$MOUNT"
|
||||||
|
|
||||||
|
if ! makeDir "$MOUNT"; then
|
||||||
|
error "Failed to create directory \"$MOUNT\" !" && exit 93
|
||||||
|
fi
|
||||||
|
|
||||||
|
MSG="Extracting system partition..."
|
||||||
|
info "Install: $MSG" && html "$MSG"
|
||||||
|
|
||||||
|
HDA="$TMP/hda1"
|
||||||
|
IDB="$TMP/indexdb"
|
||||||
|
PKG="$TMP/packages"
|
||||||
|
HDP="$TMP/synohdpack_img"
|
||||||
|
|
||||||
|
[ ! -s "$HDA.tgz" ] && error "The PAT file contains no OS image." && exit 64
|
||||||
|
mv "$HDA.tgz" "$HDA.txz"
|
||||||
|
|
||||||
|
[ -d "$PKG" ] && mv "$PKG/" "$MOUNT/.SynoUpgradePackages/"
|
||||||
|
rm -f "$MOUNT/.SynoUpgradePackages/ActiveInsight-"*
|
||||||
|
|
||||||
|
INDEX_DB="$MOUNT/usr/syno/synoman/indexdb"
|
||||||
|
|
||||||
|
if [ -s "$IDB.txz" ]; then
|
||||||
|
mkdir -p "$INDEX_DB"
|
||||||
|
fi
|
||||||
|
|
||||||
|
LABEL="1.44.1-42218"
|
||||||
|
OFFSET="1048576" # 2048 * 512
|
||||||
|
NUMBLOCKS="2097152" # (16777216 * 512) / 4096
|
||||||
|
MSG="Installing system partition..."
|
||||||
|
|
||||||
|
fakeroot -- bash -c "set -Eeu;\
|
||||||
|
[ -s $HDP.txz ] && tar xpfJ $HDP.txz --absolute-names -C $MOUNT/;\
|
||||||
|
[ -s $IDB.txz ] && tar xpfJ $IDB.txz --absolute-names -C $INDEX_DB/;\
|
||||||
|
tar xpfJ $HDA.txz --absolute-names --skip-old-files -C $MOUNT/;\
|
||||||
|
printf '%b%s%b' '\E[1;34m❯ \E[1;36m' 'Install: $MSG' '\E[0m\n';\
|
||||||
|
mke2fs -q -t ext4 -b 4096 -d $MOUNT/ -L $LABEL -F -E offset=$OFFSET $SYSTEM $NUMBLOCKS"
|
||||||
|
|
||||||
|
rm -rf "$MOUNT"
|
||||||
|
echo "$BASE" > "$STORAGE/dsm.ver"
|
||||||
|
! setOwner "$STORAGE/dsm.ver" && warn "failed to set the owner for \"$STORAGE/dsm.ver\" !"
|
||||||
|
|
||||||
|
if [[ "$URL" == "file://$STORAGE/$BASE.pat" ]]; then
|
||||||
|
rm -f "$PAT"
|
||||||
|
else
|
||||||
|
mv -f "$PAT" "$STORAGE/$BASE.pat"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f "$STORAGE/$BASE.pat" ]; then
|
||||||
|
! setOwner "$STORAGE/$BASE.pat" && warn "failed to set the owner for \"$STORAGE/$BASE.pat\" !"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mv -f "$BOOT" "$STORAGE/$BASE.boot.img"
|
||||||
|
! setOwner "$STORAGE/$BASE.boot.img" && warn "failed to set the owner for \"$STORAGE/$BASE.boot.img\" !"
|
||||||
|
|
||||||
|
rm -rf "$TMP"
|
||||||
|
|
||||||
|
return 0
|
||||||
+112
@@ -0,0 +1,112 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
msg="Checking memory..."
|
||||||
|
enabled "$DEBUG" && echo "$msg"
|
||||||
|
|
||||||
|
app() {
|
||||||
|
echo "Virtual DSM"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
checkConfiguredMemory() {
|
||||||
|
|
||||||
|
if disabled "$RAM_CHECK" || [[ "${RAM_SIZE,,}" == "max" || "${RAM_SIZE,,}" == "half" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local wanted msg avail_mem
|
||||||
|
wanted=$(numfmt --from=iec "$RAM_SIZE")
|
||||||
|
avail_mem=$(formatBytes "$RAM_AVAIL")
|
||||||
|
|
||||||
|
if (( (wanted + RAM_SPARE) > RAM_AVAIL )); then
|
||||||
|
msg="Your configured RAM_SIZE of ${RAM_SIZE/G/ GB} is too high for the $avail_mem of free memory available,"
|
||||||
|
if [[ "${FS,,}" == "zfs" ]]; then
|
||||||
|
info "$msg but since ZFS is active this will be ignored."
|
||||||
|
else
|
||||||
|
RAM_SIZE="max"
|
||||||
|
warn "$msg it will automatically be adjusted to a lower amount."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if (( (wanted + (RAM_SPARE * 3)) > RAM_AVAIL )); then
|
||||||
|
msg="your configured RAM_SIZE of ${RAM_SIZE/G/ GB} is very close to the $avail_mem of free memory available,"
|
||||||
|
if [[ "${FS,,}" == "zfs" ]]; then
|
||||||
|
info "$msg but since ZFS is active this will be ignored."
|
||||||
|
else
|
||||||
|
warn "$msg please consider a lower amount."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
configureHalfMemory() {
|
||||||
|
local wanted
|
||||||
|
|
||||||
|
if [[ "${RAM_SIZE,,}" != "half" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if (( (RAM_AVAIL / 2) > RAM_SPARE )); then
|
||||||
|
wanted=$(( (RAM_AVAIL / 2) / 1048577 ))
|
||||||
|
RAM_SIZE="${wanted}M"
|
||||||
|
info "Allocated $wanted MB of RAM for $(app)."
|
||||||
|
else
|
||||||
|
RAM_SIZE="max"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
configureMaxMemory() {
|
||||||
|
local wanted
|
||||||
|
|
||||||
|
if [[ "${RAM_SIZE,,}" != "max" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if (( RAM_AVAIL < (RAM_SPARE * 2) )); then
|
||||||
|
|
||||||
|
wanted=$(( RAM_AVAIL / 2 ))
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
|
wanted=$(( RAM_AVAIL - (RAM_SPARE * 3) ))
|
||||||
|
|
||||||
|
if (( wanted < (RAM_SPARE * 6) )); then
|
||||||
|
wanted=$(( RAM_AVAIL - RAM_SPARE ))
|
||||||
|
fi
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
wanted=$(( wanted / 1048577 ))
|
||||||
|
RAM_SIZE="${wanted}M"
|
||||||
|
|
||||||
|
info "Allocated $wanted MB of RAM for $(app)."
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
checkMinimumMemory() {
|
||||||
|
local wanted
|
||||||
|
|
||||||
|
wanted=$(numfmt --from=iec "$RAM_SIZE")
|
||||||
|
|
||||||
|
if [ "$wanted" -lt "$RAM_MINIMUM" ]; then
|
||||||
|
wanted=$(( wanted / 1048577 ))
|
||||||
|
error "Not enough memory available, there is only $wanted MB left!"
|
||||||
|
exit 16
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
getMemoryInfo
|
||||||
|
|
||||||
|
checkConfiguredMemory
|
||||||
|
configureHalfMemory
|
||||||
|
configureMaxMemory
|
||||||
|
checkMinimumMemory
|
||||||
|
|
||||||
|
return 0
|
||||||
+2288
File diff suppressed because it is too large
Load Diff
+394
@@ -0,0 +1,394 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
: "${SHUTDOWN:="Y"}" # Graceful ACPI shutdown
|
||||||
|
: "${TIMEOUT:="105"}" # QEMU termination timeout
|
||||||
|
: "${API_TIMEOUT:="90"}" # External API call timeout
|
||||||
|
|
||||||
|
# Configure QEMU for graceful shutdown
|
||||||
|
|
||||||
|
API_CMD=6
|
||||||
|
|
||||||
|
SHUTDOWN_SKIP=0
|
||||||
|
SHUTDOWN_SIGNAL=0
|
||||||
|
|
||||||
|
QEMU_END="$QEMU_DIR/qemu.end"
|
||||||
|
CONSOLE_PID="$QEMU_DIR/console.pid"
|
||||||
|
CONSOLE_SOCKET="$QEMU_DIR/console.sock"
|
||||||
|
QEMU_START_PID="$QEMU_DIR/qemu.start.pid"
|
||||||
|
|
||||||
|
_trap() {
|
||||||
|
|
||||||
|
local func="$1"; shift
|
||||||
|
local sig
|
||||||
|
|
||||||
|
TRAP_PID=$BASHPID
|
||||||
|
|
||||||
|
for sig; do
|
||||||
|
# Capture the local callback and signal while registering the trap.
|
||||||
|
# shellcheck disable=SC2064
|
||||||
|
trap "$func $sig" "$sig"
|
||||||
|
done
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
signalCode() {
|
||||||
|
|
||||||
|
local sig="$1"
|
||||||
|
|
||||||
|
case "$sig" in
|
||||||
|
SIGHUP) echo 129 ;;
|
||||||
|
SIGINT) echo 130 ;;
|
||||||
|
SIGQUIT) echo 131 ;;
|
||||||
|
SIGABRT) echo 134 ;;
|
||||||
|
SIGTERM) echo 143 ;;
|
||||||
|
*) echo 0 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
displayReason() {
|
||||||
|
|
||||||
|
local reason="$1"
|
||||||
|
|
||||||
|
case "$reason" in
|
||||||
|
129 ) echo "SIGHUP" ;;
|
||||||
|
130 ) echo "SIGINT" ;;
|
||||||
|
131 ) echo "SIGQUIT" ;;
|
||||||
|
134 ) echo "SIGABRT" ;;
|
||||||
|
143 ) echo "SIGTERM" ;;
|
||||||
|
* ) echo "$reason" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
readQemuPid() {
|
||||||
|
|
||||||
|
local -n _pid="$1"
|
||||||
|
local file
|
||||||
|
|
||||||
|
for file in "$QEMU_START_PID" "$QEMU_PID"; do
|
||||||
|
if [ -s "$file" ] && read -r _pid < "$file"; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
qemuPidFile() {
|
||||||
|
|
||||||
|
local -n _file="$1"
|
||||||
|
|
||||||
|
_file="$QEMU_PID"
|
||||||
|
[ -s "$QEMU_START_PID" ] && _file="$QEMU_START_PID"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
waitQemuExit() {
|
||||||
|
|
||||||
|
local timeout="${1:-10}"
|
||||||
|
local file
|
||||||
|
|
||||||
|
qemuPidFile file
|
||||||
|
waitPidFile "$file" "$timeout"
|
||||||
|
}
|
||||||
|
|
||||||
|
waitQemuPid() {
|
||||||
|
|
||||||
|
local -n _pid="$1"
|
||||||
|
local cnt=0 value
|
||||||
|
|
||||||
|
while ! readQemuPid value; do
|
||||||
|
sleep 0.02
|
||||||
|
cnt=$((cnt + 1))
|
||||||
|
(( cnt >= 50 )) && return 1
|
||||||
|
done
|
||||||
|
|
||||||
|
_pid="$value"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
forceKillQemu() {
|
||||||
|
|
||||||
|
local reason="$1"
|
||||||
|
local pid display
|
||||||
|
|
||||||
|
! readQemuPid pid && return 0
|
||||||
|
! isAlive "$pid" && return 0
|
||||||
|
|
||||||
|
display=$(displayReason "$reason")
|
||||||
|
error "Forcefully terminating $(app), reason: $display..."
|
||||||
|
{ disown "$pid" || :; kill -9 -- "$pid" || :; } 2>/dev/null
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanupHelpers() {
|
||||||
|
|
||||||
|
local pids=( "${HOST_PID:-}" "${WSD_PID:-}" "${CONSOLE_PID:-}" \
|
||||||
|
"${WEB_PID:-}" "${PASST_PID:-}" "${DNSMASQ_PID:-}" )
|
||||||
|
|
||||||
|
mKill "${pids[@]}"
|
||||||
|
fKill "print.sh"
|
||||||
|
|
||||||
|
rm -f -- "$HOST_API_SOCKET" "$HOST_AGENT_SOCKET"
|
||||||
|
|
||||||
|
closeNetwork
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
startConsole() {
|
||||||
|
|
||||||
|
local output="${1:-/dev/tty}"
|
||||||
|
local cnt=0
|
||||||
|
|
||||||
|
rm -f -- "$CONSOLE_SOCKET" "$CONSOLE_PID"
|
||||||
|
|
||||||
|
if ! stty -icanon -echo isig -ixon min 1 time 0 </dev/tty; then
|
||||||
|
error "Failed to configure serial console terminal!"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
(
|
||||||
|
trap '' INT QUIT
|
||||||
|
exec nc -lU "$CONSOLE_SOCKET" </dev/tty >"$output"
|
||||||
|
) &
|
||||||
|
|
||||||
|
local pid="$!"
|
||||||
|
echo "$pid" > "$CONSOLE_PID"
|
||||||
|
|
||||||
|
while [ ! -S "$CONSOLE_SOCKET" ]; do
|
||||||
|
|
||||||
|
if ! isAlive "$pid"; then
|
||||||
|
rm -f -- "$CONSOLE_PID"
|
||||||
|
error "Serial console relay exited unexpectedly!"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep 0.02
|
||||||
|
cnt=$((cnt + 1))
|
||||||
|
|
||||||
|
if (( cnt > 100 )); then
|
||||||
|
error "Failed to start serial console relay!"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
done
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
stopConsole() {
|
||||||
|
|
||||||
|
mKill "$CONSOLE_PID"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
startQemu() {
|
||||||
|
|
||||||
|
rm -f -- "$QEMU_START_PID"
|
||||||
|
|
||||||
|
(
|
||||||
|
trap '' INT QUIT
|
||||||
|
|
||||||
|
# shellcheck disable=SC2016
|
||||||
|
exec setsid -f -w sh -c '
|
||||||
|
file=$1
|
||||||
|
shift
|
||||||
|
|
||||||
|
"$@" &
|
||||||
|
pid=$!
|
||||||
|
printf "%s\n" "$pid" > "$file" || exit 1
|
||||||
|
|
||||||
|
rc=0
|
||||||
|
wait "$pid" 2>/dev/null || rc=$?
|
||||||
|
exit "$rc"
|
||||||
|
' sh "$QEMU_START_PID" "$@"
|
||||||
|
) </dev/null &
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
finish() {
|
||||||
|
|
||||||
|
local reason=$1 failed=0
|
||||||
|
|
||||||
|
if [ ! -f "$QEMU_END" ] && (( reason != 0 )); then
|
||||||
|
failed=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
touch "$QEMU_END"
|
||||||
|
|
||||||
|
forceKillQemu "$reason"
|
||||||
|
cleanupHelpers
|
||||||
|
|
||||||
|
if ! waitQemuExit 10; then
|
||||||
|
warn "Timed out while waiting for $(app) to exit!"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
|
||||||
|
if (( failed == 0 )); then
|
||||||
|
echo "❯ Shutdown completed!"
|
||||||
|
else
|
||||||
|
error "QEMU exited unexpectedly!"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit "$reason"
|
||||||
|
}
|
||||||
|
|
||||||
|
sendGuestShutdown() {
|
||||||
|
|
||||||
|
local pid="$1"
|
||||||
|
local response
|
||||||
|
|
||||||
|
# Don't send the powerdown signal because vDSM ignores ACPI signals
|
||||||
|
# nc -q 1 -w 1 -U "$QEMU_DIR/monitor.sock" &> /dev/null <<<'system_powerdown' || :
|
||||||
|
|
||||||
|
# Send shutdown command to guest agent via serial port
|
||||||
|
API_TIMEOUT=$(strip "$API_TIMEOUT")
|
||||||
|
local url="http://localhost/read?command=$API_CMD&timeout=$API_TIMEOUT"
|
||||||
|
response=$(curl --unix-socket "$HOST_API_SOCKET" -sk -m "$(( API_TIMEOUT+2 ))" -S "$url" 2>&1)
|
||||||
|
|
||||||
|
if [[ "$response" =~ "\"success\"" ]]; then
|
||||||
|
|
||||||
|
echo && info "Virtual DSM is now ready to shutdown..."
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
|
response="${response#*message\"\: \"}"
|
||||||
|
[ -z "$response" ] && response="second signal"
|
||||||
|
|
||||||
|
echo && error "Forcefully terminating because of: ${response%%\"*}"
|
||||||
|
kill -15 -- "$pid" 2>/dev/null || :
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
normalizeTimeout() {
|
||||||
|
|
||||||
|
local term_grace=3 # seconds before loop ends to send SIGTERM
|
||||||
|
local cleanup_grace=3 # seconds reserved after the loop for cleanup
|
||||||
|
|
||||||
|
TIMEOUT=$(strip "$TIMEOUT")
|
||||||
|
if [[ ! "$TIMEOUT" =~ ^[0-9]+$ ]]; then
|
||||||
|
TIMEOUT=105
|
||||||
|
fi
|
||||||
|
|
||||||
|
if (( TIMEOUT >= 30 )); then
|
||||||
|
term_grace=5
|
||||||
|
cleanup_grace=5
|
||||||
|
elif (( TIMEOUT >= 15 )); then
|
||||||
|
term_grace=4
|
||||||
|
cleanup_grace=4
|
||||||
|
fi
|
||||||
|
|
||||||
|
local elapsed=$((SECONDS - start))
|
||||||
|
local timeout_left=$((TIMEOUT - elapsed))
|
||||||
|
|
||||||
|
local min=$((term_grace + cleanup_grace + 1))
|
||||||
|
(( timeout_left < min )) && timeout_left=$min
|
||||||
|
|
||||||
|
wait_until=$((timeout_left - cleanup_grace))
|
||||||
|
sigterm_at=$((wait_until - term_grace))
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
waitForShutdown() {
|
||||||
|
|
||||||
|
local cnt=0
|
||||||
|
local pid="$1"
|
||||||
|
local name="$APP"
|
||||||
|
|
||||||
|
while (( cnt <= wait_until && SHUTDOWN_SKIP == 0 )); do
|
||||||
|
|
||||||
|
sleep 1 &
|
||||||
|
local slp=$!
|
||||||
|
|
||||||
|
# Stop waiting if the process has exited
|
||||||
|
! isAlive "$pid" && break
|
||||||
|
|
||||||
|
# Workaround for stale/zombie QEMU pid file
|
||||||
|
[ ! -s "$QEMU_START_PID" ] && [ ! -s "$QEMU_PID" ] && break
|
||||||
|
|
||||||
|
if (( cnt == sigterm_at )); then
|
||||||
|
info "${name^} is still running, sending SIGTERM... ($cnt/$wait_until)"
|
||||||
|
kill -15 -- "$pid" 2>/dev/null || :
|
||||||
|
elif (( cnt > 0 )) && enabled "${DEBUG:-}"; then
|
||||||
|
info "Waiting for $name to shut down... ($cnt/$wait_until)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
wait "$slp" || :
|
||||||
|
(( cnt++ ))
|
||||||
|
|
||||||
|
done
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
graceful_shutdown() {
|
||||||
|
|
||||||
|
local sig="$1"
|
||||||
|
local pid code
|
||||||
|
|
||||||
|
[[ $BASHPID != "$TRAP_PID" ]] && return
|
||||||
|
|
||||||
|
code=$(signalCode "$sig")
|
||||||
|
|
||||||
|
if [ -f "$QEMU_END" ]; then
|
||||||
|
|
||||||
|
if (( code == 130 && SHUTDOWN_SIGNAL == code )); then
|
||||||
|
SHUTDOWN_SKIP=1
|
||||||
|
echo && info "Received SIGINT again, forcing shutdown..."
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo && info "Received $sig signal while already shutting down..."
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
set +e
|
||||||
|
start=$SECONDS
|
||||||
|
SHUTDOWN_SIGNAL=$code
|
||||||
|
|
||||||
|
touch "$QEMU_END"
|
||||||
|
echo && info "Received $sig signal, sending shutdown command..."
|
||||||
|
|
||||||
|
if ! readQemuPid pid; then
|
||||||
|
if ! interactive || ! waitQemuPid pid; then
|
||||||
|
warn "QEMU PID file does not exist?"
|
||||||
|
finish "$code"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$pid" ] || ! isAlive "$pid"; then
|
||||||
|
warn "QEMU process with PID $pid does not exist?"
|
||||||
|
finish "$code"
|
||||||
|
fi
|
||||||
|
|
||||||
|
sendGuestShutdown "$pid"
|
||||||
|
normalizeTimeout
|
||||||
|
waitForShutdown "$pid"
|
||||||
|
|
||||||
|
finish "$code"
|
||||||
|
}
|
||||||
|
|
||||||
|
! enabled "$SHUTDOWN" && return 0
|
||||||
|
[ -n "${QEMU_TIMEOUT:-}" ] && TIMEOUT="$QEMU_TIMEOUT"
|
||||||
|
|
||||||
|
if interactive; then
|
||||||
|
_trap graceful_shutdown SIGINT
|
||||||
|
fi
|
||||||
|
|
||||||
|
_trap graceful_shutdown SIGTERM SIGHUP SIGABRT SIGQUIT
|
||||||
|
|
||||||
|
return 0
|
||||||
+220
@@ -0,0 +1,220 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# shellcheck disable=SC2329
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
: "${DHCP:="N"}"
|
||||||
|
: "${NETWORK:="Y"}"
|
||||||
|
|
||||||
|
cd /run
|
||||||
|
. utils.sh # Load functions
|
||||||
|
|
||||||
|
info () { printf "%b%s%b" "\E[1;34m❯ \E[1;36m" "$1" "\E[0m\n" >&2; }
|
||||||
|
warn () { printf "%b%s%b" "\E[1;33m❯ " "WARNING: $1" "\E[0m\n" >&2; }
|
||||||
|
error () { printf "%b%s%b" "\E[1;31m❯ " "ERROR: $1" "\E[0m\n" >&2; }
|
||||||
|
|
||||||
|
disabled "$NETWORK" && exit 0
|
||||||
|
|
||||||
|
file="/run/shm/dsm.url"
|
||||||
|
msgs="/run/shm/msg.html"
|
||||||
|
driver="/run/shm/qemu.nic"
|
||||||
|
page="/run/shm/index.html"
|
||||||
|
address="/run/shm/qemu.ip"
|
||||||
|
shutdown="/run/shm/qemu.end"
|
||||||
|
socket="/run/shm/qemu-host-api.sock"
|
||||||
|
template="/var/www/index.html"
|
||||||
|
url="http://localhost/read?command=10"
|
||||||
|
|
||||||
|
resp_err="Guest returned an invalid response:"
|
||||||
|
curl_err="Failed to connect to guest: curl error"
|
||||||
|
jq_err="Failed to parse response from guest: jq error"
|
||||||
|
|
||||||
|
exitIfShuttingDown() {
|
||||||
|
|
||||||
|
[ -f "$shutdown" ] && exit 1
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
queryGuest() {
|
||||||
|
|
||||||
|
{ json=$(curl --unix-socket "$socket" -m 20 -sk "$url"); local rc=$?; } || :
|
||||||
|
|
||||||
|
exitIfShuttingDown
|
||||||
|
|
||||||
|
if (( rc != 0 )); then
|
||||||
|
error "$curl_err $rc"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
readJsonField() {
|
||||||
|
|
||||||
|
local query="$1"
|
||||||
|
local result
|
||||||
|
|
||||||
|
{ result=$(jq -r "$query" <<< "$json"); local rc=$?; } || :
|
||||||
|
|
||||||
|
if (( rc != 0 )); then
|
||||||
|
error "$jq_err $rc ( $json )"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$result" == "null" ]]; then
|
||||||
|
error "$resp_err $json"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s\n' "$result"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
readGuestStatus() {
|
||||||
|
|
||||||
|
local result msg
|
||||||
|
|
||||||
|
result=$(readJsonField '.status') || return 1
|
||||||
|
|
||||||
|
if [[ "$result" != "success" ]]; then
|
||||||
|
{ msg=$(jq -r '.message // empty' <<< "$json"); local rc=$?; } || :
|
||||||
|
|
||||||
|
if (( rc != 0 )); then
|
||||||
|
error "$jq_err $rc ( $json )"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
error "Guest replied $result: $msg"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
readGuestPort() {
|
||||||
|
|
||||||
|
port=$(readJsonField '.data.data.dsm_setting.data.http_port') || return 1
|
||||||
|
[ -z "$port" ] && return 1
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
readGuestIp() {
|
||||||
|
|
||||||
|
ip=$(readJsonField '.data.data.ip.data[] | select(.name=="eth0" and has("ip")) | .ip | select(test("^[0-9]+\\."))') || return 1
|
||||||
|
[ -z "$ip" ] && return 1
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
writeDsmLocation() {
|
||||||
|
|
||||||
|
echo "$ip:$port" > "$file"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pollGuestLocation() {
|
||||||
|
|
||||||
|
while [ ! -s "$file" ]; do
|
||||||
|
|
||||||
|
# Check if not shutting down
|
||||||
|
exitIfShuttingDown
|
||||||
|
|
||||||
|
sleep 3
|
||||||
|
|
||||||
|
exitIfShuttingDown
|
||||||
|
[ -s "$file" ] && break
|
||||||
|
|
||||||
|
# Retrieve network info from guest VM
|
||||||
|
queryGuest || continue
|
||||||
|
readGuestStatus || continue
|
||||||
|
readGuestPort || continue
|
||||||
|
readGuestIp || continue
|
||||||
|
writeDsmLocation
|
||||||
|
|
||||||
|
done
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
checkAddressConflict() {
|
||||||
|
|
||||||
|
local guest_ip="${location%:*}"
|
||||||
|
local container_ip=""
|
||||||
|
|
||||||
|
[ -s "$address" ] && container_ip=$(<"$address")
|
||||||
|
[ -z "$container_ip" ] && return 0
|
||||||
|
[[ "$guest_ip" != "$container_ip" ]] && return 0
|
||||||
|
|
||||||
|
warn "DSM is using the same IP as the container, this will cause connectivity issues."
|
||||||
|
warn "change the container's macvlan IP or assign DSM a different address in your router."
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
writeDhcpPage() {
|
||||||
|
|
||||||
|
local html
|
||||||
|
|
||||||
|
msg="http://$location"
|
||||||
|
local title="<title>Virtual DSM</title>"
|
||||||
|
local body="The location of DSM is <a href='http://$location'>http://$location</a>"
|
||||||
|
local script="<script>setTimeout(function(){ window.location.assign('http://$location'); }, 3000);</script>"
|
||||||
|
|
||||||
|
html=$(<"$template")
|
||||||
|
html="${html/\[1\]/$title}"
|
||||||
|
html="${html/\[2\]/$script}"
|
||||||
|
html="${html/\[3\]/$body}"
|
||||||
|
html="${html/\[4\]/}"
|
||||||
|
html="${html/\[5\]/}"
|
||||||
|
|
||||||
|
echo "$html" > "$page"
|
||||||
|
echo "$body" > "$msgs"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
buildStaticMessage() {
|
||||||
|
|
||||||
|
local nic ip
|
||||||
|
|
||||||
|
nic=$(<"$driver")
|
||||||
|
ip=$(<"$address")
|
||||||
|
local port="${location##*:}"
|
||||||
|
|
||||||
|
if [[ "${nic,,}" != "macvlan" ]]; then
|
||||||
|
msg="port $port"
|
||||||
|
else
|
||||||
|
msg="http://$ip:$port"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
printLoginMessage() {
|
||||||
|
|
||||||
|
echo "" >&2
|
||||||
|
info "-----------------------------------------------------------"
|
||||||
|
info " You can now login to DSM at $msg"
|
||||||
|
info "-----------------------------------------------------------"
|
||||||
|
echo "" >&2
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pollGuestLocation
|
||||||
|
exitIfShuttingDown
|
||||||
|
|
||||||
|
location=$(<"$file")
|
||||||
|
|
||||||
|
if enabled "$DHCP"; then
|
||||||
|
checkAddressConflict
|
||||||
|
writeDhcpPage
|
||||||
|
else
|
||||||
|
buildStaticMessage
|
||||||
|
fi
|
||||||
|
|
||||||
|
printLoginMessage
|
||||||
|
|
||||||
|
exit 0
|
||||||
+199
@@ -0,0 +1,199 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
# Docker environment variables
|
||||||
|
|
||||||
|
: "${HOST_CPU:=""}"
|
||||||
|
: "${CPU_FLAGS:=""}"
|
||||||
|
: "${CPU_MODEL:=""}"
|
||||||
|
|
||||||
|
HOST_CPU=$(strip "$HOST_CPU")
|
||||||
|
CPU_FLAGS=$(strip "$CPU_FLAGS")
|
||||||
|
CPU_MODEL=$(strip "$CPU_MODEL")
|
||||||
|
|
||||||
|
selectClocksource() {
|
||||||
|
|
||||||
|
CLOCKSOURCE="tsc"
|
||||||
|
[[ "${ARCH,,}" == "arm64" ]] && CLOCKSOURCE="arch_sys_counter"
|
||||||
|
CLOCK="/sys/devices/system/clocksource/clocksource0/current_clocksource"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
checkClocksource() {
|
||||||
|
|
||||||
|
local result
|
||||||
|
|
||||||
|
if [ ! -f "$CLOCK" ]; then
|
||||||
|
warn "file \"$CLOCK\" cannot be found?"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
result=$(<"$CLOCK")
|
||||||
|
result="${result//[![:print:]]/}"
|
||||||
|
|
||||||
|
case "${result,,}" in
|
||||||
|
"${CLOCKSOURCE,,}" ) ;;
|
||||||
|
"kvm-clock" ) info "Nested KVM virtualization detected.." ;;
|
||||||
|
"hyperv_clocksource_tsc_page" ) info "Nested Hyper-V virtualization detected.." ;;
|
||||||
|
"hpet" ) warn "unsupported clock source detected: '$result'. Please set host clock source to '$CLOCKSOURCE'." ;;
|
||||||
|
*) warn "unexpected clock source detected: '$result'. Please set host clock source to '$CLOCKSOURCE'." ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
checkSse42() {
|
||||||
|
|
||||||
|
if ! hasFlag "sse4_2"; then
|
||||||
|
error "Your CPU does not have the SSE4 instruction set that Virtual DSM requires!"
|
||||||
|
! enabled "$DEBUG" && exit 88
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
trimSpaces() {
|
||||||
|
|
||||||
|
local value="$1"
|
||||||
|
|
||||||
|
value="${value#"${value%%[![:space:]]*}"}"
|
||||||
|
value="${value%"${value##*[![:space:]]}"}"
|
||||||
|
|
||||||
|
echo "$value"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
removeCpuArgument() {
|
||||||
|
|
||||||
|
local args=" ${ARGUMENTS:-} "
|
||||||
|
|
||||||
|
while [[ "$args" =~ [[:space:]]-cpu([[:space:]][^[:space:]]+|=[^[:space:]]+)? ]]; do
|
||||||
|
local cpu="${BASH_REMATCH[0]}"
|
||||||
|
args="${args/$cpu/ }"
|
||||||
|
warn "Ignoring '${cpu#" "}' from ARGUMENTS, use CPU_MODEL and CPU_FLAGS instead."
|
||||||
|
done
|
||||||
|
|
||||||
|
ARGUMENTS=$(trimSpaces "$args")
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
configureKvmCpuModel() {
|
||||||
|
|
||||||
|
CPU_FEATURES="kvm=on,l3-cache=on,+hypervisor"
|
||||||
|
KVM_OPTS=",accel=kvm -enable-kvm -global kvm-pit.lost_tick_policy=discard"
|
||||||
|
|
||||||
|
if [ -z "$CPU_MODEL" ]; then
|
||||||
|
CPU_MODEL="host"
|
||||||
|
CPU_FEATURES+=",migratable=no"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
appendKvmInvtscFeature() {
|
||||||
|
|
||||||
|
if hasFlag "svm"; then
|
||||||
|
|
||||||
|
# AMD processor
|
||||||
|
if hasFlag "tsc_scale"; then
|
||||||
|
CPU_FEATURES+=",+invtsc"
|
||||||
|
fi
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
|
# Intel processor
|
||||||
|
local vmx
|
||||||
|
vmx=$(sed -ne '/^vmx flags/s/^.*: //p' /proc/cpuinfo)
|
||||||
|
|
||||||
|
if grep -qw "tsc_scaling" <<< "$vmx"; then
|
||||||
|
CPU_FEATURES+=",+invtsc"
|
||||||
|
fi
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
configureKvm() {
|
||||||
|
|
||||||
|
configureKvmCpuModel
|
||||||
|
checkSse42
|
||||||
|
appendKvmInvtscFeature
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
configureTcgCpuModel() {
|
||||||
|
|
||||||
|
if [ -n "$CPU_MODEL" ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$ARCH" == "amd64" ]]; then
|
||||||
|
CPU_MODEL="max"
|
||||||
|
CPU_FEATURES+=",migratable=no"
|
||||||
|
else
|
||||||
|
CPU_MODEL="qemu64"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
configureTcg() {
|
||||||
|
|
||||||
|
KVM_OPTS=""
|
||||||
|
CPU_FEATURES="l3-cache=on,+hypervisor"
|
||||||
|
|
||||||
|
if [[ "$ARCH" == "amd64" ]]; then
|
||||||
|
KVM_OPTS=" -accel tcg,thread=multi"
|
||||||
|
fi
|
||||||
|
|
||||||
|
configureTcgCpuModel
|
||||||
|
CPU_FEATURES+=",+ssse3,+sse4.1,+sse4.2"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
composeCpuFlags() {
|
||||||
|
|
||||||
|
CPU_FLAGS="${CPU_MODEL}${CPU_FEATURES:+,$CPU_FEATURES}${CPU_FLAGS:+,$CPU_FLAGS}"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
configureHostCpuName() {
|
||||||
|
|
||||||
|
if [ -z "$HOST_CPU" ]; then
|
||||||
|
[[ "${CPU,,}" != "unknown" ]] && HOST_CPU="$CPU"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$HOST_CPU" ]; then
|
||||||
|
HOST_CPU="${HOST_CPU%%,*},,"
|
||||||
|
else
|
||||||
|
HOST_CPU="QEMU, Virtual CPU,"
|
||||||
|
if [ "$ARCH" == "amd64" ]; then
|
||||||
|
HOST_CPU+=" X86_64"
|
||||||
|
else
|
||||||
|
HOST_CPU+=" $ARCH"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
selectClocksource
|
||||||
|
checkClocksource
|
||||||
|
|
||||||
|
if ! disabled "$KVM"; then
|
||||||
|
configureKvm
|
||||||
|
else
|
||||||
|
configureTcg
|
||||||
|
fi
|
||||||
|
|
||||||
|
removeCpuArgument
|
||||||
|
composeCpuFlags
|
||||||
|
configureHostCpuName
|
||||||
|
|
||||||
|
return 0
|
||||||
+302
@@ -0,0 +1,302 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
info="/run/shm/msg.html"
|
||||||
|
info_tmp="${info}.${BASHPID}.tmp"
|
||||||
|
|
||||||
|
escape() {
|
||||||
|
|
||||||
|
local s
|
||||||
|
|
||||||
|
s=${1//&/\&}
|
||||||
|
s=${s//</\<}
|
||||||
|
s=${s//>/\>}
|
||||||
|
s=${s//'"'/\"}
|
||||||
|
s=${s//"'"/\'}
|
||||||
|
|
||||||
|
printf '%s' "$s"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
writeInfo() {
|
||||||
|
|
||||||
|
local content="$1"
|
||||||
|
|
||||||
|
if ! printf '%s\n' "$content" > "$info_tmp"; then
|
||||||
|
rm -f -- "$info_tmp"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! mv -f -- "$info_tmp" "$info"; then
|
||||||
|
rm -f -- "$info_tmp"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
getBytes() {
|
||||||
|
|
||||||
|
local path="$1"
|
||||||
|
local mode="$2"
|
||||||
|
local bytes="0"
|
||||||
|
|
||||||
|
if [[ "$mode" == "counter" ]]; then
|
||||||
|
if [ -r "$path" ]; then
|
||||||
|
read -r bytes < "$path" || bytes="0"
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ "$bytes" =~ ^[0-9]+$ ]] || bytes="0"
|
||||||
|
printf '%s\n' "$bytes"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -s "$path" ] && [ ! -d "$path" ]; then
|
||||||
|
printf '0\n'
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$mode" == "allocated" ]]; then
|
||||||
|
bytes=$(du -sB1 -- "$path" 2>/dev/null | cut -f1) || bytes="0"
|
||||||
|
else
|
||||||
|
bytes=$(du -sb -- "$path" 2>/dev/null | cut -f1) || bytes="0"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s\n' "$bytes"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
getStatus() {
|
||||||
|
|
||||||
|
local file="$1"
|
||||||
|
local bytes total extra=""
|
||||||
|
|
||||||
|
[ -r "$file" ] || return 1
|
||||||
|
read -r bytes total extra < "$file" || return 1
|
||||||
|
|
||||||
|
if [[ ! "$bytes" =~ ^[0-9]+$ ||
|
||||||
|
! "$total" =~ ^[0-9]+$ ||
|
||||||
|
-n "$extra" ]]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s %s\n' "$bytes" "$total"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
formatSize() {
|
||||||
|
|
||||||
|
local bytes="$1"
|
||||||
|
local size
|
||||||
|
|
||||||
|
size=$(numfmt --to=iec --suffix=B "$bytes" |
|
||||||
|
sed -r 's/([A-Z])/ \1/') ||
|
||||||
|
size="${bytes} bytes"
|
||||||
|
|
||||||
|
printf '%s' "$size"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
printPercentProgress() {
|
||||||
|
|
||||||
|
local percent="$1"
|
||||||
|
|
||||||
|
while (( next_percent <= percent && next_percent <= 100 )); do
|
||||||
|
if [[ "$printed" == "Y" ]]; then
|
||||||
|
printf ' → %s%%' "$next_percent"
|
||||||
|
else
|
||||||
|
printf '%s%%' "$next_percent"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printed="Y"
|
||||||
|
next_percent=$((next_percent + 10))
|
||||||
|
done
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
printCurrentSize() {
|
||||||
|
|
||||||
|
local bytes="$1"
|
||||||
|
local size
|
||||||
|
|
||||||
|
size=$(formatSize "$bytes")
|
||||||
|
|
||||||
|
if [[ "$printed" == "Y" ]]; then
|
||||||
|
printf ' → %s' "$size"
|
||||||
|
else
|
||||||
|
printf '%s' "$size"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printed="Y"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
printSizeProgress() {
|
||||||
|
|
||||||
|
local bytes="$1"
|
||||||
|
local size
|
||||||
|
|
||||||
|
while (( bytes >= next_bytes )); do
|
||||||
|
size=$(formatSize "$next_bytes")
|
||||||
|
|
||||||
|
if [[ "$printed" == "Y" ]]; then
|
||||||
|
printf ' → %s' "$size"
|
||||||
|
else
|
||||||
|
printf '%s' "$size"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printed="Y"
|
||||||
|
next_bytes=$((next_bytes + step_bytes))
|
||||||
|
done
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
stopProgress() {
|
||||||
|
|
||||||
|
if [ -z "$status_file" ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
stopping="Y"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
finishProgress() {
|
||||||
|
|
||||||
|
rm -f -- "$info_tmp"
|
||||||
|
|
||||||
|
if [[ "$output" == "log" && "$printed" == "Y" ]]; then
|
||||||
|
printf '\n'
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
path="$1"
|
||||||
|
total="$2"
|
||||||
|
body=$(escape "$3")
|
||||||
|
output="${4:-}"
|
||||||
|
step_bytes="${5:-536870912}"
|
||||||
|
mode="${6:-apparent}"
|
||||||
|
status_file="${7:-}"
|
||||||
|
|
||||||
|
if [[ -n "$total" && ! "$total" =~ ^(0|[1-9][0-9]*)$ ]]; then
|
||||||
|
printf 'Invalid total size: %s\n' "$total" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! "$step_bytes" =~ ^[1-9][0-9]*$ ]]; then
|
||||||
|
printf 'Invalid progress interval: %s\n' "$step_bytes" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$mode" in
|
||||||
|
apparent | allocated | counter ) ;;
|
||||||
|
* )
|
||||||
|
printf 'Invalid progress mode: %s\n' "$mode" >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
case "$output" in
|
||||||
|
"" | log ) ;;
|
||||||
|
* )
|
||||||
|
printf 'Invalid progress output: %s\n' "$output" >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
printed="N"
|
||||||
|
next_percent=10
|
||||||
|
next_bytes="$step_bytes"
|
||||||
|
log_mode="percent"
|
||||||
|
stopping="N"
|
||||||
|
|
||||||
|
if [ -z "$total" ] || [[ "$total" == "0" ]]; then
|
||||||
|
log_mode="size"
|
||||||
|
fi
|
||||||
|
|
||||||
|
trap finishProgress EXIT
|
||||||
|
trap 'exit 0' HUP INT QUIT
|
||||||
|
trap stopProgress TERM
|
||||||
|
|
||||||
|
if [[ "$body" == *"..." ]]; then
|
||||||
|
body="<p class=\"loading\">${body::-3}</p>"
|
||||||
|
fi
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
|
||||||
|
final_pass="${stopping:-}"
|
||||||
|
bytes=$(getBytes "$path" "$mode")
|
||||||
|
effective_total="$total"
|
||||||
|
|
||||||
|
if [ -n "$status_file" ] && status=$(getStatus "$status_file"); then
|
||||||
|
read -r status_bytes status_total <<< "$status"
|
||||||
|
bytes="$status_bytes"
|
||||||
|
|
||||||
|
if (( status_total > 0 )); then
|
||||||
|
effective_total="$status_total"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# A real total may become available shortly after aria2 starts.
|
||||||
|
if [[ "$log_mode" == "size" &&
|
||||||
|
"$printed" == "N" &&
|
||||||
|
-n "$effective_total" &&
|
||||||
|
"$effective_total" != "0" ]]; then
|
||||||
|
log_mode="percent"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if (( bytes > 4096 )); then
|
||||||
|
|
||||||
|
write_html="Y"
|
||||||
|
|
||||||
|
if [ -z "$effective_total" ] ||
|
||||||
|
[[ "$effective_total" == "0" ]] ||
|
||||||
|
(( bytes > effective_total )); then
|
||||||
|
size=$(formatSize "$bytes")
|
||||||
|
|
||||||
|
if [[ "$output" == "log" ]]; then
|
||||||
|
if [[ "$log_mode" == "percent" ]]; then
|
||||||
|
printCurrentSize "$bytes"
|
||||||
|
next_bytes=$(((bytes / step_bytes + 1) * step_bytes))
|
||||||
|
log_mode="size"
|
||||||
|
else
|
||||||
|
printSizeProgress "$bytes"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Truncate to one decimal so progress is never reported early.
|
||||||
|
progress=$((bytes * 1000 / effective_total))
|
||||||
|
(( progress > 1000 )) && progress=1000
|
||||||
|
|
||||||
|
percent=$((progress / 10))
|
||||||
|
|
||||||
|
printf -v size '%d.%d%%' \
|
||||||
|
"$((progress / 10))" \
|
||||||
|
"$((progress % 10))"
|
||||||
|
|
||||||
|
if [[ "$output" == "log" ]]; then
|
||||||
|
if [[ "$log_mode" == "size" ]]; then
|
||||||
|
printSizeProgress "$bytes"
|
||||||
|
else
|
||||||
|
printPercentProgress "$percent"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Do not update the web viewer until at least 0.1% is reached.
|
||||||
|
(( progress == 0 )) && write_html="N"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$write_html" == "Y" ]]; then
|
||||||
|
writeInfo "${body//(\[P\])/($size)}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ "$final_pass" == "Y" ]] && break
|
||||||
|
|
||||||
|
sleep 1 &
|
||||||
|
wait $! || :
|
||||||
|
done
|
||||||
+355
@@ -0,0 +1,355 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
trap 'error "Status $? while: $BASH_COMMAND (line $LINENO/$BASH_LINENO)"' ERR
|
||||||
|
enabled "${TRACE:-}" && set -o functrace && trap 'echo "# $BASH_COMMAND" >&2' DEBUG
|
||||||
|
|
||||||
|
[ ! -f "/run/entry.sh" ] && error "Script must be run inside the container!" && exit 11
|
||||||
|
[ "$(id -u)" -ne "0" ] && error "Script must be executed with root privileges." && exit 12
|
||||||
|
|
||||||
|
# Docker environment variables
|
||||||
|
|
||||||
|
: "${TZ:=""}" # System timezone
|
||||||
|
: "${KVM:="Y"}" # KVM acceleration
|
||||||
|
: "${DEBUG:="N"}" # Disable debugging mode
|
||||||
|
: "${COUNTRY:=""}" # Country code for mirror
|
||||||
|
: "${MACHINE:="q35"}" # Machine type selection
|
||||||
|
: "${ALLOCATE:=""}" # Preallocate diskspace
|
||||||
|
: "${ARGUMENTS:=""}" # Extra QEMU parameters
|
||||||
|
: "${CPU_CORES:="2"}" # Amount of CPU cores
|
||||||
|
: "${RAM_SIZE:="2G"}" # Maximum RAM amount
|
||||||
|
: "${RAM_CHECK:="Y"}" # Check available RAM
|
||||||
|
: "${DISK_SIZE:="16G"}" # Initial data disk size
|
||||||
|
: "${STORAGE:="/storage"}" # Storage folder location
|
||||||
|
|
||||||
|
detectEngine() {
|
||||||
|
|
||||||
|
if [ -f "/run/.containerenv" ]; then
|
||||||
|
ENGINE="${container:-}"
|
||||||
|
|
||||||
|
if [[ "${ENGINE,,}" == *"podman"* ]]; then
|
||||||
|
ENGINE="Podman"
|
||||||
|
else
|
||||||
|
[ -z "$ENGINE" ] && ENGINE="Kubernetes"
|
||||||
|
fi
|
||||||
|
elif [ -f "/.dockerenv" ]; then
|
||||||
|
ENGINE="Docker"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
detectRootless() {
|
||||||
|
|
||||||
|
local uid_map
|
||||||
|
|
||||||
|
uid_map=$(awk '{$1=$1; print}' /proc/self/uid_map 2>/dev/null || true)
|
||||||
|
|
||||||
|
if [[ "$uid_map" == "0 0 4294967295" ]]; then
|
||||||
|
ROOTLESS="N"
|
||||||
|
else
|
||||||
|
ROOTLESS="Y"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
checkPrivileged() {
|
||||||
|
|
||||||
|
local cap_bnd
|
||||||
|
local last_cap
|
||||||
|
# Get the capability bounding set
|
||||||
|
cap_bnd=$(grep '^CapBnd:' /proc/$$/status | awk '{print $2}')
|
||||||
|
cap_bnd=$(printf "%d" "0x${cap_bnd}")
|
||||||
|
|
||||||
|
# Get the last capability number
|
||||||
|
last_cap=$(cat /proc/sys/kernel/cap_last_cap)
|
||||||
|
|
||||||
|
# Calculate the maximum capability value
|
||||||
|
local max_cap=$(((1 << (last_cap + 1)) - 1))
|
||||||
|
|
||||||
|
if [ "$cap_bnd" -eq "$max_cap" ]; then
|
||||||
|
PRIVILEGED="Y"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
normalizeCpuCores() {
|
||||||
|
|
||||||
|
CPU_CORES=$(strip "$CPU_CORES")
|
||||||
|
[ -z "$CPU_CORES" ] && CPU_CORES=2
|
||||||
|
[[ "${CPU_CORES,,}" == "max" ]] && CPU_CORES="$CORES"
|
||||||
|
[[ "${CPU_CORES,,}" == "half" ]] && CPU_CORES=$(( CORES / 2 ))
|
||||||
|
[ -z "${CPU_CORES##*[!0-9]*}" ] && error "Invalid amount of CPU_CORES: $CPU_CORES" && exit 15
|
||||||
|
|
||||||
|
[ "$CPU_CORES" -lt "1" ] && CPU_CORES=1
|
||||||
|
if [ "$CPU_CORES" -gt "$CORES" ]; then
|
||||||
|
warn "The amount for CPU_CORES (${CPU_CORES}) exceeds the amount of logical cores available (${CORES}) and will be limited."
|
||||||
|
CPU_CORES="$CORES"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
checkStorage() {
|
||||||
|
|
||||||
|
# Check system
|
||||||
|
|
||||||
|
QEMU_DIR="/run/shm"
|
||||||
|
|
||||||
|
if [ ! -d "/dev/shm" ]; then
|
||||||
|
error "Directory /dev/shm not found!" && exit 14
|
||||||
|
else
|
||||||
|
[ ! -d "$QEMU_DIR" ] && ln -s /dev/shm "$QEMU_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
QEMU_PID="$QEMU_DIR/qemu.pid"
|
||||||
|
|
||||||
|
# Check folder
|
||||||
|
|
||||||
|
if [[ "${STORAGE,,}" != "/storage" ]]; then
|
||||||
|
mkdir -p "$STORAGE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -d "$STORAGE" ]; then
|
||||||
|
error "Storage folder ($STORAGE) not found!" && exit 13
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -w "$STORAGE" ]; then
|
||||||
|
msg="Storage folder ($STORAGE) is not writeable!"
|
||||||
|
msg+=" If SELinux is active, you need to add the \":Z\" flag to the bind mount."
|
||||||
|
error "$msg" && exit 13
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
checkFilesystem() {
|
||||||
|
|
||||||
|
# Check filesystem
|
||||||
|
FS=$(stat -f -c %T "$STORAGE")
|
||||||
|
|
||||||
|
if [[ "${FS,,}" == "ecryptfs" || "${FS,,}" == "tmpfs" ]]; then
|
||||||
|
DISK_IO="threads"
|
||||||
|
DISK_CACHE="writeback"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
finiteMemoryLimit() {
|
||||||
|
|
||||||
|
local limit="$1"
|
||||||
|
local sentinel="4611686018427387904"
|
||||||
|
local i
|
||||||
|
|
||||||
|
[[ "$limit" =~ ^[0-9]+$ ]] || return 1
|
||||||
|
|
||||||
|
(( ${#limit} < ${#sentinel} )) && return 0
|
||||||
|
(( ${#limit} > ${#sentinel} )) && return 1
|
||||||
|
|
||||||
|
for (( i=0; i<${#sentinel}; i++ )); do
|
||||||
|
local left="${limit:i:1}"
|
||||||
|
local right="${sentinel:i:1}"
|
||||||
|
|
||||||
|
(( left < right )) && return 0
|
||||||
|
(( left > right )) && return 1
|
||||||
|
done
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
getMemoryInfo() {
|
||||||
|
|
||||||
|
local limit="" current=""
|
||||||
|
local host_total host_avail
|
||||||
|
|
||||||
|
host_total=$(free -b | awk '/^Mem:/ {print $2; exit}')
|
||||||
|
host_avail=$(free -b | awk '/^Mem:/ {print $7; exit}')
|
||||||
|
|
||||||
|
RAM_TOTAL="$host_total"
|
||||||
|
RAM_AVAIL="$host_avail"
|
||||||
|
|
||||||
|
if [ -r /sys/fs/cgroup/memory.max ] && [ -r /sys/fs/cgroup/memory.current ]; then
|
||||||
|
limit=$(< /sys/fs/cgroup/memory.max)
|
||||||
|
current=$(< /sys/fs/cgroup/memory.current)
|
||||||
|
elif [ -r /sys/fs/cgroup/memory/memory.limit_in_bytes ] && [ -r /sys/fs/cgroup/memory/memory.usage_in_bytes ]; then
|
||||||
|
limit=$(< /sys/fs/cgroup/memory/memory.limit_in_bytes)
|
||||||
|
current=$(< /sys/fs/cgroup/memory/memory.usage_in_bytes)
|
||||||
|
fi
|
||||||
|
|
||||||
|
if finiteMemoryLimit "$limit" && [[ "$current" =~ ^[0-9]+$ ]]; then
|
||||||
|
(( limit < RAM_TOTAL )) && RAM_TOTAL="$limit"
|
||||||
|
|
||||||
|
local available=$(( limit - current ))
|
||||||
|
(( available < 0 )) && available=0
|
||||||
|
(( available < RAM_AVAIL )) && RAM_AVAIL="$available"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
normalizeRamSize() {
|
||||||
|
|
||||||
|
# Read host and container memory limits.
|
||||||
|
getMemoryInfo
|
||||||
|
|
||||||
|
RAM_SPARE=500000000
|
||||||
|
RAM_MINIMUM=136314880
|
||||||
|
|
||||||
|
RAM_SIZE=$(strip "$RAM_SIZE")
|
||||||
|
RAM_SIZE="${RAM_SIZE// /}"
|
||||||
|
[ -z "$RAM_SIZE" ] && RAM_SIZE="2G"
|
||||||
|
|
||||||
|
if [[ "${RAM_SIZE,,}" != "max" && "${RAM_SIZE,,}" != "half" ]]; then
|
||||||
|
|
||||||
|
if [ -z "${RAM_SIZE//[0-9. ]}" ]; then
|
||||||
|
[ "${RAM_SIZE%%.*}" -lt "130" ] && RAM_SIZE="${RAM_SIZE}G" || RAM_SIZE="${RAM_SIZE}M"
|
||||||
|
fi
|
||||||
|
|
||||||
|
RAM_SIZE=$(echo "${RAM_SIZE^^}" | sed 's/MB/M/g;s/GB/G/g;s/TB/T/g')
|
||||||
|
! numfmt --from=iec "$RAM_SIZE" &>/dev/null && error "Invalid RAM_SIZE: $RAM_SIZE" && exit 16
|
||||||
|
wanted=$(numfmt --from=iec "$RAM_SIZE")
|
||||||
|
[ "$wanted" -lt "$RAM_MINIMUM" ] && error "RAM_SIZE is too low: $RAM_SIZE" && exit 16
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
checkKvm() {
|
||||||
|
|
||||||
|
# Check KVM support
|
||||||
|
|
||||||
|
if [[ "${PLATFORM,,}" == "x64" ]]; then
|
||||||
|
TARGET="amd64"
|
||||||
|
else
|
||||||
|
TARGET="arm64"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if disabled "$KVM"; then
|
||||||
|
warn "KVM acceleration is disabled, this will cause the machine to run about 10 times slower!"
|
||||||
|
else
|
||||||
|
if [[ "${ARCH,,}" != "$TARGET" ]]; then
|
||||||
|
KVM="N"
|
||||||
|
warn "your CPU architecture is ${ARCH^^} and cannot provide KVM acceleration for ${PLATFORM^^} instructions, so the machine will run about 10 times slower."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! disabled "$KVM"; then
|
||||||
|
|
||||||
|
KVM_ERR=""
|
||||||
|
|
||||||
|
if [ ! -e /dev/kvm ]; then
|
||||||
|
KVM_ERR="(/dev/kvm is missing)"
|
||||||
|
else
|
||||||
|
if ! sh -c 'echo -n > /dev/kvm' &> /dev/null; then
|
||||||
|
KVM_ERR="(/dev/kvm is unwriteable)"
|
||||||
|
else
|
||||||
|
if [[ "${PLATFORM,,}" == "x64" ]]; then
|
||||||
|
flags=$(sed -ne '/^flags/s/^.*: //p' /proc/cpuinfo)
|
||||||
|
if ! grep -qw "vmx\|svm" <<< "$flags"; then
|
||||||
|
KVM_ERR="(not enabled in BIOS)"
|
||||||
|
fi
|
||||||
|
if ! grep -qw "sse4_2" <<< "$flags"; then
|
||||||
|
error "Your CPU does not have the SSE4 instruction set that Virtual DSM requires!"
|
||||||
|
! enabled "$DEBUG" && exit 88
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$KVM_ERR" ]; then
|
||||||
|
KVM="N"
|
||||||
|
if [[ "$OSTYPE" =~ ^darwin ]]; then
|
||||||
|
warn "you are using macOS which has no KVM support, so the machine will run about 10 times slower."
|
||||||
|
else
|
||||||
|
kernel=$(uname -a)
|
||||||
|
case "${kernel,,}" in
|
||||||
|
*"microsoft"* )
|
||||||
|
error "Please bind '/dev/kvm' as a volume in the optional container settings when using Docker Desktop." ;;
|
||||||
|
*"synology"* )
|
||||||
|
error "Please make sure that Synology VMM (Virtual Machine Manager) is installed and that '/dev/kvm' is binded to this container." ;;
|
||||||
|
*)
|
||||||
|
error "KVM acceleration is not available $KVM_ERR, this will cause the machine to run about 10 times slower."
|
||||||
|
error "See the FAQ for possible causes, or disable acceleration by adding the \"KVM=N\" variable (not recommended)." ;;
|
||||||
|
esac
|
||||||
|
! enabled "$DEBUG" && exit 88
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Sanitize variables
|
||||||
|
TZ=$(strip "$TZ")
|
||||||
|
STORAGE=$(strip "$STORAGE")
|
||||||
|
COUNTRY=$(strip "$COUNTRY")
|
||||||
|
DISK_SIZE=$(strip "$DISK_SIZE")
|
||||||
|
|
||||||
|
# Helper variables
|
||||||
|
ROOTLESS="N"
|
||||||
|
PRIVILEGED="N"
|
||||||
|
ENGINE="Docker"
|
||||||
|
PROCESS="${APP,,}"
|
||||||
|
PROCESS="${PROCESS// /-}"
|
||||||
|
|
||||||
|
detectEngine
|
||||||
|
detectRootless
|
||||||
|
|
||||||
|
echo "❯ Starting $APP for $ENGINE v$(</etc/version)..."
|
||||||
|
echo "❯ For support visit $SUPPORT"
|
||||||
|
|
||||||
|
checkPrivileged
|
||||||
|
|
||||||
|
INFO="/run/shm/msg.html"
|
||||||
|
PAGE="/run/shm/index.html"
|
||||||
|
TEMPLATE="/var/www/index.html"
|
||||||
|
FOOTER1="$APP for $ENGINE v$(</etc/version)"
|
||||||
|
FOOTER2="<a href='$SUPPORT'>$SUPPORT</a>"
|
||||||
|
|
||||||
|
SOCKETS=1
|
||||||
|
CPU=$(cpu)
|
||||||
|
SYS=$(uname -r)
|
||||||
|
ARCH=$(dpkg --print-architecture)
|
||||||
|
IFS=. read -r KERNEL MINOR _ <<< "$SYS"
|
||||||
|
CORES=$(grep -c '^processor' /proc/cpuinfo)
|
||||||
|
|
||||||
|
if grep -qi "socket(s)" <<< "$(lscpu)"; then
|
||||||
|
SOCKETS=$(lscpu | grep -m 1 -i 'socket(s)' | awk '{print $2}')
|
||||||
|
[ -z "${SOCKETS##*[!0-9]*}" ] && SOCKETS=1
|
||||||
|
[ "$SOCKETS" -lt "1" ] && SOCKETS=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
normalizeCpuCores
|
||||||
|
checkStorage
|
||||||
|
checkFilesystem
|
||||||
|
normalizeRamSize
|
||||||
|
|
||||||
|
# Print system info
|
||||||
|
SYS="${SYS/-generic/}"
|
||||||
|
FS="${FS/UNKNOWN //}"
|
||||||
|
FS="${FS/ext2\/ext3/ext4}"
|
||||||
|
FS=$(echo "$FS" | sed 's/[)(]//g')
|
||||||
|
SPACE=$(df --output=avail -B 1 "$STORAGE" | tail -n 1)
|
||||||
|
SPACE_GB=$(formatBytes "$SPACE" "down")
|
||||||
|
AVAIL_MEM=$(formatBytes "$RAM_AVAIL" "down")
|
||||||
|
TOTAL_MEM=$(formatBytes "$RAM_TOTAL" "up")
|
||||||
|
|
||||||
|
echo "❯ CPU: ${CPU} | RAM: ${AVAIL_MEM/ GB/}/$TOTAL_MEM | DISK: $SPACE_GB (${FS}) | KERNEL: ${SYS}"
|
||||||
|
echo
|
||||||
|
|
||||||
|
checkKvm
|
||||||
|
|
||||||
|
# Cleanup files
|
||||||
|
rm -f "$QEMU_DIR"/dsm.url
|
||||||
|
rm -f "$QEMU_DIR"/{qemu.*,*.{pid,sock,pipe}}
|
||||||
|
|
||||||
|
# Cleanup dirs
|
||||||
|
rm -rf /tmp/dsm
|
||||||
|
rm -rf "$STORAGE/tmp"
|
||||||
|
|
||||||
|
return 0
|
||||||
+143
@@ -0,0 +1,143 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
# Docker environment variables
|
||||||
|
|
||||||
|
: "${HOST_MAC:=""}"
|
||||||
|
: "${HOST_DEBUG:=""}"
|
||||||
|
: "${HOST_MODEL:=""}"
|
||||||
|
: "${HOST_SERIAL:=""}"
|
||||||
|
: "${GUEST_SERIAL:=""}"
|
||||||
|
|
||||||
|
# Sanitize variables
|
||||||
|
HOST_MAC=$(strip "$HOST_MAC")
|
||||||
|
HOST_MODEL=$(strip "$HOST_MODEL")
|
||||||
|
HOST_SERIAL=$(strip "$HOST_SERIAL")
|
||||||
|
GUEST_SERIAL=$(strip "$GUEST_SERIAL")
|
||||||
|
|
||||||
|
HOST_PID="$QEMU_DIR/host.pid"
|
||||||
|
HOST_API_SOCKET="$QEMU_DIR/qemu-host-api.sock"
|
||||||
|
HOST_AGENT_SOCKET="$QEMU_DIR/qemu-host-agent.sock"
|
||||||
|
|
||||||
|
validateHostMac() {
|
||||||
|
local m
|
||||||
|
|
||||||
|
if [ -z "$HOST_MAC" ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
HOST_MAC="${HOST_MAC//-/:}"
|
||||||
|
|
||||||
|
if [[ ${#HOST_MAC} == 12 ]]; then
|
||||||
|
m="$HOST_MAC"
|
||||||
|
HOST_MAC="${m:0:2}:${m:2:2}:${m:4:2}:${m:6:2}:${m:8:2}:${m:10:2}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ${#HOST_MAC} != 17 ]]; then
|
||||||
|
error "Invalid HOST_MAC address: '$HOST_MAC', should be 12 or 17 digits long!" && exit 28
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
buildHostArguments() {
|
||||||
|
|
||||||
|
HOST_ARGS=()
|
||||||
|
HOST_ARGS+=("-cpu=$CPU_CORES")
|
||||||
|
HOST_ARGS+=("-cpu_arch=$HOST_CPU")
|
||||||
|
HOST_ARGS+=("-api=$HOST_API_SOCKET")
|
||||||
|
HOST_ARGS+=("-addr=$HOST_AGENT_SOCKET")
|
||||||
|
|
||||||
|
[ -n "$HOST_MAC" ] && HOST_ARGS+=("-mac=$HOST_MAC")
|
||||||
|
[ -n "$HOST_MODEL" ] && HOST_ARGS+=("-model=$HOST_MODEL")
|
||||||
|
[ -n "$HOST_SERIAL" ] && HOST_ARGS+=("-hostsn=$HOST_SERIAL")
|
||||||
|
[ -n "$GUEST_SERIAL" ] && HOST_ARGS+=("-guestsn=$GUEST_SERIAL")
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
startHostBinary() {
|
||||||
|
|
||||||
|
local pid
|
||||||
|
|
||||||
|
rm -f -- "$HOST_PID" "$HOST_API_SOCKET" "$HOST_AGENT_SOCKET" || return 1
|
||||||
|
|
||||||
|
if enabled "$HOST_DEBUG"; then
|
||||||
|
set -x
|
||||||
|
./host.bin "${HOST_ARGS[@]}" &
|
||||||
|
{ set +x; } 2>/dev/null
|
||||||
|
pid=$!
|
||||||
|
echo
|
||||||
|
else
|
||||||
|
./host.bin "${HOST_ARGS[@]}" >/dev/null &
|
||||||
|
pid=$!
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$pid" > "$HOST_PID"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
waitForSocket() {
|
||||||
|
|
||||||
|
local socket="$1"
|
||||||
|
local exit_code="$2"
|
||||||
|
local pid cnt=0
|
||||||
|
|
||||||
|
while [ ! -S "$socket" ]; do
|
||||||
|
|
||||||
|
if ! read -r pid < "$HOST_PID" || ! isAlive "$pid"; then
|
||||||
|
error "qemu-host exited unexpectedly!"
|
||||||
|
exit "$exit_code"
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep 0.1
|
||||||
|
cnt=$((cnt + 1))
|
||||||
|
|
||||||
|
if (( cnt > 50 )); then
|
||||||
|
error "Failed to create qemu-host socket: $socket"
|
||||||
|
exit "$exit_code"
|
||||||
|
fi
|
||||||
|
|
||||||
|
done
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
configureSerialPorts() {
|
||||||
|
|
||||||
|
if enabled "${SHUTDOWN:-Y}" && interactive; then
|
||||||
|
|
||||||
|
CONSOLE_SOCKET="$QEMU_DIR/console.sock"
|
||||||
|
MONITOR_SOCKET="$QEMU_DIR/monitor.sock"
|
||||||
|
|
||||||
|
SERIAL_OPTS="-chardev socket,id=console0,path=$CONSOLE_SOCKET,reconnect-ms=1000 \
|
||||||
|
-serial chardev:console0 \
|
||||||
|
-chardev socket,id=monitor0,path=$MONITOR_SOCKET,server=on,wait=off \
|
||||||
|
-mon chardev=monitor0,mode=readline"
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
|
SERIAL_OPTS="-serial mon:stdio"
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
SERIAL_OPTS+=" \
|
||||||
|
-device virtio-serial-pci,id=virtio-serial0,bus=pcie.0,addr=0x3 \
|
||||||
|
-chardev socket,id=charchannel0,path=$HOST_AGENT_SOCKET,reconnect-ms=1000 \
|
||||||
|
-device virtserialport,bus=virtio-serial0.0,nr=1,chardev=charchannel0,id=channel0,name=vchannel"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
validateHostMac
|
||||||
|
|
||||||
|
buildHostArguments
|
||||||
|
startHostBinary
|
||||||
|
|
||||||
|
waitForSocket "$HOST_API_SOCKET" 58
|
||||||
|
waitForSocket "$HOST_AGENT_SOCKET" 59
|
||||||
|
|
||||||
|
configureSerialPorts
|
||||||
|
|
||||||
|
return 0
|
||||||
+152
@@ -0,0 +1,152 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
: "${WEB_PORT:="5000"}" # Webserver port
|
||||||
|
: "${WSD_PORT:="8004"}" # Websockets port
|
||||||
|
|
||||||
|
# Sanitize port variables
|
||||||
|
WEB_PORT=$(strip "$WEB_PORT")
|
||||||
|
WSD_PORT=$(strip "$WSD_PORT")
|
||||||
|
|
||||||
|
WEB_PID="/run/nginx.pid"
|
||||||
|
WSD_LOG="/var/log/websocketd.log"
|
||||||
|
WSD_PID="$QEMU_DIR/websocketd.pid"
|
||||||
|
|
||||||
|
prepareWebFiles() {
|
||||||
|
|
||||||
|
cp -r /var/www/* "$QEMU_DIR" || return 1
|
||||||
|
rm -f -- "$WSD_PID" "$WEB_PID" "$WSD_LOG" || return 1
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
configureWebPorts() {
|
||||||
|
|
||||||
|
if ! sed -i \
|
||||||
|
-e "s|listen 5000 default_server;|listen $WEB_PORT default_server;|g" \
|
||||||
|
-e "s|proxy_pass http://127.0.0.1:8004/;|proxy_pass http://127.0.0.1:$WSD_PORT/;|g" \
|
||||||
|
/etc/nginx/sites-enabled/web.conf; then
|
||||||
|
error "Failed to configure webserver ports!"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
configureIpv6Listen() {
|
||||||
|
|
||||||
|
if [ -f /proc/net/if_inet6 ] && [[ "$(cat /proc/sys/net/ipv6/conf/all/disable_ipv6 2>/dev/null)" != "1" ]]; then
|
||||||
|
|
||||||
|
if ! sed -i \
|
||||||
|
"s/listen $WEB_PORT default_server;/listen [::]:$WEB_PORT default_server ipv6only=off;/g" \
|
||||||
|
/etc/nginx/sites-enabled/web.conf; then
|
||||||
|
error "Failed to configure IPv6 webserver listener!"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
configureWebServer() {
|
||||||
|
|
||||||
|
mkdir -p /etc/nginx/sites-enabled
|
||||||
|
cp /etc/nginx/default.conf /etc/nginx/sites-enabled/web.conf
|
||||||
|
|
||||||
|
configureWebPorts || return 1
|
||||||
|
configureIpv6Listen || return 1
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
stopWebServer() {
|
||||||
|
|
||||||
|
local pid
|
||||||
|
|
||||||
|
if [ -s "$WEB_PID" ] && read -r pid < "$WEB_PID" && [ -n "$pid" ]; then
|
||||||
|
pKill "$pid" 2
|
||||||
|
|
||||||
|
if isAlive "$pid"; then
|
||||||
|
kill -9 -- "$pid" 2>/dev/null || :
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -f -- "$WEB_PID"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
startWebServer() {
|
||||||
|
|
||||||
|
# Start webserver
|
||||||
|
nginx -e stderr || return 1
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
stopWebsocketServer() {
|
||||||
|
|
||||||
|
local pid
|
||||||
|
|
||||||
|
if [ -s "$WSD_PID" ] && read -r pid < "$WSD_PID" && [ -n "$pid" ]; then
|
||||||
|
pKill "$pid" 2
|
||||||
|
|
||||||
|
if isAlive "$pid"; then
|
||||||
|
kill -9 -- "$pid" 2>/dev/null || :
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -f -- "$WSD_PID"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
startWebsocketServer() {
|
||||||
|
|
||||||
|
# Start websocket server
|
||||||
|
websocketd \
|
||||||
|
--address 127.0.0.1 \
|
||||||
|
--port="$WSD_PORT" \
|
||||||
|
/run/socket.sh \
|
||||||
|
>"$WSD_LOG" 2>&1 &
|
||||||
|
|
||||||
|
local pid=$!
|
||||||
|
|
||||||
|
if ! echo "$pid" > "$WSD_PID"; then
|
||||||
|
kill "$pid" 2>/dev/null || :
|
||||||
|
rm -f -- "$WSD_PID"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local i
|
||||||
|
for (( i = 1; i <= 5; i++ )); do
|
||||||
|
|
||||||
|
if ! isAlive "$pid"; then
|
||||||
|
rm -f -- "$WSD_PID"
|
||||||
|
[ -s "$WSD_LOG" ] && cat "$WSD_LOG" >&2
|
||||||
|
error "Failed to start websocket server!"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep 0.1
|
||||||
|
|
||||||
|
done
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
prepareWebFiles
|
||||||
|
|
||||||
|
html "Starting $APP for $ENGINE..."
|
||||||
|
|
||||||
|
disabled "${WEB:-}" && return 0
|
||||||
|
|
||||||
|
configureWebServer
|
||||||
|
|
||||||
|
if startWebServer && startWebsocketServer; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
stopWebsocketServer || :
|
||||||
|
stopWebServer || :
|
||||||
|
|
||||||
|
return 1
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
lastmsg=""
|
||||||
|
path="/run/shm/msg.html"
|
||||||
|
dir=$(dirname -- "$path")
|
||||||
|
name=$(basename -- "$path")
|
||||||
|
|
||||||
|
refresh() {
|
||||||
|
|
||||||
|
[ ! -f "$path" ] && return 0
|
||||||
|
[ ! -s "$path" ] && return 0
|
||||||
|
|
||||||
|
msg=$(< "$path") || return 0
|
||||||
|
msg="${msg%$'\n'}"
|
||||||
|
|
||||||
|
[ -z "$msg" ] && return 0
|
||||||
|
[[ "$msg" == "$lastmsg" ]] && return 0
|
||||||
|
|
||||||
|
lastmsg="$msg"
|
||||||
|
echo "s: $msg"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh
|
||||||
|
|
||||||
|
inotifywait \
|
||||||
|
-m -q \
|
||||||
|
-e close_write,moved_to,delete \
|
||||||
|
--format '%e %f' \
|
||||||
|
"$dir" |
|
||||||
|
while read -r event file; do
|
||||||
|
|
||||||
|
[[ "$file" == "$name" ]] || continue
|
||||||
|
|
||||||
|
case "${event,,}" in
|
||||||
|
"delete"* )
|
||||||
|
echo "c: vnc" ;;
|
||||||
|
"close_write"* | "moved_to"* )
|
||||||
|
refresh ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
done
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
# You can override this hook to execute a script before startup!
|
||||||
|
|
||||||
|
return 0
|
||||||
+536
@@ -0,0 +1,536 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
# Helper functions
|
||||||
|
|
||||||
|
info () { printf "%b%s%b" "\E[1;34m❯ \E[1;36m" "${1:-}" "\E[0m\n"; }
|
||||||
|
error () { printf "%b%s%b" "\E[1;31m❯ " "ERROR: ${1:-}" "\E[0m\n" >&2; }
|
||||||
|
warn () { printf "%b%s%b" "\E[1;31m❯ " "Warning: ${1:-}" "\E[0m\n" >&2; }
|
||||||
|
|
||||||
|
hasFlag() {
|
||||||
|
|
||||||
|
# Match a whitespace-delimited token in /proc/cpuinfo
|
||||||
|
grep -m1 '^flags[[:space:]]*:' /proc/cpuinfo | grep -Fqw -- "$1"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
hasFeature() {
|
||||||
|
|
||||||
|
# Match a whitespace-delimited token in /proc/cpuinfo
|
||||||
|
grep -m1 '^Features[[:space:]]*:' /proc/cpuinfo | grep -Fqw -- "$1"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
isAmdCpu() {
|
||||||
|
|
||||||
|
local vendor
|
||||||
|
vendor=$(awk -F ': *' '/^vendor_id/{print $2; exit}' /proc/cpuinfo)
|
||||||
|
|
||||||
|
[[ "$vendor" == "AuthenticAMD" ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
interactive() {
|
||||||
|
|
||||||
|
[ -t 0 ] && : 2>/dev/null </dev/tty >/dev/tty
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
strip() {
|
||||||
|
|
||||||
|
local value="${1:-}"
|
||||||
|
|
||||||
|
# Remove surrounding whitespace
|
||||||
|
value="${value#"${value%%[![:space:]]*}"}"
|
||||||
|
value="${value%"${value##*[![:space:]]}"}"
|
||||||
|
|
||||||
|
# Remove leading/trailing single/double quotes
|
||||||
|
value="${value%\"}"
|
||||||
|
value="${value#\"}"
|
||||||
|
value="${value%\'}"
|
||||||
|
value="${value#\'}"
|
||||||
|
|
||||||
|
# Remove surrounding whitespace again
|
||||||
|
value="${value#"${value%%[![:space:]]*}"}"
|
||||||
|
value="${value%"${value##*[![:space:]]}"}"
|
||||||
|
|
||||||
|
printf '%s' "$value"
|
||||||
|
}
|
||||||
|
|
||||||
|
enabled() {
|
||||||
|
|
||||||
|
local value
|
||||||
|
value=$(strip "${1:-}")
|
||||||
|
|
||||||
|
case "${value,,}" in
|
||||||
|
y|yes|true|1|on|enable|enabled) return 0 ;;
|
||||||
|
*) return 1 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
disabled() {
|
||||||
|
|
||||||
|
local value
|
||||||
|
value=$(strip "${1:-}")
|
||||||
|
|
||||||
|
case "${value,,}" in
|
||||||
|
n|no|none|false|0|off|disable|disabled) return 0 ;;
|
||||||
|
*) return 1 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
formatBytes() {
|
||||||
|
|
||||||
|
local result
|
||||||
|
|
||||||
|
if ! result=$(numfmt --to=iec --suffix=B "$1" | sed -r 's/([A-Z])/ \1/' | sed 's/ B/ bytes/g;'); then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local unit="${result//[0-9. ]}"
|
||||||
|
result="${result//[a-zA-Z ]/}"
|
||||||
|
|
||||||
|
if [[ "${2:-}" == "up" ]]; then
|
||||||
|
if [[ "$result" == *"."* ]]; then
|
||||||
|
result="${result%%.*}"
|
||||||
|
result=$((result+1))
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ "${2:-}" == "down" ]]; then
|
||||||
|
result="${result%%.*}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$result $unit"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
isAlive() {
|
||||||
|
|
||||||
|
local pid="$1"
|
||||||
|
[ -z "$pid" ] && return 1
|
||||||
|
|
||||||
|
if kill -0 "$pid" 2>/dev/null; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
waitPid() {
|
||||||
|
|
||||||
|
local i=0
|
||||||
|
local pid="$1"
|
||||||
|
local timeout="${2:-10}"
|
||||||
|
|
||||||
|
while [ -n "$pid" ] && isAlive "$pid"; do
|
||||||
|
sleep 0.2
|
||||||
|
i=$((i + 1))
|
||||||
|
(( i >= timeout * 5 )) && return 1
|
||||||
|
done
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
waitPidFile() {
|
||||||
|
|
||||||
|
local i=0 pid
|
||||||
|
local file="$1"
|
||||||
|
local timeout="${2:-10}"
|
||||||
|
|
||||||
|
[ ! -s "$file" ] && return 0
|
||||||
|
! read -r pid <"$file" && return 0
|
||||||
|
[ -z "$pid" ] && return 0
|
||||||
|
|
||||||
|
while [ -s "$file" ] && isAlive "$pid"; do
|
||||||
|
sleep 0.2
|
||||||
|
i=$((i + 1))
|
||||||
|
(( i >= timeout * 5 )) && return 1
|
||||||
|
done
|
||||||
|
|
||||||
|
rm -f -- "$file"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pKill() {
|
||||||
|
|
||||||
|
local pid="$1"
|
||||||
|
local timeout="${2:-10}"
|
||||||
|
|
||||||
|
{ kill -15 -- "$pid" || :; } 2>/dev/null
|
||||||
|
|
||||||
|
if ! waitPid "$pid" "$timeout"; then
|
||||||
|
warn "Timed out while waiting for PID $pid"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fWait() {
|
||||||
|
|
||||||
|
local i=0
|
||||||
|
local name="$1"
|
||||||
|
local timeout="${2:-10}"
|
||||||
|
|
||||||
|
[ -z "$name" ] && return 0
|
||||||
|
|
||||||
|
while pgrep -f -l "$name" >/dev/null; do
|
||||||
|
sleep 0.2
|
||||||
|
i=$((i + 1))
|
||||||
|
if (( i >= timeout * 5 )); then
|
||||||
|
warn "Timed out while waiting for process: $name"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fKill() {
|
||||||
|
|
||||||
|
local name="$1"
|
||||||
|
local timeout="${2:-10}"
|
||||||
|
|
||||||
|
[ -z "$name" ] && return 0
|
||||||
|
|
||||||
|
{ pkill -f "$name" || :; } 2>/dev/null
|
||||||
|
fWait "$name" "$timeout"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
sKill() {
|
||||||
|
|
||||||
|
local pid
|
||||||
|
local file="$1"
|
||||||
|
|
||||||
|
[ ! -s "$file" ] && return 0
|
||||||
|
! read -r pid <"$file" && return 0
|
||||||
|
[ -z "$pid" ] && return 0
|
||||||
|
|
||||||
|
if isAlive "$pid"; then
|
||||||
|
{ kill -15 -- "$pid" || :; } 2>/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
mKill() {
|
||||||
|
|
||||||
|
local timeout=10
|
||||||
|
local files=("$@")
|
||||||
|
|
||||||
|
for file in "${files[@]}"; do
|
||||||
|
sKill "$file"
|
||||||
|
done
|
||||||
|
|
||||||
|
for file in "${files[@]}"; do
|
||||||
|
if ! waitPidFile "$file" "$timeout"; then
|
||||||
|
warn "Timed out while waiting for PID file: $file"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
setOwner() {
|
||||||
|
|
||||||
|
local file="$1"
|
||||||
|
local dir uid gid
|
||||||
|
|
||||||
|
[ ! -f "$file" ] && return 1
|
||||||
|
|
||||||
|
dir=$(dirname -- "$file")
|
||||||
|
uid=$(stat -c '%u' "$dir") || return 1
|
||||||
|
gid=$(stat -c '%g' "$dir") || return 1
|
||||||
|
|
||||||
|
! chown "$uid:$gid" "$file" && return 1
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
makeDir() {
|
||||||
|
|
||||||
|
local path="$1"
|
||||||
|
local dir uid gid
|
||||||
|
|
||||||
|
[ -d "$path" ] && return 0
|
||||||
|
! mkdir -p "$path" && return 1
|
||||||
|
|
||||||
|
dir=$(dirname -- "$path")
|
||||||
|
|
||||||
|
if ! uid=$(stat -c '%u' "$dir") || ! gid=$(stat -c '%g' "$dir"); then
|
||||||
|
warn "failed to determine the owner for \"$path\"."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! chown "$uid:$gid" "$path"; then
|
||||||
|
warn "failed to set the owner for \"$path\"."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
stateFile() {
|
||||||
|
|
||||||
|
local name="$1"
|
||||||
|
local prefix="${2:-$PROCESS}"
|
||||||
|
|
||||||
|
[[ "$name" == */* ]] && printf '%s\n' "$name" && return 0
|
||||||
|
|
||||||
|
printf '%s/%s.%s\n' "$STORAGE" "$prefix" "$name"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
writeFile() {
|
||||||
|
|
||||||
|
local txt="$1"
|
||||||
|
local path="$2"
|
||||||
|
|
||||||
|
if ! printf '%s\n' "$txt" > "$path"; then
|
||||||
|
error "Failed to write file \"$path\" !"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! setOwner "$path"; then
|
||||||
|
warn "failed to set the owner for \"$path\"."
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
writeAtomic() {
|
||||||
|
|
||||||
|
local path="$1"
|
||||||
|
local content="$2"
|
||||||
|
local tmp="${path}.${BASHPID}.tmp"
|
||||||
|
|
||||||
|
if ! printf '%s\n' "$content" > "$tmp"; then
|
||||||
|
rm -f -- "$tmp"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! mv -f -- "$tmp" "$path"; then
|
||||||
|
rm -f -- "$tmp"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
readFile() {
|
||||||
|
|
||||||
|
local path="$1"
|
||||||
|
local value
|
||||||
|
|
||||||
|
[ -s "$path" ] || return 0
|
||||||
|
|
||||||
|
value=$(<"$path") || return 1
|
||||||
|
value="${value//[![:print:]]/}"
|
||||||
|
|
||||||
|
printf '%s\n' "$value"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
writeState() {
|
||||||
|
|
||||||
|
local name="$1"
|
||||||
|
local value="$2"
|
||||||
|
local prefix="${3:-$PROCESS}"
|
||||||
|
local path
|
||||||
|
|
||||||
|
[ -z "$value" ] && return 0
|
||||||
|
|
||||||
|
path=$(stateFile "$name" "$prefix") || return 1
|
||||||
|
writeFile "$value" "$path"
|
||||||
|
|
||||||
|
return $?
|
||||||
|
}
|
||||||
|
|
||||||
|
readState() {
|
||||||
|
|
||||||
|
local name="$1"
|
||||||
|
local prefix="${2:-$PROCESS}"
|
||||||
|
local path
|
||||||
|
|
||||||
|
path=$(stateFile "$name" "$prefix") || return 1
|
||||||
|
readFile "$path"
|
||||||
|
|
||||||
|
return $?
|
||||||
|
}
|
||||||
|
|
||||||
|
restoreState() {
|
||||||
|
|
||||||
|
local var="$1"
|
||||||
|
local name="$2"
|
||||||
|
local force="${3:-N}"
|
||||||
|
local prefix="${4:-$PROCESS}"
|
||||||
|
local value
|
||||||
|
|
||||||
|
if ! enabled "$force"; then
|
||||||
|
[ -z "${!var:-}" ] || return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
value=$(readState "$name" "$prefix") || return 1
|
||||||
|
[ -n "$value" ] || return 0
|
||||||
|
|
||||||
|
printf -v "$var" '%s' "$value" || return 1
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
escape () {
|
||||||
|
|
||||||
|
local s=${1//&/\&}
|
||||||
|
s=${s//</\<}
|
||||||
|
s=${s//>/\>}
|
||||||
|
s=${s//'"'/\"}
|
||||||
|
|
||||||
|
printf -- %s "$s"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
escapeXML() {
|
||||||
|
|
||||||
|
printf '%s' "$1" | sed \
|
||||||
|
-e 's/&/\&/g' \
|
||||||
|
-e 's/</\</g' \
|
||||||
|
-e 's/>/\>/g' \
|
||||||
|
-e 's/"/\"/g' \
|
||||||
|
-e "s/'/\'/g"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
html() {
|
||||||
|
|
||||||
|
local title
|
||||||
|
local body
|
||||||
|
local script="${2:-}"
|
||||||
|
local footer
|
||||||
|
|
||||||
|
title=$(escape "$APP")
|
||||||
|
title="<title>$title</title>"
|
||||||
|
footer=$(escape "$FOOTER1")
|
||||||
|
|
||||||
|
body=$(escape "$1")
|
||||||
|
if [[ "$body" == *"..." ]]; then
|
||||||
|
body="<p class=\"loading\">${body/.../}</p>"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local HTML
|
||||||
|
HTML=$(<"$TEMPLATE")
|
||||||
|
HTML="${HTML/\[1\]/$title}"
|
||||||
|
HTML="${HTML/\[2\]/$script}"
|
||||||
|
HTML="${HTML/\[3\]/$body}"
|
||||||
|
HTML="${HTML/\[4\]/$footer}"
|
||||||
|
HTML="${HTML/\[5\]/$FOOTER2}"
|
||||||
|
|
||||||
|
writeAtomic "$PAGE" "$HTML" || return 1
|
||||||
|
writeAtomic "$INFO" "$body" || return 1
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
cpu() {
|
||||||
|
|
||||||
|
local ret
|
||||||
|
local cpu=""
|
||||||
|
|
||||||
|
ret=$(lscpu)
|
||||||
|
|
||||||
|
if grep -qi "model name" <<< "$ret"; then
|
||||||
|
cpu=$(echo "$ret" | grep -m 1 -i 'model name' | cut -f 2 -d ":" | awk '{$1=$1}1' | sed 's# @.*##g' | sed s/"(R)"//g | sed 's/[^[:alnum:] ]\+/ /g' | sed 's/ */ /g')
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "${cpu// /}" ] && grep -qi "model:" <<< "$ret"; then
|
||||||
|
cpu=$(echo "$ret" | grep -m 1 -i 'model:' | cut -f 2 -d ":" | awk '{$1=$1}1' | sed 's# @.*##g' | sed s/"(R)"//g | sed 's/[^[:alnum:] ]\+/ /g' | sed 's/ */ /g')
|
||||||
|
fi
|
||||||
|
|
||||||
|
cpu="${cpu// CPU/}"
|
||||||
|
cpu="${cpu// [0-9][0-9][0-9] Core}"
|
||||||
|
cpu="${cpu// [0-9][0-9] Core}"
|
||||||
|
cpu="${cpu// [0-9] Core}"
|
||||||
|
cpu="${cpu//[0-9][0-9]th Gen }"
|
||||||
|
cpu="${cpu//[0-9]th Gen }"
|
||||||
|
cpu="${cpu// Processor/}"
|
||||||
|
cpu="${cpu// Quad core/}"
|
||||||
|
cpu="${cpu// Dual core/}"
|
||||||
|
cpu="${cpu// Octa core/}"
|
||||||
|
cpu="${cpu// Hexa core/}"
|
||||||
|
cpu="${cpu// Core TM/ Core}"
|
||||||
|
cpu="${cpu// with Radeon Graphics/}"
|
||||||
|
cpu="${cpu// with Radeon Vega Graphics/}"
|
||||||
|
cpu="${cpu// with Radeon Vega Mobile Gfx/}"
|
||||||
|
cpu="${cpu// w Radeon [0-9][0-9][0-9]M Graphics/}"
|
||||||
|
|
||||||
|
[ -z "${cpu// /}" ] && cpu="Unknown"
|
||||||
|
|
||||||
|
echo "$cpu"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
getCountry() {
|
||||||
|
|
||||||
|
local url=$1
|
||||||
|
local query=$2
|
||||||
|
local json result
|
||||||
|
|
||||||
|
{ json=$(curl -m 5 -H "Accept: application/json" -sfk "$url"); local rc=$?; } || :
|
||||||
|
(( rc != 0 )) && return 0
|
||||||
|
|
||||||
|
{ result=$(echo "$json" | jq -r "$query" 2> /dev/null); rc=$?; } || :
|
||||||
|
(( rc != 0 )) && return 0
|
||||||
|
|
||||||
|
[[ ${#result} -ne 2 ]] && return 0
|
||||||
|
[[ "${result^^}" == "XX" ]] && return 0
|
||||||
|
|
||||||
|
COUNTRY="${result^^}"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
setCountry() {
|
||||||
|
|
||||||
|
[[ "${TZ,,}" == "asia/harbin" ]] && COUNTRY="CN"
|
||||||
|
[[ "${TZ,,}" == "asia/beijing" ]] && COUNTRY="CN"
|
||||||
|
[[ "${TZ,,}" == "asia/urumqi" ]] && COUNTRY="CN"
|
||||||
|
[[ "${TZ,,}" == "asia/kashgar" ]] && COUNTRY="CN"
|
||||||
|
[[ "${TZ,,}" == "asia/shanghai" ]] && COUNTRY="CN"
|
||||||
|
[[ "${TZ,,}" == "asia/chongqing" ]] && COUNTRY="CN"
|
||||||
|
|
||||||
|
[ -z "$COUNTRY" ] && getCountry "https://api.ipapi.is" ".location.country_code"
|
||||||
|
[ -z "$COUNTRY" ] && getCountry "https://ifconfig.co/json" ".country_iso"
|
||||||
|
[ -z "$COUNTRY" ] && getCountry "https://api.ip2location.io" ".country_code"
|
||||||
|
[ -z "$COUNTRY" ] && getCountry "https://ipinfo.io/json" ".country"
|
||||||
|
[ -z "$COUNTRY" ] && getCountry "https://api.ipquery.io/?format=json" ".location.country_code"
|
||||||
|
[ -z "$COUNTRY" ] && getCountry "https://api.myip.com" ".cc"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
addPackage() {
|
||||||
|
|
||||||
|
local pkg=$1
|
||||||
|
local desc=$2
|
||||||
|
|
||||||
|
if apt-mark showinstall | grep -qx "$pkg"; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
MSG="Installing $desc..."
|
||||||
|
info "$MSG" && html "$MSG"
|
||||||
|
|
||||||
|
[ -z "$COUNTRY" ] && setCountry
|
||||||
|
|
||||||
|
if [[ "${COUNTRY^^}" == "CN" ]]; then
|
||||||
|
sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list.d/debian.sources
|
||||||
|
fi
|
||||||
|
|
||||||
|
DEBIAN_FRONTEND=noninteractive apt-get -qq update || return 1
|
||||||
|
DEBIAN_FRONTEND=noninteractive apt-get -qq --no-install-recommends -y install "$pkg" > /dev/null || return 1
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
server {
|
||||||
|
listen 5000 default_server;
|
||||||
|
|
||||||
|
autoindex on;
|
||||||
|
tcp_nodelay on;
|
||||||
|
server_tokens off;
|
||||||
|
absolute_redirect off;
|
||||||
|
|
||||||
|
error_log /dev/null;
|
||||||
|
access_log /dev/null;
|
||||||
|
|
||||||
|
include /etc/nginx/mime.types;
|
||||||
|
|
||||||
|
gzip on;
|
||||||
|
gzip_vary on;
|
||||||
|
gzip_proxied any;
|
||||||
|
gzip_comp_level 5;
|
||||||
|
gzip_min_length 500;
|
||||||
|
gzip_disable "msie6";
|
||||||
|
gzip_types text/css text/javascript text/xml text/plain text/x-component application/javascript application/json application/xml application/rss+xml font/truetype font/opentype application/vnd.ms-fontobject image/svg+xml;
|
||||||
|
|
||||||
|
add_header Cache-Control "no-cache";
|
||||||
|
|
||||||
|
location / {
|
||||||
|
|
||||||
|
root /run/shm;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
location /status {
|
||||||
|
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
|
||||||
|
proxy_set_header Connection 'upgrade';
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_read_timeout 3600s;
|
||||||
|
proxy_send_timeout 3600s;
|
||||||
|
|
||||||
|
proxy_pass http://127.0.0.1:8004/;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
body {
|
||||||
|
color: white;
|
||||||
|
background-color: #125bdb;
|
||||||
|
font-smoothing: antialiased;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
font-family: Verdana, Geneva, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
#info {
|
||||||
|
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
#content {
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
margin-top: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
width: 98%;
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0px;
|
||||||
|
height: 40px;
|
||||||
|
text-align: center;
|
||||||
|
color: #0c8aeb;
|
||||||
|
text-shadow: 0 0 1px #0c8aeb;
|
||||||
|
}
|
||||||
|
|
||||||
|
#empty {
|
||||||
|
height: 40px;
|
||||||
|
/* Same height as footer */
|
||||||
|
}
|
||||||
|
|
||||||
|
a,
|
||||||
|
a:hover,
|
||||||
|
a:active,
|
||||||
|
a:visited {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer a:link,
|
||||||
|
footer a:visited,
|
||||||
|
footer a:active {
|
||||||
|
color: #0c8aeb;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer a:hover {
|
||||||
|
color: #73e6ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading:after {
|
||||||
|
content: " .";
|
||||||
|
animation: dots 1s steps(5, end) infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes dots {
|
||||||
|
|
||||||
|
0%,
|
||||||
|
20% {
|
||||||
|
color: rgba(0, 0, 0, 0);
|
||||||
|
text-shadow: 0.25em 0 0 rgba(0, 0, 0, 0), 0.5em 0 0 rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
40% {
|
||||||
|
color: white;
|
||||||
|
text-shadow: 0.25em 0 0 rgba(0, 0, 0, 0), 0.5em 0 0 rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
60% {
|
||||||
|
text-shadow: 0.25em 0 0 white, 0.5em 0 0 rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
80%,
|
||||||
|
100% {
|
||||||
|
text-shadow: 0.25em 0 0 white, 0.5em 0 0 white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner_LWk7 {
|
||||||
|
animation: spinner_GWy6 1.2s linear infinite, spinner_BNNO 1.2s linear infinite
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner_yOMU {
|
||||||
|
animation: spinner_GWy6 1.2s linear infinite, spinner_pVqn 1.2s linear infinite;
|
||||||
|
animation-delay: .15s
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner_KS4S {
|
||||||
|
animation: spinner_GWy6 1.2s linear infinite, spinner_6uKB 1.2s linear infinite;
|
||||||
|
animation-delay: .3s
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner_zVee {
|
||||||
|
animation: spinner_GWy6 1.2s linear infinite, spinner_Qw4x 1.2s linear infinite;
|
||||||
|
animation-delay: .45s
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spinner_GWy6 {
|
||||||
|
|
||||||
|
0%,
|
||||||
|
50% {
|
||||||
|
width: 9px;
|
||||||
|
height: 9px
|
||||||
|
}
|
||||||
|
|
||||||
|
10% {
|
||||||
|
width: 11px;
|
||||||
|
height: 11px
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spinner_BNNO {
|
||||||
|
|
||||||
|
0%,
|
||||||
|
50% {
|
||||||
|
x: 1.5px;
|
||||||
|
y: 1.5px
|
||||||
|
}
|
||||||
|
|
||||||
|
10% {
|
||||||
|
x: .5px;
|
||||||
|
y: .5px
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spinner_pVqn {
|
||||||
|
|
||||||
|
0%,
|
||||||
|
50% {
|
||||||
|
x: 13.5px;
|
||||||
|
y: 1.5px
|
||||||
|
}
|
||||||
|
|
||||||
|
10% {
|
||||||
|
x: 12.5px;
|
||||||
|
y: .5px
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spinner_6uKB {
|
||||||
|
|
||||||
|
0%,
|
||||||
|
50% {
|
||||||
|
x: 13.5px;
|
||||||
|
y: 13.5px
|
||||||
|
}
|
||||||
|
|
||||||
|
10% {
|
||||||
|
x: 12.5px;
|
||||||
|
y: 12.5px
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spinner_Qw4x {
|
||||||
|
|
||||||
|
0%,
|
||||||
|
50% {
|
||||||
|
x: 1.5px;
|
||||||
|
y: 13.5px
|
||||||
|
}
|
||||||
|
|
||||||
|
10% {
|
||||||
|
x: .5px;
|
||||||
|
y: 12.5px
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<svg id="Capa_1" enable-background="new 0 0 511.962 511.962" height="512" viewBox="0 0 511.962 511.962" width="512" xmlns="http://www.w3.org/2000/svg"><g><path d="m489.965 120.063c0-5.77-3.31-11.028-8.512-13.524l-218.984-105.063c-4.102-1.967-8.875-1.967-12.977 0l-218.985 105.063c-5.202 2.496-8.511 7.755-8.511 13.524l-.003 271.834c0 5.77 3.31 11.028 8.512 13.524l218.989 105.064c2.051.983 4.27 1.476 6.488 1.476 2.219 0 4.438-.492 6.488-1.476l218.989-105.064c5.202-2.496 8.512-7.755 8.512-13.524z" fill="#4e6ba6"/><path d="m489.965 120.063c0-5.77-3.31-11.028-8.512-13.524l-218.984-105.063c-2.051-.984-4.269-1.476-6.488-1.476v511.962c2.219 0 4.438-.492 6.488-1.476l218.989-105.064c5.202-2.496 8.512-7.755 8.512-13.524z" fill="#28487a"/><path d="m425.812 160.441c0-2.27-.519-4.457-1.457-6.432l-336.701-.095c-.967 1.999-1.504 4.22-1.504 6.526l-.002 191.081c0 5.769 3.31 11.028 8.512 13.524l154.833 74.285c2.051.983 4.27 1.476 6.488 1.476 2.219 0 4.438-.492 6.488-1.476l154.834-74.285c5.202-2.496 8.512-7.755 8.512-13.524z" fill="#8dc2eb"/><path d="m424.354 154.009h-168.373v286.798c2.219 0 4.438-.492 6.488-1.476l154.834-74.285c5.202-2.496 8.512-7.755 8.512-13.524l-.003-191.081c0-2.27-.52-4.458-1.458-6.432z" fill="#5e9ff6"/><path d="m417.3 146.916-154.831-74.284c-4.102-1.967-8.875-1.967-12.977 0l-154.831 74.284c-3.122 1.498-5.555 3.996-7.007 6.998l168.328 80.812 168.374-80.717c-1.448-3.044-3.9-5.579-7.056-7.093z" fill="#ecf9fd"/><path d="m417.3 146.916-154.831-74.284c-2.051-.983-4.27-1.476-6.488-1.476v163.569l168.374-80.717c-1.447-3.043-3.899-5.578-7.055-7.092z" fill="#d9f3fc"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 1.6 KiB |
@@ -0,0 +1,34 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
[1]
|
||||||
|
<meta http-equiv="Cache-Control" content="no-cache" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="css/style.css" />
|
||||||
|
<link rel="icon" href="img/favicon.svg" type="image/x-icon">
|
||||||
|
[2]
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="page">
|
||||||
|
<div id="content">
|
||||||
|
<svg id="spinner" width="64" height="64" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect class="spinner_LWk7" fill="#0c8aeb" x="1.5" y="1.5" rx="1" width="9" height="9"/>
|
||||||
|
<rect class="spinner_yOMU" fill="#0c8aeb" x="13.5" y="1.5" rx="1" width="9" height="9"/>
|
||||||
|
<rect class="spinner_KS4S" fill="#0c8aeb" x="13.5" y="13.5" rx="1" width="9" height="9"/>
|
||||||
|
<rect class="spinner_zVee" fill="#0c8aeb" x="1.5" y="13.5" rx="1" width="9" height="9"/>
|
||||||
|
</svg>
|
||||||
|
<h1 id="info">[3]</h1>
|
||||||
|
</div>
|
||||||
|
<div id="empty">
|
||||||
|
</div>
|
||||||
|
<footer id="footer">
|
||||||
|
[4]<br />
|
||||||
|
[5]
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
<script type="text/javascript" src="js/script.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
var timer;
|
||||||
|
var request;
|
||||||
|
var booting = false;
|
||||||
|
var interval = 1000;
|
||||||
|
|
||||||
|
function abortRequest() {
|
||||||
|
|
||||||
|
if (!request) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
request.onreadystatechange = null;
|
||||||
|
request.abort();
|
||||||
|
request = null;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getInfo() {
|
||||||
|
|
||||||
|
var url = "msg.html";
|
||||||
|
|
||||||
|
try {
|
||||||
|
abortRequest();
|
||||||
|
|
||||||
|
if (window.XMLHttpRequest) {
|
||||||
|
request = new XMLHttpRequest();
|
||||||
|
} else {
|
||||||
|
throw new Error("XMLHttpRequest not available!");
|
||||||
|
}
|
||||||
|
|
||||||
|
request.onreadystatechange = processInfo;
|
||||||
|
request.open("GET", url, true);
|
||||||
|
request.send();
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
setError("Error: " + e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getURL() {
|
||||||
|
|
||||||
|
var protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
|
||||||
|
var path = window.location.pathname.replace(/[^/]*$/, '').replace(/\/$/, '');
|
||||||
|
|
||||||
|
return protocol + "//" + window.location.host + path;
|
||||||
|
}
|
||||||
|
|
||||||
|
function processMsg(msg) {
|
||||||
|
|
||||||
|
if (msg.toLowerCase().indexOf("href=") !== -1) {
|
||||||
|
var div = document.createElement("div");
|
||||||
|
div.innerHTML = msg;
|
||||||
|
var url = div.querySelector("a").href;
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.assign(url);
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
setInfo(msg);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function processInfo() {
|
||||||
|
try {
|
||||||
|
|
||||||
|
if (request.readyState != 4) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var response = request;
|
||||||
|
request = null;
|
||||||
|
|
||||||
|
var msg = response.responseText;
|
||||||
|
if (msg == null || msg.length == 0) {
|
||||||
|
|
||||||
|
if (booting) {
|
||||||
|
schedule();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.location.reload();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var notFound = (response.status == 404);
|
||||||
|
|
||||||
|
if (response.status == 200) {
|
||||||
|
if (msg.toLowerCase().indexOf("<html>") !== -1) {
|
||||||
|
notFound = true;
|
||||||
|
} else {
|
||||||
|
processMsg(msg);
|
||||||
|
if (msg.toLowerCase().indexOf("href=") == -1) {
|
||||||
|
schedule();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notFound) {
|
||||||
|
setInfo("Connecting to web portal", true);
|
||||||
|
reload();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
setError("Error: Received statuscode " + response.status);
|
||||||
|
return false;
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
setError("Error: " + e.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractContent(s) {
|
||||||
|
var span = document.createElement('span');
|
||||||
|
span.innerHTML = s;
|
||||||
|
return span.textContent || span.innerText;
|
||||||
|
};
|
||||||
|
|
||||||
|
function setInfo(msg, loading, error) {
|
||||||
|
try {
|
||||||
|
|
||||||
|
if (msg == null || msg.length == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.includes("Booting ")) {
|
||||||
|
booting = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var el = document.getElementById("info");
|
||||||
|
|
||||||
|
if (el.innerText == msg || el.innerHTML == msg) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var spin = document.getElementById("spinner");
|
||||||
|
|
||||||
|
error = !!error;
|
||||||
|
if (!error) {
|
||||||
|
spin.style.visibility = 'visible';
|
||||||
|
} else {
|
||||||
|
spin.style.visibility = 'hidden';
|
||||||
|
}
|
||||||
|
|
||||||
|
var p = "<p class=\"loading\">";
|
||||||
|
loading = !!loading;
|
||||||
|
if (loading) {
|
||||||
|
msg = p + msg + "</p>";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.includes(p)) {
|
||||||
|
if (el.innerHTML.includes(p)) {
|
||||||
|
el.getElementsByClassName('loading')[0].textContent = extractContent(msg);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
el.innerHTML = msg;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
console.log("Error: " + e.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setError(text) {
|
||||||
|
console.warn(text);
|
||||||
|
return setInfo(text, false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function schedule() {
|
||||||
|
|
||||||
|
clearTimeout(timer);
|
||||||
|
timer = setTimeout(getInfo, interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
function reload() {
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.reload();
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function connect() {
|
||||||
|
|
||||||
|
var wsUrl = getURL() + "/status";
|
||||||
|
var ws = new WebSocket(wsUrl);
|
||||||
|
|
||||||
|
ws.onmessage = function(e) {
|
||||||
|
|
||||||
|
var pos = e.data.indexOf(":");
|
||||||
|
var cmd = e.data.substring(0, pos);
|
||||||
|
var msg = e.data.substring(pos + 2);
|
||||||
|
|
||||||
|
switch (cmd) {
|
||||||
|
case "s":
|
||||||
|
|
||||||
|
var aborted = abortRequest();
|
||||||
|
|
||||||
|
processMsg(msg);
|
||||||
|
|
||||||
|
if (aborted &&
|
||||||
|
msg.toLowerCase().indexOf("href=") == -1) {
|
||||||
|
schedule();
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "e":
|
||||||
|
|
||||||
|
if (abortRequest()) {
|
||||||
|
schedule();
|
||||||
|
}
|
||||||
|
|
||||||
|
setError(msg);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
console.warn("Unknown event: " + cmd);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onclose = function(e) {
|
||||||
|
setTimeout(function() {
|
||||||
|
connect();
|
||||||
|
}, interval);
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onerror = function(e) {
|
||||||
|
ws.close();
|
||||||
|
if (!booting) {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
schedule();
|
||||||
|
connect();
|
||||||
Reference in New Issue
Block a user