mirror of
https://github.com/vdsm/virtual-dsm.git
synced 2026-08-29 04:07:02 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c9a2a6966b | ||
|
|
498248f43e | ||
|
|
8963228b1c | ||
|
|
827d6d9e10 | ||
|
|
1c577abef4 | ||
|
|
20fd1a975c | ||
|
|
770774680c | ||
|
|
0c75aa81fe | ||
|
|
36ec57ac15 | ||
|
|
9c488c52b9 | ||
|
|
2ba756260f | ||
|
|
b306012743 | ||
|
|
61b3ee7e01 | ||
|
|
245036f844 | ||
|
|
68cadfe035 | ||
|
|
96102832b1 | ||
|
|
211a56bf73 | ||
|
|
506fb7b114 | ||
|
|
bcc3393b7f | ||
|
|
45c0f91d6e | ||
|
|
a1910546a6 | ||
|
|
637ed0e3a6 | ||
|
|
954a6076ad | ||
|
|
415aaa3038 | ||
|
|
c87e45719b | ||
|
|
102945ab48 | ||
|
|
1c3d490303 | ||
|
|
73fa70f52f | ||
|
|
7b095702fc | ||
|
|
f82b2769d3 | ||
|
|
cafe619028 | ||
|
|
e94a0c4a52 | ||
|
|
d6a353df0e | ||
|
|
050812e996 | ||
|
|
75280e9aa9 | ||
|
|
8dcc8ccee1 | ||
|
|
ba866e9e75 | ||
|
|
06e7a8edb9 | ||
|
|
495c67734a | ||
|
|
1557735331 | ||
|
|
f45b308c78 | ||
|
|
d5821cd782 | ||
|
|
312816b18e | ||
|
|
f42a3f1662 | ||
|
|
558e5022ca | ||
|
|
e03822f26e | ||
|
|
7b90ca30ba | ||
|
|
22c01bf2f1 | ||
|
|
1f601db8b0 | ||
|
|
e7e2c35cb7 | ||
|
|
50089112c0 | ||
|
|
f9538ce53b | ||
|
|
49d25a13d0 | ||
|
|
7c9d2b1fd5 | ||
|
|
b68d243b6a | ||
|
|
79e85f9fd1 | ||
|
|
476048a4ee | ||
|
|
f983dc3e46 | ||
|
|
38f9c49200 | ||
|
|
7157717a85 | ||
|
|
031674424d | ||
|
|
28e6f8da78 | ||
|
|
d5802607b5 | ||
|
|
7168f2f843 | ||
|
|
819dd29025 | ||
|
|
48ef47b85b | ||
|
|
10c902bc9b | ||
|
|
d6de10efe1 | ||
|
|
6d1d9d92b4 | ||
|
|
799649e78c | ||
|
|
12060c72b2 | ||
|
|
6eadd1b953 | ||
|
|
342ca4da03 | ||
|
|
b0b6190560 | ||
|
|
075410e4cf | ||
|
|
2a388b434b | ||
|
|
280c4037f3 | ||
|
|
007bdc735d | ||
|
|
41fc0a6a10 | ||
|
|
7753e4ac74 | ||
|
|
f74bf35202 | ||
|
|
aa472c96c6 | ||
|
|
0dcb880800 | ||
|
|
8daa6973b7 | ||
|
|
3884befa56 | ||
|
|
85b83f8eb1 | ||
|
|
f395dfaa66 | ||
|
|
b3124075ea | ||
|
|
aed909bdee | ||
|
|
d4cf5778e2 | ||
|
|
48de7dd00b | ||
|
|
686e64da3f | ||
|
|
7517dc8b3e | ||
|
|
660fe715e4 | ||
|
|
04ea29ec77 | ||
|
|
02c891cb16 | ||
|
|
0b5d21357e | ||
|
|
e0545b37d7 | ||
|
|
4161c21082 | ||
|
|
48d9a1771d | ||
|
|
471cdbb338 | ||
|
|
e77bca202b | ||
|
|
2e6c01e934 | ||
|
|
302c991c0c | ||
|
|
a89007ee03 | ||
|
|
8a89149d58 | ||
|
|
5e8bbc2868 | ||
|
|
4e48920309 | ||
|
|
8b145924b9 | ||
|
|
a0328e1e9c | ||
|
|
b7f5214a7b | ||
|
|
b0e4c4ac5f | ||
|
|
bbb67aac93 | ||
|
|
433c83b393 | ||
|
|
5577178eeb | ||
|
|
221b0242fa | ||
|
|
c05623f8af | ||
|
|
eb9884cc96 | ||
|
|
8e2490e6bc | ||
|
|
399243886e | ||
|
|
b0dbfcb805 | ||
|
|
dec2dc9230 | ||
|
|
2c44669d91 | ||
|
|
175d90aad7 | ||
|
|
e1e5f8f91d | ||
|
|
dff8abbe1e | ||
|
|
0f97091e61 | ||
|
|
ea0d7ee6cd | ||
|
|
75daa31d36 | ||
|
|
8c44319c45 | ||
|
|
dac574d933 | ||
|
|
fd4c5001e8 | ||
|
|
dfe0b23995 | ||
|
|
6158372eaa | ||
|
|
b1a778d7b2 | ||
|
|
bc0defd813 | ||
|
|
48e7a9fff0 | ||
|
|
c2fa58ef27 | ||
|
|
ea49cb144b | ||
|
|
b8e778a79d | ||
|
|
c70e12f0a2 | ||
|
|
6281205912 | ||
|
|
1ffc5c55b2 | ||
|
|
c3302e1720 | ||
|
|
25227944b5 | ||
|
|
06650e916a | ||
|
|
2e34dffed5 | ||
|
|
3db91f077f | ||
|
|
fae14f4dd9 | ||
|
|
d190b3ba87 | ||
|
|
554f3295cb | ||
|
|
cdc4689d6a | ||
|
|
21d18fd439 | ||
|
|
fd5ec52226 | ||
|
|
fc7ab22741 | ||
|
|
59cf59faa4 | ||
|
|
705c6ce7f1 | ||
|
|
f2937ab507 | ||
|
|
399829cf3c | ||
|
|
b694d6faf8 | ||
|
|
7acd1f6cdb | ||
|
|
09ca3bf118 | ||
|
|
6cac45c397 | ||
|
|
f18663d840 | ||
|
|
fefe1af9e6 | ||
|
|
87a8cf7513 | ||
|
|
7f31cb6023 | ||
|
|
138742c953 | ||
|
|
2c6efc45f2 | ||
|
|
24d795fbe3 | ||
|
|
7fae62d286 | ||
|
|
2135df07ea | ||
|
|
521beedf1c | ||
|
|
e362c9a8a9 | ||
|
|
78817ecfc1 | ||
|
|
8de7f56ff8 | ||
|
|
2e2017470e | ||
|
|
0a0cd98de3 | ||
|
|
17187dd23a | ||
|
|
10237b31cf | ||
|
|
6883efb857 | ||
|
|
1ca55cad8f | ||
|
|
f841eb1ef1 | ||
|
|
1ee49332f3 | ||
|
|
095e618785 | ||
|
|
57a902ad9f | ||
|
|
a7e229aaae | ||
|
|
10466d7851 | ||
|
|
238ebd273b | ||
|
|
0b08f4212e | ||
|
|
9aec5d20e3 | ||
|
|
a19bf2f066 | ||
|
|
bee68ee548 | ||
|
|
520c70ab22 | ||
|
|
67a7fbc94b | ||
|
|
1b8054e847 | ||
|
|
631a558a9f | ||
|
|
d5805b4e08 | ||
|
|
1dc15ce2e0 | ||
|
|
4145e811df | ||
|
|
fe8615d7d3 | ||
|
|
27ea5506bc | ||
|
|
394131a0d7 | ||
|
|
4d0d16023f | ||
|
|
60dd794b29 | ||
|
|
f13d104968 | ||
|
|
914099fd12 | ||
|
|
639725957d | ||
|
|
64bbe82c4a | ||
|
|
73a13e5697 | ||
|
|
e8ec5fb8ed | ||
|
|
0b500ca377 | ||
|
|
df58745ed8 | ||
|
|
55e8ab4930 | ||
|
|
4882fd3bf6 | ||
|
|
3e69f4bcc9 | ||
|
|
39428909d6 | ||
|
|
71d2ed40db | ||
|
|
9ad21a28a6 | ||
|
|
0c7acd6e71 | ||
|
|
ad26129375 | ||
|
|
4e7822fedd | ||
|
|
4dfcbe4ab1 | ||
|
|
8fb6f1f9ad | ||
|
|
11d4fafa6d | ||
|
|
be15557798 | ||
|
|
1e83757494 | ||
|
|
fad463a439 | ||
|
|
608563d029 | ||
|
|
5fcd22b09f | ||
|
|
65548da2ef | ||
|
|
111e513dac | ||
|
|
7fce3a98fe | ||
|
|
4018eeadb4 | ||
|
|
a8e4647fa6 | ||
|
|
36d3fca4fc | ||
|
|
89b28f36d3 | ||
|
|
b4f7d70a7a | ||
|
|
902bafbd0c | ||
|
|
7ad5c7fa70 | ||
|
|
4d29f056b7 | ||
|
|
36af9a3483 | ||
|
|
5e6f931433 | ||
|
|
beadfe720c | ||
|
|
92b4bfc383 | ||
|
|
092ed23085 | ||
|
|
c70fa3d00a | ||
|
|
fea0ba09f6 | ||
|
|
7d2af63eac | ||
|
|
2e73bf560e | ||
|
|
bfc8d7a9c6 | ||
|
|
a1e9936572 | ||
|
|
3675a50129 | ||
|
|
91229152bd | ||
|
|
a1993e590a | ||
|
|
52fe712b6f | ||
|
|
7f400b6b59 | ||
|
|
9cb88a99e6 | ||
|
|
b967a471b5 | ||
|
|
f40127df01 | ||
|
|
8c5e0ee274 | ||
|
|
2adf0b292b | ||
|
|
1c9b793c75 | ||
|
|
00c4ef7795 | ||
|
|
619657adf2 | ||
|
|
41db7c1035 | ||
|
|
d71834a777 | ||
|
|
d078af0397 | ||
|
|
2827d1d375 | ||
|
|
898499a4e3 | ||
|
|
eb010cc215 | ||
|
|
84440d5159 | ||
|
|
ff3744ead9 | ||
|
|
d00fe4b3eb | ||
|
|
72a86a5d7f | ||
|
|
811ab622df | ||
|
|
e76b72cddf | ||
|
|
e9edacc9c3 | ||
|
|
a6694a6b29 | ||
|
|
59323cd375 | ||
|
|
6dc714e449 | ||
|
|
5d75a9b039 | ||
|
|
92b4cf5997 | ||
|
|
906e61b1b2 | ||
|
|
dab230f9d5 | ||
|
|
5e8bcda9fc | ||
|
|
d4bf83ae86 | ||
|
|
8244a48511 | ||
|
|
43ffa18a5f | ||
|
|
b131a32d0c | ||
|
|
c81787b837 | ||
|
|
f9df3c6db6 | ||
|
|
e383ec30e3 | ||
|
|
1197c4791e | ||
|
|
106c684389 | ||
|
|
a199ced77b | ||
|
|
77ac73666e | ||
|
|
64975557c2 | ||
|
|
b62321806a | ||
|
|
7c392082b1 | ||
|
|
392e9a6417 | ||
|
|
fb5f684e7e | ||
|
|
0dbc794223 | ||
|
|
6295ed3b7b | ||
|
|
0f2d889858 | ||
|
|
60e6b01982 | ||
|
|
1dc69f9e22 | ||
|
|
fcca41ad93 | ||
|
|
9897747425 | ||
|
|
4dbe9dcefd | ||
|
|
be8bee90f2 | ||
|
|
f8879029ec | ||
|
|
7b491b3cee | ||
|
|
2ece865417 | ||
|
|
0d8c693c65 | ||
|
|
9f7d1396b6 | ||
|
|
5ad323486e | ||
|
|
19b4248929 | ||
|
|
c135c4cac3 | ||
|
|
fb1751ff26 | ||
|
|
34f32d4ac6 | ||
|
|
36f1e47c0a | ||
|
|
c5dd5c2e46 | ||
|
|
d1d920372a | ||
|
|
12d8fd3ed0 | ||
|
|
729aed536e | ||
|
|
b588f8c90d | ||
|
|
1f1007a0f1 | ||
|
|
2ec37e2802 | ||
|
|
3126b3847b | ||
|
|
41e0157e9d | ||
|
|
1d64410849 | ||
|
|
55034b0f40 | ||
|
|
acffcf3774 | ||
|
|
fba0eb527b | ||
|
|
d9fc2714a6 | ||
|
|
d1f1772d74 | ||
|
|
bf1d47e4f3 | ||
|
|
3da564dfd1 | ||
|
|
abd30b9d91 | ||
|
|
c86408cbd6 | ||
|
|
1f51974c48 | ||
|
|
29f4cde296 | ||
|
|
c4a0035062 | ||
|
|
6724ddbd7d | ||
|
|
8d8ed63122 | ||
|
|
6f4ea81907 | ||
|
|
6d162744ec | ||
|
|
145b4aab5b | ||
|
|
2e4eb56d0b | ||
|
|
39c019193e | ||
|
|
faec563b4a | ||
|
|
9840f8e07a | ||
|
|
3a5895fa0f | ||
|
|
1bac5c8a7f | ||
|
|
dd76c60e2a | ||
|
|
ac78cc7dc5 | ||
|
|
66ac2d2002 | ||
|
|
0bd099d704 | ||
|
|
d2dac3cfb4 | ||
|
|
e81dc0f31d | ||
|
|
5015597183 | ||
|
|
64e2af9fa2 | ||
|
|
debb4b69fc | ||
|
|
c6d3dda171 | ||
|
|
7c0693c2ff | ||
|
|
76355d4857 | ||
|
|
404aaadefc | ||
|
|
be027e10be | ||
|
|
1c8cad92f8 | ||
|
|
fabb8ea3b7 | ||
|
|
2ee4abca54 | ||
|
|
5896928030 | ||
|
|
8652544982 | ||
|
|
a70338ec3c | ||
|
|
a84878abfc | ||
|
|
8421a391b7 | ||
|
|
f9340ec3d6 | ||
|
|
0cca9c5f83 | ||
|
|
13d60b7f47 | ||
|
|
f74771a9cc | ||
|
|
f24ba41930 | ||
|
|
f412580a4a | ||
|
|
5cde1b4438 | ||
|
|
7cfb57b1bc | ||
|
|
a478b58f97 | ||
|
|
8297f4f880 | ||
|
|
4c67343d33 | ||
|
|
53cc6998f0 | ||
|
|
d857d71e0d | ||
|
|
003c2766ce | ||
|
|
78594098cc | ||
|
|
3c31bc91e4 | ||
|
|
72141bab7a | ||
|
|
bc52463aa4 | ||
|
|
9fa68908a9 | ||
|
|
740dbec1b1 | ||
|
|
440d203730 | ||
|
|
1a83c67e2c | ||
|
|
34a707a2a5 | ||
|
|
cabb2cdfc9 | ||
|
|
dc52ccf172 | ||
|
|
bdd7fec3c3 | ||
|
|
bd8b03d089 | ||
|
|
a10588b0ce | ||
|
|
3503b86e12 | ||
|
|
9e124980cd | ||
|
|
675aa5e122 | ||
|
|
2a62d4e938 | ||
|
|
9cbb51cc86 | ||
|
|
7790f81d15 | ||
|
|
fdbff4879b | ||
|
|
739e679a66 | ||
|
|
0dea507d85 | ||
|
|
4f524c47d8 | ||
|
|
0c74201eb4 | ||
|
|
1e13258bc9 | ||
|
|
2c7cea042f | ||
|
|
fc92b66ff4 | ||
|
|
89ae24a2ac | ||
|
|
bd4a23b287 | ||
|
|
1b8c4d9f08 | ||
|
|
a1187decb5 | ||
|
|
b9d7aa182d | ||
|
|
b908c1118d | ||
|
|
fab776764f | ||
|
|
f3e17e399d | ||
|
|
3706ca873b | ||
|
|
10c565f32b | ||
|
|
d237e5b9e6 | ||
|
|
3c7e1ce12f | ||
|
|
f422f64325 | ||
|
|
df0656b345 | ||
|
|
1fc9c56c8f | ||
|
|
893a013ae9 | ||
|
|
6d3812d1d0 | ||
|
|
6422aec780 | ||
|
|
fcd7b8a825 | ||
|
|
575da1f574 | ||
|
|
3a507f5bf6 | ||
|
|
a3d6e3740c | ||
|
|
53e0330e21 | ||
|
|
f935c1e28a | ||
|
|
6a0c708224 | ||
|
|
a407d2d94d | ||
|
|
53605bd6e8 | ||
|
|
944faaa927 | ||
|
|
fb7cfc09de | ||
|
|
b9ae73e322 | ||
|
|
c28dbb6b9b | ||
|
|
dcc26b67a6 | ||
|
|
2627d320f3 | ||
|
|
fd4bc28835 | ||
|
|
63e4d588a2 | ||
|
|
feb1680909 | ||
|
|
9d21489b8e | ||
|
|
e70ed1900f | ||
|
|
d65b5a089a | ||
|
|
84643647cc | ||
|
|
57f487db6d | ||
|
|
0862cad2ce | ||
|
|
5ae4f59315 | ||
|
|
3fc3005ba5 | ||
|
|
20f48edd00 | ||
|
|
b854aad830 | ||
|
|
6cbe03f656 | ||
|
|
63cac9a75e | ||
|
|
08616f1057 | ||
|
|
e6193b1020 | ||
|
|
f28b9903f3 | ||
|
|
7bf2d119ea | ||
|
|
527bded1b2 | ||
|
|
1208c53ebb | ||
|
|
973efa2d27 | ||
|
|
d09588b915 | ||
|
|
19aa313753 | ||
|
|
9db12cd25f | ||
|
|
69e785e6ee | ||
|
|
159fce6839 | ||
|
|
08e4084458 | ||
|
|
06f210846c | ||
|
|
74629e4b55 | ||
|
|
6e8af6e52f | ||
|
|
38611a7af2 | ||
|
|
f089acc01a | ||
|
|
5a7ecb48d6 | ||
|
|
5b3880aa5e | ||
|
|
4653aafbee | ||
|
|
281f2992ff | ||
|
|
4bdcf8bfe1 | ||
|
|
62acaa95bf | ||
|
|
369bff339d | ||
|
|
5332d387f4 | ||
|
|
f0d08ef263 | ||
|
|
e4334f9499 | ||
|
|
627ec56262 | ||
|
|
251cf8121e | ||
|
|
87fad1b0e9 | ||
|
|
698516ac8c | ||
|
|
dae5d75674 | ||
|
|
95facffa9b | ||
|
|
682e0a9952 | ||
|
|
9a97dfdc70 | ||
|
|
2f383699f9 | ||
|
|
8137a137b3 | ||
|
|
1339d51796 | ||
|
|
dce447c974 | ||
|
|
ef5b650991 | ||
|
|
fd19c7b4f3 | ||
|
|
727297642c | ||
|
|
cd457801e7 | ||
|
|
392e7afdfe | ||
|
|
b425e34907 |
@@ -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
|
||||||
+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.
|
||||||
|
|||||||
@@ -7,8 +7,24 @@ 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: actions/checkout@v7
|
||||||
|
- parallel:
|
||||||
|
-
|
||||||
|
name: Run ShellCheck
|
||||||
uses: ludeeus/action-shellcheck@master
|
uses: ludeeus/action-shellcheck@master
|
||||||
env:
|
env:
|
||||||
SHELLCHECK_OPTS: -x --source-path=src -e SC2001 -e SC2002 -e SC2223 -e SC2034 -e SC2064 -e SC2317 -e SC2028 -e SC2153 -e SC2004
|
SHELLCHECK_OPTS: -x --source-path=src -e SC2001 -e SC2034 -e SC2064 -e SC2317 -e SC2153 -e SC2028
|
||||||
|
-
|
||||||
|
name: Lint Dockerfile
|
||||||
|
uses: hadolint/hadolint-action@v3.3.0
|
||||||
|
with:
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
ignore: DL3008,DL3003,DL3006,DL3013
|
||||||
|
failure-threshold: warning
|
||||||
|
-
|
||||||
|
name: Validate JSON and YML files
|
||||||
|
uses: GrantBirki/json-yaml-validate@v5.0.0
|
||||||
|
with:
|
||||||
|
yaml_exclude_regex: ".*\\kubernetes\\.yml$"
|
||||||
|
|||||||
+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,71 @@
|
|||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
name: "Review"
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
|
checks: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
review:
|
||||||
|
name: review
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
-
|
||||||
|
name: Checkout
|
||||||
|
uses: actions/checkout@v7
|
||||||
|
- parallel:
|
||||||
|
-
|
||||||
|
name: Spelling
|
||||||
|
uses: reviewdog/action-misspell@v1
|
||||||
|
with:
|
||||||
|
locale: "US"
|
||||||
|
level: warning
|
||||||
|
pattern: |
|
||||||
|
*.md
|
||||||
|
*.sh
|
||||||
|
reporter: github-pr-review
|
||||||
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
-
|
||||||
|
name: Hadolint
|
||||||
|
uses: reviewdog/action-hadolint@v1
|
||||||
|
with:
|
||||||
|
level: warning
|
||||||
|
fail_level: error
|
||||||
|
reporter: github-pr-review
|
||||||
|
hadolint_ignore: DL3008 DL3003 DL3006 DL3013
|
||||||
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
-
|
||||||
|
name: YamlLint
|
||||||
|
uses: reviewdog/action-yamllint@v1
|
||||||
|
with:
|
||||||
|
level: warning
|
||||||
|
reporter: github-pr-review
|
||||||
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
-
|
||||||
|
name: ActionLint
|
||||||
|
uses: reviewdog/action-actionlint@v1
|
||||||
|
with:
|
||||||
|
level: warning
|
||||||
|
reporter: github-pr-review
|
||||||
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
-
|
||||||
|
name: Shellformat
|
||||||
|
uses: reviewdog/action-shfmt@v1
|
||||||
|
if: false
|
||||||
|
with:
|
||||||
|
level: warning
|
||||||
|
fail_on_error: "true"
|
||||||
|
shfmt_flags: "-i 2 -ci -bn"
|
||||||
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
-
|
||||||
|
name: Shellcheck
|
||||||
|
uses: reviewdog/action-shellcheck@v1
|
||||||
|
with:
|
||||||
|
level: warning
|
||||||
|
fail_level: error
|
||||||
|
reporter: github-pr-review
|
||||||
|
shellcheck_flags: -x -e SC1091 -e SC2001 -e SC2034 -e SC2064 -e SC2317 -e SC2153 -e SC2028
|
||||||
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@@ -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: {}
|
||||||
|
|||||||
+74
-48
@@ -1,61 +1,87 @@
|
|||||||
FROM qemux/qemu-host as builder
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
# FROM golang as builder
|
|
||||||
# WORKDIR /
|
|
||||||
# RUN git clone https://github.com/qemu-tools/qemu-host.git
|
|
||||||
# WORKDIR /qemu-host/src
|
|
||||||
# RUN go mod download
|
|
||||||
# RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o /qemu-host.bin .
|
|
||||||
|
|
||||||
|
FROM qemux/qemu-host:2.05 AS builder
|
||||||
FROM debian:trixie-slim
|
FROM debian:trixie-slim
|
||||||
|
|
||||||
|
ARG TARGETARCH
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
|
ARG VERSION_ARG="0.0"
|
||||||
|
ARG VERSION_PASST="2026_06_11"
|
||||||
|
|
||||||
ARG DEBCONF_NOWARNINGS="yes"
|
ARG DEBCONF_NOWARNINGS="yes"
|
||||||
ARG DEBIAN_FRONTEND noninteractive
|
ARG DEBIAN_FRONTEND="noninteractive"
|
||||||
|
ARG DEBCONF_NONINTERACTIVE_SEEN="true"
|
||||||
|
|
||||||
RUN apt-get update && apt-get -y upgrade \
|
RUN <<EOF
|
||||||
&& if [ "$TARGETPLATFORM" != "linux/amd64" ]; then extra="qemu-user"; fi \
|
set -eu
|
||||||
&& apt-get --no-install-recommends -y install \
|
|
||||||
jq \
|
|
||||||
tini \
|
|
||||||
curl \
|
|
||||||
cpio \
|
|
||||||
wget \
|
|
||||||
fdisk \
|
|
||||||
unzip \
|
|
||||||
socat \
|
|
||||||
procps \
|
|
||||||
xz-utils \
|
|
||||||
iptables \
|
|
||||||
iproute2 \
|
|
||||||
dnsmasq \
|
|
||||||
net-tools \
|
|
||||||
qemu-utils \
|
|
||||||
ca-certificates \
|
|
||||||
netcat-openbsd \
|
|
||||||
qemu-system-x86 \
|
|
||||||
"$extra" \
|
|
||||||
&& apt-get clean \
|
|
||||||
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
|
||||||
|
|
||||||
COPY ./src /run/
|
apt-get update
|
||||||
COPY --from=builder /qemu-host.bin /run/host.bin
|
apt-get --no-install-recommends -y install \
|
||||||
RUN chmod +x /run/*.sh && chmod +x /run/*.bin
|
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 \
|
||||||
|
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
|
||||||
|
|
||||||
|
# Configure QEMU
|
||||||
|
mkdir -p /etc/qemu
|
||||||
|
echo "allow br0" > /etc/qemu/bridge.conf
|
||||||
|
|
||||||
|
# Configure nginx
|
||||||
|
unlink /etc/nginx/sites-enabled/default
|
||||||
|
sed -i 's/^worker_processes.*/worker_processes 1;/' /etc/nginx/nginx.conf
|
||||||
|
|
||||||
|
# Set version file
|
||||||
|
echo "$VERSION_ARG" > /etc/version
|
||||||
|
|
||||||
|
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||||
|
EOF
|
||||||
|
|
||||||
|
COPY --chmod=755 ./src /run/
|
||||||
|
COPY --chmod=755 ./web /var/www/
|
||||||
|
COPY --chmod=755 --from=builder /qemu-host.bin /run/host.bin
|
||||||
|
COPY --chmod=744 ./web/conf/nginx.conf /etc/nginx/default.conf
|
||||||
|
ADD --chmod=775 https://raw.githubusercontent.com/sud0woodo/patology/refs/heads/main/patology.py /run/extract.py
|
||||||
|
|
||||||
VOLUME /storage
|
VOLUME /storage
|
||||||
|
EXPOSE 22 139 445 5000
|
||||||
|
|
||||||
EXPOSE 22
|
ENV RAM_SIZE="2G"
|
||||||
EXPOSE 80
|
ENV CPU_CORES="2"
|
||||||
EXPOSE 139
|
ENV DISK_SIZE="256G"
|
||||||
EXPOSE 445
|
|
||||||
EXPOSE 5000
|
|
||||||
|
|
||||||
ENV RAM_SIZE "1G"
|
|
||||||
ENV DISK_SIZE "16G"
|
|
||||||
ENV CPU_CORES "1"
|
|
||||||
|
|
||||||
ARG VERSION_ARG="0.0"
|
|
||||||
RUN echo "$VERSION_ARG" > /run/version
|
|
||||||
|
|
||||||
HEALTHCHECK --interval=60s --start-period=45s --retries=2 CMD /run/check.sh
|
HEALTHCHECK --interval=60s --start-period=45s --retries=2 CMD /run/check.sh
|
||||||
|
|
||||||
|
|||||||
-140
@@ -1,140 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -u
|
|
||||||
|
|
||||||
VERSION="9"
|
|
||||||
HEADER="VirtualDSM Agent"
|
|
||||||
|
|
||||||
# Functions
|
|
||||||
|
|
||||||
error () { echo -e "\E[1;31m❯ ERROR: $1\E[0m" ; }
|
|
||||||
info () { echo -e "\E[1;34m❯\E[1;36m $1\E[0m" ; }
|
|
||||||
|
|
||||||
finish() {
|
|
||||||
|
|
||||||
echo "❯ $HEADER: Shutting down.."
|
|
||||||
exit
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkNMI {
|
|
||||||
|
|
||||||
local nmi
|
|
||||||
nmi=$(cat /proc/interrupts | grep NMI | sed 's/[^1-9]*//g')
|
|
||||||
|
|
||||||
if [ "$nmi" != "" ]; then
|
|
||||||
|
|
||||||
info "Received shutdown request through NMI.."
|
|
||||||
|
|
||||||
/usr/syno/sbin/synoshutdown -s > /dev/null
|
|
||||||
finish
|
|
||||||
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
function downloadUpdate {
|
|
||||||
|
|
||||||
TMP="/tmp/agent.sh"
|
|
||||||
rm -f "${TMP}"
|
|
||||||
|
|
||||||
# Auto update the agent
|
|
||||||
|
|
||||||
URL="https://raw.githubusercontent.com/vdsm/virtual-dsm/master/agent/agent.sh"
|
|
||||||
|
|
||||||
remote_size=$(curl -sIk -m 4 "${URL}" | grep -i "content-length:" | tr -d " \t" | cut -d ':' -f 2)
|
|
||||||
remote_size=${remote_size//$'\r'}
|
|
||||||
|
|
||||||
[[ "$remote_size" == "" || "$remote_size" == "0" ]] && return
|
|
||||||
|
|
||||||
remote_size=$(($remote_size+0))
|
|
||||||
((remote_size<100)) && return
|
|
||||||
|
|
||||||
SCRIPT=$(readlink -f "${BASH_SOURCE[0]}")
|
|
||||||
local_size=$(stat -c%s "$SCRIPT")
|
|
||||||
local_size=$(($local_size+0))
|
|
||||||
|
|
||||||
[[ remote_size -eq local_size ]] && return
|
|
||||||
|
|
||||||
if ! curl -sfk -m 10 -o "${TMP}" "${URL}"; then
|
|
||||||
error "$HEADER: curl error ($?)" && return
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ! -f "${TMP}" ]; then
|
|
||||||
error "$HEADER: update error, file not found.." && return
|
|
||||||
fi
|
|
||||||
|
|
||||||
line=$(head -1 "${TMP}")
|
|
||||||
|
|
||||||
if [[ "$line" != "#!/usr/bin/env bash" ]]; then
|
|
||||||
error "$HEADER: update error, invalid header: $line" && return
|
|
||||||
fi
|
|
||||||
|
|
||||||
if cmp --silent -- "${TMP}" "${SCRIPT}"; then
|
|
||||||
error "$HEADER: update file is already equal? (${local_size} / ${remote_size})" && return
|
|
||||||
fi
|
|
||||||
|
|
||||||
mv -f "${TMP}" "${SCRIPT}"
|
|
||||||
chmod 755 "${SCRIPT}"
|
|
||||||
|
|
||||||
info "$HEADER: succesfully installed update..."
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function installPackages {
|
|
||||||
|
|
||||||
for filename in /usr/local/packages/*.spk; do
|
|
||||||
if [ -f "$filename" ]; then
|
|
||||||
|
|
||||||
BASE=$(basename "$filename" .spk)
|
|
||||||
BASE="${BASE%%-*}"
|
|
||||||
|
|
||||||
[[ $BASE == "ActiveInsight" ]] && continue
|
|
||||||
|
|
||||||
info "Installing package ${BASE}.."
|
|
||||||
|
|
||||||
/usr/syno/bin/synopkg install "$filename" > /dev/null
|
|
||||||
/usr/syno/bin/synopkg start "$BASE" > /dev/null &
|
|
||||||
|
|
||||||
rm "$filename"
|
|
||||||
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
trap finish SIGINT SIGTERM
|
|
||||||
|
|
||||||
ts=$(date +%s%N)
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "❯ Started $HEADER v$VERSION..."
|
|
||||||
|
|
||||||
checkNMI
|
|
||||||
|
|
||||||
# Install packages
|
|
||||||
|
|
||||||
first_run=false
|
|
||||||
|
|
||||||
for filename in /usr/local/packages/*.spk; do
|
|
||||||
if [ -f "$filename" ]; then
|
|
||||||
first_run=true
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ "$first_run" = true ]; then
|
|
||||||
|
|
||||||
installPackages
|
|
||||||
|
|
||||||
else
|
|
||||||
|
|
||||||
downloadUpdate
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Wait for NMI interrupt as a shutdown signal
|
|
||||||
|
|
||||||
while true; do
|
|
||||||
|
|
||||||
checkNMI
|
|
||||||
sleep 2 & wait $!
|
|
||||||
|
|
||||||
done
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
PIDFILE="/var/run/agent.pid"
|
|
||||||
SCRIPT="/usr/local/bin/agent.sh"
|
|
||||||
|
|
||||||
error () { echo -e "\E[1;31m❯ ERROR: $1\E[0m" ; }
|
|
||||||
info () { echo -e "\E[1;34m❯\E[1;36m $1\E[0m" ; }
|
|
||||||
|
|
||||||
status() {
|
|
||||||
|
|
||||||
if [ -f "$PIDFILE" ] && kill -0 "$(cat "$PIDFILE")"; then
|
|
||||||
echo 'Service running'
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
start() {
|
|
||||||
|
|
||||||
if [ -f "$PIDFILE" ] && kill -0 "$(cat "$PIDFILE")"; then
|
|
||||||
echo 'Service already running'
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo 'Starting agent service...'
|
|
||||||
chmod 666 /dev/ttyS0
|
|
||||||
|
|
||||||
if [ ! -f "$SCRIPT" ]; then
|
|
||||||
|
|
||||||
URL="https://raw.githubusercontent.com/vdsm/virtual-dsm/master/agent/agent.sh"
|
|
||||||
|
|
||||||
if ! curl -sfk -m 10 -o "${SCRIPT}" "${URL}"; then
|
|
||||||
error 'Failed to download agent script.' > /dev/ttyS0
|
|
||||||
rm -f "${SCRIPT}"
|
|
||||||
return 1
|
|
||||||
else
|
|
||||||
info 'Agent script was missing?' > /dev/ttyS0
|
|
||||||
fi
|
|
||||||
|
|
||||||
chmod 755 "${SCRIPT}"
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "-" > /var/lock/subsys/agent.sh
|
|
||||||
"$SCRIPT" &> /dev/ttyS0 & echo $! > "$PIDFILE"
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
stop() {
|
|
||||||
|
|
||||||
if [ ! -f "$PIDFILE" ] || ! kill -0 "$(cat "$PIDFILE")"; then
|
|
||||||
echo 'Service not running'
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo 'Stopping agent service...'
|
|
||||||
|
|
||||||
chmod 666 /dev/ttyS0
|
|
||||||
info 'Stopping agent service...' > /dev/ttyS0
|
|
||||||
|
|
||||||
kill -15 "$(cat "$PIDFILE")" && rm -f "$PIDFILE"
|
|
||||||
rm -f /var/lock/subsys/agent.sh
|
|
||||||
|
|
||||||
echo 'Service stopped'
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
case "$1" in
|
|
||||||
start)
|
|
||||||
start
|
|
||||||
;;
|
|
||||||
stop)
|
|
||||||
stop
|
|
||||||
;;
|
|
||||||
status)
|
|
||||||
status
|
|
||||||
;;
|
|
||||||
restart)
|
|
||||||
stop
|
|
||||||
start
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "Usage: $0 {start|stop|restart}"
|
|
||||||
exit 1
|
|
||||||
esac
|
|
||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
services:
|
||||||
|
dsm:
|
||||||
|
container_name: dsm
|
||||||
|
image: vdsm/virtual-dsm
|
||||||
|
environment:
|
||||||
|
DISK_SIZE: "256G"
|
||||||
|
devices:
|
||||||
|
- /dev/kvm
|
||||||
|
- /dev/net/tun
|
||||||
|
cap_add:
|
||||||
|
- NET_ADMIN
|
||||||
|
ports:
|
||||||
|
- 5000:5000
|
||||||
|
volumes:
|
||||||
|
- ./dsm:/storage
|
||||||
|
restart: always
|
||||||
|
stop_grace_period: 2m
|
||||||
@@ -1,23 +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
|
|
||||||
- /dev/net/tun
|
|
||||||
- /dev/vhost-net
|
|
||||||
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,77 @@
|
|||||||
|
---
|
||||||
|
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
|
||||||
|
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,225 +1,290 @@
|
|||||||
<h1 align="center">Virtual DSM for Docker<br />
|
<h1 align="center">Virtual DSM<br />
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<img src="https://github.com/vdsm/virtual-dsm/raw/master/.github/screen.jpg" title="Screenshot" style="max-width:100%;" width="432" />
|
<a href="https://github.com/vdsm/virtual-dsm"><img src="https://github.com/vdsm/virtual-dsm/raw/master/.github/screen.jpg" title="Screenshot" style="max-width:100%;" width="432" /></a>
|
||||||
</div>
|
</div>
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
[![Build]][build_url]
|
[![Build]][build_url]
|
||||||
[![Version]][tag_url]
|
[![Version]][tag_url]
|
||||||
[![Size]][tag_url]
|
[![Size]][tag_url]
|
||||||
|
[![Package]][pkg_url]
|
||||||
[![Pulls]][hub_url]
|
[![Pulls]][hub_url]
|
||||||
|
|
||||||
</div></h1>
|
</div></h1>
|
||||||
|
|
||||||
Virtual DSM in a docker container.
|
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 passthrough
|
- 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 bridge, user-mode, macvlan, and macvtap networking
|
||||||
|
|
||||||
Via `docker-compose.yml`
|
## Usage 🐳
|
||||||
|
|
||||||
|
##### Docker Compose:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
version: "3"
|
|
||||||
services:
|
services:
|
||||||
dsm:
|
dsm:
|
||||||
container_name: dsm
|
container_name: dsm
|
||||||
image: vdsm/virtual-dsm:latest
|
image: vdsm/virtual-dsm
|
||||||
environment:
|
environment:
|
||||||
DISK_SIZE: "16G"
|
DISK_SIZE: "256G"
|
||||||
devices:
|
devices:
|
||||||
- /dev/kvm
|
- /dev/kvm
|
||||||
|
- /dev/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:latest
|
docker run -it --rm --name dsm -e "DISK_SIZE=256G" -p 5000:5000 --device=/dev/kvm --device=/dev/net/tun --cap-add NET_ADMIN -v "${PWD:-.}/dsm:/storage" --stop-timeout 120 docker.io/vdsm/virtual-dsm
|
||||||
```
|
```
|
||||||
|
|
||||||
## FAQ
|
##### Kubernetes:
|
||||||
|
|
||||||
* ### How do I change the size of the virtual disk?
|
```shell
|
||||||
|
kubectl apply -f https://raw.githubusercontent.com/vdsm/virtual-dsm/refs/heads/master/kubernetes.yml
|
||||||
|
```
|
||||||
|
|
||||||
To expand the default size of 16 GB, locate the `DISK_SIZE` setting in your compose file and modify it to your preferred capacity:
|
##### GitHub Codespaces:
|
||||||
|
|
||||||
```yaml
|
[](https://codespaces.new/vdsm/virtual-dsm)
|
||||||
environment:
|
|
||||||
DISK_SIZE: "128G"
|
|
||||||
```
|
|
||||||
|
|
||||||
This can also be used to resize the existing disk to a larger capacity without any data loss.
|
|
||||||
|
|
||||||
* ### How do I change the location of the virtual disk?
|
## Requirements ⚙️
|
||||||
|
|
||||||
To change the location of the virtual disk, include the following bind mount in your compose file:
|
- A Linux host with KVM support, or Docker Desktop / Podman on Windows 11 with nested virtualization enabled.
|
||||||
|
- At least 2 GB of RAM available.
|
||||||
|
- At least 32 GB of free disk space.
|
||||||
|
|
||||||
```yaml
|
> [!NOTE]
|
||||||
volumes:
|
> Docker Desktop on macOS and Windows 10 do not currently provide the required KVM support for this image.
|
||||||
- /var/dsm:/storage
|
|
||||||
```
|
|
||||||
|
|
||||||
Replace the example path `/var/dsm` with the desired storage folder.
|
## FAQ 💬
|
||||||
|
|
||||||
* ### How do I add multiple disks?
|
### How do I use it?
|
||||||
|
|
||||||
To create additional disks, modify your compose file like this:
|
Very simple! These are the steps:
|
||||||
|
|
||||||
```yaml
|
- Start the container and connect to [port 5000](http://127.0.0.1:5000/) using your web browser.
|
||||||
environment:
|
|
||||||
DISK2_SIZE: "32G"
|
|
||||||
DISK3_SIZE: "64G"
|
|
||||||
volumes:
|
|
||||||
- /home/example:/storage2
|
|
||||||
- /mnt/data/example:/storage3
|
|
||||||
```
|
|
||||||
|
|
||||||
* ### How do I create a growable disk?
|
- 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!
|
||||||
|
|
||||||
By default, the entire capacity of the disk is reserved in advance.
|
### How do I change the storage location?
|
||||||
|
|
||||||
To create a growable disk that only allocates space that is actually used, add the following environment variables:
|
To change the storage location, include the following bind mount in your compose file:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
environment:
|
volumes:
|
||||||
ALLOCATE: "N"
|
- ./dsm:/storage
|
||||||
DISK_FMT: "qcow2"
|
```
|
||||||
```
|
|
||||||
|
|
||||||
Please note that this may reduce the write performance of the disk.
|
Replace the example path `./dsm` with the desired storage folder or named volume.
|
||||||
|
|
||||||
|
### How do I change the size of the disk?
|
||||||
|
|
||||||
* ### How do I increase the amount of CPU or RAM?
|
To expand the default size of 256 GB, locate the `DISK_SIZE` setting in your compose file and modify it to your preferred capacity:
|
||||||
|
|
||||||
By default, a single core and 1 GB of RAM are allocated to the container.
|
```yaml
|
||||||
|
environment:
|
||||||
|
DISK_SIZE: "512G"
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> This can also be used to resize an existing disk to a larger capacity without any data loss.
|
||||||
|
|
||||||
To increase this, add the following environment variables:
|
### How do I add multiple disks?
|
||||||
|
|
||||||
```yaml
|
To create additional disks, modify your compose file like this:
|
||||||
environment:
|
|
||||||
RAM_SIZE: "4G"
|
```yaml
|
||||||
CPU_CORES: "4"
|
environment:
|
||||||
```
|
DISK2_SIZE: "500G"
|
||||||
|
DISK3_SIZE: "750G"
|
||||||
|
volumes:
|
||||||
|
- ./example2:/storage2
|
||||||
|
- ./example3:/storage3
|
||||||
|
```
|
||||||
|
|
||||||
* ### How do I verify if my system supports KVM?
|
### How do I pass through a disk?
|
||||||
|
|
||||||
To verify if your system supports KVM, run the following commands:
|
You can pass through disk devices or partitions directly by adding them to your compose file in this way:
|
||||||
|
|
||||||
```bash
|
```yaml
|
||||||
sudo apt install cpu-checker
|
devices:
|
||||||
sudo kvm-ok
|
- /dev/sdb:/disk1
|
||||||
```
|
- /dev/sdc1:/disk2
|
||||||
|
```
|
||||||
|
|
||||||
If you receive an error from `kvm-ok` indicating that KVM acceleration can't be used, check your BIOS settings.
|
Make sure it is totally empty (without any filesystem), otherwise DSM may not format it as a volume.
|
||||||
|
|
||||||
* ### How do I assign an individual IP address to the container?
|
### How do I change the amount of CPU or RAM?
|
||||||
|
|
||||||
By default, the container uses bridge networking, which shares the IP address with the host.
|
By default, Virtual DSM will be allowed to use 2 CPU cores and 2 GB of RAM.
|
||||||
|
|
||||||
If you want to assign an individual IP address to the container, you can create a macvlan network as follows:
|
If you want to adjust this, you can specify the desired amount using the following environment variables:
|
||||||
|
|
||||||
```bash
|
```yaml
|
||||||
docker network create -d macvlan \
|
environment:
|
||||||
--subnet=192.168.0.0/24 \
|
RAM_SIZE: "4G"
|
||||||
--gateway=192.168.0.1 \
|
CPU_CORES: "4"
|
||||||
--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:
|
### How do I verify if my system supports KVM?
|
||||||
|
|
||||||
```yaml
|
First check if your software is compatible using this chart:
|
||||||
services:
|
|
||||||
dsm:
|
|
||||||
container_name: dsm
|
|
||||||
..<snip>..
|
|
||||||
networks:
|
|
||||||
vdsm:
|
|
||||||
ipv4_address: 192.168.0.100
|
|
||||||
|
|
||||||
networks:
|
| **Product** | **Linux** | **Win11** | **Win10** | **macOS** |
|
||||||
vdsm:
|
|---|---|---|---|---|
|
||||||
external: true
|
| Docker CLI | ✅ | ✅ | ❌ | ❌ |
|
||||||
```
|
| Docker Desktop | ❌ | ✅ | ❌ | ❌ |
|
||||||
|
| Podman CLI | ✅ | ✅ | ❌ | ❌ |
|
||||||
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.
|
| Podman Desktop | ✅ | ✅ | ❌ | ❌ |
|
||||||
|
|
||||||
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.
|
After that you can run the following commands in Linux to check your system:
|
||||||
|
|
||||||
* ### How can the container acquire an IP address from my router?
|
```bash
|
||||||
|
sudo apt install cpu-checker
|
||||||
|
sudo kvm-ok
|
||||||
|
```
|
||||||
|
|
||||||
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.
|
If you receive an error from `kvm-ok` indicating that KVM cannot be used, please check whether:
|
||||||
|
|
||||||
To enable this feature, add the following lines to your compose file:
|
- the virtualization extensions (`Intel VT-x` or `AMD SVM`) are enabled in your BIOS.
|
||||||
|
|
||||||
```yaml
|
- you enabled "nested virtualization" if you are running the container inside a virtual machine.
|
||||||
environment:
|
|
||||||
DHCP: "Y"
|
|
||||||
devices:
|
|
||||||
- /dev/vhost-net
|
|
||||||
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.
|
- you are not using a cloud provider, as most of them do not allow nested virtualization for their VPSs.
|
||||||
|
|
||||||
* ### How do I passthrough the GPU?
|
If you did not receive any error from `kvm-ok` but the container still complains about a missing KVM device, it could help to add `privileged: true` to your compose file (or `sudo` to your `docker` command) to rule out any permission issue.
|
||||||
|
|
||||||
To passthrough your Intel GPU, add the following lines to your compose file:
|
### 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:
|
|
||||||
GPU: "Y"
|
|
||||||
devices:
|
|
||||||
- /dev/dri
|
|
||||||
```
|
|
||||||
|
|
||||||
This can be used to enable the facial recognition function in Synology Photos for example.
|
If you want to assign an individual IP address to the container, you can create a macvlan network as follows:
|
||||||
|
|
||||||
* ### How do I install a specific version of vDSM?
|
```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.
|
||||||
|
|
||||||
By default, version 7.2.1 will be installed, but if you prefer an older version, you can add its download URL to your compose file as follows:
|
Once you have created the network, change your compose file to look as follows:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
environment:
|
services:
|
||||||
URL: "https://global.synologydownload.com/download/DSM/release/7.0.1/42218/DSM_VirtualDSM_42218.pat"
|
dsm:
|
||||||
```
|
container_name: dsm
|
||||||
|
..<snip>..
|
||||||
|
networks:
|
||||||
|
vdsm:
|
||||||
|
ipv4_address: 192.168.0.100
|
||||||
|
|
||||||
With this method, it is even possible to switch between different versions while keeping all your file data intact.
|
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.
|
||||||
|
|
||||||
* ### What are the differences compared to the standard DSM?
|
> [!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.
|
||||||
|
|
||||||
There are only two minor differences: the Virtual Machine Manager package is not available, and Surveillance Station will not include any free licenses.
|
### How can DSM acquire an IP address from my router?
|
||||||
|
|
||||||
* ### 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.
|
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.
|
||||||
|
|
||||||
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
|
To enable this mode, in which the container and DSM will have separate IP addresses, add the following lines to your compose file:
|
||||||
|
|
||||||
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.
|
```yaml
|
||||||
|
environment:
|
||||||
|
DHCP: "Y"
|
||||||
|
devices:
|
||||||
|
- /dev/vhost-net
|
||||||
|
device_cgroup_rules:
|
||||||
|
- 'c *:* rwm'
|
||||||
|
```
|
||||||
|
|
||||||
|
### How do I pass through the GPU?
|
||||||
|
|
||||||
|
To pass through your Intel GPU, add the following lines to your compose file:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
environment:
|
||||||
|
GPU: "Y"
|
||||||
|
devices:
|
||||||
|
- /dev/dri
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> This can be used to enable the facial recognition function in Synology Photos, but does not provide hardware transcoding for video.
|
||||||
|
|
||||||
|
### How do I install a specific version of vDSM?
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
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 back and forth between versions while keeping your file data intact.
|
||||||
|
|
||||||
|
Alternatively, you can also skip the download and use a local file instead, by binding it in your compose file in this way:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
volumes:
|
||||||
|
- ./DSM_VirtualDSM_42218.pat:/boot.pat
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace the example path `./DSM_VirtualDSM_42218.pat` with the filename of your desired `.pat` file. The value of `URL` will be ignored in this case.
|
||||||
|
|
||||||
|
### 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
|
||||||
|
|||||||
+27
-6
@@ -1,16 +1,37 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -Eeuo pipefail
|
set -Eeuo pipefail
|
||||||
|
|
||||||
[ ! -f "/run/qemu.pid" ] && echo "QEMU not running yet.." && exit 0
|
cd /run
|
||||||
[ -f "/run/qemu.count" ] && echo "QEMU is shutting down.." && exit 1
|
. utils.sh # Load functions
|
||||||
|
|
||||||
file="/run/dsm.url"
|
: "${DHCP:="N"}"
|
||||||
[ ! -f "$file" ] && echo "DSM has not enabled networking yet.." && exit 1
|
: "${NETWORK:="Y"}"
|
||||||
|
|
||||||
location=$(cat "$file")
|
[ -f "/run/shm/qemu.end" ] && echo "QEMU is shutting down..." && exit 1
|
||||||
|
[ ! -s "/run/shm/qemu.pid" ] && echo "QEMU is not running yet..." && exit 0
|
||||||
|
disabled "$NETWORK" && echo "Networking is disabled." && exit 0
|
||||||
|
|
||||||
|
file="/run/shm/dsm.url"
|
||||||
|
address="/run/shm/qemu.ip"
|
||||||
|
gateway="/run/shm/qemu.gw"
|
||||||
|
|
||||||
|
[ ! -s "$file" ] && echo "DSM has not enabled networking yet..." && exit 1
|
||||||
|
|
||||||
|
location=$(<"$file")
|
||||||
|
|
||||||
if ! curl -m 20 -ILfSs "http://$location/" > /dev/null; then
|
if ! curl -m 20 -ILfSs "http://$location/" > /dev/null; then
|
||||||
echo "Failed to reach page at http://$location" && exit 1
|
|
||||||
|
if enabled "$DHCP"; then
|
||||||
|
ip=$(<"$address")
|
||||||
|
echo "Failed to reach DSM at http://$location"
|
||||||
|
else
|
||||||
|
ip=$(<"$gateway")
|
||||||
|
port="${location##*:}"
|
||||||
|
echo "Failed to reach DSM at port $port"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "You might need to whitelist IP $ip in the DSM firewall." && exit 1
|
||||||
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Healthcheck OK"
|
echo "Healthcheck OK"
|
||||||
|
|||||||
+9
-13
@@ -1,20 +1,16 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -Eeuo pipefail
|
set -Eeuo pipefail
|
||||||
|
|
||||||
DEF_OPTS="-nographic -nodefaults -boot strict=on -display none"
|
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')
|
RAM_OPTS=$(echo "-m ${RAM_SIZE^^}" | sed 's/MB/M/g;s/GB/G/g;s/TB/T/g')
|
||||||
CPU_OPTS="-cpu $CPU_MODEL -smp $CPU_CORES,sockets=1,dies=1,cores=$CPU_CORES,threads=1"
|
MON_OPTS="-name $PROCESS,process=$PROCESS,debug-threads=on -pidfile $QEMU_PID"
|
||||||
MAC_OPTS="-machine type=q35,usb=off,dump-guest-core=off,hpet=off${KVM_OPTS}"
|
CPU_OPTS="-cpu $CPU_FLAGS -smp $CPU_CORES,sockets=1,dies=1,cores=$CPU_CORES,threads=1"
|
||||||
EXTRA_OPTS="-device virtio-balloon-pci,id=balloon0,bus=pcie.0,addr=0x4"
|
MAC_OPTS="-machine type=q35,smm=off,usb=off,vmport=off,dump-guest-core=off,hpet=off${KVM_OPTS}"
|
||||||
EXTRA_OPTS="$EXTRA_OPTS -object rng-random,id=objrng0,filename=/dev/urandom"
|
DEV_OPTS="-device virtio-balloon-pci,id=balloon0,bus=pcie.0,addr=0x4"
|
||||||
EXTRA_OPTS="$EXTRA_OPTS -device virtio-rng-pci,rng=objrng0,id=rng0,bus=pcie.0,addr=0x1c"
|
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"
|
||||||
|
|
||||||
if [[ "$GPU" == [Yy1]* ]] && [[ "$ARCH" == "amd64" ]]; then
|
ARGS="$DEF_OPTS $CPU_OPTS $RAM_OPTS $MAC_OPTS $DISPLAY_OPTS $MON_OPTS $SERIAL_OPTS $NET_OPTS $DISK_OPTS $DEV_OPTS $ARGUMENTS"
|
||||||
DEF_OPTS="-nodefaults -boot strict=on -display egl-headless,rendernode=/dev/dri/renderD128"
|
|
||||||
DEF_OPTS="$DEF_OPTS -device virtio-vga,id=video0,max_outputs=1,bus=pcie.0,addr=0x1"
|
|
||||||
fi
|
|
||||||
|
|
||||||
ARGS="$DEF_OPTS $CPU_OPTS $RAM_OPTS $MAC_OPTS $MON_OPTS $SERIAL_OPTS $NET_OPTS $DISK_OPTS $EXTRA_OPTS $ARGUMENTS"
|
|
||||||
ARGS=$(echo "$ARGS" | sed 's/\t/ /g' | tr -s ' ')
|
ARGS=$(echo "$ARGS" | sed 's/\t/ /g' | tr -s ' ')
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
-61
@@ -1,61 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -Eeuo pipefail
|
|
||||||
|
|
||||||
# Docker environment variables
|
|
||||||
|
|
||||||
: ${HOST_CPU:=''}
|
|
||||||
: ${CPU_MODEL:='host'}
|
|
||||||
: ${CPU_FEATURES:='+ssse3,+sse4.1,+sse4.2'}
|
|
||||||
|
|
||||||
KVM_ERR=""
|
|
||||||
KVM_OPTS=""
|
|
||||||
|
|
||||||
if [[ "$ARCH" == "amd64" && "$KVM" != [Nn]* ]]; then
|
|
||||||
|
|
||||||
if [ -e /dev/kvm ] && sh -c 'echo -n > /dev/kvm' &> /dev/null; then
|
|
||||||
if ! grep -q -e vmx -e svm /proc/cpuinfo; then
|
|
||||||
KVM_ERR="(vmx/svm disabled)"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
[ -e /dev/kvm ] && KVM_ERR="(no write access)" || KVM_ERR="(device file missing)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "$KVM_ERR" ]; then
|
|
||||||
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 skip this error by setting KVM=N (not recommended)."
|
|
||||||
[[ "$DEBUG" != [Yy1]* ]] && exit 88
|
|
||||||
[[ "$CPU_MODEL" == "host"* ]] && CPU_MODEL="max,$CPU_FEATURES"
|
|
||||||
else
|
|
||||||
KVM_OPTS=",accel=kvm -enable-kvm"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "$KVM_OPTS" ]; then
|
|
||||||
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
|
|
||||||
|
|
||||||
[[ "$CPU_MODEL" == "host"* ]] && CPU_MODEL="max,$CPU_FEATURES"
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$HOST_CPU" ]; then
|
|
||||||
HOST_CPU=$(lscpu | grep 'Model name' | cut -f 2 -d ":" | awk '{$1=$1}1' | sed 's# @.*##g' | sed s/"(R)"//g | sed 's/[^[:alnum:] ]\+/ /g' | sed 's/ */ /g')
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "$HOST_CPU" ]; then
|
|
||||||
HOST_CPU="${HOST_CPU%%,*},,"
|
|
||||||
else
|
|
||||||
HOST_CPU="QEMU, Virtual CPU,"
|
|
||||||
if [ "$ARCH" == "amd64" ]; then
|
|
||||||
HOST_CPU="$HOST_CPU X86_64"
|
|
||||||
else
|
|
||||||
HOST_CPU="$HOST_CPU $ARCH"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
return 0
|
|
||||||
+597
-251
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,54 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
# Docker environment variables
|
||||||
|
|
||||||
|
: "${GPU:="N"}" # GPU passthrough
|
||||||
|
: "${VGA:="virtio"}" # VGA adaptor
|
||||||
|
: "${DISPLAY:="none"}" # Display type
|
||||||
|
: "${RENDERNODE:="/dev/dri/renderD128"}" # Render node
|
||||||
|
|
||||||
|
# Sanitize variables
|
||||||
|
VGA=$(strip "$VGA")
|
||||||
|
DISPLAY=$(strip "$DISPLAY")
|
||||||
|
RENDERNODE=$(strip "$RENDERNODE")
|
||||||
|
|
||||||
|
CPU_VENDOR=$(lscpu | awk '/Vendor ID/{print $3}')
|
||||||
|
|
||||||
|
if ! enabled "$GPU" || [[ "$CPU_VENDOR" != "GenuineIntel" || "$ARCH" != "amd64" ]]; then
|
||||||
|
|
||||||
|
[[ "${DISPLAY,,}" == "none" ]] && VGA="none"
|
||||||
|
DISPLAY_OPTS="-display $DISPLAY -vga $VGA"
|
||||||
|
return 0
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
msg="Configuring display drivers..."
|
||||||
|
html "$msg"
|
||||||
|
enabled "$DEBUG" && echo "$msg"
|
||||||
|
|
||||||
|
DISPLAY_OPTS="-display egl-headless,rendernode=$RENDERNODE"
|
||||||
|
DISPLAY_OPTS+=" -vga $VGA"
|
||||||
|
|
||||||
|
[ ! -d /dev/dri ] && mkdir -m 755 /dev/dri
|
||||||
|
|
||||||
|
# Extract the card number from the render node
|
||||||
|
CARD_NUMBER=$(echo "$RENDERNODE" | grep -oP '(?<=renderD)\d+')
|
||||||
|
CARD_DEVICE="/dev/dri/card$((CARD_NUMBER - 128))"
|
||||||
|
|
||||||
|
if [ ! -c "$CARD_DEVICE" ]; then
|
||||||
|
if mknod "$CARD_DEVICE" c 226 $((CARD_NUMBER - 128)); then
|
||||||
|
chmod 666 "$CARD_DEVICE"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -c "$RENDERNODE" ]; then
|
||||||
|
if mknod "$RENDERNODE" c 226 "$CARD_NUMBER"; then
|
||||||
|
chmod 666 "$RENDERNODE"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
addPackage "xserver-xorg-video-intel" "Intel GPU drivers"
|
||||||
|
addPackage "qemu-system-modules-opengl" "OpenGL module"
|
||||||
|
|
||||||
|
return 0
|
||||||
+27
-15
@@ -1,34 +1,46 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -Eeuo pipefail
|
set -Eeuo pipefail
|
||||||
|
|
||||||
echo "❯ Starting Virtual DSM for Docker v$(</run/version)..."
|
: "${PLATFORM:="x64"}"
|
||||||
echo "❯ For support visit 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
|
||||||
. network.sh # Initialize network
|
. network.sh # Initialize network
|
||||||
. gpu.sh # Initialize graphics
|
. proc.sh # Initialize processor
|
||||||
. cpu.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 -pidfile "$QEMU_PID" ${ARGS:+ $ARGS}
|
version=$("${cmd[@]}" --version | awk 'NR==1 { print $4 }')
|
||||||
exit $?
|
info "Booting $APP using QEMU v$version..."
|
||||||
|
|
||||||
|
if ! enabled "$SHUTDOWN"; then
|
||||||
|
exec "${cmd[@]}" ${ARGS:+ $ARGS}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
set -m
|
if [ ! -t 1 ] || [ ! -c /dev/tty ]; then
|
||||||
(
|
"${cmd[@]}" ${ARGS:+ $ARGS} &
|
||||||
[[ "$DEBUG" == [Yy1]* ]] && info "$VERS" && set -x
|
else
|
||||||
qemu-system-x86_64 ${ARGS:+ $ARGS} & echo $! > "$QEMU_PID"
|
"${cmd[@]}" ${ARGS:+ $ARGS} </dev/tty >/dev/tty &
|
||||||
{ set +x; } 2>/dev/null
|
fi
|
||||||
)
|
|
||||||
set +m
|
|
||||||
|
|
||||||
tail --pid "$(cat "$QEMU_PID")" --follow /dev/null & wait $!
|
rc=0
|
||||||
|
wait $! || 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
|
||||||
-24
@@ -1,24 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -Eeuo pipefail
|
|
||||||
|
|
||||||
if [[ "$GPU" != [Yy1]* ]] || [[ "$ARCH" != "amd64" ]]; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
[ ! -d /dev/dri ] && mkdir -m 755 /dev/dri
|
|
||||||
|
|
||||||
if [ ! -c /dev/dri/card0 ]; then
|
|
||||||
mknod /dev/dri/card0 c 226 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ! -c /dev/dri/renderD128 ]; then
|
|
||||||
mknod /dev/dri/renderD128 c 226 128
|
|
||||||
fi
|
|
||||||
|
|
||||||
chmod 666 /dev/dri/card0
|
|
||||||
chmod 666 /dev/dri/renderD128
|
|
||||||
|
|
||||||
addPackage "xserver-xorg-video-intel" "Intel GPU drivers"
|
|
||||||
addPackage "qemu-system-modules-opengl" "OpenGL module"
|
|
||||||
|
|
||||||
return 0
|
|
||||||
+188
-185
@@ -1,24 +1,52 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -Eeuo pipefail
|
set -Eeuo pipefail
|
||||||
|
|
||||||
: ${URL:=''} # URL of the PAT file to be downloaded.
|
: "${URL:=""}" # URL of the PAT file to be downloaded.
|
||||||
: ${DEV:='Y'} # Controls whether device nodes are created.
|
|
||||||
|
|
||||||
if [ -f "$STORAGE"/dsm.ver ]; then
|
if [ -f "$STORAGE/dsm.ver" ]; then
|
||||||
BASE=$(cat "$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
|
||||||
|
|
||||||
[ -n "$URL" ] && BASE=$(basename "$URL" .pat)
|
FN="boot.pat"
|
||||||
|
DIR=$(find / -maxdepth 1 -type d -iname "$FN" -print -quit)
|
||||||
|
[ ! -d "$DIR" ] && DIR=$(find "$STORAGE" -maxdepth 1 -type d -iname "$FN" -print -quit)
|
||||||
|
|
||||||
if [[ -f "$STORAGE/$BASE.boot.img" ]] && [[ -f "$STORAGE/$BASE.system.img" ]]; then
|
if [ -d "$DIR" ]; then
|
||||||
|
BASE="DSM_VirtualDSM" && URL="file://$DIR"
|
||||||
|
if [[ ! -s "$STORAGE/$BASE.boot.img" || ! -s "$STORAGE/$BASE.system.img" ]]; then
|
||||||
|
error "The bind $DIR maps to a file that does not exist!" && exit 65
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
FILE=$(find / -maxdepth 1 -type f -iname "$FN" -print -quit)
|
||||||
|
[ ! -s "$FILE" ] && FILE=$(find "$STORAGE" -maxdepth 1 -type f -iname "$FN" -print -quit)
|
||||||
|
[ -s "$FILE" ] && BASE="DSM_VirtualDSM" && URL="file://$FILE"
|
||||||
|
|
||||||
|
URL=$(strip "$URL")
|
||||||
|
|
||||||
|
if [ -n "$URL" ] && [ ! -s "$FILE" ] && [ ! -d "$DIR" ]; then
|
||||||
|
BASE=$(basename "$URL" .pat)
|
||||||
|
if [ ! -s "$STORAGE/$BASE.system.img" ]; then
|
||||||
|
BASE=$(basename "${URL%%\?*}" .pat)
|
||||||
|
printf -v BASE '%b' "${BASE//%/\\x}"
|
||||||
|
BASE="${BASE//[!A-Za-z0-9._-]/_}"
|
||||||
|
fi
|
||||||
|
if [[ "${URL,,}" != "http"* && "${URL,,}" != "file:"* ]] ; then
|
||||||
|
[ ! -s "$STORAGE/$BASE.pat" ] && error "Invalid URL: $URL" && exit 65
|
||||||
|
URL="file://$STORAGE/$BASE.pat"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -s "$STORAGE/$BASE.boot.img" && -s "$STORAGE/$BASE.system.img" ]]; then
|
||||||
return 0 # Previous installation found
|
return 0 # Previous installation found
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Display wait message
|
html "Please wait while Virtual DSM is being installed..."
|
||||||
/run/server.sh 5000 install &
|
|
||||||
|
|
||||||
DL=""
|
DL=""
|
||||||
DL_CHINA="https://cndl.synology.cn/download/DSM"
|
DL_CHINA="https://cndl.synology.cn/download/DSM"
|
||||||
@@ -33,63 +61,70 @@ 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=$(basename "${URL%%\?*}" .pat)
|
||||||
|
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"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
rm -f "$STORAGE"/"$BASE".agent
|
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
|
||||||
MIN_ROOT=471859200
|
|
||||||
MIN_SPACE=6442450944
|
|
||||||
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
|
|
||||||
if [[ "$FS" == "btrfs"* ]]; then
|
|
||||||
FA=$(lsattr -d "$STORAGE")
|
|
||||||
if [[ "$FA" != *"C"* ]]; then
|
|
||||||
info "Warning: the filesystem of $STORAGE is BTRFS, and COW (copy on write) is not disabled for that folder!"
|
|
||||||
info "This will negatively affect write performance, please empty the folder and disable COW (chattr +C <path>)."
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$FS" != "fat"* && "$FS" != "vfat"* && "$FS" != "exfat"* && \
|
if [[ "${FS,,}" == "fuse"* ]]; then
|
||||||
"$FS" != "ntfs"* && "$FS" != "fuse"* && "$FS" != "msdos"* ]]; then
|
warn "the filesystem of $STORAGE is FUSE, this extra layer will negatively affect performance!"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${FS,,}" == "ecryptfs" || "${FS,,}" == "tmpfs" ]]; then
|
||||||
|
warn "the filesystem of $STORAGE is $FS, which does not support O_DIRECT mode, adjusting settings..."
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${FS,,}" == "fat"* || "${FS,,}" == "vfat"* || "${FS,,}" == "msdos"* ]]; then
|
||||||
|
error "Unable to install on $FS filesystems, please use a different filesystem for /storage." && exit 61
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${FS,,}" != "exfat"* && "${FS,,}" != "ntfs"* && "${FS,,}" != "unknown"* ]]; then
|
||||||
TMP="$STORAGE/tmp"
|
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
|
||||||
SPACE=$(df --output=avail -B 1 /tmp | tail -n 1)
|
SPACE=$(df --output=avail -B 1 /tmp | tail -n 1)
|
||||||
if (( MIN_SPACE > SPACE )); then
|
SPACE_MB=$(formatBytes "$SPACE")
|
||||||
TMP="$STORAGE/tmp"
|
if (( TMP_SPACE > SPACE )); then
|
||||||
info "Warning: the $FS filesystem of $STORAGE does not support UNIX permissions.."
|
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
|
||||||
SPACE=$(df --output=avail -B 1 / | tail -n 1)
|
SPACE=$(df --output=avail -B 1 / | tail -n 1)
|
||||||
(( MIN_ROOT > SPACE )) && error "Not enough free space in container root, need at least 450 MB available." && exit 96
|
SPACE_MB=$(formatBytes "$SPACE" "down")
|
||||||
|
(( ROOT_SPACE > SPACE )) && error "Not enough free space inside the container, have $SPACE_MB available but need at least 500 MB." && exit 96
|
||||||
|
|
||||||
SPACE=$(df --output=avail -B 1 "$TMP" | tail -n 1)
|
MIN_SPACE=15032385536
|
||||||
SPACE_GB=$(( (SPACE + 1073741823)/1073741824 ))
|
SPACE=$(df --output=avail -B 1 "$STORAGE" | tail -n 1)
|
||||||
(( MIN_SPACE > SPACE )) && error "Not enough free space for installation in $STORAGE, have $SPACE_GB GB available but need at least 6 GB." && exit 95
|
SPACE_GB=$(formatBytes "$SPACE")
|
||||||
|
(( MIN_SPACE > SPACE )) && error "Not enough free space for installation in $STORAGE, have $SPACE_GB available but need at least 14 GB." && exit 94
|
||||||
if [[ "$TMP" != "$STORAGE/tmp" ]]; then
|
|
||||||
SPACE=$(df --output=avail -B 1 "$STORAGE" | tail -n 1)
|
|
||||||
SPACE_GB=$(( (SPACE + 1073741823)/1073741824 ))
|
|
||||||
(( MIN_SPACE > SPACE )) && error "Not enough free space for installation in $STORAGE, have $SPACE_GB GB available but need at least 6 GB." && exit 94
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check if output is to interactive TTY
|
# Check if output is to interactive TTY
|
||||||
if [ -t 1 ]; then
|
if [ -t 1 ]; then
|
||||||
@@ -98,100 +133,50 @@ else
|
|||||||
PROGRESS="--progress=dot:giga"
|
PROGRESS="--progress=dot:giga"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Download the required files from the Synology website
|
if [[ "$URL" == "file://"* ]]; then
|
||||||
|
MSG="Copying DSM"
|
||||||
RDC="$STORAGE/dsm.rd"
|
ERR="Failed to copy ${URL:7}"
|
||||||
|
info "Install: Copying installation image..."
|
||||||
if [ ! -f "$RDC" ]; then
|
else
|
||||||
|
MSG="Downloading DSM"
|
||||||
info "Install: Downloading installer..."
|
ERR="Failed to download $URL"
|
||||||
|
info "Install: Downloading $BASE.pat..."
|
||||||
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
|
fi
|
||||||
|
|
||||||
if [ -f "$RDC" ]; then
|
html "$MSG..."
|
||||||
|
|
||||||
{ xz -dc <"$RDC" >"$TMP/rd" 2>/dev/null; rc=$?; } || :
|
|
||||||
(( rc != 1 )) && error "Failed to unxz $RDC, reason $rc" && exit 91
|
|
||||||
|
|
||||||
if [[ "$DEV" == [Nn]* ]]; then
|
|
||||||
# Exclude dev/ from cpio extract
|
|
||||||
{ (cd "$TMP" && cpio -it < "$TMP/rd" | grep -Ev 'dev/' | while read -r entry; do cpio -idm "$entry" < "$TMP/rd" 2>/dev/null; done); rc=$?; } || :
|
|
||||||
(( rc != 0 )) && error "Failed to extract $RDC, reason $rc" && exit 92
|
|
||||||
else
|
|
||||||
{ (cd "$TMP" && cpio -idm <"$TMP/rd" 2>/dev/null); rc=$?; } || :
|
|
||||||
(( rc != 0 )) && error "Failed to extract $RDC, reason $rc"
|
|
||||||
(( rc != 0 )) && error "If the container runs unprivileged, please set DEV=N to exclude device nodes." && exit 92
|
|
||||||
fi
|
|
||||||
|
|
||||||
mkdir -p /run/extract
|
|
||||||
for file in $TMP/usr/lib/libcurl.so.4 \
|
|
||||||
$TMP/usr/lib/libmbedcrypto.so.5 \
|
|
||||||
$TMP/usr/lib/libmbedtls.so.13 \
|
|
||||||
$TMP/usr/lib/libmbedx509.so.1 \
|
|
||||||
$TMP/usr/lib/libmsgpackc.so.2 \
|
|
||||||
$TMP/usr/lib/libsodium.so \
|
|
||||||
$TMP/usr/lib/libsynocodesign-ng-virtual-junior-wins.so.7 \
|
|
||||||
$TMP/usr/syno/bin/scemd; do
|
|
||||||
cp "$file" /run/extract/
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ "$ARCH" != "amd64" ]; then
|
|
||||||
mkdir -p /lib64/
|
|
||||||
cp "$TMP/usr/lib/libc.so.6" /lib64/
|
|
||||||
cp "$TMP/usr/lib/libpthread.so.0" /lib64/
|
|
||||||
cp "$TMP/usr/lib/ld-linux-x86-64.so.2" /lib64/
|
|
||||||
fi
|
|
||||||
|
|
||||||
mv /run/extract/scemd /run/extract/syno_extract_system_patch
|
|
||||||
chmod +x /run/extract/syno_extract_system_patch
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
rm -rf "$TMP" && mkdir -p "$TMP"
|
|
||||||
|
|
||||||
info "Install: Downloading $(basename "$URL")..."
|
|
||||||
|
|
||||||
PAT="/$BASE.pat"
|
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
|
[[ "${URL,,}" == *"_72806.pat" ]] && SIZE=361010261
|
||||||
|
[[ "${URL,,}" == *"_69057.pat" ]] && SIZE=363837333
|
||||||
|
[[ "${URL,,}" == *"_42218.pat" ]] && SIZE=379637760
|
||||||
|
|
||||||
|
/run/progress.sh "$PAT" "$SIZE" "$MSG ([P])..." &
|
||||||
|
|
||||||
|
{ wget "$URL" -O "$PAT" -q --no-check-certificate --timeout=10 --no-http-keep-alive --show-progress "$PROGRESS"; rc=$?; } || :
|
||||||
|
|
||||||
|
fKill "progress.sh"
|
||||||
|
|
||||||
|
(( rc == 3 )) && error "$ERR , cannot write file (disk full?)" && exit 69
|
||||||
|
(( rc == 4 )) && error "$ERR , network failure!" && exit 69
|
||||||
|
(( rc == 8 )) && error "$ERR , server issued an error response!" && exit 69
|
||||||
|
(( rc != 0 )) && error "$ERR , reason: $rc" && exit 69
|
||||||
|
|
||||||
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,62 +184,68 @@ 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 installation image..."
|
||||||
|
info "Install: $MSG" && html "$MSG"
|
||||||
|
|
||||||
if { tar tf "$PAT"; } >/dev/null 2>&1; then
|
if { tar tf "$PAT"; } >/dev/null 2>&1; then
|
||||||
|
|
||||||
info "Install: Extracting downloaded image..."
|
|
||||||
tar xpf "$PAT" -C "$TMP/."
|
tar xpf "$PAT" -C "$TMP/."
|
||||||
|
|
||||||
else
|
else
|
||||||
|
|
||||||
info "Install: Extracting downloaded image..."
|
{ (cd "$TMP" && python3 /run/extract.py -i "$PAT" -d 2>/run/extract.log); rc=$?; } || :
|
||||||
|
|
||||||
export LD_LIBRARY_PATH="/run/extract"
|
if (( rc != 0 )); then
|
||||||
|
cat /run/extract.log
|
||||||
if [ "$ARCH" == "amd64" ]; then
|
error "Failed to extract PAT file, reason $rc" && exit 63
|
||||||
{ /run/extract/syno_extract_system_patch "$PAT" "$TMP/."; rc=$?; } || :
|
|
||||||
else
|
|
||||||
{ 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
|
||||||
|
|
||||||
HDA="$TMP/hda1"
|
MSG="Preparing system partition..."
|
||||||
IDB="$TMP/indexdb"
|
info "Install: $MSG" && html "$MSG"
|
||||||
PKG="$TMP/packages"
|
|
||||||
HDP="$TMP/synohdpack_img"
|
|
||||||
|
|
||||||
[ ! -f "$HDA.tgz" ] && error "The PAT file contains no OS image." && exit 64
|
BOOT=$(find "$TMP" -name "*.bin.zip" -print -quit)
|
||||||
|
[ -z "$BOOT" ] && error "The PAT file contains no boot image." && exit 67
|
||||||
|
[ ! -s "$BOOT" ] && error "The PAT boot image archive is empty." && exit 67
|
||||||
|
|
||||||
BOOT=$(find "$TMP" -name "*.bin.zip")
|
unzip -q -o "$BOOT" -d "$TMP"
|
||||||
[ ! -f "$BOOT" ] && error "The PAT file contains no boot image." && exit 67
|
BOOT="${BOOT%.zip}"
|
||||||
|
|
||||||
BOOT=$(echo "$BOOT" | head -c -5)
|
SYSTEM="$STORAGE/$BASE.system.img"
|
||||||
unzip -q -o "$BOOT".zip -d "$TMP"
|
rm -f "$SYSTEM"
|
||||||
|
|
||||||
SYSTEM="$TMP/sys.img"
|
|
||||||
SYSTEM_SIZE=4954537983
|
|
||||||
|
|
||||||
# Check free diskspace
|
# Check free diskspace
|
||||||
SPACE=$(df --output=avail -B 1 "$TMP" | tail -n 1)
|
SYSTEM_SIZE=10738466816
|
||||||
SPACE_GB=$(( (SPACE + 1073741823)/1073741824 ))
|
SPACE=$(df --output=avail -B 1 "$STORAGE" | tail -n 1)
|
||||||
(( SYSTEM_SIZE > SPACE )) && error "Not enough free space to create a 4 GB system disk, have only $SPACE_GB GB available." && exit 97
|
SPACE_MB=$(formatBytes "$SPACE")
|
||||||
|
|
||||||
if ! fallocate -l "$SYSTEM_SIZE" "$SYSTEM"; then
|
if (( SYSTEM_SIZE > SPACE )); then
|
||||||
if ! truncate -s "$SYSTEM_SIZE" "$SYSTEM"; then
|
error "Not enough free space in $STORAGE to create a 10 GB system disk, have only $SPACE_MB available." && exit 97
|
||||||
rm -f "$SYSTEM" && error "Could not allocate a file for the system disk." && exit 98
|
fi
|
||||||
|
|
||||||
|
if ! touch "$SYSTEM"; then
|
||||||
|
error "Could not create file $SYSTEM for the system disk." && exit 98
|
||||||
|
fi
|
||||||
|
|
||||||
|
! setOwner "$SYSTEM" && error "Failed to set the owner for \"$SYSTEM\" !"
|
||||||
|
|
||||||
|
if [[ "${FS,,}" == "btrfs" ]]; then
|
||||||
|
{ chattr +C "$SYSTEM"; } || :
|
||||||
|
FA=$(lsattr "$SYSTEM")
|
||||||
|
if [[ "$FA" != *"C"* ]]; then
|
||||||
|
error "Failed to disable COW for system image $SYSTEM on ${FS^^} filesystem."
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if file exists
|
if ! fallocate -l "$SYSTEM_SIZE" "$SYSTEM" &>/dev/null; then
|
||||||
[ ! -f "$SYSTEM" ] && error "System disk does not exist ($SYSTEM)" && exit 99
|
if ! fallocate -l -x "$SYSTEM_SIZE" "$SYSTEM"; then
|
||||||
|
if ! truncate -s "$SYSTEM_SIZE" "$SYSTEM"; then
|
||||||
# Check the filesize
|
rm -f "$SYSTEM"
|
||||||
SIZE=$(stat -c%s "$SYSTEM")
|
error "Could not allocate file $SYSTEM for the system disk." && exit 98
|
||||||
[[ SIZE -ne SYSTEM_SIZE ]] && rm -f "$SYSTEM" && error "System disk has the wrong size: $SIZE" && exit 90
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
PART="$TMP/partition.fdisk"
|
PART="$TMP/partition.fdisk"
|
||||||
|
|
||||||
@@ -264,56 +255,68 @@ PART="$TMP/partition.fdisk"
|
|||||||
echo "unit: sectors"
|
echo "unit: sectors"
|
||||||
echo "sector-size: 512"
|
echo "sector-size: 512"
|
||||||
echo ""
|
echo ""
|
||||||
echo "${SYSTEM}1 : start= 2048, size= 4980480, type=83"
|
echo "${SYSTEM}1 : start= 2048, size= 16777216, type=83"
|
||||||
echo "${SYSTEM}2 : start= 4982528, size= 4194304, type=82"
|
echo "${SYSTEM}2 : start= 16779264, size= 4194304, type=82"
|
||||||
} > "$PART"
|
} > "$PART"
|
||||||
|
|
||||||
sfdisk -q "$SYSTEM" < "$PART"
|
sfdisk -q "$SYSTEM" < "$PART"
|
||||||
|
|
||||||
info "Install: Extracting system partition..."
|
|
||||||
|
|
||||||
MOUNT="$TMP/system"
|
MOUNT="$TMP/system"
|
||||||
rm -rf "$MOUNT" && mkdir -p "$MOUNT"
|
rm -rf "$MOUNT"
|
||||||
|
|
||||||
mv "$HDA.tgz" "$HDA.txz"
|
if ! makeDir "$MOUNT"; then
|
||||||
|
error "Failed to create directory \"$MOUNT\" !" && exit 93
|
||||||
if [[ "$DEV" == [Nn]* ]]; then
|
|
||||||
# Exclude dev/ from tar extract
|
|
||||||
tar xpfJ "$HDA.txz" --absolute-names --exclude="dev" -C "$MOUNT/"
|
|
||||||
else
|
|
||||||
tar xpfJ "$HDA.txz" --absolute-names -C "$MOUNT/"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
MSG="Extracting system partition..."
|
||||||
|
info "Install: $MSG" && html "$MSG"
|
||||||
|
|
||||||
|
HDA="$TMP/hda1"
|
||||||
|
IDB="$TMP/indexdb"
|
||||||
|
PKG="$TMP/packages"
|
||||||
|
HDP="$TMP/synohdpack_img"
|
||||||
|
|
||||||
|
[ ! -s "$HDA.tgz" ] && error "The PAT file contains no OS image." && exit 64
|
||||||
|
mv "$HDA.tgz" "$HDA.txz"
|
||||||
|
|
||||||
[ -d "$PKG" ] && mv "$PKG/" "$MOUNT/.SynoUpgradePackages/"
|
[ -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"
|
||||||
[ -f "$IDB.txz" ] && tar xpfJ "$IDB.txz" --absolute-names -C "$MOUNT/usr/syno/synoman/indexdb/"
|
|
||||||
|
|
||||||
info "Install: Installing system partition..."
|
if [ -s "$IDB.txz" ]; then
|
||||||
|
mkdir -p "$INDEX_DB"
|
||||||
|
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..."
|
||||||
|
|
||||||
mke2fs -q -t ext4 -b 4096 -d "$MOUNT/" -L "$LABEL" -F -E "offset=$OFFSET" "$SYSTEM" "$NUMBLOCKS"
|
fakeroot -- bash -c "set -Eeu;\
|
||||||
|
[ -s $HDP.txz ] && tar xpfJ $HDP.txz --absolute-names -C $MOUNT/;\
|
||||||
|
[ -s $IDB.txz ] && tar xpfJ $IDB.txz --absolute-names -C $INDEX_DB/;\
|
||||||
|
tar xpfJ $HDA.txz --absolute-names --skip-old-files -C $MOUNT/;\
|
||||||
|
printf '%b%s%b' '\E[1;34m❯ \E[1;36m' 'Install: $MSG' '\E[0m\n';\
|
||||||
|
mke2fs -q -t ext4 -b 4096 -d $MOUNT/ -L $LABEL -F -E offset=$OFFSET $SYSTEM $NUMBLOCKS"
|
||||||
|
|
||||||
rm -rf "$MOUNT"
|
rm -rf "$MOUNT"
|
||||||
|
echo "$BASE" > "$STORAGE/dsm.ver"
|
||||||
echo "$BASE" > "$STORAGE"/dsm.ver
|
! setOwner "$STORAGE/dsm.ver" && error "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"
|
||||||
else
|
else
|
||||||
mv -f "$PAT" "$STORAGE"/"$BASE".pat
|
mv -f "$PAT" "$STORAGE/$BASE.pat"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
mv -f "$BOOT" "$STORAGE"/"$BASE".boot.img
|
if [ -f "$STORAGE/$BASE.pat" ]; then
|
||||||
mv -f "$SYSTEM" "$STORAGE"/"$BASE".system.img
|
! setOwner "$STORAGE/$BASE.pat" && error "Failed to set the owner for \"$STORAGE/$BASE.pat\" !"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mv -f "$BOOT" "$STORAGE/$BASE.boot.img"
|
||||||
|
! setOwner "$STORAGE/$BASE.boot.img" && error "Failed to set the owner for \"$STORAGE/$BASE.boot.img\" !"
|
||||||
|
|
||||||
rm -rf "$TMP"
|
rm -rf "$TMP"
|
||||||
|
|
||||||
{ set +x; } 2>/dev/null
|
|
||||||
[[ "$DEBUG" == [Yy1]* ]] && echo
|
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
+111
@@ -0,0 +1,111 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
msg="Checking memory..."
|
||||||
|
enabled "$DEBUG" && echo "$msg"
|
||||||
|
|
||||||
|
RAM_AVAIL=$(free -b | grep -m 1 Mem: | awk '{print $7}')
|
||||||
|
AVAIL_MEM=$(formatBytes "$RAM_AVAIL")
|
||||||
|
|
||||||
|
checkConfiguredMemory() {
|
||||||
|
|
||||||
|
local wanted msg
|
||||||
|
|
||||||
|
if disabled "$RAM_CHECK" || [[ "${RAM_SIZE,,}" == "max" || "${RAM_SIZE,,}" == "half" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
wanted=$(numfmt --from=iec "$RAM_SIZE")
|
||||||
|
|
||||||
|
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 the virtual machine."
|
||||||
|
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 the virtual machine."
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
checkConfiguredMemory
|
||||||
|
configureHalfMemory
|
||||||
|
configureMaxMemory
|
||||||
|
checkMinimumMemory
|
||||||
|
|
||||||
|
return 0
|
||||||
+1439
-143
File diff suppressed because it is too large
Load Diff
+217
-48
@@ -1,40 +1,133 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -Eeuo pipefail
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
: "${SHUTDOWN:="Y"}" # Graceful ACPI shutdown
|
||||||
|
: "${TIMEOUT:="115"}" # QEMU termination timeout
|
||||||
|
: "${API_TIMEOUT:="90"}" # External API call timeout
|
||||||
|
|
||||||
# Configure QEMU for graceful shutdown
|
# Configure QEMU for graceful shutdown
|
||||||
|
|
||||||
QEMU_PORT=7100
|
API_CMD=6
|
||||||
QEMU_TIMEOUT=55
|
API_HOST="127.0.0.1:$COM_PORT"
|
||||||
QEMU_PID="/run/qemu.pid"
|
|
||||||
QEMU_COUNT="/run/qemu.count"
|
|
||||||
|
|
||||||
rm -f "$QEMU_PID"
|
# Configure QEMU for graceful shutdown
|
||||||
rm -f "$QEMU_COUNT"
|
|
||||||
|
|
||||||
_trap(){
|
QEMU_END="$QEMU_DIR/qemu.end"
|
||||||
func="$1" ; shift
|
|
||||||
for sig ; do
|
_trap() {
|
||||||
trap "$func $sig" "$sig"
|
|
||||||
done
|
local func="$1"; shift
|
||||||
|
local sig
|
||||||
|
TRAP_PID=$BASHPID
|
||||||
|
|
||||||
|
for sig; do
|
||||||
|
trap "$func $sig" "$sig"
|
||||||
|
done
|
||||||
|
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
_graceful_shutdown() {
|
signalCode() {
|
||||||
|
|
||||||
set +e
|
local sig="$1"
|
||||||
local cnt response
|
|
||||||
|
|
||||||
[ ! -f "$QEMU_PID" ] && exit 130
|
case "$sig" in
|
||||||
[ -f "$QEMU_COUNT" ] && return
|
SIGHUP) echo 129 ;;
|
||||||
|
SIGINT) echo 130 ;;
|
||||||
|
SIGQUIT) echo 131 ;;
|
||||||
|
SIGABRT) echo 134 ;;
|
||||||
|
SIGTERM) echo 143 ;;
|
||||||
|
*) echo 0 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
echo 0 > "$QEMU_COUNT"
|
return 0
|
||||||
echo && info "Received $1 signal, sending shutdown command..."
|
}
|
||||||
|
|
||||||
|
displayReason() {
|
||||||
|
|
||||||
|
local reason="$1"
|
||||||
|
|
||||||
|
case "$reason" in
|
||||||
|
129 ) echo "SIGHUP" ;;
|
||||||
|
130 ) echo "SIGINT" ;;
|
||||||
|
131 ) echo "SIGQUIT" ;;
|
||||||
|
134 ) echo "SIGABRT" ;;
|
||||||
|
143 ) echo "SIGTERM" ;;
|
||||||
|
* ) echo "$reason" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
readQemuPid() {
|
||||||
|
|
||||||
|
local -n _pid="$1"
|
||||||
|
|
||||||
|
if [ ! -s "$QEMU_PID" ] || ! read -r _pid <"$QEMU_PID"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
forceKillQemu() {
|
||||||
|
|
||||||
|
local reason="$1"
|
||||||
|
local pid=""
|
||||||
|
local 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:-}" \
|
||||||
|
"${WEB_PID:-}" "${PASST_PID:-}" "${DNSMASQ_PID:-}" )
|
||||||
|
|
||||||
|
mKill "${pids[@]}"
|
||||||
|
fKill "print.sh"
|
||||||
|
|
||||||
|
closeNetwork
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
finish() {
|
||||||
|
|
||||||
|
local reason=$1
|
||||||
|
touch "$QEMU_END"
|
||||||
|
|
||||||
|
forceKillQemu "$reason"
|
||||||
|
cleanupHelpers
|
||||||
|
|
||||||
|
if ! waitPidFile "$QEMU_PID" 10; then
|
||||||
|
warn "Timed out while waiting for $(app) to exit!"
|
||||||
|
fi
|
||||||
|
|
||||||
|
(( reason != 1 )) && echo && echo "❯ Shutdown completed!"
|
||||||
|
|
||||||
|
exit "$reason"
|
||||||
|
}
|
||||||
|
|
||||||
|
sendGuestShutdown() {
|
||||||
|
|
||||||
|
local pid="$1"
|
||||||
|
local response
|
||||||
|
local url
|
||||||
|
|
||||||
# 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://127.0.0.1:2210/read?command=6&timeout=50"
|
API_TIMEOUT=$(strip "$API_TIMEOUT")
|
||||||
response=$(curl -sk -m 52 -S "$url" 2>&1)
|
url="http://$API_HOST/read?command=$API_CMD&timeout=$API_TIMEOUT"
|
||||||
|
response=$(curl -sk -m "$(( API_TIMEOUT+2 ))" -S "$url" 2>&1)
|
||||||
|
|
||||||
if [[ "$response" =~ "\"success\"" ]]; then
|
if [[ "$response" =~ "\"success\"" ]]; then
|
||||||
|
|
||||||
@@ -43,46 +136,122 @@ _graceful_shutdown() {
|
|||||||
else
|
else
|
||||||
|
|
||||||
response="${response#*message\"\: \"}"
|
response="${response#*message\"\: \"}"
|
||||||
echo && error "Failed to send shutdown command: ${response%%\"*}"
|
[ -z "$response" ] && response="second signal"
|
||||||
|
|
||||||
kill -15 "$(cat "$QEMU_PID")"
|
echo && error "Forcefully terminating because of: ${response%%\"*}"
|
||||||
pkill -f qemu-system-x86_64 || true
|
kill -15 -- "$pid" 2>/dev/null || :
|
||||||
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
while [ "$(cat $QEMU_COUNT)" -lt "$QEMU_TIMEOUT" ]; do
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
# Increase the counter
|
normalizeTimeout() {
|
||||||
echo $(($(cat $QEMU_COUNT)+1)) > "$QEMU_COUNT"
|
|
||||||
|
|
||||||
# Try to connect to qemu
|
local term_grace=3 # seconds before loop ends to send SIGTERM
|
||||||
if echo 'info version'| nc -q 1 -w 1 localhost "$QEMU_PORT" >/dev/null 2>&1 ; then
|
local cleanup_grace=3 # seconds reserved after the loop for cleanup
|
||||||
|
local elapsed
|
||||||
|
local timeout_left
|
||||||
|
local min
|
||||||
|
|
||||||
sleep 1
|
TIMEOUT=$(strip "$TIMEOUT")
|
||||||
|
if [[ ! "$TIMEOUT" =~ ^[0-9]+$ ]]; then
|
||||||
|
TIMEOUT=115
|
||||||
|
fi
|
||||||
|
|
||||||
cnt="$(cat $QEMU_COUNT)/$QEMU_TIMEOUT"
|
if (( TIMEOUT >= 30 )); then
|
||||||
[[ "$DEBUG" == [Yy1]* ]] && info "Shutting down, waiting... ($cnt)"
|
term_grace=5
|
||||||
|
cleanup_grace=5
|
||||||
|
elif (( TIMEOUT >= 15 )); then
|
||||||
|
term_grace=4
|
||||||
|
cleanup_grace=4
|
||||||
|
fi
|
||||||
|
|
||||||
|
elapsed=$((SECONDS - start))
|
||||||
|
timeout_left=$((TIMEOUT - elapsed))
|
||||||
|
|
||||||
|
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"
|
||||||
|
local slp
|
||||||
|
|
||||||
|
while (( cnt <= wait_until )); do
|
||||||
|
|
||||||
|
sleep 1 &
|
||||||
|
slp=$!
|
||||||
|
|
||||||
|
# Stop waiting if the process has exited
|
||||||
|
! isAlive "$pid" && break
|
||||||
|
|
||||||
|
# Workaround for stale/zombie QEMU pid file
|
||||||
|
[ ! -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
|
fi
|
||||||
|
|
||||||
|
wait "$slp"
|
||||||
|
(( cnt++ ))
|
||||||
|
|
||||||
done
|
done
|
||||||
|
|
||||||
if [ "$(cat $QEMU_COUNT)" -ge "$QEMU_TIMEOUT" ]; then
|
return 0
|
||||||
echo && error "Shutdown timeout reached, forcefully quitting.."
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo && echo "❯ Quitting..."
|
|
||||||
echo 'quit' | nc -q 1 -w 1 localhost "$QEMU_PORT" >/dev/null 2>&1 || true
|
|
||||||
|
|
||||||
{ pkill -f print.sh || true; } 2>/dev/null
|
|
||||||
{ pkill -f host.bin || true; } 2>/dev/null
|
|
||||||
|
|
||||||
closeNetwork
|
|
||||||
sleep 1
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_trap _graceful_shutdown SIGTERM SIGHUP SIGINT SIGABRT SIGQUIT
|
graceful_shutdown() {
|
||||||
|
|
||||||
MON_OPTS="-monitor telnet:localhost:$QEMU_PORT,server,nowait,nodelay"
|
local sig="$1"
|
||||||
|
local pid=""
|
||||||
|
local code=0
|
||||||
|
|
||||||
|
[[ $BASHPID != "$TRAP_PID" ]] && return
|
||||||
|
|
||||||
|
code=$(signalCode "$sig")
|
||||||
|
|
||||||
|
if [ -f "$QEMU_END" ]; then
|
||||||
|
echo && info "Received $1 signal while already shutting down..."
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
set +e
|
||||||
|
start=$SECONDS
|
||||||
|
touch "$QEMU_END"
|
||||||
|
echo && info "Received $1 signal, sending shutdown command..."
|
||||||
|
|
||||||
|
if ! readQemuPid pid; then
|
||||||
|
warn "QEMU PID file ($QEMU_PID) does not exist?"
|
||||||
|
finish "$code"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! 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"
|
||||||
|
|
||||||
|
_trap graceful_shutdown SIGTERM SIGHUP SIGABRT SIGQUIT
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|||||||
+167
-41
@@ -1,79 +1,205 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
# shellcheck disable=SC2329
|
||||||
set -Eeuo pipefail
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
: "${DHCP:="N"}"
|
||||||
|
: "${NETWORK:="Y"}"
|
||||||
|
|
||||||
|
cd /run
|
||||||
|
. utils.sh # Load functions
|
||||||
|
|
||||||
info () { printf "%b%s%b" "\E[1;34m❯ \E[1;36m" "$1" "\E[0m\n" >&2; }
|
info () { printf "%b%s%b" "\E[1;34m❯ \E[1;36m" "$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; }
|
||||||
|
|
||||||
file="/run/dsm.url"
|
disabled "$NETWORK" && exit 0
|
||||||
shutdown="/run/qemu.count"
|
|
||||||
|
file="/run/shm/dsm.url"
|
||||||
|
msgs="/run/shm/msg.html"
|
||||||
|
driver="/run/shm/qemu.nic"
|
||||||
|
page="/run/shm/index.html"
|
||||||
|
address="/run/shm/qemu.ip"
|
||||||
|
shutdown="/run/shm/qemu.end"
|
||||||
|
template="/var/www/index.html"
|
||||||
url="http://127.0.0.1:2210/read?command=10"
|
url="http://127.0.0.1:2210/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"
|
||||||
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 IP from guest VM
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
queryGuest() {
|
||||||
|
|
||||||
|
local rc
|
||||||
|
|
||||||
{ json=$(curl -m 20 -sk "$url"); rc=$?; } || :
|
{ json=$(curl -m 20 -sk "$url"); rc=$?; } || :
|
||||||
|
|
||||||
[ -f "$shutdown" ] && exit 1
|
exitIfShuttingDown
|
||||||
(( rc != 0 )) && error "Failed to connect to guest: curl error $rc" && continue
|
|
||||||
|
|
||||||
{ result=$(echo "$json" | jq -r '.status'); rc=$?; } || :
|
if (( rc != 0 )); then
|
||||||
(( rc != 0 )) && error "$jq_err $rc ( $json )" && continue
|
error "$curl_err $rc"
|
||||||
[[ "$result" == "null" ]] && error "$resp_err $json" && continue
|
return 1
|
||||||
|
|
||||||
if [[ "$result" != "success" ]] ; then
|
|
||||||
{ msg=$(echo "$json" | jq -r '.message'); rc=$?; } || :
|
|
||||||
error "Guest replied $result: $msg" && continue
|
|
||||||
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
|
|
||||||
[ -z "$ip" ] && continue
|
|
||||||
|
|
||||||
echo "$ip:$port" > $file
|
local query="$1"
|
||||||
|
local result
|
||||||
|
local rc
|
||||||
|
|
||||||
done
|
{ result=$(jq -r "$query" <<< "$json"); rc=$?; } || :
|
||||||
|
|
||||||
[ -f "$shutdown" ] && exit 1
|
if (( rc != 0 )); then
|
||||||
|
error "$jq_err $rc ( $json )"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
location=$(cat "$file")
|
if [[ "$result" == "null" ]]; then
|
||||||
|
error "$resp_err $json"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ "$location" != "20.20"* ]]; then
|
printf '%s\n' "$result"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
readGuestStatus() {
|
||||||
|
|
||||||
|
local result msg rc
|
||||||
|
|
||||||
|
result=$(readJsonField '.status') || return 1
|
||||||
|
|
||||||
|
if [[ "$result" != "success" ]]; then
|
||||||
|
{ msg=$(jq -r '.message // empty' <<< "$json"); 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
|
||||||
|
}
|
||||||
|
|
||||||
|
writeDhcpPage() {
|
||||||
|
|
||||||
|
local title body script html
|
||||||
|
|
||||||
msg="http://$location"
|
msg="http://$location"
|
||||||
|
title="<title>Virtual DSM</title>"
|
||||||
|
body="The location of DSM is <a href='http://$location'>http://$location</a>"
|
||||||
|
script="<script>setTimeout(function(){ window.location.assign('http://$location'); }, 3000);</script>"
|
||||||
|
|
||||||
else
|
html=$(<"$template")
|
||||||
|
html="${html/\[1\]/$title}"
|
||||||
|
html="${html/\[2\]/$script}"
|
||||||
|
html="${html/\[3\]/$body}"
|
||||||
|
html="${html/\[4\]/}"
|
||||||
|
html="${html/\[5\]/}"
|
||||||
|
|
||||||
ip=$(ip address show dev eth0 | grep inet | awk '/inet / { print $2 }' | cut -f1 -d/)
|
echo "$html" > "$page"
|
||||||
|
echo "$body" > "$msgs"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
buildStaticMessage() {
|
||||||
|
|
||||||
|
local nic ip port
|
||||||
|
|
||||||
|
nic=$(<"$driver")
|
||||||
|
ip=$(<"$address")
|
||||||
port="${location##*:}"
|
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
|
||||||
|
writeDhcpPage
|
||||||
|
else
|
||||||
|
buildStaticMessage
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "" >&2
|
printLoginMessage
|
||||||
info "-----------------------------------------------------------"
|
|
||||||
info " You can now login to DSM at $msg"
|
exit 0
|
||||||
info "-----------------------------------------------------------"
|
|
||||||
echo "" >&2
|
|
||||||
|
|||||||
+218
@@ -0,0 +1,218 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
# Docker environment variables
|
||||||
|
|
||||||
|
: "${HOST_CPU:=""}"
|
||||||
|
: "${CPU_FLAGS:=""}"
|
||||||
|
: "${CPU_MODEL:=""}"
|
||||||
|
|
||||||
|
HOST_CPU=$(strip "$HOST_CPU")
|
||||||
|
CPU_FLAGS=$(strip "$CPU_FLAGS")
|
||||||
|
CPU_MODEL=$(strip "$CPU_MODEL")
|
||||||
|
|
||||||
|
selectClocksource() {
|
||||||
|
|
||||||
|
CLOCKSOURCE="tsc"
|
||||||
|
[[ "${ARCH,,}" == "arm64" ]] && CLOCKSOURCE="arch_sys_counter"
|
||||||
|
CLOCK="/sys/devices/system/clocksource/clocksource0/current_clocksource"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
checkClocksource() {
|
||||||
|
|
||||||
|
local result
|
||||||
|
|
||||||
|
if [ ! -f "$CLOCK" ]; then
|
||||||
|
warn "file \"$CLOCK\" cannot be found?"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
result=$(<"$CLOCK")
|
||||||
|
result="${result//[![:print:]]/}"
|
||||||
|
|
||||||
|
case "${result,,}" in
|
||||||
|
"${CLOCKSOURCE,,}" ) ;;
|
||||||
|
"kvm-clock" ) info "Nested KVM virtualization detected.." ;;
|
||||||
|
"hyperv_clocksource_tsc_page" ) info "Nested Hyper-V virtualization detected.." ;;
|
||||||
|
"hpet" ) warn "unsupported clock source detected: '$result'. Please set host clock source to '$CLOCKSOURCE'." ;;
|
||||||
|
*) warn "unexpected clock source detected: '$result'. Please set host clock source to '$CLOCKSOURCE'." ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
checkSse42() {
|
||||||
|
|
||||||
|
if ! 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
|
||||||
|
|
||||||
|
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 grep -qw "svm" <<< "$flags"; then
|
||||||
|
|
||||||
|
# AMD processor
|
||||||
|
if grep -qw "tsc_scale" <<< "$flags"; then
|
||||||
|
CPU_FEATURES+=",+invtsc"
|
||||||
|
fi
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
|
# Intel processor
|
||||||
|
local vmx
|
||||||
|
vmx=$(sed -ne '/^vmx flags/s/^.*: //p' /proc/cpuinfo)
|
||||||
|
|
||||||
|
if grep -qw "tsc_scaling" <<< "$vmx"; then
|
||||||
|
CPU_FEATURES+=",+invtsc"
|
||||||
|
fi
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
configureKvm() {
|
||||||
|
|
||||||
|
configureKvmCpuModel
|
||||||
|
checkSse42
|
||||||
|
appendKvmInvtscFeature
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
configureTcgCpuModel() {
|
||||||
|
|
||||||
|
if [ -n "$CPU_MODEL" ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$ARCH" == "amd64" ]]; then
|
||||||
|
CPU_MODEL="max"
|
||||||
|
CPU_FEATURES+=",migratable=no"
|
||||||
|
else
|
||||||
|
CPU_MODEL="qemu64"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
configureTcg() {
|
||||||
|
|
||||||
|
KVM_OPTS=""
|
||||||
|
CPU_FEATURES="l3-cache=on,+hypervisor"
|
||||||
|
|
||||||
|
if [[ "$ARCH" == "amd64" ]]; then
|
||||||
|
KVM_OPTS=" -accel tcg,thread=multi"
|
||||||
|
fi
|
||||||
|
|
||||||
|
configureTcgCpuModel
|
||||||
|
CPU_FEATURES+=",+ssse3,+sse4.1,+sse4.2"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
extractHostCpuArgument() {
|
||||||
|
|
||||||
|
local args prefix suffix param
|
||||||
|
|
||||||
|
if [[ "$ARGUMENTS" == *"-cpu host,"* ]]; then
|
||||||
|
|
||||||
|
args="${ARGUMENTS} "
|
||||||
|
prefix="${args/-cpu host,*/}"
|
||||||
|
suffix="${args/*-cpu host,/}"
|
||||||
|
param="${suffix%% *}"
|
||||||
|
suffix="${suffix#* }"
|
||||||
|
args="${prefix}${suffix}"
|
||||||
|
ARGUMENTS="${args::-1}"
|
||||||
|
|
||||||
|
if [ -z "$CPU_FLAGS" ]; then
|
||||||
|
CPU_FLAGS="$param"
|
||||||
|
else
|
||||||
|
CPU_FLAGS+=",$param"
|
||||||
|
fi
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
|
if [[ "$ARGUMENTS" == *"-cpu host"* ]]; then
|
||||||
|
ARGUMENTS="${ARGUMENTS//-cpu host/}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
composeCpuFlags() {
|
||||||
|
|
||||||
|
if [ -z "$CPU_FLAGS" ]; then
|
||||||
|
if [ -z "$CPU_FEATURES" ]; then
|
||||||
|
CPU_FLAGS="$CPU_MODEL"
|
||||||
|
else
|
||||||
|
CPU_FLAGS="$CPU_MODEL,$CPU_FEATURES"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [ -z "$CPU_FEATURES" ]; then
|
||||||
|
CPU_FLAGS="$CPU_MODEL,$CPU_FLAGS"
|
||||||
|
else
|
||||||
|
CPU_FLAGS="$CPU_MODEL,$CPU_FEATURES,$CPU_FLAGS"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
configureHostCpuName() {
|
||||||
|
|
||||||
|
if [ -z "$HOST_CPU" ]; then
|
||||||
|
[[ "${CPU,,}" != "unknown" ]] && HOST_CPU="$CPU"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$HOST_CPU" ]; then
|
||||||
|
HOST_CPU="${HOST_CPU%%,*},,"
|
||||||
|
else
|
||||||
|
HOST_CPU="QEMU, Virtual CPU,"
|
||||||
|
if [ "$ARCH" == "amd64" ]; then
|
||||||
|
HOST_CPU+=" X86_64"
|
||||||
|
else
|
||||||
|
HOST_CPU+=" $ARCH"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
selectClocksource
|
||||||
|
checkClocksource
|
||||||
|
|
||||||
|
flags=$(sed -ne '/^flags/s/^.*: //p' /proc/cpuinfo)
|
||||||
|
|
||||||
|
if ! disabled "$KVM"; then
|
||||||
|
configureKvm
|
||||||
|
else
|
||||||
|
configureTcg
|
||||||
|
fi
|
||||||
|
|
||||||
|
extractHostCpuArgument
|
||||||
|
composeCpuFlags
|
||||||
|
configureHostCpuName
|
||||||
|
|
||||||
|
return 0
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
info="/run/shm/msg.html"
|
||||||
|
|
||||||
|
escape () {
|
||||||
|
local s
|
||||||
|
s=${1//&/\&}
|
||||||
|
s=${s//</\<}
|
||||||
|
s=${s//>/\>}
|
||||||
|
s=${s//'"'/\"}
|
||||||
|
printf -- %s "$s"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
path="$1"
|
||||||
|
total="$2"
|
||||||
|
body=$(escape "$3")
|
||||||
|
|
||||||
|
if [[ "$body" == *"..." ]]; then
|
||||||
|
body="<p class=\"loading\">${body::-3}</p>"
|
||||||
|
fi
|
||||||
|
|
||||||
|
while true
|
||||||
|
do
|
||||||
|
|
||||||
|
if [ ! -s "$path" ] && [ ! -d "$path" ]; then
|
||||||
|
bytes="0"
|
||||||
|
else
|
||||||
|
bytes=$(du -sb "$path" | cut -f1)
|
||||||
|
fi
|
||||||
|
|
||||||
|
if (( bytes > 4096 )); then
|
||||||
|
if [ -z "$total" ] || [[ "$total" == "0" ]] || [ "$bytes" -gt "$total" ]; then
|
||||||
|
size=$(numfmt --to=iec --suffix=B "$bytes" | sed -r 's/([A-Z])/ \1/')
|
||||||
|
else
|
||||||
|
size="$(echo "$bytes" "$total" | awk '{printf "%.1f", $1 * 100 / $2}')"
|
||||||
|
size="$size%"
|
||||||
|
fi
|
||||||
|
[[ "$size" != "0.0%" ]] && echo "${body//(\[P\])/($size)}"> "$info"
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep 1 & wait $!
|
||||||
|
|
||||||
|
done
|
||||||
+266
-84
@@ -1,107 +1,289 @@
|
|||||||
#!/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; }
|
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
# Docker environment variables
|
# Docker environment variables
|
||||||
|
|
||||||
: ${GPU:='N'} # Disable GPU passthrough
|
: "${TZ:=""}" # System timezone
|
||||||
: ${KVM:='Y'} # Enable KVM acceleration
|
: "${KVM:="Y"}" # KVM acceleration
|
||||||
: ${DEBUG:='N'} # Disable debugging mode
|
: "${DEBUG:="N"}" # Disable debugging mode
|
||||||
: ${COUNTRY:=''} # Country code for mirror
|
: "${COUNTRY:=""}" # Country code for mirror
|
||||||
: ${CONSOLE:='N'} # Disable console mode
|
: "${ALLOCATE:=""}" # Preallocate diskspace
|
||||||
: ${ALLOCATE:='Y'} # Preallocate diskspace
|
: "${ARGUMENTS:=""}" # Extra QEMU parameters
|
||||||
: ${ARGUMENTS:=''} # Extra QEMU parameters
|
: "${CPU_CORES:="2"}" # Amount of CPU cores
|
||||||
: ${CPU_CORES:='1'} # Amount of CPU cores
|
: "${RAM_SIZE:="2G"}" # Maximum RAM amount
|
||||||
: ${RAM_SIZE:='1G'} # Maximum RAM amount
|
: "${RAM_CHECK:="Y"}" # Check available RAM
|
||||||
: ${DISK_SIZE:='16G'} # Initial data disk size
|
: "${DISK_SIZE:="16G"}" # Initial data disk size
|
||||||
|
: "${STORAGE:="/storage"}" # Storage folder location
|
||||||
|
|
||||||
|
detectEngine() {
|
||||||
|
|
||||||
|
if [ -f "/run/.containerenv" ]; then
|
||||||
|
ENGINE="${container:-}"
|
||||||
|
if [[ "${ENGINE,,}" == *"podman"* ]]; then
|
||||||
|
ROOTLESS="Y"
|
||||||
|
ENGINE="Podman"
|
||||||
|
else
|
||||||
|
[ -z "$ENGINE" ] && ENGINE="Kubernetes"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
checkPrivileged() {
|
||||||
|
|
||||||
|
local cap_bnd
|
||||||
|
local last_cap
|
||||||
|
local max_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
|
||||||
|
max_cap=$(((1 << (last_cap + 1)) - 1))
|
||||||
|
|
||||||
|
if [ "$cap_bnd" -eq "$max_cap" ]; then
|
||||||
|
ROOTLESS="N"
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
normalizeRamSize() {
|
||||||
|
|
||||||
|
# Read memory
|
||||||
|
RAM_AVAIL=$(free -b | grep -m 1 Mem: | awk '{print $7}')
|
||||||
|
RAM_TOTAL=$(free -b | grep -m 1 Mem: | awk '{print $2}')
|
||||||
|
|
||||||
|
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// /-}"
|
||||||
|
|
||||||
KERNEL=$(uname -r | cut -b 1)
|
detectEngine
|
||||||
MINOR=$(uname -r | cut -d '.' -f2)
|
|
||||||
|
echo "❯ Starting $APP for $ENGINE v$(</etc/version)..."
|
||||||
|
echo "❯ For support visit $SUPPORT"
|
||||||
|
|
||||||
|
checkPrivileged
|
||||||
|
|
||||||
|
INFO="/run/shm/msg.html"
|
||||||
|
PAGE="/run/shm/index.html"
|
||||||
|
TEMPLATE="/var/www/index.html"
|
||||||
|
FOOTER1="$APP for $ENGINE v$(</etc/version)"
|
||||||
|
FOOTER2="<a href='$SUPPORT'>$SUPPORT</a>"
|
||||||
|
|
||||||
|
SOCKETS=1
|
||||||
|
CPU=$(cpu)
|
||||||
|
SYS=$(uname -r)
|
||||||
|
KERNEL=$(echo "$SYS" | cut -b 1)
|
||||||
|
MINOR=$(echo "$SYS" | cut -d '.' -f2)
|
||||||
ARCH=$(dpkg --print-architecture)
|
ARCH=$(dpkg --print-architecture)
|
||||||
VERS=$(qemu-system-x86_64 --version | head -n 1 | cut -d '(' -f 1)
|
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}')
|
||||||
|
[ -z "${SOCKETS##*[!0-9]*}" ] && SOCKETS=1
|
||||||
|
[ "$SOCKETS" -lt "1" ] && SOCKETS=1
|
||||||
|
fi
|
||||||
|
|
||||||
STORAGE="/storage"
|
normalizeCpuCores
|
||||||
[ ! -d "$STORAGE" ] && error "Storage folder ($STORAGE) not found!" && exit 13
|
checkStorage
|
||||||
|
checkFilesystem
|
||||||
|
normalizeRamSize
|
||||||
|
|
||||||
|
# Print system info
|
||||||
|
SYS="${SYS/-generic/}"
|
||||||
|
FS="${FS/UNKNOWN //}"
|
||||||
|
FS="${FS/ext2\/ext3/ext4}"
|
||||||
|
FS=$(echo "$FS" | sed 's/[)(]//g')
|
||||||
|
SPACE=$(df --output=avail -B 1 "$STORAGE" | tail -n 1)
|
||||||
|
SPACE_GB=$(formatBytes "$SPACE" "down")
|
||||||
|
AVAIL_MEM=$(formatBytes "$RAM_AVAIL" "down")
|
||||||
|
TOTAL_MEM=$(formatBytes "$RAM_TOTAL" "up")
|
||||||
|
|
||||||
|
echo "❯ CPU: ${CPU} | RAM: ${AVAIL_MEM/ GB/}/$TOTAL_MEM | DISK: $SPACE_GB (${FS}) | KERNEL: ${SYS}..."
|
||||||
|
echo
|
||||||
|
|
||||||
|
checkKvm
|
||||||
|
|
||||||
# Cleanup files
|
# Cleanup files
|
||||||
|
rm -f "$QEMU_DIR"/dsm.url
|
||||||
rm -f /run/dsm.url
|
rm -f "$QEMU_DIR"/qemu.* "$QEMU_DIR"/*.pid "$QEMU_DIR"/*.sock
|
||||||
rm -f /run/qemu.pid
|
|
||||||
rm -f /run/qemu.count
|
|
||||||
|
|
||||||
# Cleanup dirs
|
# Cleanup dirs
|
||||||
|
|
||||||
rm -rf /tmp/dsm
|
rm -rf /tmp/dsm
|
||||||
rm -f /tmp/server.*
|
|
||||||
rm -rf "$STORAGE/tmp"
|
rm -rf "$STORAGE/tmp"
|
||||||
|
|
||||||
# Helper functions
|
|
||||||
|
|
||||||
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 () {
|
|
||||||
|
|
||||||
[ -z "$COUNTRY" ] && getCountry "https://api.ipapi.is" ".location.country_code"
|
|
||||||
[ -z "$COUNTRY" ] && getCountry "https://ifconfig.co/json" ".country_iso"
|
|
||||||
[ -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
|
|
||||||
|
|
||||||
info "Installing $desc..."
|
|
||||||
|
|
||||||
export DEBCONF_NOWARNINGS="yes"
|
|
||||||
export DEBIAN_FRONTEND="noninteractive"
|
|
||||||
|
|
||||||
[ -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
|
|
||||||
|
|
||||||
apt-get -qq update
|
|
||||||
apt-get -qq --no-install-recommends -y install "$pkg" > /dev/null
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
+91
-39
@@ -3,55 +3,107 @@ set -Eeuo pipefail
|
|||||||
|
|
||||||
# Docker environment variables
|
# Docker environment variables
|
||||||
|
|
||||||
: ${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")
|
validateHostMac() {
|
||||||
[ -n "$HOST_MODEL" ] && HOST_ARGS+=("-model=$HOST_MODEL")
|
local m
|
||||||
[ -n "$HOST_SERIAL" ] && HOST_ARGS+=("-hostsn=$HOST_SERIAL")
|
|
||||||
[ -n "$GUEST_SERIAL" ] && HOST_ARGS+=("-guestsn=$GUEST_SERIAL")
|
|
||||||
|
|
||||||
if [[ "$HOST_DEBUG" == [Yy1]* ]]; then
|
if [ -z "$HOST_MAC" ]; then
|
||||||
set -x
|
return 0
|
||||||
./host.bin "${HOST_ARGS[@]}" &
|
fi
|
||||||
{ set +x; } 2>/dev/null
|
|
||||||
echo
|
|
||||||
else
|
|
||||||
./host.bin "${HOST_ARGS[@]}" >/dev/null &
|
|
||||||
fi
|
|
||||||
|
|
||||||
cnt=0
|
HOST_MAC="${HOST_MAC//-/:}"
|
||||||
sleep 0.2
|
|
||||||
|
|
||||||
while ! nc -z -w2 127.0.0.1 2210 > /dev/null 2>&1; do
|
if [[ ${#HOST_MAC} == 12 ]]; then
|
||||||
sleep 0.1
|
m="$HOST_MAC"
|
||||||
cnt=$((cnt + 1))
|
HOST_MAC="${m:0:2}:${m:2:2}:${m:4:2}:${m:6:2}:${m:8:2}:${m:10:2}"
|
||||||
(( cnt > 50 )) && error "Failed to connect to qemu-host.." && exit 58
|
fi
|
||||||
done
|
|
||||||
|
|
||||||
cnt=0
|
if [[ ${#HOST_MAC} != 17 ]]; then
|
||||||
|
error "Invalid HOST_MAC address: '$HOST_MAC', should be 12 or 17 digits long!" && exit 28
|
||||||
|
fi
|
||||||
|
|
||||||
while ! nc -z -w2 127.0.0.1 12345 > /dev/null 2>&1; do
|
return 0
|
||||||
sleep 0.1
|
}
|
||||||
cnt=$((cnt + 1))
|
|
||||||
(( cnt > 50 )) && error "Failed to connect to qemu-host.." && exit 59
|
|
||||||
done
|
|
||||||
|
|
||||||
# Configure serial ports
|
buildHostArguments() {
|
||||||
|
|
||||||
SERIAL_OPTS="\
|
HOST_ARGS=()
|
||||||
-serial mon:stdio \
|
HOST_ARGS+=("-cpu=$CPU_CORES")
|
||||||
|
HOST_ARGS+=("-cpu_arch=$HOST_CPU")
|
||||||
|
|
||||||
|
[ -n "$HOST_MAC" ] && HOST_ARGS+=("-mac=$HOST_MAC")
|
||||||
|
[ -n "$HOST_MODEL" ] && HOST_ARGS+=("-model=$HOST_MODEL")
|
||||||
|
[ -n "$HOST_SERIAL" ] && HOST_ARGS+=("-hostsn=$HOST_SERIAL")
|
||||||
|
[ -n "$GUEST_SERIAL" ] && HOST_ARGS+=("-guestsn=$GUEST_SERIAL")
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
startHostBinary() {
|
||||||
|
|
||||||
|
if enabled "$HOST_DEBUG"; then
|
||||||
|
set -x
|
||||||
|
./host.bin "${HOST_ARGS[@]}" &
|
||||||
|
{ set +x; } 2>/dev/null
|
||||||
|
echo "$!" > "$HOST_PID"
|
||||||
|
echo
|
||||||
|
else
|
||||||
|
./host.bin "${HOST_ARGS[@]}" >/dev/null &
|
||||||
|
echo "$!" > "$HOST_PID"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
waitForPort() {
|
||||||
|
|
||||||
|
local port="$1"
|
||||||
|
local exit_code="$2"
|
||||||
|
local cnt=0
|
||||||
|
|
||||||
|
while ! nc -z -w2 127.0.0.1 "$port" > /dev/null 2>&1; do
|
||||||
|
sleep 0.1
|
||||||
|
cnt=$((cnt + 1))
|
||||||
|
(( cnt > 50 )) && error "Failed to connect to qemu-host.." && exit "$exit_code"
|
||||||
|
done
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
configureSerialPorts() {
|
||||||
|
|
||||||
|
# Configure serial ports
|
||||||
|
SERIAL_OPTS="-serial mon:stdio \
|
||||||
-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 pty,id=charserial0 \
|
-chardev socket,id=charchannel0,host=127.0.0.1,port=$CHR_PORT,reconnect=10 \
|
||||||
-device isa-serial,chardev=charserial0,id=serial0 \
|
|
||||||
-chardev socket,id=charchannel0,host=127.0.0.1,port=12345,reconnect=10 \
|
|
||||||
-device virtserialport,bus=virtio-serial0.0,nr=1,chardev=charchannel0,id=channel0,name=vchannel"
|
-device virtserialport,bus=virtio-serial0.0,nr=1,chardev=charchannel0,id=channel0,name=vchannel"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
validateHostMac
|
||||||
|
|
||||||
|
HOST_PID="$QEMU_DIR/host.pid"
|
||||||
|
|
||||||
|
buildHostArguments
|
||||||
|
startHostBinary
|
||||||
|
|
||||||
|
sleep 0.2
|
||||||
|
|
||||||
|
waitForPort "$COM_PORT" 58
|
||||||
|
waitForPort "$CHR_PORT" 59
|
||||||
|
|
||||||
|
configureSerialPorts
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
+62
-52
@@ -1,66 +1,76 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -eu
|
set -Eeuo pipefail
|
||||||
|
|
||||||
TMP_FILE=$(mktemp -q /tmp/server.XXXXXX)
|
: "${COM_PORT:="2210"}" # Comm port
|
||||||
|
: "${WEB_PORT:="5000"}" # Webserver port
|
||||||
|
: "${CHR_PORT:="12345"}" # Character port
|
||||||
|
: "${WSD_PORT:="8004"}" # Websockets port
|
||||||
|
|
||||||
stop() {
|
# Sanitize port variables
|
||||||
trap - SIGINT EXIT
|
COM_PORT=$(strip "$COM_PORT")
|
||||||
{ pkill -f socat || true; } 2>/dev/null
|
WEB_PORT=$(strip "$WEB_PORT")
|
||||||
[ -f "$TMP_FILE" ] && rm -f "$TMP_FILE"
|
CHR_PORT=$(strip "$CHR_PORT")
|
||||||
|
WSD_PORT=$(strip "$WSD_PORT")
|
||||||
|
|
||||||
|
WEB_PID="/run/nginx.pid"
|
||||||
|
WSD_PID="$QEMU_DIR/websocketd.pid"
|
||||||
|
|
||||||
|
prepareWebFiles() {
|
||||||
|
cp -r /var/www/* "$QEMU_DIR"
|
||||||
|
rm -f "$WSD_PID" "$WEB_PID"
|
||||||
|
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
trap 'stop' EXIT SIGINT SIGTERM SIGHUP
|
configureWebPorts() {
|
||||||
|
sed -i "s/listen 5000 default_server;/listen $WEB_PORT default_server;/g" /etc/nginx/sites-enabled/web.conf
|
||||||
|
sed -i "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
|
||||||
|
|
||||||
html()
|
return 0
|
||||||
{
|
|
||||||
local h="<!DOCTYPE html><HTML><HEAD><TITLE>VirtualDSM</TITLE>"
|
|
||||||
h="$h<STYLE>body { color: white; background-color: #125bdb; font-family: Verdana,"
|
|
||||||
h="$h Arial,sans-serif; } a, a:hover, a:active, a:visited { color: white; }</STYLE></HEAD>"
|
|
||||||
h="$h<BODY><BR><BR><H1><CENTER>$1</CENTER></H1></BODY></HTML>"
|
|
||||||
|
|
||||||
echo "$h"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if [[ "$2" != "/"* ]]; then
|
configureIpv6Listen() {
|
||||||
|
|
||||||
BODY="$2"
|
# shellcheck disable=SC2143
|
||||||
|
if [ -f /proc/net/if_inet6 ] && [[ "$(cat /proc/sys/net/ipv6/conf/all/disable_ipv6 2>/dev/null)" != "1" ]] && [ -n "$(ifconfig -a | grep inet6)" ]; then
|
||||||
if [[ "$BODY" == "install" ]]; then
|
sed -i "s/listen $WEB_PORT default_server;/listen [::]:$WEB_PORT default_server ipv6only=off;/g" /etc/nginx/sites-enabled/web.conf
|
||||||
BODY="Please wait while Virtual DSM is being installed..."
|
|
||||||
BODY="$BODY<script>setTimeout(() => { document.location.reload(); }, 9999);</script>"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
HTML=$(html "$BODY")
|
return 0
|
||||||
printf '%b' "HTTP/1.1 200 OK\nContent-Length: ${#HTML}\nConnection: close\n\n$HTML" > "$TMP_FILE"
|
}
|
||||||
|
|
||||||
socat TCP4-LISTEN:80,reuseaddr,fork,crlf SYSTEM:"cat $TMP_FILE" 2> /dev/null &
|
configureWebServer() {
|
||||||
socat TCP4-LISTEN:"${1:-5000}",reuseaddr,fork,crlf SYSTEM:"cat $TMP_FILE" 2> /dev/null & wait $!
|
|
||||||
|
mkdir -p /etc/nginx/sites-enabled
|
||||||
exit
|
cp /etc/nginx/default.conf /etc/nginx/sites-enabled/web.conf
|
||||||
|
|
||||||
|
configureWebPorts
|
||||||
|
configureIpv6Listen
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
startWebServer() {
|
||||||
|
|
||||||
|
# Start webserver
|
||||||
|
nginx -e stderr
|
||||||
|
}
|
||||||
|
|
||||||
|
startWebsocketServer() {
|
||||||
|
|
||||||
|
# Start websocket server
|
||||||
|
websocketd --address 127.0.0.1 --port="$WSD_PORT" /run/socket.sh >/var/log/websocketd.log &
|
||||||
|
echo "$!" > "$WSD_PID"
|
||||||
|
}
|
||||||
|
|
||||||
|
prepareWebFiles
|
||||||
|
|
||||||
|
html "Starting $APP for $ENGINE..."
|
||||||
|
|
||||||
|
if ! disabled "${WEB:-}"; then
|
||||||
|
configureWebServer
|
||||||
|
startWebServer
|
||||||
|
startWebsocketServer
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$2" != "/run/ip.sh" ]]; then
|
return 0
|
||||||
|
|
||||||
cp "$2" "$TMP_FILE"
|
|
||||||
|
|
||||||
else
|
|
||||||
|
|
||||||
BODY="The location of DSM is <a href='http://\$LOCATION'>http://\$LOCATION</a><script>"
|
|
||||||
BODY="$BODY setTimeout(function(){ window.location.assign('http://\$LOCATION'); }, 3000);</script>"
|
|
||||||
WAIT="Please wait while discovering IP...<script>setTimeout(() => { document.location.reload(); }, 4999);</script>"
|
|
||||||
|
|
||||||
HTML=$(html "xxx")
|
|
||||||
|
|
||||||
{ echo "#!/bin/bash"
|
|
||||||
echo "[ -f \"/run/dsm.url\" ] && LOCATION=\$(cat \"/run/dsm.url\")"
|
|
||||||
echo "HTML=\"$HTML\"; [ -z \"\$LOCATION\" ] && BODY=\"$WAIT\" || BODY=\"$BODY\"; HTML=\${HTML/xxx/\$BODY}"
|
|
||||||
echo "printf '%b' \"HTTP/1.1 200 OK\\nContent-Length: \${#HTML}\\nConnection: close\\n\\n\$HTML\""
|
|
||||||
} > "$TMP_FILE"
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
chmod +x "$TMP_FILE"
|
|
||||||
|
|
||||||
socat TCP4-LISTEN:80,reuseaddr,fork,crlf SYSTEM:"$TMP_FILE" 2> /dev/null &
|
|
||||||
socat TCP4-LISTEN:"${1:-5000}",reuseaddr,fork,crlf SYSTEM:"$TMP_FILE" 2> /dev/null & wait $!
|
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
lastmsg=""
|
||||||
|
path="/run/shm/msg.html"
|
||||||
|
|
||||||
|
refresh() {
|
||||||
|
|
||||||
|
[ ! -f "$path" ] && return 0
|
||||||
|
[ ! -s "$path" ] && return 0
|
||||||
|
|
||||||
|
msg=$(< "$path")
|
||||||
|
msg="${msg%$'\n'}"
|
||||||
|
|
||||||
|
[ -z "$msg" ] && return 0
|
||||||
|
[[ "$msg" == "$lastmsg" ]] && return 0
|
||||||
|
|
||||||
|
lastmsg="$msg"
|
||||||
|
echo "s: $msg"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh
|
||||||
|
|
||||||
|
inotifywait -m "$path" |
|
||||||
|
while read -r fp event fn; do
|
||||||
|
case "${event,,}" in
|
||||||
|
"modify"* ) refresh ;;
|
||||||
|
"delete_self" ) echo "c: vnc" ;;
|
||||||
|
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
|
||||||
+370
@@ -0,0 +1,370 @@
|
|||||||
|
#!/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; }
|
||||||
|
|
||||||
|
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() {
|
||||||
|
case "$(strip "${1:-}")" in
|
||||||
|
Y|y|YES|Yes|yes|TRUE|True|true|1|ON|On|on) return 0 ;;
|
||||||
|
*) return 1 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
disabled() {
|
||||||
|
case "$(strip "${1:-}")" in
|
||||||
|
N|n|NO|No|no|FALSE|False|false|0|OFF|Off|off) return 0 ;;
|
||||||
|
*) return 1 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
formatBytes() {
|
||||||
|
local result
|
||||||
|
result=$(numfmt --to=iec --suffix=B "$1" | sed -r 's/([A-Z])/ \1/' | sed 's/ B/ bytes/g;')
|
||||||
|
local unit="${result//[0-9. ]}"
|
||||||
|
result="${result//[a-zA-Z ]/}"
|
||||||
|
if [[ "${2:-}" == "up" ]]; then
|
||||||
|
if [[ "$result" == *"."* ]]; then
|
||||||
|
result="${result%%.*}"
|
||||||
|
result=$((result+1))
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ "${2:-}" == "down" ]]; then
|
||||||
|
result="${result%%.*}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
echo "$result $unit"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
isAlive() {
|
||||||
|
local pid="$1"
|
||||||
|
[ -z "$pid" ] && return 1
|
||||||
|
|
||||||
|
if kill -0 "$pid" 2>/dev/null; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
waitPid() {
|
||||||
|
local i=0
|
||||||
|
local pid="$1"
|
||||||
|
local timeout="${2:-10}"
|
||||||
|
|
||||||
|
while [ -n "$pid" ] && isAlive "$pid"; do
|
||||||
|
sleep 0.2
|
||||||
|
i=$((i + 1))
|
||||||
|
(( i >= timeout * 5 )) && return 1
|
||||||
|
done
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
waitPidFile() {
|
||||||
|
local i=0
|
||||||
|
local pid=""
|
||||||
|
local file="$1"
|
||||||
|
local timeout="${2:-10}"
|
||||||
|
|
||||||
|
[ ! -s "$file" ] && return 0
|
||||||
|
! read -r pid <"$file" && return 0
|
||||||
|
[ -z "$pid" ] && return 0
|
||||||
|
|
||||||
|
while [ -s "$file" ] && isAlive "$pid"; do
|
||||||
|
sleep 0.2
|
||||||
|
i=$((i + 1))
|
||||||
|
(( i >= timeout * 5 )) && return 1
|
||||||
|
done
|
||||||
|
|
||||||
|
rm -f -- "$file"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pKill() {
|
||||||
|
local pid="$1"
|
||||||
|
local timeout="${2:-10}"
|
||||||
|
|
||||||
|
{ kill -15 -- "$pid" || :; } 2>/dev/null
|
||||||
|
|
||||||
|
if ! waitPid "$pid" "$timeout"; then
|
||||||
|
warn "Timed out while waiting for PID $pid"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fWait() {
|
||||||
|
local i=0
|
||||||
|
local name="$1"
|
||||||
|
local timeout="${2:-10}"
|
||||||
|
|
||||||
|
[ -z "$name" ] && return 0
|
||||||
|
|
||||||
|
while pgrep -f -l "$name" >/dev/null; do
|
||||||
|
sleep 0.2
|
||||||
|
i=$((i + 1))
|
||||||
|
if (( i >= timeout * 5 )); then
|
||||||
|
warn "Timed out while waiting for process: $name"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fKill() {
|
||||||
|
local name="$1"
|
||||||
|
local timeout="${2:-10}"
|
||||||
|
|
||||||
|
[ -z "$name" ] && return 0
|
||||||
|
|
||||||
|
{ pkill -f "$name" || :; } 2>/dev/null
|
||||||
|
fWait "$name" "$timeout"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
sKill() {
|
||||||
|
local pid=""
|
||||||
|
local file="$1"
|
||||||
|
|
||||||
|
[ ! -s "$file" ] && return 0
|
||||||
|
! read -r pid <"$file" && return 0
|
||||||
|
[ -z "$pid" ] && return 0
|
||||||
|
|
||||||
|
if isAlive "$pid"; then
|
||||||
|
{ kill -15 -- "$pid" || :; } 2>/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
mKill() {
|
||||||
|
local timeout=10
|
||||||
|
local files=("$@")
|
||||||
|
|
||||||
|
for file in "${files[@]}"; do
|
||||||
|
sKill "$file"
|
||||||
|
done
|
||||||
|
|
||||||
|
for file in "${files[@]}"; do
|
||||||
|
if ! waitPidFile "$file" "$timeout"; then
|
||||||
|
warn "Timed out while waiting for PID file: $file"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
setOwner() {
|
||||||
|
local file="$1"
|
||||||
|
local dir uid gid
|
||||||
|
|
||||||
|
[ ! -f "$file" ] && return 1
|
||||||
|
|
||||||
|
dir=$(dirname -- "$file")
|
||||||
|
uid=$(stat -c '%u' "$dir")
|
||||||
|
gid=$(stat -c '%g' "$dir")
|
||||||
|
|
||||||
|
! 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")
|
||||||
|
uid=$(stat -c '%u' "$dir")
|
||||||
|
gid=$(stat -c '%g' "$dir")
|
||||||
|
|
||||||
|
! chown "$uid:$gid" "$path" && return 1
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
hasDisk() {
|
||||||
|
|
||||||
|
enabled "${DISK_DISABLE:-}" && return 1
|
||||||
|
|
||||||
|
[ -b "/disk" ] && return 0
|
||||||
|
[ -b "/disk1" ] && return 0
|
||||||
|
[ -b "/dev/disk1" ] && return 0
|
||||||
|
[ -b "${DEVICE:-}" ] && return 0
|
||||||
|
|
||||||
|
[ -z "${DISK_NAME:-}" ] && DISK_NAME="data"
|
||||||
|
[ -s "$STORAGE/$DISK_NAME.img" ] && return 0
|
||||||
|
[ -s "$STORAGE/$DISK_NAME.qcow2" ] && return 0
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
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.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
|
||||||
|
DEBIAN_FRONTEND=noninteractive apt-get -qq --no-install-recommends -y install "$pkg" > /dev/null
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
server {
|
||||||
|
listen 5000 default_server;
|
||||||
|
|
||||||
|
autoindex on;
|
||||||
|
tcp_nodelay on;
|
||||||
|
server_tokens off;
|
||||||
|
absolute_redirect off;
|
||||||
|
|
||||||
|
error_log /dev/null;
|
||||||
|
access_log /dev/null;
|
||||||
|
|
||||||
|
include /etc/nginx/mime.types;
|
||||||
|
|
||||||
|
gzip on;
|
||||||
|
gzip_vary on;
|
||||||
|
gzip_proxied any;
|
||||||
|
gzip_comp_level 5;
|
||||||
|
gzip_min_length 500;
|
||||||
|
gzip_disable "msie6";
|
||||||
|
gzip_types text/css text/javascript text/xml text/plain text/x-component application/javascript application/json application/xml application/rss+xml font/truetype font/opentype application/vnd.ms-fontobject image/svg+xml;
|
||||||
|
|
||||||
|
add_header Cache-Control "no-cache";
|
||||||
|
|
||||||
|
location / {
|
||||||
|
|
||||||
|
root /run/shm;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
location /status {
|
||||||
|
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
|
||||||
|
proxy_set_header Connection 'upgrade';
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_read_timeout 3600s;
|
||||||
|
proxy_send_timeout 3600s;
|
||||||
|
|
||||||
|
proxy_pass http://127.0.0.1:8004/;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
body {
|
||||||
|
color: white;
|
||||||
|
background-color: #125bdb;
|
||||||
|
font-smoothing: antialiased;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
font-family: Verdana, Geneva, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
#info {
|
||||||
|
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
#content {
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
margin-top: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
width: 98%;
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0px;
|
||||||
|
height: 40px;
|
||||||
|
text-align: center;
|
||||||
|
color: #0c8aeb;
|
||||||
|
text-shadow: 0 0 1px #0c8aeb;
|
||||||
|
}
|
||||||
|
|
||||||
|
#empty {
|
||||||
|
height: 40px;
|
||||||
|
/* Same height as footer */
|
||||||
|
}
|
||||||
|
|
||||||
|
a,
|
||||||
|
a:hover,
|
||||||
|
a:active,
|
||||||
|
a:visited {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer a:link,
|
||||||
|
footer a:visited,
|
||||||
|
footer a:active {
|
||||||
|
color: #0c8aeb;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer a:hover {
|
||||||
|
color: #73e6ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading:after {
|
||||||
|
content: " .";
|
||||||
|
animation: dots 1s steps(5, end) infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes dots {
|
||||||
|
|
||||||
|
0%,
|
||||||
|
20% {
|
||||||
|
color: rgba(0, 0, 0, 0);
|
||||||
|
text-shadow: 0.25em 0 0 rgba(0, 0, 0, 0), 0.5em 0 0 rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
40% {
|
||||||
|
color: white;
|
||||||
|
text-shadow: 0.25em 0 0 rgba(0, 0, 0, 0), 0.5em 0 0 rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
60% {
|
||||||
|
text-shadow: 0.25em 0 0 white, 0.5em 0 0 rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
80%,
|
||||||
|
100% {
|
||||||
|
text-shadow: 0.25em 0 0 white, 0.5em 0 0 white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner_LWk7 {
|
||||||
|
animation: spinner_GWy6 1.2s linear infinite, spinner_BNNO 1.2s linear infinite
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner_yOMU {
|
||||||
|
animation: spinner_GWy6 1.2s linear infinite, spinner_pVqn 1.2s linear infinite;
|
||||||
|
animation-delay: .15s
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner_KS4S {
|
||||||
|
animation: spinner_GWy6 1.2s linear infinite, spinner_6uKB 1.2s linear infinite;
|
||||||
|
animation-delay: .3s
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner_zVee {
|
||||||
|
animation: spinner_GWy6 1.2s linear infinite, spinner_Qw4x 1.2s linear infinite;
|
||||||
|
animation-delay: .45s
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spinner_GWy6 {
|
||||||
|
|
||||||
|
0%,
|
||||||
|
50% {
|
||||||
|
width: 9px;
|
||||||
|
height: 9px
|
||||||
|
}
|
||||||
|
|
||||||
|
10% {
|
||||||
|
width: 11px;
|
||||||
|
height: 11px
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spinner_BNNO {
|
||||||
|
|
||||||
|
0%,
|
||||||
|
50% {
|
||||||
|
x: 1.5px;
|
||||||
|
y: 1.5px
|
||||||
|
}
|
||||||
|
|
||||||
|
10% {
|
||||||
|
x: .5px;
|
||||||
|
y: .5px
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spinner_pVqn {
|
||||||
|
|
||||||
|
0%,
|
||||||
|
50% {
|
||||||
|
x: 13.5px;
|
||||||
|
y: 1.5px
|
||||||
|
}
|
||||||
|
|
||||||
|
10% {
|
||||||
|
x: 12.5px;
|
||||||
|
y: .5px
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spinner_6uKB {
|
||||||
|
|
||||||
|
0%,
|
||||||
|
50% {
|
||||||
|
x: 13.5px;
|
||||||
|
y: 13.5px
|
||||||
|
}
|
||||||
|
|
||||||
|
10% {
|
||||||
|
x: 12.5px;
|
||||||
|
y: 12.5px
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spinner_Qw4x {
|
||||||
|
|
||||||
|
0%,
|
||||||
|
50% {
|
||||||
|
x: 1.5px;
|
||||||
|
y: 13.5px
|
||||||
|
}
|
||||||
|
|
||||||
|
10% {
|
||||||
|
x: .5px;
|
||||||
|
y: 12.5px
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<svg id="Capa_1" enable-background="new 0 0 511.962 511.962" height="512" viewBox="0 0 511.962 511.962" width="512" xmlns="http://www.w3.org/2000/svg"><g><path d="m489.965 120.063c0-5.77-3.31-11.028-8.512-13.524l-218.984-105.063c-4.102-1.967-8.875-1.967-12.977 0l-218.985 105.063c-5.202 2.496-8.511 7.755-8.511 13.524l-.003 271.834c0 5.77 3.31 11.028 8.512 13.524l218.989 105.064c2.051.983 4.27 1.476 6.488 1.476 2.219 0 4.438-.492 6.488-1.476l218.989-105.064c5.202-2.496 8.512-7.755 8.512-13.524z" fill="#4e6ba6"/><path d="m489.965 120.063c0-5.77-3.31-11.028-8.512-13.524l-218.984-105.063c-2.051-.984-4.269-1.476-6.488-1.476v511.962c2.219 0 4.438-.492 6.488-1.476l218.989-105.064c5.202-2.496 8.512-7.755 8.512-13.524z" fill="#28487a"/><path d="m425.812 160.441c0-2.27-.519-4.457-1.457-6.432l-336.701-.095c-.967 1.999-1.504 4.22-1.504 6.526l-.002 191.081c0 5.769 3.31 11.028 8.512 13.524l154.833 74.285c2.051.983 4.27 1.476 6.488 1.476 2.219 0 4.438-.492 6.488-1.476l154.834-74.285c5.202-2.496 8.512-7.755 8.512-13.524z" fill="#8dc2eb"/><path d="m424.354 154.009h-168.373v286.798c2.219 0 4.438-.492 6.488-1.476l154.834-74.285c5.202-2.496 8.512-7.755 8.512-13.524l-.003-191.081c0-2.27-.52-4.458-1.458-6.432z" fill="#5e9ff6"/><path d="m417.3 146.916-154.831-74.284c-4.102-1.967-8.875-1.967-12.977 0l-154.831 74.284c-3.122 1.498-5.555 3.996-7.007 6.998l168.328 80.812 168.374-80.717c-1.448-3.044-3.9-5.579-7.056-7.093z" fill="#ecf9fd"/><path d="m417.3 146.916-154.831-74.284c-2.051-.983-4.27-1.476-6.488-1.476v163.569l168.374-80.717c-1.447-3.043-3.899-5.578-7.055-7.092z" fill="#d9f3fc"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 1.6 KiB |
@@ -0,0 +1,34 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
[1]
|
||||||
|
<meta http-equiv="Cache-Control" content="no-cache" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="css/style.css" />
|
||||||
|
<link rel="icon" href="img/favicon.svg" type="image/x-icon">
|
||||||
|
[2]
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="page">
|
||||||
|
<div id="content">
|
||||||
|
<svg id="spinner" width="64" height="64" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect class="spinner_LWk7" fill="#0c8aeb" x="1.5" y="1.5" rx="1" width="9" height="9"/>
|
||||||
|
<rect class="spinner_yOMU" fill="#0c8aeb" x="13.5" y="1.5" rx="1" width="9" height="9"/>
|
||||||
|
<rect class="spinner_KS4S" fill="#0c8aeb" x="13.5" y="13.5" rx="1" width="9" height="9"/>
|
||||||
|
<rect class="spinner_zVee" fill="#0c8aeb" x="1.5" y="13.5" rx="1" width="9" height="9"/>
|
||||||
|
</svg>
|
||||||
|
<h1 id="info">[3]</h1>
|
||||||
|
</div>
|
||||||
|
<div id="empty">
|
||||||
|
</div>
|
||||||
|
<footer id="footer">
|
||||||
|
[4]<br />
|
||||||
|
[5]
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
<script type="text/javascript" src="js/script.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,208 @@
|
|||||||
|
var request;
|
||||||
|
var booting = false;
|
||||||
|
var interval = 1000;
|
||||||
|
|
||||||
|
function getInfo() {
|
||||||
|
|
||||||
|
var url = "msg.html";
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (request) {
|
||||||
|
request.abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.XMLHttpRequest) {
|
||||||
|
request = new XMLHttpRequest();
|
||||||
|
} else {
|
||||||
|
throw new Error("XMLHttpRequest not available!");
|
||||||
|
}
|
||||||
|
|
||||||
|
request.onreadystatechange = processInfo;
|
||||||
|
request.open("GET", url, true);
|
||||||
|
request.send();
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
setError("Error: " + e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getURL() {
|
||||||
|
|
||||||
|
var protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
|
||||||
|
var path = window.location.pathname.replace(/[^/]*$/, '').replace(/\/$/, '');
|
||||||
|
|
||||||
|
return protocol + "//" + window.location.host + path;
|
||||||
|
}
|
||||||
|
|
||||||
|
function processMsg(msg) {
|
||||||
|
|
||||||
|
if (msg.toLowerCase().indexOf("href=") !== -1) {
|
||||||
|
var div = document.createElement("div");
|
||||||
|
div.innerHTML = msg;
|
||||||
|
var url = div.querySelector("a").href;
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.assign(url);
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
setInfo(msg);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function processInfo() {
|
||||||
|
try {
|
||||||
|
|
||||||
|
if (request.readyState != 4) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var msg = request.responseText;
|
||||||
|
if (msg == null || msg.length == 0) {
|
||||||
|
|
||||||
|
if (booting) {
|
||||||
|
schedule();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.location.reload();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var notFound = (request.status == 404);
|
||||||
|
|
||||||
|
if (request.status == 200) {
|
||||||
|
if (msg.toLowerCase().indexOf("<html>") !== -1) {
|
||||||
|
notFound = true;
|
||||||
|
} else {
|
||||||
|
processMsg(msg);
|
||||||
|
if (msg.toLowerCase().indexOf("href=") == -1) {
|
||||||
|
schedule();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notFound) {
|
||||||
|
setInfo("Connecting to web portal", true);
|
||||||
|
reload();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
setError("Error: Received statuscode " + request.status);
|
||||||
|
return false;
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
setError("Error: " + e.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractContent(s) {
|
||||||
|
var span = document.createElement('span');
|
||||||
|
span.innerHTML = s;
|
||||||
|
return span.textContent || span.innerText;
|
||||||
|
};
|
||||||
|
|
||||||
|
function setInfo(msg, loading, error) {
|
||||||
|
try {
|
||||||
|
|
||||||
|
if (msg == null || msg.length == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.includes("Booting ")) {
|
||||||
|
booting = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var el = document.getElementById("info");
|
||||||
|
|
||||||
|
if (el.innerText == msg || el.innerHTML == msg) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var spin = document.getElementById("spinner");
|
||||||
|
|
||||||
|
error = !!error;
|
||||||
|
if (!error) {
|
||||||
|
spin.style.visibility = 'visible';
|
||||||
|
} else {
|
||||||
|
spin.style.visibility = 'hidden';
|
||||||
|
}
|
||||||
|
|
||||||
|
var p = "<p class=\"loading\">";
|
||||||
|
loading = !!loading;
|
||||||
|
if (loading) {
|
||||||
|
msg = p + msg + "</p>";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.includes(p)) {
|
||||||
|
if (el.innerHTML.includes(p)) {
|
||||||
|
el.getElementsByClassName('loading')[0].textContent = extractContent(msg);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
el.innerHTML = msg;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
console.log("Error: " + e.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setError(text) {
|
||||||
|
console.warn(text);
|
||||||
|
return setInfo(text, false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function schedule() {
|
||||||
|
setTimeout(getInfo, interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
function reload() {
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.reload();
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function connect() {
|
||||||
|
|
||||||
|
var wsUrl = getURL() + "/status";
|
||||||
|
var ws = new WebSocket(wsUrl);
|
||||||
|
|
||||||
|
ws.onmessage = function(e) {
|
||||||
|
|
||||||
|
var pos = e.data.indexOf(":");
|
||||||
|
var cmd = e.data.substring(0, pos);
|
||||||
|
var msg = e.data.substring(pos + 2);
|
||||||
|
|
||||||
|
switch (cmd) {
|
||||||
|
case "s":
|
||||||
|
processMsg(msg);
|
||||||
|
break;
|
||||||
|
case "e":
|
||||||
|
setError(msg);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.warn("Unknown event: " + cmd);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onclose = function(e) {
|
||||||
|
setTimeout(function() {
|
||||||
|
connect();
|
||||||
|
}, interval);
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onerror = function(e) {
|
||||||
|
ws.close();
|
||||||
|
if (!booting) {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
schedule();
|
||||||
|
connect();
|
||||||
Reference in New Issue
Block a user