Compare commits

..
245 Commits
Author SHA1 Message Date
KroeseandGitHub 6fe19a8af4 feat: Improve download progress reporting (#1316) 2026-07-21 06:00:58 +02:00
KroeseandGitHub 072b5c3070 build: Update workflow (#1315) 2026-07-21 01:31:29 +02:00
KroeseandGitHub 89dbaeb2a4 feat: Improve download progress reporting (#1314) 2026-07-21 00:13:53 +02:00
KroeseandGitHub 02f76b44cd fix: Resolve shellcheck error (#1313) 2026-07-20 17:00:32 +02:00
KroeseandGitHub 99d6b8f830 feat: Preserve graceful shutdown for interactive console (#1312) 2026-07-20 16:31:21 +02:00
KroeseandGitHub e6b68b2f08 fix: Avoid shellcheck warning (#1311) 2026-07-20 14:32:03 +02:00
KroeseandGitHub 7a8f0041e7 build: Update workflow (#1310) 2026-07-20 12:31:13 +02:00
KroeseandGitHub 64809bb4d4 feat: Support progress tracking for segmented downloads (#1309) 2026-07-20 05:48:16 +02:00
KroeseandGitHub f08b2cbd99 fix: Decrease default timeout (#1307) 2026-07-19 09:17:51 +02:00
KroeseandGitHub 18d9ecd434 fix: Decrease default timeout (#1308) 2026-07-19 08:57:09 +02:00
KroeseandGitHub 6944293a32 fix: Add Recreate strategy to Kubernetes deployment (#1306) 2026-07-19 03:15:10 +02:00
KroeseandGitHub d807a365ff feat: Added optional lossy VNC compression (#1305) 2026-07-19 03:09:35 +02:00
KroeseandGitHub 066af83d07 fix: Decrease default timeout (#1304) 2026-07-19 01:03:21 +02:00
KroeseandGitHub 81536818df feat: Add escapeHTML function (#1303) 2026-07-18 22:00:45 +02:00
KroeseandGitHub 949c288565 fix: Set correct MTU option for dnsmasq (#1302) 2026-07-18 20:40:52 +02:00
KroeseandGitHub 5d5bbbcf4d feat: Improve NAT networking (#1300) 2026-07-18 15:34:46 +02:00
KroeseandGitHub 82a62c0240 build: Update workflow (#1301) 2026-07-18 15:32:07 +02:00
KroeseandGitHub 5399e1d463 feat: Use Unix sockets for qemu-host communication (#1299) 2026-07-18 11:31:20 +02:00
KroeseandGitHub c7e7b60f01 fix: Improve IP tables cleanup verification (#1298) 2026-07-18 10:54:20 +02:00
KroeseandGitHub f1defa6890 docs: Environment variables (#1297) 2026-07-18 00:28:24 +02:00
KroeseandGitHub a7fb161974 feat: Improve download progress readability (#1296) 2026-07-17 22:59:07 +02:00
KroeseandGitHub dcdf5e5293 fix: Improve interactive terminal detection (#1295) 2026-07-17 22:45:07 +02:00
KroeseandGitHub 1eb0db66ba feat: Improve download progress in container logs (#1294) 2026-07-17 22:29:10 +02:00
KroeseandGitHub 3eec8e03ed feat: Improve AMD detection (#1293) 2026-07-17 20:54:41 +02:00
KroeseandGitHub 5483981ed4 feat: Refactor config code (#1292) 2026-07-17 20:37:54 +02:00
KroeseandGitHub 62330a5bf4 fix: Cleanup stale pipe files (#1291) 2026-07-17 20:01:53 +02:00
KroeseandGitHub 2b0b59dfba feat: Preserve graceful shutdown for interactive console (#1290) 2026-07-17 19:56:05 +02:00
KroeseandGitHub b680ff9dd8 feat: Preserve graceful shutdown for interactive console (#1289) 2026-07-17 19:16:30 +02:00
KroeseandGitHub 5c889b272d fix: Store pid for serial debugging (#1288) 2026-07-17 14:44:49 +02:00
KroeseandGitHub 585ebb3f53 build: Update Passt to v2026_07_16 (#1287) 2026-07-17 13:49:26 +02:00
KroeseandGitHub 401307b981 build: Update QEMU host to v2.06 (#1286) 2026-07-17 13:24:34 +02:00
KroeseandGitHub 46820fe6eb build: Set dependabot cooldown period (#1284) 2026-07-17 13:20:19 +02:00
KroeseandGitHub 9452d48694 feat: Make machine type configurable (#1283) 2026-07-17 11:33:05 +02:00
KroeseandGitHub 5c3fed1387 fix: Clear service logs on startup (#1281) 2026-07-17 06:26:25 +02:00
KroeseandGitHub 797411ae7f fix: Show QEMU errors on unexpected exit (#1280) 2026-07-17 05:54:04 +02:00
KroeseandGitHub e81c509208 feat: Check RENDERNODE permissions (#1279) 2026-07-17 02:11:38 +02:00
KroeseandGitHub ace8614a50 feat: Refactor disk code (#1278) 2026-07-17 01:52:47 +02:00
KroeseandGitHub 8263b3a737 feat: Improve iptable backend selection (#1277) 2026-07-17 01:51:39 +02:00
KroeseandGitHub 61c6142988 feat: Remove unused functions (#1276) 2026-07-17 00:10:59 +02:00
KroeseandGitHub 5d26338bb3 feat: Improve detection of valid data disk (#1275) 2026-07-16 21:41:29 +02:00
KroeseandGitHub 084d475cde build: Add diffutils package (#1274) 2026-07-16 20:55:47 +02:00
KroeseandGitHub c36b0dcb00 docs: Environment variables (#1273) 2026-07-16 20:50:17 +02:00
KroeseandGitHub 6e9993742b feat: Limit wget progress output (#1272) 2026-07-15 23:01:39 +02:00
KroeseandGitHub 6946af0212 build: Update workflow (#1271) 2026-07-15 21:55:07 +02:00
KroeseandGitHub 0a9361d62f fix: Localize available memory formatting (#1270) 2026-07-15 21:13:51 +02:00
KroeseandGitHub 7cbfcd44a6 fix: Remove unnecessary guards (#1269) 2026-07-15 21:00:50 +02:00
KroeseandGitHub 6bcacf5e59 feat: Improve boolean helpers (#1268) 2026-07-15 19:35:16 +02:00
KroeseandGitHub 3bea87f087 feat: Suppress empty iptables debug sections (#1267) 2026-07-15 19:21:43 +02:00
KroeseandGitHub f01e1564f7 fix: Make setting owner non-fatal (#1266) 2026-07-15 18:00:24 +02:00
KroeseandGitHub cf8cdbf0c1 fix: Make failed chown non-fatal (#1265) 2026-07-15 17:39:05 +02:00
KroeseandGitHub 0813494ee0 feat: Use cgroup when calculating RAM (#1264) 2026-07-15 15:40:51 +02:00
KroeseandGitHub 0d71e30f50 feat: Add state helpers to utils (#1263) 2026-07-15 15:28:42 +02:00
KroeseandGitHub c203deab9a feat: Improve webserver configuration (#1262) 2026-07-15 15:27:24 +02:00
KroeseandGitHub 9cf31df266 fix: Validate subnet mask (#1261) 2026-07-15 14:17:01 +02:00
KroeseandGitHub 0cd08f2ee1 fix: Improve NAT port forwarding (#1260) 2026-07-15 12:24:25 +02:00
KroeseandGitHub a32d215e90 feat: Improve wget error reporting (#1259) 2026-07-15 11:34:55 +02:00
KroeseandGitHub c1f9a690f9 fix: Remove dots from info line (#1258) 2026-07-15 09:00:14 +02:00
KroeseandGitHub 5de504deb3 feat: Improve rootless container detection (#1256) 2026-07-15 06:02:12 +02:00
KroeseandGitHub 250eb18238 docs: Readme (#1255) 2026-07-14 12:50:07 +02:00
KroeseandGitHub ca80da59bd docs: Environment variables (#1254) 2026-07-14 11:29:10 +02:00
KroeseandGitHub 9cd86dfe82 fix: Use exec form for health check (#1253) 2026-07-13 19:40:30 +02:00
KroeseandGitHub 5bb93651f7 docs: Readme (#1252) 2026-07-13 00:38:58 +02:00
KroeseandGitHub 0c268308a4 feat: Preserve source image until conversion completes (#1251) 2026-07-12 23:33:10 +02:00
KroeseandGitHub 7b741d3e4b feat: Warn about UDP host ports (#1250) 2026-07-12 22:14:11 +02:00
KroeseandGitHub f1d29212ae feat: Improve iptables rule cleanup (#1249) 2026-07-12 21:58:49 +02:00
KroeseandGitHub fdee9f89a3 feat: Improve iptables backend selection (#1248) 2026-07-12 21:36:52 +02:00
KroeseandGitHub 210a360797 fix: Restore compatible NAT forwarding rules (#1247) 2026-07-12 13:18:52 +03:00
KroeseandGitHub 991bb2627a fix: Prefer legacy iptables backend for NAT (#1246) 2026-07-12 12:44:00 +03:00
KroeseandGitHub 764a186836 feat: Allow all forwarded NAT traffic (#1245) 2026-07-11 11:58:35 +03:00
KroeseandGitHub 726786a98a feat: Default host and user ports to TCP only (#1244) 2026-07-09 11:06:01 +02:00
KroeseandGitHub 2caf97d407 docs: Network modes (#1243) 2026-07-09 00:39:52 +02:00
KroeseandGitHub 0e67286599 fix: Improve file handling (#1242) 2026-07-08 23:55:02 +02:00
KroeseandGitHub 2b9fc80275 feat: Validate MAC file writes (#1241) 2026-07-08 23:28:56 +02:00
KroeseandGitHub 0d88774bd3 feat: Display base directory in warnings (#1240) 2026-07-08 23:16:20 +02:00
KroeseandGitHub fa23052ed4 fix: Set default DHCP lease time to 1 hour (#1239) 2026-07-08 22:46:47 +02:00
KroeseandGitHub cc3267f258 docs: Correct hostname (#1238) 2026-07-08 18:57:55 +02:00
KroeseandGitHub fd002564a6 feat: Validate custom QEMU CPU arguments (#1237) 2026-07-08 18:50:31 +02:00
KroeseandGitHub 2703a97cfa docs: Refer to environment documentation (#1236) 2026-07-08 18:44:04 +02:00
KroeseandGitHub 29e2d034bf docs: Document all environment variables (#1235) 2026-07-08 17:11:10 +02:00
KroeseandGitHub cbe3644335 fix: Add extra line after boot (#1234) 2026-07-08 16:50:21 +02:00
KroeseandGitHub c9a2a6966b feat: Always show network info (#1233) 2026-07-07 19:37:59 +02:00
KroeseandGitHub 498248f43e feat: Display guest info before mode warning (#1232) 2026-07-07 12:21:25 +02:00
KroeseandGitHub 8963228b1c fix: Set returnvalue for waitForPort (#1231) 2026-07-07 12:13:13 +02:00
KroeseandGitHub 827d6d9e10 fix: Status monitor exited early (#1230) 2026-07-07 12:03:58 +02:00
KroeseandGitHub 1c577abef4 fix: Guest IP was not retrieved correctly (#1229) 2026-07-07 11:16:22 +02:00
KroeseandGitHub 20fd1a975c fix: Read QEMU pid correctly (#1228) 2026-07-07 10:53:53 +02:00
KroeseandGitHub 770774680c feat: Improve shutdown logic (#1226) 2026-07-07 03:16:35 +02:00
KroeseandGitHub 0c75aa81fe fix: Detect guest IP without assuming eth0 (#1225) 2026-07-06 23:54:24 +02:00
KroeseandGitHub 36ec57ac15 fix: Initialize indexdb path before extraction (#1224) 2026-07-06 23:50:26 +02:00
KroeseandGitHub 9c488c52b9 feat: Normalize disk image format (#1223)
*
2026-07-06 23:44:14 +02:00
KroeseandGitHub 2ba756260f fix: Sanitize guest hostname (#1222) 2026-07-06 23:33:34 +02:00
KroeseandGitHub b306012743 feat: Normalize discard mode for qemu drives (#1221) 2026-07-06 23:21:25 +02:00
KroeseandGitHub 61b3ee7e01 feat: Read qcow2 virtual size from qemu-img json (#1220) 2026-07-06 23:15:27 +02:00
KroeseandGitHub 245036f844 fix: Preserve port protocols in passt mode (#1219) 2026-07-06 23:11:34 +02:00
KroeseandGitHub 68cadfe035 fix: Tolerate missing sector-size output (#1218) 2026-07-06 22:56:00 +02:00
KroeseandGitHub 96102832b1 fix: Tolerate missing sector-size output (#1217) 2026-07-06 22:38:58 +02:00
KroeseandGitHub 211a56bf73 feat: Show additional guest info in debug mode (#1216) 2026-07-06 22:11:03 +02:00
KroeseandGitHub 506fb7b114 fix: Parse Debian ipcalc output correctly (#1215) 2026-07-06 21:58:11 +02:00
KroeseandGitHub bcc3393b7f fix: Shutdown Passt after DNS failure (#1214) 2026-07-06 19:21:48 +02:00
KroeseandGitHub 45c0f91d6e feat: Refactor networking code (#1213) 2026-07-06 17:47:29 +02:00
KroeseandGitHub a1910546a6 feat: Check file operation results (#1212) 2026-07-06 07:44:41 +02:00
KroeseandGitHub 637ed0e3a6 fix: Do not match multiple files (#1211) 2026-07-06 05:09:29 +02:00
KroeseandGitHub 954a6076ad feat: Add returnvalues to functions (#1210) 2026-07-06 03:48:39 +02:00
KroeseandGitHub 415aaa3038 feat: Refactor server code (#1209) 2026-07-05 23:35:56 +02:00
KroeseandGitHub c87e45719b feat: Refactor serial code (#1208) 2026-07-05 23:24:43 +02:00
KroeseandGitHub 102945ab48 feat: Refactor memory check (#1207) 2026-07-05 23:17:53 +02:00
KroeseandGitHub 1c3d490303 feat: Refactor status monitor (#1206) 2026-07-05 23:02:51 +02:00
KroeseandGitHub 73fa70f52f feat: Refactor CPU logic (#1205) 2026-07-05 22:46:39 +02:00
KroeseandGitHub 7b095702fc feat: Refactor shutdown logic (#1204) 2026-07-05 22:09:49 +02:00
KroeseandGitHub f82b2769d3 feat: Refactor entrypoint (#1203) 2026-07-05 21:43:10 +02:00
Peng LiuandGitHub cafe619028 fix: Parse quoted values consistently (#1202) 2026-07-05 21:25:33 +02:00
KroeseandGitHub e94a0c4a52 feat: Refactor disk code (#1201) 2026-07-05 20:09:47 +02:00
KroeseandGitHub d6a353df0e feat: Refactor network code (#1200) 2026-07-05 19:58:36 +02:00
KroeseandGitHub 050812e996 fix: Filter IPv6 DNS records when disabled (#1199) 2026-07-05 19:30:41 +02:00
KroeseandGitHub 75280e9aa9 feat: Improve bus detection (#1198) 2026-07-05 18:51:56 +02:00
KroeseandGitHub 8dcc8ccee1 fix: Move MTU check to finish (#1197) 2026-07-05 18:34:55 +02:00
KroeseandGitHub ba866e9e75 feat: Improve support for non-standard MTU sizes (#1196) 2026-07-05 17:10:59 +02:00
KroeseandGitHub 06e7a8edb9 docs: Readme (#1195) 2026-07-05 14:49:47 +02:00
KroeseandGitHub 495c67734a fix: Check adapter before setting MTU (#1194) 2026-07-05 03:13:14 +02:00
KroeseandGitHub 1557735331 build: Use heredoc for Dockerfile commands (#1193) 2026-07-04 20:34:32 +02:00
KroeseandGitHub f45b308c78 feat: Sanitize environment variables (#1192) 2026-07-03 17:14:59 +02:00
CopilotandGitHub d5821cd782 feat: Normalize boolean option handling (#1191) 2026-07-03 15:17:14 +02:00
KroeseandGitHub 312816b18e feat: Implement strip() function (#1190)
Added strip function to remove whitespace and quotes.
2026-07-03 14:48:58 +02:00
KroeseandGitHub f42a3f1662 feat: Add enabled() function (#1189) 2026-07-03 14:06:42 +02:00
KroeseandGitHub 558e5022ca docs: Readme (#1188) 2026-07-02 20:26:15 +02:00
KroeseandGitHub e03822f26e feat: Improve shutdown logic (#1187) 2026-07-02 20:14:05 +02:00
KroeseandGitHub 7b90ca30ba feat: Improve socket cleanup (#1186) 2026-07-02 18:51:40 +02:00
KroeseandGitHub 22c01bf2f1 docs: Readme (#1185) 2026-07-02 12:28:35 +02:00
KroeseandGitHub 1f601db8b0 build: Update Passt to v2026_06_11 (#1183) 2026-06-28 12:43:44 +02:00
KroeseandGitHub e7e2c35cb7 build: Run review steps in parallel (#1182) 2026-06-25 22:31:34 +02:00
KroeseandGitHub 50089112c0 docs: Update stargazers chart (#1181) 2026-06-25 14:21:53 +02:00
KroeseandGitHub f9538ce53b feat: Improve IPv6 check (#1180) 2026-06-19 13:24:54 +02:00
renovate[bot]andGitHub 49d25a13d0 chore(deps): update actions/checkout action to v7 (#1179) 2026-06-19 11:48:11 +02:00
KroeseandGitHub 7c9d2b1fd5 feat: Improved start and stop logic (#1178) 2026-06-05 02:37:20 +02:00
KroeseandGitHub b68d243b6a fix: Add fail_level to reviewdog actions (#1177) 2026-06-04 23:45:01 +02:00
KroeseandGitHub 79e85f9fd1 fix: Provide exitcode on shutdown (#1176) 2026-05-31 00:32:49 +02:00
KroeseandGitHub 476048a4ee fix: Update paths for QEMU files (#1175) 2026-05-30 23:50:39 +02:00
KroeseandGitHub f983dc3e46 fix: Remove stale QEMU files (#1174) 2026-05-30 23:05:16 +02:00
KroeseandGitHub 38f9c49200 feat: Added finishDisks function (#1173) 2026-05-30 20:51:58 +02:00
KroeseandGitHub 7157717a85 feat: Added option to disable data disks (#1171) 2026-05-30 19:52:54 +02:00
KroeseandGitHub 031674424d fix: Handle iptables-save errors better (#1170) 2026-05-30 18:58:12 +02:00
KroeseandGitHub 28e6f8da78 fix: IP address parsing (#1168) 2026-05-29 17:13:47 +02:00
KroeseandGitHub d5802607b5 fix: Throw object for XMLHttpRequest (#1166) 2026-05-29 16:54:45 +02:00
KroeseandGitHub 7168f2f843 feat: Update version file path (#1165) 2026-05-29 16:08:23 +02:00
KroeseandGitHub 819dd29025 fix: Network interface detection (#1164) 2026-05-29 14:34:34 +02:00
KroeseandGitHub 48ef47b85b fix: Previous XHR not aborted (#1163) 2026-05-29 14:33:23 +02:00
KroeseandGitHub 10c902bc9b build: Update Passt to v2026_05_26 (#1162) 2026-05-29 12:52:20 +02:00
KroeseandGitHub d6de10efe1 feat: Flush iptables on container restarts (#1161) 2026-05-29 05:00:56 +02:00
KroeseandGitHub 6d1d9d92b4 feat: Improve iptable rules (#1160) 2026-05-28 23:00:17 +02:00
KroeseandGitHub 799649e78c docs: Improve low diskspace warning (#1158) 2026-05-24 14:29:17 +02:00
KroeseandGitHub 12060c72b2 feat: Update warning message (#1157) 2026-05-23 20:31:37 +02:00
KroeseandGitHub 6eadd1b953 fix: Timeout comparison operator (#1155) 2026-05-17 13:01:57 +02:00
KroeseandGitHub 342ca4da03 fix: Wrong IP was passed to getSlirp (#1154) 2026-05-17 12:27:58 +02:00
KroeseandGitHub b0b6190560 feat: Add warning when macvlan parent is a bridge (#1153)
Fixes #1152
2026-05-17 06:56:55 +02:00
KroeseandGitHub 075410e4cf fix: CPU cores validation logic (#1151) 2026-05-16 20:59:57 +02:00
KroeseandGitHub 2a388b434b feat: Improve Dnsmasq error handling (#1149) 2026-05-16 11:35:04 +02:00
KroeseandGitHub 280c4037f3 fix: CPU cores validation logic (#1150) 2026-05-16 11:33:23 +02:00
KroeseandGitHub 007bdc735d fix: Warning message for missing clock source (#1148) 2026-05-16 10:58:37 +02:00
KroeseandGitHub 41fc0a6a10 fix: Undeclared variable in ConvertDisk (#1147) 2026-05-16 10:11:37 +02:00
KroeseandGitHub 7753e4ac74 fix: Slirp portmapping was missing last port (#1146) 2026-05-16 02:22:44 +02:00
KroeseandGitHub f74bf35202 fix: Socket detection logic (#1145) 2026-05-16 02:17:55 +02:00
KroeseandGitHub aa472c96c6 feat: Use iptables multiport extension (#1144) 2026-05-15 23:28:01 +02:00
KroeseandGitHub 0dcb880800 fix: Do not remove QEMU files (#1143) 2026-05-15 22:43:44 +02:00
KroeseandGitHub 8daa6973b7 feat: Do not require NET_RAW for dnsmasq (#1142) 2026-05-15 21:30:16 +02:00
KroeseandGitHub 3884befa56 feat: Improve memory allocation logic (#1141) 2026-05-15 17:58:41 +02:00
KroeseandGitHub 85b83f8eb1 feat: Remove reliability on iptables multiport extension (#1140) 2026-05-15 17:44:35 +02:00
KroeseandGitHub f395dfaa66 feat: Removed legacy code (#1139) 2026-05-14 20:59:36 +02:00
KroeseandGitHub b3124075ea feat: Refactor QEMU PID variables (#1138) 2026-05-10 17:34:22 +02:00
KroeseandGitHub aed909bdee fix: NFT tables detection (#1137) 2026-05-10 17:24:02 +02:00
KroeseandGitHub d4cf5778e2 build: Update Passt to v2026_05_07 (#1136) 2026-05-10 17:20:26 +02:00
KroeseandGitHub 48de7dd00b fix: Size comparison variables (#1135) 2026-05-10 17:16:20 +02:00
dependabot[bot]andGitHub 686e64da3f build(deps): Bump docker/setup-buildx-action from 3 to 4 (#1130) 2026-05-10 17:15:02 +02:00
dependabot[bot]andGitHub 7517dc8b3e build(deps): Bump docker/metadata-action from 5 to 6 (#1131) 2026-05-10 17:13:41 +02:00
renovate[bot]andGitHub 660fe715e4 chore(deps): update grantbirki/json-yaml-validate action to v5 (#1134) 2026-05-10 17:06:56 +02:00
dependabot[bot]andGitHub 04ea29ec77 build(deps): Bump docker/build-push-action from 6 to 7 (#1132) 2026-05-10 17:05:14 +02:00
dependabot[bot]andGitHub 02c891cb16 build(deps): Bump docker/login-action from 3 to 4 (#1129) 2026-05-10 17:04:00 +02:00
Quang-Linh LEandGitHub 0b5d21357e fiz: Ignore sector size for whole disk passthrough (#1121) 2026-01-13 11:40:35 +01:00
KroeseandGitHub e0545b37d7 fix: Avoid duplicating dnsmasq arguments (#1113) 2025-11-22 04:28:11 +01:00
renovate[bot]andGitHub 4161c21082 chore(deps): update actions/checkout action to v6 (#1112) 2025-11-21 12:39:39 +01:00
KroeseandGitHub 48d9a1771d fix: Update Codespaces configuration (#1110) 2025-11-14 06:05:19 +01:00
KroeseandGitHub 471cdbb338 fix: Workaround AppArmor profile for passt (#1108) 2025-11-12 07:03:22 +01:00
KroeseandGitHub e77bca202b fix: Spelling mistake (#1105) 2025-11-06 03:46:35 +01:00
KroeseandGitHub 2e6c01e934 feat: Detect if container is running in privileged mode (#1104) 2025-11-06 03:39:46 +01:00
KroeseandGitHub 302c991c0c fix: Change condition for OverlayFS warning (#1103) 2025-11-06 03:22:47 +01:00
KroeseandGitHub a89007ee03 build: Use Github token (#1100) 2025-10-29 14:05:53 +01:00
KroeseandGitHub 8a89149d58 feat: Check for SSE4 instruction set (#1099) 2025-10-29 08:32:42 +01:00
KroeseandGitHub 5e8bbc2868 fix: Remove unnecessary operation (#1097) 2025-10-24 04:30:21 +02:00
KroeseandGitHub 4e48920309 fix: Do not assume Podman never has privileges (#1096) 2025-10-24 01:19:38 +02:00
KroeseandGitHub 8b145924b9 fix: Reduce spare disk space threshold (#1093) 2025-10-22 02:47:50 +02:00
KroeseandGitHub a0328e1e9c fix: Inherit owner from parent folder (#1092) 2025-10-22 02:38:57 +02:00
KroeseandGitHub b7f5214a7b build: Add code quality checks (#1091) 2025-10-22 00:59:35 +02:00
KroeseandGitHub b0e4c4ac5f docs: Update docker run command (#1090) 2025-10-21 23:15:31 +02:00
KroeseandGitHub bbb67aac93 build: Add review workflow for shell formatting (#1089) 2025-10-21 22:39:50 +02:00
KroeseandGitHub 433c83b393 fix: Kill QEMU after 5 seconds if it hangs (#1088) 2025-10-19 17:56:44 +02:00
KroeseandGitHub 5577178eeb fix: Only display non-zero percentage (#1087) 2025-10-19 17:32:25 +02:00
KroeseandGitHub 221b0242fa fix: Kill QEMU after 5 seconds when it hangs (#1086) 2025-10-19 16:57:51 +02:00
KroeseandGitHub c05623f8af fix: Round filesize up to nearest cluster (#1085) 2025-10-19 14:38:36 +02:00
KroeseandGitHub eb9884cc96 feat: Improve Github Codespaces configuration (#1084) 2025-10-19 12:48:40 +02:00
KroeseandGitHub 8e2490e6bc feat: Improve Github Codespaces configuration (#1082) 2025-10-19 10:40:03 +02:00
KroeseandGitHub 399243886e feat: Show directory size (#1083) 2025-10-19 09:40:45 +02:00
KroeseandGitHub b0dbfcb805 feat: Improve Github Codespaces configuration (#1081) 2025-10-19 01:14:11 +02:00
KroeseandGitHub dec2dc9230 feat: Check free diskspace during startup (#1080) 2025-10-17 16:47:43 +02:00
KroeseandGitHub 2c44669d91 feat: Improve Codespaces configuration (#1079) 2025-10-17 13:25:02 +02:00
KroeseandGitHub 175d90aad7 fix: Move order in entry file (#1078) 2025-10-17 13:04:03 +02:00
KroeseandGitHub e1e5f8f91d fix: Validate status messages (#1077) 2025-10-17 12:07:13 +02:00
KroeseandGitHub dff8abbe1e build: Validate JSON and YML files (#1076) 2025-10-17 10:05:52 +02:00
KroeseandGitHub 0f97091e61 fix: Set network flag (#1075) 2025-10-17 01:39:49 +02:00
KroeseandGitHub ea0d7ee6cd feat: Implement extra disksize preset (#1074) 2025-10-17 00:17:30 +02:00
KroeseandGitHub 75daa31d36 feat: Implement extra CPU_CORES preset (#1073) 2025-10-17 00:15:56 +02:00
KroeseandGitHub 8c44319c45 feat: Move memory check to own module (#1072) 2025-10-16 23:49:16 +02:00
KroeseandGitHub dac574d933 feat: Automatically adjust RAM size when too high (#1071) 2025-10-16 12:36:49 +02:00
KroeseandGitHub fd4c5001e8 feat: Update Github Codespaces configuration (#1070) 2025-10-16 09:43:19 +02:00
KroeseandGitHub dfe0b23995 feat: Improve Github Codespaces configuration (#1069) 2025-10-15 23:58:01 +02:00
KroeseandGitHub 6158372eaa fix: Remove nat option (#1068) 2025-10-15 14:39:28 +02:00
KroeseandGitHub b1a778d7b2 docs: Readme (#1067) 2025-10-15 12:43:07 +02:00
KroeseandGitHub bc0defd813 feat: Add custom .yml for Github Codespaces (#1066) 2025-10-15 11:39:36 +02:00
KroeseandGitHub 48e7a9fff0 fix: Round down minimum disk size (#1065) 2025-10-15 11:37:05 +02:00
KroeseandGitHub c2fa58ef27 feat: Fall back to slirp when passt fails (#1064) 2025-10-15 10:31:42 +02:00
KroeseandGitHub ea49cb144b feat: Validate user port configuration (#1063) 2025-10-14 16:44:11 +02:00
KroeseandGitHub b8e778a79d fix: Configure ports for Slirp networking (#1062) 2025-10-14 14:16:20 +02:00
KroeseandGitHub c70e12f0a2 fix: Lower spare disk space (#1061)
Reduced spare disk space threshold from 2GB to 512MB.
2025-10-14 03:33:25 +02:00
KroeseandGitHub 6281205912 feat: Add "max" setting for DISK_SIZE (#1060) 2025-10-14 01:22:44 +02:00
KroeseandGitHub 1ffc5c55b2 fix: Show Passt output on error (#1059) 2025-10-13 14:11:54 +02:00
KroeseandGitHub c3302e1720 fix: Rename physical to logical (#1058) 2025-10-13 09:28:25 +02:00
KroeseandGitHub 25227944b5 fix: Do not reset loading animation (#1056) 2025-10-12 01:51:17 +02:00
KroeseandGitHub 06650e916a build: Run check for all files (#1057)
Removed specific paths from pull request triggers.
2025-10-12 01:48:25 +02:00
KroeseandGitHub 2e34dffed5 feat: Expose only selected ports with Passt (#1055) 2025-10-12 01:44:24 +02:00
KroeseandGitHub 3db91f077f fix: Relay last status message (#1054) 2025-10-11 17:56:44 +02:00
KroeseandGitHub fae14f4dd9 feat: Set listening interface for Passt (#1052) 2025-10-09 11:48:08 +02:00
KroeseandGitHub d190b3ba87 feat: Use websocket for status messages (#1051) 2025-10-08 21:28:26 +02:00
KroeseandGitHub 554f3295cb fix: Terminate tail on exit (#1049) 2025-10-08 08:13:27 +02:00
KroeseandGitHub cdc4689d6a feat: Use the engine variable (#1048) 2025-10-07 12:41:42 +02:00
KroeseandGitHub 21d18fd439 feat: Improve healthcheck (#1047) 2025-10-07 05:53:06 +02:00
KroeseandGitHub fd5ec52226 fix: Check if logfile exists (#1046) 2025-10-07 02:21:49 +02:00
KroeseandGitHub fc7ab22741 fix: Use gateway MAC for passt (#1045) 2025-10-07 02:15:26 +02:00
KroeseandGitHub 59cf59faa4 feat: Improved networking implementation (#1044) 2025-10-07 00:58:34 +02:00
KroeseandGitHub 705c6ce7f1 fix: Exclude 'tap' from bus check (#1043) 2025-10-05 17:06:57 +02:00
KroeseandGitHub f2937ab507 feat: Allow large MTU sizes (#1042) 2025-10-04 10:35:20 +02:00
KroeseandGitHub 399829cf3c feat: Make monitor port configurable (#1041) 2025-10-04 09:39:20 +02:00
xrh0905andGitHub b694d6faf8 feat: Support 8k sector sizes (#1040) 2025-10-03 12:57:08 +02:00
KroeseandGitHub 7acd1f6cdb feat: Enhanced Dnsmasq logging (#1039) 2025-10-03 01:54:44 +02:00
KroeseandGitHub 09ca3bf118 feat: Add debug trace option (#1038) 2025-10-02 17:18:53 +02:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
6cac45c397 chore(deps): update peter-evans/dockerhub-description action to v5 (#1037)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-01 23:39:30 +02:00
KroeseandGitHub f18663d840 feat: Network mode detection (#1036) 2025-10-01 13:05:13 +02:00
KroeseandGitHub fefe1af9e6 fix: CPU detection (#1035)
Refactor CPU string processing to remove 'th Gen' suffix.
2025-09-30 17:18:43 +02:00
35 changed files with 5025 additions and 1478 deletions
-6
View File
@@ -1,6 +0,0 @@
{
"name": "dsm",
"service": "dsm",
"forwardPorts": [5000],
"dockerComposeFile": "compose.yml"
}
+19
View File
@@ -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
+15
View File
@@ -0,0 +1,15 @@
{
"name": "Virtual DSM",
"service": "dsm",
"forwardPorts": [5000],
"portsAttributes": {
"5000": {
"label": "Web",
"onAutoForward": "notify"
}
},
"otherPortsAttributes": {
"onAutoForward": "ignore"
},
"dockerComposeFile": "codespaces.yml"
}
+1
View File
@@ -1,4 +1,5 @@
.dockerignore .dockerignore
.devcontainer
.git .git
.github .github
.gitignore .gitignore
+4
View File
@@ -4,7 +4,11 @@ updates:
directory: / directory: /
schedule: schedule:
interval: weekly interval: weekly
cooldown:
default-days: 7
- package-ecosystem: github-actions - package-ecosystem: github-actions
directory: / directory: /
schedule: schedule:
interval: weekly interval: weekly
cooldown:
default-days: 7
+6 -6
View File
@@ -22,13 +22,13 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v5 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: |
@@ -43,23 +43,23 @@ jobs:
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@v6 uses: docker/build-push-action@v7
with: with:
context: . context: .
push: true push: true
+27 -14
View File
@@ -1,5 +1,7 @@
on: [workflow_call] on: [workflow_call]
name: "Check" name: "Check"
permissions: {} permissions: {}
jobs: jobs:
@@ -7,18 +9,29 @@ jobs:
name: shellcheck name: shellcheck
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
-
name: Checkout
uses: actions/checkout@v5
- -
name: Run ShellCheck name: Checkout
uses: ludeeus/action-shellcheck@master uses: actions/checkout@v7
env: - parallel:
SHELLCHECK_OPTS: -x --source-path=src -e SC2001 -e SC2034 -e SC2064 -e SC2317 -e SC2153 -e SC2028 -
- name: Run ShellCheck
name: Lint Dockerfile uses: ludeeus/action-shellcheck@master
uses: hadolint/hadolint-action@v3.3.0 env:
with: SHELLCHECK_OPTS: >-
dockerfile: Dockerfile -x
ignore: DL3008,DL3003,DL3006,DL3013 --source-path=src
failure-threshold: warning -e SC1091
-e SC2001
-e SC2034
-e SC2317
-e SC2153
-
name: Lint Dockerfile
uses: hadolint/hadolint-action@v3.3.0
with:
dockerfile: Dockerfile
ignore: DL3008
failure-threshold: warning
-
name: Validate JSON and YML files
uses: GrantBirki/json-yaml-validate@v5
+12 -10
View File
@@ -12,13 +12,15 @@ jobs:
dockerHubDescription: dockerHubDescription:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v5 -
- name: Checkout repo
name: Docker Hub Description uses: actions/checkout@v7
uses: peter-evans/dockerhub-description@v4 -
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
+14
View File
@@ -0,0 +1,14 @@
on:
pull_request:
name: "Review"
permissions:
contents: read
pull-requests: write
checks: write
jobs:
review:
name: review
uses: action-pack/.github/.github/workflows/review.yml@master
-5
View File
@@ -1,11 +1,6 @@
on: on:
workflow_dispatch: workflow_dispatch:
pull_request: pull_request:
paths:
- '**/*.sh'
- 'Dockerfile'
- '.github/workflows/test.yml'
- '.github/workflows/check.yml'
name: "Test" name: "Test"
permissions: {} permissions: {}
+65 -47
View File
@@ -1,59 +1,77 @@
# syntax=docker/dockerfile:1 # syntax=docker/dockerfile:1
FROM qemux/qemu-host:2.05 AS builder FROM qemux/qemu-host:2.06 AS builder
# FROM golang as builder
# WORKDIR /
# RUN git clone https://github.com/qemus/qemu-host.git
# WORKDIR /qemu-host/src
# RUN go mod download
# RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o /qemu-host.bin .
FROM debian:trixie-slim FROM debian:trixie-slim
ARG TARGETARCH
ARG TARGETPLATFORM ARG TARGETPLATFORM
ARG VERSION_ARG="0.0" ARG VERSION_ARG="0.0"
ARG VERSION_CSTRUCT="4.7"
ARG VERSION_PASST="2026_07_16"
ARG DEBCONF_NOWARNINGS="yes" ARG DEBCONF_NOWARNINGS="yes"
ARG DEBIAN_FRONTEND="noninteractive" ARG DEBIAN_FRONTEND="noninteractive"
ARG DEBCONF_NONINTERACTIVE_SEEN="true" ARG DEBCONF_NONINTERACTIVE_SEEN="true"
RUN set -eu && \ RUN <<EOF
apt-get update && \ set -eu
apt-get --no-install-recommends -y install \
jq \ apt-get update
tini \ apt-get --no-install-recommends -y install \
curl \ jq \
wget \ tini \
fdisk \ curl \
unzip \ wget \
nginx \ fdisk \
procps \ unzip \
ethtool \ nginx \
python3 \ procps \
python3-pip \ ipcalc \
python3-msgpack \ ethtool \
python3-pysodium \ python3 \
xz-utils \ python3-pip \
iptables \ python3-msgpack \
iproute2 \ python3-pysodium \
apt-utils \ xz-utils \
dnsmasq \ iptables \
fakeroot \ iproute2 \
net-tools \ dnsmasq \
e2fsprogs \ fakeroot \
qemu-utils \ apt-utils \
iputils-ping \ net-tools \
ca-certificates \ e2fsprogs \
netcat-openbsd \ diffutils \
qemu-system-x86 && \ qemu-utils \
apt-get clean && \ websocketd \
pip3 install --no-cache-dir --break-system-packages --root-user-action=ignore dissect.cstruct && \ iputils-ping \
mkdir -p /etc/qemu && \ inotify-tools \
echo "allow br0" > /etc/qemu/bridge.conf && \ ca-certificates \
unlink /etc/nginx/sites-enabled/default && \ netcat-openbsd \
sed -i 's/^worker_processes.*/worker_processes 1;/' /etc/nginx/nginx.conf && \ qemu-system-x86
echo "$VERSION_ARG" > /run/version && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* # Install Passt package
wget "https://github.com/qemus/passt/releases/download/v${VERSION_PASST}/passt_${VERSION_PASST}_${TARGETARCH}.deb" -O /tmp/passt.deb -q --timeout=10
dpkg -i /tmp/passt.deb
apt-get clean
# Install Python dependencies
pip3 install --no-cache-dir --break-system-packages --root-user-action=ignore "dissect.cstruct==$VERSION_CSTRUCT"
# Configure QEMU
mkdir -p /etc/qemu
echo "allow br0" > /etc/qemu/bridge.conf
# Configure nginx
unlink /etc/nginx/sites-enabled/default
sed -i 's/^worker_processes.*/worker_processes 1;/' /etc/nginx/nginx.conf
# Set version file
echo "$VERSION_ARG" > /etc/version
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
EOF
COPY --chmod=755 ./src /run/ COPY --chmod=755 ./src /run/
COPY --chmod=755 ./web /var/www/ COPY --chmod=755 ./web /var/www/
@@ -68,6 +86,6 @@ ENV RAM_SIZE="2G"
ENV CPU_CORES="2" ENV CPU_CORES="2"
ENV DISK_SIZE="256G" ENV DISK_SIZE="256G"
HEALTHCHECK --interval=60s --start-period=45s --retries=2 CMD /run/check.sh HEALTHCHECK --interval=60s --start-period=45s --retries=2 CMD ["/run/check.sh"]
ENTRYPOINT ["/usr/bin/tini", "-s", "/run/entry.sh"] ENTRYPOINT ["/usr/bin/tini", "-s", "/run/entry.sh"]
+99
View File
@@ -0,0 +1,99 @@
# Environment Variables
This page lists all the environment variables that can be used to configure the container.
An empty default means the variable is unset and its value is determined automatically when applicable.
## 💽 Virtual DSM
| Variable | Default | Description |
|---|---|---|
| `URL` | | URL or local path to the DSM `.pat` installation file. Downloads the default Virtual DSM image automatically when unset. |
| `COUNTRY` | | Country code used to select the Synology download mirror. Detected automatically when unset. |
| `HOST_MAC` | | MAC address reported to DSM. |
| `HOST_MODEL` | | Synology host model reported to DSM. |
| `HOST_SERIAL` | | Synology host serial number reported to DSM. |
| `GUEST_SERIAL` | | Synology guest serial number reported to DSM. |
## 🧠 CPU and Memory
| Variable | Default | Description |
|---|---|---|
| `CPU_CORES` | `2` | Number of virtual CPU cores, such as `4`, `half`, or `max`. |
| `CPU_MODEL` | `host` | QEMU CPU model. |
| `CPU_FLAGS` | | Additional QEMU CPU flags. |
| `HOST_CPU` | | CPU name reported to DSM. Selected automatically when unset. |
| `KVM` | `Y` | Enables KVM hardware acceleration. |
| `RAM_SIZE` | `2G` | Amount of RAM assigned to DSM, such as `2G`, `4G`, `half`, or `max`. |
| `RAM_CHECK` | `Y` | Checks whether enough host memory is available before starting DSM. |
## 💾 Storage
| Variable | Default | Description |
|---|---|---|
| `DISK_SIZE` | `256G` | Size of the main data disk. |
| `DISK_FMT` | `raw` | Disk image format: `raw` or `qcow2`. |
| `DISK_TYPE` | `scsi` | Disk device type, such as `sata`, `scsi`, `nvme`, or `blk`. |
| `DISK_CACHE` | `none` | Disk cache mode, such as `none` or `writeback`. |
| `DISK_IO` | `native` | Disk I/O mode, such as `native`, `threads`, or `io_uring`. |
| `DISK_DISCARD` | `unmap` | Discard/TRIM mode for the primary disk. |
| `DISK_ROTATION` | `1` | Rotation rate reported to the guest. Use `1` to identify the disk as an SSD. |
| `DISK_FLAGS` | | Additional options used when creating `qcow2` disks. |
| `ALLOCATE` | `N` | Preallocates space for the data disks. |
| `STORAGE` | `/storage` | Storage directory used for disks, settings, and downloads. |
## 🌐 Networking
| Variable | Default | Description |
|---|---|---|
| `NETWORK` | | Network mode, such as `nat`, `user`, or `N` to disable networking. |
| `DHCP` | `N` | Enables macvtap networking so DSM receives an address from the external LAN through DHCP. |
| `HOST` | `VirtualDSM` | Hostname assigned to DSM. |
| `IP` | | Overrides the automatically selected guest IPv4 address. |
| `MAC` | | Guest network adapter MAC address. |
| `ADAPTER` | `virtio-net-pci` | QEMU network adapter model. |
| `DEV` | `eth0` | Container network interface used as the uplink. |
| `MTU` | | MTU assigned to the guest network interface. |
| `MASK` | `255.255.255.0` | IPv4 netmask. |
| `TAP` | `dsm` | TAP or macvtap interface name. |
| `BRIDGE` | `docker` | Bridge name used for NAT networking. |
| `HOST_PORTS` | | Ports excluded from guest forwarding. |
| `USER_PORTS` | | Additional ports to forward to DSM when using user-mode networking. |
| `DNSMASQ_OPTS` | | Additional options passed to dnsmasq. |
| `DNSMASQ_DEBUG` | `N` | Enables dnsmasq debug output. |
| `DNSMASQ_DISABLE` | `N` | Disables the internal dnsmasq resolver. |
| `PASST_OPTS` | | Additional options passed to passt. |
| `PASST_DEBUG` | `N` | Enables passt debug output. |
## 🖥️ Display
| Variable | Default | Description |
|---|---|---|
| `DISPLAY` | `none` | Display backend, such as `vnc`, `disabled`, or `none`. |
| `LOSSY` | `N` | Enables lossy VNC compression to reduce bandwidth usage. |
| `VGA` | `none` | QEMU video adapter model. |
| `GPU` | `N` | Enables Intel iGPU acceleration. |
| `RENDERNODE` | `/dev/dri/renderD128` | Render node used for GPU acceleration. |
## ⚙️ System
| Variable | Default | Description |
|---|---|---|
| `MACHINE` | `q35` | QEMU machine type. |
| `ARGUMENTS` | | Additional raw arguments appended to the QEMU command line. |
## 🔌 Shutdown
| Variable | Default | Description |
|---|---|---|
| `SHUTDOWN` | `Y` | Enables graceful shutdown. |
| `TIMEOUT` | `115` | Maximum time, in seconds, to wait before forcing DSM to stop. |
| `API_TIMEOUT` | `90` | Maximum time, in seconds, to wait for the shutdown API call. |
## 🐞 Debugging
| Variable | Default | Description |
|---|---|---|
| `DEBUG` | `N` | Enables verbose debug output. |
| `TRACE` | `N` | Enables shell command tracing. |
| `HOST_DEBUG` | `N` | Enables debug output for the DSM host helper. |
+2
View File
@@ -18,6 +18,8 @@ metadata:
name: dsm name: dsm
spec: spec:
replicas: 1 replicas: 1
strategy:
type: Recreate
selector: selector:
matchLabels: matchLabels:
app: dsm app: dsm
+67 -46
View File
@@ -16,13 +16,17 @@ 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
- Upgrades supported - Web-based access to the DSM desktop
- Near-native performance with KVM acceleration
- Customizable CPU, memory, and storage allocation
- Supports multiple disks and physical disk passthrough
- Supports NAT, user-mode, macvlan, and macvtap networking
## Usage 🐳 ## Usage 🐳
##### Via Docker Compose: ##### Docker Compose:
```yaml ```yaml
services: services:
@@ -44,22 +48,32 @@ services:
stop_grace_period: 2m stop_grace_period: 2m
``` ```
##### Via Docker CLI: ##### Docker CLI:
```bash ```bash
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 vdsm/virtual-dsm docker run -it --rm --name dsm -e "DISK_SIZE=256G" -p 5000:5000 --device=/dev/kvm --device=/dev/net/tun --cap-add NET_ADMIN -v "${PWD:-.}/dsm:/storage" --stop-timeout 120 docker.io/vdsm/virtual-dsm
``` ```
##### Via Kubernetes: ##### Kubernetes:
```shell ```shell
kubectl apply -f https://raw.githubusercontent.com/vdsm/virtual-dsm/refs/heads/master/kubernetes.yml kubectl apply -f https://raw.githubusercontent.com/vdsm/virtual-dsm/refs/heads/master/kubernetes.yml
``` ```
##### Via Github Codespaces: ##### GitHub Codespaces:
[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/vdsm/virtual-dsm) [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/vdsm/virtual-dsm)
## Requirements ⚙️
- Docker or Podman on a Linux host with KVM support.
- Docker Desktop or Podman (Desktop) on Windows 11 with nested virtualization enabled.
- At least 2 GB of available RAM.
- At least 32 GB of free disk space.
> [!NOTE]
> Docker Desktop on Linux, macOS, and Windows 10 does not currently provide KVM access to containers and is therefore not supported.
## FAQ 💬 ## FAQ 💬
### How do I use it? ### How do I use it?
@@ -70,7 +84,7 @@ kubectl apply -f https://raw.githubusercontent.com/vdsm/virtual-dsm/refs/heads/m
- Wait until DSM finishes its installation - Wait until DSM finishes its installation
- Choose an username and password, and you will be taken to the desktop. - 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! Enjoy your brand new NAS, and don't forget to star this repo!
@@ -95,7 +109,7 @@ kubectl apply -f https://raw.githubusercontent.com/vdsm/virtual-dsm/refs/heads/m
``` ```
> [!TIP] > [!TIP]
> This can also be used to resize the existing disk to a larger capacity without any data loss. > This can also be used to resize an existing disk to a larger capacity without any data loss.
### How do I add multiple disks? ### How do I add multiple disks?
@@ -110,9 +124,9 @@ kubectl apply -f https://raw.githubusercontent.com/vdsm/virtual-dsm/refs/heads/m
- ./example3:/storage3 - ./example3:/storage3
``` ```
### How do I pass-through a disk? ### How do I pass through a disk?
It is possible to pass-through disk devices or partitions directly by adding them to your compose file in this way: You can pass through disk devices or partitions directly by adding them to your compose file in this way:
```yaml ```yaml
devices: devices:
@@ -124,7 +138,7 @@ kubectl apply -f https://raw.githubusercontent.com/vdsm/virtual-dsm/refs/heads/m
### How do I change the amount of CPU or RAM? ### How do I change the amount of CPU or RAM?
By default, the container will be allowed to use a maximum of 2 CPU cores and 2 GB of RAM. By default, Virtual DSM will be allowed to use 2 CPU cores and 2 GB of RAM.
If you want to adjust this, you can specify the desired amount using the following environment variables: If you want to adjust this, you can specify the desired amount using the following environment variables:
@@ -134,34 +148,6 @@ kubectl apply -f https://raw.githubusercontent.com/vdsm/virtual-dsm/refs/heads/m
CPU_CORES: "4" CPU_CORES: "4"
``` ```
### How do I verify if my system supports KVM?
First check if your software is compatible using this chart:
| **Product** | **Linux** | **Win11** | **Win10** | **macOS** |
|---|---|---|---|---|
| Docker CLI | ✅ | ✅ | ❌ | ❌ |
| Docker Desktop | ❌ | ✅ | ❌ | ❌ |
| Podman CLI | ✅ | ✅ | ❌ | ❌ |
| Podman Desktop | ✅ | ✅ | ❌ | ❌ |
After that you can run the following commands in Linux to check your system:
```bash
sudo apt install cpu-checker
sudo kvm-ok
```
If you receive an error from `kvm-ok` indicating that KVM cannot be used, please check whether:
- the virtualization extensions (`Intel VT-x` or `AMD SVM`) are enabled in your BIOS.
- you enabled "nested virtualization" if you are running the container inside a virtual machine.
- you are not using a cloud provider, as most of them do not allow nested virtualization for their VPS's.
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.
### How do I assign an individual IP address to the container? ### How do I assign an individual IP address to the container?
By default, the container uses bridge networking, which shares the IP address with the host. By default, the container uses bridge networking, which shares the IP address with the host.
@@ -214,9 +200,9 @@ kubectl apply -f https://raw.githubusercontent.com/vdsm/virtual-dsm/refs/heads/m
- 'c *:* rwm' - 'c *:* rwm'
``` ```
### How do I pass-through the GPU? ### How do I pass through the GPU?
To pass-through your Intel GPU, add the following lines to your compose file: To pass through your Intel GPU, add the following lines to your compose file:
```yaml ```yaml
environment: environment:
@@ -248,18 +234,53 @@ kubectl apply -f https://raw.githubusercontent.com/vdsm/virtual-dsm/refs/heads/m
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. Replace the example path `./DSM_VirtualDSM_42218.pat` with the filename of your desired `.pat` file. The value of `URL` will be ignored in this case.
### Are these all available options?
No. For a complete overview of all supported settings, see the [environment variables](docs/environment.md) page.
### How do I verify that KVM is available?
First, make sure your platform and container runtime meet the [requirements](#requirements-) listed above.
On a Linux host, install `cpu-checker` and run:
```bash
sudo apt install cpu-checker
sudo kvm-ok
```
A working configuration should report:
```text
KVM acceleration can be used
```
You can also verify that the KVM device exists:
```bash
ls -l /dev/kvm
```
If KVM is unavailable, check whether:
- Hardware virtualization (`Intel VT-x` or `AMD-V`) is enabled in your BIOS or UEFI.
- Nested virtualization is enabled when the host itself is a virtual machine.
- Your VPS or cloud provider supports nested virtualization.
If `kvm-ok` succeeds but the container still reports that KVM is unavailable, you can temporarily add `privileged: true` to your Compose file to rule out a permission or device-access issue.
### What are the differences compared to the standard DSM? ### 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. 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? ### 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 will be considered 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. 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 🌟 ## Stars 🌟
[![Stars](https://starchart.cc/vdsm/virtual-dsm.svg?variant=adaptive)](https://starchart.cc/vdsm/virtual-dsm) [![Stargazers](https://raw.githubusercontent.com/star-stats/stars/refs/heads/data/charts/vdsm-virtual-dsm.svg)](https://github.com/vdsm/virtual-dsm/stargazers)
## Disclaimer ⚖️ ## Disclaimer ⚖️
+14 -9
View File
@@ -1,28 +1,33 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -Eeuo pipefail set -Eeuo pipefail
cd /run
. utils.sh # Load functions
: "${DHCP:="N"}"
: "${NETWORK:="Y"}" : "${NETWORK:="Y"}"
[ -f "/run/shm/qemu.end" ] && echo "QEMU is shutting down.." && exit 1 [ -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 [ ! -s "/run/shm/qemu.pid" ] && echo "QEMU is not running yet..." && exit 0
[[ "$NETWORK" == [Nn]* ]] && echo "Networking is disabled.." && exit 0 disabled "$NETWORK" && echo "Networking is disabled." && exit 0
file="/run/shm/dsm.url" file="/run/shm/dsm.url"
address="/run/shm/qemu.ip" address="/run/shm/qemu.ip"
gateway="/run/shm/qemu.gw"
[ ! -s "$file" ] && echo "DSM has not enabled networking yet.." && exit 1 [ ! -s "$file" ] && echo "DSM has not enabled networking yet..." && exit 1
location=$(<"$file") location=$(<"$file")
if ! curl -m 20 -ILfSs "http://$location/" > /dev/null; then if ! curl -m 20 -ILfSs "http://$location/" > /dev/null; then
if [[ "$location" == "20.20"* ]]; then if enabled "$DHCP"; then
ip="20.20.20.1" ip=$(<"$address")
echo "Failed to reach DSM at http://$location"
else
ip=$(<"$gateway")
port="${location##*:}" port="${location##*:}"
echo "Failed to reach DSM at port $port" echo "Failed to reach DSM at port $port"
else
echo "Failed to reach DSM at http://$location"
ip=$(<"$address")
fi fi
echo "You might need to whitelist IP $ip in the DSM firewall." && exit 1 echo "You might need to whitelist IP $ip in the DSM firewall." && exit 1
+50 -30
View File
@@ -2,42 +2,62 @@
set -Eeuo pipefail set -Eeuo pipefail
DEF_OPTS="-nodefaults -boot strict=on" DEF_OPTS="-nodefaults -boot strict=on"
RAM_OPTS=$(echo "-m ${RAM_SIZE^^}" | sed 's/MB/M/g;s/GB/G/g;s/TB/T/g') DEV_OPTS=""
CPU_OPTS="-cpu $CPU_FLAGS -smp $CPU_CORES,sockets=1,dies=1,cores=$CPU_CORES,threads=1"
MAC_OPTS="-machine type=q35,smm=off,usb=off,vmport=off,dump-guest-core=off,hpet=off${KVM_OPTS}"
DEV_OPTS="-device virtio-balloon-pci,id=balloon0,bus=pcie.0,addr=0x4"
DEV_OPTS+=" -object rng-random,id=objrng0,filename=/dev/urandom"
DEV_OPTS+=" -device virtio-rng-pci,rng=objrng0,id=rng0,bus=pcie.0,addr=0x1c"
ARGS="$DEF_OPTS $CPU_OPTS $RAM_OPTS $MAC_OPTS $DISPLAY_OPTS $MON_OPTS $SERIAL_OPTS $NET_OPTS $DISK_OPTS $DEV_OPTS $ARGUMENTS" configureProcessor() {
ARGS=$(echo "$ARGS" | sed 's/\t/ /g' | tr -s ' ')
# Check available memory as the very last step CPU_OPTS="-cpu $CPU_FLAGS"
CPU_OPTS+=" -smp $CPU_CORES,sockets=1,dies=1,cores=$CPU_CORES,threads=1"
if [[ "$RAM_CHECK" != [Nn]* ]]; then return 0
}
RAM_AVAIL=$(free -b | grep -m 1 Mem: | awk '{print $7}') configureMemory() {
AVAIL_MEM=$(formatBytes "$RAM_AVAIL")
if (( (RAM_WANTED + RAM_SPARE) > RAM_AVAIL )); then RAM_OPTS=$(echo "-m ${RAM_SIZE^^}" | sed 's/MB/M/g;s/GB/G/g;s/TB/T/g')
msg="Your configured RAM_SIZE of ${RAM_SIZE/G/ GB} is too high for the $AVAIL_MEM of memory available, please set a lower value."
[[ "${FS,,}" != "zfs" ]] && error "$msg" && exit 17
info "$msg"
else
if (( (RAM_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 memory available, please consider a lower value."
if [[ "${FS,,}" != "zfs" ]]; then
warn "$msg"
else
info "$msg"
fi
fi
fi
fi return 0
}
if [[ "$DEBUG" == [Yy1]* ]]; then configureMonitor() {
printf "Arguments:\n\n%s\n\n" "${ARGS// -/$'\n-'}"
fi MON_OPTS="-name $PROCESS,process=$PROCESS,debug-threads=on"
MON_OPTS+=" -pidfile $QEMU_PID"
return 0
}
configureMachine() {
MAC_OPTS="-machine type=$MACHINE,smm=off,usb=off"
MAC_OPTS+=",vmport=off,dump-guest-core=off,hpet=off${KVM_OPTS}"
return 0
}
configureVirtioDevices() {
DEV_OPTS="-device virtio-balloon-pci,id=balloon0,bus=pcie.0,addr=0x4"
DEV_OPTS+=" -object rng-random,id=objrng0,filename=/dev/urandom"
DEV_OPTS+=" -device virtio-rng-pci,rng=objrng0,id=rng0,bus=pcie.0,addr=0x1c"
return 0
}
buildArguments() {
ARGS="$DEF_OPTS $CPU_OPTS $RAM_OPTS $MAC_OPTS $DISPLAY_OPTS $MON_OPTS $SERIAL_OPTS $NET_OPTS $DISK_OPTS $DEV_OPTS $ARGUMENTS"
ARGS=$(echo "$ARGS" | sed 's/\t/ /g' | tr -s ' ')
return 0
}
configureMemory
configureMonitor
configureMachine
configureProcessor
configureVirtioDevices
buildArguments
return 0 return 0
+539 -338
View File
File diff suppressed because it is too large Load Diff
+23 -3
View File
@@ -6,19 +6,35 @@ set -Eeuo pipefail
: "${GPU:="N"}" # GPU passthrough : "${GPU:="N"}" # GPU passthrough
: "${VGA:="virtio"}" # VGA adaptor : "${VGA:="virtio"}" # VGA adaptor
: "${DISPLAY:="none"}" # Display type : "${DISPLAY:="none"}" # Display type
: "${LOSSY:="N"}" # Lossy VNC compression
: "${RENDERNODE:="/dev/dri/renderD128"}" # Render node : "${RENDERNODE:="/dev/dri/renderD128"}" # Render node
# Sanitize variables
VGA=$(strip "$VGA")
LOSSY=$(strip "$LOSSY")
DISPLAY=$(strip "$DISPLAY")
RENDERNODE=$(strip "$RENDERNODE")
CPU_VENDOR=$(lscpu | awk '/Vendor ID/{print $3}') CPU_VENDOR=$(lscpu | awk '/Vendor ID/{print $3}')
if [[ "$GPU" != [Yy1]* || "$CPU_VENDOR" != "GenuineIntel" || "$ARCH" != "amd64" ]]; then if ! enabled "$GPU" || isAmdCpu || [[ "$ARCH" != "amd64" ]]; then
[[ "${DISPLAY,,}" == "none" ]] && VGA="none" [[ "${DISPLAY,,}" == "none" ]] && VGA="none"
DISPLAY_OPTS="-display $DISPLAY -vga $VGA"
if enabled "$LOSSY" && [[ "${DISPLAY,,}" == vnc=* ]]; then
DISPLAY+=",lossy=on"
fi
DISPLAY_OPTS="-display ${DISPLAY} -vga ${VGA}"
return 0 return 0
fi fi
DISPLAY_OPTS="-display egl-headless,rendernode=$RENDERNODE" msg="Configuring display drivers..."
html "$msg"
enabled "$DEBUG" && echo "$msg"
DISPLAY_OPTS="-display egl-headless,rendernode=${RENDERNODE}"
DISPLAY_OPTS+=" -vga $VGA" DISPLAY_OPTS+=" -vga $VGA"
[ ! -d /dev/dri ] && mkdir -m 755 /dev/dri [ ! -d /dev/dri ] && mkdir -m 755 /dev/dri
@@ -39,6 +55,10 @@ if [ ! -c "$RENDERNODE" ]; then
fi fi
fi fi
if [ ! -c "$RENDERNODE" ] || [ ! -r "$RENDERNODE" ] || [ ! -w "$RENDERNODE" ]; then
warn "render device '${RENDERNODE}' is unavailable or inaccessible."
fi
addPackage "xserver-xorg-video-intel" "Intel GPU drivers" addPackage "xserver-xorg-video-intel" "Intel GPU drivers"
addPackage "qemu-system-modules-opengl" "OpenGL module" addPackage "qemu-system-modules-opengl" "OpenGL module"
+22 -10
View File
@@ -1,13 +1,16 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -Eeuo pipefail set -Eeuo pipefail
: "${PLATFORM:="x64"}"
: "${APP:="Virtual DSM"}" : "${APP:="Virtual DSM"}"
: "${SUPPORT:="https://github.com/vdsm/virtual-dsm"}" : "${SUPPORT:="https://github.com/vdsm/virtual-dsm"}"
cd /run cd /run
. start.sh # Startup hook
. utils.sh # Load functions . utils.sh # Load functions
. reset.sh # Initialize system . reset.sh # Initialize system
. server.sh # Start webserver
. install.sh # Run installation . install.sh # Run installation
. disk.sh # Initialize disks . disk.sh # Initialize disks
. display.sh # Initialize graphics . display.sh # Initialize graphics
@@ -15,23 +18,32 @@ cd /run
. proc.sh # Initialize processor . proc.sh # Initialize processor
. serial.sh # Initialize serialport . serial.sh # Initialize serialport
. power.sh # Configure shutdown . power.sh # Configure shutdown
. memory.sh # Check available memory
. config.sh # Configure arguments . config.sh # Configure arguments
. finish.sh # Finish initialization
trap - ERR trap - ERR
version=$(qemu-system-x86_64 --version | head -n 1 | cut -d '(' -f 1 | awk '{ print $NF }') cmd=(qemu-system-x86_64)
info "Booting $APP using QEMU v$version..." version=$("${cmd[@]}" --version | awk 'NR==1 { print $4 }')
info "Booting $APP using QEMU v$version..." && echo
if [[ "$CONSOLE" == [Yy]* ]]; then if ! enabled "$SHUTDOWN"; then
exec qemu-system-x86_64 ${ARGS:+ $ARGS} exec "${cmd[@]}" ${ARGS:+ $ARGS}
fi fi
{ qemu-system-x86_64 ${ARGS:+ $ARGS} >"$QEMU_OUT" 2>"$QEMU_LOG"; rc=$?; } || : if ! interactive; then
(( rc != 0 )) && error "$(<"$QEMU_LOG")" && exit 15 "${cmd[@]}" ${ARGS:+ $ARGS} &
else
startConsole
startQemu "${cmd[@]}" ${ARGS:+ $ARGS}
fi
terminal pid=$!
tail -fn +0 "$QEMU_LOG" 2>/dev/null & rc=0
cat "$QEMU_TERM" 2>/dev/null & wait $! || :
wait "$pid" || rc=$?
[ -f "$QEMU_END" ] && exit "$rc"
sleep 1 & wait $! sleep 1 & wait $!
[ ! -f "$QEMU_END" ] && finish 0 finish "$rc"
+8
View File
@@ -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
+87 -43
View File
@@ -17,7 +17,7 @@ DIR=$(find / -maxdepth 1 -type d -iname "$FN" -print -quit)
[ ! -d "$DIR" ] && DIR=$(find "$STORAGE" -maxdepth 1 -type d -iname "$FN" -print -quit) [ ! -d "$DIR" ] && DIR=$(find "$STORAGE" -maxdepth 1 -type d -iname "$FN" -print -quit)
if [ -d "$DIR" ]; then if [ -d "$DIR" ]; then
BASE="DSM_VirtualDSM" && URL="file://$DIR" BASE="DSM_VirtualDSM" && URL="file://$DIR"
if [[ ! -s "$STORAGE/$BASE.boot.img" || ! -s "$STORAGE/$BASE.system.img" ]]; then 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 error "The bind $DIR maps to a file that does not exist!" && exit 65
fi fi
@@ -25,16 +25,18 @@ fi
FILE=$(find / -maxdepth 1 -type f -iname "$FN" -print -quit) 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" ] && FILE=$(find "$STORAGE" -maxdepth 1 -type f -iname "$FN" -print -quit)
[ -s "$FILE" ] && BASE="DSM_VirtualDSM" && URL="file://$FILE" [ -s "$FILE" ] && BASE="DSM_VirtualDSM" && URL="file://$FILE"
URL=$(strip "$URL")
if [ -n "$URL" ] && [ ! -s "$FILE" ] && [ ! -d "$DIR" ]; then if [ -n "$URL" ] && [ ! -s "$FILE" ] && [ ! -d "$DIR" ]; then
BASE=$(basename "$URL" .pat) BASE=$(basename "$URL" .pat)
if [ ! -s "$STORAGE/$BASE.system.img" ]; then if [ ! -s "$STORAGE/$BASE.system.img" ]; then
BASE=$(basename "${URL%%\?*}" .pat) BASE=$(basename "${URL%%\?*}" .pat)
: "${BASE//+/ }"; printf -v BASE '%b' "${_//%/\\x}" printf -v BASE '%b' "${BASE//%/\\x}"
BASE=$(echo "$BASE" | sed -e 's/[^A-Za-z0-9._-]/_/g') BASE="${BASE//[!A-Za-z0-9._-]/_}"
fi fi
if [[ "${URL,,}" != "http"* && "${URL,,}" != "file:"* ]] ; then if [[ "${URL,,}" != "http"* && "${URL,,}" != "file:"* ]]; then
[ ! -s "$STORAGE/$BASE.pat" ] && error "Invalid URL: $URL" && exit 65 [ ! -s "$STORAGE/$BASE.pat" ] && error "Invalid URL: $URL" && exit 65
URL="file://$STORAGE/$BASE.pat" URL="file://$STORAGE/$BASE.pat"
fi fi
@@ -65,8 +67,8 @@ fi
if [ ! -s "$FILE" ]; then if [ ! -s "$FILE" ]; then
BASE=$(basename "${URL%%\?*}" .pat) BASE=$(basename "${URL%%\?*}" .pat)
: "${BASE//+/ }"; printf -v BASE '%b' "${_//%/\\x}" printf -v BASE '%b' "${BASE//%/\\x}"
BASE=$(echo "$BASE" | sed -e 's/[^A-Za-z0-9._-]/_/g') BASE="${BASE//[!A-Za-z0-9._-]/_}"
fi fi
if [[ "$URL" != "file://$STORAGE/$BASE.pat" ]]; then if [[ "$URL" != "file://$STORAGE/$BASE.pat" ]]; then
@@ -80,16 +82,16 @@ rm -f "$STORAGE/$BASE.system.img"
# Check filesystem # Check filesystem
FS=$(stat -f -c %T "$STORAGE") FS=$(stat -f -c %T "$STORAGE")
if [[ "${FS,,}" == "overlay"* ]]; then if [[ "${FS,,}" == "overlay"* && "${ENGINE,,}" == "docker" ]]; then
info "Warning: the filesystem of $STORAGE is OverlayFS, this usually means it was binded to an invalid path!" warn "the filesystem of $STORAGE is OverlayFS, this usually means it was binded to an invalid path!"
fi fi
if [[ "${FS,,}" == "fuse"* ]]; then if [[ "${FS,,}" == "fuse"* ]]; then
info "Warning: the filesystem of $STORAGE is FUSE, this extra layer will negatively affect performance!" warn "the filesystem of $STORAGE is FUSE, this extra layer will negatively affect performance!"
fi fi
if [[ "${FS,,}" == "ecryptfs" || "${FS,,}" == "tmpfs" ]]; then if [[ "${FS,,}" == "ecryptfs" || "${FS,,}" == "tmpfs" ]]; then
info "Warning: the filesystem of $STORAGE is $FS, which does not support O_DIRECT mode, adjusting settings..." warn "the filesystem of $STORAGE is $FS, which does not support O_DIRECT mode, adjusting settings..."
fi fi
if [[ "${FS,,}" == "fat"* || "${FS,,}" == "vfat"* || "${FS,,}" == "msdos"* ]]; then if [[ "${FS,,}" == "fat"* || "${FS,,}" == "vfat"* || "${FS,,}" == "msdos"* ]]; then
@@ -98,6 +100,10 @@ fi
if [[ "${FS,,}" != "exfat"* && "${FS,,}" != "ntfs"* && "${FS,,}" != "unknown"* ]]; then if [[ "${FS,,}" != "exfat"* && "${FS,,}" != "ntfs"* && "${FS,,}" != "unknown"* ]]; then
TMP="$STORAGE/tmp" TMP="$STORAGE/tmp"
rm -rf "$TMP"
if ! makeDir "$TMP"; then
error "Failed to create directory \"$TMP\" !" && exit 93
fi
else else
TMP="/tmp/dsm" TMP="/tmp/dsm"
TMP_SPACE=2147483648 TMP_SPACE=2147483648
@@ -106,10 +112,9 @@ else
if (( TMP_SPACE > SPACE )); then if (( TMP_SPACE > SPACE )); then
error "Not enough free space inside the container, have $SPACE_MB available but need at least 2 GB." && exit 93 error "Not enough free space inside the container, have $SPACE_MB available but need at least 2 GB." && exit 93
fi fi
rm -rf "$TMP" && mkdir -p "$TMP"
fi fi
rm -rf "$TMP" && mkdir -p "$TMP"
# Check free diskspace # Check free diskspace
ROOT_SPACE=536870912 ROOT_SPACE=536870912
SPACE=$(df --output=avail -B 1 / | tail -n 1) SPACE=$(df --output=avail -B 1 / | tail -n 1)
@@ -121,13 +126,6 @@ SPACE=$(df --output=avail -B 1 "$STORAGE" | tail -n 1)
SPACE_GB=$(formatBytes "$SPACE") 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 (( MIN_SPACE > SPACE )) && error "Not enough free space for installation in $STORAGE, have $SPACE_GB available but need at least 14 GB." && exit 94
# Check if output is to interactive TTY
if [ -t 1 ]; then
PROGRESS="--progress=bar:noscroll"
else
PROGRESS="--progress=dot:giga"
fi
if [[ "$URL" == "file://"* ]]; then if [[ "$URL" == "file://"* ]]; then
MSG="Copying DSM" MSG="Copying DSM"
ERR="Failed to copy ${URL:7}" ERR="Failed to copy ${URL:7}"
@@ -154,20 +152,53 @@ if [[ "$URL" == "file://"* ]]; then
else else
SIZE=0 SIZE=0
REASON=""
PROGRESS=()
OUTPUT=""
LOG=$(mktemp)
[[ "${URL,,}" == *"_72806.pat" ]] && SIZE=361010261 [[ "${URL,,}" == *"_72806.pat" ]] && SIZE=361010261
[[ "${URL,,}" == *"_69057.pat" ]] && SIZE=363837333 [[ "${URL,,}" == *"_69057.pat" ]] && SIZE=363837333
[[ "${URL,,}" == *"_42218.pat" ]] && SIZE=379637760 [[ "${URL,,}" == *"_42218.pat" ]] && SIZE=379637760
/run/progress.sh "$PAT" "$SIZE" "$MSG ([P])..." & # Use Wget's progress bar in a terminal and progress.sh in container logs.
if [ -t 1 ]; then
PROGRESS=( --show-progress --progress=bar:noscroll )
else
OUTPUT="log"
fi
{ wget "$URL" -O "$PAT" -q --no-check-certificate --timeout=10 --no-http-keep-alive --show-progress "$PROGRESS"; rc=$?; } || : /run/progress.sh "$PAT" "$SIZE" "$MSG ([P])..." "$OUTPUT" 52428800 &
{
LC_ALL=C wget "$URL" -O "$PAT" --no-verbose --no-check-certificate \
--timeout=30 --no-http-keep-alive "${PROGRESS[@]}" \
--output-file="$LOG"
rc=$?
} || :
fKill "progress.sh" fKill "progress.sh"
(( rc == 3 )) && error "$ERR , cannot write file (disk full?)" && exit 69 if (( rc != 0 )); then
(( rc == 4 )) && error "$ERR , network failure!" && exit 69 REASON=$(sed -n \
(( rc == 8 )) && error "$ERR , server issued an error response!" && exit 69 -e 's/^wget: //p' \
(( rc != 0 )) && error "$ERR , reason: $rc" && exit 69 -e 's/^[0-9-]\{10\} [0-9:]\{8\} ERROR //p' \
"$LOG" | tail -n 1)
fi
rm -f "$LOG"
if (( rc == 3 )); then
error "$ERR because the file could not be written (disk full?)."
exit 69
elif (( rc != 0 )); then
if [ -n "$REASON" ]; then
error "$ERR: ${REASON%.}."
else
error "$ERR with exit status $rc."
fi
exit 69
fi
fi fi
@@ -200,11 +231,12 @@ fi
MSG="Preparing system partition..." MSG="Preparing system partition..."
info "Install: $MSG" && html "$MSG" info "Install: $MSG" && html "$MSG"
BOOT=$(find "$TMP" -name "*.bin.zip") BOOT=$(find "$TMP" -name "*.bin.zip" -print -quit)
[ ! -s "$BOOT" ] && error "The PAT file contains no boot image." && exit 67 [ -z "$BOOT" ] && error "The PAT file contains no boot image." && exit 67
[ ! -s "$BOOT" ] && error "The PAT boot image archive is empty." && exit 67
BOOT=$(echo "$BOOT" | head -c -5) unzip -q -o "$BOOT" -d "$TMP"
unzip -q -o "$BOOT".zip -d "$TMP" BOOT="${BOOT%.zip}"
SYSTEM="$STORAGE/$BASE.system.img" SYSTEM="$STORAGE/$BASE.system.img"
rm -f "$SYSTEM" rm -f "$SYSTEM"
@@ -222,6 +254,8 @@ if ! touch "$SYSTEM"; then
error "Could not create file $SYSTEM for the system disk." && exit 98 error "Could not create file $SYSTEM for the system disk." && exit 98
fi fi
! setOwner "$SYSTEM" && warn "failed to set the owner for \"$SYSTEM\" !"
if [[ "${FS,,}" == "btrfs" ]]; then if [[ "${FS,,}" == "btrfs" ]]; then
{ chattr +C "$SYSTEM"; } || : { chattr +C "$SYSTEM"; } || :
FA=$(lsattr "$SYSTEM") FA=$(lsattr "$SYSTEM")
@@ -241,20 +275,25 @@ fi
PART="$TMP/partition.fdisk" PART="$TMP/partition.fdisk"
{ echo "label: dos" {
echo "label-id: 0x6f9ee2e9" echo "label: dos"
echo "device: $SYSTEM" echo "label-id: 0x6f9ee2e9"
echo "unit: sectors" echo "device: $SYSTEM"
echo "sector-size: 512" echo "unit: sectors"
echo "" echo "sector-size: 512"
echo "${SYSTEM}1 : start= 2048, size= 16777216, type=83" echo ""
echo "${SYSTEM}2 : start= 16779264, size= 4194304, type=82" echo "${SYSTEM}1 : start= 2048, size= 16777216, type=83"
echo "${SYSTEM}2 : start= 16779264, size= 4194304, type=82"
} > "$PART" } > "$PART"
sfdisk -q "$SYSTEM" < "$PART" sfdisk -q "$SYSTEM" < "$PART"
MOUNT="$TMP/system" MOUNT="$TMP/system"
rm -rf "$MOUNT" && mkdir -p "$MOUNT" rm -rf "$MOUNT"
if ! makeDir "$MOUNT"; then
error "Failed to create directory \"$MOUNT\" !" && exit 93
fi
MSG="Extracting system partition..." MSG="Extracting system partition..."
info "Install: $MSG" && html "$MSG" info "Install: $MSG" && html "$MSG"
@@ -270,8 +309,9 @@ 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-"*
INDEX_DB="$MOUNT/usr/syno/synoman/indexdb"
if [ -s "$IDB.txz" ]; then if [ -s "$IDB.txz" ]; then
INDEX_DB="$MOUNT/usr/syno/synoman/indexdb"
mkdir -p "$INDEX_DB" mkdir -p "$INDEX_DB"
fi fi
@@ -289,6 +329,7 @@ fakeroot -- bash -c "set -Eeu;\
rm -rf "$MOUNT" rm -rf "$MOUNT"
echo "$BASE" > "$STORAGE/dsm.ver" echo "$BASE" > "$STORAGE/dsm.ver"
! setOwner "$STORAGE/dsm.ver" && warn "failed to set the owner for \"$STORAGE/dsm.ver\" !"
if [[ "$URL" == "file://$STORAGE/$BASE.pat" ]]; then if [[ "$URL" == "file://$STORAGE/$BASE.pat" ]]; then
rm -f "$PAT" rm -f "$PAT"
@@ -296,10 +337,13 @@ else
mv -f "$PAT" "$STORAGE/$BASE.pat" mv -f "$PAT" "$STORAGE/$BASE.pat"
fi fi
if [ -f "$STORAGE/$BASE.pat" ]; then
! setOwner "$STORAGE/$BASE.pat" && warn "failed to set the owner for \"$STORAGE/$BASE.pat\" !"
fi
mv -f "$BOOT" "$STORAGE/$BASE.boot.img" mv -f "$BOOT" "$STORAGE/$BASE.boot.img"
! setOwner "$STORAGE/$BASE.boot.img" && warn "failed to set the owner for \"$STORAGE/$BASE.boot.img\" !"
rm -rf "$TMP" rm -rf "$TMP"
html "Booting DSM instance..."
sleep 1.2
return 0 return 0
+112
View File
@@ -0,0 +1,112 @@
#!/usr/bin/env bash
set -Eeuo pipefail
msg="Checking memory..."
enabled "$DEBUG" && echo "$msg"
app() {
echo "Virtual DSM"
return 0
}
checkConfiguredMemory() {
if disabled "$RAM_CHECK" || [[ "${RAM_SIZE,,}" == "max" || "${RAM_SIZE,,}" == "half" ]]; then
return 0
fi
local wanted msg avail_mem
wanted=$(numfmt --from=iec "$RAM_SIZE")
avail_mem=$(formatBytes "$RAM_AVAIL")
if (( (wanted + RAM_SPARE) > RAM_AVAIL )); then
msg="Your configured RAM_SIZE of ${RAM_SIZE/G/ GB} is too high for the $avail_mem of free memory available,"
if [[ "${FS,,}" == "zfs" ]]; then
info "$msg but since ZFS is active this will be ignored."
else
RAM_SIZE="max"
warn "$msg it will automatically be adjusted to a lower amount."
fi
else
if (( (wanted + (RAM_SPARE * 3)) > RAM_AVAIL )); then
msg="your configured RAM_SIZE of ${RAM_SIZE/G/ GB} is very close to the $avail_mem of free memory available,"
if [[ "${FS,,}" == "zfs" ]]; then
info "$msg but since ZFS is active this will be ignored."
else
warn "$msg please consider a lower amount."
fi
fi
fi
return 0
}
configureHalfMemory() {
local wanted
if [[ "${RAM_SIZE,,}" != "half" ]]; then
return 0
fi
if (( (RAM_AVAIL / 2) > RAM_SPARE )); then
wanted=$(( (RAM_AVAIL / 2) / 1048577 ))
RAM_SIZE="${wanted}M"
info "Allocated $wanted MB of RAM for $(app)."
else
RAM_SIZE="max"
fi
return 0
}
configureMaxMemory() {
local wanted
if [[ "${RAM_SIZE,,}" != "max" ]]; then
return 0
fi
if (( RAM_AVAIL < (RAM_SPARE * 2) )); then
wanted=$(( RAM_AVAIL / 2 ))
else
wanted=$(( RAM_AVAIL - (RAM_SPARE * 3) ))
if (( wanted < (RAM_SPARE * 6) )); then
wanted=$(( RAM_AVAIL - RAM_SPARE ))
fi
fi
wanted=$(( wanted / 1048577 ))
RAM_SIZE="${wanted}M"
info "Allocated $wanted MB of RAM for $(app)."
return 0
}
checkMinimumMemory() {
local wanted
wanted=$(numfmt --from=iec "$RAM_SIZE")
if [ "$wanted" -lt "$RAM_MINIMUM" ]; then
wanted=$(( wanted / 1048577 ))
error "Not enough memory available, there is only $wanted MB left!"
exit 16
fi
return 0
}
getMemoryInfo
checkConfiguredMemory
configureHalfMemory
configureMaxMemory
checkMinimumMemory
return 0
+1961 -329
View File
File diff suppressed because it is too large Load Diff
+330 -121
View File
@@ -1,137 +1,260 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -Eeuo pipefail set -Eeuo pipefail
: "${SHUTDOWN:="Y"}" # Graceful ACPI shutdown
: "${TIMEOUT:="105"}" # QEMU termination timeout
: "${API_TIMEOUT:="90"}" # External API call timeout
# Configure QEMU for graceful shutdown # Configure QEMU for graceful shutdown
API_CMD=6 API_CMD=6
API_HOST="127.0.0.1:2210"
: "${API_TIMEOUT:="50"}" # API Call timeout
QEMU_TERM="" SHUTDOWN_SKIP=0
QEMU_PORT=7100 SHUTDOWN_SIGNAL=0
: "${QEMU_TIMEOUT:="50"}" # QEMU Termination timeout
QEMU_DIR="/run/shm"
QEMU_PID="$QEMU_DIR/qemu.pid"
QEMU_LOG="$QEMU_DIR/qemu.log"
QEMU_OUT="$QEMU_DIR/qemu.out"
QEMU_END="$QEMU_DIR/qemu.end" QEMU_END="$QEMU_DIR/qemu.end"
CONSOLE_PID="$QEMU_DIR/console.pid"
if [[ "$KVM" == [Nn]* ]]; then CONSOLE_SOCKET="$QEMU_DIR/console.sock"
API_TIMEOUT=$(( API_TIMEOUT*2 )) QEMU_START_PID="$QEMU_DIR/qemu.start.pid"
QEMU_TIMEOUT=$(( QEMU_TIMEOUT*2 ))
fi
touch "$QEMU_LOG"
_trap() { _trap() {
func="$1" ; shift
for sig ; do local func="$1"; shift
local sig
TRAP_PID=$BASHPID
for sig; do
# Capture the local callback and signal while registering the trap.
# shellcheck disable=SC2064
trap "$func $sig" "$sig" trap "$func $sig" "$sig"
done done
return 0
}
signalCode() {
local sig="$1"
case "$sig" in
SIGHUP) echo 129 ;;
SIGINT) echo 130 ;;
SIGQUIT) echo 131 ;;
SIGABRT) echo 134 ;;
SIGTERM) echo 143 ;;
*) echo 0 ;;
esac
return 0
}
displayReason() {
local reason="$1"
case "$reason" in
129 ) echo "SIGHUP" ;;
130 ) echo "SIGINT" ;;
131 ) echo "SIGQUIT" ;;
134 ) echo "SIGABRT" ;;
143 ) echo "SIGTERM" ;;
* ) echo "$reason" ;;
esac
return 0
}
readQemuPid() {
local -n _pid="$1"
local file
for file in "$QEMU_START_PID" "$QEMU_PID"; do
if [ -s "$file" ] && read -r _pid < "$file"; then
return 0
fi
done
return 1
}
qemuPidFile() {
local -n _file="$1"
_file="$QEMU_PID"
[ -s "$QEMU_START_PID" ] && _file="$QEMU_START_PID"
return 0
}
waitQemuExit() {
local timeout="${1:-10}"
local file=""
qemuPidFile file
waitPidFile "$file" "$timeout"
}
waitQemuPid() {
local -n _pid="$1"
local cnt=0 value=""
while ! readQemuPid value; do
sleep 0.02
cnt=$((cnt + 1))
(( cnt >= 50 )) && return 1
done
_pid="$value"
return 0
}
forceKillQemu() {
local reason="$1"
local pid="" display
! readQemuPid pid && return 0
! isAlive "$pid" && return 0
display=$(displayReason "$reason")
error "Forcefully terminating $(app), reason: $display..."
{ disown "$pid" || :; kill -9 -- "$pid" || :; } 2>/dev/null
return 0
}
cleanupHelpers() {
local pids=( "${HOST_PID:-}" "${WSD_PID:-}" "${CONSOLE_PID:-}" \
"${WEB_PID:-}" "${PASST_PID:-}" "${DNSMASQ_PID:-}" )
mKill "${pids[@]}"
fKill "print.sh"
rm -f -- "$HOST_API_SOCKET" "$HOST_AGENT_SOCKET"
closeNetwork
return 0
}
startConsole() {
local output="${1:-/dev/tty}"
local cnt=0 pid=""
rm -f -- "$CONSOLE_SOCKET" "$CONSOLE_PID"
if ! stty -icanon -echo isig -ixon min 1 time 0 </dev/tty; then
error "Failed to configure serial console terminal!"
return 1
fi
(
trap '' INT QUIT
exec nc -lU "$CONSOLE_SOCKET" </dev/tty >"$output"
) &
pid=$!
echo "$pid" > "$CONSOLE_PID"
while [ ! -S "$CONSOLE_SOCKET" ]; do
if ! isAlive "$pid"; then
rm -f -- "$CONSOLE_PID"
error "Serial console relay exited unexpectedly!"
return 1
fi
sleep 0.02
cnt=$((cnt + 1))
if (( cnt > 100 )); then
error "Failed to start serial console relay!"
return 1
fi
done
return 0
}
stopConsole() {
mKill "$CONSOLE_PID"
return 0
}
startQemu() {
rm -f -- "$QEMU_START_PID"
(
trap '' INT QUIT
# shellcheck disable=SC2016
exec setsid -f -w sh -c '
file=$1
shift
"$@" &
pid=$!
printf "%s\n" "$pid" > "$file" || exit 1
rc=0
wait "$pid" 2>/dev/null || rc=$?
exit "$rc"
' sh "$QEMU_START_PID" "$@"
) </dev/null &
return 0
} }
finish() { finish() {
local pid local reason=$1 failed=0
local reason=$1
if [ ! -f "$QEMU_END" ] && (( reason != 0 )); then
failed=1
fi
touch "$QEMU_END" touch "$QEMU_END"
if [ -s "$QEMU_PID" ]; then forceKillQemu "$reason"
cleanupHelpers
pid=$(<"$QEMU_PID") if ! waitQemuExit 10; then
echo && error "Forcefully terminating QEMU process, reason: $reason..." warn "Timed out while waiting for $(app) to exit!"
{ kill -15 "$pid" || true; } 2>/dev/null
while isAlive "$pid"; do
sleep 1
# Workaround for zombie pid
[ ! -s "$QEMU_PID" ] && break
done
fi fi
fKill "print.sh" echo
fKill "host.bin"
closeNetwork if (( failed == 0 )); then
echo " Shutdown completed!"
sleep 1 else
echo && echo " Shutdown completed!" error "QEMU exited unexpectedly!"
fi
exit "$reason" exit "$reason"
} }
terminal() { sendGuestShutdown() {
local dev="" local pid="$1"
local response url
if [ -s "$QEMU_OUT" ]; then
local msg
msg=$(<"$QEMU_OUT")
if [ -n "$msg" ]; then
if [[ "${msg,,}" != "char"* || "$msg" != *"serial0)" ]]; then
echo "$msg"
fi
dev="${msg#*/dev/p}"
dev="/dev/p${dev%% *}"
fi
fi
if [ ! -c "$dev" ]; then
dev=$(echo 'info chardev' | nc -q 1 -w 1 localhost "$QEMU_PORT" | tr -d '\000')
dev="${dev#*serial0}"
dev="${dev#*pty:}"
dev="${dev%%$'\n'*}"
dev="${dev%%$'\r'*}"
fi
if [ ! -c "$dev" ]; then
error "Device '$dev' not found!"
finish 34 && return 34
fi
QEMU_TERM="$dev"
return 0
}
_graceful_shutdown() {
local code=$?
local pid url response
set +e
if [ -f "$QEMU_END" ]; then
echo && info "Received $1 signal while already shutting down..."
return
fi
touch "$QEMU_END"
echo && info "Received $1 signal, sending shutdown command..."
if [ ! -s "$QEMU_PID" ]; then
echo && error "QEMU PID file does not exist?"
finish "$code" && return "$code"
fi
pid=$(<"$QEMU_PID")
if ! isAlive "$pid"; then
echo && error "QEMU process does not exist?"
finish "$code" && return "$code"
fi
# Don't send the powerdown signal because vDSM ignores ACPI signals # Don't send the powerdown signal because vDSM ignores ACPI signals
# echo 'system_powerdown' | nc -q 1 -w 1 localhost "${QEMU_PORT}" > /dev/null # nc -q 1 -w 1 -U "$QEMU_DIR/monitor.sock" &> /dev/null <<<'system_powerdown' || :
# Send shutdown command to guest agent via serial port # Send shutdown command to guest agent via serial port
url="http://$API_HOST/read?command=$API_CMD&timeout=$API_TIMEOUT" API_TIMEOUT=$(strip "$API_TIMEOUT")
response=$(curl -sk -m "$(( API_TIMEOUT+2 ))" -S "$url" 2>&1) url="http://localhost/read?command=$API_CMD&timeout=$API_TIMEOUT"
response=$(curl --unix-socket "$HOST_API_SOCKET" -sk -m "$(( API_TIMEOUT+2 ))" -S "$url" 2>&1)
if [[ "$response" =~ "\"success\"" ]]; then if [[ "$response" =~ "\"success\"" ]]; then
@@ -141,45 +264,131 @@ _graceful_shutdown() {
response="${response#*message\"\: \"}" response="${response#*message\"\: \"}"
[ -z "$response" ] && response="second signal" [ -z "$response" ] && response="second signal"
echo && error "Forcefully terminating because of: ${response%%\"*}" echo && error "Forcefully terminating because of: ${response%%\"*}"
{ kill -15 "$pid" || true; } 2>/dev/null kill -15 -- "$pid" 2>/dev/null || :
fi fi
return 0
}
normalizeTimeout() {
local term_grace=3 # seconds before loop ends to send SIGTERM
local cleanup_grace=3 # seconds reserved after the loop for cleanup
TIMEOUT=$(strip "$TIMEOUT")
if [[ ! "$TIMEOUT" =~ ^[0-9]+$ ]]; then
TIMEOUT=105
fi
if (( TIMEOUT >= 30 )); then
term_grace=5
cleanup_grace=5
elif (( TIMEOUT >= 15 )); then
term_grace=4
cleanup_grace=4
fi
local elapsed=$((SECONDS - start))
local timeout_left=$((TIMEOUT - elapsed))
local min=$((term_grace + cleanup_grace + 1))
(( timeout_left < min )) && timeout_left=$min
wait_until=$((timeout_left - cleanup_grace))
sigterm_at=$((wait_until - term_grace))
return 0
}
waitForShutdown() {
local cnt=0 local cnt=0
local pid="$1"
local name="$APP"
while [ "$cnt" -lt "$QEMU_TIMEOUT" ]; do while (( cnt <= wait_until && SHUTDOWN_SKIP == 0 )); do
sleep 1 &
local slp=$!
# Stop waiting if the process has exited
! isAlive "$pid" && break ! isAlive "$pid" && break
sleep 1 # Workaround for stale/zombie QEMU pid file
cnt=$((cnt+1)) [ ! -s "$QEMU_START_PID" ] && [ ! -s "$QEMU_PID" ] && break
[[ "$DEBUG" == [Yy1]* ]] && info "Shutting down, waiting... ($cnt/$QEMU_TIMEOUT)" 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
# Workaround for zombie pid wait "$slp" || :
[ ! -s "$QEMU_PID" ] && break (( cnt++ ))
done done
if [ "$cnt" -ge "$QEMU_TIMEOUT" ]; then return 0
echo && error "Shutdown timeout reached, aborting..."
fi
finish "$code" && return "$code"
} }
MON_OPTS="\ graceful_shutdown() {
-pidfile $QEMU_PID \
-name $PROCESS,process=$PROCESS,debug-threads=on \
-monitor telnet:localhost:$QEMU_PORT,server,nowait,nodelay"
if [[ "$CONSOLE" != [Yy]* ]]; then local sig="$1"
local pid="" code=0
MON_OPTS+=" -daemonize -D $QEMU_LOG" [[ $BASHPID != "$TRAP_PID" ]] && return
_trap _graceful_shutdown SIGTERM SIGHUP SIGINT SIGABRT SIGQUIT code=$(signalCode "$sig")
if [ -f "$QEMU_END" ]; then
if (( code == 130 && SHUTDOWN_SIGNAL == code )); then
SHUTDOWN_SKIP=1
echo && info "Received SIGINT again, forcing shutdown..."
return
fi
echo && info "Received $sig signal while already shutting down..."
return
fi
set +e
start=$SECONDS
SHUTDOWN_SIGNAL=$code
touch "$QEMU_END"
echo && info "Received $sig signal, sending shutdown command..."
if ! readQemuPid pid; then
if ! interactive || ! waitQemuPid pid; then
warn "QEMU PID file does not exist?"
finish "$code"
fi
fi
if [ -z "$pid" ] || ! isAlive "$pid"; then
warn "QEMU process with PID $pid does not exist?"
finish "$code"
fi
sendGuestShutdown "$pid"
normalizeTimeout
waitForShutdown "$pid"
finish "$code"
}
! enabled "$SHUTDOWN" && return 0
[ -n "${QEMU_TIMEOUT:-}" ] && TIMEOUT="$QEMU_TIMEOUT"
if interactive; then
_trap graceful_shutdown SIGINT
fi fi
_trap graceful_shutdown SIGTERM SIGHUP SIGABRT SIGQUIT
return 0 return 0
+149 -51
View File
@@ -1,108 +1,206 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# shellcheck disable=SC2329
set -Eeuo pipefail set -Eeuo pipefail
: "${DHCP:="N"}" : "${DHCP:="N"}"
: "${NETWORK:="Y"}" : "${NETWORK:="Y"}"
[[ "$NETWORK" == [Nn]* ]] && exit 0 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; }
disabled "$NETWORK" && exit 0
file="/run/shm/dsm.url" file="/run/shm/dsm.url"
info="/run/shm/msg.html" msgs="/run/shm/msg.html"
driver="/run/shm/qemu.nic"
page="/run/shm/index.html" page="/run/shm/index.html"
address="/run/shm/qemu.ip" address="/run/shm/qemu.ip"
shutdown="/run/shm/qemu.end" shutdown="/run/shm/qemu.end"
socket="/run/shm/qemu-host-api.sock"
template="/var/www/index.html" template="/var/www/index.html"
url="http://127.0.0.1:2210/read?command=10" url="http://localhost/read?command=10"
resp_err="Guest returned an invalid response:" resp_err="Guest returned an invalid response:"
curl_err="Failed to connect to guest: curl error" curl_err="Failed to connect to guest: curl error"
jq_err="Failed to parse response from guest: jq error" jq_err="Failed to parse response from guest: jq error"
while [ ! -s "$file" ] exitIfShuttingDown() {
do
# Check if not shutting down
[ -f "$shutdown" ] && exit 1
sleep 3
[ -f "$shutdown" ] && exit 1 [ -f "$shutdown" ] && exit 1
[ -s "$file" ] && break
# Retrieve network info from guest VM return 0
{ json=$(curl -m 20 -sk "$url"); rc=$?; } || : }
[ -f "$shutdown" ] && exit 1 queryGuest() {
(( rc != 0 )) && error "$curl_err $rc" && continue
{ result=$(echo "$json" | jq -r '.status'); rc=$?; } || : local rc
(( rc != 0 )) && error "$jq_err $rc ( $json )" && continue
[[ "$result" == "null" ]] && error "$resp_err $json" && continue
if [[ "$result" != "success" ]] ; then { json=$(curl --unix-socket "$socket" -m 20 -sk "$url"); rc=$?; } || :
{ msg=$(echo "$json" | jq -r '.message'); rc=$?; } || :
error "Guest replied $result: $msg" && continue exitIfShuttingDown
if (( rc != 0 )); then
error "$curl_err $rc"
return 1
fi fi
{ port=$(echo "$json" | jq -r '.data.data.dsm_setting.data.http_port'); rc=$?; } || : return 0
(( rc != 0 )) && error "$jq_err $rc ( $json )" && continue }
[[ "$port" == "null" ]] && error "$resp_err $json" && continue
[ -z "$port" ] && continue
{ ip=$(echo "$json" | jq -r '.data.data.ip.data[] | select((.name=="eth0") and has("ip")).ip'); rc=$?; } || : readJsonField() {
(( rc != 0 )) && error "$jq_err $rc ( $json )" && continue
[[ "$ip" == "null" ]] && error "$resp_err $json" && continue
if [ -z "$ip" ]; then local query="$1"
[[ "$DHCP" == [Yy1]* ]] && continue local result
ip="20.20.20.21" local rc
{ result=$(jq -r "$query" <<< "$json"); rc=$?; } || :
if (( rc != 0 )); then
error "$jq_err $rc ( $json )"
return 1
fi fi
echo "$ip:$port" > $file if [[ "$result" == "null" ]]; then
error "$resp_err $json"
return 1
fi
done printf '%s\n' "$result"
return 0
}
[ -f "$shutdown" ] && exit 1 readGuestStatus() {
location=$(<"$file") local result msg rc
if [[ "$location" != "20.20"* ]]; then 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>" title="<title>Virtual DSM</title>"
body="The location of DSM is <a href='http://$location'>http://$location</a>" body="The location of DSM is <a href='http://$location'>http://$location</a>"
script="<script>setTimeout(function(){ window.location.assign('http://$location'); }, 3000);</script>" script="<script>setTimeout(function(){ window.location.assign('http://$location'); }, 3000);</script>"
HTML=$(<"$template") html=$(<"$template")
HTML="${HTML/\[1\]/$title}" html="${html/\[1\]/$title}"
HTML="${HTML/\[2\]/$script}" html="${html/\[2\]/$script}"
HTML="${HTML/\[3\]/$body}" html="${html/\[3\]/$body}"
HTML="${HTML/\[4\]/}" html="${html/\[4\]/}"
HTML="${HTML/\[5\]/}" html="${html/\[5\]/}"
echo "$HTML" > "$page" echo "$html" > "$page"
echo "$body" > "$info" echo "$body" > "$msgs"
else return 0
}
buildStaticMessage() {
local nic ip port
nic=$(<"$driver")
ip=$(<"$address") 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"
info "-----------------------------------------------------------"
echo "" >&2
exit 0 exit 0
+129 -117
View File
@@ -3,20 +3,35 @@ set -Eeuo pipefail
# Docker environment variables # Docker environment variables
: "${KVM:="Y"}"
: "${HOST_CPU:=""}" : "${HOST_CPU:=""}"
: "${CPU_FLAGS:=""}" : "${CPU_FLAGS:=""}"
: "${CPU_MODEL:=""}" : "${CPU_MODEL:=""}"
CLOCKSOURCE="tsc" HOST_CPU=$(strip "$HOST_CPU")
[[ "${ARCH,,}" == "arm64" ]] && CLOCKSOURCE="arch_sys_counter" CPU_FLAGS=$(strip "$CPU_FLAGS")
CLOCK="/sys/devices/system/clocksource/clocksource0/current_clocksource" 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
if [ ! -f "$CLOCK" ]; then
warn "file \"$CLOCK\" cannot not found?"
else
result=$(<"$CLOCK") result=$(<"$CLOCK")
result="${result//[![:print:]]/}" result="${result//[![:print:]]/}"
case "${result,,}" in case "${result,,}" in
"${CLOCKSOURCE,,}" ) ;; "${CLOCKSOURCE,,}" ) ;;
"kvm-clock" ) info "Nested KVM virtualization detected.." ;; "kvm-clock" ) info "Nested KVM virtualization detected.." ;;
@@ -24,83 +39,72 @@ else
"hpet" ) warn "unsupported clock source detected: '$result'. Please set host clock source to '$CLOCKSOURCE'." ;; "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'." ;; *) warn "unexpected clock source detected: '$result'. Please set host clock source to '$CLOCKSOURCE'." ;;
esac esac
fi
if [[ "$KVM" == [Nn]* ]]; then return 0
warn "KVM acceleration is disabled, this will cause the machine to run about 10 times slower!" }
else
if [[ "${ARCH,,}" != "amd64" ]]; then
KVM="N"
warn "your CPU architecture is ${ARCH^^} and cannot provide KVM acceleration for x64 instructions, so the machine will run about 10 times slower."
fi
fi
if [[ "$KVM" != [Nn]* ]]; then checkSse42() {
KVM_ERR="" if ! hasFlag "sse4_2"; then
error "Your CPU does not have the SSE4 instruction set that Virtual DSM requires!"
if [ ! -e /dev/kvm ]; then ! enabled "$DEBUG" && exit 88
KVM_ERR="(/dev/kvm is missing)"
else
if ! sh -c 'echo -n > /dev/kvm' &> /dev/null; then
KVM_ERR="(/dev/kvm is unwriteable)"
else
flags=$(sed -ne '/^flags/s/^.*: //p' /proc/cpuinfo)
if ! grep -qw "vmx\|svm" <<< "$flags"; then
KVM_ERR="(not enabled in BIOS)"
fi
fi
fi fi
if [ -n "$KVM_ERR" ]; then return 0
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
[[ "$DEBUG" != [Yy1]* ]] && exit 88
fi
fi
fi trimSpaces() {
if [[ "$KVM" != [Nn]* ]]; then local value="$1"
value="${value#"${value%%[![:space:]]*}"}"
value="${value%"${value##*[![:space:]]}"}"
echo "$value"
return 0
}
removeCpuArgument() {
local args=" ${ARGUMENTS:-} "
while [[ "$args" =~ [[:space:]]-cpu([[:space:]][^[:space:]]+|=[^[:space:]]+)? ]]; do
local cpu="${BASH_REMATCH[0]}"
args="${args/$cpu/ }"
warn "Ignoring '${cpu#" "}' from ARGUMENTS, use CPU_MODEL and CPU_FLAGS instead."
done
ARGUMENTS=$(trimSpaces "$args")
return 0
}
configureKvmCpuModel() {
CPU_FEATURES="kvm=on,l3-cache=on,+hypervisor" CPU_FEATURES="kvm=on,l3-cache=on,+hypervisor"
KVM_OPTS=",accel=kvm -enable-kvm -global kvm-pit.lost_tick_policy=discard" KVM_OPTS=",accel=kvm -enable-kvm -global kvm-pit.lost_tick_policy=discard"
if ! grep -qw "sse4_2" <<< "$flags"; then
info "Your CPU does not have the SSE4 instruction set that Virtual DSM requires, it will be emulated..."
[ -z "$CPU_MODEL" ] && CPU_MODEL="qemu64"
CPU_FEATURES+=",+ssse3,+sse4.1,+sse4.2"
fi
if [ -z "$CPU_MODEL" ]; then if [ -z "$CPU_MODEL" ]; then
CPU_MODEL="host" CPU_MODEL="host"
CPU_FEATURES+=",migratable=no" CPU_FEATURES+=",migratable=no"
fi fi
if grep -qw "svm" <<< "$flags"; then return 0
}
appendKvmInvtscFeature() {
if hasFlag "svm"; then
# AMD processor # AMD processor
if hasFlag "tsc_scale"; then
if grep -qw "tsc_scale" <<< "$flags"; then
CPU_FEATURES+=",+invtsc" CPU_FEATURES+=",+invtsc"
fi fi
else else
# Intel processor # Intel processor
local vmx
vmx=$(sed -ne '/^vmx flags/s/^.*: //p' /proc/cpuinfo) vmx=$(sed -ne '/^vmx flags/s/^.*: //p' /proc/cpuinfo)
if grep -qw "tsc_scaling" <<< "$vmx"; then if grep -qw "tsc_scaling" <<< "$vmx"; then
@@ -109,7 +113,35 @@ if [[ "$KVM" != [Nn]* ]]; then
fi fi
else 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="" KVM_OPTS=""
CPU_FEATURES="l3-cache=on,+hypervisor" CPU_FEATURES="l3-cache=on,+hypervisor"
@@ -118,70 +150,50 @@ else
KVM_OPTS=" -accel tcg,thread=multi" KVM_OPTS=" -accel tcg,thread=multi"
fi fi
if [ -z "$CPU_MODEL" ]; then configureTcgCpuModel
if [[ "$ARCH" == "amd64" ]]; then CPU_FEATURES+=",+ssse3,+sse4.1,+sse4.2"
CPU_MODEL="max"
CPU_FEATURES+=",migratable=no" return 0
}
composeCpuFlags() {
CPU_FLAGS="${CPU_MODEL}${CPU_FEATURES:+,$CPU_FEATURES}${CPU_FLAGS:+,$CPU_FLAGS}"
return 0
}
configureHostCpuName() {
if [ -z "$HOST_CPU" ]; then
[[ "${CPU,,}" != "unknown" ]] && HOST_CPU="$CPU"
fi
if [ -n "$HOST_CPU" ]; then
HOST_CPU="${HOST_CPU%%,*},,"
else
HOST_CPU="QEMU, Virtual CPU,"
if [ "$ARCH" == "amd64" ]; then
HOST_CPU+=" X86_64"
else else
CPU_MODEL="qemu64" HOST_CPU+=" $ARCH"
fi fi
fi fi
CPU_FEATURES+=",+ssse3,+sse4.1,+sse4.2" return 0
}
fi selectClocksource
checkClocksource
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
if ! disabled "$KVM"; then
configureKvm
else else
configureTcg
if [[ "$ARGUMENTS" == *"-cpu host"* ]]; then
ARGUMENTS="${ARGUMENTS//-cpu host/}"
fi
fi fi
if [ -z "$CPU_FLAGS" ]; then removeCpuArgument
if [ -z "$CPU_FEATURES" ]; then composeCpuFlags
CPU_FLAGS="$CPU_MODEL" configureHostCpuName
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
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 return 0
+215 -26
View File
@@ -1,38 +1,227 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -Eeuo pipefail set -Eeuo pipefail
escape () {
local s
s=${1//&/\&amp;}
s=${s//</\&lt;}
s=${s//>/\&gt;}
s=${s//'"'/\&quot;}
printf -- %s "$s"
return 0
}
file="$1"
total="$2"
body=$(escape "$3")
info="/run/shm/msg.html" info="/run/shm/msg.html"
if [[ "$body" == *"..." ]]; then escape() {
body="<p class=\"loading\">${body/.../}</p>"
local s
s=${1//&/\&amp;}
s=${s//</\&lt;}
s=${s//>/\&gt;}
s=${s//'"'/\&quot;}
s=${s//"'"/\&#39;}
printf '%s' "$s"
return 0
}
getBytes() {
local path="$1"
local mode="$2"
local bytes="0"
if [[ "$mode" == "counter" ]]; then
if [ -r "$path" ]; then
read -r bytes < "$path" || bytes="0"
fi
[[ "$bytes" =~ ^[0-9]+$ ]] || bytes="0"
printf '%s\n' "$bytes"
return 0
fi
if [ ! -s "$path" ] && [ ! -d "$path" ]; then
printf '0\n'
return 0
fi
if [[ "$mode" == "allocated" ]]; then
bytes=$(du -sB1 -- "$path" 2>/dev/null | cut -f1) || bytes="0"
else
bytes=$(du -sb -- "$path" 2>/dev/null | cut -f1) || bytes="0"
fi
printf '%s\n' "$bytes"
return 0
}
formatSize() {
local bytes="$1"
local size
size=$(numfmt --to=iec --suffix=B "$bytes" |
sed -r 's/([A-Z])/ \1/') ||
size="${bytes} bytes"
printf '%s' "$size"
return 0
}
printPercentProgress() {
local percent="$1"
while (( next_percent <= percent && next_percent <= 100 )); do
if [[ "$printed" == "Y" ]]; then
printf ' → %s%%' "$next_percent"
else
printf '%s%%' "$next_percent"
fi
printed="Y"
next_percent=$((next_percent + 10))
done
return 0
}
printCurrentSize() {
local bytes="$1"
local size
size=$(formatSize "$bytes")
if [[ "$printed" == "Y" ]]; then
printf ' → %s' "$size"
else
printf '%s' "$size"
fi
printed="Y"
return 0
}
printSizeProgress() {
local bytes="$1"
local size
while (( bytes >= next_bytes )); do
size=$(formatSize "$next_bytes")
if [[ "$printed" == "Y" ]]; then
printf ' → %s' "$size"
else
printf '%s' "$size"
fi
printed="Y"
next_bytes=$((next_bytes + step_bytes))
done
return 0
}
finishLogProgress() {
if [[ "$output" == "log" && "$printed" == "Y" ]]; then
printf '\n'
fi
return 0
}
path="$1"
total="$2"
body=$(escape "$3")
output="${4:-}"
step_bytes="${5:-536870912}"
mode="${6:-apparent}"
if [[ -n "$total" && ! "$total" =~ ^(0|[1-9][0-9]*)$ ]]; then
printf 'Invalid total size: %s\n' "$total" >&2
exit 2
fi fi
while true if [[ ! "$step_bytes" =~ ^[1-9][0-9]*$ ]]; then
do printf 'Invalid progress interval: %s\n' "$step_bytes" >&2
if [ -s "$file" ]; then exit 2
bytes=$(du -sb "$file" | cut -f1) fi
if (( bytes > 1000 )); then
if [ -z "$total" ] || [[ "$total" == "0" ]]; then case "$mode" in
size=$(numfmt --to=iec --suffix=B "$bytes" | sed -r 's/([A-Z])/ \1/') apparent | allocated | counter ) ;;
else * )
size="$(echo "$bytes" "$total" | awk '{printf "%.1f", $1 * 100 / $2}')" printf 'Invalid progress mode: %s\n' "$mode" >&2
size="$size%" exit 2
;;
esac
case "$output" in
"" | log ) ;;
* )
printf 'Invalid progress output: %s\n' "$output" >&2
exit 2
;;
esac
printed="N"
next_percent=10
next_bytes="$step_bytes"
log_mode="percent"
if [ -z "$total" ] || [[ "$total" == "0" ]]; then
log_mode="size"
fi
trap finishLogProgress EXIT
trap 'exit 0' HUP INT QUIT TERM
if [[ "$body" == *"..." ]]; then
body="<p class=\"loading\">${body::-3}</p>"
fi
while true; do
bytes=$(getBytes "$path" "$mode")
if (( bytes > 4096 )); then
write_html="Y"
if [ -z "$total" ] || [[ "$total" == "0" ]] || (( bytes > total )); then
size=$(formatSize "$bytes")
if [[ "$output" == "log" ]]; then
if [[ "$log_mode" == "percent" ]]; then
printCurrentSize "$bytes"
next_bytes=$(((bytes / step_bytes + 1) * step_bytes))
log_mode="size"
else
printSizeProgress "$bytes"
fi
fi fi
echo "${body//(\[P\])/($size)}"> "$info" else
# Truncate to one decimal so progress is never reported early.
progress=$((bytes * 1000 / total))
(( progress > 1000 )) && progress=1000
percent=$((progress / 10))
printf -v size '%d.%d%%' \
"$((progress / 10))" \
"$((progress % 10))"
if [[ "$output" == "log" ]]; then
if [[ "$log_mode" == "size" ]]; then
printSizeProgress "$bytes"
else
printPercentProgress "$percent"
fi
fi
# Do not update the web viewer until at least 0.1% is reached.
(( progress == 0 )) && write_html="N"
fi
if [[ "$write_html" == "Y" ]]; then
printf '%s\n' "${body//(\[P\])/($size)}" > "$info"
fi fi
fi fi
sleep 1 & wait $! sleep 1 & wait $!
done done
+306 -173
View File
@@ -2,19 +2,18 @@
set -Eeuo pipefail set -Eeuo pipefail
trap 'error "Status $? while: $BASH_COMMAND (line $LINENO/$BASH_LINENO)"' ERR trap 'error "Status $? while: $BASH_COMMAND (line $LINENO/$BASH_LINENO)"' ERR
enabled "${TRACE:-}" && set -o functrace && trap 'echo "# $BASH_COMMAND" >&2' DEBUG
[ ! -f "/run/entry.sh" ] && error "Script must run inside Docker container!" && exit 11 [ ! -f "/run/entry.sh" ] && error "Script must be run inside the container!" && exit 11
[ "$(id -u)" -ne "0" ] && error "Script must be executed with root privileges." && exit 12 [ "$(id -u)" -ne "0" ] && error "Script must be executed with root privileges." && exit 12
echo " Starting $APP for Docker v$(</run/version)..."
echo " For support visit $SUPPORT"
# Docker environment variables # Docker environment variables
: "${TZ:=""}" # System local timezone : "${TZ:=""}" # System timezone
: "${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 : "${MACHINE:="q35"}" # Machine type selection
: "${ALLOCATE:=""}" # Preallocate diskspace : "${ALLOCATE:=""}" # Preallocate diskspace
: "${ARGUMENTS:=""}" # Extra QEMU parameters : "${ARGUMENTS:=""}" # Extra QEMU parameters
: "${CPU_CORES:="2"}" # Amount of CPU cores : "${CPU_CORES:="2"}" # Amount of CPU cores
@@ -23,93 +22,319 @@ echo " For support visit $SUPPORT"
: "${DISK_SIZE:="16G"}" # Initial data disk size : "${DISK_SIZE:="16G"}" # Initial data disk size
: "${STORAGE:="/storage"}" # Storage folder location : "${STORAGE:="/storage"}" # Storage folder location
# Helper variables detectEngine() {
if [ -f "/run/.containerenv" ]; then
ENGINE="${container:-}"
if [[ "${ENGINE,,}" == *"podman"* ]]; then
ENGINE="Podman"
else
[ -z "$ENGINE" ] && ENGINE="Kubernetes"
fi
elif [ -f "/.dockerenv" ]; then
ENGINE="Docker"
fi
return 0
}
detectRootless() {
local uid_map=""
uid_map=$(awk '{$1=$1; print}' /proc/self/uid_map 2>/dev/null || true)
if [[ "$uid_map" == "0 0 4294967295" ]]; then
ROOTLESS="N"
else
ROOTLESS="Y"
fi
return 0
}
checkPrivileged() {
local cap_bnd
local last_cap
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
PRIVILEGED="Y"
fi
return 0
}
normalizeCpuCores() {
CPU_CORES=$(strip "$CPU_CORES")
[ -z "$CPU_CORES" ] && CPU_CORES=2
[[ "${CPU_CORES,,}" == "max" ]] && CPU_CORES="$CORES"
[[ "${CPU_CORES,,}" == "half" ]] && CPU_CORES=$(( CORES / 2 ))
[ -z "${CPU_CORES##*[!0-9]*}" ] && error "Invalid amount of CPU_CORES: $CPU_CORES" && exit 15
[ "$CPU_CORES" -lt "1" ] && CPU_CORES=1
if [ "$CPU_CORES" -gt "$CORES" ]; then
warn "The amount for CPU_CORES (${CPU_CORES}) exceeds the amount of logical cores available (${CORES}) and will be limited."
CPU_CORES="$CORES"
fi
return 0
}
checkStorage() {
# Check system
QEMU_DIR="/run/shm"
if [ ! -d "/dev/shm" ]; then
error "Directory /dev/shm not found!" && exit 14
else
[ ! -d "$QEMU_DIR" ] && ln -s /dev/shm "$QEMU_DIR"
fi
QEMU_PID="$QEMU_DIR/qemu.pid"
# Check folder
if [[ "${STORAGE,,}" != "/storage" ]]; then
mkdir -p "$STORAGE"
fi
if [ ! -d "$STORAGE" ]; then
error "Storage folder ($STORAGE) not found!" && exit 13
fi
if [ ! -w "$STORAGE" ]; then
msg="Storage folder ($STORAGE) is not writeable!"
msg+=" If SELinux is active, you need to add the \":Z\" flag to the bind mount."
error "$msg" && exit 13
fi
return 0
}
checkFilesystem() {
# Check filesystem
FS=$(stat -f -c %T "$STORAGE")
if [[ "${FS,,}" == "ecryptfs" || "${FS,,}" == "tmpfs" ]]; then
DISK_IO="threads"
DISK_CACHE="writeback"
fi
return 0
}
finiteMemoryLimit() {
local limit="$1"
local sentinel="4611686018427387904"
local i=0
local left=""
local right=""
[[ "$limit" =~ ^[0-9]+$ ]] || return 1
(( ${#limit} < ${#sentinel} )) && return 0
(( ${#limit} > ${#sentinel} )) && return 1
for (( i=0; i<${#sentinel}; i++ )); do
left="${limit:i:1}"
right="${sentinel:i:1}"
(( left < right )) && return 0
(( left > right )) && return 1
done
return 1
}
getMemoryInfo() {
local host_total=""
local host_avail=""
local limit=""
local current=""
local available=""
host_total=$(free -b | awk '/^Mem:/ {print $2; exit}')
host_avail=$(free -b | awk '/^Mem:/ {print $7; exit}')
RAM_TOTAL="$host_total"
RAM_AVAIL="$host_avail"
if [ -r /sys/fs/cgroup/memory.max ] && [ -r /sys/fs/cgroup/memory.current ]; then
limit=$(< /sys/fs/cgroup/memory.max)
current=$(< /sys/fs/cgroup/memory.current)
elif [ -r /sys/fs/cgroup/memory/memory.limit_in_bytes ] && [ -r /sys/fs/cgroup/memory/memory.usage_in_bytes ]; then
limit=$(< /sys/fs/cgroup/memory/memory.limit_in_bytes)
current=$(< /sys/fs/cgroup/memory/memory.usage_in_bytes)
fi
if finiteMemoryLimit "$limit" && [[ "$current" =~ ^[0-9]+$ ]]; then
(( limit < RAM_TOTAL )) && RAM_TOTAL="$limit"
available=$(( limit - current ))
(( available < 0 )) && available=0
(( available < RAM_AVAIL )) && RAM_AVAIL="$available"
fi
return 0
}
normalizeRamSize() {
# Read host and container memory limits.
getMemoryInfo
RAM_SPARE=500000000
RAM_MINIMUM=136314880
RAM_SIZE=$(strip "$RAM_SIZE")
RAM_SIZE="${RAM_SIZE// /}"
[ -z "$RAM_SIZE" ] && RAM_SIZE="2G"
if [[ "${RAM_SIZE,,}" != "max" && "${RAM_SIZE,,}" != "half" ]]; then
if [ -z "${RAM_SIZE//[0-9. ]}" ]; then
[ "${RAM_SIZE%%.*}" -lt "130" ] && RAM_SIZE="${RAM_SIZE}G" || RAM_SIZE="${RAM_SIZE}M"
fi
RAM_SIZE=$(echo "${RAM_SIZE^^}" | sed 's/MB/M/g;s/GB/G/g;s/TB/T/g')
! numfmt --from=iec "$RAM_SIZE" &>/dev/null && error "Invalid RAM_SIZE: $RAM_SIZE" && exit 16
wanted=$(numfmt --from=iec "$RAM_SIZE")
[ "$wanted" -lt "$RAM_MINIMUM" ] && error "RAM_SIZE is too low: $RAM_SIZE" && exit 16
fi
return 0
}
checkKvm() {
# Check KVM support
if [[ "${PLATFORM,,}" == "x64" ]]; then
TARGET="amd64"
else
TARGET="arm64"
fi
if disabled "$KVM"; then
warn "KVM acceleration is disabled, this will cause the machine to run about 10 times slower!"
else
if [[ "${ARCH,,}" != "$TARGET" ]]; then
KVM="N"
warn "your CPU architecture is ${ARCH^^} and cannot provide KVM acceleration for ${PLATFORM^^} instructions, so the machine will run about 10 times slower."
fi
fi
if ! disabled "$KVM"; then
KVM_ERR=""
if [ ! -e /dev/kvm ]; then
KVM_ERR="(/dev/kvm is missing)"
else
if ! sh -c 'echo -n > /dev/kvm' &> /dev/null; then
KVM_ERR="(/dev/kvm is unwriteable)"
else
if [[ "${PLATFORM,,}" == "x64" ]]; then
flags=$(sed -ne '/^flags/s/^.*: //p' /proc/cpuinfo)
if ! grep -qw "vmx\|svm" <<< "$flags"; then
KVM_ERR="(not enabled in BIOS)"
fi
if ! grep -qw "sse4_2" <<< "$flags"; then
error "Your CPU does not have the SSE4 instruction set that Virtual DSM requires!"
! enabled "$DEBUG" && exit 88
fi
fi
fi
fi
if [ -n "$KVM_ERR" ]; then
KVM="N"
if [[ "$OSTYPE" =~ ^darwin ]]; then
warn "you are using macOS which has no KVM support, so the machine will run about 10 times slower."
else
kernel=$(uname -a)
case "${kernel,,}" in
*"microsoft"* )
error "Please bind '/dev/kvm' as a volume in the optional container settings when using Docker Desktop." ;;
*"synology"* )
error "Please make sure that Synology VMM (Virtual Machine Manager) is installed and that '/dev/kvm' is binded to this container." ;;
*)
error "KVM acceleration is not available $KVM_ERR, this will cause the machine to run about 10 times slower."
error "See the FAQ for possible causes, or disable acceleration by adding the \"KVM=N\" variable (not recommended)." ;;
esac
! enabled "$DEBUG" && exit 88
fi
fi
fi
return 0
}
# Sanitize variables
TZ=$(strip "$TZ")
STORAGE=$(strip "$STORAGE")
COUNTRY=$(strip "$COUNTRY")
DISK_SIZE=$(strip "$DISK_SIZE")
# Helper variables
ROOTLESS="N"
PRIVILEGED="N"
ENGINE="Docker"
PROCESS="${APP,,}" PROCESS="${APP,,}"
PROCESS="${PROCESS// /-}" PROCESS="${PROCESS// /-}"
detectEngine
detectRootless
echo " Starting $APP for $ENGINE v$(</etc/version)..."
echo " For support visit $SUPPORT"
checkPrivileged
INFO="/run/shm/msg.html" INFO="/run/shm/msg.html"
PAGE="/run/shm/index.html" PAGE="/run/shm/index.html"
TEMPLATE="/var/www/index.html" TEMPLATE="/var/www/index.html"
FOOTER1="$APP for Docker v$(</run/version)" FOOTER1="$APP for $ENGINE v$(</etc/version)"
FOOTER2="<a href='$SUPPORT'>$SUPPORT</a>" FOOTER2="<a href='$SUPPORT'>$SUPPORT</a>"
SOCKETS=1
CPU=$(cpu) CPU=$(cpu)
SYS=$(uname -r) SYS=$(uname -r)
HOST=$(hostname -s)
KERNEL=$(echo "$SYS" | cut -b 1)
MINOR=$(echo "$SYS" | cut -d '.' -f2)
ARCH=$(dpkg --print-architecture) ARCH=$(dpkg --print-architecture)
IFS=. read -r KERNEL MINOR _ <<< "$SYS"
CORES=$(grep -c '^processor' /proc/cpuinfo) CORES=$(grep -c '^processor' /proc/cpuinfo)
if ! grep -qi "socket(s)" <<< "$(lscpu)"; then if grep -qi "socket(s)" <<< "$(lscpu)"; then
SOCKETS=1 SOCKETS=$(lscpu | grep -m 1 -i 'socket(s)' | awk '{print $2}')
else [ -z "${SOCKETS##*[!0-9]*}" ] && SOCKETS=1
SOCKETS=$(lscpu | grep -m 1 -i 'socket(s)' | awk '{print $(2)}') [ "$SOCKETS" -lt "1" ] && SOCKETS=1
fi fi
CPU_CORES="${CPU_CORES// /}" normalizeCpuCores
[[ "${CPU_CORES,,}" == "max" ]] && CPU_CORES="$CORES" checkStorage
[ -n "${CPU_CORES//[0-9 ]}" ] && error "Invalid amount of CPU_CORES: $CPU_CORES" && exit 15 checkFilesystem
normalizeRamSize
if [ "$CPU_CORES" -gt "$CORES" ]; then
warn "The amount for CPU_CORES (${CPU_CORES}) exceeds the amount of physical cores, so will be limited to ${CORES}."
CPU_CORES="$CORES"
fi
# Check system
if [ ! -d "/dev/shm" ]; then
error "Directory /dev/shm not found!" && exit 14
else
[ ! -d "/run/shm" ] && ln -s /dev/shm /run/shm
fi
# Check folder
if [[ "${COMMIT:-}" == [Yy1]* ]]; then
STORAGE="/local"
mkdir -p "$STORAGE"
fi
if [ ! -d "$STORAGE" ]; then
error "Storage folder ($STORAGE) not found!" && exit 13
fi
if [ ! -w "$STORAGE" ]; then
error "Storage folder ($STORAGE) is not writeable!" && exit 13
fi
# Check filesystem
FS=$(stat -f -c %T "$STORAGE")
if [[ "${FS,,}" == "ecryptfs" || "${FS,,}" == "tmpfs" ]]; then
DISK_IO="threads"
DISK_CACHE="writeback"
fi
# Read memory
RAM_SPARE=500000000
RAM_AVAIL=$(free -b | grep -m 1 Mem: | awk '{print $7}')
RAM_TOTAL=$(free -b | grep -m 1 Mem: | awk '{print $2}')
RAM_SIZE="${RAM_SIZE// /}"
[ -z "$RAM_SIZE" ] && error "RAM_SIZE not specified!" && exit 16
if [[ "${RAM_SIZE,,}" == "max" ]]; then
RAM_WANTED=$(( RAM_AVAIL - RAM_SPARE - RAM_SPARE ))
RAM_WANTED=$(( RAM_WANTED / 1073741825 ))
RAM_SIZE="${RAM_WANTED}G"
fi
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
RAM_WANTED=$(numfmt --from=iec "$RAM_SIZE")
[ "$RAM_WANTED" -lt "136314880 " ] && error "RAM_SIZE is too low: $RAM_SIZE" && exit 16
# Print system info # Print system info
SYS="${SYS/-generic/}" SYS="${SYS/-generic/}"
@@ -121,109 +346,17 @@ SPACE_GB=$(formatBytes "$SPACE" "down")
AVAIL_MEM=$(formatBytes "$RAM_AVAIL" "down") AVAIL_MEM=$(formatBytes "$RAM_AVAIL" "down")
TOTAL_MEM=$(formatBytes "$RAM_TOTAL" "up") TOTAL_MEM=$(formatBytes "$RAM_TOTAL" "up")
echo " CPU: ${CPU} | RAM: ${AVAIL_MEM/ GB/}/$TOTAL_MEM | DISK: $SPACE_GB (${FS}) | KERNEL: ${SYS}..." echo " CPU: ${CPU} | RAM: ${AVAIL_MEM/ GB/}/$TOTAL_MEM | DISK: $SPACE_GB (${FS}) | KERNEL: ${SYS}"
echo echo
# Check available memory checkKvm
if [[ "$RAM_CHECK" != [Nn]* ]] && (( (RAM_WANTED + RAM_SPARE) > RAM_AVAIL )); then
AVAIL_MEM=$(formatBytes "$RAM_AVAIL")
msg="Your configured RAM_SIZE of ${RAM_SIZE/G/ GB} is too high for the $AVAIL_MEM of memory available, please set a lower value."
[[ "${FS,,}" != "zfs" ]] && error "$msg" && exit 17
info "$msg"
fi
# Cleanup files # Cleanup files
rm -f /run/shm/qemu.* rm -f "$QEMU_DIR"/dsm.url
rm -f /run/shm/dsm.url rm -f "$QEMU_DIR"/{qemu.*,*.{pid,sock,pipe}}
# Cleanup dirs # Cleanup dirs
rm -rf /tmp/dsm rm -rf /tmp/dsm
rm -rf "$STORAGE/tmp" rm -rf "$STORAGE/tmp"
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
}
: "${WEB_PORT:="5000"}" # Webserver port
cp -r /var/www/* /run/shm
html "Starting $APP for Docker..."
if [[ "${WEB:-}" != [Nn]* ]]; then
mkdir -p /etc/nginx/sites-enabled
cp /etc/nginx/default.conf /etc/nginx/sites-enabled/web.conf
sed -i "s/listen 5000 default_server;/listen $WEB_PORT default_server;/g" /etc/nginx/sites-enabled/web.conf
# shellcheck disable=SC2143
if [ -f /proc/net/if_inet6 ] && [ -n "$(ifconfig -a | grep inet6)" ]; then
sed -i "s/listen $WEB_PORT default_server;/listen [::]:$WEB_PORT default_server ipv6only=off;/g" /etc/nginx/sites-enabled/web.conf
fi
# Start webserver
nginx -e stderr
fi
return 0 return 0
+107 -39
View File
@@ -5,11 +5,26 @@ set -Eeuo pipefail
: "${HOST_MAC:=""}" : "${HOST_MAC:=""}"
: "${HOST_DEBUG:=""}" : "${HOST_DEBUG:=""}"
: "${HOST_SERIAL:=""}"
: "${HOST_MODEL:=""}" : "${HOST_MODEL:=""}"
: "${HOST_SERIAL:=""}"
: "${GUEST_SERIAL:=""}" : "${GUEST_SERIAL:=""}"
if [ -n "$HOST_MAC" ]; then # Sanitize variables
HOST_MAC=$(strip "$HOST_MAC")
HOST_MODEL=$(strip "$HOST_MODEL")
HOST_SERIAL=$(strip "$HOST_SERIAL")
GUEST_SERIAL=$(strip "$GUEST_SERIAL")
HOST_PID="$QEMU_DIR/host.pid"
HOST_API_SOCKET="$QEMU_DIR/qemu-host-api.sock"
HOST_AGENT_SOCKET="$QEMU_DIR/qemu-host-agent.sock"
validateHostMac() {
local m
if [ -z "$HOST_MAC" ]; then
return 0
fi
HOST_MAC="${HOST_MAC//-/:}" HOST_MAC="${HOST_MAC//-/:}"
@@ -22,54 +37,107 @@ if [ -n "$HOST_MAC" ]; then
error "Invalid HOST_MAC address: '$HOST_MAC', should be 12 or 17 digits long!" && exit 28 error "Invalid HOST_MAC address: '$HOST_MAC', should be 12 or 17 digits long!" && exit 28
fi fi
fi return 0
}
HOST_ARGS=() buildHostArguments() {
HOST_ARGS+=("-cpu=$CPU_CORES")
HOST_ARGS+=("-cpu_arch=$HOST_CPU")
[ -n "$HOST_MAC" ] && HOST_ARGS+=("-mac=$HOST_MAC") HOST_ARGS=()
[ -n "$HOST_MODEL" ] && HOST_ARGS+=("-model=$HOST_MODEL") HOST_ARGS+=("-cpu=$CPU_CORES")
[ -n "$HOST_SERIAL" ] && HOST_ARGS+=("-hostsn=$HOST_SERIAL") HOST_ARGS+=("-cpu_arch=$HOST_CPU")
[ -n "$GUEST_SERIAL" ] && HOST_ARGS+=("-guestsn=$GUEST_SERIAL") HOST_ARGS+=("-api=$HOST_API_SOCKET")
HOST_ARGS+=("-addr=$HOST_AGENT_SOCKET")
if [[ "$HOST_DEBUG" == [Yy1]* ]]; then [ -n "$HOST_MAC" ] && HOST_ARGS+=("-mac=$HOST_MAC")
set -x [ -n "$HOST_MODEL" ] && HOST_ARGS+=("-model=$HOST_MODEL")
./host.bin "${HOST_ARGS[@]}" & [ -n "$HOST_SERIAL" ] && HOST_ARGS+=("-hostsn=$HOST_SERIAL")
{ set +x; } 2>/dev/null [ -n "$GUEST_SERIAL" ] && HOST_ARGS+=("-guestsn=$GUEST_SERIAL")
echo
else
./host.bin "${HOST_ARGS[@]}" >/dev/null &
fi
cnt=0 return 0
sleep 0.2 }
while ! nc -z -w2 127.0.0.1 2210 > /dev/null 2>&1; do startHostBinary() {
sleep 0.1
cnt=$((cnt + 1))
(( cnt > 50 )) && error "Failed to connect to qemu-host.." && exit 58
done
cnt=0 local pid
while ! nc -z -w2 127.0.0.1 12345 > /dev/null 2>&1; do rm -f -- "$HOST_PID" "$HOST_API_SOCKET" "$HOST_AGENT_SOCKET" || return 1
sleep 0.1
cnt=$((cnt + 1))
(( cnt > 50 )) && error "Failed to connect to qemu-host.." && exit 59
done
# Configure serial ports if enabled "$HOST_DEBUG"; then
set -x
./host.bin "${HOST_ARGS[@]}" &
{ set +x; } 2>/dev/null
pid=$!
echo
else
./host.bin "${HOST_ARGS[@]}" >/dev/null &
pid=$!
fi
if [[ "$CONSOLE" != [Yy]* ]]; then echo "$pid" > "$HOST_PID"
SERIAL_OPTS="-serial pty"
else
SERIAL_OPTS="-serial mon:stdio"
fi
SERIAL_OPTS+=" \ return 0
}
waitForSocket() {
local socket="$1"
local exit_code="$2"
local pid cnt=0
while [ ! -S "$socket" ]; do
if ! read -r pid < "$HOST_PID" || ! isAlive "$pid"; then
error "qemu-host exited unexpectedly!"
exit "$exit_code"
fi
sleep 0.1
cnt=$((cnt + 1))
if (( cnt > 50 )); then
error "Failed to create qemu-host socket: $socket"
exit "$exit_code"
fi
done
return 0
}
configureSerialPorts() {
if enabled "${SHUTDOWN:-Y}" && interactive; then
CONSOLE_SOCKET="$QEMU_DIR/console.sock"
MONITOR_SOCKET="$QEMU_DIR/monitor.sock"
SERIAL_OPTS="-chardev socket,id=console0,path=$CONSOLE_SOCKET,reconnect-ms=1000 \
-serial chardev:console0 \
-chardev socket,id=monitor0,path=$MONITOR_SOCKET,server=on,wait=off \
-mon chardev=monitor0,mode=readline"
else
SERIAL_OPTS="-serial mon:stdio"
fi
SERIAL_OPTS+=" \
-device virtio-serial-pci,id=virtio-serial0,bus=pcie.0,addr=0x3 \ -device virtio-serial-pci,id=virtio-serial0,bus=pcie.0,addr=0x3 \
-chardev socket,id=charchannel0,host=127.0.0.1,port=12345,reconnect=10 \ -chardev socket,id=charchannel0,path=$HOST_AGENT_SOCKET,reconnect-ms=1000 \
-device virtserialport,bus=virtio-serial0.0,nr=1,chardev=charchannel0,id=channel0,name=vchannel" -device virtserialport,bus=virtio-serial0.0,nr=1,chardev=charchannel0,id=channel0,name=vchannel"
return 0
}
validateHostMac
buildHostArguments
startHostBinary
waitForSocket "$HOST_API_SOCKET" 58
waitForSocket "$HOST_AGENT_SOCKET" 59
configureSerialPorts
return 0 return 0
+99
View File
@@ -0,0 +1,99 @@
#!/usr/bin/env bash
set -Eeuo pipefail
: "${WEB_PORT:="5000"}" # Webserver port
: "${WSD_PORT:="8004"}" # Websockets port
# Sanitize port variables
WEB_PORT=$(strip "$WEB_PORT")
WSD_PORT=$(strip "$WSD_PORT")
WEB_PID="/run/nginx.pid"
WSD_LOG="/var/log/websocketd.log"
WSD_PID="$QEMU_DIR/websocketd.pid"
prepareWebFiles() {
cp -r /var/www/* "$QEMU_DIR" || return 1
rm -f "$WSD_PID" "$WEB_PID" "$WSD_LOG" || return 1
return 0
}
configureWebPorts() {
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
return 0
}
configureIpv6Listen() {
# shellcheck disable=SC2143
if [ -f /proc/net/if_inet6 ] && [[ "$(cat /proc/sys/net/ipv6/conf/all/disable_ipv6 2>/dev/null)" != "1" ]]; then
sed -i "s/listen $WEB_PORT default_server;/listen [::]:$WEB_PORT default_server ipv6only=off;/g" /etc/nginx/sites-enabled/web.conf
fi
return 0
}
configureWebServer() {
mkdir -p /etc/nginx/sites-enabled
cp /etc/nginx/default.conf /etc/nginx/sites-enabled/web.conf
configureWebPorts || return 1
configureIpv6Listen || return 1
return 0
}
startWebServer() {
# Start webserver
nginx -e stderr || return 1
return 0
}
startWebsocketServer() {
# Start websocket server
websocketd \
--address 127.0.0.1 \
--port="$WSD_PORT" \
/run/socket.sh \
>"$WSD_LOG" 2>&1 &
local pid=$!
if ! echo "$pid" > "$WSD_PID"; then
kill "$pid" 2>/dev/null || :
return 1
fi
sleep 0.1
if ! isAlive "$pid"; then
rm -f "$WSD_PID"
[ -s "$WSD_LOG" ] && cat "$WSD_LOG" >&2
error "Failed to start websocket server!"
return 1
fi
return 0
}
prepareWebFiles
html "Starting $APP for $ENGINE..."
disabled "${WEB:-}" && return 0
configureWebServer
startWebServer
startWebsocketServer
return 0
+31
View File
@@ -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") || return 0
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
+6
View File
@@ -0,0 +1,6 @@
#!/usr/bin/env bash
set -Eeuo pipefail
# You can override this hook to execute a script before startup!
return 0
+382 -23
View File
@@ -7,11 +7,88 @@ 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; } 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; } warn () { printf "%b%s%b" "\E[1;31m " "Warning: ${1:-}" "\E[0m\n" >&2; }
hasFlag() {
# Match a whitespace-delimited token in /proc/cpuinfo
grep -m1 '^flags[[:space:]]*:' /proc/cpuinfo | grep -Fqw -- "$1"
}
hasFeature() {
# Match a whitespace-delimited token in /proc/cpuinfo
grep -m1 '^Features[[:space:]]*:' /proc/cpuinfo | grep -Fqw -- "$1"
}
isAmdCpu() {
local vendor
vendor=$(awk -F ': *' '/^vendor_id/{print $2; exit}' /proc/cpuinfo)
[[ "$vendor" == "AuthenticAMD" ]]
}
interactive() {
[ -t 0 ] && : 2>/dev/null </dev/tty >/dev/tty
}
strip() {
local value="${1:-}"
# Remove surrounding whitespace
value="${value#"${value%%[![:space:]]*}"}"
value="${value%"${value##*[![:space:]]}"}"
# Remove leading/trailing single/double quotes
value="${value%\"}"
value="${value#\"}"
value="${value%\'}"
value="${value#\'}"
# Remove surrounding whitespace again
value="${value#"${value%%[![:space:]]*}"}"
value="${value%"${value##*[![:space:]]}"}"
printf '%s' "$value"
}
enabled() {
local value
value=$(strip "${1:-}")
case "${value,,}" in
y|yes|true|1|on|enable|enabled) return 0 ;;
*) return 1 ;;
esac
}
disabled() {
local value
value=$(strip "${1:-}")
case "${value,,}" in
n|no|none|false|0|off|disable|disabled) return 0 ;;
*) return 1 ;;
esac
}
formatBytes() { formatBytes() {
local result local result
result=$(numfmt --to=iec --suffix=B "$1" | sed -r 's/([A-Z])/ \1/' | sed 's/ B/ bytes/g;')
if ! result=$(numfmt --to=iec --suffix=B "$1" | sed -r 's/([A-Z])/ \1/' | sed 's/ B/ bytes/g;'); then
return 1
fi
local unit="${result//[0-9. ]}" local unit="${result//[0-9. ]}"
result="${result//[a-zA-Z ]/}" result="${result//[a-zA-Z ]/}"
if [[ "${2:-}" == "up" ]]; then if [[ "${2:-}" == "up" ]]; then
if [[ "$result" == *"."* ]]; then if [[ "$result" == *"."* ]]; then
result="${result%%.*}" result="${result%%.*}"
@@ -22,12 +99,15 @@ formatBytes() {
result="${result%%.*}" result="${result%%.*}"
fi fi
fi fi
echo "$result $unit" echo "$result $unit"
return 0 return 0
} }
isAlive() { isAlive() {
local pid="$1" local pid="$1"
[ -z "$pid" ] && return 1
if kill -0 "$pid" 2>/dev/null; then if kill -0 "$pid" 2>/dev/null; then
return 0 return 0
@@ -36,48 +116,277 @@ isAlive() {
return 1 return 1
} }
pKill() { waitPid() {
local i=0
local pid="$1" local pid="$1"
local timeout="${2:-10}"
{ kill -15 "$pid" || true; } 2>/dev/null while [ -n "$pid" ] && isAlive "$pid"; do
while isAlive "$pid"; do
sleep 0.2 sleep 0.2
i=$((i + 1))
(( i >= timeout * 5 )) && return 1
done done
return 0 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() { fWait() {
local i=0
local name="$1" local name="$1"
local timeout="${2:-10}"
[ -z "$name" ] && return 0
while pgrep -f -l "$name" >/dev/null; do while pgrep -f -l "$name" >/dev/null; do
sleep 0.2 sleep 0.2
i=$((i + 1))
if (( i >= timeout * 5 )); then
warn "Timed out while waiting for process: $name"
break
fi
done done
return 0 return 0
} }
fKill() { fKill() {
local name="$1"
{ pkill -f "$name" || true; } 2>/dev/null local name="$1"
fWait "$name" local timeout="${2:-10}"
[ -z "$name" ] && return 0
{ pkill -f "$name" || :; } 2>/dev/null
fWait "$name" "$timeout"
return 0 return 0
} }
sKill() {
local pid=""
local file="$1"
[ ! -s "$file" ] && return 0
! read -r pid <"$file" && return 0
[ -z "$pid" ] && return 0
if isAlive "$pid"; then
{ kill -15 -- "$pid" || :; } 2>/dev/null
fi
return 0
}
mKill() {
local timeout=10
local files=("$@")
for file in "${files[@]}"; do
sKill "$file"
done
for file in "${files[@]}"; do
if ! waitPidFile "$file" "$timeout"; then
warn "Timed out while waiting for PID file: $file"
fi
done
return 0
}
setOwner() {
local file="$1"
local dir uid gid
[ ! -f "$file" ] && return 1
dir=$(dirname -- "$file")
uid=$(stat -c '%u' "$dir") || return 1
gid=$(stat -c '%g' "$dir") || return 1
! chown "$uid:$gid" "$file" && return 1
return 0
}
makeDir() {
local path="$1"
local dir uid gid
[ -d "$path" ] && return 0
! mkdir -p "$path" && return 1
dir=$(dirname -- "$path")
if ! uid=$(stat -c '%u' "$dir") || ! gid=$(stat -c '%g' "$dir"); then
warn "failed to determine the owner for \"$path\"."
return 0
fi
if ! chown "$uid:$gid" "$path"; then
warn "failed to set the owner for \"$path\"."
return 0
fi
return 0
}
stateFile() {
local name="$1"
local prefix="${2:-$PROCESS}"
[[ "$name" == */* ]] && printf '%s\n' "$name" && return 0
printf '%s/%s.%s\n' "$STORAGE" "$prefix" "$name"
return 0
}
writeFile() {
local txt="$1"
local path="$2"
if ! printf '%s\n' "$txt" > "$path"; then
error "Failed to write file \"$path\" !"
return 1
fi
if ! setOwner "$path"; then
warn "failed to set the owner for \"$path\"."
fi
return 0
}
readFile() {
local path="$1"
local value
[ -s "$path" ] || return 0
value=$(<"$path") || return 1
value="${value//[![:print:]]/}"
printf '%s\n' "$value"
return 0
}
writeState() {
local name="$1"
local value="$2"
local prefix="${3:-$PROCESS}"
local path
[ -z "$value" ] && return 0
path=$(stateFile "$name" "$prefix") || return 1
writeFile "$value" "$path"
return $?
}
readState() {
local name="$1"
local prefix="${2:-$PROCESS}"
local path
path=$(stateFile "$name" "$prefix") || return 1
readFile "$path"
return $?
}
restoreState() {
local var="$1"
local name="$2"
local force="${3:-N}"
local prefix="${4:-$PROCESS}"
local value
if ! enabled "$force"; then
[ -z "${!var:-}" ] || return 0
fi
value=$(readState "$name" "$prefix") || return 1
[ -n "$value" ] || return 0
printf -v "$var" '%s' "$value" || return 1
return 0
}
escape () { escape () {
local s local s
s=${1//&/\&amp;} s=${1//&/\&amp;}
s=${s//</\&lt;} s=${s//</\&lt;}
s=${s//>/\&gt;} s=${s//>/\&gt;}
s=${s//'"'/\&quot;} s=${s//'"'/\&quot;}
printf -- %s "$s" printf -- %s "$s"
return 0
}
escapeXML() {
printf '%s' "$1" | sed \
-e 's/&/\&amp;/g' \
-e 's/</\&lt;/g' \
-e 's/>/\&gt;/g' \
-e 's/"/\&quot;/g' \
-e "s/'/\&apos;/g"
return 0 return 0
} }
html() { html() {
local title local title
local body local body
local script local script
@@ -102,13 +411,14 @@ html() {
HTML="${HTML/\[4\]/$footer}" HTML="${HTML/\[4\]/$footer}"
HTML="${HTML/\[5\]/$FOOTER2}" HTML="${HTML/\[5\]/$FOOTER2}"
echo "$HTML" > "$PAGE" echo "$HTML" > "$PAGE" || return 1
echo "$body" > "$INFO" echo "$body" > "$INFO" || return 1
return 0 return 0
} }
cpu() { cpu() {
local ret local ret
local cpu="" local cpu=""
@@ -123,11 +433,11 @@ cpu() {
fi fi
cpu="${cpu// CPU/}" cpu="${cpu// CPU/}"
cpu="${cpu// [0-9] Core}"
cpu="${cpu// [0-9][0-9] Core}"
cpu="${cpu// [0-9][0-9][0-9] Core}" cpu="${cpu// [0-9][0-9][0-9] Core}"
cpu="${cpu// [0-9]th Gen}" cpu="${cpu// [0-9][0-9] Core}"
cpu="${cpu// [0-9][0-9]th Gen}" cpu="${cpu// [0-9] Core}"
cpu="${cpu//[0-9][0-9]th Gen }"
cpu="${cpu//[0-9]th Gen }"
cpu="${cpu// Processor/}" cpu="${cpu// Processor/}"
cpu="${cpu// Quad core/}" cpu="${cpu// Quad core/}"
cpu="${cpu// Dual core/}" cpu="${cpu// Dual core/}"
@@ -145,18 +455,67 @@ cpu() {
return 0 return 0
} }
hasDisk() { getCountry() {
[ -b "/disk" ] && return 0 local url=$1
[ -b "/disk1" ] && return 0 local query=$2
[ -b "/dev/disk1" ] && return 0 local rc json result
[ -b "${DEVICE:-}" ] && return 0
[ -z "${DISK_NAME:-}" ] && DISK_NAME="data" { json=$(curl -m 5 -H "Accept: application/json" -sfk "$url"); rc=$?; } || :
[ -s "$STORAGE/$DISK_NAME.img" ] && return 0 (( rc != 0 )) && return 0
[ -s "$STORAGE/$DISK_NAME.qcow2" ] && return 0
return 1 { result=$(echo "$json" | jq -r "$query" 2> /dev/null); rc=$?; } || :
(( rc != 0 )) && return 0
[[ ${#result} -ne 2 ]] && return 0
[[ "${result^^}" == "XX" ]] && return 0
COUNTRY="${result^^}"
return 0
}
setCountry() {
[[ "${TZ,,}" == "asia/harbin" ]] && COUNTRY="CN"
[[ "${TZ,,}" == "asia/beijing" ]] && COUNTRY="CN"
[[ "${TZ,,}" == "asia/urumqi" ]] && COUNTRY="CN"
[[ "${TZ,,}" == "asia/kashgar" ]] && COUNTRY="CN"
[[ "${TZ,,}" == "asia/shanghai" ]] && COUNTRY="CN"
[[ "${TZ,,}" == "asia/chongqing" ]] && COUNTRY="CN"
[ -z "$COUNTRY" ] && getCountry "https://api.ipapi.is" ".location.country_code"
[ -z "$COUNTRY" ] && getCountry "https://ifconfig.co/json" ".country_iso"
[ -z "$COUNTRY" ] && getCountry "https://api.ip2location.io" ".country_code"
[ -z "$COUNTRY" ] && getCountry "https://ipinfo.io/json" ".country"
[ -z "$COUNTRY" ] && getCountry "https://api.ipquery.io/?format=json" ".location.country_code"
[ -z "$COUNTRY" ] && getCountry "https://api.myip.com" ".cc"
return 0
}
addPackage() {
local pkg=$1
local desc=$2
if apt-mark showinstall | grep -qx "$pkg"; then
return 0
fi
MSG="Installing $desc..."
info "$MSG" && html "$MSG"
[ -z "$COUNTRY" ] && setCountry
if [[ "${COUNTRY^^}" == "CN" ]]; then
sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list.d/debian.sources
fi
DEBIAN_FRONTEND=noninteractive apt-get -qq update || return 1
DEBIAN_FRONTEND=noninteractive apt-get -qq --no-install-recommends -y install "$pkg" > /dev/null || return 1
return 0
} }
return 0 return 0
+14
View File
@@ -27,4 +27,18 @@ server {
index index.html; index index.html;
} }
location /status {
proxy_http_version 1.1;
proxy_set_header Connection 'upgrade';
proxy_set_header Upgrade $http_upgrade;
proxy_buffering off;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
proxy_pass http://127.0.0.1:8004/;
}
} }
+110 -32
View File
@@ -1,4 +1,5 @@
var request; var request;
var booting = false;
var interval = 1000; var interval = 1000;
function getInfo() { function getInfo() {
@@ -6,11 +7,14 @@ function getInfo() {
var url = "msg.html"; var url = "msg.html";
try { try {
if (request) {
request.abort();
}
if (window.XMLHttpRequest) { if (window.XMLHttpRequest) {
request = new XMLHttpRequest(); request = new XMLHttpRequest();
} else { } else {
throw "XMLHttpRequest not available!"; throw new Error("XMLHttpRequest not available!");
} }
request.onreadystatechange = processInfo; request.onreadystatechange = processInfo;
@@ -18,22 +22,49 @@ function getInfo() {
request.send(); request.send();
} catch (e) { } catch (e) {
var err = "Error: " + e.message; setError("Error: " + e.message);
console.log(err);
setError(err);
} }
} }
function getURL() {
var protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
var path = window.location.pathname.replace(/[^/]*$/, '').replace(/\/$/, '');
return protocol + "//" + window.location.host + path;
}
function processMsg(msg) {
if (msg.toLowerCase().indexOf("href=") !== -1) {
var div = document.createElement("div");
div.innerHTML = msg;
var url = div.querySelector("a").href;
setTimeout(() => {
window.location.assign(url);
}, 3000);
}
setInfo(msg);
return true;
}
function processInfo() { function processInfo() {
try { try {
if (request.readyState != 4) { if (request.readyState != 4) {
return true; return true;
} }
var msg = request.responseText; var msg = request.responseText;
if (msg == null || msg.length == 0) { if (msg == null || msg.length == 0) {
setInfo("Booting DSM instance", true);
schedule(); if (booting) {
schedule();
return true;
}
window.location.reload();
return false; return false;
} }
@@ -43,20 +74,11 @@ function processInfo() {
if (msg.toLowerCase().indexOf("<html>") !== -1) { if (msg.toLowerCase().indexOf("<html>") !== -1) {
notFound = true; notFound = true;
} else { } else {
if (msg.toLowerCase().indexOf("href=") !== -1) { processMsg(msg);
var div = document.createElement("div"); if (msg.toLowerCase().indexOf("href=") == -1) {
div.innerHTML = msg;
var url = div.querySelector("a").href;
setTimeout(() => {
window.location.assign(url);
}, 3000);
setInfo(msg);
return true;
} else {
setInfo(msg);
schedule(); schedule();
return true;
} }
return true;
} }
} }
@@ -67,44 +89,60 @@ function processInfo() {
} }
setError("Error: Received statuscode " + request.status); setError("Error: Received statuscode " + request.status);
schedule();
return false; return false;
} catch (e) { } catch (e) {
var err = "Error: " + e.message; setError("Error: " + e.message);
console.log(err);
setError(err);
return false; return false;
} }
} }
function setInfo(msg, loading, error) { function extractContent(s) {
var span = document.createElement('span');
span.innerHTML = s;
return span.textContent || span.innerText;
};
function setInfo(msg, loading, error) {
try { try {
if (msg == null || msg.length == 0) { if (msg == null || msg.length == 0) {
return false; return false;
} }
var el = document.getElementById("spinner"); if (msg.includes("Booting ")) {
booting = true;
}
var el = document.getElementById("info");
if (el.innerText == msg || el.innerHTML == msg) {
return true;
}
var spin = document.getElementById("spinner");
error = !!error; error = !!error;
if (!error) { if (!error) {
el.style.visibility = 'visible'; spin.style.visibility = 'visible';
} else { } else {
el.style.visibility = 'hidden'; spin.style.visibility = 'hidden';
} }
var p = "<p class=\"loading\">";
loading = !!loading; loading = !!loading;
if (loading) { if (loading) {
msg = "<p class=\"loading\">" + msg + "</p>"; msg = p + msg + "</p>";
} }
el = document.getElementById("info"); if (msg.includes(p)) {
if (el.innerHTML.includes(p)) {
if (el.innerHTML != msg) { el.getElementsByClassName('loading')[0].textContent = extractContent(msg);
el.innerHTML = msg; return true;
}
} }
el.innerHTML = msg;
return true; return true;
} catch (e) { } catch (e) {
@@ -114,6 +152,7 @@ function setInfo(msg, loading, error) {
} }
function setError(text) { function setError(text) {
console.warn(text);
return setInfo(text, false, true); return setInfo(text, false, true);
} }
@@ -123,8 +162,47 @@ function schedule() {
function reload() { function reload() {
setTimeout(() => { setTimeout(() => {
document.location.reload(); window.location.reload();
}, 3000); }, 3000);
} }
function connect() {
var wsUrl = getURL() + "/status";
var ws = new WebSocket(wsUrl);
ws.onmessage = function(e) {
var pos = e.data.indexOf(":");
var cmd = e.data.substring(0, pos);
var msg = e.data.substring(pos + 2);
switch (cmd) {
case "s":
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(); schedule();
connect();