mirror of
https://github.com/vdsm/virtual-dsm.git
synced 2026-08-29 04:07:02 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47af1d07f4 | ||
|
|
4660512f12 | ||
|
|
6d37a0426b | ||
|
|
604cad0207 | ||
|
|
3ad7e78787 | ||
|
|
5f908488ef | ||
|
|
dcdc796e59 | ||
|
|
087d876eaf | ||
|
|
25a7d55cdd | ||
|
|
9683b195f3 | ||
|
|
f810576651 | ||
|
|
5fd954bea4 | ||
|
|
768b8fa51a | ||
|
|
b909555ef3 | ||
|
|
a77026eafb | ||
|
|
e8ef357ed2 | ||
|
|
a16c27ecce | ||
|
|
1917eae0ae | ||
|
|
d5bb90f012 | ||
|
|
d854ae811c | ||
|
|
03993aa773 | ||
|
|
b39ff267f2 | ||
|
|
f7870d1693 | ||
|
|
c648338a18 | ||
|
|
9fe52c874e | ||
|
|
c5eae88dfc | ||
|
|
1d0ac30ae1 | ||
|
|
c355cbe5dc | ||
|
|
6f69eb5c68 | ||
|
|
07595976b2 | ||
|
|
ec8440db03 | ||
|
|
06ddd19d1a | ||
|
|
04cd929c9c | ||
|
|
c64046411b | ||
|
|
cb8f440564 | ||
|
|
8b6624cf2f | ||
|
|
c70b93c2b6 | ||
|
|
4c700e87fe | ||
|
|
030255004f | ||
|
|
e64f9f2c39 | ||
|
|
537fc97d53 | ||
|
|
438e992653 | ||
|
|
615fad2a0d | ||
|
|
aac543e5c7 | ||
|
|
fec4f876e7 | ||
|
|
dced815c06 | ||
|
|
51d6f3aeca | ||
|
|
4544620d48 | ||
|
|
de8468161f | ||
|
|
18d6be8210 | ||
|
|
e5b8cf3bf8 | ||
|
|
ec12039f43 | ||
|
|
3c7c3ca1b1 | ||
|
|
2b27f32cd4 | ||
|
|
55d1d50284 | ||
|
|
784b73b5b5 | ||
|
|
85f00bb9cc | ||
|
|
efe8732e47 | ||
|
|
f6871dd61b | ||
|
|
a713b728ff | ||
|
|
0d74c6ac80 | ||
|
|
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 | ||
|
|
0cd1ddf0a1 | ||
|
|
98245a1efe | ||
|
|
f425c869c6 | ||
|
|
f5b8c2a2ef | ||
|
|
f44584261e | ||
|
|
4134d9e3d3 | ||
|
|
db47f561d3 | ||
|
|
895bc04a57 | ||
|
|
d9e882fce4 | ||
|
|
478b6af755 | ||
|
|
fecd4052fc | ||
|
|
0b8306d827 | ||
|
|
c9d0688424 | ||
|
|
413a089e02 | ||
|
|
01e23f22fb | ||
|
|
308a764bb0 | ||
|
|
5ad5f8a8ef | ||
|
|
3c342a05aa | ||
|
|
d793921bcf | ||
|
|
5365a9ed4e | ||
|
|
7a55c650d0 | ||
|
|
ede42b3647 | ||
|
|
8e41b4e567 | ||
|
|
899687d3f2 | ||
|
|
2d97bc1cef | ||
|
|
538d7f0195 | ||
|
|
dcf95a8591 | ||
|
|
7f7272b7c8 | ||
|
|
c30248f93e | ||
|
|
cf90c9da1f | ||
|
|
461b5598a9 | ||
|
|
47ed2e8bac | ||
|
|
1c78e3c8b1 | ||
|
|
9f17dfa949 | ||
|
|
3de29b6c00 | ||
|
|
037d52957a | ||
|
|
c7ccc912b5 | ||
|
|
72cbf87986 | ||
|
|
317024d327 | ||
|
|
121c487383 | ||
|
|
fd92b60218 | ||
|
|
771429c5d3 | ||
|
|
674b6e5bda | ||
|
|
7c65e2740f | ||
|
|
d462c6d7a2 | ||
|
|
d5637e8da0 | ||
|
|
1b75bd3c44 | ||
|
|
2551413040 | ||
|
|
44d3e23545 | ||
|
|
d340361320 | ||
|
|
f8f8c16200 | ||
|
|
47fd7931eb | ||
|
|
7ac9c242da | ||
|
|
78d330055f | ||
|
|
06cf0a4edc | ||
|
|
e26c208cc7 | ||
|
|
c2f4823d8b | ||
|
|
ca01bf8cb9 | ||
|
|
a5b2b655dd | ||
|
|
0f2f3b2ea8 | ||
|
|
0705c1c21b | ||
|
|
e4de05ce88 | ||
|
|
a24b62ae67 | ||
|
|
b5a9361b68 | ||
|
|
0e35e4a6d9 | ||
|
|
208a6e6636 | ||
|
|
3fec9bf5ef | ||
|
|
8d8efeb341 | ||
|
|
e9f27899a6 | ||
|
|
0c1c422758 | ||
|
|
f56c523627 | ||
|
|
66879b5a6a | ||
|
|
b4eef6161b | ||
|
|
cf38b1f237 | ||
|
|
2c09811365 | ||
|
|
761f5babfc | ||
|
|
26be942a74 | ||
|
|
9c6aeca709 | ||
|
|
81b0bba667 | ||
|
|
e2c00a2e44 | ||
|
|
7bb33f26cd | ||
|
|
63b2d703a4 | ||
|
|
a97dfbfdf2 | ||
|
|
b255cb03e8 | ||
|
|
7a79ff1d2d | ||
|
|
06fda133ed | ||
|
|
acedd1cdcf | ||
|
|
96083fddb3 | ||
|
|
5d7604a205 | ||
|
|
24d44924fe | ||
|
|
db840db76f | ||
|
|
38148641c0 | ||
|
|
5941eae237 | ||
|
|
5fa117c4d8 | ||
|
|
28fc911f00 | ||
|
|
c8eb659c3c | ||
|
|
16ce7c07cf | ||
|
|
f745e5e09d | ||
|
|
16b823f69b | ||
|
|
d69cceba8d | ||
|
|
c2cd9f52f4 | ||
|
|
0d2a222af0 | ||
|
|
868c7d1661 | ||
|
|
b6ca62da34 | ||
|
|
da4555925b | ||
|
|
8c6f7e49ef | ||
|
|
06829aac67 | ||
|
|
e5b7ade0d0 | ||
|
|
c1de32eff6 | ||
|
|
c2dc0cd3b6 | ||
|
|
e2a4ab7515 | ||
|
|
d94a9d0966 | ||
|
|
6d9c281add | ||
|
|
da51c1df1f | ||
|
|
97918abdb0 | ||
|
|
6a8093a3f4 | ||
|
|
0bd0806ceb | ||
|
|
386c2ccc45 | ||
|
|
ff6a3b8776 | ||
|
|
f290fb7031 | ||
|
|
b4e19ccc23 | ||
|
|
e3c514f8dc | ||
|
|
101b509f81 | ||
|
|
6a5301b3f2 | ||
|
|
cad5e5d5c2 | ||
|
|
024170d190 | ||
|
|
0ce14f6c2d | ||
|
|
272e491281 | ||
|
|
0518e1100b | ||
|
|
8aa797b557 | ||
|
|
4356e5ebf9 | ||
|
|
502c068d30 | ||
|
|
d5e3ed8907 | ||
|
|
f0e5a61e73 | ||
|
|
b07577b60d | ||
|
|
4571fe9c4f | ||
|
|
9f6fbc3f7c | ||
|
|
bbc033f032 | ||
|
|
160e5a07be | ||
|
|
0cca2a7079 | ||
|
|
64257d1a6f | ||
|
|
d6dde093b2 | ||
|
|
d1957f5eb7 | ||
|
|
f3f9a9f480 | ||
|
|
15d67203c9 | ||
|
|
c33c63114e | ||
|
|
80169e6b50 | ||
|
|
61f733fe66 | ||
|
|
03da1f38f4 | ||
|
|
8543ea5dad | ||
|
|
36ebb950eb | ||
|
|
76ad3ff502 | ||
|
|
4673feb282 | ||
|
|
df6436bf6a | ||
|
|
d904ebfc85 |
@@ -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
|
||||
.devcontainer
|
||||
.git
|
||||
.github
|
||||
.gitignore
|
||||
@@ -6,7 +7,10 @@
|
||||
.gitmodules
|
||||
Dockerfile
|
||||
Dockerfile.archive
|
||||
compose.yml
|
||||
compose.yaml
|
||||
docker-compose.yml
|
||||
docker-compose.yaml
|
||||
|
||||
*.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
|
||||
@@ -1,10 +1,21 @@
|
||||
version: 2
|
||||
|
||||
updates:
|
||||
- package-ecosystem: docker
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
cooldown:
|
||||
default-days: 7
|
||||
ignore:
|
||||
- dependency-name: "*"
|
||||
update-types:
|
||||
- version-update:semver-minor
|
||||
- version-update:semver-patch
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
{
|
||||
"extends": [
|
||||
"config:base",
|
||||
":disableDependencyDashboard"
|
||||
]
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": ["config:recommended", ":disableDependencyDashboard"]
|
||||
}
|
||||
|
||||
+52
-81
@@ -2,17 +2,10 @@ name: Build
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths-ignore:
|
||||
- '**/*.md'
|
||||
- '**/*.yml'
|
||||
- '.gitignore'
|
||||
- '.dockerignore'
|
||||
- '.github/**'
|
||||
- '.github/workflows/**'
|
||||
- 'Dockerfile'
|
||||
|
||||
concurrency:
|
||||
group: build
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
shellcheck:
|
||||
@@ -23,101 +16,67 @@ jobs:
|
||||
needs: shellcheck
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: write
|
||||
packages: write
|
||||
contents: read
|
||||
steps:
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
-
|
||||
name: Prepare Docker build
|
||||
id: prepare
|
||||
run: |
|
||||
|
||||
PLATFORMS="linux/amd64,linux/arm64"
|
||||
VERSION="${{ vars.MAJOR }}.${{ vars.MINOR }}"
|
||||
|
||||
TAGS=()
|
||||
TAGS=("${{ github.repository }}:latest")
|
||||
TAGS+=("${{ github.repository }}:${VERSION}")
|
||||
TAGS+=("ghcr.io/${{ github.repository }}:latest")
|
||||
TAGS+=("ghcr.io/${{ github.repository }}:${VERSION}")
|
||||
|
||||
echo "tags=${TAGS[@]}" >> $GITHUB_OUTPUT
|
||||
echo "version=${VERSION}" >> $GITHUB_OUTPUT
|
||||
echo "docker_platforms=${PLATFORMS}" >> $GITHUB_OUTPUT
|
||||
echo "build_date=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT
|
||||
name: Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v6
|
||||
with:
|
||||
context: git
|
||||
images: |
|
||||
${{ secrets.DOCKERHUB_REPO }}
|
||||
ghcr.io/${{ github.repository }}
|
||||
tags: |
|
||||
type=raw,value=latest,priority=100
|
||||
type=raw,value=${{ vars.MAJOR }}.${{ vars.MINOR }}
|
||||
labels: |
|
||||
org.opencontainers.image.title=${{ vars.NAME }}
|
||||
env:
|
||||
DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
-
|
||||
name: Login into Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
-
|
||||
name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
-
|
||||
-
|
||||
name: Build Docker image
|
||||
run: |
|
||||
|
||||
TAGS=(${{ steps.prepare.outputs.tags }})
|
||||
|
||||
echo "Build date: ${{ steps.prepare.outputs.build_date }}"
|
||||
echo "Docker platform: ${{ steps.prepare.outputs.docker_platforms }}"
|
||||
echo "Tags: ${{ steps.prepare.outputs.tags }}"
|
||||
|
||||
docker buildx build --platform ${{ steps.prepare.outputs.docker_platforms }} \
|
||||
--output "type=image,push=true" \
|
||||
--progress=plain \
|
||||
--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: Get previous tag
|
||||
id: previousTag
|
||||
run: |
|
||||
name=$(git --no-pager tag --sort=creatordate --merged ${{ github.ref_name }} | tail -1)
|
||||
echo "previousTag: $name"
|
||||
echo "previousTag=$name" >> $GITHUB_ENV
|
||||
-
|
||||
name: Generate changelog
|
||||
id: changelog
|
||||
uses: requarks/changelog-action@v1
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
token: ${{ github.token }}
|
||||
fromTag: ${{ github.ref_name }}
|
||||
toTag: ${{ env.previousTag }}
|
||||
writeToFile: false
|
||||
reverseOrder: true
|
||||
includeInvalidCommits: true
|
||||
excludeTypes: "docs,build,chore"
|
||||
context: .
|
||||
push: true
|
||||
provenance: false
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
annotations: ${{ steps.meta.outputs.annotations }}
|
||||
build-args: |
|
||||
VERSION_ARG=${{ steps.meta.outputs.version }}
|
||||
-
|
||||
name: Create a release
|
||||
uses: action-pack/github-release@v2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.REPO_ACCESS_TOKEN }}
|
||||
with:
|
||||
tag: "v${{ steps.prepare.outputs.version }}"
|
||||
title: "v${{ steps.prepare.outputs.version }}"
|
||||
body: |
|
||||
${{ steps.changelog.outputs.changes }}
|
||||
|
||||
**Full Changelog**: https://github.com/${{ github.repository }}/compare//${{ env.previousTag }}...v${{ steps.prepare.outputs.version }}
|
||||
tag: "v${{ steps.meta.outputs.version }}"
|
||||
title: "v${{ steps.meta.outputs.version }}"
|
||||
token: ${{ secrets.REPO_ACCESS_TOKEN }}
|
||||
-
|
||||
name: Increment version variable
|
||||
uses: action-pack/bump@v2
|
||||
@@ -130,3 +89,15 @@ jobs:
|
||||
url: ${{ secrets.GITLAB_URL }}
|
||||
token: ${{ secrets.GITLAB_TOKEN }}
|
||||
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]
|
||||
|
||||
name: "Check"
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
@@ -7,8 +9,29 @@ jobs:
|
||||
name: shellcheck
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Run ShellCheck
|
||||
uses: ludeeus/action-shellcheck@master
|
||||
env:
|
||||
SHELLCHECK_OPTS: -x -e SC2001 -e SC2002 -e SC2223 -e SC2034 -e SC2064 -e SC2317 -e SC2028
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
- parallel:
|
||||
-
|
||||
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.4.0
|
||||
with:
|
||||
dockerfile: Dockerfile
|
||||
ignore: DL3008,DL3067
|
||||
failure-threshold: warning
|
||||
-
|
||||
name: Validate JSON and YML files
|
||||
uses: GrantBirki/json-yaml-validate@v5
|
||||
|
||||
+12
-10
@@ -12,13 +12,15 @@ jobs:
|
||||
dockerHubDescription:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
-
|
||||
name: Docker Hub Description
|
||||
uses: peter-evans/dockerhub-description@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
repository: ${{ github.repository }}
|
||||
short-description: ${{ github.event.repository.description }}
|
||||
readme-filepath: ./readme.md
|
||||
-
|
||||
name: Checkout repo
|
||||
uses: actions/checkout@v7
|
||||
-
|
||||
name: Docker Hub Description
|
||||
uses: peter-evans/dockerhub-description@v5
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
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:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
paths:
|
||||
- '**/*.sh'
|
||||
- '.github/workflows/test.yml'
|
||||
- '.github/workflows/check.yml'
|
||||
|
||||
name: "Test"
|
||||
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
|
||||
|
||||
+38
-56
@@ -1,68 +1,50 @@
|
||||
FROM ghcr.io/qemu-tools/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 host
|
||||
FROM scratch
|
||||
|
||||
FROM debian:bookworm-slim
|
||||
COPY --from=qemux/qemu:7.48 / /
|
||||
|
||||
ARG VERSION_ARG="0.0"
|
||||
ARG VERSION_CSTRUCT="4.7"
|
||||
|
||||
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 && \
|
||||
apt-get --no-install-recommends -y install \
|
||||
curl \
|
||||
cpio \
|
||||
wget \
|
||||
fdisk \
|
||||
unzip \
|
||||
socat \
|
||||
procps \
|
||||
dnsmasq \
|
||||
xz-utils \
|
||||
iptables \
|
||||
iproute2 \
|
||||
net-tools \
|
||||
btrfs-progs \
|
||||
netcat-openbsd \
|
||||
ca-certificates \
|
||||
qemu-system-x86 \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
COPY run/*.sh /run/
|
||||
COPY agent/*.sh /agent/
|
||||
RUN <<EOF
|
||||
set -eu
|
||||
|
||||
COPY --from=builder /qemu-host.bin /run/host.bin
|
||||
apt-get update
|
||||
apt-get --no-install-recommends -y install \
|
||||
fakeroot \
|
||||
python3-msgpack \
|
||||
python3-pysodium
|
||||
|
||||
RUN chmod +x /run/*.sh && chmod +x /run/*.bin
|
||||
apt-get clean
|
||||
|
||||
# Install Python dependencies
|
||||
pip3 install --no-cache-dir --break-system-packages --root-user-action=ignore "dissect.cstruct==$VERSION_CSTRUCT"
|
||||
|
||||
# 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=host /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
|
||||
EXPOSE 445 5000
|
||||
|
||||
EXPOSE 22
|
||||
EXPOSE 80
|
||||
EXPOSE 139
|
||||
EXPOSE 445
|
||||
EXPOSE 5000
|
||||
ENV RAM_SIZE="2G"
|
||||
ENV CPU_CORES="2"
|
||||
ENV DISK_SIZE="256G"
|
||||
|
||||
ENV CPU_CORES "1"
|
||||
ENV DISK_SIZE "16G"
|
||||
ENV RAM_SIZE "512M"
|
||||
HEALTHCHECK --interval=60s --start-period=45s --retries=2 CMD ["/run/check.sh"]
|
||||
|
||||
ARG DATE_ARG=""
|
||||
ARG BUILD_ARG=0
|
||||
ARG VERSION_ARG="0.0"
|
||||
ENV VERSION=$VERSION_ARG
|
||||
|
||||
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/kroese/virtual-dsm/
|
||||
LABEL org.opencontainers.image.url=https://hub.docker.com/r/kroese/virtual-dsm/
|
||||
|
||||
HEALTHCHECK --interval=60s --retries=2 CMD /run/check.sh
|
||||
|
||||
ENTRYPOINT ["/run/run.sh"]
|
||||
ENTRYPOINT ["/usr/bin/tini", "-s", "/run/entry.sh"]
|
||||
|
||||
-157
@@ -1,157 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -u
|
||||
|
||||
VERSION="7"
|
||||
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/kroese/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
|
||||
|
||||
SCRIPT=$(readlink -f "${BASH_SOURCE[0]}")
|
||||
local_size=$(stat -c%s "$SCRIPT")
|
||||
|
||||
[[ 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 "❯ 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
|
||||
|
||||
delay=500
|
||||
elapsed=$((($(date +%s%N) - ts)/1000000))
|
||||
|
||||
if [[ delay -gt elapsed ]]; then
|
||||
difference=$((delay-elapsed))
|
||||
float=$(echo | awk -v diff="${difference}" '{print diff * 0.001}')
|
||||
sleep "$float"
|
||||
fi
|
||||
|
||||
# Display message in docker log output
|
||||
|
||||
IP=$(ip address show dev eth0 | grep inet | awk '/inet / { print $2 }' | cut -f1 -d/)
|
||||
|
||||
if [[ "$IP" == "20.20"* ]]; then
|
||||
MSG="port 5000"
|
||||
else
|
||||
MSG="http://${IP}:5000"
|
||||
fi
|
||||
|
||||
info "--------------------------------------------------------"
|
||||
info " You can now login to DSM at ${MSG}"
|
||||
info "--------------------------------------------------------"
|
||||
|
||||
# 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/kroese/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: kroese/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,113 @@
|
||||
# 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 `ide`, `scsi`, 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. |
|
||||
| `DISK_OPTIONS` | | Additional options appended to QEMU disk devices. |
|
||||
| `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` | `qemu` | 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`. |
|
||||
| `VGA` | `none` | QEMU video adapter model. |
|
||||
| `GPU` | `N` | Enables experimental GPU acceleration. |
|
||||
| `RENDERNODE` | `/dev/dri/renderD128` | Render node used for GPU acceleration. |
|
||||
|
||||
## 🎈 Memory Ballooning
|
||||
|
||||
Also see [Dynamic memory allocation](https://github.com/qemus/qemu/blob/master/docs/ballooning.md) for usage instructions and important caveats.
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `BALLOONING` | `N` | Enables dynamic memory ballooning. |
|
||||
| `BALLOONING_MIN_MEM` | `33%` | Minimum amount of memory retained by the VM. |
|
||||
| `BALLOONING_RAM_THRESHOLD` | `80.0` | Host RAM usage percentage at which ballooning begins adjusting memory. |
|
||||
| `BALLOONING_RAM_THRESHOLD_HARD` | `90.0` | Host RAM usage percentage at which ballooning becomes more aggressive. |
|
||||
| `BALLOONING_PSI_PRESSURE` | `10.0` | PSI memory pressure level at which ballooning becomes more aggressive. |
|
||||
| `BALLOONING_PSI_PRESSURE_MAX` | `50.0` | PSI memory pressure level at which ballooning reaches its strongest response. |
|
||||
| `BALLOONING_HYSTERESIS` | `128M` | Minimum memory change before the balloon target is updated. |
|
||||
| `BALLOONING_KP` | `0.5` | Proportional gain used by the ballooning controller. |
|
||||
| `BALLOONING_KI` | `0.05` | Integral gain used by the ballooning controller. |
|
||||
| `BALLOONING_INTERVAL` | `5` | Polling interval in seconds. |
|
||||
| `BALLOONING_DEBUG` | `N` | Enables debug output for the ballooning monitor. |
|
||||
|
||||
## 🔌 Shutdown
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `SHUTDOWN` | `Y` | Enables graceful shutdown. |
|
||||
| `TIMEOUT` | `105` | 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. |
|
||||
| `MONITOR` | | QEMU monitor configuration. |
|
||||
| `QMP` | | QEMU Machine Protocol configuration. |
|
||||
| `ARGUMENTS` | | Additional raw arguments appended to the QEMU command line. |
|
||||
@@ -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,192 +1,304 @@
|
||||
<h1 align="center">Virtual DSM for Docker<br />
|
||||
<h1 align="center">Virtual DSM<br />
|
||||
<div align="center">
|
||||
<img src="https://github.com/kroese/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 align="center">
|
||||
|
||||
[![Build]][build_url]
|
||||
[![Version]][tag_url]
|
||||
[![Size]][tag_url]
|
||||
[![Package]][pkg_url]
|
||||
[![Pulls]][hub_url]
|
||||
|
||||
</div></h1>
|
||||
Virtual DSM in a docker container.
|
||||
|
||||
## Features
|
||||
Virtual DSM in a Docker container.
|
||||
|
||||
- Multi-platform
|
||||
- KVM acceleration
|
||||
- Graceful shutdown
|
||||
- Upgrades supported
|
||||
## Features ✨
|
||||
|
||||
## 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
|
||||
- Dynamic memory allocation with memory ballooning
|
||||
- Supports NAT, user-mode, macvlan, and macvtap networking
|
||||
|
||||
Via `docker-compose.yml`
|
||||
## Usage 🐳
|
||||
|
||||
##### Docker Compose:
|
||||
|
||||
```yaml
|
||||
version: "3"
|
||||
services:
|
||||
dsm:
|
||||
container_name: dsm
|
||||
image: kroese/virtual-dsm:latest
|
||||
environment:
|
||||
DISK_SIZE: "16G"
|
||||
devices:
|
||||
- /dev/kvm
|
||||
- /dev/vhost-net
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
ports:
|
||||
- 5000:5000
|
||||
volumes:
|
||||
- /opt/dsm:/storage
|
||||
restart: on-failure
|
||||
stop_grace_period: 1m
|
||||
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
|
||||
```
|
||||
|
||||
Via `docker run`
|
||||
##### Docker CLI:
|
||||
|
||||
```bash
|
||||
docker run -it --rm -p 5000:5000 --device=/dev/kvm --cap-add NET_ADMIN --stop-timeout 60 kroese/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
|
||||
environment:
|
||||
DISK_SIZE: "256G"
|
||||
```
|
||||
[](https://codespaces.new/vdsm/virtual-dsm)
|
||||
|
||||
* ### How do I change the location of the virtual disk?
|
||||
## Requirements ⚙️
|
||||
|
||||
To change the virtual disk's location from the default Docker volume, include the following bind mount in your compose file:
|
||||
- Docker or Podman on a Linux host with KVM support.
|
||||
- Docker Desktop or Podman (Desktop) on Windows 11 with nested virtualization enabled.
|
||||
- At least 1 GB of available RAM.
|
||||
- At least 16 GB of free disk space.
|
||||
|
||||
```yaml
|
||||
volumes:
|
||||
- /home/user/data:/storage
|
||||
```
|
||||
> [!NOTE]
|
||||
> Docker Desktop on Linux, macOS, and Windows 10 does not currently provide KVM access to containers and is therefore not supported.
|
||||
|
||||
Replace the example path `/home/user/data` with the desired storage folder.
|
||||
## FAQ 💬
|
||||
|
||||
* ### How do I change the space reserved by the virtual disk?
|
||||
### How do I use it?
|
||||
|
||||
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:
|
||||
Very simple! These are the steps:
|
||||
|
||||
- Start the container and connect to [port 5000](http://127.0.0.1:5000/) using your web browser.
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
ALLOCATE: "N"
|
||||
```
|
||||
- Wait until DSM finishes its installation
|
||||
|
||||
- Choose a username and password, and you will be taken to the desktop.
|
||||
|
||||
Enjoy your brand new NAS, and don't forget to star this repo!
|
||||
|
||||
Keep in mind that this will not affect any of your existing disks, it only applies to newly created disks.
|
||||
### How do I change the storage location?
|
||||
|
||||
* ### How do I increase the amount of CPU or RAM?
|
||||
To change the storage location, include the following bind mount in your compose file:
|
||||
|
||||
By default, a single core and 512 MB of RAM are allocated to the container. To increase this, add the following environment variables:
|
||||
```yaml
|
||||
volumes:
|
||||
- ./dsm:/storage
|
||||
```
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
CPU_CORES: "4"
|
||||
RAM_SIZE: "2048M"
|
||||
```
|
||||
Replace the example path `./dsm` with the desired storage folder or named volume.
|
||||
|
||||
### How do I change the size of the disk?
|
||||
|
||||
* ### How do I verify if my system supports KVM?
|
||||
To expand the default size of 256 GB, locate the `DISK_SIZE` setting in your compose file and modify it to your preferred capacity:
|
||||
|
||||
To verify if your system supports KVM, run the following commands:
|
||||
```yaml
|
||||
environment:
|
||||
DISK_SIZE: "512G"
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> This can also be used to resize an existing disk to a larger capacity without any data loss.
|
||||
|
||||
```bash
|
||||
sudo apt install cpu-checker
|
||||
sudo kvm-ok
|
||||
```
|
||||
### How do I add multiple disks?
|
||||
|
||||
If you receive an error from `kvm-ok` indicating that KVM acceleration can't be used, check your BIOS settings.
|
||||
To create additional disks, modify your compose file like this:
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
DISK2_SIZE: "500G"
|
||||
DISK3_SIZE: "750G"
|
||||
volumes:
|
||||
- ./example2:/storage2
|
||||
- ./example3:/storage3
|
||||
```
|
||||
|
||||
* ### How do I assign an individual IP address to the container?
|
||||
### How do I pass through a disk?
|
||||
|
||||
By default, the container uses bridge networking, which shares the IP address with the host.
|
||||
You can pass through disk devices or partitions directly by adding them to your compose file in this way:
|
||||
|
||||
If you want to assign an individual IP address to the container, you can create a macvlan network as follows:
|
||||
```yaml
|
||||
devices:
|
||||
- /dev/sdb:/disk1
|
||||
- /dev/sdc1:/disk2
|
||||
```
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
Be sure to modify these values to match your local subnet.
|
||||
Make sure it is totally empty (without any filesystem), otherwise DSM may not format it as a volume.
|
||||
|
||||
Once you have created the network, change your compose file to look as follows:
|
||||
### How do I change the amount of CPU or RAM?
|
||||
|
||||
```yaml
|
||||
services:
|
||||
dsm:
|
||||
container_name: dsm
|
||||
..<snip>..
|
||||
networks:
|
||||
vdsm:
|
||||
ipv4_address: 192.168.0.100
|
||||
By default, Virtual DSM will be allowed to use 2 CPU cores and 2 GB of RAM.
|
||||
|
||||
networks:
|
||||
If you want to adjust this, you can specify the desired amount using the following environment variables:
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
RAM_SIZE: "4G"
|
||||
CPU_CORES: "4"
|
||||
```
|
||||
|
||||
### How do I assign an individual IP address to the container?
|
||||
|
||||
By default, the container uses bridge networking, which shares the IP address with the host.
|
||||
|
||||
If you want to assign an individual IP address to the container, you can create a macvlan network as follows:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
Be sure to modify these values to match your local subnet.
|
||||
|
||||
Once you have created the network, change your compose file to look as follows:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
dsm:
|
||||
container_name: dsm
|
||||
..<snip>..
|
||||
networks:
|
||||
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.
|
||||
ipv4_address: 192.168.0.100
|
||||
|
||||
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.
|
||||
networks:
|
||||
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.
|
||||
|
||||
* ### How can the container acquire an IP address from my router?
|
||||
> [!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.
|
||||
|
||||
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.
|
||||
### How can DSM acquire an IP address from my router?
|
||||
|
||||
To enable this feature, add the following lines to your compose file:
|
||||
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.
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
DHCP: "Y"
|
||||
devices:
|
||||
- /dev/vhost-net
|
||||
device_cgroup_rules:
|
||||
- 'c *:* rwm'
|
||||
```
|
||||
To enable this mode, in which the container and DSM will have separate IP addresses, add the following lines to your compose file:
|
||||
|
||||
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.
|
||||
```yaml
|
||||
environment:
|
||||
DHCP: "Y"
|
||||
devices:
|
||||
- /dev/vhost-net
|
||||
device_cgroup_rules:
|
||||
- 'c *:* rwm'
|
||||
```
|
||||
|
||||
* ### How do I install a specific version of vDSM?
|
||||
### How do I enable GPU acceleration?
|
||||
|
||||
By default, version 7.2 will be installed, but if you prefer an older version, you can add its URL to your compose file as follows:
|
||||
To enable hardware-accelerated graphics, add the following lines to your compose file:
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
URL: "https://global.synologydownload.com/download/DSM/release/7.0.1/42218/DSM_VirtualDSM_42218.pat"
|
||||
```
|
||||
```yaml
|
||||
environment:
|
||||
GPU: "Y"
|
||||
devices:
|
||||
- /dev/dri
|
||||
```
|
||||
|
||||
With this method, you are able to switch between different versions while keeping your file data.
|
||||
This can be used to enable the facial recognition function in Synology Photos, but does not provide hardware transcoding for video.
|
||||
|
||||
* ### What are the differences compared to the standard DSM?
|
||||
### How do I enable dynamic memory allocation?
|
||||
|
||||
There are only two minor differences: the Virtual Machine Manager package is not provided, and Surveillance Station doesn't include any free licenses.
|
||||
|
||||
* ### Is this project legal?
|
||||
By default, the DSM is allocated the full amount of RAM configured via `RAM_SIZE` for its entire lifetime.
|
||||
|
||||
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.
|
||||
|
||||
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 via the Container Manager package, as any other use will be a violation of their terms and conditions.
|
||||
However, you can enable [memory ballooning](https://github.com/qemus/qemu/blob/master/docs/ballooning.md) if you want the container to dynamically reclaim unused guest RAM based on host memory pressure.
|
||||
|
||||
## Disclaimer
|
||||
### How do I install a specific version of vDSM?
|
||||
|
||||
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.
|
||||
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:
|
||||
|
||||
[build_url]: https://github.com/kroese/virtual-dsm/
|
||||
[hub_url]: https://hub.docker.com/r/kroese/virtual-dsm
|
||||
[tag_url]: https://hub.docker.com/r/kroese/virtual-dsm/tags
|
||||
```yaml
|
||||
environment:
|
||||
URL: "https://global.synologydownload.com/download/DSM/release/7.0.1/42218/DSM_VirtualDSM_42218.pat"
|
||||
```
|
||||
|
||||
[Build]: https://github.com/kroese/virtual-dsm/actions/workflows/build.yml/badge.svg
|
||||
[Size]: https://img.shields.io/docker/image-size/kroese/virtual-dsm/latest?color=066da5&label=size
|
||||
[Pulls]: https://img.shields.io/docker/pulls/kroese/virtual-dsm.svg?style=flat&label=pulls&logo=docker
|
||||
[Version]: https://img.shields.io/docker/v/kroese/virtual-dsm?arch=amd64&sort=date&color=066da5
|
||||
With this method, it is even possible to switch back and forth between versions while keeping your file data intact.
|
||||
|
||||
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/
|
||||
[hub_url]: https://hub.docker.com/r/vdsm/virtual-dsm
|
||||
[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
|
||||
[Size]: https://img.shields.io/docker/image-size/vdsm/virtual-dsm/latest?color=066da5&label=size
|
||||
[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
|
||||
[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,49 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -u
|
||||
|
||||
[ ! -f "/run/qemu.pid" ] && echo "QEMU not running yet.." && exit 0
|
||||
|
||||
# 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
|
||||
|
||||
if ! curl -m 3 -ILfSs "http://${IP}:${PORT}/" > /dev/null; then
|
||||
echo "Failed to reach ${IP}:${PORT}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$IP" == "20.20"* ]]; then
|
||||
echo "Healthcheck OK"
|
||||
else
|
||||
echo "Healthcheck OK ( $IP )"
|
||||
fi
|
||||
|
||||
exit 0
|
||||
-146
@@ -1,146 +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
|
||||
|
||||
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 < 6442450944 )); 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
|
||||
truncate -s "${DATA_SIZE}" "${DATA}";
|
||||
|
||||
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
|
||||
error "Could not allocate a file for the virtual disk." && exit 85
|
||||
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
|
||||
truncate -s "${DATA_SIZE}" "${DATA}"
|
||||
|
||||
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
|
||||
rm -f "${DATA}"
|
||||
error "Could not allocate a file for the virtual disk." && exit 87
|
||||
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
|
||||
|
||||
# Format as BTRFS filesystem
|
||||
mkfs.btrfs -q -L data -d single -m dup "${DATA}" > /dev/null
|
||||
|
||||
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
|
||||
|
||||
AGENT="${STORAGE}/${BASE}.agent"
|
||||
[ -f "$AGENT" ] && AGENT_VERSION=$(cat "${AGENT}") || AGENT_VERSION=1
|
||||
|
||||
if ((AGENT_VERSION < 5)); then
|
||||
info "The installed VirtualDSM Agent v${AGENT_VERSION} is an outdated version, please upgrade it."
|
||||
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"
|
||||
-260
@@ -1,260 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
# 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/64561/DSM_VirtualDSM_64561.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
|
||||
|
||||
TMP="$STORAGE/tmp"
|
||||
RDC="$STORAGE/dsm.rd"
|
||||
|
||||
rm -rf "$TMP" && mkdir -p "$TMP"
|
||||
|
||||
[[ "${DEBUG}" == [Yy1]* ]] && set -x
|
||||
|
||||
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
|
||||
|
||||
info "Install: Extracting downloaded image..."
|
||||
|
||||
if { tar tf "$PAT"; } >/dev/null 2>&1; then
|
||||
|
||||
tar xpf "$PAT" -C "$TMP/."
|
||||
|
||||
else
|
||||
|
||||
if [ "$ARCH" != "amd64" ]; then
|
||||
|
||||
export DEBCONF_NOWARNINGS="yes"
|
||||
export DEBIAN_FRONTEND="noninteractive"
|
||||
|
||||
apt-get -qq update
|
||||
apt-get -qq -y upgrade
|
||||
apt-get -qq --no-install-recommends -y install qemu-user > /dev/null
|
||||
|
||||
export DEBIAN_FRONTEND=""
|
||||
export DEBCONF_NOWARNINGS=""
|
||||
|
||||
fi
|
||||
|
||||
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
|
||||
rm -f "${SYSTEM}" && error "Could not allocate a file for the system disk." && exit 88
|
||||
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/"
|
||||
|
||||
# Install Agent
|
||||
|
||||
LOC="$MOUNT/usr/local/bin"
|
||||
mkdir -p "$LOC"
|
||||
cp /agent/agent.sh "$LOC/agent.sh"
|
||||
chmod 755 "$LOC/agent.sh"
|
||||
|
||||
LOC="$MOUNT/usr/local/etc/rc.d"
|
||||
mkdir -p "$LOC"
|
||||
cp /agent/service.sh "$LOC/agent.sh"
|
||||
chmod 755 "$LOC/agent.sh"
|
||||
|
||||
# Store agent version
|
||||
echo "7" > "$STORAGE"/"$BASE".agent
|
||||
|
||||
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
|
||||
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
|
||||
-223
@@ -1,223 +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
|
||||
|
||||
ip link set "${VM_NET_TAP}" up
|
||||
|
||||
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
|
||||
ip link set dockerbridge up
|
||||
|
||||
# QEMU Works with taps, set tap to the bridge created
|
||||
ip tuntap add dev "${VM_NET_TAP}" mode tap
|
||||
ip link set "${VM_NET_TAP}" up promisc on
|
||||
ip link set dev "${VM_NET_TAP}" master dockerbridge
|
||||
|
||||
# Add internet connection to the VM
|
||||
iptables -t nat -A POSTROUTING -o "${VM_NET_DEV}" -j MASQUERADE
|
||||
iptables -t nat -A PREROUTING -i "${VM_NET_DEV}" -p tcp -j DNAT --to $VM_NET_IP
|
||||
iptables -t nat -A PREROUTING -i "${VM_NET_DEV}" -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
|
||||
}
|
||||
|
||||
# ######################################
|
||||
# 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
|
||||
error "You can only enable DHCP while the container is on a macvlan network!" && exit 86
|
||||
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,89 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
# Configure QEMU for graceful shutdown
|
||||
|
||||
QEMU_MONPORT=7100
|
||||
QEMU_POWERDOWN_TIMEOUT=50
|
||||
|
||||
_QEMU_PID=/run/qemu.pid
|
||||
_QEMU_SHUTDOWN_COUNTER=/run/qemu.counter
|
||||
|
||||
rm -f "${_QEMU_PID}"
|
||||
rm -f "${_QEMU_SHUTDOWN_COUNTER}"
|
||||
|
||||
_trap(){
|
||||
func="$1" ; shift
|
||||
for sig ; do
|
||||
trap "$func $sig" "$sig"
|
||||
done
|
||||
}
|
||||
|
||||
_graceful_shutdown() {
|
||||
|
||||
set +e
|
||||
|
||||
[ ! -f "${_QEMU_PID}" ] && return
|
||||
[ -f "${_QEMU_SHUTDOWN_COUNTER}" ] && return
|
||||
|
||||
echo && info "Received $1 signal, shutting down..."
|
||||
echo 0 > "${_QEMU_SHUTDOWN_COUNTER}"
|
||||
|
||||
# Don't send the powerdown signal because vDSM ignores ACPI signals
|
||||
# echo 'system_powerdown' | nc -q 1 -w 1 localhost "${QEMU_MONPORT}" > /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)"
|
||||
|
||||
# If we cannot shutdown the usual way, fallback to the NMI method
|
||||
|
||||
AGENT="${STORAGE}/${BASE}.agent"
|
||||
[ -f "$AGENT" ] && AGENT_VERSION=$(cat "${AGENT}") || AGENT_VERSION=1
|
||||
|
||||
if ((AGENT_VERSION > 1)); then
|
||||
|
||||
# Send a NMI interrupt which will be detected by the kernel
|
||||
if ! echo 'nmi' | nc -q 1 -w 1 localhost "${QEMU_MONPORT}" > /dev/null ; then
|
||||
AGENT_VERSION=0
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
if ((AGENT_VERSION < 2)); then
|
||||
|
||||
echo && info "Please update the VirtualDSM Agent to allow for gracefull shutdowns..."
|
||||
|
||||
kill -15 "$(cat "${_QEMU_PID}")"
|
||||
pkill -f qemu-system-x86_64 || true
|
||||
|
||||
fi
|
||||
fi
|
||||
|
||||
while [ "$(cat ${_QEMU_SHUTDOWN_COUNTER})" -lt "${QEMU_POWERDOWN_TIMEOUT}" ]; do
|
||||
|
||||
# Increase the counter
|
||||
echo $(($(cat ${_QEMU_SHUTDOWN_COUNTER})+1)) > ${_QEMU_SHUTDOWN_COUNTER}
|
||||
|
||||
# Try to connect to qemu
|
||||
if echo 'info version'| nc -q 1 -w 1 localhost "${QEMU_MONPORT}" >/dev/null 2>&1 ; then
|
||||
|
||||
sleep 1
|
||||
#info "Shutting down, waiting... ($(cat ${_QEMU_SHUTDOWN_COUNTER})/${QEMU_POWERDOWN_TIMEOUT})"
|
||||
|
||||
fi
|
||||
|
||||
done
|
||||
|
||||
echo && echo "❯ Quitting..."
|
||||
echo 'quit' | nc -q 1 -w 1 localhost "${QEMU_MONPORT}" >/dev/null 2>&1 || true
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
_trap _graceful_shutdown SIGTERM SIGHUP SIGINT SIGABRT SIGQUIT
|
||||
|
||||
MON_OPTS="-monitor telnet:localhost:${QEMU_MONPORT},server,nowait,nodelay"
|
||||
-99
@@ -1,99 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
# Docker environment variables
|
||||
|
||||
: ${URL:=''} # URL of the PAT file
|
||||
: ${DEBUG:='N'} # Enable debug mode
|
||||
: ${ALLOCATE:='Y'} # Preallocate diskspace
|
||||
: ${CPU_CORES:='1'} # Amount of CPU cores
|
||||
: ${DISK_SIZE:='16G'} # Initial data disk size
|
||||
: ${RAM_SIZE:='512M'} # Maximum RAM amount
|
||||
|
||||
echo "❯ Starting Virtual DSM for Docker v${VERSION}..."
|
||||
|
||||
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
|
||||
|
||||
STORAGE="/storage"
|
||||
KERNEL=$(uname -r | cut -b 1)
|
||||
ARCH=$(dpkg --print-architecture)
|
||||
VERS=$(qemu-system-x86_64 --version | head -n 1 | cut -d '(' -f 1)
|
||||
|
||||
[ ! -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
|
||||
. /run/install.sh
|
||||
fi
|
||||
|
||||
# Initialize disks
|
||||
. /run/disk.sh
|
||||
|
||||
# Initialize network
|
||||
. /run/network.sh
|
||||
|
||||
# Initialize serialport
|
||||
. /run/serial.sh
|
||||
|
||||
# Configure shutdown
|
||||
. /run/power.sh
|
||||
|
||||
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"
|
||||
|
||||
ARGS="${DEF_OPTS} ${CPU_OPTS} ${RAM_OPTS} ${MAC_OPTS} ${MON_OPTS} ${SERIAL_OPTS} ${NET_OPTS} ${DISK_OPTS} ${EXTRA_OPTS}"
|
||||
ARGS=$(echo "$ARGS" | sed 's/\t/ /g' | tr -s ' ')
|
||||
|
||||
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
|
||||
|
||||
if (( KERNEL > 4 )); then
|
||||
pidwait -F "${_QEMU_PID}" & wait $!
|
||||
else
|
||||
tail --pid "$(cat "${_QEMU_PID}")" --follow /dev/null & wait $!
|
||||
fi
|
||||
@@ -1,59 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
# Docker environment variables
|
||||
|
||||
: ${HOST_CPU:=''}
|
||||
: ${HOST_MAC:=''}
|
||||
: ${HOST_BUILD:=''}
|
||||
: ${HOST_DEBUG:=''}
|
||||
: ${HOST_SERIAL:=''}
|
||||
: ${GUEST_SERIAL:=''}
|
||||
: ${HOST_MODEL:=''}
|
||||
: ${HOST_VERSION:=''}
|
||||
: ${HOST_TIMESTAMP:=''}
|
||||
|
||||
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_BUILD" ] && HOST_ARGS+=("-build=${HOST_BUILD}")
|
||||
[ -n "$HOST_MODEL" ] && HOST_ARGS+=("-model=${HOST_MODEL}")
|
||||
[ -n "$HOST_SERIAL" ] && HOST_ARGS+=("-hostsn=${HOST_SERIAL}")
|
||||
[ -n "$GUEST_SERIAL" ] && HOST_ARGS+=("-guestsn=${GUEST_SERIAL}")
|
||||
[ -n "$HOST_VERSION" ] && HOST_ARGS+=("-version=${HOST_VERSION}")
|
||||
[ -n "$HOST_TIMESTAMP" ] && HOST_ARGS+=("-ts=${HOST_TIMESTAMP}")
|
||||
|
||||
if [[ "${HOST_DEBUG}" == [Yy1]* ]]; then
|
||||
set -x
|
||||
./run/host.bin "${HOST_ARGS[@]}" &
|
||||
{ set +x; } 2>/dev/null
|
||||
echo
|
||||
else
|
||||
./run/host.bin "${HOST_ARGS[@]}" 2> /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"
|
||||
@@ -1,68 +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://\${IP}:\${PORT}'>http://\${IP}:\${PORT}</a><script>"
|
||||
BODY="${BODY}setTimeout(function(){ window.location.assign('http://\${IP}:\${PORT}'); }, 3000);</script>"
|
||||
WAIT="Please wait while discovering IP...<script>setTimeout(() => { document.location.reload(); }, 4999);</script>"
|
||||
|
||||
HTML=$(html "xxx")
|
||||
|
||||
{ echo "#!/bin/bash"
|
||||
echo "INFO=\$(curl -s -m 2 -S http://127.0.0.1:2210/read?command=10 2>/dev/null)"
|
||||
echo "rest=\${INFO#*http_port}; rest=\${rest#*:}; rest=\${rest%%,*}; PORT=\${rest%%\\\"*}"
|
||||
echo "rest=\${INFO#*eth0}; rest=\${rest#*ip}; rest=\${rest#*:}; rest=\${rest#*\\\"}; IP=\${rest%%\\\"*}"
|
||||
echo "HTML=\"$HTML\"; [ -z \"\${IP}\" ] && 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,46 @@
|
||||
#!/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
|
||||
|
||||
# Treat the startup window as healthy so container health checks do not restart
|
||||
# the service before QEMU has had time to publish its PID.
|
||||
[ ! -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.host"
|
||||
gateway="/run/shm/qemu.gw"
|
||||
|
||||
# dsm.url is written only after the guest agent reports both the DSM
|
||||
# address and its configured HTTP port.
|
||||
[ ! -s "$file" ] && echo "DSM has not enabled networking yet..." && exit 0
|
||||
|
||||
location=$(<"$file")
|
||||
|
||||
if ! curl -m 20 -LfSs -o /dev/null "http://$location/"; then
|
||||
|
||||
# In DHCP mode the firewall must allow the container address; with port
|
||||
# forwarding it must allow the internal gateway used to reach the guest.
|
||||
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
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
: "${RNG:=""}"
|
||||
: "${QMP:=""}"
|
||||
: "${UUID:=""}"
|
||||
: "${MONITOR:=""}"
|
||||
|
||||
msg="Configuring QEMU..."
|
||||
enabled "$DEBUG" && echo "$msg"
|
||||
|
||||
# Sanitize variables
|
||||
QMP=$(strip "$QMP")
|
||||
UUID=$(strip "$UUID")
|
||||
MONITOR=$(strip "$MONITOR")
|
||||
|
||||
configureProcessor() {
|
||||
|
||||
# Expose one thread per core in a single socket; DSM licensing and topology
|
||||
# reporting are more predictable with this fixed layout.
|
||||
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=""
|
||||
[ -n "$QMP" ] && MON_OPTS+=" -qmp $QMP"
|
||||
[ -n "$MONITOR" ] && MON_OPTS+=" -monitor $MONITOR"
|
||||
MON_OPTS="${MON_OPTS# }"
|
||||
|
||||
local name="${APP// /-}"
|
||||
ID_OPTS="-name $name,process=$PROCESS,debug-threads=on"
|
||||
PID_OPTS="-pidfile $QEMU_PID"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
configureMachine() {
|
||||
|
||||
local smm="off"
|
||||
enabled "${SMM:-}" && smm="on"
|
||||
|
||||
# Disable firmware and chipset features that Virtual DSM does not use and
|
||||
# that can introduce extra devices or timing differences.
|
||||
MAC_OPTS="-machine type=$MACHINE,smm=$smm,usb=off"
|
||||
MAC_OPTS+=",vmport=off,dump-guest-core=off,hpet=off${KVM_OPTS}"
|
||||
|
||||
[ -n "$UUID" ] && ID_OPTS+=" -uuid $UUID"
|
||||
[ -n "${SM_BIOS:-}" ] && ID_OPTS+=" $SM_BIOS"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
configureDevices() {
|
||||
|
||||
local bus
|
||||
bus=$(getPciBus)
|
||||
|
||||
DEV_OPTS=""
|
||||
|
||||
if ! disabled "$RNG"; then
|
||||
DEV_OPTS+=" -object rng-random,id=objrng0,filename=/dev/urandom"
|
||||
DEV_OPTS+=" -device virtio-rng-pci,rng=objrng0,id=rng0,bus=$bus,addr=0x1c"
|
||||
fi
|
||||
|
||||
# Keep the existing balloon device by default. When dynamic ballooning is
|
||||
# enabled, expose guest statistics and a dedicated QMP control socket.
|
||||
if ! enabled "${BALLOONING:-}"; then
|
||||
DEV_OPTS+=" -device virtio-balloon-pci,id=balloon0,bus=$bus,addr=0x4"
|
||||
else
|
||||
MON_OPTS+=" -qmp unix:${BALLOONING_SOCKET},server=on,wait=off"
|
||||
DEV_OPTS+=" -device virtio-balloon-pci,free-page-reporting=on,guest-stats-polling-interval=1,id=balloon0,bus=$bus,addr=0x4"
|
||||
fi
|
||||
|
||||
DEV_OPTS="${DEV_OPTS# }"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
buildArguments() {
|
||||
|
||||
local default="-nodefaults"
|
||||
local boot="-boot strict=on"
|
||||
|
||||
ARGS="$default $MAC_OPTS $CPU_OPTS $RAM_OPTS $ID_OPTS $PID_OPTS $DISPLAY_OPTS $MON_OPTS $SERIAL_OPTS $NET_OPTS $DISK_OPTS $boot $BOOT_OPTS $DEV_OPTS $ARGUMENTS"
|
||||
|
||||
# Collapse whitespace after optional argument groups are assembled so empty
|
||||
# features cannot leave malformed spacing in the final QEMU command.
|
||||
ARGS=$(echo "$ARGS" | sed 's/\t/ /g' | tr -s ' ')
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
finalizeMemory
|
||||
|
||||
configureMemory
|
||||
configureMonitor
|
||||
configureMachine
|
||||
configureProcessor
|
||||
configureDevices
|
||||
|
||||
buildArguments
|
||||
|
||||
return 0
|
||||
Executable
+64
@@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
: "${PLATFORM:="x64"}"
|
||||
: "${APP:="Virtual DSM"}"
|
||||
: "${SUPPORT:="https://github.com/vdsm/virtual-dsm"}"
|
||||
|
||||
: "${USB:="N"}"
|
||||
: "${AUDIO:="N"}"
|
||||
: "${VGA:="none"}"
|
||||
: "${DISPLAY:="none"}"
|
||||
: "${WEB_PORT:="5000"}"
|
||||
: "${DISK_OFFSET:="2"}"
|
||||
: "${DISK_SIZE:="256G"}"
|
||||
: "${RAM_MINIMUM:="1G"}"
|
||||
: "${DISK_MINIMUM:="6G"}"
|
||||
: "${BOOT_MODE:="legacy"}"
|
||||
|
||||
cd /run
|
||||
|
||||
. start.sh # Startup hook
|
||||
. utils.sh # Load functions
|
||||
. init.sh # Initialize system
|
||||
. memory.sh # Check memory
|
||||
. server.sh # Start webserver
|
||||
. download.sh # Load functions
|
||||
. install.sh # Run installation
|
||||
. disk.sh # Initialize disks
|
||||
. display.sh # Initialize graphics
|
||||
. prepare.sh # Prepare for launch
|
||||
. network.sh # Initialize network
|
||||
. boot.sh # Configure boot
|
||||
. proc.sh # Initialize processor
|
||||
. serial.sh # Initialize serialport
|
||||
. power.sh # Configure shutdown
|
||||
. balloon.sh # Initialize ballooning
|
||||
. 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,18 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
fKill "progress.sh"
|
||||
|
||||
if [ -s "$QEMU_DIR/qemu.host" ] && [[ "$(<"$QEMU_DIR/qemu.host")" == "172.17."* ]]; then
|
||||
warn "your container IP starts with 172.17.* which will cause conflicts when you install the Container Manager package inside DSM!"
|
||||
fi
|
||||
|
||||
if enabled "$DEBUG"; then
|
||||
echo
|
||||
printf "QEMU arguments:\n\n %s\n\n" "${ARGS// -/$'\n -'}"
|
||||
fi
|
||||
|
||||
# Must always remain the very last command
|
||||
enableTrap
|
||||
|
||||
return 0
|
||||
+662
@@ -0,0 +1,662 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
: "${TZ:=""}" # System timezone
|
||||
: "${COUNTRY:=""}" # Country code for mirror
|
||||
: "${URL:=""}" # URL of the PAT file to be downloaded.
|
||||
|
||||
TZ=$(strip "$TZ")
|
||||
COUNTRY=$(strip "$COUNTRY")
|
||||
|
||||
checkDsmFilesystem() {
|
||||
|
||||
local fs="$1"
|
||||
|
||||
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."
|
||||
return 61
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
checkDsmSpace() {
|
||||
|
||||
local rootSpace=536870912
|
||||
local minSpace=15032385536
|
||||
local space available
|
||||
|
||||
space=$(df --output=avail -B 1 / | tail -n 1) || return $?
|
||||
available=$(formatBytes "$space" "down") || return $?
|
||||
|
||||
if (( rootSpace > space )); then
|
||||
error "Not enough free space inside the container, have $available available but need at least 500 MB."
|
||||
return 96
|
||||
fi
|
||||
|
||||
space=$(df --output=avail -B 1 "$STORAGE" | tail -n 1) || return $?
|
||||
available=$(formatBytes "$space") || return $?
|
||||
|
||||
if (( minSpace > space )); then
|
||||
error "Not enough free space for installation in $STORAGE, have $available available but need at least 14 GB."
|
||||
return 94
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
downloadFile() {
|
||||
|
||||
local url="$1"
|
||||
local pat="$2"
|
||||
local msg="$3"
|
||||
local connections="${4:-1}"
|
||||
|
||||
downloadToFile \
|
||||
"$url" \
|
||||
"$pat" \
|
||||
"$msg" \
|
||||
"0" \
|
||||
"$connections" \
|
||||
"Y"
|
||||
}
|
||||
|
||||
downloadPat() {
|
||||
|
||||
local pat="$1"
|
||||
local msg err
|
||||
|
||||
if [[ "$URL" == "file://"* ]]; then
|
||||
msg="Copying DSM"
|
||||
err="Failed to copy ${URL:7}"
|
||||
info "Install: Copying installation image..."
|
||||
html "$msg..." || return $?
|
||||
|
||||
rm -f "$pat" || return $?
|
||||
|
||||
if [ ! -f "${URL:7}" ]; then
|
||||
error "File '${URL:7}' does not exist!"
|
||||
return 65
|
||||
fi
|
||||
|
||||
cp "${URL:7}" "$pat" || return $?
|
||||
|
||||
else
|
||||
|
||||
msg="Downloading DSM"
|
||||
err="Failed to download $URL"
|
||||
info "Install: Downloading $BASE.pat..."
|
||||
|
||||
downloadRetry \
|
||||
"$pat" \
|
||||
"$CONNECTIONS" \
|
||||
"5" \
|
||||
"$BASE.pat" \
|
||||
"0" \
|
||||
"$URL" \
|
||||
"$pat" \
|
||||
"$msg" || return 69
|
||||
fi
|
||||
|
||||
if [ ! -s "$pat" ]; then
|
||||
error "$err"
|
||||
return 69
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
extractPat() {
|
||||
|
||||
local pat="$1"
|
||||
local tmp="$2"
|
||||
local size="$3"
|
||||
local msg="Extracting installation image..."
|
||||
local rc
|
||||
|
||||
info "Install: $msg" && html "$msg" || return $?
|
||||
/run/progress.sh "$tmp" "$size" "$msg ([P])..." &
|
||||
|
||||
# Newer PAT files are normal tar archives; older encrypted/proprietary forms
|
||||
# require the bundled extractor as a compatibility fallback.
|
||||
if { tar tf "$pat"; } >/dev/null 2>&1; then
|
||||
|
||||
tar xpf "$pat" -C "$tmp/." || {
|
||||
rc=$?
|
||||
fKill "progress.sh"
|
||||
return "$rc"
|
||||
}
|
||||
|
||||
else
|
||||
|
||||
{ (cd "$tmp" && python3 /run/extract.py -i "$pat" -d 2>/run/extract.log); rc=$?; } || :
|
||||
|
||||
if (( rc != 0 )); then
|
||||
fKill "progress.sh"
|
||||
cat /run/extract.log
|
||||
error "Failed to extract PAT file, reason $rc"
|
||||
return 63
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
fKill "progress.sh"
|
||||
return 0
|
||||
}
|
||||
|
||||
createSystemImage() {
|
||||
|
||||
local tmp="$1"
|
||||
local fs="$2"
|
||||
local msg="Preparing system partition..."
|
||||
|
||||
info "Install: $msg" && html "$msg" || return $?
|
||||
|
||||
# The PAT boot archive becomes the persistent QEMU boot disk after its
|
||||
# companion system partition has been assembled.
|
||||
DSM_BOOT=$(find "$tmp" -name "*.bin.zip" -print -quit) || return $?
|
||||
|
||||
if [ -z "$DSM_BOOT" ]; then
|
||||
error "The PAT file contains no boot image."
|
||||
return 67
|
||||
fi
|
||||
|
||||
if [ ! -s "$DSM_BOOT" ]; then
|
||||
error "The PAT boot image archive is empty."
|
||||
return 67
|
||||
fi
|
||||
|
||||
local bootTmp="$tmp/boot" bootBase
|
||||
bootBase="$(basename "${DSM_BOOT%.zip}")"
|
||||
|
||||
rm -rf "$bootTmp" || return $?
|
||||
|
||||
if ! makeDir "$bootTmp"; then
|
||||
error "Failed to create directory \"$bootTmp\" !"
|
||||
return 93
|
||||
fi
|
||||
|
||||
if [[ "${fs,,}" == "btrfs" ]]; then
|
||||
{ chattr +C "$bootTmp"; } || :
|
||||
fi
|
||||
|
||||
7z x -y -o"$bootTmp" "$DSM_BOOT" >/dev/null || return $?
|
||||
DSM_BOOT="$bootTmp/$bootBase"
|
||||
|
||||
if [ ! -s "$DSM_BOOT" ]; then
|
||||
error "The PAT boot image could not be extracted."
|
||||
return 67
|
||||
fi
|
||||
|
||||
if [[ "${fs,,}" == "btrfs" ]]; then
|
||||
local attrs
|
||||
attrs=$(lsattr "$DSM_BOOT") || return $?
|
||||
if [[ "$attrs" != *"C"* ]]; then
|
||||
error "Failed to disable COW for boot image $DSM_BOOT on ${fs^^} filesystem."
|
||||
fi
|
||||
fi
|
||||
|
||||
SYSTEM="$STORAGE/$BASE.system.img"
|
||||
rm -f "$SYSTEM" || return $?
|
||||
|
||||
# Check free diskspace
|
||||
local systemSize=10738466816
|
||||
local space available
|
||||
|
||||
space=$(df --output=avail -B 1 "$STORAGE" | tail -n 1) || return $?
|
||||
available=$(formatBytes "$space") || return $?
|
||||
|
||||
if (( systemSize > space )); then
|
||||
error "Not enough free space in $STORAGE to create a 10 GB system disk, have only $available available."
|
||||
return 97
|
||||
fi
|
||||
|
||||
if ! touch "$SYSTEM"; then
|
||||
error "Could not create file $SYSTEM for the system disk."
|
||||
return 98
|
||||
fi
|
||||
|
||||
setOwner "$SYSTEM" || warn "failed to set the owner for \"$SYSTEM\" !"
|
||||
|
||||
if [[ "${fs,,}" == "btrfs" ]]; then
|
||||
{ chattr +C "$SYSTEM"; } || :
|
||||
local attrs
|
||||
attrs=$(lsattr "$SYSTEM") || return $?
|
||||
if [[ "$attrs" != *"C"* ]]; then
|
||||
error "Failed to disable COW for system image $SYSTEM on ${fs^^} filesystem."
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! fallocate -l "$systemSize" "$SYSTEM" &>/dev/null; then
|
||||
if ! fallocate -l -x "$systemSize" "$SYSTEM"; then
|
||||
if ! truncate -s "$systemSize" "$SYSTEM"; then
|
||||
rm -f "$SYSTEM"
|
||||
error "Could not allocate file $SYSTEM for the system disk."
|
||||
return 98
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Recreate Synology's expected DOS partition layout inside the fixed 10 GiB
|
||||
# system image before populating the ext4 root partition.
|
||||
local part="$tmp/partition.fdisk"
|
||||
|
||||
if ! {
|
||||
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"; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
sfdisk -q "$SYSTEM" < "$part" || return $?
|
||||
|
||||
local mount="$tmp/system"
|
||||
rm -rf "$mount" || return $?
|
||||
|
||||
if ! makeDir "$mount"; then
|
||||
error "Failed to create directory \"$mount\" !"
|
||||
return 93
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
installSystemPartition() {
|
||||
|
||||
local tmp="$1"
|
||||
local mount="$tmp/system"
|
||||
local msg="Extracting system partition..."
|
||||
|
||||
info "Install: $msg" && html "$msg" || return $?
|
||||
|
||||
local hda="$tmp/hda1"
|
||||
local idb="$tmp/indexdb"
|
||||
local pkg="$tmp/packages"
|
||||
local hdp="$tmp/synohdpack_img"
|
||||
|
||||
if [ ! -s "$hda.tgz" ]; then
|
||||
error "The PAT file contains no OS image."
|
||||
return 64
|
||||
fi
|
||||
|
||||
mv "$hda.tgz" "$hda.txz" || return $?
|
||||
|
||||
if [ -d "$pkg" ]; then
|
||||
mv "$pkg/" "$mount/.SynoUpgradePackages/" || return $?
|
||||
fi
|
||||
|
||||
rm -f "$mount/.SynoUpgradePackages/ActiveInsight-"* || return $?
|
||||
|
||||
local indexDb="$mount/usr/syno/synoman/indexdb"
|
||||
|
||||
if [ -s "$idb.txz" ]; then
|
||||
mkdir -p "$indexDb" || return $?
|
||||
fi
|
||||
|
||||
local label="1.44.1-42218"
|
||||
local offset="1048576" # 2048 * 512
|
||||
local numBlocks="2097152" # (16777216 * 512) / 4096
|
||||
local rc
|
||||
msg="Installing system partition..."
|
||||
|
||||
# Build the ext4 filesystem directly from the extracted tree under fakeroot,
|
||||
# preserving archive ownership without mounting a loop device.
|
||||
if 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 $indexDb/;\
|
||||
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"; then
|
||||
:
|
||||
else
|
||||
rc=$?
|
||||
return "$rc"
|
||||
fi
|
||||
|
||||
rm -rf "$mount" || return $?
|
||||
return 0
|
||||
}
|
||||
|
||||
checkSse42() {
|
||||
|
||||
if disabled "$KVM" || [[ "${PLATFORM,,}" != "x64" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if ! hasFlag "sse4_2"; then
|
||||
error "Your CPU does not have the SSE4.2 instruction set that Virtual DSM requires!"
|
||||
enabled "$DEBUG" || return 88
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
sanitizePatBase() {
|
||||
|
||||
local source="$1"
|
||||
local base
|
||||
|
||||
base=$(basename "${source%%\?*}" .pat) || return 1
|
||||
printf -v base '%b' "${base//%/\\x}" || return 1
|
||||
base="${base//[!A-Za-z0-9._-]/_}"
|
||||
|
||||
printf '%s' "$base"
|
||||
}
|
||||
|
||||
reservePorts() {
|
||||
|
||||
local port ports=""
|
||||
local hostPorts="${HOST_PORTS:-}"
|
||||
|
||||
# Older configurations may reserve DSM's web ports for the container.
|
||||
# They now need to be forwarded to the guest instead.
|
||||
for port in ${hostPorts//,/ }; do
|
||||
|
||||
case "${port,,}" in
|
||||
5000|5000/tcp|5001|5001/tcp)
|
||||
continue ;;
|
||||
esac
|
||||
|
||||
ports+="${ports:+,}$port"
|
||||
|
||||
done
|
||||
|
||||
HOST_PORTS="$ports"
|
||||
|
||||
# NAT can fall back to user-mode networking after this point,
|
||||
# so always add the DSM web interface ports for non-DHCP networking.
|
||||
USER_PORTS="${USER_PORTS:+$USER_PORTS,}5000/tcp,5001/tcp,80/tcp,443/tcp,445/tcp"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
prepareMac() {
|
||||
|
||||
local file="$STORAGE/$PROCESS.mac"
|
||||
local legacy="$STORAGE/dsm.mac"
|
||||
local container mac=""
|
||||
|
||||
# An explicitly configured MAC remains network.sh's responsibility.
|
||||
[ -n "${MAC:-}" ] && return 0
|
||||
[ -s "$file" ] && return 0
|
||||
|
||||
# Preserve the address generated by older Virtual DSM releases.
|
||||
if [ -s "$legacy" ]; then
|
||||
mac=$(readFile "$legacy") || return $?
|
||||
fi
|
||||
|
||||
if [ -z "$mac" ]; then
|
||||
container=$(containerID) || return $?
|
||||
mac=$(echo "$container" | md5sum |
|
||||
sed 's/^\(..\)\(..\)\(..\)\(..\)\(..\).*$/02:11:32:\3:\4:\5/') || return $?
|
||||
fi
|
||||
|
||||
if ! writeState "mac" "${mac^^}"; then
|
||||
error "Failed to write MAC address to \"$file\" !"
|
||||
return 28
|
||||
fi
|
||||
|
||||
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"
|
||||
|
||||
# Country detection is best-effort and tries independent services in order;
|
||||
# failure leaves mirror selection at its global default.
|
||||
[ -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
|
||||
}
|
||||
|
||||
finishDsmInstall() {
|
||||
|
||||
local pat="$1"
|
||||
local tmp="$2"
|
||||
|
||||
echo "$BASE" > "$STORAGE/dsm.ver" || return 1
|
||||
setOwner "$STORAGE/dsm.ver" || warn "failed to set the owner for \"$STORAGE/dsm.ver\" !"
|
||||
|
||||
# Do not keep a second copy when the source PAT already lives in storage;
|
||||
# downloaded or externally mounted sources are cached for later reuse.
|
||||
if [[ "$URL" == "file://$STORAGE/$BASE.pat" ]]; then
|
||||
rm -f "$pat" || return $?
|
||||
else
|
||||
mv -f "$pat" "$STORAGE/$BASE.pat" || return $?
|
||||
fi
|
||||
|
||||
if [ -f "$STORAGE/$BASE.pat" ]; then
|
||||
setOwner "$STORAGE/$BASE.pat" || warn "failed to set the owner for \"$STORAGE/$BASE.pat\" !"
|
||||
fi
|
||||
|
||||
mv -f "$DSM_BOOT" "$STORAGE/$BASE.boot.img" || return $?
|
||||
setOwner "$STORAGE/$BASE.boot.img" || warn "failed to set the owner for \"$STORAGE/$BASE.boot.img\" !"
|
||||
|
||||
rm -rf "$tmp" || return $?
|
||||
return 0
|
||||
}
|
||||
|
||||
installDSM() {
|
||||
|
||||
rm -rf /tmp/dsm || return $?
|
||||
rm -f "$QEMU_DIR/dsm.url" || return $?
|
||||
|
||||
checkSse42 || return $?
|
||||
reservePorts || return $?
|
||||
prepareMac || return $?
|
||||
|
||||
local patName="boot.pat"
|
||||
local patDir patFile
|
||||
|
||||
# Persist the exact PAT base name so future starts reopen the matching boot,
|
||||
# system, and cached installation files.
|
||||
if [ -f "$STORAGE/dsm.ver" ]; then
|
||||
BASE=$(<"$STORAGE/dsm.ver") || return $?
|
||||
BASE="${BASE//[![:print:]]/}"
|
||||
[ -z "$BASE" ] && BASE="DSM_VirtualDSM_69057"
|
||||
else
|
||||
# Fallback for old installs
|
||||
BASE="DSM_VirtualDSM_42962"
|
||||
fi
|
||||
|
||||
patDir=$(find / -maxdepth 1 -type d -iname "$patName" -print -quit) || return $?
|
||||
|
||||
if [ ! -d "$patDir" ]; then
|
||||
patDir=$(find "$STORAGE" -maxdepth 1 -type d -iname "$patName" -print -quit) || return $?
|
||||
fi
|
||||
|
||||
# A boot.pat directory bind represents already extracted boot and system
|
||||
# images and therefore takes precedence over PAT file or URL discovery.
|
||||
if [ -d "$patDir" ]; then
|
||||
BASE="DSM_VirtualDSM"
|
||||
URL="file://$patDir"
|
||||
|
||||
if [[ ! -s "$STORAGE/$BASE.boot.img" || ! -s "$STORAGE/$BASE.system.img" ]]; then
|
||||
error "The bind $patDir maps to a file that does not exist!"
|
||||
return 65
|
||||
fi
|
||||
fi
|
||||
|
||||
patFile=$(find / -maxdepth 1 -type f -iname "$patName" -print -quit) || return $?
|
||||
|
||||
if [ ! -s "$patFile" ]; then
|
||||
patFile=$(find "$STORAGE" -maxdepth 1 -type f -iname "$patName" -print -quit) || return $?
|
||||
fi
|
||||
|
||||
if [ -s "$patFile" ]; then
|
||||
BASE="DSM_VirtualDSM"
|
||||
URL="file://$patFile"
|
||||
fi
|
||||
|
||||
URL=$(strip "$URL") || return $?
|
||||
|
||||
# Derive a filesystem-safe identity from the URL only when no local boot.pat
|
||||
# source was supplied; preserve an existing system image identity if present.
|
||||
if [ -n "$URL" ] && [ ! -s "$patFile" ] && [ ! -d "$patDir" ]; then
|
||||
BASE=$(basename "$URL" .pat) || return $?
|
||||
|
||||
if [ ! -s "$STORAGE/$BASE.system.img" ]; then
|
||||
BASE=$(sanitizePatBase "$URL") || return $?
|
||||
fi
|
||||
|
||||
if [[ "${URL,,}" != "http"* && "${URL,,}" != "file:"* ]]; then
|
||||
if [ ! -s "$STORAGE/$BASE.pat" ]; then
|
||||
error "Invalid URL: $URL"
|
||||
return 65
|
||||
fi
|
||||
|
||||
URL="file://$STORAGE/$BASE.pat"
|
||||
fi
|
||||
fi
|
||||
|
||||
# A complete matching image pair is the installation marker; the cached PAT
|
||||
# itself is optional after installation.
|
||||
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..." || return $?
|
||||
|
||||
local mirror=""
|
||||
local chinaMirror="https://cndl.synology.cn/download/DSM"
|
||||
local globalMirror="https://global.synologydownload.com/download/DSM"
|
||||
|
||||
[[ "${URL,,}" == *"cndl.synology"* ]] && mirror="$chinaMirror"
|
||||
[[ "${URL,,}" == *"global.synology"* ]] && mirror="$globalMirror"
|
||||
|
||||
# Honor an explicitly selected Synology mirror first, otherwise choose the
|
||||
# China or global endpoint from the detected country.
|
||||
if [ -z "$mirror" ]; then
|
||||
if [ -z "$COUNTRY" ]; then
|
||||
setCountry || return $?
|
||||
fi
|
||||
|
||||
[ -z "$COUNTRY" ] && info "Warning: could not detect country to select mirror!"
|
||||
[[ "${COUNTRY^^}" == "CN" ]] && mirror="$chinaMirror" || mirror="$globalMirror"
|
||||
fi
|
||||
|
||||
if [ -z "$URL" ]; then
|
||||
URL="$mirror/release/7.2.2/72806/DSM_VirtualDSM_72806.pat"
|
||||
fi
|
||||
|
||||
if [ ! -s "$patFile" ]; then
|
||||
BASE=$(sanitizePatBase "$URL") || return $?
|
||||
fi
|
||||
|
||||
if [[ "$URL" != "file://$STORAGE/$BASE.pat" ]]; then
|
||||
rm -f "$STORAGE/$BASE.pat" || return $?
|
||||
fi
|
||||
|
||||
rm -f "$STORAGE/$BASE.agent" || return $?
|
||||
rm -f "$STORAGE/$BASE.boot.img" || return $?
|
||||
rm -f "$STORAGE/$BASE.system.img" || return $?
|
||||
|
||||
# Check filesystem
|
||||
local fs
|
||||
fs=$(stat -f -c %T "$STORAGE") || return $?
|
||||
checkDsmFilesystem "$fs" || return $?
|
||||
|
||||
local tmp
|
||||
|
||||
# Extract beside storage on Unix filesystems to avoid container-space limits;
|
||||
# use /tmp for filesystems that cannot safely host the installer workspace.
|
||||
if [[ "${fs,,}" != "exfat"* && "${fs,,}" != "ntfs"* && "${fs,,}" != "unknown"* ]]; then
|
||||
tmp="$STORAGE/tmp"
|
||||
rm -rf "$tmp" || return $?
|
||||
|
||||
if ! makeDir "$tmp"; then
|
||||
error "Failed to create directory \"$tmp\" !"
|
||||
return 93
|
||||
fi
|
||||
else
|
||||
tmp="/tmp/dsm"
|
||||
local tmpSpace=2147483648
|
||||
local space available
|
||||
|
||||
space=$(df --output=avail -B 1 /tmp | tail -n 1) || return $?
|
||||
available=$(formatBytes "$space") || return $?
|
||||
|
||||
if (( tmpSpace > space )); then
|
||||
error "Not enough free space inside the container, have $available available but need at least 2 GB."
|
||||
return 93
|
||||
fi
|
||||
|
||||
rm -rf "$tmp" || return $?
|
||||
mkdir -p "$tmp" || return $?
|
||||
fi
|
||||
|
||||
# Check free diskspace
|
||||
checkDsmSpace || return $?
|
||||
|
||||
local pat="/$BASE.pat"
|
||||
downloadPat "$pat" || return $?
|
||||
|
||||
local size
|
||||
size=$(stat -c%s "$pat") || return $?
|
||||
|
||||
# Full Virtual DSM PAT files are substantially larger than update packs;
|
||||
# reject undersized inputs before attempting destructive image preparation.
|
||||
if (( size < 250000000 )); then
|
||||
error "The specified PAT file is probably an update pack as it's too small."
|
||||
return 62
|
||||
fi
|
||||
|
||||
extractPat "$pat" "$tmp" "$size" || return $?
|
||||
createSystemImage "$tmp" "$fs" || return $?
|
||||
installSystemPartition "$tmp" || return $?
|
||||
finishDsmInstall "$pat" "$tmp" || return $?
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
installDSM || exit $?
|
||||
return 0
|
||||
+220
@@ -0,0 +1,220 @@
|
||||
#!/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"
|
||||
|
||||
finish() {
|
||||
|
||||
local reason=$1 failed=0
|
||||
|
||||
# A nonzero exit is unexpected only when QEMU_END is missing.
|
||||
if [ ! -f "$QEMU_END" ] && (( reason != 0 )); then
|
||||
failed=1
|
||||
fi
|
||||
|
||||
touch "$QEMU_END" || :
|
||||
|
||||
forceKillQemu "$reason"
|
||||
cleanupHelpers "$HOST_PID"
|
||||
|
||||
fKill "print.sh"
|
||||
rm -f -- "$HOST_API_SOCKET" "$HOST_AGENT_SOCKET"
|
||||
|
||||
if ! waitQemuExit 10; then
|
||||
warn "Timed out while waiting for $(app) to exit!"
|
||||
fi
|
||||
|
||||
stopConsole
|
||||
echo
|
||||
|
||||
if (( failed == 0 )); then
|
||||
echo "❯ Shutdown completed!"
|
||||
else
|
||||
error "QEMU exited unexpectedly!"
|
||||
fi
|
||||
|
||||
exit "$reason"
|
||||
}
|
||||
|
||||
sendGuestShutdown() {
|
||||
|
||||
local pid="$1"
|
||||
local response
|
||||
|
||||
# Virtual DSM ignores ACPI powerdown, so graceful shutdown must go through
|
||||
# the qemu-host guest API exposed on the Unix socket.
|
||||
# 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() {
|
||||
|
||||
# Divide the remaining timeout into guest wait, SIGTERM grace, and final
|
||||
# cleanup instead of allowing the API call to consume the entire budget.
|
||||
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
|
||||
|
||||
# The process state is authoritative, but disappearance of both pidfiles
|
||||
# also ends the wait when a wrapper exits before process reaping completes.
|
||||
# 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
|
||||
}
|
||||
|
||||
gracefulShutdown() {
|
||||
|
||||
local sig="$1"
|
||||
local pid code
|
||||
|
||||
[[ $BASHPID != "$TRAP_PID" ]] && return
|
||||
|
||||
code=$(signalCode "$sig")
|
||||
|
||||
if (( SHUTDOWN_SIGNAL != 0 )); then
|
||||
|
||||
# A second Ctrl-C is the explicit user request to skip the remaining
|
||||
# graceful-shutdown wait and proceed to forced cleanup.
|
||||
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
|
||||
|
||||
start=$SECONDS
|
||||
SHUTDOWN_SIGNAL=$code
|
||||
|
||||
# Shutdown handlers must continue through missing processes and failed cleanup
|
||||
# commands instead of being aborted by errexit.
|
||||
set +e
|
||||
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"
|
||||
}
|
||||
|
||||
enableTrap() {
|
||||
|
||||
enabled "$SHUTDOWN" || return 0
|
||||
|
||||
# Keep Ctrl-C available to interactive users without installing an unnecessary
|
||||
# SIGINT handler for background/container execution.
|
||||
if interactive; then
|
||||
_trap gracefulShutdown SIGINT
|
||||
fi
|
||||
|
||||
_trap gracefulShutdown SIGTERM SIGHUP SIGABRT SIGQUIT
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
[ -n "${QEMU_TIMEOUT:-}" ] && TIMEOUT="$QEMU_TIMEOUT"
|
||||
|
||||
return 0
|
||||
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
addSystemDisks() {
|
||||
|
||||
local boot="$STORAGE/$BASE.boot.img"
|
||||
local system="$STORAGE/$BASE.system.img"
|
||||
|
||||
[ ! -s "$boot" ] && error "Virtual DSM boot-image does not exist ($boot)" && return 81
|
||||
[ ! -s "$system" ] && error "Virtual DSM system-image does not exist ($system)" && return 82
|
||||
|
||||
setOwner "$boot" || warn "failed to set the owner for \"$boot\" !"
|
||||
setOwner "$system" || warn "failed to set the owner for \"$system\" !"
|
||||
|
||||
DISK_OPTS+=$(createDevice "$boot" "$DISK_TYPE" "1" "0xa" "raw" "$DISK_IO" "$DISK_CACHE" "" "" "synoboot")
|
||||
DISK_OPTS+=$(createDevice "$system" "$DISK_TYPE" "2" "0xb" "raw" "$DISK_IO" "$DISK_CACHE" "" "" "synosys")
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
closeWebserver() {
|
||||
|
||||
disabled "${NETWORK:-Y}" && return 0
|
||||
|
||||
MSG="Booting DSM instance..."
|
||||
html "$MSG" || return $?
|
||||
|
||||
if enabled "${DHCP:-N}" || disabled "${WEB:-}"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
writeAtomic "$WSD_COMMAND" "portal" || return $?
|
||||
|
||||
sleep 1.2
|
||||
stopAllServers
|
||||
|
||||
WEB="N"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
addSystemDisks || return $?
|
||||
closeWebserver || return $?
|
||||
|
||||
return 0
|
||||
+228
@@ -0,0 +1,228 @@
|
||||
#!/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.host"
|
||||
shutdown="/run/shm/qemu.end"
|
||||
command="/run/shm/status.cmd"
|
||||
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() {
|
||||
|
||||
# Query DSM through the qemu-host sidecar rather than the guest network,
|
||||
# which may not be configured yet.
|
||||
{ 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() {
|
||||
|
||||
# Keep polling until the guest reports a usable address, but stop promptly
|
||||
# when container shutdown begins.
|
||||
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"
|
||||
writeAtomic "$command" "portal http://$location"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
buildStaticMessage() {
|
||||
|
||||
local nic ip
|
||||
|
||||
nic=$(<"$driver")
|
||||
ip=$(<"$address")
|
||||
local port="${location##*:}"
|
||||
|
||||
# NAT and user-mode networking are reached through a forwarded host port;
|
||||
# macvlan exposes DSM directly on the container-facing LAN address.
|
||||
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
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
# Docker environment variables
|
||||
|
||||
: "${HOST_MAC:=""}"
|
||||
: "${HOST_CPU:=""}"
|
||||
: "${HOST_DEBUG:=""}"
|
||||
: "${HOST_MODEL:=""}"
|
||||
: "${HOST_SERIAL:=""}"
|
||||
: "${GUEST_SERIAL:=""}"
|
||||
|
||||
# Sanitize variables
|
||||
HOST_MAC=$(strip "$HOST_MAC")
|
||||
HOST_CPU=$(strip "$HOST_CPU")
|
||||
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() {
|
||||
|
||||
if [ -z "$HOST_MAC" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
HOST_MAC="${HOST_MAC//-/:}"
|
||||
|
||||
if [[ ${#HOST_MAC} == 12 ]]; then
|
||||
local 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
|
||||
}
|
||||
|
||||
configureHostCpuName() {
|
||||
|
||||
if [ -z "$HOST_CPU" ]; then
|
||||
[[ "${CPU,,}" != "unknown" ]] && HOST_CPU="$CPU"
|
||||
fi
|
||||
|
||||
if [ -n "$HOST_CPU" ]; then
|
||||
# qemu-host expects a comma-separated CPU description with empty family
|
||||
# and suffix fields, not QEMU's -cpu syntax.
|
||||
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
|
||||
}
|
||||
|
||||
buildHostArguments() {
|
||||
|
||||
# qemu-host is a sidecar that bridges DSM's proprietary serial agent to
|
||||
# Unix sockets used by shutdown and post-boot discovery helpers.
|
||||
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
|
||||
|
||||
# Remove stale sockets and pid state before starting the sidecar; a Unix
|
||||
# socket path cannot be rebound while an old filesystem entry remains.
|
||||
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
|
||||
|
||||
printf '%s\n' "$pid" > "$HOST_PID"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
waitForSocket() {
|
||||
|
||||
local socket="$1"
|
||||
local exit_code="$2"
|
||||
local timeout=5 pid
|
||||
local deadline=$((SECONDS + timeout))
|
||||
|
||||
# Do not start QEMU until both sidecar sockets are ready; otherwise the
|
||||
# VirtIO serial channel or API client may race initial creation.
|
||||
while [ ! -S "$socket" ]; do
|
||||
|
||||
if ! readPidFile pid "$HOST_PID" || ! isAlive "$pid"; then
|
||||
error "qemu-host exited unexpectedly!"
|
||||
exit "$exit_code"
|
||||
fi
|
||||
|
||||
if (( SECONDS >= deadline )); then
|
||||
error "Failed to create qemu-host socket: $socket"
|
||||
exit "$exit_code"
|
||||
fi
|
||||
|
||||
sleep 0.1
|
||||
done
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
configureSerialPorts() {
|
||||
|
||||
local bus
|
||||
bus=$(getPciBus)
|
||||
|
||||
# Managed interactive mode separates the console and QEMU monitor into
|
||||
# reconnecting sockets; other runs keep the simple combined stdio monitor.
|
||||
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=$bus,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
|
||||
configureHostCpuName
|
||||
|
||||
buildHostArguments
|
||||
startHostBinary
|
||||
|
||||
waitForSocket "$HOST_API_SOCKET" 58
|
||||
waitForSocket "$HOST_AGENT_SOCKET" 59
|
||||
|
||||
configureSerialPorts
|
||||
|
||||
return 0
|
||||
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
lastmsg=""
|
||||
lastcmd=""
|
||||
status="/run/shm/msg.html"
|
||||
command="/run/shm/status.cmd"
|
||||
dir=$(dirname -- "$status")
|
||||
status_name=$(basename -- "$status")
|
||||
command_name=$(basename -- "$command")
|
||||
|
||||
# websocketd clients use s: for status updates and c: for control commands.
|
||||
refreshStatus() {
|
||||
|
||||
[ ! -f "$status" ] && return 0
|
||||
[ ! -s "$status" ] && return 0
|
||||
|
||||
msg=$(< "$status") || return 0
|
||||
msg="${msg%$'\n'}"
|
||||
|
||||
[ -z "$msg" ] && return 0
|
||||
[[ "$msg" == "$lastmsg" ]] && return 0
|
||||
|
||||
lastmsg="$msg"
|
||||
echo "s: $msg"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
refreshCommand() {
|
||||
|
||||
[ ! -f "$command" ] && return 0
|
||||
[ ! -s "$command" ] && return 0
|
||||
|
||||
cmd=$(< "$command") || return 0
|
||||
cmd="${cmd%$'\n'}"
|
||||
|
||||
[ -z "$cmd" ] && return 0
|
||||
[[ "$cmd" == "$lastcmd" ]] && return 0
|
||||
|
||||
lastcmd="$cmd"
|
||||
echo "c: $cmd"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
refreshStatus
|
||||
refreshCommand
|
||||
|
||||
# Watch the directory because status and command updates use atomic rename.
|
||||
inotifywait \
|
||||
-m -q \
|
||||
-e close_write,moved_to \
|
||||
--format '%e %f' \
|
||||
"$dir" |
|
||||
while read -r event file; do
|
||||
|
||||
case "$file" in
|
||||
"$status_name" )
|
||||
case "${event,,}" in
|
||||
"close_write"* | "moved_to"* )
|
||||
refreshStatus ;;
|
||||
esac ;;
|
||||
"$command_name" )
|
||||
case "${event,,}" in
|
||||
"close_write"* | "moved_to"* )
|
||||
refreshCommand ;;
|
||||
esac ;;
|
||||
esac
|
||||
|
||||
done
|
||||
@@ -0,0 +1,54 @@
|
||||
server {
|
||||
|
||||
listen 5000 default_server;
|
||||
|
||||
autoindex on;
|
||||
tcp_nodelay on;
|
||||
server_tokens off;
|
||||
absolute_redirect off;
|
||||
|
||||
error_log /dev/null;
|
||||
access_log /dev/null;
|
||||
|
||||
auth_basic off;
|
||||
auth_basic_user_file /etc/nginx/.htpasswd;
|
||||
|
||||
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;
|
||||
|
||||
set $cache_control "no-cache";
|
||||
|
||||
if ($uri = /msg.html) {
|
||||
set $cache_control "no-store";
|
||||
}
|
||||
|
||||
add_header Cache-Control $cache_control always;
|
||||
|
||||
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://unix:/run/shm/status-ws.sock:/;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,630 @@
|
||||
var timer;
|
||||
var request;
|
||||
var failureTimer;
|
||||
var navigationTimer;
|
||||
var booting = false;
|
||||
var portal = false;
|
||||
var portalUrl = "";
|
||||
var interval = 1000;
|
||||
var lastStatus = "";
|
||||
|
||||
function abortRequest() {
|
||||
|
||||
if (!request) {
|
||||
return false;
|
||||
}
|
||||
|
||||
request.onreadystatechange = null;
|
||||
request.abort();
|
||||
request = null;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function clearFailure() {
|
||||
|
||||
if (!failureTimer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
clearTimeout(failureTimer);
|
||||
failureTimer = null;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function connectionLost() {
|
||||
|
||||
if (booting || (portal && portalUrl.length == 0) ||
|
||||
document.hidden || failureTimer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
failureTimer = setTimeout(function() {
|
||||
|
||||
if (booting || (portal && portalUrl.length == 0) ||
|
||||
document.hidden) {
|
||||
failureTimer = null;
|
||||
return;
|
||||
}
|
||||
|
||||
setStopped();
|
||||
}, interval * 3);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function clearNavigation() {
|
||||
|
||||
if (!navigationTimer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
clearTimeout(navigationTimer);
|
||||
navigationTimer = null;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function visibilityChanged() {
|
||||
|
||||
clearFailure();
|
||||
clearNavigation();
|
||||
|
||||
if (document.hidden) {
|
||||
return false;
|
||||
}
|
||||
|
||||
getInfo();
|
||||
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 redirect(url) {
|
||||
|
||||
if (document.hidden || navigationTimer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
navigationTimer = setTimeout(function() {
|
||||
navigationTimer = null;
|
||||
window.location.assign(url);
|
||||
}, 3000);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function beginPortal(url) {
|
||||
|
||||
portal = true;
|
||||
portalUrl = url || "";
|
||||
booting = false;
|
||||
|
||||
clearFailure();
|
||||
setInfo("Connecting to web portal", true);
|
||||
schedule();
|
||||
|
||||
if (portalUrl.length > 0) {
|
||||
redirect(portalUrl);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function processCommand(msg) {
|
||||
|
||||
if (msg == "portal") {
|
||||
return beginPortal("");
|
||||
}
|
||||
|
||||
if (msg.indexOf("portal ") == 0) {
|
||||
return beginPortal(msg.substring(7));
|
||||
}
|
||||
|
||||
console.warn("Unknown command: " + msg);
|
||||
return false;
|
||||
}
|
||||
|
||||
function processMsg(msg) {
|
||||
|
||||
rememberStatus(msg);
|
||||
setInfo(msg);
|
||||
|
||||
if (msg.toLowerCase().indexOf("href=") !== -1) {
|
||||
var div = document.createElement("div");
|
||||
div.innerHTML = msg;
|
||||
return beginPortal(div.querySelector("a").href);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function processInfo() {
|
||||
|
||||
if (request.readyState != 4) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var response = request;
|
||||
request = null;
|
||||
|
||||
var status = response.status;
|
||||
|
||||
if (status == 502 || status == 503 || status == 504) {
|
||||
connectionLost();
|
||||
schedule();
|
||||
return true;
|
||||
}
|
||||
|
||||
var msg = response.responseText;
|
||||
if (msg == null || msg.length == 0) {
|
||||
connectionLost();
|
||||
schedule();
|
||||
return false;
|
||||
}
|
||||
|
||||
var notFound = (status == 404);
|
||||
|
||||
if (status == 200) {
|
||||
if (msg.toLowerCase().indexOf("<html>") !== -1) {
|
||||
notFound = true;
|
||||
} else {
|
||||
clearFailure();
|
||||
processMsg(msg);
|
||||
|
||||
if (portalUrl.length > 0) {
|
||||
setInfo("Connecting to web portal", true);
|
||||
redirect(portalUrl);
|
||||
} else {
|
||||
schedule();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (notFound) {
|
||||
clearFailure();
|
||||
booting = false;
|
||||
setInfo("Connecting to web portal", true);
|
||||
|
||||
if (portalUrl.length > 0) {
|
||||
redirect(portalUrl);
|
||||
} else {
|
||||
portal = true;
|
||||
reload();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
setError("Error: Received statuscode " + status);
|
||||
return false;
|
||||
}
|
||||
|
||||
function extractContent(s) {
|
||||
var span = document.createElement('span');
|
||||
span.innerHTML = s;
|
||||
return span.textContent || span.innerText;
|
||||
};
|
||||
|
||||
function escapeContent(s) {
|
||||
var span = document.createElement('span');
|
||||
span.textContent = s;
|
||||
return span.innerHTML;
|
||||
}
|
||||
|
||||
function rememberStatus(msg) {
|
||||
|
||||
var text = extractContent(msg).trim();
|
||||
if (text.length == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (text.includes("Booting ")) {
|
||||
booting = true;
|
||||
}
|
||||
|
||||
lastStatus = text;
|
||||
return true;
|
||||
}
|
||||
|
||||
function parseSize(value, unit) {
|
||||
|
||||
var powers = {
|
||||
"B": 0,
|
||||
"KB": 1,
|
||||
"KIB": 1,
|
||||
"MB": 2,
|
||||
"MIB": 2,
|
||||
"GB": 3,
|
||||
"GIB": 3,
|
||||
"TB": 4,
|
||||
"TIB": 4,
|
||||
"PB": 5,
|
||||
"PIB": 5,
|
||||
"EB": 6,
|
||||
"EIB": 6
|
||||
};
|
||||
|
||||
unit = unit.toUpperCase();
|
||||
|
||||
if (!Object.prototype.hasOwnProperty.call(powers, unit)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var bytes = Number(value) * Math.pow(1024, powers[unit]);
|
||||
|
||||
if (!Number.isFinite(bytes) || bytes < 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
function estimateProgress(bytes) {
|
||||
|
||||
var boundary = 512 * 1024 * 1024;
|
||||
var previousBoundary = 0;
|
||||
|
||||
while (bytes > boundary) {
|
||||
previousBoundary = boundary;
|
||||
boundary *= 2;
|
||||
}
|
||||
|
||||
if (previousBoundary === 0) {
|
||||
return Math.min(bytes / boundary * 100, 99.9);
|
||||
}
|
||||
|
||||
var rangeProgress =
|
||||
(bytes - previousBoundary) /
|
||||
(boundary - previousBoundary);
|
||||
|
||||
return Math.min(30 + Math.pow(rangeProgress, 2) * 70, 99.9);
|
||||
}
|
||||
|
||||
function parseProgress(msg) {
|
||||
|
||||
var container = document.createElement("div");
|
||||
container.innerHTML = msg;
|
||||
|
||||
var walker = document.createTreeWalker(
|
||||
container,
|
||||
NodeFilter.SHOW_TEXT
|
||||
);
|
||||
|
||||
var node;
|
||||
var lastNode = null;
|
||||
|
||||
while ((node = walker.nextNode())) {
|
||||
if (node.nodeValue.trim() !== "") {
|
||||
lastNode = node;
|
||||
}
|
||||
}
|
||||
|
||||
if (!lastNode) {
|
||||
return {
|
||||
message: msg,
|
||||
progress: null,
|
||||
size: null
|
||||
};
|
||||
}
|
||||
|
||||
var percentMatch = lastNode.nodeValue.match(
|
||||
/\s+\((\d+(?:\.\d+)?)%\)\s*$/
|
||||
);
|
||||
|
||||
if (percentMatch) {
|
||||
var progress = Number(percentMatch[1]);
|
||||
|
||||
if (Number.isFinite(progress) && progress >= 0 && progress <= 100) {
|
||||
lastNode.nodeValue = lastNode.nodeValue.slice(
|
||||
0,
|
||||
percentMatch.index
|
||||
);
|
||||
|
||||
return {
|
||||
message: container.innerHTML,
|
||||
progress: progress,
|
||||
size: null
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
var sizeMatch = lastNode.nodeValue.match(
|
||||
/\s+\((\d+(?:\.\d+)?)\s+([KMGTPE]?i?B)\)\s*$/i
|
||||
);
|
||||
|
||||
if (sizeMatch) {
|
||||
var bytes = parseSize(sizeMatch[1], sizeMatch[2]);
|
||||
|
||||
if (bytes != null) {
|
||||
var size = sizeMatch[1] + " " + sizeMatch[2];
|
||||
|
||||
return {
|
||||
message: msg,
|
||||
progress: estimateProgress(bytes),
|
||||
size: size
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
message: msg,
|
||||
progress: null,
|
||||
size: null
|
||||
};
|
||||
}
|
||||
|
||||
function resizeProgress() {
|
||||
|
||||
var info = document.getElementById("info");
|
||||
var progress = document.getElementById("progress");
|
||||
|
||||
if (!info || !progress || progress.hidden) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var style = window.getComputedStyle(info);
|
||||
var measurement = document.createElement("span");
|
||||
|
||||
measurement.textContent = info.innerText;
|
||||
measurement.style.position = "fixed";
|
||||
measurement.style.left = "-9999px";
|
||||
measurement.style.top = "-9999px";
|
||||
measurement.style.visibility = "hidden";
|
||||
measurement.style.whiteSpace = "nowrap";
|
||||
measurement.style.fontFamily = style.fontFamily;
|
||||
measurement.style.fontSize = style.fontSize;
|
||||
measurement.style.fontWeight = style.fontWeight;
|
||||
measurement.style.fontStyle = style.fontStyle;
|
||||
measurement.style.letterSpacing = style.letterSpacing;
|
||||
measurement.style.textTransform = style.textTransform;
|
||||
|
||||
document.body.appendChild(measurement);
|
||||
|
||||
var textWidth = measurement.getBoundingClientRect().width;
|
||||
var loading = info.getElementsByClassName("loading").length > 0;
|
||||
var dotsWidth = 0;
|
||||
|
||||
if (loading) {
|
||||
dotsWidth = parseFloat(style.fontSize) * 0.75;
|
||||
}
|
||||
|
||||
measurement.remove();
|
||||
|
||||
var maximumWidth = window.innerWidth * 0.8;
|
||||
var width = Math.min(textWidth + dotsWidth + 100, maximumWidth);
|
||||
|
||||
progress.style.width = width + "px";
|
||||
progress.style.transform = loading
|
||||
? "translateX(" + (dotsWidth / 2) + "px)"
|
||||
: "";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function setProgress(value, size) {
|
||||
|
||||
var progress = document.getElementById("progress");
|
||||
var fill = document.getElementById("progress-fill");
|
||||
|
||||
if (value == null) {
|
||||
progress.hidden = true;
|
||||
progress.removeAttribute("title");
|
||||
progress.removeAttribute("aria-valuenow");
|
||||
progress.removeAttribute("aria-valuetext");
|
||||
fill.style.width = "0%";
|
||||
return true;
|
||||
}
|
||||
|
||||
progress.hidden = false;
|
||||
progress.title = size != null ? size : value + "%";
|
||||
progress.setAttribute("aria-valuenow", value);
|
||||
|
||||
if (size != null) {
|
||||
progress.setAttribute("aria-valuetext", size);
|
||||
} else {
|
||||
progress.removeAttribute("aria-valuetext");
|
||||
}
|
||||
|
||||
fill.style.width = value + "%";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function setInfo(msg, loading, error) {
|
||||
|
||||
if (msg == null || msg.length == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var parsed = parseProgress(msg);
|
||||
msg = parsed.message;
|
||||
setProgress(parsed.progress, parsed.size);
|
||||
|
||||
var el = document.getElementById("info");
|
||||
|
||||
if (el.innerText == msg || el.innerHTML == msg) {
|
||||
var progressEl = document.getElementById("progress");
|
||||
if (progressEl && !progressEl.hidden && !progressEl.style.width) {
|
||||
resizeProgress();
|
||||
}
|
||||
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);
|
||||
resizeProgress();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
el.innerHTML = msg;
|
||||
resizeProgress();
|
||||
return true;
|
||||
}
|
||||
|
||||
function setError(text) {
|
||||
console.warn(text);
|
||||
return setInfo(text, false, true);
|
||||
}
|
||||
|
||||
function setStopped() {
|
||||
|
||||
var msg = "<span style=\"display:block\">The container has stopped.</span>";
|
||||
msg += "<span style=\"display:block; margin-top:1em; font-size:0.85em; font-weight:normal\">Check the container log for more details.</span>";
|
||||
|
||||
if (lastStatus.length > 0) {
|
||||
msg += "<span style=\"display:block; margin-top:1em; font-size:0.75em; font-weight:normal; color:rgba(255,255,255,0.65)\">[ Last status: " + escapeContent(lastStatus) + " ]</span>";
|
||||
}
|
||||
|
||||
return setError(msg);
|
||||
}
|
||||
|
||||
function schedule() {
|
||||
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(getInfo, interval);
|
||||
}
|
||||
|
||||
function reload() {
|
||||
|
||||
if (document.hidden) {
|
||||
return false;
|
||||
}
|
||||
|
||||
clearNavigation();
|
||||
|
||||
navigationTimer = setTimeout(function() {
|
||||
navigationTimer = null;
|
||||
window.location.replace(window.location.href);
|
||||
}, 3000);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function connect() {
|
||||
|
||||
var wsUrl = getURL() + "/status";
|
||||
var ws = new WebSocket(wsUrl);
|
||||
|
||||
ws.onopen = function(e) {
|
||||
|
||||
clearFailure();
|
||||
|
||||
if (portalUrl.length > 0) {
|
||||
redirect(portalUrl);
|
||||
}
|
||||
};
|
||||
|
||||
ws.onmessage = function(e) {
|
||||
|
||||
clearFailure();
|
||||
|
||||
var pos = e.data.indexOf(":");
|
||||
var cmd = e.data.substring(0, pos);
|
||||
var msg = e.data.substring(pos + 2);
|
||||
|
||||
switch (cmd) {
|
||||
case "s":
|
||||
|
||||
if (abortRequest()) {
|
||||
schedule();
|
||||
}
|
||||
|
||||
processMsg(msg);
|
||||
break;
|
||||
|
||||
case "c":
|
||||
|
||||
abortRequest();
|
||||
processCommand(msg);
|
||||
break;
|
||||
|
||||
case "e":
|
||||
|
||||
if (abortRequest()) {
|
||||
schedule();
|
||||
}
|
||||
|
||||
rememberStatus(msg);
|
||||
setError(msg);
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn("Unknown event: " + cmd);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
ws.onclose = function(e) {
|
||||
|
||||
connectionLost();
|
||||
|
||||
if (portal && portalUrl.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(function() {
|
||||
connect();
|
||||
}, interval);
|
||||
};
|
||||
|
||||
ws.onerror = function(e) {
|
||||
connectionLost();
|
||||
ws.close();
|
||||
};
|
||||
}
|
||||
|
||||
window.addEventListener("resize", resizeProgress);
|
||||
document.addEventListener("visibilitychange", visibilityChanged);
|
||||
rememberStatus(document.getElementById("info").innerHTML);
|
||||
|
||||
schedule();
|
||||
connect();
|
||||
Reference in New Issue
Block a user