mirror of
https://github.com/vdsm/virtual-dsm.git
synced 2026-08-29 12:17:07 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
@@ -0,0 +1,19 @@
|
|||||||
|
services:
|
||||||
|
dsm:
|
||||||
|
container_name: dsm
|
||||||
|
image: ghcr.io/vdsm/virtual-dsm
|
||||||
|
environment:
|
||||||
|
RAM_SIZE: "half"
|
||||||
|
DISK_SIZE: "max"
|
||||||
|
CPU_CORES: "max"
|
||||||
|
devices:
|
||||||
|
- /dev/kvm
|
||||||
|
- /dev/net/tun
|
||||||
|
cap_add:
|
||||||
|
- NET_ADMIN
|
||||||
|
ports:
|
||||||
|
- 5000:5000
|
||||||
|
volumes:
|
||||||
|
- ./dsm:/storage
|
||||||
|
restart: on-failure
|
||||||
|
stop_grace_period: 2m
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"name": "Virtual DSM",
|
||||||
|
"service": "dsm",
|
||||||
|
"forwardPorts": [5000],
|
||||||
|
"portsAttributes": {
|
||||||
|
"5000": {
|
||||||
|
"label": "Web",
|
||||||
|
"onAutoForward": "notify"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"otherPortsAttributes": {
|
||||||
|
"onAutoForward": "ignore"
|
||||||
|
},
|
||||||
|
"dockerComposeFile": "codespaces.yml"
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
.dockerignore
|
.dockerignore
|
||||||
|
.devcontainer
|
||||||
.git
|
.git
|
||||||
.github
|
.github
|
||||||
.gitignore
|
.gitignore
|
||||||
@@ -6,7 +7,10 @@
|
|||||||
.gitmodules
|
.gitmodules
|
||||||
Dockerfile
|
Dockerfile
|
||||||
Dockerfile.archive
|
Dockerfile.archive
|
||||||
|
compose.yml
|
||||||
|
compose.yaml
|
||||||
docker-compose.yml
|
docker-compose.yml
|
||||||
|
docker-compose.yaml
|
||||||
|
|
||||||
*.md
|
*.md
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
name: "\U0001F6A8 Technical issue"
|
||||||
|
description: When you're experiencing problems using the container
|
||||||
|
body:
|
||||||
|
- type: input
|
||||||
|
id: os
|
||||||
|
attributes:
|
||||||
|
label: Operating system
|
||||||
|
description: Your Linux distribution (can be shown by `lsb_release -a`).
|
||||||
|
placeholder: e.g. Ubuntu 24.04
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: summary
|
||||||
|
attributes:
|
||||||
|
label: Description
|
||||||
|
description: A clear and concise description of your issue.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: compose
|
||||||
|
attributes:
|
||||||
|
label: Docker compose
|
||||||
|
description: The compose file (or otherwise the `docker run` command used).
|
||||||
|
render: yaml
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: log
|
||||||
|
attributes:
|
||||||
|
label: Docker log
|
||||||
|
description: The logfile of the container (as shown by `docker logs dsm`).
|
||||||
|
render: shell
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: screenshot
|
||||||
|
attributes:
|
||||||
|
label: Screenshots (optional)
|
||||||
|
description: Screenshots that might help to make the problem more clear.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
name: "\U0001F680 Feature request"
|
||||||
|
description: Suggest an idea for improving the container
|
||||||
|
title: "[Feature]: "
|
||||||
|
labels: ["enhancement"]
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
id: problem
|
||||||
|
attributes:
|
||||||
|
label: Is your proposal related to a problem?
|
||||||
|
description: |
|
||||||
|
Provide a clear and concise description of what the problem is.
|
||||||
|
For example, "I'm always frustrated when..."
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: solution
|
||||||
|
attributes:
|
||||||
|
label: Describe the solution you'd like.
|
||||||
|
description: |
|
||||||
|
Provide a clear and concise description of what you want to happen.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: alternatives
|
||||||
|
attributes:
|
||||||
|
label: Describe alternatives you've considered.
|
||||||
|
description: |
|
||||||
|
Let us know about other solutions you've tried or researched.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: context
|
||||||
|
attributes:
|
||||||
|
label: Additional context
|
||||||
|
description: |
|
||||||
|
Is there anything else you can add about the proposal?
|
||||||
|
You might want to link to related issues here, if you haven't already.
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
name: "\U0001F41E Bug report"
|
||||||
|
description: Create a report to help us improve the container
|
||||||
|
title: "[Bug]: "
|
||||||
|
labels: ["bug"]
|
||||||
|
body:
|
||||||
|
- type: input
|
||||||
|
id: os
|
||||||
|
attributes:
|
||||||
|
label: Operating system
|
||||||
|
description: Your Linux distribution (can be shown by `lsb_release -a`).
|
||||||
|
placeholder: e.g. Ubuntu 24.04
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: summary
|
||||||
|
attributes:
|
||||||
|
label: Description
|
||||||
|
description: Describe the expected behaviour, the actual behaviour, and the steps to reproduce.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: compose
|
||||||
|
attributes:
|
||||||
|
label: Docker compose
|
||||||
|
description: The compose file (or otherwise the `docker run` command used).
|
||||||
|
render: yaml
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: log
|
||||||
|
attributes:
|
||||||
|
label: Docker log
|
||||||
|
description: The logfile of the container (as shown by `docker logs dsm`).
|
||||||
|
render: shell
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: screenshot
|
||||||
|
attributes:
|
||||||
|
label: Screenshots (optional)
|
||||||
|
description: Screenshots that might help to make the problem more clear.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
name: "\U00002753 General question"
|
||||||
|
description: Questions about the container not related to an issue
|
||||||
|
title: "[Question]: "
|
||||||
|
labels: ["question"]
|
||||||
|
body:
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: Is your question not already answered in the FAQ?
|
||||||
|
description: Please read the [FAQ](https://github.com/vdsm/virtual-dsm/blob/master/readme.md) carefully to avoid asking duplicate questions.
|
||||||
|
options:
|
||||||
|
- label: I made sure the question is not listed in the [FAQ](https://github.com/vdsm/virtual-dsm/blob/master/readme.md).
|
||||||
|
required: true
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: Is this a general question and not a technical issue?
|
||||||
|
description: For questions related to issues you must use the [technical issue](https://github.com/vdsm/virtual-dsm/issues/new?assignees=&labels=&projects=&template=1-issue.yml) form instead. It contains all the right fields (system info, logfiles, etc.) we need in order to be able to help you.
|
||||||
|
options:
|
||||||
|
- label: I am sure my question is not about a technical issue.
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: question
|
||||||
|
attributes:
|
||||||
|
label: Question
|
||||||
|
description: What's the question you have about the container?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
@@ -1,10 +1,21 @@
|
|||||||
version: 2
|
version: 2
|
||||||
|
|
||||||
updates:
|
updates:
|
||||||
- package-ecosystem: docker
|
- package-ecosystem: docker
|
||||||
directory: /
|
directory: /
|
||||||
schedule:
|
schedule:
|
||||||
interval: weekly
|
interval: weekly
|
||||||
|
cooldown:
|
||||||
|
default-days: 7
|
||||||
|
|
||||||
- package-ecosystem: github-actions
|
- package-ecosystem: github-actions
|
||||||
directory: /
|
directory: /
|
||||||
schedule:
|
schedule:
|
||||||
interval: weekly
|
interval: weekly
|
||||||
|
cooldown:
|
||||||
|
default-days: 7
|
||||||
|
ignore:
|
||||||
|
- dependency-name: "*"
|
||||||
|
update-types:
|
||||||
|
- version-update:semver-minor
|
||||||
|
- version-update:semver-patch
|
||||||
|
|||||||
+20
-18
@@ -2,16 +2,6 @@ name: Build
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
paths-ignore:
|
|
||||||
- '**/*.md'
|
|
||||||
- '**/*.yml'
|
|
||||||
- '.gitignore'
|
|
||||||
- '.dockerignore'
|
|
||||||
- '.github/**'
|
|
||||||
- '.github/workflows/**'
|
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: build
|
group: build
|
||||||
@@ -32,13 +22,13 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
name: Checkout
|
name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
-
|
-
|
||||||
name: Docker metadata
|
name: Docker metadata
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v5
|
uses: docker/metadata-action@v6
|
||||||
with:
|
with:
|
||||||
context: git
|
context: git
|
||||||
images: |
|
images: |
|
||||||
@@ -50,31 +40,31 @@ jobs:
|
|||||||
labels: |
|
labels: |
|
||||||
org.opencontainers.image.title=${{ vars.NAME }}
|
org.opencontainers.image.title=${{ vars.NAME }}
|
||||||
env:
|
env:
|
||||||
DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index
|
DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index
|
||||||
-
|
-
|
||||||
name: Set up Docker Buildx
|
name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v4
|
||||||
-
|
-
|
||||||
name: Login into Docker Hub
|
name: Login into Docker Hub
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v4
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
-
|
-
|
||||||
name: Login to GitHub Container Registry
|
name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v4
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
-
|
-
|
||||||
name: Build Docker image
|
name: Build Docker image
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v7
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: true
|
push: true
|
||||||
provenance: false
|
provenance: false
|
||||||
platforms: linux/amd64,linux/arm64,linux/arm
|
platforms: linux/amd64,linux/arm64
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
annotations: ${{ steps.meta.outputs.annotations }}
|
annotations: ${{ steps.meta.outputs.annotations }}
|
||||||
@@ -99,3 +89,15 @@ jobs:
|
|||||||
url: ${{ secrets.GITLAB_URL }}
|
url: ${{ secrets.GITLAB_URL }}
|
||||||
token: ${{ secrets.GITLAB_TOKEN }}
|
token: ${{ secrets.GITLAB_TOKEN }}
|
||||||
username: ${{ secrets.GITLAB_USERNAME }}
|
username: ${{ secrets.GITLAB_USERNAME }}
|
||||||
|
-
|
||||||
|
name: Send mail
|
||||||
|
uses: action-pack/send-mail@v1
|
||||||
|
with:
|
||||||
|
to: ${{secrets.MAILTO}}
|
||||||
|
from: Github Actions <${{secrets.MAILTO}}>
|
||||||
|
connection_url: ${{secrets.MAIL_CONNECTION}}
|
||||||
|
subject: Build of ${{ github.event.repository.name }} v${{ steps.meta.outputs.version }} completed
|
||||||
|
body: |
|
||||||
|
The build job of ${{ github.event.repository.name }} v${{ steps.meta.outputs.version }} was completed successfully!
|
||||||
|
|
||||||
|
See https://github.com/${{ github.repository }}/actions for more information.
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
on: [workflow_call]
|
on: [workflow_call]
|
||||||
|
|
||||||
name: "Check"
|
name: "Check"
|
||||||
|
|
||||||
permissions: {}
|
permissions: {}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -7,8 +9,29 @@ jobs:
|
|||||||
name: shellcheck
|
name: shellcheck
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
-
|
||||||
- name: Run ShellCheck
|
name: Checkout
|
||||||
uses: ludeeus/action-shellcheck@master
|
uses: actions/checkout@v7
|
||||||
env:
|
- parallel:
|
||||||
SHELLCHECK_OPTS: -x --source-path=src -e SC2001 -e SC2034 -e SC2064 -e SC2317 -e SC2153 -e SC2028
|
-
|
||||||
|
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
|
||||||
|
failure-threshold: warning
|
||||||
|
-
|
||||||
|
name: Validate JSON and YML files
|
||||||
|
uses: GrantBirki/json-yaml-validate@v5
|
||||||
|
|||||||
+12
-10
@@ -12,13 +12,15 @@ jobs:
|
|||||||
dockerHubDescription:
|
dockerHubDescription:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
-
|
||||||
-
|
name: Checkout repo
|
||||||
name: Docker Hub Description
|
uses: actions/checkout@v7
|
||||||
uses: peter-evans/dockerhub-description@v3
|
-
|
||||||
with:
|
name: Docker Hub Description
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
uses: peter-evans/dockerhub-description@v5
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
with:
|
||||||
repository: ${{ secrets.DOCKERHUB_REPO }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
short-description: ${{ github.event.repository.description }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
readme-filepath: ./readme.md
|
repository: ${{ secrets.DOCKERHUB_REPO }}
|
||||||
|
short-description: ${{ github.event.repository.description }}
|
||||||
|
readme-filepath: ./readme.md
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
name: "Review"
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
|
checks: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
review:
|
||||||
|
name: review
|
||||||
|
uses: action-pack/.github/.github/workflows/review.yml@master
|
||||||
@@ -1,10 +1,6 @@
|
|||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
|
||||||
- '**/*.sh'
|
|
||||||
- '.github/workflows/test.yml'
|
|
||||||
- '.github/workflows/check.yml'
|
|
||||||
|
|
||||||
name: "Test"
|
name: "Test"
|
||||||
permissions: {}
|
permissions: {}
|
||||||
|
|||||||
+268
-1
@@ -1 +1,268 @@
|
|||||||
build.sh
|
##############################
|
||||||
|
# Operating System Files
|
||||||
|
##############################
|
||||||
|
|
||||||
|
.DS_Store
|
||||||
|
.AppleDouble
|
||||||
|
.LSOverride
|
||||||
|
Thumbs.db
|
||||||
|
ehthumbs.db
|
||||||
|
Desktop.ini
|
||||||
|
Icon?
|
||||||
|
$RECYCLE.BIN/
|
||||||
|
.Spotlight-V100/
|
||||||
|
.Trashes/
|
||||||
|
.fseventsd
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# IDEs
|
||||||
|
##############################
|
||||||
|
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# VS Code
|
||||||
|
##############################
|
||||||
|
|
||||||
|
.history/
|
||||||
|
*.code-workspace
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# Vim
|
||||||
|
##############################
|
||||||
|
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
Session.vim
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# Sublime
|
||||||
|
##############################
|
||||||
|
|
||||||
|
*.sublime-workspace
|
||||||
|
*.sublime-project
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# Temporary Files
|
||||||
|
##############################
|
||||||
|
|
||||||
|
*.tmp
|
||||||
|
*.temp
|
||||||
|
*.bak
|
||||||
|
*.old
|
||||||
|
*.orig
|
||||||
|
*.rej
|
||||||
|
*.save
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# Logs
|
||||||
|
##############################
|
||||||
|
|
||||||
|
*.log
|
||||||
|
logs/
|
||||||
|
log/
|
||||||
|
*.out
|
||||||
|
*.err
|
||||||
|
*.trace
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# Runtime
|
||||||
|
##############################
|
||||||
|
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# Secrets
|
||||||
|
##############################
|
||||||
|
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
*.pem
|
||||||
|
*.key
|
||||||
|
*.crt
|
||||||
|
*.cer
|
||||||
|
*.p12
|
||||||
|
*.pfx
|
||||||
|
*.kdbx
|
||||||
|
*.secret
|
||||||
|
*.token
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# SSH
|
||||||
|
##############################
|
||||||
|
|
||||||
|
.ssh/
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# Docker
|
||||||
|
##############################
|
||||||
|
|
||||||
|
docker-compose.override.yml
|
||||||
|
docker-compose.local.yml
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# VM Storage
|
||||||
|
##############################
|
||||||
|
|
||||||
|
storage/
|
||||||
|
windows/
|
||||||
|
downloads/
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# Disk Images
|
||||||
|
##############################
|
||||||
|
|
||||||
|
*.qcow2
|
||||||
|
*.qcow
|
||||||
|
*.vhd
|
||||||
|
*.vhdx
|
||||||
|
*.vdi
|
||||||
|
*.raw
|
||||||
|
*.img
|
||||||
|
*.iso
|
||||||
|
*.bin
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# QEMU
|
||||||
|
##############################
|
||||||
|
|
||||||
|
*.nvram
|
||||||
|
*.fd
|
||||||
|
*.efi
|
||||||
|
*.sock
|
||||||
|
*.monitor
|
||||||
|
*.serial
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# Samba
|
||||||
|
##############################
|
||||||
|
|
||||||
|
shared/
|
||||||
|
share/
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# Backups
|
||||||
|
##############################
|
||||||
|
|
||||||
|
backup/
|
||||||
|
backups/
|
||||||
|
*.backup
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# Cache
|
||||||
|
##############################
|
||||||
|
|
||||||
|
.cache/
|
||||||
|
.cache-loader/
|
||||||
|
.tmp/
|
||||||
|
temp/
|
||||||
|
tmp/
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# Python
|
||||||
|
##############################
|
||||||
|
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
.pytest_cache/
|
||||||
|
.mypy_cache/
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# Node
|
||||||
|
##############################
|
||||||
|
|
||||||
|
node_modules/
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# Build
|
||||||
|
##############################
|
||||||
|
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
out/
|
||||||
|
release/
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# Coverage
|
||||||
|
##############################
|
||||||
|
|
||||||
|
coverage/
|
||||||
|
.coverage
|
||||||
|
coverage.xml
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# Archives
|
||||||
|
##############################
|
||||||
|
|
||||||
|
*.zip
|
||||||
|
*.tar
|
||||||
|
*.tar.gz
|
||||||
|
*.tgz
|
||||||
|
*.7z
|
||||||
|
*.rar
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# Generated Config
|
||||||
|
##############################
|
||||||
|
|
||||||
|
config.local.*
|
||||||
|
settings.local.*
|
||||||
|
local.env
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# Test Files
|
||||||
|
##############################
|
||||||
|
|
||||||
|
test-output/
|
||||||
|
playwright-report/
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# macOS
|
||||||
|
##############################
|
||||||
|
|
||||||
|
.AppleDB
|
||||||
|
.AppleDesktop
|
||||||
|
Network Trash Folder
|
||||||
|
Temporary Items
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# Linux
|
||||||
|
##############################
|
||||||
|
|
||||||
|
*~
|
||||||
|
.nfs*
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# Windows
|
||||||
|
##############################
|
||||||
|
|
||||||
|
*.stackdump
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# Misc
|
||||||
|
##############################
|
||||||
|
|
||||||
|
*.cache
|
||||||
|
*.lock
|
||||||
|
*.lock.json
|
||||||
|
*.bak.*
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# Keep Examples
|
||||||
|
##############################
|
||||||
|
|
||||||
|
!.gitkeep
|
||||||
|
!.env.example
|
||||||
|
|||||||
+77
-51
@@ -1,65 +1,91 @@
|
|||||||
FROM qemux/qemu-host as builder
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
# FROM golang as builder
|
|
||||||
# WORKDIR /
|
|
||||||
# RUN git clone https://github.com/qemus/qemu-host.git
|
|
||||||
# WORKDIR /qemu-host/src
|
|
||||||
# RUN go mod download
|
|
||||||
# RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o /qemu-host.bin .
|
|
||||||
|
|
||||||
|
FROM qemux/qemu-host:2.06 AS builder
|
||||||
FROM debian:trixie-slim
|
FROM debian:trixie-slim
|
||||||
|
|
||||||
|
ARG TARGETARCH
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
ARG DEBCONF_NOWARNINGS "yes"
|
|
||||||
ARG DEBIAN_FRONTEND "noninteractive"
|
|
||||||
ARG DEBCONF_NONINTERACTIVE_SEEN "true"
|
|
||||||
|
|
||||||
RUN if [ "$TARGETPLATFORM" != "linux/amd64" ]; then extra="qemu-user"; fi \
|
ARG VERSION_ARG="0.0"
|
||||||
&& apt-get update \
|
ARG VERSION_CSTRUCT="4.7"
|
||||||
&& apt-get --no-install-recommends -y install \
|
ARG VERSION_PASST="2026_07_28"
|
||||||
jq \
|
|
||||||
tini \
|
|
||||||
curl \
|
|
||||||
cpio \
|
|
||||||
wget \
|
|
||||||
fdisk \
|
|
||||||
unzip \
|
|
||||||
nginx \
|
|
||||||
procps \
|
|
||||||
xz-utils \
|
|
||||||
iptables \
|
|
||||||
iproute2 \
|
|
||||||
apt-utils \
|
|
||||||
dnsmasq \
|
|
||||||
fakeroot \
|
|
||||||
net-tools \
|
|
||||||
qemu-utils \
|
|
||||||
ca-certificates \
|
|
||||||
netcat-openbsd \
|
|
||||||
qemu-system-x86 \
|
|
||||||
"$extra" \
|
|
||||||
&& apt-get clean \
|
|
||||||
&& unlink /etc/nginx/sites-enabled/default \
|
|
||||||
&& sed -i 's/^worker_processes.*/worker_processes 1;/' /etc/nginx/nginx.conf \
|
|
||||||
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
|
||||||
|
|
||||||
COPY ./src /run/
|
ARG DEBCONF_NOWARNINGS="yes"
|
||||||
COPY ./web /var/www/
|
ARG DEBIAN_FRONTEND="noninteractive"
|
||||||
COPY --from=builder /qemu-host.bin /run/host.bin
|
ARG DEBCONF_NONINTERACTIVE_SEEN="true"
|
||||||
|
|
||||||
RUN chmod +x /run/*.sh && chmod +x /run/*.bin
|
RUN <<EOF
|
||||||
RUN mv /var/www/nginx.conf /etc/nginx/sites-enabled/web.conf
|
set -eu
|
||||||
|
|
||||||
|
apt-get update
|
||||||
|
apt-get --no-install-recommends -y install \
|
||||||
|
jq \
|
||||||
|
tini \
|
||||||
|
curl \
|
||||||
|
wget \
|
||||||
|
fdisk \
|
||||||
|
unzip \
|
||||||
|
nginx \
|
||||||
|
procps \
|
||||||
|
ipcalc \
|
||||||
|
ethtool \
|
||||||
|
python3 \
|
||||||
|
python3-pip \
|
||||||
|
python3-msgpack \
|
||||||
|
python3-pysodium \
|
||||||
|
xz-utils \
|
||||||
|
iptables \
|
||||||
|
iproute2 \
|
||||||
|
dnsmasq \
|
||||||
|
fakeroot \
|
||||||
|
apt-utils \
|
||||||
|
net-tools \
|
||||||
|
e2fsprogs \
|
||||||
|
diffutils \
|
||||||
|
qemu-utils \
|
||||||
|
websocketd \
|
||||||
|
iputils-ping \
|
||||||
|
inotify-tools \
|
||||||
|
ca-certificates \
|
||||||
|
netcat-openbsd \
|
||||||
|
qemu-system-x86
|
||||||
|
|
||||||
|
# Install Passt package
|
||||||
|
wget "https://github.com/qemus/passt/releases/download/v${VERSION_PASST}/passt_${VERSION_PASST}_${TARGETARCH}.deb" -O /tmp/passt.deb -q --timeout=10
|
||||||
|
dpkg -i /tmp/passt.deb
|
||||||
|
|
||||||
|
apt-get clean
|
||||||
|
|
||||||
|
# Install Python dependencies
|
||||||
|
pip3 install --no-cache-dir --break-system-packages --root-user-action=ignore "dissect.cstruct==$VERSION_CSTRUCT"
|
||||||
|
|
||||||
|
# Configure QEMU
|
||||||
|
mkdir -p /etc/qemu
|
||||||
|
echo "allow br0" > /etc/qemu/bridge.conf
|
||||||
|
|
||||||
|
# Configure nginx
|
||||||
|
unlink /etc/nginx/sites-enabled/default
|
||||||
|
sed -i 's/^worker_processes.*/worker_processes 1;/' /etc/nginx/nginx.conf
|
||||||
|
|
||||||
|
# Set version file
|
||||||
|
echo "$VERSION_ARG" > /etc/version
|
||||||
|
|
||||||
|
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||||
|
EOF
|
||||||
|
|
||||||
|
COPY --chmod=755 ./src /run/
|
||||||
|
COPY --chmod=755 ./web /var/www/
|
||||||
|
COPY --chmod=755 --from=builder /qemu-host.bin /run/host.bin
|
||||||
|
COPY --chmod=744 ./web/conf/nginx.conf /etc/nginx/default.conf
|
||||||
|
ADD --chmod=775 https://raw.githubusercontent.com/sud0woodo/patology/refs/heads/main/patology.py /run/extract.py
|
||||||
|
|
||||||
VOLUME /storage
|
VOLUME /storage
|
||||||
EXPOSE 22 139 445 5000
|
EXPOSE 22 139 445 5000
|
||||||
|
|
||||||
ENV RAM_SIZE "1G"
|
ENV RAM_SIZE="2G"
|
||||||
ENV DISK_SIZE "16G"
|
ENV CPU_CORES="2"
|
||||||
ENV CPU_CORES "1"
|
ENV DISK_SIZE="256G"
|
||||||
|
|
||||||
ARG VERSION_ARG "0.0"
|
HEALTHCHECK --interval=60s --start-period=45s --retries=2 CMD ["/run/check.sh"]
|
||||||
RUN echo "$VERSION_ARG" > /run/version
|
|
||||||
|
|
||||||
HEALTHCHECK --interval=60s --start-period=45s --retries=2 CMD /run/check.sh
|
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/bin/tini", "-s", "/run/entry.sh"]
|
ENTRYPOINT ["/usr/bin/tini", "-s", "/run/entry.sh"]
|
||||||
|
|||||||
+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,21 +0,0 @@
|
|||||||
version: "3"
|
|
||||||
services:
|
|
||||||
dsm:
|
|
||||||
container_name: dsm
|
|
||||||
image: vdsm/virtual-dsm:latest
|
|
||||||
environment:
|
|
||||||
DISK_SIZE: "16G"
|
|
||||||
RAM_SIZE: "1G"
|
|
||||||
CPU_CORES: "1"
|
|
||||||
devices:
|
|
||||||
- /dev/kvm
|
|
||||||
device_cgroup_rules:
|
|
||||||
- 'c *:* rwm'
|
|
||||||
cap_add:
|
|
||||||
- NET_ADMIN
|
|
||||||
ports:
|
|
||||||
- 5000:5000
|
|
||||||
volumes:
|
|
||||||
- /var/dsm:/storage
|
|
||||||
restart: on-failure
|
|
||||||
stop_grace_period: 2m
|
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
# Environment Variables
|
||||||
|
|
||||||
|
This page lists all the environment variables that can be used to configure the container.
|
||||||
|
|
||||||
|
An empty default means the variable is unset and its value is determined automatically when applicable.
|
||||||
|
|
||||||
|
## 💽 Virtual DSM
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `URL` | | URL or local path to a custom `.pat` installation file. |
|
||||||
|
| `COUNTRY` | | Country code used to select the Synology download mirror. |
|
||||||
|
| `HOST_MAC` | | MAC address reported to DSM. |
|
||||||
|
| `HOST_MODEL` | | Synology host model reported to DSM. |
|
||||||
|
| `HOST_SERIAL` | | Synology host serial number reported to DSM. |
|
||||||
|
| `GUEST_SERIAL` | | Synology guest serial number reported to DSM. |
|
||||||
|
|
||||||
|
## 🧠 CPU and Memory
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `CPU_CORES` | `2` | Number of virtual CPU cores, such as `4`, `half`, or `max`. |
|
||||||
|
| `CPU_MODEL` | `host` | QEMU CPU model. |
|
||||||
|
| `CPU_FLAGS` | | Additional QEMU CPU flags. |
|
||||||
|
| `HOST_CPU` | | CPU name reported to DSM. Selected automatically when unset. |
|
||||||
|
| `KVM` | `Y` | Enables KVM hardware acceleration. |
|
||||||
|
| `RAM_SIZE` | `2G` | Amount of RAM assigned to DSM, such as `2G`, `4G`, `half`, or `max`. |
|
||||||
|
| `RAM_CHECK` | `Y` | Checks whether enough host memory is available before starting DSM. |
|
||||||
|
|
||||||
|
## 💾 Storage
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `DISK_SIZE` | `256G` | Size of the main data disk. |
|
||||||
|
| `DISK_FMT` | `raw` | Disk image format: `raw` or `qcow2`. |
|
||||||
|
| `DISK_TYPE` | `scsi` | Disk device type, such as `sata`, `scsi`, `nvme`, or `blk`. |
|
||||||
|
| `DISK_CACHE` | `none` | Disk cache mode, such as `none` or `writeback`. |
|
||||||
|
| `DISK_IO` | `native` | Disk I/O mode, such as `native`, `threads`, or `io_uring`. |
|
||||||
|
| `DISK_DISCARD` | `unmap` | Discard/TRIM mode for the primary disk. |
|
||||||
|
| `DISK_ROTATION` | `1` | Rotation rate reported to the guest. Use `1` to identify the disk as an SSD. |
|
||||||
|
| `DISK_FLAGS` | | Additional options used when creating `qcow2` disks. |
|
||||||
|
| `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` | `dsm` | TAP or macvtap interface name. |
|
||||||
|
| `BRIDGE` | `docker` | Bridge name used for NAT networking. |
|
||||||
|
| `HOST_PORTS` | | Ports excluded from guest forwarding. |
|
||||||
|
| `USER_PORTS` | | Additional ports to forward to DSM when using user-mode networking. |
|
||||||
|
| `DNSMASQ_OPTS` | | Additional options passed to dnsmasq. |
|
||||||
|
| `DNSMASQ_DEBUG` | `N` | Enables dnsmasq debug output. |
|
||||||
|
| `DNSMASQ_DISABLE` | `N` | Disables the internal dnsmasq resolver. |
|
||||||
|
| `PASST_OPTS` | | Additional options passed to passt. |
|
||||||
|
| `PASST_DEBUG` | `N` | Enables passt debug output. |
|
||||||
|
|
||||||
|
## 🖥️ Display
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `DISPLAY` | `none` | Display backend, such as `vnc`, `disabled`, or `none`. |
|
||||||
|
| `LOSSY` | `N` | Enables lossy VNC compression to reduce bandwidth usage. |
|
||||||
|
| `VGA` | `none` | QEMU video adapter model. |
|
||||||
|
| `GPU` | `N` | Enables Intel iGPU acceleration. |
|
||||||
|
| `RENDERNODE` | `/dev/dri/renderD128` | Render node used for GPU acceleration. |
|
||||||
|
|
||||||
|
## ⚙️ System
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `MACHINE` | `q35` | QEMU machine type. |
|
||||||
|
| `ARGUMENTS` | | Additional raw arguments appended to the QEMU command line. |
|
||||||
|
|
||||||
|
## 🔌 Shutdown
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `SHUTDOWN` | `Y` | Enables graceful shutdown. |
|
||||||
|
| `TIMEOUT` | `115` | Maximum time, in seconds, to wait before forcing DSM to stop. |
|
||||||
|
| `API_TIMEOUT` | `90` | Maximum time, in seconds, to wait for the shutdown API call. |
|
||||||
|
|
||||||
|
## 🐞 Debugging
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `DEBUG` | `N` | Enables verbose debug output. |
|
||||||
|
| `TRACE` | `N` | Enables shell command tracing. |
|
||||||
|
| `HOST_DEBUG` | `N` | Enables debug output for the DSM host helper. |
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: dsm-pvc
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 256Gi
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: dsm
|
||||||
|
labels:
|
||||||
|
name: dsm
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
strategy:
|
||||||
|
type: Recreate
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: dsm
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: dsm
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: dsm
|
||||||
|
image: vdsm/virtual-dsm
|
||||||
|
env:
|
||||||
|
- name: DISK_SIZE
|
||||||
|
value: "256G"
|
||||||
|
ports:
|
||||||
|
- containerPort: 5000
|
||||||
|
name: http
|
||||||
|
protocol: TCP
|
||||||
|
securityContext:
|
||||||
|
capabilities:
|
||||||
|
add:
|
||||||
|
- NET_ADMIN
|
||||||
|
privileged: true
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: /storage
|
||||||
|
name: storage
|
||||||
|
- mountPath: /dev/kvm
|
||||||
|
name: dev-kvm
|
||||||
|
- mountPath: /dev/net/tun
|
||||||
|
name: dev-tun
|
||||||
|
terminationGracePeriodSeconds: 120
|
||||||
|
volumes:
|
||||||
|
- name: storage
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: dsm-pvc
|
||||||
|
- hostPath:
|
||||||
|
path: /dev/kvm
|
||||||
|
name: dev-kvm
|
||||||
|
- hostPath:
|
||||||
|
path: /dev/net/tun
|
||||||
|
type: CharDevice
|
||||||
|
name: dev-tun
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: dsm
|
||||||
|
spec:
|
||||||
|
internalTrafficPolicy: Cluster
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 5000
|
||||||
|
protocol: TCP
|
||||||
|
targetPort: 5000
|
||||||
|
selector:
|
||||||
|
app: dsm
|
||||||
|
type: ClusterIP
|
||||||
@@ -1,251 +1,298 @@
|
|||||||
<h1 align="center">Virtual DSM<br />
|
<h1 align="center">Virtual DSM<br />
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<img src="https://github.com/vdsm/virtual-dsm/raw/master/.github/screen.jpg" title="Screenshot" style="max-width:100%;" width="432" />
|
<a href="https://github.com/vdsm/virtual-dsm"><img src="https://github.com/vdsm/virtual-dsm/raw/master/.github/screen.jpg" title="Screenshot" style="max-width:100%;" width="432" /></a>
|
||||||
</div>
|
</div>
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
[![Build]][build_url]
|
[![Build]][build_url]
|
||||||
[![Version]][tag_url]
|
[![Version]][tag_url]
|
||||||
[![Size]][tag_url]
|
[![Size]][tag_url]
|
||||||
|
[![Package]][pkg_url]
|
||||||
[![Pulls]][hub_url]
|
[![Pulls]][hub_url]
|
||||||
|
|
||||||
</div></h1>
|
</div></h1>
|
||||||
|
|
||||||
Virtual DSM in a docker container.
|
Virtual DSM in a Docker container.
|
||||||
|
|
||||||
## Features
|
## Features ✨
|
||||||
|
|
||||||
- Multiple disks
|
- Runs Virtual DSM inside a Docker container
|
||||||
- KVM acceleration
|
- Automatic download of the installation files
|
||||||
- GPU pass-through
|
- Web-based access to the DSM desktop
|
||||||
- Upgrades supported
|
- Near-native performance with KVM acceleration
|
||||||
|
- Customizable CPU, memory, and storage allocation
|
||||||
## Usage
|
- Supports multiple disks and physical disk passthrough
|
||||||
|
- Supports NAT, user-mode, macvlan, and macvtap networking
|
||||||
|
|
||||||
Via `docker-compose.yml`
|
## Usage 🐳
|
||||||
|
|
||||||
|
##### Docker Compose:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
version: "3"
|
|
||||||
services:
|
services:
|
||||||
dsm:
|
dsm:
|
||||||
container_name: dsm
|
container_name: dsm
|
||||||
image: vdsm/virtual-dsm
|
image: vdsm/virtual-dsm
|
||||||
environment:
|
environment:
|
||||||
DISK_SIZE: "16G"
|
DISK_SIZE: "256G"
|
||||||
devices:
|
devices:
|
||||||
- /dev/kvm
|
- /dev/kvm
|
||||||
|
- /dev/net/tun
|
||||||
cap_add:
|
cap_add:
|
||||||
- NET_ADMIN
|
- NET_ADMIN
|
||||||
ports:
|
ports:
|
||||||
- 5000:5000
|
- 5000:5000
|
||||||
volumes:
|
volumes:
|
||||||
- /var/dsm:/storage
|
- ./dsm:/storage
|
||||||
restart: on-failure
|
restart: always
|
||||||
stop_grace_period: 2m
|
stop_grace_period: 2m
|
||||||
```
|
```
|
||||||
|
|
||||||
Via `docker run`
|
##### Docker CLI:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run -it --rm -p 5000:5000 --device=/dev/kvm --cap-add NET_ADMIN --stop-timeout 60 vdsm/virtual-dsm
|
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 use it?
|
```shell
|
||||||
|
kubectl apply -f https://raw.githubusercontent.com/vdsm/virtual-dsm/refs/heads/master/kubernetes.yml
|
||||||
|
```
|
||||||
|
|
||||||
Very simple! These are the steps:
|
##### GitHub Codespaces:
|
||||||
|
|
||||||
- Start the container and get some coffee.
|
|
||||||
|
|
||||||
- Connect to [port 5000](http://localhost:5000) of the container in your web browser.
|
[](https://codespaces.new/vdsm/virtual-dsm)
|
||||||
|
|
||||||
- Wait until DSM is ready, choose an username and password, and you will be taken to the desktop.
|
## Requirements ⚙️
|
||||||
|
|
||||||
- Enjoy your brand new machine, and don't forget to star this repo!
|
- Docker or Podman on a Linux host with KVM support.
|
||||||
|
- Docker Desktop or Podman (Desktop) on Windows 11 with nested virtualization enabled.
|
||||||
|
- At least 2 GB of available RAM.
|
||||||
|
- At least 32 GB of free disk space.
|
||||||
|
|
||||||
* ### How do I change the size of the disk?
|
> [!NOTE]
|
||||||
|
> Docker Desktop on Linux, macOS, and Windows 10 does not currently provide KVM access to containers and is therefore not supported.
|
||||||
|
|
||||||
To expand the default size of 16 GB, locate the `DISK_SIZE` setting in your compose file and modify it to your preferred capacity:
|
## FAQ 💬
|
||||||
|
|
||||||
```yaml
|
### How do I use it?
|
||||||
environment:
|
|
||||||
DISK_SIZE: "128G"
|
|
||||||
```
|
|
||||||
|
|
||||||
This can also be used to resize the existing disk to a larger capacity without any data loss.
|
|
||||||
|
|
||||||
* ### How do I change the storage location?
|
Very simple! These are the steps:
|
||||||
|
|
||||||
|
- Start the container and connect to [port 5000](http://127.0.0.1:5000/) using your web browser.
|
||||||
|
|
||||||
To change the storage location, include the following bind mount in your compose file:
|
- 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!
|
||||||
|
|
||||||
```yaml
|
### How do I change the storage location?
|
||||||
volumes:
|
|
||||||
- /var/dsm:/storage
|
|
||||||
```
|
|
||||||
|
|
||||||
Replace the example path `/var/dsm` with the desired storage folder.
|
To change the storage location, include the following bind mount in your compose file:
|
||||||
|
|
||||||
* ### How do I create a growable disk?
|
```yaml
|
||||||
|
volumes:
|
||||||
|
- ./dsm:/storage
|
||||||
|
```
|
||||||
|
|
||||||
By default, the entire capacity of the disk is reserved in advance.
|
Replace the example path `./dsm` with the desired storage folder or named volume.
|
||||||
|
|
||||||
|
### How do I change the size of the disk?
|
||||||
|
|
||||||
To create a growable disk that only allocates space that is actually used, add the following environment variable:
|
To expand the default size of 256 GB, locate the `DISK_SIZE` setting in your compose file and modify it to your preferred capacity:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
environment:
|
environment:
|
||||||
DISK_FMT: "qcow2"
|
DISK_SIZE: "512G"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> This can also be used to resize an existing disk to a larger capacity without any data loss.
|
||||||
|
|
||||||
Please note that this may reduce the write performance of the disk.
|
### How do I add multiple disks?
|
||||||
|
|
||||||
* ### How do I add multiple disks?
|
To create additional disks, modify your compose file like this:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
environment:
|
||||||
|
DISK2_SIZE: "500G"
|
||||||
|
DISK3_SIZE: "750G"
|
||||||
|
volumes:
|
||||||
|
- ./example2:/storage2
|
||||||
|
- ./example3:/storage3
|
||||||
|
```
|
||||||
|
|
||||||
To create additional disks, modify your compose file like this:
|
### How do I pass through a disk?
|
||||||
|
|
||||||
```yaml
|
|
||||||
environment:
|
|
||||||
DISK2_SIZE: "32G"
|
|
||||||
DISK3_SIZE: "64G"
|
|
||||||
volumes:
|
|
||||||
- /home/example:/storage2
|
|
||||||
- /mnt/data/example:/storage3
|
|
||||||
```
|
|
||||||
|
|
||||||
* ### How do I pass-through a disk?
|
You can pass through disk devices or partitions directly by adding them to your compose file in this way:
|
||||||
|
|
||||||
It is possible to pass-through disk devices directly by adding them to your compose file in this way:
|
```yaml
|
||||||
|
devices:
|
||||||
|
- /dev/sdb:/disk1
|
||||||
|
- /dev/sdc1:/disk2
|
||||||
|
```
|
||||||
|
|
||||||
```yaml
|
Make sure it is totally empty (without any filesystem), otherwise DSM may not format it as a volume.
|
||||||
environment:
|
|
||||||
DEVICE2: "/dev/sda"
|
|
||||||
DEVICE3: "/dev/sdb"
|
|
||||||
devices:
|
|
||||||
- /dev/sda
|
|
||||||
- /dev/sdb
|
|
||||||
```
|
|
||||||
|
|
||||||
Please note that the device needs to be totally empty (without any partition table) otherwise DSM does not always format it into a volume.
|
### How do I change the amount of CPU or RAM?
|
||||||
|
|
||||||
Do NOT use this feature with the goal of sharing files from the host, they will all be lost without warning when DSM creates the volume.
|
By default, Virtual DSM will be allowed to use 2 CPU cores and 2 GB of RAM.
|
||||||
|
|
||||||
* ### How do I increase the amount of CPU or RAM?
|
If you want to adjust this, you can specify the desired amount using the following environment variables:
|
||||||
|
|
||||||
By default, a single CPU core and 1 GB of RAM are allocated to the container.
|
```yaml
|
||||||
|
environment:
|
||||||
|
RAM_SIZE: "4G"
|
||||||
|
CPU_CORES: "4"
|
||||||
|
```
|
||||||
|
|
||||||
To increase this, add the following environment variables:
|
### How do I assign an individual IP address to the container?
|
||||||
|
|
||||||
```yaml
|
By default, the container uses bridge networking, which shares the IP address with the host.
|
||||||
environment:
|
|
||||||
RAM_SIZE: "4G"
|
|
||||||
CPU_CORES: "4"
|
|
||||||
```
|
|
||||||
|
|
||||||
* ### How do I verify if my system supports KVM?
|
If you want to assign an individual IP address to the container, you can create a macvlan network as follows:
|
||||||
|
|
||||||
To verify if your system supports KVM, run the following commands:
|
```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.
|
||||||
|
|
||||||
```bash
|
Once you have created the network, change your compose file to look as follows:
|
||||||
sudo apt install cpu-checker
|
|
||||||
sudo kvm-ok
|
|
||||||
```
|
|
||||||
|
|
||||||
If you receive an error from `kvm-ok` indicating that KVM acceleration can't be used, check the virtualization settings in the BIOS.
|
```yaml
|
||||||
|
services:
|
||||||
|
dsm:
|
||||||
|
container_name: dsm
|
||||||
|
..<snip>..
|
||||||
|
networks:
|
||||||
|
vdsm:
|
||||||
|
ipv4_address: 192.168.0.100
|
||||||
|
|
||||||
* ### How do I assign an individual IP address to the container?
|
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.
|
||||||
|
|
||||||
By default, the container uses bridge networking, which shares the IP address with the host.
|
> [!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.
|
||||||
|
|
||||||
If you want to assign an individual IP address to the container, you can create a macvlan network as follows:
|
### How can DSM acquire an IP address from my router?
|
||||||
|
|
||||||
```bash
|
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.
|
||||||
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:
|
To enable this mode, in which the container and DSM will have separate IP addresses, add the following lines to your compose file:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
services:
|
environment:
|
||||||
dsm:
|
DHCP: "Y"
|
||||||
container_name: dsm
|
devices:
|
||||||
..<snip>..
|
- /dev/vhost-net
|
||||||
networks:
|
device_cgroup_rules:
|
||||||
vdsm:
|
- 'c *:* rwm'
|
||||||
ipv4_address: 192.168.0.100
|
```
|
||||||
|
|
||||||
networks:
|
### How do I pass through the GPU?
|
||||||
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.
|
|
||||||
|
|
||||||
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.
|
To pass through your Intel GPU, add the following lines to your compose file:
|
||||||
|
|
||||||
* ### How can DSM acquire an IP address from my router?
|
```yaml
|
||||||
|
environment:
|
||||||
|
GPU: "Y"
|
||||||
|
devices:
|
||||||
|
- /dev/dri
|
||||||
|
```
|
||||||
|
|
||||||
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.
|
> [!NOTE]
|
||||||
|
> This can be used to enable the facial recognition function in Synology Photos, but does not provide hardware transcoding for video.
|
||||||
|
|
||||||
To enable this feature, add the following lines to your compose file:
|
### How do I install a specific version of vDSM?
|
||||||
|
|
||||||
```yaml
|
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:
|
||||||
environment:
|
|
||||||
DHCP: "Y"
|
|
||||||
device_cgroup_rules:
|
|
||||||
- 'c *:* rwm'
|
|
||||||
```
|
|
||||||
|
|
||||||
Please note that even if you don't want DHCP, it's still recommended to enable this feature, as it prevents NAT issues and increases performance by using a `macvtap` interface. In that case, just set a static IP from the DSM control panel after you enabled this mode.
|
```yaml
|
||||||
|
environment:
|
||||||
|
URL: "https://global.synologydownload.com/download/DSM/release/7.0.1/42218/DSM_VirtualDSM_42218.pat"
|
||||||
|
```
|
||||||
|
|
||||||
* ### How do I pass-through the GPU?
|
With this method, it is even possible to switch back and forth between versions while keeping your file data intact.
|
||||||
|
|
||||||
To pass-through your Intel GPU, add the following lines to your compose file:
|
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
|
||||||
|
```
|
||||||
|
|
||||||
```yaml
|
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.
|
||||||
environment:
|
|
||||||
GPU: "Y"
|
|
||||||
devices:
|
|
||||||
- /dev/dri
|
|
||||||
```
|
|
||||||
|
|
||||||
This can be used to enable the facial recognition function in Synology Photos for example.
|
### Are these all available options?
|
||||||
|
|
||||||
* ### How do I install a specific version of vDSM?
|
No. For a complete overview of all supported settings, see the [environment variables](docs/environment.md) page.
|
||||||
|
|
||||||
By default, version 7.2 will be installed, but if you prefer an older version, you can add its download URL to your compose file as follows:
|
### How do I verify that KVM is available?
|
||||||
|
|
||||||
```yaml
|
First, make sure your platform and container runtime meet the [requirements](#requirements-️) listed above.
|
||||||
environment:
|
|
||||||
URL: "https://global.synologydownload.com/download/DSM/release/7.0.1/42218/DSM_VirtualDSM_42218.pat"
|
|
||||||
```
|
|
||||||
|
|
||||||
With this method, it is even possible to switch between different versions while keeping all your file data intact.
|
On a Linux host, install `cpu-checker` and run:
|
||||||
|
|
||||||
* ### What are the differences compared to the standard DSM?
|
```bash
|
||||||
|
sudo apt install cpu-checker
|
||||||
|
sudo kvm-ok
|
||||||
|
```
|
||||||
|
|
||||||
There are only two minor differences: the Virtual Machine Manager package is not available, and Surveillance Station will not include any free licenses.
|
A working configuration should report:
|
||||||
|
|
||||||
* ### Is this project legal?
|
|
||||||
|
|
||||||
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.
|
```text
|
||||||
|
KVM acceleration can be used
|
||||||
However, by installing Synology's Virtual DSM, you must accept their end-user license agreement, which does not permit installation on non-Synology hardware. So only run this project on an official Synology NAS, as any other use will be a violation of their terms and conditions.
|
```
|
||||||
|
|
||||||
## Disclaimer
|
You can also verify that the KVM device exists:
|
||||||
|
|
||||||
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.
|
```bash
|
||||||
|
ls -l /dev/kvm
|
||||||
|
```
|
||||||
|
|
||||||
|
If KVM is unavailable, check whether:
|
||||||
|
|
||||||
|
- Hardware virtualization (`Intel VT-x` or `AMD-V`) is enabled in your BIOS or UEFI.
|
||||||
|
- Nested virtualization is enabled when the host itself is a virtual machine.
|
||||||
|
- Your VPS or cloud provider supports nested virtualization.
|
||||||
|
|
||||||
|
If `kvm-ok` succeeds but the container still reports that KVM is unavailable, you can temporarily add `privileged: true` to your Compose file to rule out a permission or device-access issue.
|
||||||
|
|
||||||
|
### What are the differences compared to the standard DSM?
|
||||||
|
|
||||||
|
There are only two minor differences: the Virtual Machine Manager package is not available, and Surveillance Station will not include any free licenses.
|
||||||
|
|
||||||
|
### Is this project legal?
|
||||||
|
|
||||||
|
Yes, this project contains only open-source code and does not distribute any material owned by Synology. Neither does it try to circumvent any copyright protection measures.
|
||||||
|
|
||||||
|
However, by installing Synology's Virtual DSM, you must accept their end-user license agreement, which does not permit installation on non-Synology hardware. So only run this container on an official Synology NAS, as any other use will be a violation of their terms and conditions.
|
||||||
|
|
||||||
|
## Stars 🌟
|
||||||
|
[](https://github.com/vdsm/virtual-dsm/stargazers)
|
||||||
|
|
||||||
|
## Disclaimer ⚖️
|
||||||
|
|
||||||
|
*Only run this container on Synology hardware, any other use is not permitted by their EULA. The product names, logos, brands, and other trademarks referred to within this project are the property of their respective trademark holders. This project is not affiliated, sponsored, or endorsed by Synology, Inc.*
|
||||||
|
|
||||||
[build_url]: https://github.com/vdsm/virtual-dsm/
|
[build_url]: https://github.com/vdsm/virtual-dsm/
|
||||||
[hub_url]: https://hub.docker.com/r/vdsm/virtual-dsm
|
[hub_url]: https://hub.docker.com/r/vdsm/virtual-dsm
|
||||||
[tag_url]: https://hub.docker.com/r/vdsm/virtual-dsm/tags
|
[tag_url]: https://hub.docker.com/r/vdsm/virtual-dsm/tags
|
||||||
|
[pkg_url]: https://github.com/vdsm/virtual-dsm/pkgs/container/virtual-dsm
|
||||||
|
|
||||||
[Build]: https://github.com/vdsm/virtual-dsm/actions/workflows/build.yml/badge.svg
|
[Build]: https://github.com/vdsm/virtual-dsm/actions/workflows/build.yml/badge.svg
|
||||||
[Size]: https://img.shields.io/docker/image-size/vdsm/virtual-dsm/latest?color=066da5&label=size
|
[Size]: https://img.shields.io/docker/image-size/vdsm/virtual-dsm/latest?color=066da5&label=size
|
||||||
[Pulls]: https://img.shields.io/docker/pulls/kroese/virtual-dsm.svg?style=flat&label=pulls&logo=docker
|
[Pulls]: https://img.shields.io/docker/pulls/vdsm/virtual-dsm.svg?style=flat&label=pulls&logo=docker
|
||||||
[Version]: https://img.shields.io/docker/v/vdsm/virtual-dsm/latest?arch=amd64&sort=semver&color=066da5
|
[Version]: https://img.shields.io/docker/v/vdsm/virtual-dsm/latest?arch=amd64&sort=semver&color=066da5
|
||||||
|
[Package]: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fipitio.github.io%2Fbackage%2Fvdsm%2Fvirtual-dsm%2Fvirtual-dsm.json&query=%24.downloads&logo=github&style=flat&color=066da5&label=pulls
|
||||||
|
|||||||
+16
-8
@@ -1,25 +1,33 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -Eeuo pipefail
|
set -Eeuo pipefail
|
||||||
|
|
||||||
[ -f "/run/shm/qemu.end" ] && echo "QEMU is shutting down.." && exit 1
|
cd /run
|
||||||
[ ! -f "/run/shm/qemu.pid" ] && echo "QEMU is not running yet.." && exit 0
|
. utils.sh # Load functions
|
||||||
|
|
||||||
|
: "${DHCP:="N"}"
|
||||||
|
: "${NETWORK:="Y"}"
|
||||||
|
|
||||||
|
[ -f "/run/shm/qemu.end" ] && echo "QEMU is shutting down..." && exit 1
|
||||||
|
[ ! -s "/run/shm/qemu.pid" ] && echo "QEMU is not running yet..." && exit 0
|
||||||
|
disabled "$NETWORK" && echo "Networking is disabled." && exit 0
|
||||||
|
|
||||||
file="/run/shm/dsm.url"
|
file="/run/shm/dsm.url"
|
||||||
address="/run/shm/qemu.ip"
|
address="/run/shm/qemu.ip"
|
||||||
|
gateway="/run/shm/qemu.gw"
|
||||||
|
|
||||||
[ ! -f "$file" ] && echo "DSM has not enabled networking yet.." && exit 1
|
[ ! -s "$file" ] && echo "DSM has not enabled networking yet..." && exit 0
|
||||||
|
|
||||||
location=$(<"$file")
|
location=$(<"$file")
|
||||||
|
|
||||||
if ! curl -m 20 -ILfSs "http://$location/" > /dev/null; then
|
if ! curl -m 20 -ILfSs "http://$location/" > /dev/null; then
|
||||||
|
|
||||||
if [[ "$location" == "20.20"* ]]; then
|
if enabled "$DHCP"; then
|
||||||
ip="20.20.20.1"
|
ip=$(<"$address")
|
||||||
|
echo "Failed to reach DSM at http://$location"
|
||||||
|
else
|
||||||
|
ip=$(<"$gateway")
|
||||||
port="${location##*:}"
|
port="${location##*:}"
|
||||||
echo "Failed to reach DSM at port $port"
|
echo "Failed to reach DSM at port $port"
|
||||||
else
|
|
||||||
echo "Failed to reach DSM at http://$location"
|
|
||||||
ip=$(<"$address")
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "You might need to whitelist IP $ip in the DSM firewall." && exit 1
|
echo "You might need to whitelist IP $ip in the DSM firewall." && exit 1
|
||||||
|
|||||||
+56
-8
@@ -2,14 +2,62 @@
|
|||||||
set -Eeuo pipefail
|
set -Eeuo pipefail
|
||||||
|
|
||||||
DEF_OPTS="-nodefaults -boot strict=on"
|
DEF_OPTS="-nodefaults -boot strict=on"
|
||||||
RAM_OPTS=$(echo "-m $RAM_SIZE" | sed 's/MB/M/g;s/GB/G/g;s/TB/T/g')
|
DEV_OPTS=""
|
||||||
CPU_OPTS="-cpu $CPU_MODEL -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}"
|
|
||||||
DEV_OPTS="-device virtio-balloon-pci,id=balloon0,bus=pcie.0,addr=0x4"
|
|
||||||
DEV_OPTS="$DEV_OPTS -object rng-random,id=objrng0,filename=/dev/urandom"
|
|
||||||
DEV_OPTS="$DEV_OPTS -device virtio-rng-pci,rng=objrng0,id=rng0,bus=pcie.0,addr=0x1c"
|
|
||||||
|
|
||||||
ARGS="$DEF_OPTS $CPU_OPTS $RAM_OPTS $MAC_OPTS $DISPLAY_OPTS $MON_OPTS $SERIAL_OPTS $NET_OPTS $DISK_OPTS $DEV_OPTS $ARGUMENTS"
|
configureProcessor() {
|
||||||
ARGS=$(echo "$ARGS" | sed 's/\t/ /g' | tr -s ' ')
|
|
||||||
|
CPU_OPTS="-cpu $CPU_FLAGS"
|
||||||
|
CPU_OPTS+=" -smp $CPU_CORES,sockets=1,dies=1,cores=$CPU_CORES,threads=1"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
configureMemory() {
|
||||||
|
|
||||||
|
RAM_OPTS=$(echo "-m ${RAM_SIZE^^}" | sed 's/MB/M/g;s/GB/G/g;s/TB/T/g')
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
configureMonitor() {
|
||||||
|
|
||||||
|
MON_OPTS="-name $PROCESS,process=$PROCESS,debug-threads=on"
|
||||||
|
MON_OPTS+=" -pidfile $QEMU_PID"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
configureMachine() {
|
||||||
|
|
||||||
|
MAC_OPTS="-machine type=$MACHINE,smm=off,usb=off"
|
||||||
|
MAC_OPTS+=",vmport=off,dump-guest-core=off,hpet=off${KVM_OPTS}"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
configureVirtioDevices() {
|
||||||
|
|
||||||
|
DEV_OPTS="-device virtio-balloon-pci,id=balloon0,bus=pcie.0,addr=0x4"
|
||||||
|
DEV_OPTS+=" -object rng-random,id=objrng0,filename=/dev/urandom"
|
||||||
|
DEV_OPTS+=" -device virtio-rng-pci,rng=objrng0,id=rng0,bus=pcie.0,addr=0x1c"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
buildArguments() {
|
||||||
|
|
||||||
|
ARGS="$DEF_OPTS $CPU_OPTS $RAM_OPTS $MAC_OPTS $DISPLAY_OPTS $MON_OPTS $SERIAL_OPTS $NET_OPTS $DISK_OPTS $DEV_OPTS $ARGUMENTS"
|
||||||
|
ARGS=$(echo "$ARGS" | sed 's/\t/ /g' | tr -s ' ')
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
configureMemory
|
||||||
|
configureMonitor
|
||||||
|
configureMachine
|
||||||
|
configureProcessor
|
||||||
|
configureVirtioDevices
|
||||||
|
|
||||||
|
buildArguments
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
+654
-324
File diff suppressed because it is too large
Load Diff
+37
-9
@@ -6,31 +6,59 @@ set -Eeuo pipefail
|
|||||||
: "${GPU:="N"}" # GPU passthrough
|
: "${GPU:="N"}" # GPU passthrough
|
||||||
: "${VGA:="virtio"}" # VGA adaptor
|
: "${VGA:="virtio"}" # VGA adaptor
|
||||||
: "${DISPLAY:="none"}" # Display type
|
: "${DISPLAY:="none"}" # Display type
|
||||||
|
: "${LOSSY:="N"}" # Lossy VNC compression
|
||||||
|
: "${RENDERNODE:="/dev/dri/renderD128"}" # Render node
|
||||||
|
|
||||||
if [[ "$GPU" != [Yy1]* ]] || [[ "$ARCH" != "amd64" ]]; then
|
# Sanitize variables
|
||||||
|
VGA=$(strip "$VGA")
|
||||||
|
LOSSY=$(strip "$LOSSY")
|
||||||
|
DISPLAY=$(strip "$DISPLAY")
|
||||||
|
RENDERNODE=$(strip "$RENDERNODE")
|
||||||
|
|
||||||
|
CPU_VENDOR=$(lscpu | awk '/Vendor ID/{print $3}')
|
||||||
|
|
||||||
|
if ! enabled "$GPU" || isAmdCpu || [[ "$ARCH" != "amd64" ]]; then
|
||||||
|
|
||||||
[[ "${DISPLAY,,}" == "none" ]] && VGA="none"
|
[[ "${DISPLAY,,}" == "none" ]] && VGA="none"
|
||||||
DISPLAY_OPTS="-display $DISPLAY -vga $VGA"
|
|
||||||
|
if enabled "$LOSSY" && [[ "${DISPLAY,,}" == vnc=* ]]; then
|
||||||
|
DISPLAY+=",lossy=on"
|
||||||
|
fi
|
||||||
|
|
||||||
|
DISPLAY_OPTS="-display ${DISPLAY} -vga ${VGA}"
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
DISPLAY_OPTS="-display egl-headless,rendernode=/dev/dri/renderD128 -vga $VGA"
|
msg="Configuring display drivers..."
|
||||||
|
html "$msg"
|
||||||
|
enabled "$DEBUG" && echo "$msg"
|
||||||
|
|
||||||
|
DISPLAY_OPTS="-display egl-headless,rendernode=${RENDERNODE}"
|
||||||
|
DISPLAY_OPTS+=" -vga $VGA"
|
||||||
|
|
||||||
[ ! -d /dev/dri ] && mkdir -m 755 /dev/dri
|
[ ! -d /dev/dri ] && mkdir -m 755 /dev/dri
|
||||||
|
|
||||||
if [ ! -c /dev/dri/card0 ]; then
|
# Extract the card number from the render node
|
||||||
if mknod /dev/dri/card0 c 226 0; then
|
CARD_NUMBER=$(echo "$RENDERNODE" | grep -oP '(?<=renderD)\d+')
|
||||||
chmod 666 /dev/dri/card0
|
CARD_DEVICE="/dev/dri/card$((CARD_NUMBER - 128))"
|
||||||
|
|
||||||
|
if [ ! -c "$CARD_DEVICE" ]; then
|
||||||
|
if mknod "$CARD_DEVICE" c 226 $((CARD_NUMBER - 128)); then
|
||||||
|
chmod 666 "$CARD_DEVICE"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ ! -c /dev/dri/renderD128 ]; then
|
if [ ! -c "$RENDERNODE" ]; then
|
||||||
if mknod /dev/dri/renderD128 c 226 128; then
|
if mknod "$RENDERNODE" c 226 "$CARD_NUMBER"; then
|
||||||
chmod 666 /dev/dri/renderD128
|
chmod 666 "$RENDERNODE"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ ! -c "$RENDERNODE" ] || [ ! -r "$RENDERNODE" ] || [ ! -w "$RENDERNODE" ]; then
|
||||||
|
warn "render device '${RENDERNODE}' is unavailable or inaccessible."
|
||||||
|
fi
|
||||||
|
|
||||||
addPackage "xserver-xorg-video-intel" "Intel GPU drivers"
|
addPackage "xserver-xorg-video-intel" "Intel GPU drivers"
|
||||||
addPackage "qemu-system-modules-opengl" "OpenGL module"
|
addPackage "qemu-system-modules-opengl" "OpenGL module"
|
||||||
|
|
||||||
|
|||||||
+27
-11
@@ -1,12 +1,16 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -Eeuo pipefail
|
set -Eeuo pipefail
|
||||||
|
|
||||||
APP="Virtual DSM"
|
: "${PLATFORM:="x64"}"
|
||||||
SUPPORT="https://github.com/vdsm/virtual-dsm"
|
: "${APP:="Virtual DSM"}"
|
||||||
|
: "${SUPPORT:="https://github.com/vdsm/virtual-dsm"}"
|
||||||
|
|
||||||
cd /run
|
cd /run
|
||||||
|
|
||||||
|
. start.sh # Startup hook
|
||||||
|
. utils.sh # Load functions
|
||||||
. reset.sh # Initialize system
|
. reset.sh # Initialize system
|
||||||
|
. server.sh # Start webserver
|
||||||
. install.sh # Run installation
|
. install.sh # Run installation
|
||||||
. disk.sh # Initialize disks
|
. disk.sh # Initialize disks
|
||||||
. display.sh # Initialize graphics
|
. display.sh # Initialize graphics
|
||||||
@@ -14,20 +18,32 @@ cd /run
|
|||||||
. proc.sh # Initialize processor
|
. proc.sh # Initialize processor
|
||||||
. serial.sh # Initialize serialport
|
. serial.sh # Initialize serialport
|
||||||
. power.sh # Configure shutdown
|
. power.sh # Configure shutdown
|
||||||
|
. memory.sh # Check available memory
|
||||||
. config.sh # Configure arguments
|
. config.sh # Configure arguments
|
||||||
|
. finish.sh # Finish initialization
|
||||||
|
|
||||||
trap - ERR
|
trap - ERR
|
||||||
|
|
||||||
if [[ "$CONSOLE" == [Yy]* ]]; then
|
cmd=(qemu-system-x86_64)
|
||||||
exec qemu-system-x86_64 ${ARGS:+ $ARGS}
|
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
|
fi
|
||||||
|
|
||||||
[[ "$DEBUG" == [Yy1]* ]] && info "$VERS" && echo "Arguments: $ARGS" && echo
|
if ! interactive; then
|
||||||
{ qemu-system-x86_64 ${ARGS:+ $ARGS} >"$QEMU_OUT" 2>"$QEMU_LOG"; rc=$?; } || :
|
"${cmd[@]}" ${ARGS:+ $ARGS} &
|
||||||
(( rc != 0 )) && error "$(<"$QEMU_LOG")" && exit 15
|
else
|
||||||
|
startConsole
|
||||||
|
startQemu "${cmd[@]}" ${ARGS:+ $ARGS}
|
||||||
|
fi
|
||||||
|
|
||||||
terminal
|
pid=$!
|
||||||
tail -fn +0 "$QEMU_LOG" 2>/dev/null &
|
rc=0
|
||||||
cat "$QEMU_TERM" 2>/dev/null & wait $! || :
|
|
||||||
|
|
||||||
sleep 1 && finish 0
|
wait "$pid" || rc=$?
|
||||||
|
[ -f "$QEMU_END" ] && exit "$rc"
|
||||||
|
|
||||||
|
sleep 1 & wait $!
|
||||||
|
finish "$rc"
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
if enabled "$DEBUG"; then
|
||||||
|
printf "QEMU arguments:\n\n %s\n\n" "${ARGS// -/$'\n -'}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
+178
-173
@@ -5,21 +5,44 @@ set -Eeuo pipefail
|
|||||||
|
|
||||||
if [ -f "$STORAGE/dsm.ver" ]; then
|
if [ -f "$STORAGE/dsm.ver" ]; then
|
||||||
BASE=$(<"$STORAGE/dsm.ver")
|
BASE=$(<"$STORAGE/dsm.ver")
|
||||||
|
BASE="${BASE//[![:print:]]/}"
|
||||||
|
[ -z "$BASE" ] && BASE="DSM_VirtualDSM_69057"
|
||||||
else
|
else
|
||||||
# Fallback for old installs
|
# Fallback for old installs
|
||||||
BASE="DSM_VirtualDSM_42962"
|
BASE="DSM_VirtualDSM_42962"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -n "$URL" ]; then
|
FN="boot.pat"
|
||||||
BASE=$(basename "$URL" .pat)
|
DIR=$(find / -maxdepth 1 -type d -iname "$FN" -print -quit)
|
||||||
if [ ! -f "$STORAGE/$BASE.system.img" ]; then
|
[ ! -d "$DIR" ] && DIR=$(find "$STORAGE" -maxdepth 1 -type d -iname "$FN" -print -quit)
|
||||||
BASE=$(basename "${URL%%\?*}" .pat)
|
|
||||||
: "${BASE//+/ }"; printf -v BASE '%b' "${_//%/\\x}"
|
if [ -d "$DIR" ]; then
|
||||||
BASE=$(echo "$BASE" | sed -e 's/[^A-Za-z0-9._-]/_/g')
|
BASE="DSM_VirtualDSM" && URL="file://$DIR"
|
||||||
|
if [[ ! -s "$STORAGE/$BASE.boot.img" || ! -s "$STORAGE/$BASE.system.img" ]]; then
|
||||||
|
error "The bind $DIR maps to a file that does not exist!" && exit 65
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -f "$STORAGE/$BASE.boot.img" ]] && [[ -f "$STORAGE/$BASE.system.img" ]]; then
|
FILE=$(find / -maxdepth 1 -type f -iname "$FN" -print -quit)
|
||||||
|
[ ! -s "$FILE" ] && FILE=$(find "$STORAGE" -maxdepth 1 -type f -iname "$FN" -print -quit)
|
||||||
|
[ -s "$FILE" ] && BASE="DSM_VirtualDSM" && URL="file://$FILE"
|
||||||
|
|
||||||
|
URL=$(strip "$URL")
|
||||||
|
|
||||||
|
if [ -n "$URL" ] && [ ! -s "$FILE" ] && [ ! -d "$DIR" ]; then
|
||||||
|
BASE=$(basename "$URL" .pat)
|
||||||
|
if [ ! -s "$STORAGE/$BASE.system.img" ]; then
|
||||||
|
BASE=$(basename "${URL%%\?*}" .pat)
|
||||||
|
printf -v BASE '%b' "${BASE//%/\\x}"
|
||||||
|
BASE="${BASE//[!A-Za-z0-9._-]/_}"
|
||||||
|
fi
|
||||||
|
if [[ "${URL,,}" != "http"* && "${URL,,}" != "file:"* ]]; then
|
||||||
|
[ ! -s "$STORAGE/$BASE.pat" ] && error "Invalid URL: $URL" && exit 65
|
||||||
|
URL="file://$STORAGE/$BASE.pat"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -s "$STORAGE/$BASE.boot.img" && -s "$STORAGE/$BASE.system.img" ]]; then
|
||||||
return 0 # Previous installation found
|
return 0 # Previous installation found
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -38,11 +61,15 @@ if [ -z "$DL" ]; then
|
|||||||
[[ "${COUNTRY^^}" == "CN" ]] && DL="$DL_CHINA" || DL="$DL_GLOBAL"
|
[[ "${COUNTRY^^}" == "CN" ]] && DL="$DL_CHINA" || DL="$DL_GLOBAL"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
[ -z "$URL" ] && URL="$DL/release/7.2.1/69057-1/DSM_VirtualDSM_69057.pat"
|
if [ -z "$URL" ]; then
|
||||||
|
URL="$DL/release/7.2.2/72806/DSM_VirtualDSM_72806.pat"
|
||||||
|
fi
|
||||||
|
|
||||||
BASE=$(basename "${URL%%\?*}" .pat)
|
if [ ! -s "$FILE" ]; then
|
||||||
: "${BASE//+/ }"; printf -v BASE '%b' "${_//%/\\x}"
|
BASE=$(basename "${URL%%\?*}" .pat)
|
||||||
BASE=$(echo "$BASE" | sed -e 's/[^A-Za-z0-9._-]/_/g')
|
printf -v BASE '%b' "${BASE//%/\\x}"
|
||||||
|
BASE="${BASE//[!A-Za-z0-9._-]/_}"
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ "$URL" != "file://$STORAGE/$BASE.pat" ]]; then
|
if [[ "$URL" != "file://$STORAGE/$BASE.pat" ]]; then
|
||||||
rm -f "$STORAGE/$BASE.pat"
|
rm -f "$STORAGE/$BASE.pat"
|
||||||
@@ -52,146 +79,130 @@ rm -f "$STORAGE/$BASE.agent"
|
|||||||
rm -f "$STORAGE/$BASE.boot.img"
|
rm -f "$STORAGE/$BASE.boot.img"
|
||||||
rm -f "$STORAGE/$BASE.system.img"
|
rm -f "$STORAGE/$BASE.system.img"
|
||||||
|
|
||||||
[[ "$DEBUG" == [Yy1]* ]] && set -x
|
|
||||||
|
|
||||||
# Check filesystem
|
# Check filesystem
|
||||||
FS=$(stat -f -c %T "$STORAGE")
|
FS=$(stat -f -c %T "$STORAGE")
|
||||||
|
|
||||||
if [[ "${FS,,}" == "overlay"* ]]; then
|
if [[ "${FS,,}" == "overlay"* && "${ENGINE,,}" == "docker" ]]; then
|
||||||
info "Warning: the filesystem of $STORAGE is OverlayFS, this usually means it was binded to an invalid path!"
|
warn "the filesystem of $STORAGE is OverlayFS, this usually means it was binded to an invalid path!"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "${FS,,}" == "fuse"* ]]; then
|
if [[ "${FS,,}" == "fuse"* ]]; then
|
||||||
info "Warning: the filesystem of $STORAGE is FUSE, this extra layer will negatively affect performance!"
|
warn "the filesystem of $STORAGE is FUSE, this extra layer will negatively affect performance!"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "${FS,,}" != "fat"* && "${FS,,}" != "vfat"* && "${FS,,}" != "exfat"* && "${FS,,}" != "ntfs"* && "${FS,,}" != "msdos"* ]]; then
|
if [[ "${FS,,}" == "ecryptfs" || "${FS,,}" == "tmpfs" ]]; then
|
||||||
|
warn "the filesystem of $STORAGE is $FS, which does not support O_DIRECT mode, adjusting settings..."
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${FS,,}" == "fat"* || "${FS,,}" == "vfat"* || "${FS,,}" == "msdos"* ]]; then
|
||||||
|
error "Unable to install on $FS filesystems, please use a different filesystem for /storage." && exit 61
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${FS,,}" != "exfat"* && "${FS,,}" != "ntfs"* && "${FS,,}" != "unknown"* ]]; then
|
||||||
TMP="$STORAGE/tmp"
|
TMP="$STORAGE/tmp"
|
||||||
|
rm -rf "$TMP"
|
||||||
|
if ! makeDir "$TMP"; then
|
||||||
|
error "Failed to create directory \"$TMP\" !" && exit 93
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
TMP="/tmp/dsm"
|
TMP="/tmp/dsm"
|
||||||
TMP_SPACE=2147483648
|
TMP_SPACE=2147483648
|
||||||
SPACE=$(df --output=avail -B 1 /tmp | tail -n 1)
|
SPACE=$(df --output=avail -B 1 /tmp | tail -n 1)
|
||||||
SPACE_MB=$(( (SPACE + 1048575)/1048576 ))
|
SPACE_MB=$(formatBytes "$SPACE")
|
||||||
if (( TMP_SPACE > SPACE )); then
|
if (( TMP_SPACE > SPACE )); then
|
||||||
error "Not enough free space inside the container, have $SPACE_MB MB available but need at least 2 GB." && exit 93
|
error "Not enough free space inside the container, have $SPACE_MB available but need at least 2 GB." && exit 93
|
||||||
fi
|
fi
|
||||||
|
rm -rf "$TMP" && mkdir -p "$TMP"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
rm -rf "$TMP" && mkdir -p "$TMP"
|
|
||||||
|
|
||||||
# Check free diskspace
|
# Check free diskspace
|
||||||
ROOT_SPACE=536870912
|
ROOT_SPACE=536870912
|
||||||
SPACE=$(df --output=avail -B 1 / | tail -n 1)
|
SPACE=$(df --output=avail -B 1 / | tail -n 1)
|
||||||
SPACE_MB=$(( (SPACE + 1048575)/1048576 ))
|
SPACE_MB=$(formatBytes "$SPACE" "down")
|
||||||
(( ROOT_SPACE > SPACE )) && error "Not enough free space inside the container, have $SPACE_MB MB available but need at least 500 MB." && exit 96
|
(( ROOT_SPACE > SPACE )) && error "Not enough free space inside the container, have $SPACE_MB available but need at least 500 MB." && exit 96
|
||||||
|
|
||||||
MIN_SPACE=8589934592
|
MIN_SPACE=15032385536
|
||||||
SPACE=$(df --output=avail -B 1 "$STORAGE" | tail -n 1)
|
SPACE=$(df --output=avail -B 1 "$STORAGE" | tail -n 1)
|
||||||
SPACE_GB=$(( (SPACE + 1073741823)/1073741824 ))
|
SPACE_GB=$(formatBytes "$SPACE")
|
||||||
(( MIN_SPACE > SPACE )) && error "Not enough free space for installation in $STORAGE, have $SPACE_GB GB available but need at least 8 GB." && exit 94
|
(( MIN_SPACE > SPACE )) && error "Not enough free space for installation in $STORAGE, have $SPACE_GB available but need at least 14 GB." && exit 94
|
||||||
|
|
||||||
# Check if output is to interactive TTY
|
if [[ "$URL" == "file://"* ]]; then
|
||||||
if [ -t 1 ]; then
|
MSG="Copying DSM"
|
||||||
PROGRESS="--progress=bar:noscroll"
|
ERR="Failed to copy ${URL:7}"
|
||||||
|
info "Install: Copying installation image..."
|
||||||
else
|
else
|
||||||
PROGRESS="--progress=dot:giga"
|
MSG="Downloading DSM"
|
||||||
|
ERR="Failed to download $URL"
|
||||||
|
info "Install: Downloading $BASE.pat..."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Download the required files from the Synology website
|
html "$MSG..."
|
||||||
|
|
||||||
ROOT="Y"
|
|
||||||
RDC="$STORAGE/dsm.rd"
|
|
||||||
|
|
||||||
if [ ! -f "$RDC" ]; then
|
|
||||||
|
|
||||||
MSG="Downloading installer..."
|
|
||||||
info "Install: $MSG" && html "$MSG"
|
|
||||||
|
|
||||||
RD="$TMP/rd.gz"
|
|
||||||
POS="65627648-71021835"
|
|
||||||
VERIFY="b4215a4b213ff5154db0488f92c87864"
|
|
||||||
LOC="$DL/release/7.0.1/42218/DSM_VirtualDSM_42218.pat"
|
|
||||||
|
|
||||||
{ curl -r "$POS" -sfk -S -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=$?; } || :
|
|
||||||
|
|
||||||
if (( rc != 0 )); then
|
|
||||||
ROOT="N"
|
|
||||||
{ (cd "$TMP" && fakeroot cpio -idmu <"$TMP/rd" 2>/dev/null); rc=$?; } || :
|
|
||||||
(( rc != 0 )) && error "Failed to extract $RDC, reason $rc" && exit 92
|
|
||||||
fi
|
|
||||||
|
|
||||||
rm -rf /run/extract && 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 $BASE.pat..."
|
|
||||||
html "Install: Downloading DSM from Synology..."
|
|
||||||
|
|
||||||
PAT="/$BASE.pat"
|
PAT="/$BASE.pat"
|
||||||
rm -f "$PAT"
|
rm -f "$PAT"
|
||||||
|
|
||||||
if [[ "$URL" == "file://"* ]]; then
|
if [[ "$URL" == "file://"* ]]; then
|
||||||
|
|
||||||
|
if [ ! -f "${URL:7}" ]; then
|
||||||
|
error "File '${URL:7}' does not exist!" && exit 65
|
||||||
|
fi
|
||||||
|
|
||||||
cp "${URL:7}" "$PAT"
|
cp "${URL:7}" "$PAT"
|
||||||
|
|
||||||
else
|
else
|
||||||
|
|
||||||
{ wget "$URL" -O "$PAT" -q --no-check-certificate --show-progress "$PROGRESS"; rc=$?; } || :
|
SIZE=0
|
||||||
(( rc != 0 )) && error "Failed to download $URL, reason: $rc" && exit 69
|
REASON=""
|
||||||
|
PROGRESS=()
|
||||||
|
OUTPUT=""
|
||||||
|
LOG=$(mktemp)
|
||||||
|
|
||||||
|
[[ "${URL,,}" == *"_72806.pat" ]] && SIZE=361010261
|
||||||
|
[[ "${URL,,}" == *"_69057.pat" ]] && SIZE=363837333
|
||||||
|
[[ "${URL,,}" == *"_42218.pat" ]] && SIZE=379637760
|
||||||
|
|
||||||
|
# Use Wget's progress bar in a terminal and progress.sh in container logs.
|
||||||
|
if [ -t 1 ]; then
|
||||||
|
PROGRESS=( --show-progress --progress=bar:noscroll )
|
||||||
|
else
|
||||||
|
OUTPUT="log"
|
||||||
|
fi
|
||||||
|
|
||||||
|
/run/progress.sh "$PAT" "$SIZE" "$MSG ([P])..." "$OUTPUT" 52428800 &
|
||||||
|
|
||||||
|
{
|
||||||
|
LC_ALL=C wget "$URL" -O "$PAT" --no-verbose --no-check-certificate \
|
||||||
|
--timeout=30 --no-http-keep-alive "${PROGRESS[@]}" \
|
||||||
|
--output-file="$LOG"
|
||||||
|
rc=$?
|
||||||
|
} || :
|
||||||
|
|
||||||
|
fKill "progress.sh"
|
||||||
|
|
||||||
|
if (( rc != 0 )); then
|
||||||
|
REASON=$(sed -n \
|
||||||
|
-e 's/^wget: //p' \
|
||||||
|
-e 's/^[0-9-]\{10\} [0-9:]\{8\} ERROR //p' \
|
||||||
|
"$LOG" | tail -n 1)
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -f "$LOG"
|
||||||
|
|
||||||
|
if (( rc == 3 )); then
|
||||||
|
error "$ERR because the file could not be written (disk full?)."
|
||||||
|
exit 69
|
||||||
|
elif (( rc != 0 )); then
|
||||||
|
if [ -n "$REASON" ]; then
|
||||||
|
error "$ERR: ${REASON%.}."
|
||||||
|
else
|
||||||
|
error "$ERR with exit status $rc."
|
||||||
|
fi
|
||||||
|
exit 69
|
||||||
|
fi
|
||||||
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
[ ! -f "$PAT" ] && error "Failed to download $URL" && exit 69
|
[ ! -s "$PAT" ] && error "$ERR" && exit 69
|
||||||
|
|
||||||
SIZE=$(stat -c%s "$PAT")
|
SIZE=$(stat -c%s "$PAT")
|
||||||
|
|
||||||
@@ -199,7 +210,7 @@ if ((SIZE<250000000)); then
|
|||||||
error "The specified PAT file is probably an update pack as it's too small." && exit 62
|
error "The specified PAT file is probably an update pack as it's too small." && exit 62
|
||||||
fi
|
fi
|
||||||
|
|
||||||
MSG="Extracting downloaded image..."
|
MSG="Extracting installation image..."
|
||||||
info "Install: $MSG" && html "$MSG"
|
info "Install: $MSG" && html "$MSG"
|
||||||
|
|
||||||
if { tar tf "$PAT"; } >/dev/null 2>&1; then
|
if { tar tf "$PAT"; } >/dev/null 2>&1; then
|
||||||
@@ -208,78 +219,81 @@ if { tar tf "$PAT"; } >/dev/null 2>&1; then
|
|||||||
|
|
||||||
else
|
else
|
||||||
|
|
||||||
export LD_LIBRARY_PATH="/run/extract"
|
{ (cd "$TMP" && python3 /run/extract.py -i "$PAT" -d 2>/run/extract.log); rc=$?; } || :
|
||||||
|
|
||||||
if [ "$ARCH" == "amd64" ]; then
|
if (( rc != 0 )); then
|
||||||
{ /run/extract/syno_extract_system_patch "$PAT" "$TMP/."; rc=$?; } || :
|
cat /run/extract.log
|
||||||
else
|
error "Failed to extract PAT file, reason $rc" && exit 63
|
||||||
{ qemu-x86_64 /run/extract/syno_extract_system_patch "$PAT" "$TMP/."; rc=$?; } || :
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
export LD_LIBRARY_PATH=""
|
|
||||||
|
|
||||||
(( rc != 0 )) && error "Failed to extract PAT file, reason $rc" && exit 63
|
|
||||||
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
rm -rf /run/extract
|
|
||||||
|
|
||||||
MSG="Preparing system partition..."
|
MSG="Preparing system partition..."
|
||||||
info "Install: $MSG" && html "$MSG"
|
info "Install: $MSG" && html "$MSG"
|
||||||
|
|
||||||
BOOT=$(find "$TMP" -name "*.bin.zip")
|
BOOT=$(find "$TMP" -name "*.bin.zip" -print -quit)
|
||||||
[ ! -f "$BOOT" ] && error "The PAT file contains no boot image." && exit 67
|
[ -z "$BOOT" ] && error "The PAT file contains no boot image." && exit 67
|
||||||
|
[ ! -s "$BOOT" ] && error "The PAT boot image archive is empty." && exit 67
|
||||||
|
|
||||||
BOOT=$(echo "$BOOT" | head -c -5)
|
unzip -q -o "$BOOT" -d "$TMP"
|
||||||
unzip -q -o "$BOOT".zip -d "$TMP"
|
BOOT="${BOOT%.zip}"
|
||||||
|
|
||||||
SYSTEM="$STORAGE/$BASE.system.img"
|
SYSTEM="$STORAGE/$BASE.system.img"
|
||||||
rm -f "$SYSTEM"
|
rm -f "$SYSTEM"
|
||||||
|
|
||||||
# Check free diskspace
|
# Check free diskspace
|
||||||
SYSTEM_SIZE=4954537983
|
SYSTEM_SIZE=10738466816
|
||||||
SPACE=$(df --output=avail -B 1 "$STORAGE" | tail -n 1)
|
SPACE=$(df --output=avail -B 1 "$STORAGE" | tail -n 1)
|
||||||
SPACE_MB=$(( (SPACE + 1048575)/1048576 ))
|
SPACE_MB=$(formatBytes "$SPACE")
|
||||||
|
|
||||||
if (( SYSTEM_SIZE > SPACE )); then
|
if (( SYSTEM_SIZE > SPACE )); then
|
||||||
error "Not enough free space in $STORAGE to create a 5 GB system disk, have only $SPACE_MB MB available." && exit 97
|
error "Not enough free space in $STORAGE to create a 10 GB system disk, have only $SPACE_MB available." && exit 97
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! touch "$SYSTEM"; then
|
if ! touch "$SYSTEM"; then
|
||||||
error "Could not create file $SYSTEM for the system disk." && exit 98
|
error "Could not create file $SYSTEM for the system disk." && exit 98
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "${FS,,}" == "xfs" || "${FS,,}" == "zfs" || "${FS,,}" == "btrfs" || "${FS,,}" == "bcachefs" ]]; then
|
setOwner "$SYSTEM" || warn "failed to set the owner for \"$SYSTEM\" !"
|
||||||
|
|
||||||
|
if [[ "${FS,,}" == "btrfs" ]]; then
|
||||||
{ chattr +C "$SYSTEM"; } || :
|
{ chattr +C "$SYSTEM"; } || :
|
||||||
FA=$(lsattr "$SYSTEM")
|
FA=$(lsattr "$SYSTEM")
|
||||||
if [[ "$FA" != *"C"* ]]; then
|
if [[ "$FA" != *"C"* ]]; then
|
||||||
error "Failed to disable COW for system image $SYSTEM on ${FS^^} filesystem (returned $FA)"
|
error "Failed to disable COW for system image $SYSTEM on ${FS^^} filesystem."
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! fallocate -l "$SYSTEM_SIZE" "$SYSTEM"; then
|
if ! fallocate -l "$SYSTEM_SIZE" "$SYSTEM" &>/dev/null; then
|
||||||
if ! truncate -s "$SYSTEM_SIZE" "$SYSTEM"; then
|
if ! fallocate -l -x "$SYSTEM_SIZE" "$SYSTEM"; then
|
||||||
rm -f "$SYSTEM"
|
if ! truncate -s "$SYSTEM_SIZE" "$SYSTEM"; then
|
||||||
error "Could not allocate file $SYSTEM for the system disk." && exit 98
|
rm -f "$SYSTEM"
|
||||||
|
error "Could not allocate file $SYSTEM for the system disk." && exit 98
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
PART="$TMP/partition.fdisk"
|
PART="$TMP/partition.fdisk"
|
||||||
|
|
||||||
{ echo "label: dos"
|
{
|
||||||
echo "label-id: 0x6f9ee2e9"
|
echo "label: dos"
|
||||||
echo "device: $SYSTEM"
|
echo "label-id: 0x6f9ee2e9"
|
||||||
echo "unit: sectors"
|
echo "device: $SYSTEM"
|
||||||
echo "sector-size: 512"
|
echo "unit: sectors"
|
||||||
echo ""
|
echo "sector-size: 512"
|
||||||
echo "${SYSTEM}1 : start= 2048, size= 4980480, type=83"
|
echo ""
|
||||||
echo "${SYSTEM}2 : start= 4982528, size= 4194304, type=82"
|
echo "${SYSTEM}1 : start= 2048, size= 16777216, type=83"
|
||||||
|
echo "${SYSTEM}2 : start= 16779264, size= 4194304, type=82"
|
||||||
} > "$PART"
|
} > "$PART"
|
||||||
|
|
||||||
sfdisk -q "$SYSTEM" < "$PART"
|
sfdisk -q "$SYSTEM" < "$PART"
|
||||||
|
|
||||||
MOUNT="$TMP/system"
|
MOUNT="$TMP/system"
|
||||||
rm -rf "$MOUNT" && mkdir -p "$MOUNT"
|
rm -rf "$MOUNT"
|
||||||
|
|
||||||
|
if ! makeDir "$MOUNT"; then
|
||||||
|
error "Failed to create directory \"$MOUNT\" !" && exit 93
|
||||||
|
fi
|
||||||
|
|
||||||
MSG="Extracting system partition..."
|
MSG="Extracting system partition..."
|
||||||
info "Install: $MSG" && html "$MSG"
|
info "Install: $MSG" && html "$MSG"
|
||||||
@@ -289,44 +303,33 @@ IDB="$TMP/indexdb"
|
|||||||
PKG="$TMP/packages"
|
PKG="$TMP/packages"
|
||||||
HDP="$TMP/synohdpack_img"
|
HDP="$TMP/synohdpack_img"
|
||||||
|
|
||||||
[ ! -f "$HDA.tgz" ] && error "The PAT file contains no OS image." && exit 64
|
[ ! -s "$HDA.tgz" ] && error "The PAT file contains no OS image." && exit 64
|
||||||
mv "$HDA.tgz" "$HDA.txz"
|
mv "$HDA.tgz" "$HDA.txz"
|
||||||
|
|
||||||
[ -d "$PKG" ] && mv "$PKG/" "$MOUNT/.SynoUpgradePackages/"
|
[ -d "$PKG" ] && mv "$PKG/" "$MOUNT/.SynoUpgradePackages/"
|
||||||
rm -f "$MOUNT/.SynoUpgradePackages/ActiveInsight-"*
|
rm -f "$MOUNT/.SynoUpgradePackages/ActiveInsight-"*
|
||||||
|
|
||||||
[ -f "$HDP.txz" ] && tar xpfJ "$HDP.txz" --absolute-names -C "$MOUNT/"
|
INDEX_DB="$MOUNT/usr/syno/synoman/indexdb"
|
||||||
|
|
||||||
if [ -f "$IDB.txz" ]; then
|
if [ -s "$IDB.txz" ]; then
|
||||||
INDEX_DB="$MOUNT/usr/syno/synoman/indexdb/"
|
|
||||||
mkdir -p "$INDEX_DB"
|
mkdir -p "$INDEX_DB"
|
||||||
tar xpfJ "$IDB.txz" --absolute-names -C "$INDEX_DB"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
LABEL="1.44.1-42218"
|
LABEL="1.44.1-42218"
|
||||||
OFFSET="1048576" # 2048 * 512
|
OFFSET="1048576" # 2048 * 512
|
||||||
NUMBLOCKS="622560" # (4980480 * 512) / 4096
|
NUMBLOCKS="2097152" # (16777216 * 512) / 4096
|
||||||
MSG="Installing system partition..."
|
MSG="Installing system partition..."
|
||||||
|
|
||||||
if [[ "$ROOT" != [Nn]* ]]; then
|
fakeroot -- bash -c "set -Eeu;\
|
||||||
|
[ -s $HDP.txz ] && tar xpfJ $HDP.txz --absolute-names -C $MOUNT/;\
|
||||||
tar xpfJ "$HDA.txz" --absolute-names --skip-old-files -C "$MOUNT/"
|
[ -s $IDB.txz ] && tar xpfJ $IDB.txz --absolute-names -C $INDEX_DB/;\
|
||||||
|
tar xpfJ $HDA.txz --absolute-names --skip-old-files -C $MOUNT/;\
|
||||||
info "Install: $MSG" && html "$MSG"
|
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"
|
||||||
mke2fs -q -t ext4 -b 4096 -d "$MOUNT/" -L "$LABEL" -F -E "offset=$OFFSET" "$SYSTEM" "$NUMBLOCKS"
|
|
||||||
|
|
||||||
else
|
|
||||||
|
|
||||||
fakeroot -- bash -c "set -Eeu;\
|
|
||||||
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"
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
rm -rf "$MOUNT"
|
rm -rf "$MOUNT"
|
||||||
echo "$BASE" > "$STORAGE/dsm.ver"
|
echo "$BASE" > "$STORAGE/dsm.ver"
|
||||||
|
setOwner "$STORAGE/dsm.ver" || warn "failed to set the owner for \"$STORAGE/dsm.ver\" !"
|
||||||
|
|
||||||
if [[ "$URL" == "file://$STORAGE/$BASE.pat" ]]; then
|
if [[ "$URL" == "file://$STORAGE/$BASE.pat" ]]; then
|
||||||
rm -f "$PAT"
|
rm -f "$PAT"
|
||||||
@@ -334,11 +337,13 @@ else
|
|||||||
mv -f "$PAT" "$STORAGE/$BASE.pat"
|
mv -f "$PAT" "$STORAGE/$BASE.pat"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ -f "$STORAGE/$BASE.pat" ]; then
|
||||||
|
setOwner "$STORAGE/$BASE.pat" || warn "failed to set the owner for \"$STORAGE/$BASE.pat\" !"
|
||||||
|
fi
|
||||||
|
|
||||||
mv -f "$BOOT" "$STORAGE/$BASE.boot.img"
|
mv -f "$BOOT" "$STORAGE/$BASE.boot.img"
|
||||||
|
setOwner "$STORAGE/$BASE.boot.img" || warn "failed to set the owner for \"$STORAGE/$BASE.boot.img\" !"
|
||||||
|
|
||||||
rm -rf "$TMP"
|
rm -rf "$TMP"
|
||||||
|
|
||||||
{ set +x; } 2>/dev/null
|
|
||||||
[[ "$DEBUG" == [Yy1]* ]] && echo
|
|
||||||
|
|
||||||
html "Installation finished successfully..."
|
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
+112
@@ -0,0 +1,112 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
msg="Checking memory..."
|
||||||
|
enabled "$DEBUG" && echo "$msg"
|
||||||
|
|
||||||
|
app() {
|
||||||
|
echo "Virtual DSM"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
checkConfiguredMemory() {
|
||||||
|
|
||||||
|
if disabled "$RAM_CHECK" || [[ "${RAM_SIZE,,}" == "max" || "${RAM_SIZE,,}" == "half" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local wanted msg avail_mem
|
||||||
|
wanted=$(numfmt --from=iec "$RAM_SIZE")
|
||||||
|
avail_mem=$(formatBytes "$RAM_AVAIL")
|
||||||
|
|
||||||
|
if (( (wanted + RAM_SPARE) > RAM_AVAIL )); then
|
||||||
|
msg="Your configured RAM_SIZE of ${RAM_SIZE/G/ GB} is too high for the $avail_mem of free memory available,"
|
||||||
|
if [[ "${FS,,}" == "zfs" ]]; then
|
||||||
|
info "$msg but since ZFS is active this will be ignored."
|
||||||
|
else
|
||||||
|
RAM_SIZE="max"
|
||||||
|
warn "$msg it will automatically be adjusted to a lower amount."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if (( (wanted + (RAM_SPARE * 3)) > RAM_AVAIL )); then
|
||||||
|
msg="your configured RAM_SIZE of ${RAM_SIZE/G/ GB} is very close to the $avail_mem of free memory available,"
|
||||||
|
if [[ "${FS,,}" == "zfs" ]]; then
|
||||||
|
info "$msg but since ZFS is active this will be ignored."
|
||||||
|
else
|
||||||
|
warn "$msg please consider a lower amount."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
configureHalfMemory() {
|
||||||
|
local wanted
|
||||||
|
|
||||||
|
if [[ "${RAM_SIZE,,}" != "half" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if (( (RAM_AVAIL / 2) > RAM_SPARE )); then
|
||||||
|
wanted=$(( (RAM_AVAIL / 2) / 1048577 ))
|
||||||
|
RAM_SIZE="${wanted}M"
|
||||||
|
info "Allocated $wanted MB of RAM for $(app)."
|
||||||
|
else
|
||||||
|
RAM_SIZE="max"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
configureMaxMemory() {
|
||||||
|
local wanted
|
||||||
|
|
||||||
|
if [[ "${RAM_SIZE,,}" != "max" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if (( RAM_AVAIL < (RAM_SPARE * 2) )); then
|
||||||
|
|
||||||
|
wanted=$(( RAM_AVAIL / 2 ))
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
|
wanted=$(( RAM_AVAIL - (RAM_SPARE * 3) ))
|
||||||
|
|
||||||
|
if (( wanted < (RAM_SPARE * 6) )); then
|
||||||
|
wanted=$(( RAM_AVAIL - RAM_SPARE ))
|
||||||
|
fi
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
wanted=$(( wanted / 1048577 ))
|
||||||
|
RAM_SIZE="${wanted}M"
|
||||||
|
|
||||||
|
info "Allocated $wanted MB of RAM for $(app)."
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
checkMinimumMemory() {
|
||||||
|
local wanted
|
||||||
|
|
||||||
|
wanted=$(numfmt --from=iec "$RAM_SIZE")
|
||||||
|
|
||||||
|
if [ "$wanted" -lt "$RAM_MINIMUM" ]; then
|
||||||
|
wanted=$(( wanted / 1048577 ))
|
||||||
|
error "Not enough memory available, there is only $wanted MB left!"
|
||||||
|
exit 16
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
getMemoryInfo
|
||||||
|
|
||||||
|
checkConfiguredMemory
|
||||||
|
configureHalfMemory
|
||||||
|
configureMaxMemory
|
||||||
|
checkMinimumMemory
|
||||||
|
|
||||||
|
return 0
|
||||||
+2184
-146
File diff suppressed because it is too large
Load Diff
+325
-121
@@ -1,135 +1,250 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -Eeuo pipefail
|
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
|
# Configure QEMU for graceful shutdown
|
||||||
|
|
||||||
API_CMD=6
|
API_CMD=6
|
||||||
API_TIMEOUT=50
|
|
||||||
API_HOST="127.0.0.1:2210"
|
|
||||||
|
|
||||||
QEMU_TERM=""
|
SHUTDOWN_SKIP=0
|
||||||
QEMU_PORT=7100
|
SHUTDOWN_SIGNAL=0
|
||||||
QEMU_TIMEOUT=50
|
|
||||||
QEMU_PID="/run/shm/qemu.pid"
|
|
||||||
QEMU_LOG="/run/shm/qemu.log"
|
|
||||||
QEMU_OUT="/run/shm/qemu.out"
|
|
||||||
QEMU_END="/run/shm/qemu.end"
|
|
||||||
|
|
||||||
if [[ "$KVM" == [Nn]* ]]; then
|
QEMU_END="$QEMU_DIR/qemu.end"
|
||||||
API_TIMEOUT=$(( API_TIMEOUT*2 ))
|
CONSOLE_PID="$QEMU_DIR/console.pid"
|
||||||
QEMU_TIMEOUT=$(( QEMU_TIMEOUT*2 ))
|
CONSOLE_SOCKET="$QEMU_DIR/console.sock"
|
||||||
fi
|
QEMU_START_PID="$QEMU_DIR/qemu.start.pid"
|
||||||
|
|
||||||
touch "$QEMU_LOG"
|
|
||||||
|
|
||||||
_trap() {
|
_trap() {
|
||||||
func="$1" ; shift
|
|
||||||
for sig ; do
|
local func="$1"; shift
|
||||||
|
local sig
|
||||||
|
|
||||||
|
TRAP_PID=$BASHPID
|
||||||
|
|
||||||
|
for sig; do
|
||||||
|
# Capture the local callback and signal while registering the trap.
|
||||||
|
# shellcheck disable=SC2064
|
||||||
trap "$func $sig" "$sig"
|
trap "$func $sig" "$sig"
|
||||||
done
|
done
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
signalCode() {
|
||||||
|
|
||||||
|
local sig="$1"
|
||||||
|
|
||||||
|
case "$sig" in
|
||||||
|
SIGHUP) echo 129 ;;
|
||||||
|
SIGINT) echo 130 ;;
|
||||||
|
SIGQUIT) echo 131 ;;
|
||||||
|
SIGABRT) echo 134 ;;
|
||||||
|
SIGTERM) echo 143 ;;
|
||||||
|
*) echo 0 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
displayReason() {
|
||||||
|
|
||||||
|
local reason="$1"
|
||||||
|
|
||||||
|
case "$reason" in
|
||||||
|
129 ) echo "SIGHUP" ;;
|
||||||
|
130 ) echo "SIGINT" ;;
|
||||||
|
131 ) echo "SIGQUIT" ;;
|
||||||
|
134 ) echo "SIGABRT" ;;
|
||||||
|
143 ) echo "SIGTERM" ;;
|
||||||
|
* ) echo "$reason" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
readQemuPid() {
|
||||||
|
|
||||||
|
readPidFile "$1" "$QEMU_START_PID" && return 0
|
||||||
|
readPidFile "$1" "$QEMU_PID"
|
||||||
|
}
|
||||||
|
|
||||||
|
qemuPidFile() {
|
||||||
|
|
||||||
|
local -n _file="$1"
|
||||||
|
|
||||||
|
_file="$QEMU_PID"
|
||||||
|
[ -s "$QEMU_START_PID" ] && _file="$QEMU_START_PID"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
waitQemuExit() {
|
||||||
|
|
||||||
|
local timeout="${1:-10}"
|
||||||
|
local file
|
||||||
|
|
||||||
|
qemuPidFile file
|
||||||
|
waitPidFile "$file" "$timeout"
|
||||||
|
}
|
||||||
|
|
||||||
|
waitQemuPid() {
|
||||||
|
|
||||||
|
local cnt=0
|
||||||
|
|
||||||
|
while ! readQemuPid "$1"; do
|
||||||
|
sleep 0.02
|
||||||
|
cnt=$((cnt + 1))
|
||||||
|
(( cnt >= 50 )) && return 1
|
||||||
|
done
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
forceKillQemu() {
|
||||||
|
|
||||||
|
local reason="$1"
|
||||||
|
local pid display
|
||||||
|
|
||||||
|
readQemuPid pid || return 0
|
||||||
|
isAlive "$pid" || return 0
|
||||||
|
|
||||||
|
display=$(displayReason "$reason")
|
||||||
|
error "Forcefully terminating $(app), reason: $display..."
|
||||||
|
{ disown "$pid" || :; kill -9 -- "$pid" || :; } 2>/dev/null
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanupHelpers() {
|
||||||
|
|
||||||
|
local pids=( "${HOST_PID:-}" "${WSD_PID:-}" "${CONSOLE_PID:-}" \
|
||||||
|
"${WEB_PID:-}" "${PASST_PID:-}" "${DNSMASQ_PID:-}" )
|
||||||
|
|
||||||
|
mKill "${pids[@]}"
|
||||||
|
fKill "print.sh"
|
||||||
|
|
||||||
|
rm -f -- "$HOST_API_SOCKET" "$HOST_AGENT_SOCKET"
|
||||||
|
|
||||||
|
closeNetwork
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
startConsole() {
|
||||||
|
|
||||||
|
local output="${1:-/dev/tty}"
|
||||||
|
local cnt=0
|
||||||
|
|
||||||
|
rm -f -- "$CONSOLE_SOCKET" "$CONSOLE_PID"
|
||||||
|
|
||||||
|
if ! stty -icanon -echo isig -ixon min 1 time 0 </dev/tty; then
|
||||||
|
error "Failed to configure serial console terminal!"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
(
|
||||||
|
trap '' INT QUIT
|
||||||
|
exec nc -lU "$CONSOLE_SOCKET" </dev/tty >"$output"
|
||||||
|
) &
|
||||||
|
|
||||||
|
local pid="$!"
|
||||||
|
echo "$pid" > "$CONSOLE_PID"
|
||||||
|
|
||||||
|
while [ ! -S "$CONSOLE_SOCKET" ]; do
|
||||||
|
|
||||||
|
if ! isAlive "$pid"; then
|
||||||
|
rm -f -- "$CONSOLE_PID"
|
||||||
|
error "Serial console relay exited unexpectedly!"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep 0.02
|
||||||
|
cnt=$((cnt + 1))
|
||||||
|
|
||||||
|
if (( cnt > 100 )); then
|
||||||
|
error "Failed to start serial console relay!"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
done
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
stopConsole() {
|
||||||
|
|
||||||
|
mKill "$CONSOLE_PID"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
startQemu() {
|
||||||
|
|
||||||
|
rm -f -- "$QEMU_START_PID"
|
||||||
|
|
||||||
|
(
|
||||||
|
trap '' INT QUIT
|
||||||
|
|
||||||
|
# shellcheck disable=SC2016
|
||||||
|
exec setsid -f -w sh -c '
|
||||||
|
file=$1
|
||||||
|
shift
|
||||||
|
|
||||||
|
"$@" &
|
||||||
|
pid=$!
|
||||||
|
printf "%s\n" "$pid" > "$file" || exit 1
|
||||||
|
|
||||||
|
rc=0
|
||||||
|
wait "$pid" 2>/dev/null || rc=$?
|
||||||
|
exit "$rc"
|
||||||
|
' sh "$QEMU_START_PID" "$@"
|
||||||
|
) </dev/null &
|
||||||
|
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
finish() {
|
finish() {
|
||||||
|
|
||||||
local pid
|
local reason=$1 failed=0
|
||||||
local reason=$1
|
|
||||||
|
|
||||||
if [ -f "$QEMU_PID" ]; then
|
if [ ! -f "$QEMU_END" ] && (( reason != 0 )); then
|
||||||
|
failed=1
|
||||||
pid=$(<"$QEMU_PID")
|
|
||||||
echo && error "Forcefully terminating QEMU process, reason: $reason..."
|
|
||||||
{ kill -15 "$pid" || true; } 2>/dev/null
|
|
||||||
|
|
||||||
while isAlive "$pid"; do
|
|
||||||
sleep 1
|
|
||||||
# Workaround for zombie pid
|
|
||||||
[ ! -f "$QEMU_PID" ] && break
|
|
||||||
done
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
fKill "print.sh"
|
touch "$QEMU_END"
|
||||||
fKill "host.bin"
|
|
||||||
|
|
||||||
closeNetwork
|
forceKillQemu "$reason"
|
||||||
|
cleanupHelpers
|
||||||
|
|
||||||
sleep 1
|
if ! waitQemuExit 10; then
|
||||||
echo && echo "❯ Shutdown completed!"
|
warn "Timed out while waiting for $(app) to exit!"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
|
||||||
|
if (( failed == 0 )); then
|
||||||
|
echo "❯ Shutdown completed!"
|
||||||
|
else
|
||||||
|
error "QEMU exited unexpectedly!"
|
||||||
|
fi
|
||||||
|
|
||||||
exit "$reason"
|
exit "$reason"
|
||||||
}
|
}
|
||||||
|
|
||||||
terminal() {
|
sendGuestShutdown() {
|
||||||
|
|
||||||
local dev=""
|
local pid="$1"
|
||||||
|
local response
|
||||||
if [ -f "$QEMU_OUT" ]; then
|
|
||||||
|
|
||||||
local msg
|
|
||||||
msg=$(<"$QEMU_OUT")
|
|
||||||
|
|
||||||
if [ -n "$msg" ]; then
|
|
||||||
|
|
||||||
if [[ "${msg,,}" != "char"* || "$msg" != *"serial0)" ]]; then
|
|
||||||
echo "$msg"
|
|
||||||
fi
|
|
||||||
|
|
||||||
dev="${msg#*/dev/p}"
|
|
||||||
dev="/dev/p${dev%% *}"
|
|
||||||
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ! -c "$dev" ]; then
|
|
||||||
dev=$(echo 'info chardev' | nc -q 1 -w 1 localhost "$QEMU_PORT" | tr -d '\000')
|
|
||||||
dev="${dev#*serial0}"
|
|
||||||
dev="${dev#*pty:}"
|
|
||||||
dev="${dev%%$'\n'*}"
|
|
||||||
dev="${dev%%$'\r'*}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ! -c "$dev" ]; then
|
|
||||||
error "Device '$dev' not found!"
|
|
||||||
finish 34 && return 34
|
|
||||||
fi
|
|
||||||
|
|
||||||
QEMU_TERM="$dev"
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
_graceful_shutdown() {
|
|
||||||
|
|
||||||
local cnt=0
|
|
||||||
local code=$?
|
|
||||||
local pid url response
|
|
||||||
|
|
||||||
set +e
|
|
||||||
|
|
||||||
if [ -f "$QEMU_END" ]; then
|
|
||||||
echo && info "Received $1 signal while already shutting down..."
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
touch "$QEMU_END"
|
|
||||||
echo && info "Received $1 signal, sending shutdown command..."
|
|
||||||
|
|
||||||
if [ ! -f "$QEMU_PID" ]; then
|
|
||||||
echo && error "QEMU PID file does not exist?"
|
|
||||||
finish "$code" && return "$code"
|
|
||||||
fi
|
|
||||||
|
|
||||||
pid=$(<"$QEMU_PID")
|
|
||||||
|
|
||||||
if ! isAlive "$pid"; then
|
|
||||||
echo && error "QEMU process does not exist?"
|
|
||||||
finish "$code" && return "$code"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Don't send the powerdown signal because vDSM ignores ACPI signals
|
# Don't send the powerdown signal because vDSM ignores ACPI signals
|
||||||
# echo 'system_powerdown' | nc -q 1 -w 1 localhost "${QEMU_PORT}" > /dev/null
|
# nc -q 1 -w 1 -U "$QEMU_DIR/monitor.sock" &> /dev/null <<<'system_powerdown' || :
|
||||||
|
|
||||||
# Send shutdown command to guest agent via serial port
|
# Send shutdown command to guest agent via serial port
|
||||||
url="http://$API_HOST/read?command=$API_CMD&timeout=$API_TIMEOUT"
|
API_TIMEOUT=$(strip "$API_TIMEOUT")
|
||||||
response=$(curl -sk -m "$(( API_TIMEOUT+2 ))" -S "$url" 2>&1)
|
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
|
if [[ "$response" =~ "\"success\"" ]]; then
|
||||||
|
|
||||||
@@ -139,42 +254,131 @@ _graceful_shutdown() {
|
|||||||
|
|
||||||
response="${response#*message\"\: \"}"
|
response="${response#*message\"\: \"}"
|
||||||
[ -z "$response" ] && response="second signal"
|
[ -z "$response" ] && response="second signal"
|
||||||
|
|
||||||
echo && error "Forcefully terminating because of: ${response%%\"*}"
|
echo && error "Forcefully terminating because of: ${response%%\"*}"
|
||||||
{ kill -15 "$pid" || true; } 2>/dev/null
|
kill -15 -- "$pid" 2>/dev/null || :
|
||||||
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
while [ "$cnt" -lt "$QEMU_TIMEOUT" ]; do
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
! isAlive "$pid" && break
|
normalizeTimeout() {
|
||||||
|
|
||||||
sleep 1
|
local term_grace=3 # seconds before loop ends to send SIGTERM
|
||||||
cnt=$((cnt+1))
|
local cleanup_grace=3 # seconds reserved after the loop for cleanup
|
||||||
|
|
||||||
[[ "$DEBUG" == [Yy1]* ]] && info "Shutting down, waiting... ($cnt/$QEMU_TIMEOUT)"
|
TIMEOUT=$(strip "$TIMEOUT")
|
||||||
|
if [[ ! "$TIMEOUT" =~ ^[0-9]+$ ]]; then
|
||||||
|
TIMEOUT=105
|
||||||
|
fi
|
||||||
|
|
||||||
# Workaround for zombie pid
|
if (( TIMEOUT >= 30 )); then
|
||||||
[ ! -f "$QEMU_PID" ] && break
|
term_grace=5
|
||||||
|
cleanup_grace=5
|
||||||
|
elif (( TIMEOUT >= 15 )); then
|
||||||
|
term_grace=4
|
||||||
|
cleanup_grace=4
|
||||||
|
fi
|
||||||
|
|
||||||
|
local elapsed=$((SECONDS - start))
|
||||||
|
local timeout_left=$((TIMEOUT - elapsed))
|
||||||
|
|
||||||
|
local min=$((term_grace + cleanup_grace + 1))
|
||||||
|
(( timeout_left < min )) && timeout_left=$min
|
||||||
|
|
||||||
|
wait_until=$((timeout_left - cleanup_grace))
|
||||||
|
sigterm_at=$((wait_until - term_grace))
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
waitForShutdown() {
|
||||||
|
|
||||||
|
local cnt=0
|
||||||
|
local pid="$1"
|
||||||
|
local name="$APP"
|
||||||
|
|
||||||
|
while (( cnt <= wait_until && SHUTDOWN_SKIP == 0 )); do
|
||||||
|
|
||||||
|
sleep 1 &
|
||||||
|
local slp=$!
|
||||||
|
|
||||||
|
# Stop waiting if the process has exited
|
||||||
|
isAlive "$pid" || break
|
||||||
|
|
||||||
|
# Workaround for stale/zombie QEMU pid file
|
||||||
|
[ ! -s "$QEMU_START_PID" ] && [ ! -s "$QEMU_PID" ] && break
|
||||||
|
|
||||||
|
if (( cnt == sigterm_at )); then
|
||||||
|
info "${name^} is still running, sending SIGTERM... ($cnt/$wait_until)"
|
||||||
|
kill -15 -- "$pid" 2>/dev/null || :
|
||||||
|
elif (( cnt > 0 )) && enabled "${DEBUG:-}"; then
|
||||||
|
info "Waiting for $name to shut down... ($cnt/$wait_until)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
wait "$slp" || :
|
||||||
|
(( cnt++ ))
|
||||||
|
|
||||||
done
|
done
|
||||||
|
|
||||||
if [ "$cnt" -ge "$QEMU_TIMEOUT" ]; then
|
return 0
|
||||||
echo && error "Shutdown timeout reached, aborting..."
|
|
||||||
fi
|
|
||||||
|
|
||||||
finish "$code" && return "$code"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MON_OPTS="\
|
graceful_shutdown() {
|
||||||
-pidfile $QEMU_PID \
|
|
||||||
-monitor telnet:localhost:$QEMU_PORT,server,nowait,nodelay"
|
|
||||||
|
|
||||||
if [[ "$CONSOLE" != [Yy]* ]]; then
|
local sig="$1"
|
||||||
|
local pid code
|
||||||
|
|
||||||
MON_OPTS="$MON_OPTS -daemonize -D $QEMU_LOG"
|
[[ $BASHPID != "$TRAP_PID" ]] && return
|
||||||
|
|
||||||
_trap _graceful_shutdown SIGTERM SIGHUP SIGINT SIGABRT SIGQUIT
|
code=$(signalCode "$sig")
|
||||||
|
|
||||||
|
if [ -f "$QEMU_END" ]; then
|
||||||
|
|
||||||
|
if (( code == 130 && SHUTDOWN_SIGNAL == code )); then
|
||||||
|
SHUTDOWN_SKIP=1
|
||||||
|
echo && info "Received SIGINT again, forcing shutdown..."
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo && info "Received $sig signal while already shutting down..."
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
set +e
|
||||||
|
start=$SECONDS
|
||||||
|
SHUTDOWN_SIGNAL=$code
|
||||||
|
|
||||||
|
touch "$QEMU_END"
|
||||||
|
echo && info "Received $sig signal, sending shutdown command..."
|
||||||
|
|
||||||
|
if ! readQemuPid pid; then
|
||||||
|
if ! interactive || ! waitQemuPid pid; then
|
||||||
|
warn "QEMU PID file does not exist?"
|
||||||
|
finish "$code"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$pid" ] || ! isAlive "$pid"; then
|
||||||
|
warn "QEMU process with PID $pid does not exist?"
|
||||||
|
finish "$code"
|
||||||
|
fi
|
||||||
|
|
||||||
|
sendGuestShutdown "$pid"
|
||||||
|
normalizeTimeout
|
||||||
|
waitForShutdown "$pid"
|
||||||
|
|
||||||
|
finish "$code"
|
||||||
|
}
|
||||||
|
|
||||||
|
enabled "$SHUTDOWN" || return 0
|
||||||
|
[ -n "${QEMU_TIMEOUT:-}" ] && TIMEOUT="$QEMU_TIMEOUT"
|
||||||
|
|
||||||
|
if interactive; then
|
||||||
|
_trap graceful_shutdown SIGINT
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
_trap graceful_shutdown SIGTERM SIGHUP SIGABRT SIGQUIT
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
+171
-54
@@ -1,103 +1,220 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
# shellcheck disable=SC2329
|
||||||
set -Eeuo pipefail
|
set -Eeuo pipefail
|
||||||
|
|
||||||
: "${DHCP:="N"}"
|
: "${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; }
|
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; }
|
error () { printf "%b%s%b" "\E[1;31m❯ " "ERROR: $1" "\E[0m\n" >&2; }
|
||||||
|
|
||||||
|
disabled "$NETWORK" && exit 0
|
||||||
|
|
||||||
file="/run/shm/dsm.url"
|
file="/run/shm/dsm.url"
|
||||||
info="/run/shm/msg.html"
|
msgs="/run/shm/msg.html"
|
||||||
|
driver="/run/shm/qemu.nic"
|
||||||
page="/run/shm/index.html"
|
page="/run/shm/index.html"
|
||||||
address="/run/shm/qemu.ip"
|
address="/run/shm/qemu.ip"
|
||||||
shutdown="/run/shm/qemu.end"
|
shutdown="/run/shm/qemu.end"
|
||||||
|
socket="/run/shm/qemu-host-api.sock"
|
||||||
template="/var/www/index.html"
|
template="/var/www/index.html"
|
||||||
url="http://127.0.0.1:2210/read?command=10"
|
url="http://localhost/read?command=10"
|
||||||
|
|
||||||
resp_err="Guest returned an invalid response:"
|
resp_err="Guest returned an invalid response:"
|
||||||
curl_err="Failed to connect to guest: curl error"
|
curl_err="Failed to connect to guest: curl error"
|
||||||
jq_err="Failed to parse response from guest: jq error"
|
jq_err="Failed to parse response from guest: jq error"
|
||||||
|
|
||||||
while [ ! -f "$file" ]
|
exitIfShuttingDown() {
|
||||||
do
|
|
||||||
|
|
||||||
# Check if not shutting down
|
|
||||||
[ -f "$shutdown" ] && exit 1
|
|
||||||
|
|
||||||
sleep 3
|
|
||||||
|
|
||||||
[ -f "$shutdown" ] && exit 1
|
[ -f "$shutdown" ] && exit 1
|
||||||
[ -f "$file" ] && break
|
|
||||||
|
|
||||||
# Retrieve network info from guest VM
|
return 0
|
||||||
{ json=$(curl -m 20 -sk "$url"); rc=$?; } || :
|
}
|
||||||
|
|
||||||
[ -f "$shutdown" ] && exit 1
|
queryGuest() {
|
||||||
(( rc != 0 )) && error "$curl_err $rc" && continue
|
|
||||||
|
|
||||||
{ result=$(echo "$json" | jq -r '.status'); rc=$?; } || :
|
{ json=$(curl --unix-socket "$socket" -m 20 -sk "$url"); local rc=$?; } || :
|
||||||
(( rc != 0 )) && error "$jq_err $rc ( $json )" && continue
|
|
||||||
[[ "$result" == "null" ]] && error "$resp_err $json" && continue
|
|
||||||
|
|
||||||
if [[ "$result" != "success" ]] ; then
|
exitIfShuttingDown
|
||||||
{ msg=$(echo "$json" | jq -r '.message'); rc=$?; } || :
|
|
||||||
error "Guest replied $result: $msg" && continue
|
if (( rc != 0 )); then
|
||||||
|
error "$curl_err $rc"
|
||||||
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
{ port=$(echo "$json" | jq -r '.data.data.dsm_setting.data.http_port'); rc=$?; } || :
|
return 0
|
||||||
(( rc != 0 )) && error "$jq_err $rc ( $json )" && continue
|
}
|
||||||
[[ "$port" == "null" ]] && error "$resp_err $json" && continue
|
|
||||||
[ -z "$port" ] && continue
|
|
||||||
|
|
||||||
{ ip=$(echo "$json" | jq -r '.data.data.ip.data[] | select((.name=="eth0") and has("ip")).ip'); rc=$?; } || :
|
readJsonField() {
|
||||||
(( rc != 0 )) && error "$jq_err $rc ( $json )" && continue
|
|
||||||
[[ "$ip" == "null" ]] && error "$resp_err $json" && continue
|
|
||||||
|
|
||||||
if [ -z "$ip" ]; then
|
local query="$1"
|
||||||
[[ "$DHCP" == [Yy1]* ]] && continue
|
local result
|
||||||
ip="20.20.20.21"
|
|
||||||
|
{ result=$(jq -r "$query" <<< "$json"); local rc=$?; } || :
|
||||||
|
|
||||||
|
if (( rc != 0 )); then
|
||||||
|
error "$jq_err $rc ( $json )"
|
||||||
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "$ip:$port" > $file
|
if [[ "$result" == "null" ]]; then
|
||||||
|
error "$resp_err $json"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
done
|
printf '%s\n' "$result"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
[ -f "$shutdown" ] && exit 1
|
readGuestStatus() {
|
||||||
|
|
||||||
location=$(<"$file")
|
local result msg
|
||||||
|
|
||||||
if [[ "$location" != "20.20"* ]]; then
|
result=$(readJsonField '.status') || return 1
|
||||||
|
|
||||||
|
if [[ "$result" != "success" ]]; then
|
||||||
|
{ msg=$(jq -r '.message // empty' <<< "$json"); local rc=$?; } || :
|
||||||
|
|
||||||
|
if (( rc != 0 )); then
|
||||||
|
error "$jq_err $rc ( $json )"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
error "Guest replied $result: $msg"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
readGuestPort() {
|
||||||
|
|
||||||
|
port=$(readJsonField '.data.data.dsm_setting.data.http_port') || return 1
|
||||||
|
[ -z "$port" ] && return 1
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
readGuestIp() {
|
||||||
|
|
||||||
|
ip=$(readJsonField '.data.data.ip.data[] | select(.name=="eth0" and has("ip")) | .ip | select(test("^[0-9]+\\."))') || return 1
|
||||||
|
[ -z "$ip" ] && return 1
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
writeDsmLocation() {
|
||||||
|
|
||||||
|
echo "$ip:$port" > "$file"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pollGuestLocation() {
|
||||||
|
|
||||||
|
while [ ! -s "$file" ]; do
|
||||||
|
|
||||||
|
# Check if not shutting down
|
||||||
|
exitIfShuttingDown
|
||||||
|
|
||||||
|
sleep 3
|
||||||
|
|
||||||
|
exitIfShuttingDown
|
||||||
|
[ -s "$file" ] && break
|
||||||
|
|
||||||
|
# Retrieve network info from guest VM
|
||||||
|
queryGuest || continue
|
||||||
|
readGuestStatus || continue
|
||||||
|
readGuestPort || continue
|
||||||
|
readGuestIp || continue
|
||||||
|
writeDsmLocation
|
||||||
|
|
||||||
|
done
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
checkAddressConflict() {
|
||||||
|
|
||||||
|
local guest_ip="${location%:*}"
|
||||||
|
local container_ip=""
|
||||||
|
|
||||||
|
[ -s "$address" ] && container_ip=$(<"$address")
|
||||||
|
[ -z "$container_ip" ] && return 0
|
||||||
|
[[ "$guest_ip" != "$container_ip" ]] && return 0
|
||||||
|
|
||||||
|
warn "DSM is using the same IP as the container, this will cause connectivity issues."
|
||||||
|
warn "change the container's macvlan IP or assign DSM a different address in your router."
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
writeDhcpPage() {
|
||||||
|
|
||||||
|
local html
|
||||||
|
|
||||||
msg="http://$location"
|
msg="http://$location"
|
||||||
title="<title>Virtual DSM</title>"
|
local title="<title>Virtual DSM</title>"
|
||||||
body="The location of DSM is <a href='http://$location'>http://$location</a>"
|
local body="The location of DSM is <a href='http://$location'>http://$location</a>"
|
||||||
script="<script>setTimeout(function(){ window.location.assign('http://$location'); }, 3000);</script>"
|
local script="<script>setTimeout(function(){ window.location.assign('http://$location'); }, 3000);</script>"
|
||||||
|
|
||||||
HTML=$(<"$template")
|
html=$(<"$template")
|
||||||
HTML="${HTML/\[1\]/$title}"
|
html="${html/\[1\]/$title}"
|
||||||
HTML="${HTML/\[2\]/$script}"
|
html="${html/\[2\]/$script}"
|
||||||
HTML="${HTML/\[3\]/$body}"
|
html="${html/\[3\]/$body}"
|
||||||
HTML="${HTML/\[4\]/}"
|
html="${html/\[4\]/}"
|
||||||
HTML="${HTML/\[5\]/}"
|
html="${html/\[5\]/}"
|
||||||
|
|
||||||
echo "$HTML" > "$page"
|
echo "$html" > "$page"
|
||||||
echo "$body" > "$info"
|
echo "$body" > "$msgs"
|
||||||
|
|
||||||
else
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
buildStaticMessage() {
|
||||||
|
|
||||||
|
local nic ip
|
||||||
|
|
||||||
|
nic=$(<"$driver")
|
||||||
ip=$(<"$address")
|
ip=$(<"$address")
|
||||||
port="${location##*:}"
|
local port="${location##*:}"
|
||||||
|
|
||||||
if [[ "$ip" == "172."* ]]; then
|
if [[ "${nic,,}" != "macvlan" ]]; then
|
||||||
msg="port $port"
|
msg="port $port"
|
||||||
else
|
else
|
||||||
msg="http://$ip:$port"
|
msg="http://$ip:$port"
|
||||||
fi
|
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
|
fi
|
||||||
|
|
||||||
echo "" >&2
|
printLoginMessage
|
||||||
info "-----------------------------------------------------------"
|
|
||||||
info " You can now login to DSM at $msg"
|
exit 0
|
||||||
info "-----------------------------------------------------------"
|
|
||||||
echo "" >&2
|
|
||||||
|
|||||||
+169
-47
@@ -3,75 +3,197 @@ set -Eeuo pipefail
|
|||||||
|
|
||||||
# Docker environment variables
|
# Docker environment variables
|
||||||
|
|
||||||
: "${KVM:="Y"}"
|
|
||||||
: "${HOST_CPU:=""}"
|
: "${HOST_CPU:=""}"
|
||||||
: "${CPU_MODEL:="host"}"
|
: "${CPU_FLAGS:=""}"
|
||||||
: "${CPU_FEATURES:="+ssse3,+sse4.1,+sse4.2"}"
|
: "${CPU_MODEL:=""}"
|
||||||
|
|
||||||
[ "$ARCH" != "amd64" ] && KVM="N"
|
HOST_CPU=$(strip "$HOST_CPU")
|
||||||
|
CPU_FLAGS=$(strip "$CPU_FLAGS")
|
||||||
|
CPU_MODEL=$(strip "$CPU_MODEL")
|
||||||
|
|
||||||
if [[ "$KVM" != [Nn]* ]]; then
|
selectClocksource() {
|
||||||
|
|
||||||
KVM_ERR=""
|
CLOCKSOURCE="tsc"
|
||||||
|
[[ "${ARCH,,}" == "arm64" ]] && CLOCKSOURCE="arch_sys_counter"
|
||||||
|
CLOCK="/sys/devices/system/clocksource/clocksource0/current_clocksource"
|
||||||
|
|
||||||
if [ ! -e /dev/kvm ]; then
|
return 0
|
||||||
KVM_ERR="(device file missing)"
|
}
|
||||||
else
|
|
||||||
if ! sh -c 'echo -n > /dev/kvm' &> /dev/null; then
|
checkClocksource() {
|
||||||
KVM_ERR="(no write access)"
|
|
||||||
else
|
local result
|
||||||
if ! grep -q -e vmx -e svm /proc/cpuinfo; then
|
|
||||||
KVM_ERR="(vmx/svm disabled)"
|
if [ ! -f "$CLOCK" ]; then
|
||||||
fi
|
warn "file \"$CLOCK\" cannot be found?"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
result=$(<"$CLOCK")
|
||||||
|
result="${result//[![:print:]]/}"
|
||||||
|
|
||||||
|
case "${result,,}" in
|
||||||
|
"${CLOCKSOURCE,,}" ) ;;
|
||||||
|
"kvm-clock" ) info "Nested KVM virtualization detected.." ;;
|
||||||
|
"hyperv_clocksource_tsc_page" ) info "Nested Hyper-V virtualization detected.." ;;
|
||||||
|
"hpet" ) warn "unsupported clock source detected: '$result'. Please set host clock source to '$CLOCKSOURCE'." ;;
|
||||||
|
*) warn "unexpected clock source detected: '$result'. Please set host clock source to '$CLOCKSOURCE'." ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
checkSse42() {
|
||||||
|
|
||||||
|
if ! hasFlag "sse4_2"; then
|
||||||
|
error "Your CPU does not have the SSE4 instruction set that Virtual DSM requires!"
|
||||||
|
enabled "$DEBUG" || exit 88
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
trimSpaces() {
|
||||||
|
|
||||||
|
local value="$1"
|
||||||
|
|
||||||
|
value="${value#"${value%%[![:space:]]*}"}"
|
||||||
|
value="${value%"${value##*[![:space:]]}"}"
|
||||||
|
|
||||||
|
echo "$value"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
removeCpuArgument() {
|
||||||
|
|
||||||
|
local args=" ${ARGUMENTS:-} "
|
||||||
|
|
||||||
|
while [[ "$args" =~ [[:space:]]-cpu([[:space:]][^[:space:]]+|=[^[:space:]]+)? ]]; do
|
||||||
|
local cpu="${BASH_REMATCH[0]}"
|
||||||
|
args="${args/$cpu/ }"
|
||||||
|
warn "Ignoring '${cpu#" "}' from ARGUMENTS, use CPU_MODEL and CPU_FLAGS instead."
|
||||||
|
done
|
||||||
|
|
||||||
|
ARGUMENTS=$(trimSpaces "$args")
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
configureKvmCpuModel() {
|
||||||
|
|
||||||
|
CPU_FEATURES="kvm=on,l3-cache=on,+hypervisor"
|
||||||
|
KVM_OPTS=",accel=kvm -enable-kvm -global kvm-pit.lost_tick_policy=discard"
|
||||||
|
|
||||||
|
if [ -z "$CPU_MODEL" ]; then
|
||||||
|
CPU_MODEL="host"
|
||||||
|
CPU_FEATURES+=",migratable=no"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
appendKvmInvtscFeature() {
|
||||||
|
|
||||||
|
if hasFlag "svm"; then
|
||||||
|
|
||||||
|
# AMD processor
|
||||||
|
if hasFlag "tsc_scale"; then
|
||||||
|
CPU_FEATURES+=",+invtsc"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
|
# Intel processor
|
||||||
|
local vmx
|
||||||
|
vmx=$(sed -ne '/^vmx flags/s/^.*: //p' /proc/cpuinfo)
|
||||||
|
|
||||||
|
if grep -qw "tsc_scaling" <<< "$vmx"; then
|
||||||
|
CPU_FEATURES+=",+invtsc"
|
||||||
|
fi
|
||||||
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -n "$KVM_ERR" ]; then
|
return 0
|
||||||
KVM="N"
|
}
|
||||||
error "KVM acceleration not detected $KVM_ERR, this will cause a major loss of performance."
|
|
||||||
error "See the FAQ on how to enable it, or continue without KVM by setting KVM=N (not recommended)."
|
configureKvm() {
|
||||||
[[ "$DEBUG" != [Yy1]* ]] && exit 88
|
|
||||||
|
configureKvmCpuModel
|
||||||
|
checkSse42
|
||||||
|
appendKvmInvtscFeature
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
configureTcgCpuModel() {
|
||||||
|
|
||||||
|
if [ -n "$CPU_MODEL" ]; then
|
||||||
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
fi
|
if [[ "$ARCH" == "amd64" ]]; then
|
||||||
|
CPU_MODEL="max"
|
||||||
if [[ "$KVM" != [Nn]* ]]; then
|
CPU_FEATURES+=",migratable=no"
|
||||||
|
else
|
||||||
KVM_OPTS=",accel=kvm -enable-kvm"
|
CPU_MODEL="qemu64"
|
||||||
|
|
||||||
if ! grep -qE '^flags.* (sse4_2)' /proc/cpuinfo; then
|
|
||||||
error "Your host CPU does not have the SSE4.2 instruction set that Virtual DSM requires to boot."
|
|
||||||
error "Disable KVM by setting KVM=N to emulate a compatible CPU, at the cost of performance."
|
|
||||||
[[ "$DEBUG" != [Yy1]* ]] && exit 89
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
else
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
configureTcg() {
|
||||||
|
|
||||||
KVM_OPTS=""
|
KVM_OPTS=""
|
||||||
|
CPU_FEATURES="l3-cache=on,+hypervisor"
|
||||||
|
|
||||||
if [[ "$CPU_MODEL" == "host"* ]]; then
|
if [[ "$ARCH" == "amd64" ]]; then
|
||||||
if [[ "$ARCH" == "amd64" ]]; then
|
KVM_OPTS=" -accel tcg,thread=multi"
|
||||||
CPU_MODEL="max,$CPU_FEATURES"
|
fi
|
||||||
|
|
||||||
|
configureTcgCpuModel
|
||||||
|
CPU_FEATURES+=",+ssse3,+sse4.1,+sse4.2"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
composeCpuFlags() {
|
||||||
|
|
||||||
|
CPU_FLAGS="${CPU_MODEL}${CPU_FEATURES:+,$CPU_FEATURES}${CPU_FLAGS:+,$CPU_FLAGS}"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
configureHostCpuName() {
|
||||||
|
|
||||||
|
if [ -z "$HOST_CPU" ]; then
|
||||||
|
[[ "${CPU,,}" != "unknown" ]] && HOST_CPU="$CPU"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$HOST_CPU" ]; then
|
||||||
|
HOST_CPU="${HOST_CPU%%,*},,"
|
||||||
|
else
|
||||||
|
HOST_CPU="QEMU, Virtual CPU,"
|
||||||
|
if [ "$ARCH" == "amd64" ]; then
|
||||||
|
HOST_CPU+=" X86_64"
|
||||||
else
|
else
|
||||||
CPU_MODEL="qemu64,$CPU_FEATURES"
|
HOST_CPU+=" $ARCH"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
fi
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
if [ -z "$HOST_CPU" ]; then
|
selectClocksource
|
||||||
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')
|
checkClocksource
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "$HOST_CPU" ]; then
|
if ! disabled "$KVM"; then
|
||||||
HOST_CPU="${HOST_CPU%%,*},,"
|
configureKvm
|
||||||
else
|
else
|
||||||
HOST_CPU="QEMU, Virtual CPU,"
|
configureTcg
|
||||||
if [ "$ARCH" == "amd64" ]; then
|
|
||||||
HOST_CPU="$HOST_CPU X86_64"
|
|
||||||
else
|
|
||||||
HOST_CPU="$HOST_CPU $ARCH"
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
removeCpuArgument
|
||||||
|
composeCpuFlags
|
||||||
|
configureHostCpuName
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
+302
@@ -0,0 +1,302 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
info="/run/shm/msg.html"
|
||||||
|
info_tmp="${info}.${BASHPID}.tmp"
|
||||||
|
|
||||||
|
escape() {
|
||||||
|
|
||||||
|
local s
|
||||||
|
|
||||||
|
s=${1//&/\&}
|
||||||
|
s=${s//</\<}
|
||||||
|
s=${s//>/\>}
|
||||||
|
s=${s//'"'/\"}
|
||||||
|
s=${s//"'"/\'}
|
||||||
|
|
||||||
|
printf '%s' "$s"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
writeInfo() {
|
||||||
|
|
||||||
|
local content="$1"
|
||||||
|
|
||||||
|
if ! printf '%s\n' "$content" > "$info_tmp"; then
|
||||||
|
rm -f -- "$info_tmp"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! mv -f -- "$info_tmp" "$info"; then
|
||||||
|
rm -f -- "$info_tmp"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
getBytes() {
|
||||||
|
|
||||||
|
local path="$1"
|
||||||
|
local mode="$2"
|
||||||
|
local bytes="0"
|
||||||
|
|
||||||
|
if [[ "$mode" == "counter" ]]; then
|
||||||
|
if [ -r "$path" ]; then
|
||||||
|
read -r bytes < "$path" || bytes="0"
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ "$bytes" =~ ^[0-9]+$ ]] || bytes="0"
|
||||||
|
printf '%s\n' "$bytes"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -s "$path" ] && [ ! -d "$path" ]; then
|
||||||
|
printf '0\n'
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$mode" == "allocated" ]]; then
|
||||||
|
bytes=$(du -sB1 -- "$path" 2>/dev/null | cut -f1) || bytes="0"
|
||||||
|
else
|
||||||
|
bytes=$(du -sb -- "$path" 2>/dev/null | cut -f1) || bytes="0"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s\n' "$bytes"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
getStatus() {
|
||||||
|
|
||||||
|
local file="$1"
|
||||||
|
local bytes total extra=""
|
||||||
|
|
||||||
|
[ -r "$file" ] || return 1
|
||||||
|
read -r bytes total extra < "$file" || return 1
|
||||||
|
|
||||||
|
if [[ ! "$bytes" =~ ^[0-9]+$ ||
|
||||||
|
! "$total" =~ ^[0-9]+$ ||
|
||||||
|
-n "$extra" ]]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s %s\n' "$bytes" "$total"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
formatSize() {
|
||||||
|
|
||||||
|
local bytes="$1"
|
||||||
|
local size
|
||||||
|
|
||||||
|
size=$(numfmt --to=iec --suffix=B "$bytes" |
|
||||||
|
sed -r 's/([A-Z])/ \1/') ||
|
||||||
|
size="${bytes} bytes"
|
||||||
|
|
||||||
|
printf '%s' "$size"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
printPercentProgress() {
|
||||||
|
|
||||||
|
local percent="$1"
|
||||||
|
|
||||||
|
while (( next_percent <= percent && next_percent <= 100 )); do
|
||||||
|
if [[ "$printed" == "Y" ]]; then
|
||||||
|
printf ' → %s%%' "$next_percent"
|
||||||
|
else
|
||||||
|
printf '%s%%' "$next_percent"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printed="Y"
|
||||||
|
next_percent=$((next_percent + 10))
|
||||||
|
done
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
printCurrentSize() {
|
||||||
|
|
||||||
|
local bytes="$1"
|
||||||
|
local size
|
||||||
|
|
||||||
|
size=$(formatSize "$bytes")
|
||||||
|
|
||||||
|
if [[ "$printed" == "Y" ]]; then
|
||||||
|
printf ' → %s' "$size"
|
||||||
|
else
|
||||||
|
printf '%s' "$size"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printed="Y"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
printSizeProgress() {
|
||||||
|
|
||||||
|
local bytes="$1"
|
||||||
|
local size
|
||||||
|
|
||||||
|
while (( bytes >= next_bytes )); do
|
||||||
|
size=$(formatSize "$next_bytes")
|
||||||
|
|
||||||
|
if [[ "$printed" == "Y" ]]; then
|
||||||
|
printf ' → %s' "$size"
|
||||||
|
else
|
||||||
|
printf '%s' "$size"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printed="Y"
|
||||||
|
next_bytes=$((next_bytes + step_bytes))
|
||||||
|
done
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
stopProgress() {
|
||||||
|
|
||||||
|
if [ -z "$status_file" ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
stopping="Y"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
finishProgress() {
|
||||||
|
|
||||||
|
rm -f -- "$info_tmp"
|
||||||
|
|
||||||
|
if [[ "$output" == "log" && "$printed" == "Y" ]]; then
|
||||||
|
printf '\n'
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
path="$1"
|
||||||
|
total="$2"
|
||||||
|
body=$(escape "$3")
|
||||||
|
output="${4:-}"
|
||||||
|
step_bytes="${5:-536870912}"
|
||||||
|
mode="${6:-apparent}"
|
||||||
|
status_file="${7:-}"
|
||||||
|
|
||||||
|
if [[ -n "$total" && ! "$total" =~ ^(0|[1-9][0-9]*)$ ]]; then
|
||||||
|
printf 'Invalid total size: %s\n' "$total" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! "$step_bytes" =~ ^[1-9][0-9]*$ ]]; then
|
||||||
|
printf 'Invalid progress interval: %s\n' "$step_bytes" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$mode" in
|
||||||
|
apparent | allocated | counter ) ;;
|
||||||
|
* )
|
||||||
|
printf 'Invalid progress mode: %s\n' "$mode" >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
case "$output" in
|
||||||
|
"" | log ) ;;
|
||||||
|
* )
|
||||||
|
printf 'Invalid progress output: %s\n' "$output" >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
printed="N"
|
||||||
|
next_percent=10
|
||||||
|
next_bytes="$step_bytes"
|
||||||
|
log_mode="percent"
|
||||||
|
stopping="N"
|
||||||
|
|
||||||
|
if [ -z "$total" ] || [[ "$total" == "0" ]]; then
|
||||||
|
log_mode="size"
|
||||||
|
fi
|
||||||
|
|
||||||
|
trap finishProgress EXIT
|
||||||
|
trap 'exit 0' HUP INT QUIT
|
||||||
|
trap stopProgress TERM
|
||||||
|
|
||||||
|
if [[ "$body" == *"..." ]]; then
|
||||||
|
body="<p class=\"loading\">${body::-3}</p>"
|
||||||
|
fi
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
|
||||||
|
final_pass="${stopping:-}"
|
||||||
|
bytes=$(getBytes "$path" "$mode")
|
||||||
|
effective_total="$total"
|
||||||
|
|
||||||
|
if [ -n "$status_file" ] && status=$(getStatus "$status_file"); then
|
||||||
|
read -r status_bytes status_total <<< "$status"
|
||||||
|
bytes="$status_bytes"
|
||||||
|
|
||||||
|
if (( status_total > 0 )); then
|
||||||
|
effective_total="$status_total"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# A real total may become available shortly after aria2 starts.
|
||||||
|
if [[ "$log_mode" == "size" &&
|
||||||
|
"$printed" == "N" &&
|
||||||
|
-n "$effective_total" &&
|
||||||
|
"$effective_total" != "0" ]]; then
|
||||||
|
log_mode="percent"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if (( bytes > 4096 )); then
|
||||||
|
|
||||||
|
write_html="Y"
|
||||||
|
|
||||||
|
if [ -z "$effective_total" ] ||
|
||||||
|
[[ "$effective_total" == "0" ]] ||
|
||||||
|
(( bytes > effective_total )); then
|
||||||
|
size=$(formatSize "$bytes")
|
||||||
|
|
||||||
|
if [[ "$output" == "log" ]]; then
|
||||||
|
if [[ "$log_mode" == "percent" ]]; then
|
||||||
|
printCurrentSize "$bytes"
|
||||||
|
next_bytes=$(((bytes / step_bytes + 1) * step_bytes))
|
||||||
|
log_mode="size"
|
||||||
|
else
|
||||||
|
printSizeProgress "$bytes"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Truncate to one decimal so progress is never reported early.
|
||||||
|
progress=$((bytes * 1000 / effective_total))
|
||||||
|
(( progress > 1000 )) && progress=1000
|
||||||
|
|
||||||
|
percent=$((progress / 10))
|
||||||
|
|
||||||
|
printf -v size '%d.%d%%' \
|
||||||
|
"$((progress / 10))" \
|
||||||
|
"$((progress % 10))"
|
||||||
|
|
||||||
|
if [[ "$output" == "log" ]]; then
|
||||||
|
if [[ "$log_mode" == "size" ]]; then
|
||||||
|
printSizeProgress "$bytes"
|
||||||
|
else
|
||||||
|
printPercentProgress "$percent"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Do not update the web viewer until at least 0.1% is reached.
|
||||||
|
(( progress == 0 )) && write_html="N"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$write_html" == "Y" ]]; then
|
||||||
|
writeInfo "${body//(\[P\])/($size)}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ "$final_pass" == "Y" ]] && break
|
||||||
|
|
||||||
|
sleep 1 &
|
||||||
|
wait $! || :
|
||||||
|
done
|
||||||
+327
-187
@@ -1,215 +1,355 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -Eeuo pipefail
|
set -Eeuo pipefail
|
||||||
|
|
||||||
info () { printf "%b%s%b" "\E[1;34m❯ \E[1;36m" "$1" "\E[0m\n"; }
|
|
||||||
error () { printf "%b%s%b" "\E[1;31m❯ " "ERROR: $1" "\E[0m\n" >&2; }
|
|
||||||
warn () { printf "%b%s%b" "\E[1;31m❯ " "Warning: $1" "\E[0m\n" >&2; }
|
|
||||||
|
|
||||||
trap 'error "Status $? while: $BASH_COMMAND (line $LINENO/$BASH_LINENO)"' ERR
|
trap 'error "Status $? while: $BASH_COMMAND (line $LINENO/$BASH_LINENO)"' ERR
|
||||||
|
enabled "${TRACE:-}" && set -o functrace && trap 'echo "# $BASH_COMMAND" >&2' DEBUG
|
||||||
|
|
||||||
[ ! -f "/run/entry.sh" ] && error "Script must run inside Docker container!" && exit 11
|
[ ! -f "/run/entry.sh" ] && error "Script must be run inside the container!" && exit 11
|
||||||
[ "$(id -u)" -ne "0" ] && error "Script must be executed with root privileges." && exit 12
|
[ "$(id -u)" -ne "0" ] && error "Script must be executed with root privileges." && exit 12
|
||||||
|
|
||||||
echo "❯ Starting $APP for Docker v$(</run/version)..."
|
|
||||||
echo "❯ For support visit $SUPPORT"
|
|
||||||
echo
|
|
||||||
|
|
||||||
# Docker environment variables
|
# Docker environment variables
|
||||||
|
|
||||||
: "${TZ:=""}" # System local timezone
|
: "${TZ:=""}" # System timezone
|
||||||
: "${DEBUG:="N"}" # Disable debugging mode
|
: "${KVM:="Y"}" # KVM acceleration
|
||||||
: "${COUNTRY:=""}" # Country code for mirror
|
: "${DEBUG:="N"}" # Disable debugging mode
|
||||||
: "${CONSOLE:="N"}" # Disable console mode
|
: "${COUNTRY:=""}" # Country code for mirror
|
||||||
: "${ALLOCATE:=""}" # Preallocate diskspace
|
: "${MACHINE:="q35"}" # Machine type selection
|
||||||
: "${ARGUMENTS:=""}" # Extra QEMU parameters
|
: "${ALLOCATE:=""}" # Preallocate diskspace
|
||||||
: "${CPU_CORES:="1"}" # Amount of CPU cores
|
: "${ARGUMENTS:=""}" # Extra QEMU parameters
|
||||||
: "${RAM_SIZE:="1G"}" # Maximum RAM amount
|
: "${CPU_CORES:="2"}" # Amount of CPU cores
|
||||||
: "${DISK_SIZE:="16G"}" # Initial data disk size
|
: "${RAM_SIZE:="2G"}" # Maximum RAM amount
|
||||||
|
: "${RAM_CHECK:="Y"}" # Check available RAM
|
||||||
|
: "${DISK_SIZE:="16G"}" # Initial data disk size
|
||||||
|
: "${STORAGE:="/storage"}" # Storage folder location
|
||||||
|
|
||||||
|
detectEngine() {
|
||||||
|
|
||||||
|
if [ -f "/run/.containerenv" ]; then
|
||||||
|
ENGINE="${container:-}"
|
||||||
|
|
||||||
|
if [[ "${ENGINE,,}" == *"podman"* ]]; then
|
||||||
|
ENGINE="Podman"
|
||||||
|
else
|
||||||
|
[ -z "$ENGINE" ] && ENGINE="Kubernetes"
|
||||||
|
fi
|
||||||
|
elif [ -f "/.dockerenv" ]; then
|
||||||
|
ENGINE="Docker"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
detectRootless() {
|
||||||
|
|
||||||
|
local uid_map
|
||||||
|
|
||||||
|
uid_map=$(awk '{$1=$1; print}' /proc/self/uid_map 2>/dev/null || true)
|
||||||
|
|
||||||
|
if [[ "$uid_map" == "0 0 4294967295" ]]; then
|
||||||
|
ROOTLESS="N"
|
||||||
|
else
|
||||||
|
ROOTLESS="Y"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
checkPrivileged() {
|
||||||
|
|
||||||
|
local cap_bnd
|
||||||
|
local last_cap
|
||||||
|
# Get the capability bounding set
|
||||||
|
cap_bnd=$(grep '^CapBnd:' /proc/$$/status | awk '{print $2}')
|
||||||
|
cap_bnd=$(printf "%d" "0x${cap_bnd}")
|
||||||
|
|
||||||
|
# Get the last capability number
|
||||||
|
last_cap=$(cat /proc/sys/kernel/cap_last_cap)
|
||||||
|
|
||||||
|
# Calculate the maximum capability value
|
||||||
|
local max_cap=$(((1 << (last_cap + 1)) - 1))
|
||||||
|
|
||||||
|
if [ "$cap_bnd" -eq "$max_cap" ]; then
|
||||||
|
PRIVILEGED="Y"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
normalizeCpuCores() {
|
||||||
|
|
||||||
|
CPU_CORES=$(strip "$CPU_CORES")
|
||||||
|
[ -z "$CPU_CORES" ] && CPU_CORES=2
|
||||||
|
[[ "${CPU_CORES,,}" == "max" ]] && CPU_CORES="$CORES"
|
||||||
|
[[ "${CPU_CORES,,}" == "half" ]] && CPU_CORES=$(( CORES / 2 ))
|
||||||
|
[ -z "${CPU_CORES##*[!0-9]*}" ] && error "Invalid amount of CPU_CORES: $CPU_CORES" && exit 15
|
||||||
|
|
||||||
|
[ "$CPU_CORES" -lt "1" ] && CPU_CORES=1
|
||||||
|
if [ "$CPU_CORES" -gt "$CORES" ]; then
|
||||||
|
warn "The amount for CPU_CORES (${CPU_CORES}) exceeds the amount of logical cores available (${CORES}) and will be limited."
|
||||||
|
CPU_CORES="$CORES"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
checkStorage() {
|
||||||
|
|
||||||
|
# Check system
|
||||||
|
|
||||||
|
QEMU_DIR="/run/shm"
|
||||||
|
|
||||||
|
if [ ! -d "/dev/shm" ]; then
|
||||||
|
error "Directory /dev/shm not found!" && exit 14
|
||||||
|
else
|
||||||
|
[ ! -d "$QEMU_DIR" ] && ln -s /dev/shm "$QEMU_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
QEMU_PID="$QEMU_DIR/qemu.pid"
|
||||||
|
|
||||||
|
# Check folder
|
||||||
|
|
||||||
|
if [[ "${STORAGE,,}" != "/storage" ]]; then
|
||||||
|
mkdir -p "$STORAGE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -d "$STORAGE" ]; then
|
||||||
|
error "Storage folder ($STORAGE) not found!" && exit 13
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -w "$STORAGE" ]; then
|
||||||
|
msg="Storage folder ($STORAGE) is not writeable!"
|
||||||
|
msg+=" If SELinux is active, you need to add the \":Z\" flag to the bind mount."
|
||||||
|
error "$msg" && exit 13
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
checkFilesystem() {
|
||||||
|
|
||||||
|
# Check filesystem
|
||||||
|
FS=$(stat -f -c %T "$STORAGE")
|
||||||
|
|
||||||
|
if [[ "${FS,,}" == "ecryptfs" || "${FS,,}" == "tmpfs" ]]; then
|
||||||
|
DISK_IO="threads"
|
||||||
|
DISK_CACHE="writeback"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
finiteMemoryLimit() {
|
||||||
|
|
||||||
|
local limit="$1"
|
||||||
|
local sentinel="4611686018427387904"
|
||||||
|
local i
|
||||||
|
|
||||||
|
[[ "$limit" =~ ^[0-9]+$ ]] || return 1
|
||||||
|
|
||||||
|
(( ${#limit} < ${#sentinel} )) && return 0
|
||||||
|
(( ${#limit} > ${#sentinel} )) && return 1
|
||||||
|
|
||||||
|
for (( i=0; i<${#sentinel}; i++ )); do
|
||||||
|
local left="${limit:i:1}"
|
||||||
|
local right="${sentinel:i:1}"
|
||||||
|
|
||||||
|
(( left < right )) && return 0
|
||||||
|
(( left > right )) && return 1
|
||||||
|
done
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
getMemoryInfo() {
|
||||||
|
|
||||||
|
local limit="" current=""
|
||||||
|
local host_total host_avail
|
||||||
|
|
||||||
|
host_total=$(free -b | awk '/^Mem:/ {print $2; exit}')
|
||||||
|
host_avail=$(free -b | awk '/^Mem:/ {print $7; exit}')
|
||||||
|
|
||||||
|
RAM_TOTAL="$host_total"
|
||||||
|
RAM_AVAIL="$host_avail"
|
||||||
|
|
||||||
|
if [ -r /sys/fs/cgroup/memory.max ] && [ -r /sys/fs/cgroup/memory.current ]; then
|
||||||
|
limit=$(< /sys/fs/cgroup/memory.max)
|
||||||
|
current=$(< /sys/fs/cgroup/memory.current)
|
||||||
|
elif [ -r /sys/fs/cgroup/memory/memory.limit_in_bytes ] && [ -r /sys/fs/cgroup/memory/memory.usage_in_bytes ]; then
|
||||||
|
limit=$(< /sys/fs/cgroup/memory/memory.limit_in_bytes)
|
||||||
|
current=$(< /sys/fs/cgroup/memory/memory.usage_in_bytes)
|
||||||
|
fi
|
||||||
|
|
||||||
|
if finiteMemoryLimit "$limit" && [[ "$current" =~ ^[0-9]+$ ]]; then
|
||||||
|
(( limit < RAM_TOTAL )) && RAM_TOTAL="$limit"
|
||||||
|
|
||||||
|
local available=$(( limit - current ))
|
||||||
|
(( available < 0 )) && available=0
|
||||||
|
(( available < RAM_AVAIL )) && RAM_AVAIL="$available"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
normalizeRamSize() {
|
||||||
|
|
||||||
|
# Read host and container memory limits.
|
||||||
|
getMemoryInfo
|
||||||
|
|
||||||
|
RAM_SPARE=500000000
|
||||||
|
RAM_MINIMUM=136314880
|
||||||
|
|
||||||
|
RAM_SIZE=$(strip "$RAM_SIZE")
|
||||||
|
RAM_SIZE="${RAM_SIZE// /}"
|
||||||
|
[ -z "$RAM_SIZE" ] && RAM_SIZE="2G"
|
||||||
|
|
||||||
|
if [[ "${RAM_SIZE,,}" != "max" && "${RAM_SIZE,,}" != "half" ]]; then
|
||||||
|
|
||||||
|
if [ -z "${RAM_SIZE//[0-9. ]}" ]; then
|
||||||
|
[ "${RAM_SIZE%%.*}" -lt "130" ] && RAM_SIZE="${RAM_SIZE}G" || RAM_SIZE="${RAM_SIZE}M"
|
||||||
|
fi
|
||||||
|
|
||||||
|
RAM_SIZE=$(echo "${RAM_SIZE^^}" | sed 's/MB/M/g;s/GB/G/g;s/TB/T/g')
|
||||||
|
numfmt --from=iec "$RAM_SIZE" &>/dev/null || { error "Invalid RAM_SIZE: $RAM_SIZE" && exit 16; }
|
||||||
|
wanted=$(numfmt --from=iec "$RAM_SIZE")
|
||||||
|
[ "$wanted" -lt "$RAM_MINIMUM" ] && error "RAM_SIZE is too low: $RAM_SIZE" && exit 16
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
checkKvm() {
|
||||||
|
|
||||||
|
# Check KVM support
|
||||||
|
|
||||||
|
if [[ "${PLATFORM,,}" == "x64" ]]; then
|
||||||
|
TARGET="amd64"
|
||||||
|
else
|
||||||
|
TARGET="arm64"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if disabled "$KVM"; then
|
||||||
|
warn "KVM acceleration is disabled, this will cause the machine to run about 10 times slower!"
|
||||||
|
else
|
||||||
|
if [[ "${ARCH,,}" != "$TARGET" ]]; then
|
||||||
|
KVM="N"
|
||||||
|
warn "your CPU architecture is ${ARCH^^} and cannot provide KVM acceleration for ${PLATFORM^^} instructions, so the machine will run about 10 times slower."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! disabled "$KVM"; then
|
||||||
|
|
||||||
|
KVM_ERR=""
|
||||||
|
|
||||||
|
if [ ! -e /dev/kvm ]; then
|
||||||
|
KVM_ERR="(/dev/kvm is missing)"
|
||||||
|
else
|
||||||
|
if ! sh -c 'echo -n > /dev/kvm' &> /dev/null; then
|
||||||
|
KVM_ERR="(/dev/kvm is unwriteable)"
|
||||||
|
else
|
||||||
|
if [[ "${PLATFORM,,}" == "x64" ]]; then
|
||||||
|
flags=$(sed -ne '/^flags/s/^.*: //p' /proc/cpuinfo)
|
||||||
|
if ! grep -qw "vmx\|svm" <<< "$flags"; then
|
||||||
|
KVM_ERR="(not enabled in BIOS)"
|
||||||
|
fi
|
||||||
|
if ! grep -qw "sse4_2" <<< "$flags"; then
|
||||||
|
error "Your CPU does not have the SSE4 instruction set that Virtual DSM requires!"
|
||||||
|
enabled "$DEBUG" || exit 88
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$KVM_ERR" ]; then
|
||||||
|
KVM="N"
|
||||||
|
if [[ "$OSTYPE" =~ ^darwin ]]; then
|
||||||
|
warn "you are using macOS which has no KVM support, so the machine will run about 10 times slower."
|
||||||
|
else
|
||||||
|
kernel=$(uname -a)
|
||||||
|
case "${kernel,,}" in
|
||||||
|
*"microsoft"* )
|
||||||
|
error "Please bind '/dev/kvm' as a volume in the optional container settings when using Docker Desktop." ;;
|
||||||
|
*"synology"* )
|
||||||
|
error "Please make sure that Synology VMM (Virtual Machine Manager) is installed and that '/dev/kvm' is binded to this container." ;;
|
||||||
|
*)
|
||||||
|
error "KVM acceleration is not available $KVM_ERR, this will cause the machine to run about 10 times slower."
|
||||||
|
error "See the FAQ for possible causes, or disable acceleration by adding the \"KVM=N\" variable (not recommended)." ;;
|
||||||
|
esac
|
||||||
|
enabled "$DEBUG" || exit 88
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Sanitize variables
|
||||||
|
TZ=$(strip "$TZ")
|
||||||
|
STORAGE=$(strip "$STORAGE")
|
||||||
|
COUNTRY=$(strip "$COUNTRY")
|
||||||
|
DISK_SIZE=$(strip "$DISK_SIZE")
|
||||||
|
|
||||||
# Helper variables
|
# Helper variables
|
||||||
|
ROOTLESS="N"
|
||||||
|
PRIVILEGED="N"
|
||||||
|
ENGINE="Docker"
|
||||||
|
PROCESS="${APP,,}"
|
||||||
|
PROCESS="${PROCESS// /-}"
|
||||||
|
|
||||||
STORAGE="/storage"
|
detectEngine
|
||||||
INFO="/dev/shm/msg.html"
|
detectRootless
|
||||||
|
|
||||||
|
echo "❯ Starting $APP for $ENGINE v$(</etc/version)..."
|
||||||
|
echo "❯ For support visit $SUPPORT"
|
||||||
|
|
||||||
|
checkPrivileged
|
||||||
|
|
||||||
|
INFO="/run/shm/msg.html"
|
||||||
PAGE="/run/shm/index.html"
|
PAGE="/run/shm/index.html"
|
||||||
TEMPLATE="/var/www/index.html"
|
TEMPLATE="/var/www/index.html"
|
||||||
FOOTER1="$APP for Docker v$(</run/version)"
|
FOOTER1="$APP for $ENGINE v$(</etc/version)"
|
||||||
FOOTER2="<a href='$SUPPORT'>$SUPPORT</a>"
|
FOOTER2="<a href='$SUPPORT'>$SUPPORT</a>"
|
||||||
|
|
||||||
KERNEL=$(uname -r | cut -b 1)
|
SOCKETS=1
|
||||||
MINOR=$(uname -r | cut -d '.' -f2)
|
CPU=$(cpu)
|
||||||
|
SYS=$(uname -r)
|
||||||
ARCH=$(dpkg --print-architecture)
|
ARCH=$(dpkg --print-architecture)
|
||||||
VERS=$(qemu-system-x86_64 --version | head -n 1 | cut -d '(' -f 1)
|
IFS=. read -r KERNEL MINOR _ <<< "$SYS"
|
||||||
|
CORES=$(grep -c '^processor' /proc/cpuinfo)
|
||||||
|
|
||||||
# Check folder
|
if grep -qi "socket(s)" <<< "$(lscpu)"; then
|
||||||
|
SOCKETS=$(lscpu | grep -m 1 -i 'socket(s)' | awk '{print $2}')
|
||||||
if [ ! -d "$STORAGE" ]; then
|
[ -z "${SOCKETS##*[!0-9]*}" ] && SOCKETS=1
|
||||||
error "Storage folder ($STORAGE) not found!" && exit 13
|
[ "$SOCKETS" -lt "1" ] && SOCKETS=1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ ! -d "/run/shm" ]; then
|
normalizeCpuCores
|
||||||
if [ -d "/dev/shm" ]; then
|
checkStorage
|
||||||
ln -s /dev/shm /run/shm
|
checkFilesystem
|
||||||
else
|
normalizeRamSize
|
||||||
error "Folder /dev/shm not found!" && exit 14
|
|
||||||
fi
|
# Print system info
|
||||||
fi
|
SYS="${SYS/-generic/}"
|
||||||
|
FS="${FS/UNKNOWN //}"
|
||||||
|
FS="${FS/ext2\/ext3/ext4}"
|
||||||
|
FS=$(echo "$FS" | sed 's/[)(]//g')
|
||||||
|
SPACE=$(df --output=avail -B 1 "$STORAGE" | tail -n 1)
|
||||||
|
SPACE_GB=$(formatBytes "$SPACE" "down")
|
||||||
|
AVAIL_MEM=$(formatBytes "$RAM_AVAIL" "down")
|
||||||
|
TOTAL_MEM=$(formatBytes "$RAM_TOTAL" "up")
|
||||||
|
|
||||||
|
echo "❯ CPU: ${CPU} | RAM: ${AVAIL_MEM/ GB/}/$TOTAL_MEM | DISK: $SPACE_GB (${FS}) | KERNEL: ${SYS}"
|
||||||
|
echo
|
||||||
|
|
||||||
|
checkKvm
|
||||||
|
|
||||||
# Cleanup files
|
# Cleanup files
|
||||||
rm -f /run/shm/qemu.*
|
rm -f "$QEMU_DIR"/dsm.url
|
||||||
rm -f /run/shm/dsm.url
|
rm -f "$QEMU_DIR"/{qemu.*,*.{pid,sock,pipe}}
|
||||||
|
|
||||||
# Cleanup dirs
|
# Cleanup dirs
|
||||||
rm -rf /tmp/dsm
|
rm -rf /tmp/dsm
|
||||||
rm -rf "$STORAGE/tmp"
|
rm -rf "$STORAGE/tmp"
|
||||||
|
|
||||||
# Helper functions
|
|
||||||
|
|
||||||
isAlive() {
|
|
||||||
local pid=$1
|
|
||||||
|
|
||||||
if kill -0 "$pid" 2>/dev/null; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
pKill() {
|
|
||||||
local pid=$1
|
|
||||||
|
|
||||||
{ kill -15 "$pid" || true; } 2>/dev/null
|
|
||||||
|
|
||||||
while isAlive "$pid"; do
|
|
||||||
sleep 0.2
|
|
||||||
done
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
fWait() {
|
|
||||||
local name=$1
|
|
||||||
|
|
||||||
while pgrep -f -l "$name" >/dev/null; do
|
|
||||||
sleep 0.2
|
|
||||||
done
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
fKill() {
|
|
||||||
local name=$1
|
|
||||||
|
|
||||||
{ pkill -f "$name" || true; } 2>/dev/null
|
|
||||||
fWait "$name"
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
escape () {
|
|
||||||
local s
|
|
||||||
s=${1//&/\&}
|
|
||||||
s=${s//</\<}
|
|
||||||
s=${s//>/\>}
|
|
||||||
s=${s//'"'/\"}
|
|
||||||
printf -- %s "$s"
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
html()
|
|
||||||
{
|
|
||||||
local title
|
|
||||||
local body
|
|
||||||
local script
|
|
||||||
local footer
|
|
||||||
|
|
||||||
title=$(escape "$APP")
|
|
||||||
title="<title>$title</title>"
|
|
||||||
footer=$(escape "$FOOTER1")
|
|
||||||
|
|
||||||
body=$(escape "$1")
|
|
||||||
if [[ "$body" == *"..." ]]; then
|
|
||||||
body="<p class=\"loading\">${body/.../}</p>"
|
|
||||||
fi
|
|
||||||
|
|
||||||
[ -n "${2:-}" ] && script="$2" || script=""
|
|
||||||
|
|
||||||
local HTML
|
|
||||||
HTML=$(<"$TEMPLATE")
|
|
||||||
HTML="${HTML/\[1\]/$title}"
|
|
||||||
HTML="${HTML/\[2\]/$script}"
|
|
||||||
HTML="${HTML/\[3\]/$body}"
|
|
||||||
HTML="${HTML/\[4\]/$footer}"
|
|
||||||
HTML="${HTML/\[5\]/$FOOTER2}"
|
|
||||||
|
|
||||||
echo "$HTML" > "$PAGE"
|
|
||||||
echo "$body" > "$INFO"
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
getCountry() {
|
|
||||||
local url=$1
|
|
||||||
local query=$2
|
|
||||||
local rc json result
|
|
||||||
|
|
||||||
{ json=$(curl -m 5 -H "Accept: application/json" -sfk "$url"); rc=$?; } || :
|
|
||||||
(( rc != 0 )) && return 0
|
|
||||||
|
|
||||||
{ result=$(echo "$json" | jq -r "$query" 2> /dev/null); rc=$?; } || :
|
|
||||||
(( rc != 0 )) && return 0
|
|
||||||
|
|
||||||
[[ ${#result} -ne 2 ]] && return 0
|
|
||||||
[[ "${result^^}" == "XX" ]] && return 0
|
|
||||||
|
|
||||||
COUNTRY="${result^^}"
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
setCountry() {
|
|
||||||
|
|
||||||
[[ "${TZ,,}" == "asia/harbin" ]] && COUNTRY="CN"
|
|
||||||
[[ "${TZ,,}" == "asia/beijing" ]] && COUNTRY="CN"
|
|
||||||
[[ "${TZ,,}" == "asia/urumqi" ]] && COUNTRY="CN"
|
|
||||||
[[ "${TZ,,}" == "asia/kashgar" ]] && COUNTRY="CN"
|
|
||||||
[[ "${TZ,,}" == "asia/shanghai" ]] && COUNTRY="CN"
|
|
||||||
[[ "${TZ,,}" == "asia/chongqing" ]] && COUNTRY="CN"
|
|
||||||
|
|
||||||
[ -z "$COUNTRY" ] && getCountry "https://api.ipapi.is" ".location.country_code"
|
|
||||||
[ -z "$COUNTRY" ] && getCountry "https://ifconfig.co/json" ".country_iso"
|
|
||||||
[ -z "$COUNTRY" ] && getCountry "https://api.ip2location.io" ".country_code"
|
|
||||||
[ -z "$COUNTRY" ] && getCountry "https://ipinfo.io/json" ".country"
|
|
||||||
[ -z "$COUNTRY" ] && getCountry "https://api.myip.com" ".cc"
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
addPackage() {
|
|
||||||
local pkg=$1
|
|
||||||
local desc=$2
|
|
||||||
|
|
||||||
if apt-mark showinstall | grep -qx "$pkg"; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
MSG="Installing $desc..."
|
|
||||||
info "$MSG" && html "$MSG"
|
|
||||||
|
|
||||||
[ -z "$COUNTRY" ] && setCountry
|
|
||||||
|
|
||||||
if [[ "${COUNTRY^^}" == "CN" ]]; then
|
|
||||||
sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list.d/debian.sources
|
|
||||||
fi
|
|
||||||
|
|
||||||
DEBIAN_FRONTEND=noninteractive apt-get -qq update
|
|
||||||
DEBIAN_FRONTEND=noninteractive apt-get -qq --no-install-recommends -y install "$pkg" > /dev/null
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
# Start webserver
|
|
||||||
cp -r /var/www/* /run/shm
|
|
||||||
html "Starting $APP for Docker..."
|
|
||||||
nginx -e stderr
|
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
+119
-37
@@ -5,56 +5,138 @@ set -Eeuo pipefail
|
|||||||
|
|
||||||
: "${HOST_MAC:=""}"
|
: "${HOST_MAC:=""}"
|
||||||
: "${HOST_DEBUG:=""}"
|
: "${HOST_DEBUG:=""}"
|
||||||
: "${HOST_SERIAL:=""}"
|
|
||||||
: "${HOST_MODEL:=""}"
|
: "${HOST_MODEL:=""}"
|
||||||
|
: "${HOST_SERIAL:=""}"
|
||||||
: "${GUEST_SERIAL:=""}"
|
: "${GUEST_SERIAL:=""}"
|
||||||
|
|
||||||
HOST_ARGS=()
|
# Sanitize variables
|
||||||
HOST_ARGS+=("-cpu=$CPU_CORES")
|
HOST_MAC=$(strip "$HOST_MAC")
|
||||||
HOST_ARGS+=("-cpu_arch=$HOST_CPU")
|
HOST_MODEL=$(strip "$HOST_MODEL")
|
||||||
|
HOST_SERIAL=$(strip "$HOST_SERIAL")
|
||||||
|
GUEST_SERIAL=$(strip "$GUEST_SERIAL")
|
||||||
|
|
||||||
[ -n "$HOST_MAC" ] && HOST_ARGS+=("-mac=$HOST_MAC")
|
HOST_PID="$QEMU_DIR/host.pid"
|
||||||
[ -n "$HOST_MODEL" ] && HOST_ARGS+=("-model=$HOST_MODEL")
|
HOST_API_SOCKET="$QEMU_DIR/qemu-host-api.sock"
|
||||||
[ -n "$HOST_SERIAL" ] && HOST_ARGS+=("-hostsn=$HOST_SERIAL")
|
HOST_AGENT_SOCKET="$QEMU_DIR/qemu-host-agent.sock"
|
||||||
[ -n "$GUEST_SERIAL" ] && HOST_ARGS+=("-guestsn=$GUEST_SERIAL")
|
|
||||||
|
|
||||||
if [[ "$HOST_DEBUG" == [Yy1]* ]]; then
|
validateHostMac() {
|
||||||
set -x
|
local m
|
||||||
./host.bin "${HOST_ARGS[@]}" &
|
|
||||||
{ set +x; } 2>/dev/null
|
|
||||||
echo
|
|
||||||
else
|
|
||||||
./host.bin "${HOST_ARGS[@]}" >/dev/null &
|
|
||||||
fi
|
|
||||||
|
|
||||||
cnt=0
|
if [ -z "$HOST_MAC" ]; then
|
||||||
sleep 0.2
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
while ! nc -z -w2 127.0.0.1 2210 > /dev/null 2>&1; do
|
HOST_MAC="${HOST_MAC//-/:}"
|
||||||
sleep 0.1
|
|
||||||
cnt=$((cnt + 1))
|
|
||||||
(( cnt > 50 )) && error "Failed to connect to qemu-host.." && exit 58
|
|
||||||
done
|
|
||||||
|
|
||||||
cnt=0
|
if [[ ${#HOST_MAC} == 12 ]]; then
|
||||||
|
m="$HOST_MAC"
|
||||||
|
HOST_MAC="${m:0:2}:${m:2:2}:${m:4:2}:${m:6:2}:${m:8:2}:${m:10:2}"
|
||||||
|
fi
|
||||||
|
|
||||||
while ! nc -z -w2 127.0.0.1 12345 > /dev/null 2>&1; do
|
if [[ ${#HOST_MAC} != 17 ]]; then
|
||||||
sleep 0.1
|
error "Invalid HOST_MAC address: '$HOST_MAC', should be 12 or 17 digits long!" && exit 28
|
||||||
cnt=$((cnt + 1))
|
fi
|
||||||
(( cnt > 50 )) && error "Failed to connect to qemu-host.." && exit 59
|
|
||||||
done
|
|
||||||
|
|
||||||
# Configure serial ports
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
if [[ "$CONSOLE" != [Yy]* ]]; then
|
buildHostArguments() {
|
||||||
SERIAL_OPTS="-serial pty"
|
|
||||||
else
|
|
||||||
SERIAL_OPTS="-serial mon:stdio"
|
|
||||||
fi
|
|
||||||
|
|
||||||
SERIAL_OPTS="$SERIAL_OPTS \
|
HOST_ARGS=()
|
||||||
|
HOST_ARGS+=("-cpu=$CPU_CORES")
|
||||||
|
HOST_ARGS+=("-cpu_arch=$HOST_CPU")
|
||||||
|
HOST_ARGS+=("-api=$HOST_API_SOCKET")
|
||||||
|
HOST_ARGS+=("-addr=$HOST_AGENT_SOCKET")
|
||||||
|
|
||||||
|
[ -n "$HOST_MAC" ] && HOST_ARGS+=("-mac=$HOST_MAC")
|
||||||
|
[ -n "$HOST_MODEL" ] && HOST_ARGS+=("-model=$HOST_MODEL")
|
||||||
|
[ -n "$HOST_SERIAL" ] && HOST_ARGS+=("-hostsn=$HOST_SERIAL")
|
||||||
|
[ -n "$GUEST_SERIAL" ] && HOST_ARGS+=("-guestsn=$GUEST_SERIAL")
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
startHostBinary() {
|
||||||
|
|
||||||
|
local pid
|
||||||
|
|
||||||
|
rm -f -- "$HOST_PID" "$HOST_API_SOCKET" "$HOST_AGENT_SOCKET" || return 1
|
||||||
|
|
||||||
|
if enabled "$HOST_DEBUG"; then
|
||||||
|
set -x
|
||||||
|
./host.bin "${HOST_ARGS[@]}" &
|
||||||
|
{ set +x; } 2>/dev/null
|
||||||
|
pid=$!
|
||||||
|
echo
|
||||||
|
else
|
||||||
|
./host.bin "${HOST_ARGS[@]}" >/dev/null &
|
||||||
|
pid=$!
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s\n' "$pid" > "$HOST_PID"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
waitForSocket() {
|
||||||
|
|
||||||
|
local socket="$1"
|
||||||
|
local exit_code="$2"
|
||||||
|
local timeout=5 pid
|
||||||
|
local deadline=$((SECONDS + timeout))
|
||||||
|
|
||||||
|
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() {
|
||||||
|
|
||||||
|
if enabled "${SHUTDOWN:-Y}" && interactive; then
|
||||||
|
|
||||||
|
CONSOLE_SOCKET="$QEMU_DIR/console.sock"
|
||||||
|
MONITOR_SOCKET="$QEMU_DIR/monitor.sock"
|
||||||
|
|
||||||
|
SERIAL_OPTS="-chardev socket,id=console0,path=$CONSOLE_SOCKET,reconnect-ms=1000 \
|
||||||
|
-serial chardev:console0 \
|
||||||
|
-chardev socket,id=monitor0,path=$MONITOR_SOCKET,server=on,wait=off \
|
||||||
|
-mon chardev=monitor0,mode=readline"
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
|
SERIAL_OPTS="-serial mon:stdio"
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
SERIAL_OPTS+=" \
|
||||||
-device virtio-serial-pci,id=virtio-serial0,bus=pcie.0,addr=0x3 \
|
-device virtio-serial-pci,id=virtio-serial0,bus=pcie.0,addr=0x3 \
|
||||||
-chardev socket,id=charchannel0,host=127.0.0.1,port=12345,reconnect=10 \
|
-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"
|
-device virtserialport,bus=virtio-serial0.0,nr=1,chardev=charchannel0,id=channel0,name=vchannel"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
validateHostMac
|
||||||
|
|
||||||
|
buildHostArguments
|
||||||
|
startHostBinary
|
||||||
|
|
||||||
|
waitForSocket "$HOST_API_SOCKET" 58
|
||||||
|
waitForSocket "$HOST_AGENT_SOCKET" 59
|
||||||
|
|
||||||
|
configureSerialPorts
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
+152
@@ -0,0 +1,152 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
: "${WEB_PORT:="5000"}" # Webserver port
|
||||||
|
: "${WSD_PORT:="8004"}" # Websockets port
|
||||||
|
|
||||||
|
# Sanitize port variables
|
||||||
|
WEB_PORT=$(strip "$WEB_PORT")
|
||||||
|
WSD_PORT=$(strip "$WSD_PORT")
|
||||||
|
|
||||||
|
WEB_PID="/run/nginx.pid"
|
||||||
|
WSD_LOG="/var/log/websocketd.log"
|
||||||
|
WSD_PID="$QEMU_DIR/websocketd.pid"
|
||||||
|
|
||||||
|
prepareWebFiles() {
|
||||||
|
|
||||||
|
cp -r /var/www/* "$QEMU_DIR" || return 1
|
||||||
|
rm -f -- "$WSD_PID" "$WEB_PID" "$WSD_LOG" || return 1
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
configureWebPorts() {
|
||||||
|
|
||||||
|
if ! sed -i \
|
||||||
|
-e "s|listen 5000 default_server;|listen $WEB_PORT default_server;|g" \
|
||||||
|
-e "s|proxy_pass http://127.0.0.1:8004/;|proxy_pass http://127.0.0.1:$WSD_PORT/;|g" \
|
||||||
|
/etc/nginx/sites-enabled/web.conf; then
|
||||||
|
error "Failed to configure webserver ports!"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
configureIpv6Listen() {
|
||||||
|
|
||||||
|
if [ -f /proc/net/if_inet6 ] && [[ "$(cat /proc/sys/net/ipv6/conf/all/disable_ipv6 2>/dev/null)" != "1" ]]; then
|
||||||
|
|
||||||
|
if ! sed -i \
|
||||||
|
"s/listen $WEB_PORT default_server;/listen [::]:$WEB_PORT default_server ipv6only=off;/g" \
|
||||||
|
/etc/nginx/sites-enabled/web.conf; then
|
||||||
|
error "Failed to configure IPv6 webserver listener!"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
configureWebServer() {
|
||||||
|
|
||||||
|
mkdir -p /etc/nginx/sites-enabled
|
||||||
|
cp /etc/nginx/default.conf /etc/nginx/sites-enabled/web.conf
|
||||||
|
|
||||||
|
configureWebPorts || return 1
|
||||||
|
configureIpv6Listen || return 1
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
stopWebServer() {
|
||||||
|
|
||||||
|
local pid
|
||||||
|
|
||||||
|
if readPidFile pid "$WEB_PID"; then
|
||||||
|
pKill "$pid" 2
|
||||||
|
|
||||||
|
if isAlive "$pid"; then
|
||||||
|
kill -9 -- "$pid" 2>/dev/null || :
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -f -- "$WEB_PID"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
startWebServer() {
|
||||||
|
|
||||||
|
# Start webserver
|
||||||
|
nginx -e stderr || return 1
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
stopWebsocketServer() {
|
||||||
|
|
||||||
|
local pid
|
||||||
|
|
||||||
|
if readPidFile pid "$WSD_PID"; then
|
||||||
|
pKill "$pid" 2
|
||||||
|
|
||||||
|
if isAlive "$pid"; then
|
||||||
|
kill -9 -- "$pid" 2>/dev/null || :
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -f -- "$WSD_PID"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
startWebsocketServer() {
|
||||||
|
|
||||||
|
# Start websocket server
|
||||||
|
websocketd \
|
||||||
|
--address 127.0.0.1 \
|
||||||
|
--port="$WSD_PORT" \
|
||||||
|
/run/socket.sh \
|
||||||
|
>"$WSD_LOG" 2>&1 &
|
||||||
|
|
||||||
|
local pid=$!
|
||||||
|
|
||||||
|
if ! echo "$pid" > "$WSD_PID"; then
|
||||||
|
kill "$pid" 2>/dev/null || :
|
||||||
|
rm -f -- "$WSD_PID"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local i
|
||||||
|
for (( i = 1; i <= 5; i++ )); do
|
||||||
|
|
||||||
|
if ! isAlive "$pid"; then
|
||||||
|
rm -f -- "$WSD_PID"
|
||||||
|
[ -s "$WSD_LOG" ] && cat "$WSD_LOG" >&2
|
||||||
|
error "Failed to start websocket server!"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep 0.1
|
||||||
|
|
||||||
|
done
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
prepareWebFiles
|
||||||
|
|
||||||
|
html "Starting $APP for $ENGINE..."
|
||||||
|
|
||||||
|
disabled "${WEB:-}" && return 0
|
||||||
|
|
||||||
|
configureWebServer
|
||||||
|
|
||||||
|
if startWebServer && startWebsocketServer; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
stopWebsocketServer || :
|
||||||
|
stopWebServer || :
|
||||||
|
|
||||||
|
return 1
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
lastmsg=""
|
||||||
|
path="/run/shm/msg.html"
|
||||||
|
dir=$(dirname -- "$path")
|
||||||
|
name=$(basename -- "$path")
|
||||||
|
|
||||||
|
refresh() {
|
||||||
|
|
||||||
|
[ ! -f "$path" ] && return 0
|
||||||
|
[ ! -s "$path" ] && return 0
|
||||||
|
|
||||||
|
msg=$(< "$path") || return 0
|
||||||
|
msg="${msg%$'\n'}"
|
||||||
|
|
||||||
|
[ -z "$msg" ] && return 0
|
||||||
|
[[ "$msg" == "$lastmsg" ]] && return 0
|
||||||
|
|
||||||
|
lastmsg="$msg"
|
||||||
|
echo "s: $msg"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh
|
||||||
|
|
||||||
|
inotifywait \
|
||||||
|
-m -q \
|
||||||
|
-e close_write,moved_to,delete \
|
||||||
|
--format '%e %f' \
|
||||||
|
"$dir" |
|
||||||
|
while read -r event file; do
|
||||||
|
|
||||||
|
[[ "$file" == "$name" ]] || continue
|
||||||
|
|
||||||
|
case "${event,,}" in
|
||||||
|
"delete"* )
|
||||||
|
echo "c: vnc" ;;
|
||||||
|
"close_write"* | "moved_to"* )
|
||||||
|
refresh ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
done
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
# You can override this hook to execute a script before startup!
|
||||||
|
|
||||||
|
return 0
|
||||||
+549
@@ -0,0 +1,549 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
# Helper functions
|
||||||
|
|
||||||
|
info () { printf "%b%s%b" "\E[1;34m❯ \E[1;36m" "${1:-}" "\E[0m\n"; }
|
||||||
|
error () { printf "%b%s%b" "\E[1;31m❯ " "ERROR: ${1:-}" "\E[0m\n" >&2; }
|
||||||
|
warn () { printf "%b%s%b" "\E[1;31m❯ " "Warning: ${1:-}" "\E[0m\n" >&2; }
|
||||||
|
|
||||||
|
readPidFile() {
|
||||||
|
|
||||||
|
local -n _pid="$1"
|
||||||
|
_pid=""
|
||||||
|
|
||||||
|
if ! _pid=$(cat -- "$2" 2>/dev/null); then
|
||||||
|
_pid=""
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! "$_pid" =~ ^[1-9][0-9]*$ ]]; then
|
||||||
|
_pid=""
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
hasFlag() {
|
||||||
|
|
||||||
|
# Match a whitespace-delimited token in /proc/cpuinfo
|
||||||
|
grep -m1 '^flags[[:space:]]*:' /proc/cpuinfo | grep -Fqw -- "$1"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
hasFeature() {
|
||||||
|
|
||||||
|
# Match a whitespace-delimited token in /proc/cpuinfo
|
||||||
|
grep -m1 '^Features[[:space:]]*:' /proc/cpuinfo | grep -Fqw -- "$1"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
isAmdCpu() {
|
||||||
|
|
||||||
|
local vendor
|
||||||
|
vendor=$(awk -F ': *' '/^vendor_id/{print $2; exit}' /proc/cpuinfo)
|
||||||
|
|
||||||
|
[[ "$vendor" == "AuthenticAMD" ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
interactive() {
|
||||||
|
|
||||||
|
[ -t 0 ] && : 2>/dev/null </dev/tty >/dev/tty
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
strip() {
|
||||||
|
|
||||||
|
local value="${1:-}"
|
||||||
|
|
||||||
|
# Remove surrounding whitespace
|
||||||
|
value="${value#"${value%%[![:space:]]*}"}"
|
||||||
|
value="${value%"${value##*[![:space:]]}"}"
|
||||||
|
|
||||||
|
# Remove leading/trailing single/double quotes
|
||||||
|
value="${value%\"}"
|
||||||
|
value="${value#\"}"
|
||||||
|
value="${value%\'}"
|
||||||
|
value="${value#\'}"
|
||||||
|
|
||||||
|
# Remove surrounding whitespace again
|
||||||
|
value="${value#"${value%%[![:space:]]*}"}"
|
||||||
|
value="${value%"${value##*[![:space:]]}"}"
|
||||||
|
|
||||||
|
printf '%s' "$value"
|
||||||
|
}
|
||||||
|
|
||||||
|
enabled() {
|
||||||
|
|
||||||
|
local value
|
||||||
|
value=$(strip "${1:-}")
|
||||||
|
|
||||||
|
case "${value,,}" in
|
||||||
|
y|yes|true|1|on|enable|enabled) return 0 ;;
|
||||||
|
*) return 1 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
disabled() {
|
||||||
|
|
||||||
|
local value
|
||||||
|
value=$(strip "${1:-}")
|
||||||
|
|
||||||
|
case "${value,,}" in
|
||||||
|
n|no|none|false|0|off|disable|disabled) return 0 ;;
|
||||||
|
*) return 1 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
formatBytes() {
|
||||||
|
|
||||||
|
local result
|
||||||
|
|
||||||
|
if ! result=$(numfmt --to=iec --suffix=B "$1" | sed -r 's/([A-Z])/ \1/' | sed 's/ B/ bytes/g;'); then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local unit="${result//[0-9. ]}"
|
||||||
|
result="${result//[a-zA-Z ]/}"
|
||||||
|
|
||||||
|
if [[ "${2:-}" == "up" ]]; then
|
||||||
|
if [[ "$result" == *"."* ]]; then
|
||||||
|
result="${result%%.*}"
|
||||||
|
result=$((result+1))
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ "${2:-}" == "down" ]]; then
|
||||||
|
result="${result%%.*}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$result $unit"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
isAlive() {
|
||||||
|
|
||||||
|
local pid="$1"
|
||||||
|
[ -z "$pid" ] && return 1
|
||||||
|
|
||||||
|
if kill -0 "$pid" 2>/dev/null; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
waitPid() {
|
||||||
|
|
||||||
|
local pid="$1"
|
||||||
|
local timeout="${2:-10}"
|
||||||
|
local deadline=$((SECONDS + timeout))
|
||||||
|
|
||||||
|
while [ -n "$pid" ] && isAlive "$pid"; do
|
||||||
|
(( SECONDS >= deadline )) && return 1
|
||||||
|
sleep 0.2
|
||||||
|
done
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
waitPidFile() {
|
||||||
|
|
||||||
|
local pid
|
||||||
|
local file="$1"
|
||||||
|
local timeout="${2:-10}"
|
||||||
|
local deadline=$((SECONDS + timeout))
|
||||||
|
|
||||||
|
readPidFile pid "$file" || return 0
|
||||||
|
|
||||||
|
while [ -s "$file" ] && isAlive "$pid"; do
|
||||||
|
(( SECONDS >= deadline )) && return 1
|
||||||
|
sleep 0.2
|
||||||
|
done
|
||||||
|
|
||||||
|
rm -f -- "$file"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pKill() {
|
||||||
|
|
||||||
|
local pid="$1"
|
||||||
|
local timeout="${2:-10}"
|
||||||
|
|
||||||
|
{ kill -15 -- "$pid" || :; } 2>/dev/null
|
||||||
|
|
||||||
|
if ! waitPid "$pid" "$timeout"; then
|
||||||
|
warn "Timed out while waiting for PID $pid"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fWait() {
|
||||||
|
|
||||||
|
local name="$1"
|
||||||
|
local timeout="${2:-10}"
|
||||||
|
local deadline=$((SECONDS + timeout))
|
||||||
|
|
||||||
|
[ -z "$name" ] && return 0
|
||||||
|
|
||||||
|
while pgrep -f -l "$name" >/dev/null; do
|
||||||
|
if (( SECONDS >= deadline )); then
|
||||||
|
warn "Timed out while waiting for process: $name"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep 0.2
|
||||||
|
done
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fKill() {
|
||||||
|
|
||||||
|
local name="$1"
|
||||||
|
local timeout="${2:-10}"
|
||||||
|
|
||||||
|
[ -z "$name" ] && return 0
|
||||||
|
|
||||||
|
{ pkill -f "$name" || :; } 2>/dev/null
|
||||||
|
fWait "$name" "$timeout"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
sKill() {
|
||||||
|
|
||||||
|
local pid
|
||||||
|
local file="$1"
|
||||||
|
|
||||||
|
readPidFile pid "$file" || return 0
|
||||||
|
|
||||||
|
if isAlive "$pid"; then
|
||||||
|
{ kill -15 -- "$pid" || :; } 2>/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
mKill() {
|
||||||
|
|
||||||
|
local timeout=10
|
||||||
|
local files=("$@")
|
||||||
|
|
||||||
|
for file in "${files[@]}"; do
|
||||||
|
sKill "$file"
|
||||||
|
done
|
||||||
|
|
||||||
|
for file in "${files[@]}"; do
|
||||||
|
if ! waitPidFile "$file" "$timeout"; then
|
||||||
|
warn "Timed out while waiting for PID file: $file"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
setOwner() {
|
||||||
|
|
||||||
|
local file="$1"
|
||||||
|
local dir uid gid
|
||||||
|
|
||||||
|
[ ! -f "$file" ] && return 1
|
||||||
|
|
||||||
|
dir=$(dirname -- "$file")
|
||||||
|
uid=$(stat -c '%u' "$dir") || return 1
|
||||||
|
gid=$(stat -c '%g' "$dir") || return 1
|
||||||
|
|
||||||
|
chown "$uid:$gid" "$file" || return 1
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
makeDir() {
|
||||||
|
|
||||||
|
local path="$1"
|
||||||
|
local dir uid gid
|
||||||
|
|
||||||
|
[ -d "$path" ] && return 0
|
||||||
|
mkdir -p "$path" || return 1
|
||||||
|
|
||||||
|
dir=$(dirname -- "$path")
|
||||||
|
|
||||||
|
if ! uid=$(stat -c '%u' "$dir") || ! gid=$(stat -c '%g' "$dir"); then
|
||||||
|
warn "failed to determine the owner for \"$path\"."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! chown "$uid:$gid" "$path"; then
|
||||||
|
warn "failed to set the owner for \"$path\"."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
stateFile() {
|
||||||
|
|
||||||
|
local name="$1"
|
||||||
|
local prefix="${2:-$PROCESS}"
|
||||||
|
|
||||||
|
[[ "$name" == */* ]] && printf '%s\n' "$name" && return 0
|
||||||
|
|
||||||
|
printf '%s/%s.%s\n' "$STORAGE" "$prefix" "$name"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
writeFile() {
|
||||||
|
|
||||||
|
local txt="$1"
|
||||||
|
local path="$2"
|
||||||
|
|
||||||
|
if ! printf '%s\n' "$txt" > "$path"; then
|
||||||
|
error "Failed to write file \"$path\" !"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! setOwner "$path"; then
|
||||||
|
warn "failed to set the owner for \"$path\"."
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
writeAtomic() {
|
||||||
|
|
||||||
|
local path="$1"
|
||||||
|
local content="$2"
|
||||||
|
local tmp="${path}.${BASHPID}.tmp"
|
||||||
|
|
||||||
|
if ! printf '%s\n' "$content" > "$tmp"; then
|
||||||
|
rm -f -- "$tmp"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! mv -f -- "$tmp" "$path"; then
|
||||||
|
rm -f -- "$tmp"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
readFile() {
|
||||||
|
|
||||||
|
local path="$1"
|
||||||
|
local value
|
||||||
|
|
||||||
|
[ -s "$path" ] || return 0
|
||||||
|
|
||||||
|
value=$(<"$path") || return 1
|
||||||
|
value="${value//[![:print:]]/}"
|
||||||
|
|
||||||
|
printf '%s\n' "$value"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
writeState() {
|
||||||
|
|
||||||
|
local name="$1"
|
||||||
|
local value="$2"
|
||||||
|
local prefix="${3:-$PROCESS}"
|
||||||
|
local path
|
||||||
|
|
||||||
|
[ -z "$value" ] && return 0
|
||||||
|
|
||||||
|
path=$(stateFile "$name" "$prefix") || return 1
|
||||||
|
writeFile "$value" "$path"
|
||||||
|
|
||||||
|
return $?
|
||||||
|
}
|
||||||
|
|
||||||
|
readState() {
|
||||||
|
|
||||||
|
local name="$1"
|
||||||
|
local prefix="${2:-$PROCESS}"
|
||||||
|
local path
|
||||||
|
|
||||||
|
path=$(stateFile "$name" "$prefix") || return 1
|
||||||
|
readFile "$path"
|
||||||
|
|
||||||
|
return $?
|
||||||
|
}
|
||||||
|
|
||||||
|
restoreState() {
|
||||||
|
|
||||||
|
local var="$1"
|
||||||
|
local name="$2"
|
||||||
|
local force="${3:-N}"
|
||||||
|
local prefix="${4:-$PROCESS}"
|
||||||
|
local value
|
||||||
|
|
||||||
|
if ! enabled "$force"; then
|
||||||
|
[ -z "${!var:-}" ] || return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
value=$(readState "$name" "$prefix") || return 1
|
||||||
|
[ -n "$value" ] || return 0
|
||||||
|
|
||||||
|
printf -v "$var" '%s' "$value" || return 1
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
escape () {
|
||||||
|
|
||||||
|
local s=${1//&/\&}
|
||||||
|
s=${s//</\<}
|
||||||
|
s=${s//>/\>}
|
||||||
|
s=${s//'"'/\"}
|
||||||
|
|
||||||
|
printf -- %s "$s"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
escapeXML() {
|
||||||
|
|
||||||
|
printf '%s' "$1" | sed \
|
||||||
|
-e 's/&/\&/g' \
|
||||||
|
-e 's/</\</g' \
|
||||||
|
-e 's/>/\>/g' \
|
||||||
|
-e 's/"/\"/g' \
|
||||||
|
-e "s/'/\'/g"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
html() {
|
||||||
|
|
||||||
|
local title
|
||||||
|
local body
|
||||||
|
local script="${2:-}"
|
||||||
|
local footer
|
||||||
|
|
||||||
|
title=$(escape "$APP")
|
||||||
|
title="<title>$title</title>"
|
||||||
|
footer=$(escape "$FOOTER1")
|
||||||
|
|
||||||
|
body=$(escape "$1")
|
||||||
|
if [[ "$body" == *"..." ]]; then
|
||||||
|
body="<p class=\"loading\">${body/.../}</p>"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local HTML
|
||||||
|
HTML=$(<"$TEMPLATE")
|
||||||
|
HTML="${HTML/\[1\]/$title}"
|
||||||
|
HTML="${HTML/\[2\]/$script}"
|
||||||
|
HTML="${HTML/\[3\]/$body}"
|
||||||
|
HTML="${HTML/\[4\]/$footer}"
|
||||||
|
HTML="${HTML/\[5\]/$FOOTER2}"
|
||||||
|
|
||||||
|
writeAtomic "$PAGE" "$HTML" || return 1
|
||||||
|
writeAtomic "$INFO" "$body" || return 1
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
cpu() {
|
||||||
|
|
||||||
|
local ret
|
||||||
|
local cpu=""
|
||||||
|
|
||||||
|
ret=$(lscpu)
|
||||||
|
|
||||||
|
if grep -qi "model name" <<< "$ret"; then
|
||||||
|
cpu=$(echo "$ret" | grep -m 1 -i 'model name' | cut -f 2 -d ":" | awk '{$1=$1}1' | sed 's# @.*##g' | sed s/"(R)"//g | sed 's/[^[:alnum:] ]\+/ /g' | sed 's/ */ /g')
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "${cpu// /}" ] && grep -qi "model:" <<< "$ret"; then
|
||||||
|
cpu=$(echo "$ret" | grep -m 1 -i 'model:' | cut -f 2 -d ":" | awk '{$1=$1}1' | sed 's# @.*##g' | sed s/"(R)"//g | sed 's/[^[:alnum:] ]\+/ /g' | sed 's/ */ /g')
|
||||||
|
fi
|
||||||
|
|
||||||
|
cpu="${cpu// CPU/}"
|
||||||
|
cpu="${cpu// [0-9][0-9][0-9] Core}"
|
||||||
|
cpu="${cpu// [0-9][0-9] Core}"
|
||||||
|
cpu="${cpu// [0-9] Core}"
|
||||||
|
cpu="${cpu//[0-9][0-9]th Gen }"
|
||||||
|
cpu="${cpu//[0-9]th Gen }"
|
||||||
|
cpu="${cpu// Processor/}"
|
||||||
|
cpu="${cpu// Quad core/}"
|
||||||
|
cpu="${cpu// Dual core/}"
|
||||||
|
cpu="${cpu// Octa core/}"
|
||||||
|
cpu="${cpu// Hexa core/}"
|
||||||
|
cpu="${cpu// Core TM/ Core}"
|
||||||
|
cpu="${cpu// with Radeon Graphics/}"
|
||||||
|
cpu="${cpu// with Radeon Vega Graphics/}"
|
||||||
|
cpu="${cpu// with Radeon Vega Mobile Gfx/}"
|
||||||
|
cpu="${cpu// w Radeon [0-9][0-9][0-9]M Graphics/}"
|
||||||
|
|
||||||
|
[ -z "${cpu// /}" ] && cpu="Unknown"
|
||||||
|
|
||||||
|
echo "$cpu"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
getCountry() {
|
||||||
|
|
||||||
|
local url=$1
|
||||||
|
local query=$2
|
||||||
|
local json result
|
||||||
|
|
||||||
|
{ json=$(curl -m 5 -H "Accept: application/json" -sfk "$url"); local rc=$?; } || :
|
||||||
|
(( rc != 0 )) && return 0
|
||||||
|
|
||||||
|
{ result=$(echo "$json" | jq -r "$query" 2> /dev/null); rc=$?; } || :
|
||||||
|
(( rc != 0 )) && return 0
|
||||||
|
|
||||||
|
[[ ${#result} -ne 2 ]] && return 0
|
||||||
|
[[ "${result^^}" == "XX" ]] && return 0
|
||||||
|
|
||||||
|
COUNTRY="${result^^}"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
setCountry() {
|
||||||
|
|
||||||
|
[[ "${TZ,,}" == "asia/harbin" ]] && COUNTRY="CN"
|
||||||
|
[[ "${TZ,,}" == "asia/beijing" ]] && COUNTRY="CN"
|
||||||
|
[[ "${TZ,,}" == "asia/urumqi" ]] && COUNTRY="CN"
|
||||||
|
[[ "${TZ,,}" == "asia/kashgar" ]] && COUNTRY="CN"
|
||||||
|
[[ "${TZ,,}" == "asia/shanghai" ]] && COUNTRY="CN"
|
||||||
|
[[ "${TZ,,}" == "asia/chongqing" ]] && COUNTRY="CN"
|
||||||
|
|
||||||
|
[ -z "$COUNTRY" ] && getCountry "https://api.ipapi.is" ".location.country_code"
|
||||||
|
[ -z "$COUNTRY" ] && getCountry "https://ifconfig.co/json" ".country_iso"
|
||||||
|
[ -z "$COUNTRY" ] && getCountry "https://api.ip2location.io" ".country_code"
|
||||||
|
[ -z "$COUNTRY" ] && getCountry "https://ipinfo.io/json" ".country"
|
||||||
|
[ -z "$COUNTRY" ] && getCountry "https://api.ipquery.io/?format=json" ".location.country_code"
|
||||||
|
[ -z "$COUNTRY" ] && getCountry "https://api.myip.com" ".cc"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
addPackage() {
|
||||||
|
|
||||||
|
local pkg=$1
|
||||||
|
local desc=$2
|
||||||
|
|
||||||
|
if apt-mark showinstall | grep -qx "$pkg"; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
MSG="Installing $desc..."
|
||||||
|
info "$MSG" && html "$MSG"
|
||||||
|
|
||||||
|
[ -z "$COUNTRY" ] && setCountry
|
||||||
|
|
||||||
|
if [[ "${COUNTRY^^}" == "CN" ]]; then
|
||||||
|
sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list.d/debian.sources
|
||||||
|
fi
|
||||||
|
|
||||||
|
DEBIAN_FRONTEND=noninteractive apt-get -qq update || return 1
|
||||||
|
DEBIAN_FRONTEND=noninteractive apt-get -qq --no-install-recommends -y install "$pkg" > /dev/null || return 1
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
@@ -1,8 +1,5 @@
|
|||||||
server {
|
server {
|
||||||
listen 80;
|
|
||||||
listen [::]:80;
|
|
||||||
listen 5000 default_server;
|
listen 5000 default_server;
|
||||||
listen [::]:5000 default_server;
|
|
||||||
|
|
||||||
autoindex on;
|
autoindex on;
|
||||||
tcp_nodelay on;
|
tcp_nodelay on;
|
||||||
@@ -30,4 +27,18 @@ server {
|
|||||||
index index.html;
|
index index.html;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
location /status {
|
||||||
|
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
|
||||||
|
proxy_set_header Connection 'upgrade';
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_read_timeout 3600s;
|
||||||
|
proxy_send_timeout 3600s;
|
||||||
|
|
||||||
|
proxy_pass http://127.0.0.1:8004/;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+16
-3
@@ -1,7 +1,14 @@
|
|||||||
body {
|
body {
|
||||||
color: white;
|
color: white;
|
||||||
background-color: #125bdb;
|
background-color: #125bdb;
|
||||||
font-family: Verdana, Arial, sans-serif;
|
font-smoothing: antialiased;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
font-family: Verdana, Geneva, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
#info {
|
||||||
|
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
#content {
|
#content {
|
||||||
@@ -17,6 +24,7 @@ footer {
|
|||||||
height: 40px;
|
height: 40px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #0c8aeb;
|
color: #0c8aeb;
|
||||||
|
text-shadow: 0 0 1px #0c8aeb;
|
||||||
}
|
}
|
||||||
|
|
||||||
#empty {
|
#empty {
|
||||||
@@ -33,8 +41,13 @@ a:visited {
|
|||||||
|
|
||||||
footer a:link,
|
footer a:link,
|
||||||
footer a:visited,
|
footer a:visited,
|
||||||
footer a:active { color: #0c8aeb; }
|
footer a:active {
|
||||||
footer a:hover { color: #73e6ff; }
|
color: #0c8aeb;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer a:hover {
|
||||||
|
color: #73e6ff;
|
||||||
|
}
|
||||||
|
|
||||||
.loading:after {
|
.loading:after {
|
||||||
content: " .";
|
content: " .";
|
||||||
|
|||||||
+3
-3
@@ -5,8 +5,8 @@
|
|||||||
[1]
|
[1]
|
||||||
<meta http-equiv="Cache-Control" content="no-cache" />
|
<meta http-equiv="Cache-Control" content="no-cache" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<link rel="stylesheet" type="text/css" href="/css/style.css" />
|
<link rel="stylesheet" type="text/css" href="css/style.css" />
|
||||||
<link rel="icon" href="/img/favicon.svg" type="image/x-icon">
|
<link rel="icon" href="img/favicon.svg" type="image/x-icon">
|
||||||
[2]
|
[2]
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
[5]
|
[5]
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
<script type="text/javascript" src="/js/script.js"></script>
|
<script type="text/javascript" src="js/script.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+156
-38
@@ -1,16 +1,32 @@
|
|||||||
|
var timer;
|
||||||
var request;
|
var request;
|
||||||
|
var booting = false;
|
||||||
var interval = 1000;
|
var interval = 1000;
|
||||||
|
|
||||||
|
function abortRequest() {
|
||||||
|
|
||||||
|
if (!request) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
request.onreadystatechange = null;
|
||||||
|
request.abort();
|
||||||
|
request = null;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
function getInfo() {
|
function getInfo() {
|
||||||
|
|
||||||
var url = "/msg.html";
|
var url = "msg.html";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
abortRequest();
|
||||||
|
|
||||||
if (window.XMLHttpRequest) {
|
if (window.XMLHttpRequest) {
|
||||||
request = new XMLHttpRequest();
|
request = new XMLHttpRequest();
|
||||||
} else {
|
} else {
|
||||||
throw "XMLHttpRequest not available!";
|
throw new Error("XMLHttpRequest not available!");
|
||||||
}
|
}
|
||||||
|
|
||||||
request.onreadystatechange = processInfo;
|
request.onreadystatechange = processInfo;
|
||||||
@@ -18,45 +34,73 @@ function getInfo() {
|
|||||||
request.send();
|
request.send();
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
var err = "Error: " + e.message;
|
setError("Error: " + e.message);
|
||||||
console.log(err);
|
|
||||||
setError(err);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getURL() {
|
||||||
|
|
||||||
|
var protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
|
||||||
|
var path = window.location.pathname.replace(/[^/]*$/, '').replace(/\/$/, '');
|
||||||
|
|
||||||
|
return protocol + "//" + window.location.host + path;
|
||||||
|
}
|
||||||
|
|
||||||
|
function processMsg(msg) {
|
||||||
|
|
||||||
|
if (msg.toLowerCase().indexOf("href=") !== -1) {
|
||||||
|
var div = document.createElement("div");
|
||||||
|
div.innerHTML = msg;
|
||||||
|
var url = div.querySelector("a").href;
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.assign(url);
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
setInfo(msg);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
function processInfo() {
|
function processInfo() {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
if (request.readyState != 4) {
|
if (request.readyState != 4) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var msg = request.responseText;
|
var response = request;
|
||||||
if (msg == null || msg.length == 0) {
|
request = null;
|
||||||
setInfo("Booting DSM instance", true);
|
|
||||||
|
var status = response.status;
|
||||||
|
|
||||||
|
if (status == 502 || status == 503 || status == 504) {
|
||||||
schedule();
|
schedule();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var msg = response.responseText;
|
||||||
|
if (msg == null || msg.length == 0) {
|
||||||
|
|
||||||
|
if (booting) {
|
||||||
|
schedule();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.location.reload();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var notFound = (request.status == 404);
|
var notFound = (status == 404);
|
||||||
|
|
||||||
if (request.status == 200) {
|
if (status == 200) {
|
||||||
if (msg.toLowerCase().indexOf("<html>") !== -1) {
|
if (msg.toLowerCase().indexOf("<html>") !== -1) {
|
||||||
notFound = true;
|
notFound = true;
|
||||||
} else {
|
} else {
|
||||||
if (msg.toLowerCase().indexOf("href=") !== -1) {
|
processMsg(msg);
|
||||||
var div = document.createElement("div");
|
if (msg.toLowerCase().indexOf("href=") == -1) {
|
||||||
div.innerHTML = msg;
|
|
||||||
var url = div.querySelector("a").href;
|
|
||||||
setTimeout(() => {
|
|
||||||
window.location.assign(url);
|
|
||||||
}, 3000);
|
|
||||||
setInfo(msg);
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
setInfo(msg);
|
|
||||||
schedule();
|
schedule();
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,45 +110,61 @@ function processInfo() {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
setError("Error: Received statuscode " + request.status);
|
setError("Error: Received statuscode " + status);
|
||||||
schedule();
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
var err = "Error: " + e.message;
|
setError("Error: " + e.message);
|
||||||
console.log(err);
|
|
||||||
setError(err);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setInfo(msg, loading, error) {
|
function extractContent(s) {
|
||||||
|
var span = document.createElement('span');
|
||||||
|
span.innerHTML = s;
|
||||||
|
return span.textContent || span.innerText;
|
||||||
|
};
|
||||||
|
|
||||||
|
function setInfo(msg, loading, error) {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
if (msg == null || msg.length == 0) {
|
if (msg == null || msg.length == 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var el = document.getElementById("spinner");
|
if (msg.includes("Booting ")) {
|
||||||
|
booting = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var el = document.getElementById("info");
|
||||||
|
|
||||||
|
if (el.innerText == msg || el.innerHTML == msg) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var spin = document.getElementById("spinner");
|
||||||
|
|
||||||
error = !!error;
|
error = !!error;
|
||||||
if (!error) {
|
if (!error) {
|
||||||
el.style.visibility = 'visible';
|
spin.style.visibility = 'visible';
|
||||||
} else {
|
} else {
|
||||||
el.style.visibility = 'hidden';
|
spin.style.visibility = 'hidden';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var p = "<p class=\"loading\">";
|
||||||
loading = !!loading;
|
loading = !!loading;
|
||||||
if (loading) {
|
if (loading) {
|
||||||
msg = "<p class=\"loading\">" + msg + "</p>";
|
msg = p + msg + "</p>";
|
||||||
}
|
}
|
||||||
|
|
||||||
el = document.getElementById("info");
|
if (msg.includes(p)) {
|
||||||
|
if (el.innerHTML.includes(p)) {
|
||||||
if (el.innerHTML != msg) {
|
el.getElementsByClassName('loading')[0].textContent = extractContent(msg);
|
||||||
el.innerHTML = msg;
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
el.innerHTML = msg;
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -114,17 +174,75 @@ function setInfo(msg, loading, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setError(text) {
|
function setError(text) {
|
||||||
|
console.warn(text);
|
||||||
return setInfo(text, false, true);
|
return setInfo(text, false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
function schedule() {
|
function schedule() {
|
||||||
setTimeout(getInfo, interval);
|
|
||||||
|
clearTimeout(timer);
|
||||||
|
timer = setTimeout(getInfo, interval);
|
||||||
}
|
}
|
||||||
|
|
||||||
function reload() {
|
function reload() {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
document.location.reload();
|
window.location.reload();
|
||||||
}, 3000);
|
}, 3000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function connect() {
|
||||||
|
|
||||||
|
var wsUrl = getURL() + "/status";
|
||||||
|
var ws = new WebSocket(wsUrl);
|
||||||
|
|
||||||
|
ws.onmessage = function(e) {
|
||||||
|
|
||||||
|
var pos = e.data.indexOf(":");
|
||||||
|
var cmd = e.data.substring(0, pos);
|
||||||
|
var msg = e.data.substring(pos + 2);
|
||||||
|
|
||||||
|
switch (cmd) {
|
||||||
|
case "s":
|
||||||
|
|
||||||
|
var aborted = abortRequest();
|
||||||
|
|
||||||
|
processMsg(msg);
|
||||||
|
|
||||||
|
if (aborted &&
|
||||||
|
msg.toLowerCase().indexOf("href=") == -1) {
|
||||||
|
schedule();
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "e":
|
||||||
|
|
||||||
|
if (abortRequest()) {
|
||||||
|
schedule();
|
||||||
|
}
|
||||||
|
|
||||||
|
setError(msg);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
console.warn("Unknown event: " + cmd);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onclose = function(e) {
|
||||||
|
setTimeout(function() {
|
||||||
|
connect();
|
||||||
|
}, interval);
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onerror = function(e) {
|
||||||
|
ws.close();
|
||||||
|
if (!booting) {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
schedule();
|
schedule();
|
||||||
|
connect();
|
||||||
|
|||||||
Reference in New Issue
Block a user