Compare commits

...
291 Commits
Author SHA1 Message Date
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
KroeseandGitHub 87a8cf7513 feat: Support 32k sector sizes (#1034) 2025-09-30 10:49:07 +02:00
KroeseandGitHub 7f31cb6023 fix: Detect host mode networking (#1033) 2025-09-30 10:32:00 +02:00
KroeseandGitHub 138742c953 feat: Increase default disksize (#1030) 2025-09-28 18:22:44 +02:00
KroeseandGitHub 2c6efc45f2 feat: Make webserver port configurable (#1028) 2025-09-27 11:44:13 +02:00
KroeseandGitHub 24d795fbe3 buid: Add ethtool package (#1027) 2025-09-27 10:45:09 +02:00
KroeseandGitHub 7fae62d286 feat: Detect host mode networking (#1026) 2025-09-27 10:44:02 +02:00
KroeseandGitHub 2135df07ea docs: Additional info for user-mode networking (#1025) 2025-09-27 10:08:11 +02:00
KroeseandGitHub 521beedf1c feat: Add note for MAC address availability (#1024) 2025-09-27 09:49:11 +02:00
KroeseandGitHub e362c9a8a9 feat: Update error message for KVM acceleration (#1023) 2025-09-24 16:10:32 +02:00
KroeseandGitHub 78817ecfc1 fix: Remove default model (#1022) 2025-09-23 16:04:44 +02:00
KroeseandGitHub 8de7f56ff8 feat: Add KVM warning (#1021) 2025-09-23 16:02:42 +02:00
KroeseandGitHub 2e2017470e fix: Syntax for arithmetic operations (#1020) 2025-09-22 20:24:23 +02:00
KroeseandGitHub 0a0cd98de3 feat: Add "max" setting to automaticly set CPU cores and RAM (#1019) 2025-09-22 16:59:02 +02:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
17187dd23a chore(deps): update hadolint/hadolint-action action to v3.3.0 (#1018)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-22 16:56:58 +02:00
KroeseandGitHub 10237b31cf revert: Simplify conditional checks (#1017)
This reverts commit 6883efb857.
2025-09-21 23:08:00 +02:00
KroeseandGitHub 6883efb857 fix: Simplify conditional checks (#1016) 2025-09-21 15:09:52 +02:00
KroeseandGitHub 1ca55cad8f feat: Limit CPU_CORES to amount of physical cores (#1015) 2025-09-21 13:41:20 +02:00
KroeseandGitHub f841eb1ef1 fix: Simplify conditional checks (#1014) 2025-09-20 23:47:00 +02:00
KroeseandGitHub 1ee49332f3 feat: Improve CPU detection (#1013) 2025-09-20 21:43:35 +02:00
KroeseandGitHub 095e618785 feat: Make webserver optional (#1012) 2025-09-18 23:45:05 +02:00
KroeseandGitHub 57a902ad9f feat: add UDP support to USER_PORTS (#1011)
Allow USER_PORTS entries in the form PORT or PORT/PROTO (tcp or udp). When the protocol is omitted, TCP is assumed for backward compatibility. This enables forwarding of UDP services when running the container in user‑mode networking.
2025-09-17 21:49:20 +02:00
xrh0905andGitHub a7e229aaae feat: Add IPQuery.io as additional comparison source for the country code (#1006) 2025-09-17 17:55:54 +02:00
KroeseandGitHub 10466d7851 feat: Parse CPU flags (#1010) 2025-09-17 17:54:50 +02:00
KroeseandGitHub 238ebd273b build: Disable automatic builds (#1009)
Removed push triggers and paths to ignore from build workflow.
2025-09-14 11:54:51 +02:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
0b08f4212e chore(deps): update hadolint/hadolint-action action to v3.2.0 (#1008)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-03 16:43:41 +02:00
KroeseandGitHub 9aec5d20e3 feat: Check if storage folder is writeable (#1005) 2025-08-27 22:50:23 +02:00
KroeseandGitHub a19bf2f066 feat: Check if storage folder is writeable (#1004) 2025-08-27 22:48:23 +02:00
KroeseandGitHub bee68ee548 feat: Add syntax parser directive (#1003) 2025-08-27 21:29:28 +02:00
xrh0905andGitHub 520c70ab22 feat: Allow shutdown timeout override (#995)
Mitigation #987
2025-08-27 19:58:02 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
67a7fbc94b build(deps): Bump actions/checkout from 4 to 5 (#1001)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-27 19:56:41 +02:00
KroeseandGitHub 1b8054e847 fix: Podman detection (#990) 2025-05-27 00:50:40 +02:00
KroeseandGitHub 631a558a9f feat: Enable IPv6 support for user-mode container networking (#983) 2025-04-27 11:28:54 +02:00
KroeseandGitHub d5805b4e08 feat: Check CPU vendor (#982) 2025-04-27 11:21:37 +02:00
KroeseandGitHub 1dc15ce2e0 docs: Add input types (#981) 2025-04-23 12:54:22 +02:00
KroeseandGitHub 4145e811df feat: Improve CPU detection (#980) 2025-04-20 21:43:02 +02:00
KroeseandGitHub fe8615d7d3 docs: Add quotes around $PWD (#977) 2025-04-16 12:10:59 +02:00
KroeseandGitHub 27ea5506bc feat: Default to 2 CPU cores and 2GB of RAM (#976) 2025-04-16 11:56:02 +02:00
KroeseandGitHub 394131a0d7 feat: Podman detection (#972) 2025-04-09 09:01:01 +02:00
KroeseandGitHub 4d0d16023f feat: Podman detection (#971) 2025-04-09 08:58:02 +02:00
KroeseandGitHub 60dd794b29 fix: Avoid pipe to head on find (#970) 2025-04-09 08:03:41 +02:00
KroeseandGitHub f13d104968 build: Add root-user-action flag (#968) 2025-04-08 07:53:17 +02:00
KroeseandGitHub 914099fd12 build: Add --root-user-action flag (#967) 2025-04-07 18:47:36 +02:00
KroeseandGitHub 639725957d fix: Permission errors on QNAP (#966) 2025-04-07 18:36:47 +02:00
KroeseandGitHub 64bbe82c4a fix: KVM warning (#965) 2025-04-07 17:44:26 +02:00
KroeseandGitHub 73a13e5697 fix: QNAP permission errors (#964) 2025-04-07 17:20:14 +02:00
KroeseandGitHub e8ec5fb8ed feat: Support 4k sector sizes (#963) 2025-04-07 13:25:20 +02:00
KroeseandGitHub 0b500ca377 docs: Disk pass-through (#960) 2025-04-03 10:22:26 +02:00
KroeseandGitHub df58745ed8 fix: Ignore missing custom .pat after install (#959) 2025-04-03 01:13:14 +02:00
KroeseandGitHub 55e8ab4930 docs: Github Codespaces (#956) 2025-03-27 01:48:40 +01:00
KroeseandGitHub 4882fd3bf6 fix: Local file support (#955) 2025-03-26 19:51:58 +01:00
KroeseandGitHub 3e69f4bcc9 feat: New extraction method (#954) 2025-03-26 09:24:13 +01:00
KroeseandGitHub 39428909d6 docs: Installation (#953) 2025-03-26 02:47:28 +01:00
KroeseandGitHub 71d2ed40db docs: Github Codespaces (#952) 2025-03-25 14:49:46 +01:00
KroeseandGitHub 9ad21a28a6 docs: Disk pass-through (#951) 2025-03-25 12:18:38 +01:00
KroeseandGitHub 0c7acd6e71 fix: Remove non-printable characters (#949) 2025-03-24 13:52:38 +01:00
KroeseandGitHub ad26129375 docs: KVM information (#947) 2025-03-20 23:21:41 +01:00
KroeseandGitHub 4e7822fedd docs: Compatibility chart (#946) 2025-03-20 23:07:14 +01:00
KroeseandGitHub 4dfcbe4ab1 docs: Add Podman (#945) 2025-03-20 20:09:53 +01:00
KroeseandGitHub 8fb6f1f9ad feat: Podman detection (#944) 2025-03-20 11:54:26 +01:00
KroeseandGitHub 11d4fafa6d fix: User-mode networking (#943) 2025-03-20 03:07:01 +01:00
KroeseandGitHub be15557798 fix: User-mode networking (#942) 2025-03-20 03:05:50 +01:00
KroeseandGitHub 1e83757494 fix: Set user-mode IP address (#941) 2025-03-19 12:55:53 +01:00
KroeseandGitHub fad463a439 feat: Add devcontainer (#940) 2025-03-19 09:17:48 +01:00
KroeseandGitHub 608563d029 feat: Add devcontainer (#939) 2025-03-18 19:25:27 +01:00
KroeseandGitHub 5fcd22b09f docs: Formatting (#938) 2025-03-18 14:24:05 +01:00
KroeseandGitHub 65548da2ef docs: Github Codespaces (#937) 2025-03-18 14:10:08 +01:00
KroeseandGitHub 111e513dac docs: Github Codespaces (#936) 2025-03-18 14:09:10 +01:00
KroeseandGitHub 7fce3a98fe feat: Refactor helper functions (#935) 2025-03-18 12:57:52 +01:00
KroeseandGitHub 4018eeadb4 fix: Remove port 80 (#934) 2025-03-18 05:03:54 +01:00
KroeseandGitHub a8e4647fa6 feat: IPv6 support (#933) 2025-03-18 03:59:58 +01:00
KroeseandGitHub 36d3fca4fc fix: Fallback to POSIX fallocate (#932) 2025-03-17 11:19:04 +01:00
KroeseandGitHub 89b28f36d3 docs: Disk pass-through (#930) 2025-03-16 12:30:06 +01:00
KroeseandGitHub b4f7d70a7a docs: Readme (#929) 2025-03-16 06:52:14 +01:00
KroeseandGitHub 902bafbd0c docs: Readme (#928) 2025-03-16 06:40:38 +01:00
KroeseandGitHub 7ad5c7fa70 fix: Display available memory (#926) 2025-03-15 14:09:17 +01:00
KroeseandGitHub 4d29f056b7 fix: Format filesizes (#925) 2025-03-14 18:53:53 +01:00
KroeseandGitHub 36af9a3483 feat: Validate configured RAM (#924) 2025-03-14 14:42:08 +01:00
KroeseandGitHub 5e6f931433 feat: Detect clock source (#923) 2025-03-14 14:16:10 +01:00
KroeseandGitHub beadfe720c feat: Improve CPU detection (#921) 2025-03-14 05:23:49 +01:00
KroeseandGitHub 92b4bfc383 feat: Validate configured RAM (#920) 2025-03-13 11:35:08 +01:00
KroeseandGitHub 092ed23085 feat: Check CPU core configuration (#919) 2025-03-13 10:47:25 +01:00
KroeseandGitHub c70fa3d00a feat: Validate specified size (#918) 2025-03-12 21:17:47 +01:00
KroeseandGitHub fea0ba09f6 docs: Use relative paths (#917) 2025-03-12 12:31:50 +01:00
KroeseandGitHub 7d2af63eac docs: Kubernetes deployment (#916) 2025-03-07 00:42:29 +01:00
KroeseandGitHub 2e73bf560e docs: Add link to download (#915) 2025-03-07 00:22:29 +01:00
KroeseandGitHub bfc8d7a9c6 fix: Network shutdown (#912) 2025-03-05 04:59:57 +01:00
KroeseandGitHub a1e9936572 fix: Set MTU value (#911) 2025-03-04 15:37:37 +01:00
KroeseandGitHub 3675a50129 fix: Gateway MAC generation (#910) 2025-03-03 13:57:53 +01:00
KroeseandGitHub 91229152bd feat: Set MTU size for TAP interface (#909) 2025-03-03 13:40:15 +01:00
KroeseandGitHub a1993e590a feat: Automaticly match MTU size (#908) 2025-03-03 12:20:16 +01:00
KroeseandGitHub 52fe712b6f docs: Readme (#907) 2025-03-01 14:23:51 +01:00
KroeseandGitHub 7f400b6b59 feat: Improve CPU detection (#905) 2025-02-27 11:52:48 +01:00
KroeseandGitHub 9cb88a99e6 feat: Allow bridge networks (#904) 2025-02-27 11:46:38 +01:00
KroeseandGitHub b967a471b5 fix: Add e2fsprogs package (#902) 2025-02-26 10:03:46 +01:00
KroeseandGitHub f40127df01 feat: Make app name configurable (#900) 2025-02-25 15:13:57 +01:00
KroeseandGitHub 8c5e0ee274 fix: Generate local MAC address (#899) 2025-02-24 21:01:55 +01:00
KroeseandGitHub 2adf0b292b fix: Preserve gateway MAC address (#898) 2025-02-24 03:48:42 +01:00
KroeseandGitHub 1c9b793c75 fix: Move nginx config (#893) 2025-02-15 02:36:07 +01:00
KroeseandGitHub 00c4ef7795 feat: Remove existing TAP interface (#892) 2025-02-14 20:19:12 +01:00
KroeseandGitHub 619657adf2 docs: Add restart policy (#888) 2025-02-10 00:31:32 +01:00
KroeseandGitHub 41db7c1035 feat: Improve CPU detection (#884) 2025-02-06 02:19:39 +01:00
KroeseandGitHub d71834a777 build: Enable ARM64 platform (#873) 2025-01-10 18:44:37 +01:00
KroeseandGitHub d078af0397 fix: TUN device error (#863) 2024-12-03 11:50:56 +01:00
KroeseandGitHub 2827d1d375 docs: Add TUN device (#861) 2024-12-01 17:57:00 +01:00
KroeseandGitHub 898499a4e3 feat: Make network adapter configurable (#859) 2024-11-26 20:01:37 +01:00
KroeseandGitHub eb010cc215 feat: Improve network error handling (#856) 2024-11-20 16:24:07 +01:00
KroeseandGitHub 84440d5159 fix: Use relative URL's (#855) 2024-11-20 13:21:20 +01:00
KroeseandGitHub ff3744ead9 feat: Improve CPU detection (#854) 2024-11-20 13:13:43 +01:00
KroeseandGitHub d00fe4b3eb docs: Readme (#848) 2024-11-13 03:45:21 +01:00
KroeseandGitHub 72a86a5d7f docs: Add Kubernetes URL (#847) 2024-11-13 03:35:50 +01:00
KroeseandGitHub 811ab622df docs: Add compatibility chart (#846) 2024-11-13 03:26:15 +01:00
KroeseandGitHub e76b72cddf feat: Disable HTTP keepalives (#845) 2024-11-13 02:48:27 +01:00
KroeseandGitHub e9edacc9c3 feat: Support image commit (#844) 2024-11-13 00:39:48 +01:00
KroeseandGitHub a6694a6b29 feat: Rename host to kernel (#843) 2024-11-11 16:24:47 +01:00
KroeseandGitHub 59323cd375 docs: KVM troubleshooting (#842) 2024-11-11 14:16:21 +01:00
KroeseandGitHub 6dc714e449 feat: Display unknown filesystem (#841) 2024-11-10 15:00:38 +01:00
KroeseandGitHub 5d75a9b039 feat: Improve CPU detection (#840) 2024-11-10 11:21:51 +01:00
KroeseandGitHub 92b4cf5997 build: Use latest Debian image (#832) 2024-10-19 22:09:52 +02:00
KroeseandGitHub 906e61b1b2 feat: Improve CPU detection (#831) 2024-10-15 10:27:51 +02:00
KroeseandGitHub dab230f9d5 build: Remove ARM64 platform (#830) 2024-10-15 00:37:28 +02:00
KroeseandGitHub 5e8bcda9fc feat: Improve CPU detection (#829) 2024-10-14 18:06:02 +02:00
KroeseandGitHub d4bf83ae86 feat: Add NVME disk type (#828) 2024-10-13 03:03:32 +02:00
xrh0905andGitHub 8244a48511 Correct the possible DISK_IO (io_uring) (#827) 2024-10-06 16:26:35 +02:00
KroeseandGitHub 43ffa18a5f fix: Remove scsi parameter from virtio-blk-pci (#824) 2024-10-01 21:49:31 +02:00
KroeseandGitHub b131a32d0c buid: Pin Debian version (#818) 2024-09-27 14:40:56 +02:00
D-JyandGitHub c81787b837 feat: Allow custom rendernode (#817) 2024-09-17 23:30:58 +02:00
36 changed files with 3796 additions and 1467 deletions
+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
+2
View File
@@ -21,6 +21,7 @@ body:
attributes: attributes:
label: Docker compose label: Docker compose
description: The compose file (or otherwise the `docker run` command used). description: The compose file (or otherwise the `docker run` command used).
render: yaml
validations: validations:
required: true required: true
- type: textarea - type: textarea
@@ -28,6 +29,7 @@ body:
attributes: attributes:
label: Docker log label: Docker log
description: The logfile of the container (as shown by `docker logs dsm`). description: The logfile of the container (as shown by `docker logs dsm`).
render: shell
validations: validations:
required: true required: true
- type: textarea - type: textarea
+2
View File
@@ -23,6 +23,7 @@ body:
attributes: attributes:
label: Docker compose label: Docker compose
description: The compose file (or otherwise the `docker run` command used). description: The compose file (or otherwise the `docker run` command used).
render: yaml
validations: validations:
required: true required: true
- type: textarea - type: textarea
@@ -30,6 +31,7 @@ body:
attributes: attributes:
label: Docker log label: Docker log
description: The logfile of the container (as shown by `docker logs dsm`). description: The logfile of the container (as shown by `docker logs dsm`).
render: shell
validations: validations:
required: true required: true
- type: textarea - type: textarea
+7 -21
View File
@@ -2,20 +2,6 @@ name: Build
on: on:
workflow_dispatch: workflow_dispatch:
push:
branches:
- master
paths-ignore:
- '**/*.md'
- '**/*.yml'
- '**/*.js'
- '**/*.css'
- '**/*.html'
- 'web/**'
- '.gitignore'
- '.dockerignore'
- '.github/**'
- '.github/workflows/**'
concurrency: concurrency:
group: build group: build
@@ -36,13 +22,13 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v7
with: with:
fetch-depth: 0 fetch-depth: 0
- -
name: Docker metadata name: Docker metadata
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v6
with: with:
context: git context: git
images: | images: |
@@ -54,26 +40,26 @@ jobs:
labels: | labels: |
org.opencontainers.image.title=${{ vars.NAME }} org.opencontainers.image.title=${{ vars.NAME }}
env: env:
DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index
- -
name: Set up Docker Buildx name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v4
- -
name: Login into Docker Hub name: Login into Docker Hub
uses: docker/login-action@v3 uses: docker/login-action@v4
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- -
name: Login to GitHub Container Registry name: Login to GitHub Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v4
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- -
name: Build Docker image name: Build Docker image
uses: docker/build-push-action@v6 uses: docker/build-push-action@v7
with: with:
context: . context: .
push: true push: true
+11 -5
View File
@@ -7,9 +7,10 @@ jobs:
name: shellcheck name: shellcheck
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v7
- parallel:
- -
name: Run ShellCheck name: Run ShellCheck
uses: ludeeus/action-shellcheck@master uses: ludeeus/action-shellcheck@master
@@ -17,8 +18,13 @@ jobs:
SHELLCHECK_OPTS: -x --source-path=src -e SC2001 -e SC2034 -e SC2064 -e SC2317 -e SC2153 -e SC2028 SHELLCHECK_OPTS: -x --source-path=src -e SC2001 -e SC2034 -e SC2064 -e SC2317 -e SC2153 -e SC2028
- -
name: Lint Dockerfile name: Lint Dockerfile
uses: hadolint/hadolint-action@v3.1.0 uses: hadolint/hadolint-action@v3.3.0
with: with:
dockerfile: Dockerfile dockerfile: Dockerfile
ignore: DL3008,DL3003,DL3006 ignore: DL3008,DL3003,DL3006,DL3013
failure-threshold: warning failure-threshold: warning
-
name: Validate JSON and YML files
uses: GrantBirki/json-yaml-validate@v5.0.0
with:
yaml_exclude_regex: ".*\\kubernetes\\.yml$"
+12 -10
View File
@@ -12,13 +12,15 @@ jobs:
dockerHubDescription: dockerHubDescription:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 -
- name: Checkout repo
name: Docker Hub Description uses: actions/checkout@v7
uses: peter-evans/dockerhub-description@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
+71
View File
@@ -0,0 +1,71 @@
on:
pull_request:
name: "Review"
permissions:
contents: read
pull-requests: write
checks: write
jobs:
review:
name: review
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v7
- parallel:
-
name: Spelling
uses: reviewdog/action-misspell@v1
with:
locale: "US"
level: warning
pattern: |
*.md
*.sh
reporter: github-pr-review
github_token: ${{ secrets.GITHUB_TOKEN }}
-
name: Hadolint
uses: reviewdog/action-hadolint@v1
with:
level: warning
fail_level: error
reporter: github-pr-review
hadolint_ignore: DL3008 DL3003 DL3006 DL3013
github_token: ${{ secrets.GITHUB_TOKEN }}
-
name: YamlLint
uses: reviewdog/action-yamllint@v1
with:
level: warning
reporter: github-pr-review
github_token: ${{ secrets.GITHUB_TOKEN }}
-
name: ActionLint
uses: reviewdog/action-actionlint@v1
with:
level: warning
reporter: github-pr-review
github_token: ${{ secrets.GITHUB_TOKEN }}
-
name: Shellformat
uses: reviewdog/action-shfmt@v1
if: false
with:
level: warning
fail_on_error: "true"
shfmt_flags: "-i 2 -ci -bn"
github_token: ${{ secrets.GITHUB_TOKEN }}
-
name: Shellcheck
uses: reviewdog/action-shellcheck@v1
with:
level: warning
fail_level: error
reporter: github-pr-review
shellcheck_flags: -x -e SC1091 -e SC2001 -e SC2034 -e SC2064 -e SC2317 -e SC2153 -e SC2028
github_token: ${{ secrets.GITHUB_TOKEN }}
-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: {}
+67 -42
View File
@@ -1,62 +1,87 @@
# syntax=docker/dockerfile:1
FROM qemux/qemu-host:2.05 AS builder FROM qemux/qemu-host:2.05 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_PASST="2026_06_11"
ARG DEBCONF_NOWARNINGS="yes" ARG DEBCONF_NOWARNINGS="yes"
ARG DEBIAN_FRONTEND="noninteractive" ARG DEBIAN_FRONTEND="noninteractive"
ARG DEBCONF_NONINTERACTIVE_SEEN="true" ARG DEBCONF_NONINTERACTIVE_SEEN="true"
RUN set -eu && extra="" && \ RUN <<EOF
if [ "$TARGETPLATFORM" != "linux/amd64" ]; then extra="qemu-user"; fi && \ set -eu
apt-get update && \
apt-get --no-install-recommends -y install \ apt-get update
jq \ apt-get --no-install-recommends -y install \
tini \ jq \
curl \ tini \
cpio \ curl \
wget \ wget \
fdisk \ fdisk \
unzip \ unzip \
nginx \ nginx \
procps \ procps \
xz-utils \ ipcalc \
iptables \ ethtool \
iproute2 \ python3 \
apt-utils \ python3-pip \
dnsmasq \ python3-msgpack \
fakeroot \ python3-pysodium \
net-tools \ xz-utils \
qemu-utils \ iptables \
ca-certificates \ iproute2 \
netcat-openbsd \ dnsmasq \
qemu-system-x86 \ fakeroot \
"$extra" && \ apt-utils \
apt-get clean && \ net-tools \
unlink /etc/nginx/sites-enabled/default && \ e2fsprogs \
sed -i 's/^worker_processes.*/worker_processes 1;/' /etc/nginx/nginx.conf && \ qemu-utils \
echo "$VERSION_ARG" > /run/version && \ websocketd \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* iputils-ping \
inotify-tools \
ca-certificates \
netcat-openbsd \
qemu-system-x86
# Install Passt package
wget "https://github.com/qemus/passt/releases/download/v${VERSION_PASST}/passt_${VERSION_PASST}_${TARGETARCH}.deb" -O /tmp/passt.deb -q --timeout=10
dpkg -i /tmp/passt.deb
apt-get clean
# Install Python dependencies
pip3 install --no-cache-dir --break-system-packages --root-user-action=ignore dissect.cstruct
# Configure QEMU
mkdir -p /etc/qemu
echo "allow br0" > /etc/qemu/bridge.conf
# Configure nginx
unlink /etc/nginx/sites-enabled/default
sed -i 's/^worker_processes.*/worker_processes 1;/' /etc/nginx/nginx.conf
# Set version file
echo "$VERSION_ARG" > /etc/version
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
EOF
COPY --chmod=755 ./src /run/ COPY --chmod=755 ./src /run/
COPY --chmod=755 ./web /var/www/ COPY --chmod=755 ./web /var/www/
COPY --chmod=755 --from=builder /qemu-host.bin /run/host.bin COPY --chmod=755 --from=builder /qemu-host.bin /run/host.bin
COPY --chmod=744 ./web/nginx.conf /etc/nginx/sites-enabled/web.conf COPY --chmod=744 ./web/conf/nginx.conf /etc/nginx/default.conf
ADD --chmod=775 https://raw.githubusercontent.com/sud0woodo/patology/refs/heads/main/patology.py /run/extract.py
VOLUME /storage VOLUME /storage
EXPOSE 22 139 445 5000 EXPOSE 22 139 445 5000
ENV RAM_SIZE="1G" ENV RAM_SIZE="2G"
ENV DISK_SIZE="16G" ENV CPU_CORES="2"
ENV CPU_CORES="1" ENV DISK_SIZE="256G"
HEALTHCHECK --interval=60s --start-period=45s --retries=2 CMD /run/check.sh HEALTHCHECK --interval=60s --start-period=45s --retries=2 CMD /run/check.sh
+16 -14
View File
@@ -1,15 +1,17 @@
services: services:
dsm: dsm:
container_name: dsm container_name: dsm
image: vdsm/virtual-dsm:latest image: vdsm/virtual-dsm
environment: environment:
DISK_SIZE: "16G" DISK_SIZE: "256G"
devices: devices:
- /dev/kvm - /dev/kvm
cap_add: - /dev/net/tun
- NET_ADMIN cap_add:
ports: - NET_ADMIN
- 5000:5000 ports:
volumes: - 5000:5000
- /var/dsm:/storage volumes:
stop_grace_period: 2m - ./dsm:/storage
restart: always
stop_grace_period: 2m
+49 -31
View File
@@ -1,59 +1,77 @@
---
apiVersion: v1 apiVersion: v1
kind: PersistentVolumeClaim kind: PersistentVolumeClaim
metadata: metadata:
name: dsm-pvc name: dsm-pvc
spec: spec:
accessModes: accessModes:
- ReadWriteOnce - ReadWriteOnce
resources: resources:
requests: requests:
storage: 16Gi storage: 256Gi
--- ---
apiVersion: v1 apiVersion: apps/v1
kind: Pod kind: Deployment
metadata: metadata:
name: dsm name: dsm
labels: labels:
name: dsm name: dsm
spec: spec:
terminationGracePeriodSeconds: 120 # the Kubernetes default is 30 seconds and it may be not enough replicas: 1
containers: selector:
- name: dsm matchLabels:
image: vdsm/virtual-dsm app: dsm
ports: template:
- containerPort: 5000 metadata:
protocol: TCP labels:
securityContext: app: dsm
privileged: true spec:
env: containers:
- name: RAM_SIZE - name: dsm
value: 1G image: vdsm/virtual-dsm
- name: CPU_CORES env:
value: "1"
- name: DISK_SIZE - name: DISK_SIZE
value: "16G" # Kubernetes uses Gi, but DSM uses GB value: "256G"
volumeMounts: ports:
- containerPort: 5000
name: http
protocol: TCP
securityContext:
capabilities:
add:
- NET_ADMIN
privileged: true
volumeMounts:
- mountPath: /storage - mountPath: /storage
name: storage name: storage
- mountPath: /dev/kvm - mountPath: /dev/kvm
name: dev-kvm name: dev-kvm
volumes: - mountPath: /dev/net/tun
- name: storage name: dev-tun
persistentVolumeClaim: terminationGracePeriodSeconds: 120
claimName: dsm-pvc volumes:
- name: dev-kvm - name: storage
hostPath: persistentVolumeClaim:
path: /dev/kvm claimName: dsm-pvc
- hostPath:
path: /dev/kvm
name: dev-kvm
- hostPath:
path: /dev/net/tun
type: CharDevice
name: dev-tun
--- ---
apiVersion: v1 apiVersion: v1
kind: Service kind: Service
metadata: metadata:
name: dsm name: dsm
spec: spec:
type: NodePort internalTrafficPolicy: Cluster
selector:
name: dsm
ports: ports:
- name: tcp-5000 - name: http
port: 5000 port: 5000
protocol: TCP
targetPort: 5000 targetPort: 5000
selector:
app: dsm
type: ClusterIP
+78 -72
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 bridge, user-mode, macvlan, and macvtap networking
## Usage 🐳 ## Usage 🐳
Via Docker Compose: ##### Docker Compose:
```yaml ```yaml
services: services:
@@ -30,41 +34,58 @@ services:
container_name: dsm container_name: dsm
image: vdsm/virtual-dsm image: vdsm/virtual-dsm
environment: environment:
DISK_SIZE: "16G" DISK_SIZE: "256G"
devices: devices:
- /dev/kvm - /dev/kvm
- /dev/net/tun
cap_add: cap_add:
- NET_ADMIN - NET_ADMIN
ports: ports:
- 5000:5000 - 5000:5000
volumes: volumes:
- /var/dsm:/storage - ./dsm:/storage
restart: always
stop_grace_period: 2m stop_grace_period: 2m
``` ```
Via Docker CLI: ##### Docker CLI:
```bash ```bash
docker run -it --rm -p 5000:5000 --device=/dev/kvm --cap-add NET_ADMIN --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 kubernetes.yml kubectl apply -f https://raw.githubusercontent.com/vdsm/virtual-dsm/refs/heads/master/kubernetes.yml
``` ```
##### GitHub Codespaces:
[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/vdsm/virtual-dsm)
## Requirements ⚙️
- A Linux host with KVM support, or Docker Desktop / Podman on Windows 11 with nested virtualization enabled.
- At least 2 GB of RAM available.
- At least 32 GB of free disk space.
> [!NOTE]
> Docker Desktop on macOS and Windows 10 do not currently provide the required KVM support for this image.
## FAQ 💬 ## FAQ 💬
### How do I use it? ### How do I use it?
Very simple! These are the steps: Very simple! These are the steps:
- Start the container and connect to [port 5000](http://localhost:5000) using your web browser. - Start the container and connect to [port 5000](http://127.0.0.1:5000/) using your web browser.
- Wait until DSM is ready, choose an username and password, and you will be taken to the desktop. - Wait until DSM finishes its installation
- Choose a username and password, and you will be taken to the desktop.
Enjoy your brand new machine, and don't forget to star this repo! Enjoy your brand new NAS, and don't forget to star this repo!
### How do I change the storage location? ### How do I change the storage location?
@@ -72,36 +93,22 @@ kubectl apply -f kubernetes.yml
```yaml ```yaml
volumes: volumes:
- /var/dsm:/storage - ./dsm:/storage
``` ```
Replace the example path `/var/dsm` with the desired storage folder. Replace the example path `./dsm` with the desired storage folder or named volume.
### How do I change the size of the disk? ### How do I change the size of the disk?
To expand the default size of 16 GB, locate the `DISK_SIZE` setting in your compose file and modify it to your preferred capacity: To expand the default size of 256 GB, locate the `DISK_SIZE` setting in your compose file and modify it to your preferred capacity:
```yaml ```yaml
environment: environment:
DISK_SIZE: "128G" DISK_SIZE: "512G"
``` ```
> [!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 create a growable disk?
By default, the entire capacity of the disk is reserved in advance.
To create a growable disk that only allocates space that is actually used, add the following environment variable:
```yaml
environment:
DISK_FMT: "qcow2"
```
> [!NOTE]
> This may reduce the write performance of the disk.
### How do I add multiple disks? ### How do I add multiple disks?
@@ -109,33 +116,28 @@ kubectl apply -f kubernetes.yml
```yaml ```yaml
environment: environment:
DISK2_SIZE: "32G" DISK2_SIZE: "500G"
DISK3_SIZE: "64G" DISK3_SIZE: "750G"
volumes: volumes:
- /home/example:/storage2 - ./example2:/storage2
- /mnt/data/example:/storage3 - ./example3:/storage3
``` ```
### How do I pass-through a disk? ### How do I pass through a disk?
It is possible to pass-through a disk device directly, by adding it 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:
- /dev/disk/by-uuid/12345-12345-12345-12345-12345:/disk2 - /dev/sdb:/disk1
- /dev/sdc1:/disk2
``` ```
Make sure to bind the disk via its UUID (obtainable via `lsblk -o name,uuid`) instead of its name (`/dev/sdc`), to prevent ever binding the wrong disk when the drive letters happen to change. Make sure it is totally empty (without any filesystem), otherwise DSM may not format it as a volume.
> [!IMPORTANT]
> The device needs to be totally empty (without any partition table) otherwise DSM does not always format it into a volume.
> [!CAUTION]
> Do NOT use this feature with the goal of sharing files from the host, they will all be lost without warning when DSM creates the volume.
### 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 1 CPU core and 1 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:
@@ -147,24 +149,31 @@ kubectl apply -f kubernetes.yml
### How do I verify if my system supports KVM? ### How do I verify if my system supports KVM?
To verify that your system supports KVM, run the following commands: 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 ```bash
sudo apt install cpu-checker sudo apt install cpu-checker
sudo kvm-ok sudo kvm-ok
``` ```
If you receive an error from `kvm-ok` indicating that KVM acceleration can't be used, please check whether: 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. - the virtualization extensions (`Intel VT-x` or `AMD SVM`) are enabled in your BIOS.
- you are running an operating system that supports them, like Linux or Windows 11 (macOS and Windows 10 do not unfortunately).
- you enabled "nested virtualization" if you are running the container inside a virtual machine. - 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. - you are not using a cloud provider, as most of them do not allow nested virtualization for their VPSs.
If you didn't receive any error from `kvm-ok` at all, but the container still complains that `/dev/kvm` is missing, it might help to add `privileged: true` to your compose file (or `--privileged` to your `run` command), to rule out any permission issue. 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?
@@ -207,7 +216,7 @@ kubectl apply -f kubernetes.yml
After configuring the container for [macvlan](#how-do-i-assign-an-individual-ip-address-to-the-container), it is possible for DSM to become part of your home network by requesting an IP from your router, just like your other devices. After configuring the container for [macvlan](#how-do-i-assign-an-individual-ip-address-to-the-container), it is possible for DSM to become part of your home network by requesting an IP from your router, just like your other devices.
To enable this mode, add the following lines to your compose file: To enable this mode, in which the container and DSM will have separate IP addresses, add the following lines to your compose file:
```yaml ```yaml
environment: environment:
@@ -218,12 +227,9 @@ kubectl apply -f kubernetes.yml
- 'c *:* rwm' - 'c *:* rwm'
``` ```
> [!NOTE] ### How do I pass through the GPU?
> In this mode, the container and DSM will each have their own separate IPs.
### 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:
@@ -232,41 +238,41 @@ kubectl apply -f kubernetes.yml
- /dev/dri - /dev/dri
``` ```
> [!TIP] > [!NOTE]
> This can be used to enable the facial recognition function in Synology Photos for example. > This can be used to enable the facial recognition function in Synology Photos, but does not provide hardware transcoding for video.
### How do I install a specific version of vDSM? ### How do I install a specific version of vDSM?
By default, version 7.2 will be installed, but if you prefer an older version, you can add its download URL to your compose file as follows: By default, version 7.2 will be installed, but if you prefer an older version, you can add the download URL of the `.pat` file to your compose file as follows:
```yaml ```yaml
environment: environment:
URL: "https://global.synologydownload.com/download/DSM/release/7.0.1/42218/DSM_VirtualDSM_42218.pat" URL: "https://global.synologydownload.com/download/DSM/release/7.0.1/42218/DSM_VirtualDSM_42218.pat"
``` ```
With this method, it is even possible to switch between different versions while keeping all your file data intact. With this method, it is even possible to switch back and forth between versions while keeping your file data intact.
If you don't have internet access, it's also possible to skip the download by setting URL to:
Alternatively, you can also skip the download and use a local file instead, by binding it in your compose file in this way:
```yaml ```yaml
environment: volumes:
URL: "DSM_VirtualDSM_42218.pat" - ./DSM_VirtualDSM_42218.pat:/boot.pat
``` ```
after placing a file called `DSM_VirtualDSM_42218.pat` in your `/storage` folder. Replace the example path `./DSM_VirtualDSM_42218.pat` with the filename of your desired `.pat` file. The value of `URL` will be ignored in this case.
### What are the differences compared to the standard DSM? ### 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
+1 -22
View File
@@ -3,6 +3,7 @@ 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') RAM_OPTS=$(echo "-m ${RAM_SIZE^^}" | sed 's/MB/M/g;s/GB/G/g;s/TB/T/g')
MON_OPTS="-name $PROCESS,process=$PROCESS,debug-threads=on -pidfile $QEMU_PID"
CPU_OPTS="-cpu $CPU_FLAGS -smp $CPU_CORES,sockets=1,dies=1,cores=$CPU_CORES,threads=1" 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}" 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="-device virtio-balloon-pci,id=balloon0,bus=pcie.0,addr=0x4"
@@ -12,26 +13,4 @@ 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" 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 ' ') ARGS=$(echo "$ARGS" | sed 's/\t/ /g' | tr -s ' ')
# Check available memory as the very last step
if [[ "$RAM_CHECK" != [Nn]* ]]; then
RAM_AVAIL=$(free -b | grep -m 1 Mem: | awk '{print $7}')
AVAIL_GB=$(( RAM_AVAIL/1073741824 ))
if (( (RAM_WANTED + RAM_SPARE) > RAM_AVAIL )); then
error "Your configured RAM_SIZE of $WANTED_GB GB is too high for the $AVAIL_GB GB of memory available, please set a lower value."
exit 17
fi
if (( (RAM_WANTED + (RAM_SPARE * 3)) > RAM_AVAIL )); then
warn "your configured RAM_SIZE of $WANTED_GB GB is very close to the $AVAIL_GB GB of memory available, please consider a lower value."
fi
fi
if [[ "$DEBUG" == [Yy1]* ]]; then
printf "Arguments:\n\n%s" "${ARGS// -/$'\n-'}" && echo
fi
return 0 return 0
+394 -232
View File
@@ -3,55 +3,61 @@ set -Eeuo pipefail
# Docker environment variables # Docker environment variables
: "${DISK_IO:="native"}" # I/O Mode, can be set to 'native', 'threads' or 'io_turing' : "${DISK_IO:="native"}" # I/O Mode, can be set to 'native', 'threads' or 'io_uring'
: "${DISK_FMT:="raw"}" # Disk file format, 'raw' by default for best performance : "${DISK_FMT:="raw"}" # Disk file format, 'raw' by default for best performance
: "${DISK_TYPE:=""}" # Device type to be used, choose "ide", "usb", "blk" or "scsi" : "${DISK_TYPE:=""}" # Device type to be used, "sata", "nvme", "blk" or "scsi"
: "${DISK_FLAGS:=""}" # Specifies the options for use with the qcow2 disk format : "${DISK_FLAGS:=""}" # Specifies the options for use with the qcow2 disk format
: "${DISK_CACHE:="none"}" # Caching mode, can be set to 'writeback' for better performance : "${DISK_CACHE:="none"}" # Caching mode, can be set to 'writeback' for better performance
: "${DISK_DISCARD:="on"}" # Controls whether unmap (TRIM) commands are passed to the host. : "${DISK_DISCARD:="unmap"}" # Controls whether unmap (TRIM) commands are passed to the host.
: "${DISK_ROTATION:="1"}" # Rotation rate, set to 1 for SSD storage and increase for HDD : "${DISK_ROTATION:="1"}" # Rotation rate, set to 1 for SSD storage and increase for HDD
# Sanitize all variables
DISK_IO=$(strip "$DISK_IO")
DISK_FMT=$(strip "$DISK_FMT")
DISK_TYPE=$(strip "$DISK_TYPE")
DISK_FLAGS=$(strip "$DISK_FLAGS")
DISK_CACHE=$(strip "$DISK_CACHE")
DISK_DISCARD=$(strip "$DISK_DISCARD")
DISK_ROTATION=$(strip "$DISK_ROTATION")
BOOT="$STORAGE/$BASE.boot.img" BOOT="$STORAGE/$BASE.boot.img"
SYSTEM="$STORAGE/$BASE.system.img" SYSTEM="$STORAGE/$BASE.system.img"
[ ! -s "$BOOT" ] && error "Virtual DSM boot-image does not exist ($BOOT)" && exit 81 [ ! -s "$BOOT" ] && error "Virtual DSM boot-image does not exist ($BOOT)" && exit 81
[ ! -s "$SYSTEM" ] && error "Virtual DSM system-image does not exist ($SYSTEM)" && exit 82 [ ! -s "$SYSTEM" ] && error "Virtual DSM system-image does not exist ($SYSTEM)" && exit 82
if ! setOwner "$BOOT"; then
error "Failed to set the owner for \"$BOOT\" !"
fi
if ! setOwner "$SYSTEM"; then
error "Failed to set the owner for \"$SYSTEM\" !"
fi
fmt2ext() { fmt2ext() {
local DISK_FMT=$1 local DISK_FMT="$1"
case "${DISK_FMT,,}" in case "${DISK_FMT,,}" in
qcow2) qcow2) echo "qcow2" ;;
echo "qcow2" raw) echo "img" ;;
;; *) error "Unrecognized disk format: $DISK_FMT" && exit 78 ;;
raw)
echo "img"
;;
*)
error "Unrecognized disk format: $DISK_FMT" && exit 78
;;
esac esac
} }
ext2fmt() { ext2fmt() {
local DISK_EXT=$1 local DISK_EXT="$1"
case "${DISK_EXT,,}" in case "${DISK_EXT,,}" in
qcow2) qcow2) echo "qcow2" ;;
echo "qcow2" img) echo "raw" ;;
;; *) error "Unrecognized file extension: .$DISK_EXT" && exit 78 ;;
img)
echo "raw"
;;
*)
error "Unrecognized file extension: .$DISK_EXT" && exit 78
;;
esac esac
} }
getSize() { getSize() {
local DISK_FILE=$1
local DISK_EXT DISK_FMT local DISK_FILE="$1"
local DISK_EXT DISK_FMT size
DISK_EXT=$(echo "${DISK_FILE//*./}" | sed 's/^.*\.//') DISK_EXT=$(echo "${DISK_FILE//*./}" | sed 's/^.*\.//')
DISK_FMT=$(ext2fmt "$DISK_EXT") DISK_FMT=$(ext2fmt "$DISK_EXT")
@@ -60,17 +66,25 @@ getSize() {
raw) raw)
stat -c%s "$DISK_FILE" stat -c%s "$DISK_FILE"
;; ;;
qcow2) qcow2)
qemu-img info "$DISK_FILE" -f "$DISK_FMT" | grep '^virtual size: ' | sed 's/.*(\(.*\) bytes)/\1/' size=$(qemu-img info --output=json -f "$DISK_FMT" "$DISK_FILE" | jq -r '."virtual-size" // empty')
if [[ ! "$size" =~ ^[0-9]+$ ]]; then
error "Failed to determine virtual size of $DISK_FILE"
exit 78
fi
echo "$size"
;; ;;
*) *)
error "Unrecognized disk format: $DISK_FMT" && exit 78 error "Unrecognized disk format: $DISK_FMT"
exit 78
;; ;;
esac esac
} }
isCow() { isCow() {
local FS=$1 local FS="$1"
if [[ "${FS,,}" == "btrfs" ]]; then if [[ "${FS,,}" == "btrfs" ]]; then
return 0 return 0
@@ -80,45 +94,122 @@ isCow() {
} }
supportsDirect() { supportsDirect() {
local FS=$1 local FS="$1"
if [[ "${FS,,}" == "ecryptfs" ]] || [[ "${FS,,}" == "tmpfs" ]]; then if [[ "${FS,,}" == "ecryptfs" || "${FS,,}" == "tmpfs" ]]; then
return 1 return 1
fi fi
return 0 return 0
} }
allocateRaw() {
local DISK_FILE="$1"
local DATA_SIZE="$2"
if disabled "$ALLOCATE"; then
truncate -s "$DATA_SIZE" "$DISK_FILE"
return $?
fi
fallocate -l "$DATA_SIZE" "$DISK_FILE" &>/dev/null && return 0
fallocate -l -x "$DATA_SIZE" "$DISK_FILE" && return 0
truncate -s "$DATA_SIZE" "$DISK_FILE" || return 1
return 0
}
getDiskOptions() {
local FS="$1"
local DISK_FMT="$2"
local DISK_PARAM="$DISK_ALLOC"
isCow "$FS" && DISK_PARAM+=",nocow=on"
if [[ "${DISK_FMT,,}" != "raw" ]]; then
[ -n "$DISK_FLAGS" ] && DISK_PARAM+=",$DISK_FLAGS"
fi
echo "$DISK_PARAM"
return 0
}
normalizeSize() {
local DISK_SPACE="$1"
local DISK_DESC="$2"
local DIR="$3"
local SPACE FREE GB DATA_SIZE
if [[ "${DISK_SPACE,,}" == "max" || "${DISK_SPACE,,}" == "half" ]]; then
local SPARE=1073741824
FREE=$(df --output=avail -B 1 "$DIR" | tail -n 1)
if [[ "${DISK_SPACE,,}" == "max" ]]; then
FREE=$(( FREE - SPARE ))
else
FREE=$(( FREE / 2 ))
fi
(( FREE < SPARE )) && FREE="$SPARE"
GB=$(( FREE / 1073741825 ))
DISK_SPACE="${GB}G"
fi
SPACE="${DISK_SPACE// /}"
[ -z "$SPACE" ] && SPACE="256G"
[ -z "${SPACE//[0-9. ]}" ] && SPACE="${SPACE}G"
SPACE=$(echo "${SPACE^^}" | sed 's/MB/M/g;s/GB/G/g;s/TB/T/g')
if ! numfmt --from=iec "$SPACE" &>/dev/null; then
error "Invalid value for ${DISK_DESC^^}_SIZE: $DISK_SPACE" && exit 73
fi
DATA_SIZE=$(numfmt --from=iec "$SPACE")
if (( DATA_SIZE < 6442450944 )); then
error "Please increase the ${DISK_DESC^^}_SIZE variable to at least 6 GB." && exit 73
fi
echo "$SPACE"
return 0
}
createDisk() { createDisk() {
local DISK_FILE=$1 local DISK_FILE="$1"
local DISK_SPACE=$2 local DISK_SPACE="$2"
local DISK_DESC=$3 local DISK_DESC="$3"
local DISK_FMT=$4 local DISK_FMT="$4"
local FS=$5 local FS="$5"
local DATA_SIZE DIR SPACE FA local DATA_SIZE DIR SPACE GB FA
DATA_SIZE=$(numfmt --from=iec "$DISK_SPACE")
rm -f "$DISK_FILE" rm -f "$DISK_FILE"
if [[ "$ALLOCATE" != [Nn]* ]]; then DATA_SIZE=$(numfmt --from=iec "$DISK_SPACE")
if ! disabled "$ALLOCATE"; then
# Check free diskspace # Check free diskspace
DIR=$(dirname "$DISK_FILE") DIR=$(dirname "$DISK_FILE")
SPACE=$(df --output=avail -B 1 "$DIR" | tail -n 1) SPACE=$(df --output=avail -B 1 "$DIR" | tail -n 1)
if (( DATA_SIZE > SPACE )); then if (( DATA_SIZE > SPACE )); then
local SPACE_GB=$(( (SPACE + 1073741823)/1073741824 )) GB=$(formatBytes "$SPACE")
error "Not enough free space to create a $DISK_DESC of $DISK_SPACE in $DIR, it has only $SPACE_GB GB available..." error "Not enough free space to create a $DISK_DESC of ${DISK_SPACE/G/ GB} in $DIR, it has only $GB available..."
error "Please specify a smaller ${DISK_DESC^^}_SIZE or disable preallocation by setting ALLOCATE=N." && exit 76 error "Please specify a smaller ${DISK_DESC^^}_SIZE or disable preallocation by setting ALLOCATE=N." && exit 76
fi fi
fi fi
html "Creating a $DISK_DESC image..." html "Creating a $DISK_DESC image..."
info "Creating a $DISK_SPACE $DISK_STYLE $DISK_DESC image in $DISK_FMT format..." info "Creating a ${DISK_SPACE/G/ GB} $DISK_STYLE $DISK_DESC image in $DISK_FMT format..."
local FAIL="Could not create a $DISK_STYLE $DISK_FMT $DISK_DESC image of $DISK_SPACE ($DISK_FILE)" local FAIL="Could not create a $DISK_STYLE $DISK_FMT $DISK_DESC image of ${DISK_SPACE/G/ GB} ($DISK_FILE)"
case "${DISK_FMT,,}" in case "${DISK_FMT,,}" in
raw) raw)
@@ -130,31 +221,15 @@ createDisk() {
{ chattr +C "$DISK_FILE"; } || : { chattr +C "$DISK_FILE"; } || :
fi fi
if [[ "$ALLOCATE" == [Nn]* ]]; then if ! allocateRaw "$DISK_FILE" "$DATA_SIZE"; then
rm -f "$DISK_FILE"
# Create an empty file error "$FAIL" && exit 77
if ! truncate -s "$DATA_SIZE" "$DISK_FILE"; then
rm -f "$DISK_FILE"
error "$FAIL" && exit 77
fi
else
# Create an empty file
if ! fallocate -l "$DATA_SIZE" "$DISK_FILE"; then
if ! truncate -s "$DATA_SIZE" "$DISK_FILE"; then
rm -f "$DISK_FILE"
error "$FAIL" && exit 77
fi
fi
fi fi
;; ;;
qcow2) qcow2)
local DISK_PARAM="$DISK_ALLOC" local DISK_PARAM
isCow "$FS" && DISK_PARAM+=",nocow=on" DISK_PARAM=$(getDiskOptions "$FS" "$DISK_FMT")
[ -n "$DISK_FLAGS" ] && DISK_PARAM+=",$DISK_FLAGS"
if ! qemu-img create -f "$DISK_FMT" -o "$DISK_PARAM" -- "$DISK_FILE" "$DATA_SIZE" ; then if ! qemu-img create -f "$DISK_FMT" -o "$DISK_PARAM" -- "$DISK_FILE" "$DATA_SIZE" ; then
rm -f "$DISK_FILE" rm -f "$DISK_FILE"
@@ -175,56 +250,43 @@ createDisk() {
resizeDisk() { resizeDisk() {
local DISK_FILE=$1 local DISK_FILE="$1"
local DISK_SPACE=$2 local DISK_SPACE="$2"
local DISK_DESC=$3 local DISK_DESC="$3"
local DISK_FMT=$4 local DISK_FMT="$4"
local FS=$5 local FS="$5"
local CUR_SIZE DATA_SIZE DIR SPACE local CUR_SIZE DATA_SIZE DIR SPACE GB
CUR_SIZE=$(getSize "$DISK_FILE") CUR_SIZE=$(getSize "$DISK_FILE") || exit 71
DATA_SIZE=$(numfmt --from=iec "$DISK_SPACE") DATA_SIZE=$(numfmt --from=iec "$DISK_SPACE")
local REQ=$((DATA_SIZE-CUR_SIZE)) local REQ=$(( DATA_SIZE - CUR_SIZE ))
(( REQ < 1 )) && error "Shrinking disks is not supported yet, please increase ${DISK_DESC^^}_SIZE." && exit 71 (( REQ < 1 )) && error "Shrinking disks is not supported yet, please increase ${DISK_DESC^^}_SIZE." && exit 71
if [[ "$ALLOCATE" != [Nn]* ]]; then if ! disabled "$ALLOCATE"; then
# Check free diskspace # Check free diskspace
DIR=$(dirname "$DISK_FILE") DIR=$(dirname "$DISK_FILE")
SPACE=$(df --output=avail -B 1 "$DIR" | tail -n 1) SPACE=$(df --output=avail -B 1 "$DIR" | tail -n 1)
if (( REQ > SPACE )); then if (( REQ > SPACE )); then
local SPACE_GB=$(( (SPACE + 1073741823)/1073741824 )) GB=$(formatBytes "$SPACE")
error "Not enough free space to resize $DISK_DESC to $DISK_SPACE in $DIR, it has only $SPACE_GB GB available.." error "Not enough free space to resize $DISK_DESC to ${DISK_SPACE/G/ GB} in $DIR, it has only $GB available.."
error "Please specify a smaller ${DISK_DESC^^}_SIZE or disable preallocation by setting ALLOCATE=N." && exit 74 error "Please specify a smaller ${DISK_DESC^^}_SIZE or disable preallocation by setting ALLOCATE=N." && exit 74
fi fi
fi fi
local GB=$(( (CUR_SIZE + 1073741823)/1073741824 )) GB=$(formatBytes "$CUR_SIZE")
MSG="Resizing $DISK_DESC from ${GB}G to $DISK_SPACE..." MSG="Resizing $DISK_DESC from $GB to ${DISK_SPACE/G/ GB}..."
info "$MSG" && html "$MSG" info "$MSG" && html "$MSG"
local FAIL="Could not resize the $DISK_STYLE $DISK_FMT $DISK_DESC image from ${GB}G to $DISK_SPACE ($DISK_FILE)" local FAIL="Could not resize the $DISK_STYLE $DISK_FMT $DISK_DESC image from ${GB} to ${DISK_SPACE/G/ GB} ($DISK_FILE)"
case "${DISK_FMT,,}" in case "${DISK_FMT,,}" in
raw) raw)
if [[ "$ALLOCATE" == [Nn]* ]]; then if ! allocateRaw "$DISK_FILE" "$DATA_SIZE"; then
error "$FAIL" && exit 75
# Resize file by changing its length
if ! truncate -s "$DATA_SIZE" "$DISK_FILE"; then
error "$FAIL" && exit 75
fi
else
# Resize file by allocating more space
if ! fallocate -l "$DATA_SIZE" "$DISK_FILE"; then
if ! truncate -s "$DATA_SIZE" "$DISK_FILE"; then
error "$FAIL" && exit 75
fi
fi
fi fi
;; ;;
qcow2) qcow2)
@@ -241,34 +303,37 @@ resizeDisk() {
convertDisk() { convertDisk() {
local SOURCE_FILE=$1 local SOURCE_FILE="$1"
local SOURCE_FMT=$2 local SOURCE_FMT="$2"
local DST_FILE=$3 local DST_FILE="$3"
local DST_FMT=$4 local DST_FMT="$4"
local DISK_BASE=$5 local DISK_BASE="$5"
local DISK_DESC=$6 local DISK_DESC="$6"
local FS=$7 local FS="$7"
[ -f "$DST_FILE" ] && error "Conversion failed, destination file $DST_FILE already exists?" && exit 79 [ -f "$DST_FILE" ] && error "Conversion failed, destination file $DST_FILE already exists?" && exit 79
[ ! -f "$SOURCE_FILE" ] && error "Conversion failed, source file $SOURCE_FILE does not exists?" && exit 79 [ ! -f "$SOURCE_FILE" ] && error "Conversion failed, source file $SOURCE_FILE does not exist?" && exit 79
local TMP_FILE="$DISK_BASE.tmp" local TMP_FILE="$DISK_BASE.tmp"
rm -f "$TMP_FILE" rm -f "$TMP_FILE"
if [[ "$ALLOCATE" != [Nn]* ]]; then local DIR FA
DIR=$(dirname "$TMP_FILE")
local DIR CUR_SIZE SPACE if ! disabled "$ALLOCATE"; then
local CUR_SIZE SPACE GB
# Check free diskspace # Check free diskspace
DIR=$(dirname "$TMP_FILE") CUR_SIZE=$(getSize "$SOURCE_FILE") || exit 79
CUR_SIZE=$(getSize "$SOURCE_FILE")
SPACE=$(df --output=avail -B 1 "$DIR" | tail -n 1) SPACE=$(df --output=avail -B 1 "$DIR" | tail -n 1)
if (( CUR_SIZE > SPACE )); then if (( CUR_SIZE > SPACE )); then
local SPACE_GB=$(( (SPACE + 1073741823)/1073741824 )) GB=$(formatBytes "$SPACE")
error "Not enough free space to convert $DISK_DESC to $DST_FMT in $DIR, it has only $SPACE_GB GB available..." error "Not enough free space to convert $DISK_DESC to $DST_FMT in $DIR, it has only $GB available..."
error "Please free up some disk space or disable preallocation by setting ALLOCATE=N." && exit 76 error "Please free up some disk space or disable preallocation by setting ALLOCATE=N." && exit 76
fi fi
fi fi
local msg="Converting $DISK_DESC to $DST_FMT" local msg="Converting $DISK_DESC to $DST_FMT"
@@ -276,14 +341,13 @@ convertDisk() {
info "$msg, please wait until completed..." info "$msg, please wait until completed..."
local CONV_FLAGS="-p" local CONV_FLAGS="-p"
local DISK_PARAM="$DISK_ALLOC" local DISK_PARAM
isCow "$FS" && DISK_PARAM+=",nocow=on" DISK_PARAM=$(getDiskOptions "$FS" "$DST_FMT")
if [[ "$DST_FMT" != "raw" ]]; then if [[ "$DST_FMT" != "raw" ]]; then
if [[ "$ALLOCATE" == [Nn]* ]]; then if disabled "$ALLOCATE"; then
CONV_FLAGS+=" -c" CONV_FLAGS+=" -c"
fi fi
[ -n "$DISK_FLAGS" ] && DISK_PARAM+=",$DISK_FLAGS"
fi fi
# shellcheck disable=SC2086 # shellcheck disable=SC2086
@@ -293,17 +357,28 @@ convertDisk() {
fi fi
if [[ "$DST_FMT" == "raw" ]]; then if [[ "$DST_FMT" == "raw" ]]; then
if [[ "$ALLOCATE" != [Nn]* ]]; then if ! disabled "$ALLOCATE"; then
# Work around qemu-img bug # Work around qemu-img bug
CUR_SIZE=$(stat -c%s "$TMP_FILE") CUR_SIZE=$(stat -c%s "$TMP_FILE")
if ! fallocate -l "$CUR_SIZE" "$TMP_FILE"; then if ! fallocate -l "$CUR_SIZE" "$TMP_FILE" &>/dev/null; then
if ! fallocate -l -x "$CUR_SIZE" "$TMP_FILE"; then
error "Failed to allocate $CUR_SIZE bytes for $DISK_DESC image $TMP_FILE" error "Failed to allocate $CUR_SIZE bytes for $DISK_DESC image $TMP_FILE"
fi
fi fi
fi fi
fi fi
rm -f "$SOURCE_FILE" if ! rm -f "$SOURCE_FILE"; then
mv "$TMP_FILE" "$DST_FILE" rm -f "$TMP_FILE"
error "Failed to remove old $DISK_DESC image $SOURCE_FILE."
exit 79
fi
if ! mv "$TMP_FILE" "$DST_FILE"; then
rm -f "$TMP_FILE"
error "Failed to move converted $DISK_DESC image to $DST_FILE."
exit 79
fi
if isCow "$FS"; then if isCow "$FS"; then
FA=$(lsattr "$DST_FILE") FA=$(lsattr "$DST_FILE")
@@ -314,38 +389,38 @@ convertDisk() {
msg="Conversion of $DISK_DESC" msg="Conversion of $DISK_DESC"
html "$msg completed..." html "$msg completed..."
info "$msg to $DST_FMT completed succesfully!" info "$msg to $DST_FMT completed successfully!"
return 0 return 0
} }
checkFS () { checkFS () {
local FS=$1 local FS="$1"
local DISK_FILE=$2 local DISK_FILE="$2"
local DISK_DESC=$3 local DISK_DESC="$3"
local DIR FA local DIR FA
DIR=$(dirname "$DISK_FILE") DIR=$(dirname "$DISK_FILE")
[ ! -d "$DIR" ] && return 0 [ ! -d "$DIR" ] && return 0
if [[ "${FS,,}" == "overlay"* ]]; then if [[ "${FS,,}" == "overlay"* && "${ENGINE,,}" == "docker" ]]; then
info "Warning: the filesystem of $DIR is OverlayFS, this usually means it was binded to an invalid path!" warn "the filesystem of $DIR 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 $DIR is FUSE, this extra layer will negatively affect performance!" warn "the filesystem of $DIR is FUSE, this extra layer will negatively affect performance!"
fi fi
if ! supportsDirect "$FS"; then if ! supportsDirect "$FS"; then
info "Warning: the filesystem of $DIR is $FS, which does not support O_DIRECT mode, adjusting settings..." warn "the filesystem of $DIR is $FS, which does not support O_DIRECT mode, adjusting settings..."
fi fi
if isCow "$FS"; then if isCow "$FS"; then
if [ -f "$DISK_FILE" ]; then if [ -f "$DISK_FILE" ]; then
FA=$(lsattr "$DISK_FILE") FA=$(lsattr "$DISK_FILE")
if [[ "$FA" != *"C"* ]]; then if [[ "$FA" != *"C"* ]]; then
info "Warning: COW (copy on write) is not disabled for $DISK_DESC image file $DISK_FILE, this is recommended on ${FS^^} filesystems!" warn "COW (copy on write) is not disabled for $DISK_DESC image file $DISK_FILE, this is recommended on ${FS^^} filesystems!"
fi fi
fi fi
fi fi
@@ -355,13 +430,15 @@ checkFS () {
createDevice () { createDevice () {
local DISK_FILE=$1 local DISK_FILE="$1"
local DISK_TYPE=$2 local DISK_TYPE="$2"
local DISK_INDEX=$3 local DISK_INDEX="$3"
local DISK_ADDRESS=$4 local DISK_ADDRESS="$4"
local DISK_FMT=$5 local DISK_FMT="$5"
local DISK_IO=$6 local DISK_IO="$6"
local DISK_CACHE=$7 local DISK_CACHE="$7"
local DISK_SERIAL="$8"
local DISK_SECTORS="$9"
local DISK_ID="data$DISK_INDEX" local DISK_ID="data$DISK_INDEX"
local index="" local index=""
@@ -369,29 +446,35 @@ createDevice () {
local result=" -drive file=$DISK_FILE,id=$DISK_ID,format=$DISK_FMT,cache=$DISK_CACHE,aio=$DISK_IO,discard=$DISK_DISCARD,detect-zeroes=on" local result=" -drive file=$DISK_FILE,id=$DISK_ID,format=$DISK_FMT,cache=$DISK_CACHE,aio=$DISK_IO,discard=$DISK_DISCARD,detect-zeroes=on"
case "${DISK_TYPE,,}" in case "${DISK_TYPE,,}" in
"none" ) ;;
"auto" ) "auto" )
echo "$result" echo "$result"
;; ;;
"usb" ) "usb" )
result+=",if=none \ result+=",if=none \
-device usb-storage,drive=${DISK_ID}${index}" -device usb-storage,drive=${DISK_ID}${index}${DISK_SERIAL}${DISK_SECTORS}"
echo "$result" echo "$result"
;; ;;
"ide" ) "nvme" )
result+=",if=none \
-device nvme,drive=${DISK_ID}${index},serial=deadbeaf${DISK_INDEX}${DISK_SERIAL}${DISK_SECTORS}"
echo "$result"
;;
"ide" | "sata" )
result+=",if=none \ result+=",if=none \
-device ich9-ahci,id=ahci${DISK_INDEX},addr=$DISK_ADDRESS \ -device ich9-ahci,id=ahci${DISK_INDEX},addr=$DISK_ADDRESS \
-device ide-hd,drive=${DISK_ID},bus=ahci$DISK_INDEX.0,rotation_rate=$DISK_ROTATION${index}" -device ide-hd,drive=${DISK_ID},bus=ahci$DISK_INDEX.0,rotation_rate=$DISK_ROTATION${index}${DISK_SERIAL}${DISK_SECTORS}"
echo "$result" echo "$result"
;; ;;
"blk" | "virtio-blk" ) "blk" | "virtio-blk" )
result+=",if=none \ result+=",if=none \
-device virtio-blk-pci,drive=${DISK_ID},scsi=off,bus=pcie.0,addr=$DISK_ADDRESS,iothread=io2${index}" -device virtio-blk-pci,drive=${DISK_ID},bus=pcie.0,addr=$DISK_ADDRESS,iothread=io2${index}${DISK_SERIAL}${DISK_SECTORS}"
echo "$result" echo "$result"
;; ;;
"scsi" | "virtio-scsi" ) "scsi" | "virtio-scsi" )
result+=",if=none \ result+=",if=none \
-device virtio-scsi-pci,id=${DISK_ID}b,bus=pcie.0,addr=$DISK_ADDRESS,iothread=io2 \ -device virtio-scsi-pci,id=${DISK_ID}b,bus=pcie.0,addr=$DISK_ADDRESS,iothread=io2 \
-device scsi-hd,drive=${DISK_ID},bus=${DISK_ID}b.0,channel=0,scsi-id=0,lun=0,rotation_rate=$DISK_ROTATION${index}" -device scsi-hd,drive=${DISK_ID},bus=${DISK_ID}b.0,channel=0,scsi-id=0,lun=0,rotation_rate=$DISK_ROTATION${index}${DISK_SERIAL}${DISK_SECTORS}"
echo "$result" echo "$result"
;; ;;
esac esac
@@ -399,18 +482,32 @@ createDevice () {
return 0 return 0
} }
finishDisks () {
case "${DISK_TYPE,,}" in
"blk" | "scsi" | "virtio-blk" | "virtio-scsi" )
DISK_OPTS+=" -object iothread,id=io2" ;;
esac
if ! enabled "$DISK_DISABLE"; then
html "Initialized disks successfully..."
fi
return 0
}
addDisk () { addDisk () {
local DISK_BASE=$1 local DISK_BASE="$1"
local DISK_TYPE=$2 local DISK_TYPE="$2"
local DISK_DESC=$3 local DISK_DESC="$3"
local DISK_SPACE=$4 local DISK_SPACE="$4"
local DISK_INDEX=$5 local DISK_INDEX="$5"
local DISK_ADDRESS=$6 local DISK_ADDRESS="$6"
local DISK_FMT=$7 local DISK_FMT="$7"
local DISK_IO=$8 local DISK_IO="$8"
local DISK_CACHE=$9 local DISK_CACHE="$9"
local DISK_EXT DIR DATA_SIZE FS PREV_FMT PREV_EXT CUR_SIZE OPTS local DISK_EXT DIR SPACE DATA_SIZE FS PREV_FMT PREV_EXT CUR_SIZE LEFT FREE USED
DISK_EXT=$(fmt2ext "$DISK_FMT") DISK_EXT=$(fmt2ext "$DISK_FMT")
local DISK_FILE="$DISK_BASE.$DISK_EXT" local DISK_FILE="$DISK_BASE.$DISK_EXT"
@@ -418,18 +515,8 @@ addDisk () {
DIR=$(dirname "$DISK_FILE") DIR=$(dirname "$DISK_FILE")
[ ! -d "$DIR" ] && return 0 [ ! -d "$DIR" ] && return 0
[ -z "$DISK_SPACE" ] && DISK_SPACE="16G" SPACE=$(normalizeSize "$DISK_SPACE" "$DISK_DESC" "$DIR")
DISK_SPACE=$(echo "${DISK_SPACE^^}" | sed 's/MB/M/g;s/GB/G/g;s/TB/T/g') DATA_SIZE=$(numfmt --from=iec "$SPACE")
[[ -z "${DISK_SPACE//[0-9]}" ]] && DISK_SPACE="${DISK_SPACE}G"
DATA_SIZE=$(numfmt --from=iec "$DISK_SPACE")
if (( DATA_SIZE < 1 )); then
error "Invalid value for ${DISK_DESC^^}_SIZE: $DISK_SPACE" && exit 73
fi
if (( DATA_SIZE < 6442450944 )); then
error "Please increase ${DISK_DESC^^}_SIZE to at least 6 GB." && exit 73
fi
FS=$(stat -f -c %T "$DIR") FS=$(stat -f -c %T "$DIR")
checkFS "$FS" "$DISK_FILE" "$DISK_DESC" || exit $? checkFS "$FS" "$DISK_FILE" "$DISK_DESC" || exit $?
@@ -439,7 +526,7 @@ addDisk () {
DISK_CACHE="writeback" DISK_CACHE="writeback"
fi fi
if ! [ -s "$DISK_FILE" ] ; then if [ ! -s "$DISK_FILE" ] ; then
if [[ "${DISK_FMT,,}" != "raw" ]]; then if [[ "${DISK_FMT,,}" != "raw" ]]; then
PREV_FMT="raw" PREV_FMT="raw"
@@ -452,62 +539,169 @@ addDisk () {
if [ -s "$DISK_BASE.$PREV_EXT" ] ; then if [ -s "$DISK_BASE.$PREV_EXT" ] ; then
convertDisk "$DISK_BASE.$PREV_EXT" "$PREV_FMT" "$DISK_FILE" "$DISK_FMT" "$DISK_BASE" "$DISK_DESC" "$FS" || exit $? convertDisk "$DISK_BASE.$PREV_EXT" "$PREV_FMT" "$DISK_FILE" "$DISK_FMT" "$DISK_BASE" "$DISK_DESC" "$FS" || exit $?
fi fi
fi fi
if [ -s "$DISK_FILE" ]; then if [ -s "$DISK_FILE" ]; then
CUR_SIZE=$(getSize "$DISK_FILE") CUR_SIZE=$(getSize "$DISK_FILE") || exit 71
if (( DATA_SIZE > CUR_SIZE )); then if (( DATA_SIZE > CUR_SIZE )); then
resizeDisk "$DISK_FILE" "$DISK_SPACE" "$DISK_DESC" "$DISK_FMT" "$FS" || exit $?
resizeDisk "$DISK_FILE" "$SPACE" "$DISK_DESC" "$DISK_FMT" "$FS" || exit $?
else
if (( DATA_SIZE < CUR_SIZE )); then
if [[ "${DISK_SPACE,,}" != "max" && "${DISK_SPACE,,}" != "half" ]]; then
info "You decreased the ${DISK_DESC^^}_SIZE variable to ${DISK_SPACE/G/ GB} but shrinking disks is not supported, will be ignored..."
fi
fi
fi fi
else else
createDisk "$DISK_FILE" "$DISK_SPACE" "$DISK_DESC" "$DISK_FMT" "$FS" || exit $? createDisk "$DISK_FILE" "$SPACE" "$DISK_DESC" "$DISK_FMT" "$FS" || exit $?
fi fi
DISK_OPTS+=$(createDevice "$DISK_FILE" "$DISK_TYPE" "$DISK_INDEX" "$DISK_ADDRESS" "$DISK_FMT" "$DISK_IO" "$DISK_CACHE") if [ -f "$DISK_FILE" ] && disabled "$ALLOCATE"; then
CUR_SIZE=$(getSize "$DISK_FILE") || exit 73
USED=$(du -sB 1 "$DISK_FILE" | cut -f1)
FREE=$(df --output=avail -B 1 "$DIR" | tail -n 1)
LEFT=$(( CUR_SIZE - USED - FREE ))
(( LEFT < 0 )) && LEFT=0
if (( LEFT > 0 )); then
local GB
GB=$(formatBytes "$FREE")
LEFT=$(formatBytes "$LEFT")
CUR_SIZE=$(formatBytes "$CUR_SIZE")
msg="The virtual size of the ${DISK_DESC,,} is $CUR_SIZE"
if [ -n "$USED" ] && [[ "$USED" != "0" ]]; then
USED=$(formatBytes "$USED")
msg+=" (of which $USED is used)"
fi
info "$msg, but there is only $GB of free space remaining in $DIR now."
info "Please consider making at least $LEFT more space available in $DIR for future expansions."
fi
fi
if [ -f "$DISK_FILE" ]; then
if ! setOwner "$DISK_FILE"; then
error "Failed to set the owner for \"$DISK_FILE\" !"
fi
fi
DISK_OPTS+=$(createDevice "$DISK_FILE" "$DISK_TYPE" "$DISK_INDEX" "$DISK_ADDRESS" "$DISK_FMT" "$DISK_IO" "$DISK_CACHE" "" "")
return 0 return 0
} }
addDevice () { addDevice () {
local DISK_DEV=$1 local DISK_DEV="$1"
local DISK_TYPE=$2 local DISK_TYPE="$2"
local DISK_INDEX=$3 local DISK_INDEX="$3"
local DISK_ADDRESS=$4 local DISK_ADDRESS="$4"
[ -z "$DISK_DEV" ] && return 0 [ -z "$DISK_DEV" ] && return 0
[ ! -b "$DISK_DEV" ] && error "Device $DISK_DEV cannot be found! Please add it to the 'devices' section of your compose file." && exit 55 [ ! -b "$DISK_DEV" ] && error "Device $DISK_DEV cannot be found! Please add it to the 'devices' section of your compose file." && exit 55
DISK_OPTS+=$(createDevice "$DISK_DEV" "$DISK_TYPE" "$DISK_INDEX" "$DISK_ADDRESS" "raw" "$DISK_IO" "$DISK_CACHE") local sectors=""
local dev_type=""
dev_type=$(lsblk -no TYPE "$DISK_DEV" 2>/dev/null | head -n1)
# Only detect and apply sector sizes for partitions, not whole disks
# Whole disk passthrough with explicit sector sizes causes DSM not to recognize the disk
if [[ "$dev_type" == "part" ]]; then
local result=""
local logical=""
local physical=""
result=$(fdisk -l "$DISK_DEV" 2>/dev/null | grep -m 1 -o "(logical/physical): .*" | cut -c 21- || true)
if [ -n "$result" ]; then
logical="${result%% *}"
physical=$(echo "$result" | grep -m 1 -o "/ .*" | cut -c 3- || true)
physical="${physical%% *}"
fi
if [ -z "$logical" ] || [ -z "$physical" ]; then
warn "Failed to determine the sector size for $DISK_DEV"
elif [[ "$physical" != "512" ]]; then
sectors=",logical_block_size=$logical,physical_block_size=$physical"
fi
fi
DISK_OPTS+=$(createDevice "$DISK_DEV" "$DISK_TYPE" "$DISK_INDEX" "$DISK_ADDRESS" "raw" "$DISK_IO" "$DISK_CACHE" "" "$sectors")
return 0 return 0
} }
html "Initializing disks..."
[ -z "${DISK_OPTS:-}" ] && DISK_OPTS="" [ -z "${DISK_OPTS:-}" ] && DISK_OPTS=""
[ -z "${DISK_TYPE:-}" ] && DISK_TYPE="scsi" [ -z "${DISK_TYPE:-}" ] && DISK_TYPE="scsi"
[ -z "${DISK_NAME:-}" ] && DISK_NAME="data" [ -z "${DISK_NAME:-}" ] && DISK_NAME="data"
[ -z "${DISK_DISABLE:-}" ] && DISK_DISABLE=""
if ! enabled "$DISK_DISABLE"; then
msg="Initializing disks..."
html "$msg"
enabled "$DEBUG" && echo "$msg"
fi
if [[ "${DISK_IO,,}" == "native" && "${DISK_CACHE,,}" != "none" && "${DISK_CACHE,,}" != "directsync" ]]; then
warn "DISK_IO=native requires direct I/O caching, using DISK_IO=threads with DISK_CACHE=$DISK_CACHE."
DISK_IO="threads"
fi
case "${DISK_DISCARD,,}" in
"y" | "yes" | "true" | "1" | "on" | "unmap" )
DISK_DISCARD="unmap" ;;
"n" | "no" | "false" | "0" | "off" | "ignore" )
DISK_DISCARD="ignore" ;;
* )
warn "Invalid DISK_DISCARD value '$DISK_DISCARD', using 'unmap'."
DISK_DISCARD="unmap" ;;
esac
if [[ ! "$DISK_ROTATION" =~ ^[0-9]+$ ]]; then
warn "Invalid DISK_ROTATION value '$DISK_ROTATION', using 1."
DISK_ROTATION="1"
fi
DISK_FMT="${DISK_FMT,,}"
case "$DISK_FMT" in
"raw" | "qcow2" ) ;;
* ) error "Invalid DISK_FMT specified, value \"$DISK_FMT\" is not recognized!" && exit 78 ;;
esac
case "${DISK_TYPE,,}" in case "${DISK_TYPE,,}" in
"ide" | "usb" | "scsi" | "blk" | "auto" ) ;; "ide" | "sata" | "nvme" | "usb" | "scsi" | "blk" | "virtio-blk" | "virtio-scsi" | "auto" | "none" ) ;;
* ) error "Invalid DISK_TYPE specified, value \"$DISK_TYPE\" is not recognized!" && exit 80 ;; * ) error "Invalid DISK_TYPE specified, value \"$DISK_TYPE\" is not recognized!" && exit 80 ;;
esac esac
if [ -z "$ALLOCATE" ]; then if [[ "$DISK_FLAGS" =~ [[:space:]] ]]; then
if [[ "${DISK_FMT,,}" == "raw" ]]; then error "Invalid DISK_FLAGS value '$DISK_FLAGS', spaces are not allowed."
ALLOCATE="Y" exit 78
else
ALLOCATE="N"
fi
fi fi
if [[ "$ALLOCATE" == [Nn]* ]]; then if [ -z "$ALLOCATE" ]; then
ALLOCATE="N"
fi
if disabled "$ALLOCATE"; then
DISK_STYLE="growable" DISK_STYLE="growable"
DISK_ALLOC="preallocation=off" DISK_ALLOC="preallocation=off"
else else
@@ -515,41 +709,17 @@ else
DISK_ALLOC="preallocation=falloc" DISK_ALLOC="preallocation=falloc"
fi fi
DISK_OPTS+=$(createDevice "$BOOT" "$DISK_TYPE" "1" "0xa" "raw" "$DISK_IO" "$DISK_CACHE") DISK_OPTS+=$(createDevice "$BOOT" "$DISK_TYPE" "1" "0xa" "raw" "$DISK_IO" "$DISK_CACHE" "" "")
DISK_OPTS+=$(createDevice "$SYSTEM" "$DISK_TYPE" "2" "0xb" "raw" "$DISK_IO" "$DISK_CACHE") DISK_OPTS+=$(createDevice "$SYSTEM" "$DISK_TYPE" "2" "0xb" "raw" "$DISK_IO" "$DISK_CACHE" "" "")
if enabled "$DISK_DISABLE"; then
finishDisks
return 0
fi
DISK1_FILE="$STORAGE/${DISK_NAME}" DISK1_FILE="$STORAGE/${DISK_NAME}"
if [[ ! -f "$DISK1_FILE.img" ]] && [[ -f "$STORAGE/data${DISK_SIZE}.img" ]]; then
# Fallback for legacy installs
mv "$STORAGE/data${DISK_SIZE}.img" "$DISK1_FILE.img"
fi
DISK2_FILE="/storage2/${DISK_NAME}2" DISK2_FILE="/storage2/${DISK_NAME}2"
if [ ! -f "$DISK2_FILE.img" ]; then
# Fallback for legacy installs
FALLBACK="/storage2/data.img"
if [[ -f "$DISK1_FILE.img" ]] && [[ -f "$FALLBACK" ]]; then
SIZE1=$(stat -c%s "$FALLBACK")
SIZE2=$(stat -c%s "$DISK1_FILE.img")
if [[ SIZE1 -ne SIZE2 ]]; then
mv "$FALLBACK" "$DISK2_FILE.img"
fi
fi
fi
DISK3_FILE="/storage3/${DISK_NAME}3" DISK3_FILE="/storage3/${DISK_NAME}3"
if [ ! -f "$DISK3_FILE.img" ]; then
# Fallback for legacy installs
FALLBACK="/storage3/data.img"
if [[ -f "$DISK1_FILE.img" ]] && [[ -f "$FALLBACK" ]]; then
SIZE1=$(stat -c%s "$FALLBACK")
SIZE2=$(stat -c%s "$DISK1_FILE.img")
if [[ SIZE1 -ne SIZE2 ]]; then
mv "$FALLBACK" "$DISK3_FILE.img"
fi
fi
fi
DISK4_FILE="/storage4/${DISK_NAME}4" DISK4_FILE="/storage4/${DISK_NAME}4"
: "${DISK2_SIZE:=""}" : "${DISK2_SIZE:=""}"
@@ -572,31 +742,23 @@ DISK4_FILE="/storage4/${DISK_NAME}4"
[ -z "$DEVICE3" ] && [ -b "/dev/disk3" ] && DEVICE3="/dev/disk3" [ -z "$DEVICE3" ] && [ -b "/dev/disk3" ] && DEVICE3="/dev/disk3"
[ -z "$DEVICE4" ] && [ -b "/dev/disk4" ] && DEVICE4="/dev/disk4" [ -z "$DEVICE4" ] && [ -b "/dev/disk4" ] && DEVICE4="/dev/disk4"
if [ -n "$DEVICE" ]; then DISK_FILES=( "$DISK1_FILE" "$DISK2_FILE" "$DISK3_FILE" "$DISK4_FILE" )
addDevice "$DEVICE" "$DISK_TYPE" "3" "0xc" || exit $? DISK_DESCS=( "disk" "disk2" "disk3" "disk4" )
else DISK_SIZES=( "$DISK_SIZE" "$DISK2_SIZE" "$DISK3_SIZE" "$DISK4_SIZE" )
addDisk "$DISK1_FILE" "$DISK_TYPE" "disk" "$DISK_SIZE" "3" "0xc" "$DISK_FMT" "$DISK_IO" "$DISK_CACHE" || exit $? DISK_DEVICES=( "$DEVICE" "$DEVICE2" "$DEVICE3" "$DEVICE4" )
fi DISK_INDEXES=( "3" "4" "5" "6" )
DISK_ADDRESSES=( "0xc" "0xd" "0xe" "0xf" )
if [ -n "$DEVICE2" ]; then for i in "${!DISK_FILES[@]}"; do
addDevice "$DEVICE2" "$DISK_TYPE" "4" "0xd" || exit $?
else
addDisk "$DISK2_FILE" "$DISK_TYPE" "disk2" "$DISK2_SIZE" "4" "0xd" "$DISK_FMT" "$DISK_IO" "$DISK_CACHE" || exit $?
fi
if [ -n "$DEVICE3" ]; then if [ -n "${DISK_DEVICES[i]}" ]; then
addDevice "$DEVICE3" "$DISK_TYPE" "5" "0xe" || exit $? addDevice "${DISK_DEVICES[i]}" "$DISK_TYPE" "${DISK_INDEXES[i]}" "${DISK_ADDRESSES[i]}" || exit $?
else else
addDisk "$DISK3_FILE" "$DISK_TYPE" "disk3" "$DISK3_SIZE" "5" "0xe" "$DISK_FMT" "$DISK_IO" "$DISK_CACHE" || exit $? addDisk "${DISK_FILES[i]}" "$DISK_TYPE" "${DISK_DESCS[i]}" "${DISK_SIZES[i]}" "${DISK_INDEXES[i]}" "${DISK_ADDRESSES[i]}" "$DISK_FMT" "$DISK_IO" "$DISK_CACHE" || exit $?
fi fi
if [ -n "$DEVICE4" ]; then done
addDevice "$DEVICE4" "$DISK_TYPE" "6" "0xf" || exit $?
else
addDisk "$DISK4_FILE" "$DISK_TYPE" "disk4" "$DISK4_SIZE" "6" "0xf" "$DISK_FMT" "$DISK_IO" "$DISK_CACHE" || exit $?
fi
DISK_OPTS+=" -object iothread,id=io2" finishDisks
html "Initialized disks successfully..."
return 0 return 0
+24 -8
View File
@@ -6,8 +6,16 @@ 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
: "${RENDERNODE:="/dev/dri/renderD128"}" # Render node
if [[ "$GPU" != [Yy1]* ]] || [[ "$ARCH" != "amd64" ]]; then # Sanitize variables
VGA=$(strip "$VGA")
DISPLAY=$(strip "$DISPLAY")
RENDERNODE=$(strip "$RENDERNODE")
CPU_VENDOR=$(lscpu | awk '/Vendor ID/{print $3}')
if ! enabled "$GPU" || [[ "$CPU_VENDOR" != "GenuineIntel" || "$ARCH" != "amd64" ]]; then
[[ "${DISPLAY,,}" == "none" ]] && VGA="none" [[ "${DISPLAY,,}" == "none" ]] && VGA="none"
DISPLAY_OPTS="-display $DISPLAY -vga $VGA" DISPLAY_OPTS="-display $DISPLAY -vga $VGA"
@@ -15,20 +23,28 @@ if [[ "$GPU" != [Yy1]* ]] || [[ "$ARCH" != "amd64" ]]; then
fi fi
DISPLAY_OPTS="-display egl-headless,rendernode=/dev/dri/renderD128" 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
if [ ! -c /dev/dri/card0 ]; then # Extract the card number from the render node
if mknod /dev/dri/card0 c 226 0; then CARD_NUMBER=$(echo "$RENDERNODE" | grep -oP '(?<=renderD)\d+')
chmod 666 /dev/dri/card0 CARD_DEVICE="/dev/dri/card$((CARD_NUMBER - 128))"
if [ ! -c "$CARD_DEVICE" ]; then
if mknod "$CARD_DEVICE" c 226 $((CARD_NUMBER - 128)); then
chmod 666 "$CARD_DEVICE"
fi fi
fi fi
if [ ! -c /dev/dri/renderD128 ]; then if [ ! -c "$RENDERNODE" ]; then
if mknod /dev/dri/renderD128 c 226 128; then if mknod "$RENDERNODE" c 226 "$CARD_NUMBER"; then
chmod 666 /dev/dri/renderD128 chmod 666 "$RENDERNODE"
fi fi
fi fi
+21 -11
View File
@@ -1,12 +1,16 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -Eeuo pipefail set -Eeuo pipefail
APP="Virtual DSM" : "${PLATFORM:="x64"}"
SUPPORT="https://github.com/vdsm/virtual-dsm" : "${APP:="Virtual DSM"}"
: "${SUPPORT:="https://github.com/vdsm/virtual-dsm"}"
cd /run cd /run
. start.sh # Startup hook
. utils.sh # Load functions
. reset.sh # Initialize system . reset.sh # Initialize system
. server.sh # Start webserver
. install.sh # Run installation . install.sh # Run installation
. disk.sh # Initialize disks . disk.sh # Initialize disks
. display.sh # Initialize graphics . display.sh # Initialize graphics
@@ -14,23 +18,29 @@ 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)
version=$("${cmd[@]}" --version | awk 'NR==1 { print $4 }')
info "Booting $APP using QEMU v$version..." info "Booting $APP using QEMU v$version..."
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 [ ! -t 1 ] || [ ! -c /dev/tty ]; then
(( rc != 0 )) && error "$(<"$QEMU_LOG")" && exit 15 "${cmd[@]}" ${ARGS:+ $ARGS} &
else
"${cmd[@]}" ${ARGS:+ $ARGS} </dev/tty >/dev/tty &
fi
terminal rc=0
tail -fn +0 "$QEMU_LOG" 2>/dev/null & wait $! || rc=$?
cat "$QEMU_TERM" 2>/dev/null & wait $! || : [ -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
+105 -197
View File
@@ -5,29 +5,44 @@ set -Eeuo pipefail
if [ -f "$STORAGE/dsm.ver" ]; then if [ -f "$STORAGE/dsm.ver" ]; then
BASE=$(<"$STORAGE/dsm.ver") BASE=$(<"$STORAGE/dsm.ver")
BASE="${BASE//[![:print:]]/}"
[ -z "$BASE" ] && BASE="DSM_VirtualDSM_69057" [ -z "$BASE" ] && BASE="DSM_VirtualDSM_69057"
else else
# Fallback for old installs # Fallback for old installs
BASE="DSM_VirtualDSM_42962" BASE="DSM_VirtualDSM_42962"
fi fi
if [ -n "$URL" ]; then FN="boot.pat"
BASE=$(basename "$URL" .pat) DIR=$(find / -maxdepth 1 -type d -iname "$FN" -print -quit)
if [ ! -s "$STORAGE/$BASE.system.img" ]; then [ ! -d "$DIR" ] && DIR=$(find "$STORAGE" -maxdepth 1 -type d -iname "$FN" -print -quit)
BASE=$(basename "${URL%%\?*}" .pat)
: "${BASE//+/ }"; printf -v BASE '%b' "${_//%/\\x}" if [ -d "$DIR" ]; then
BASE=$(echo "$BASE" | sed -e 's/[^A-Za-z0-9._-]/_/g') BASE="DSM_VirtualDSM" && URL="file://$DIR"
fi if [[ ! -s "$STORAGE/$BASE.boot.img" || ! -s "$STORAGE/$BASE.system.img" ]]; then
if [[ "${URL,,}" != "http"* ]]; then error "The bind $DIR maps to a file that does not exist!" && exit 65
if [ -s "$STORAGE/$BASE.pat" ]; then
URL="file://$STORAGE/$BASE.pat"
else
error "File $STORAGE/$BASE.pat does not exist!" && exit 65
fi
fi fi
fi fi
if [[ -s "$STORAGE/$BASE.boot.img" ]] && [[ -s "$STORAGE/$BASE.system.img" ]]; then FILE=$(find / -maxdepth 1 -type f -iname "$FN" -print -quit)
[ ! -s "$FILE" ] && FILE=$(find "$STORAGE" -maxdepth 1 -type f -iname "$FN" -print -quit)
[ -s "$FILE" ] && BASE="DSM_VirtualDSM" && URL="file://$FILE"
URL=$(strip "$URL")
if [ -n "$URL" ] && [ ! -s "$FILE" ] && [ ! -d "$DIR" ]; then
BASE=$(basename "$URL" .pat)
if [ ! -s "$STORAGE/$BASE.system.img" ]; then
BASE=$(basename "${URL%%\?*}" .pat)
printf -v BASE '%b' "${BASE//%/\\x}"
BASE="${BASE//[!A-Za-z0-9._-]/_}"
fi
if [[ "${URL,,}" != "http"* && "${URL,,}" != "file:"* ]] ; then
[ ! -s "$STORAGE/$BASE.pat" ] && error "Invalid URL: $URL" && exit 65
URL="file://$STORAGE/$BASE.pat"
fi
fi
if [[ -s "$STORAGE/$BASE.boot.img" && -s "$STORAGE/$BASE.system.img" ]]; then
return 0 # Previous installation found return 0 # Previous installation found
fi fi
@@ -46,11 +61,15 @@ if [ -z "$DL" ]; then
[[ "${COUNTRY^^}" == "CN" ]] && DL="$DL_CHINA" || DL="$DL_GLOBAL" [[ "${COUNTRY^^}" == "CN" ]] && DL="$DL_CHINA" || DL="$DL_GLOBAL"
fi fi
[ -z "$URL" ] && URL="$DL/release/7.2.2/72806/DSM_VirtualDSM_72806.pat" if [ -z "$URL" ]; then
URL="$DL/release/7.2.2/72806/DSM_VirtualDSM_72806.pat"
fi
BASE=$(basename "${URL%%\?*}" .pat) if [ ! -s "$FILE" ]; then
: "${BASE//+/ }"; printf -v BASE '%b' "${_//%/\\x}" BASE=$(basename "${URL%%\?*}" .pat)
BASE=$(echo "$BASE" | sed -e 's/[^A-Za-z0-9._-]/_/g') printf -v BASE '%b' "${BASE//%/\\x}"
BASE="${BASE//[!A-Za-z0-9._-]/_}"
fi
if [[ "$URL" != "file://$STORAGE/$BASE.pat" ]]; then if [[ "$URL" != "file://$STORAGE/$BASE.pat" ]]; then
rm -f "$STORAGE/$BASE.pat" rm -f "$STORAGE/$BASE.pat"
@@ -63,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
@@ -81,28 +100,31 @@ 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
SPACE=$(df --output=avail -B 1 /tmp | tail -n 1) SPACE=$(df --output=avail -B 1 /tmp | tail -n 1)
SPACE_MB=$(( (SPACE + 1048575)/1048576 )) SPACE_MB=$(formatBytes "$SPACE")
if (( TMP_SPACE > SPACE )); then if (( TMP_SPACE > SPACE )); then
error "Not enough free space inside the container, have $SPACE_MB MB available but need at least 2 GB." && exit 93 error "Not enough free space inside the container, have $SPACE_MB available but need at least 2 GB." && exit 93
fi fi
rm -rf "$TMP" && mkdir -p "$TMP"
fi fi
rm -rf "$TMP" && mkdir -p "$TMP"
# Check free diskspace # Check free diskspace
ROOT_SPACE=536870912 ROOT_SPACE=536870912
SPACE=$(df --output=avail -B 1 / | tail -n 1) SPACE=$(df --output=avail -B 1 / | tail -n 1)
SPACE_MB=$(( (SPACE + 1048575)/1048576 )) SPACE_MB=$(formatBytes "$SPACE" "down")
(( ROOT_SPACE > SPACE )) && error "Not enough free space inside the container, have $SPACE_MB MB available but need at least 500 MB." && exit 96 (( ROOT_SPACE > SPACE )) && error "Not enough free space inside the container, have $SPACE_MB available but need at least 500 MB." && exit 96
MIN_SPACE=15032385536 MIN_SPACE=15032385536
SPACE=$(df --output=avail -B 1 "$STORAGE" | tail -n 1) SPACE=$(df --output=avail -B 1 "$STORAGE" | tail -n 1)
SPACE_GB=$(( (SPACE + 1073741823)/1073741824 )) SPACE_GB=$(formatBytes "$SPACE")
(( MIN_SPACE > SPACE )) && error "Not enough free space for installation in $STORAGE, have $SPACE_GB GB available but need at least 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 # Check if output is to interactive TTY
if [ -t 1 ]; then if [ -t 1 ]; then
@@ -111,128 +133,16 @@ else
PROGRESS="--progress=dot:giga" PROGRESS="--progress=dot:giga"
fi fi
# Download the required files from the Synology website if [[ "$URL" == "file://"* ]]; then
MSG="Copying DSM"
ROOT="Y" ERR="Failed to copy ${URL:7}"
RD="$TMP/rd.gz" info "Install: Copying installation image..."
RDC="$STORAGE/dsm.rd" else
MSG="Downloading DSM"
if [ ! -s "$RDC" ] && [[ "$URL" == "file://"* ]] && [[ "${URL,,}" == *"_42218.pat" ]]; then ERR="Failed to download $URL"
info "Install: Downloading $BASE.pat..."
rm -f "$RD"
rm -f "$RDC"
tar --extract --file="${URL:7}" --directory="$(dirname "$RD")"/. "$(basename "$RD")"
cp "$RD" "$RDC"
fi fi
if [ ! -s "$RDC" ]; then
rm -f "$RD"
rm -f "$RDC"
MSG="Downloading installer"
info "Install: $MSG..." && html "$MSG..."
SIZE=5394188
POS="65627648-71021835"
VERIFY="b4215a4b213ff5154db0488f92c87864"
LOC="$DL/release/7.0.1/42218/DSM_VirtualDSM_42218.pat"
[[ "${URL,,}" == *"_42218.pat" ]] && LOC="$URL"
/run/progress.sh "$RD" "$SIZE" "$MSG ([P])..." &
{ curl -r "$POS" -sfk --connect-timeout 10 -S -o "$RD" "$LOC"; rc=$?; } || :
fKill "progress.sh"
ERR="Failed to download $LOC"
(( rc == 3 )) && error "$ERR , cannot write file (disk full?)" && exit 60
(( rc == 4 )) && error "$ERR , network failure!" && exit 60
(( rc == 8 )) && error "$ERR , server issued an error response!" && exit 60
if (( rc != 0 )); then
if (( rc != 22 )) && (( rc != 56 )); then
error "$ERR , reason: $rc" && exit 60
fi
SUM="skip"
else
SUM=$(md5sum "$RD" | cut -f 1 -d " ")
fi
if [ "$SUM" != "$VERIFY" ]; then
PAT="/install.pat"
SIZE=379637760
rm -f "$RD"
rm -f "$PAT"
html "$MSG..."
/run/progress.sh "$PAT" "$SIZE" "$MSG ([P])..." &
{ wget "$LOC" -O "$PAT" -q --no-check-certificate --timeout=10 --show-progress "$PROGRESS"; rc=$?; } || :
fKill "progress.sh"
ERR="Failed to download $LOC"
(( rc == 3 )) && error "$ERR , cannot write file (disk full?)" && exit 60
(( rc == 4 )) && error "$ERR , network failure!" && exit 60
(( rc == 8 )) && error "$ERR , server issued an error response!" && exit 60
(( rc != 0 )) && error "$ERR , reason: $rc" && exit 60
tar --extract --file="$PAT" --directory="$(dirname "$RD")"/. "$(basename "$RD")"
rm "$PAT"
fi
cp "$RD" "$RDC"
fi
if [ -f "$RDC" ]; then
{ xz -dc <"$RDC" >"$TMP/rd" 2>/dev/null; rc=$?; } || :
(( rc != 1 )) && error "Failed to unxz $RDC on $FS, reason $rc" && exit 91
{ (cd "$TMP" && cpio -idm <"$TMP/rd" 2>/dev/null); rc=$?; } || :
if (( rc != 0 )); then
ROOT="N"
{ (cd "$TMP" && fakeroot cpio -idmu <"$TMP/rd" 2>/dev/null); rc=$?; } || :
(( rc != 0 )) && error "Failed to extract $RDC on $FS, reason $rc" && exit 92
fi
rm -rf /run/extract && mkdir -p /run/extract
for file in $TMP/usr/lib/libcurl.so.4 \
$TMP/usr/lib/libmbedcrypto.so.5 \
$TMP/usr/lib/libmbedtls.so.13 \
$TMP/usr/lib/libmbedx509.so.1 \
$TMP/usr/lib/libmsgpackc.so.2 \
$TMP/usr/lib/libsodium.so \
$TMP/usr/lib/libsynocodesign-ng-virtual-junior-wins.so.7 \
$TMP/usr/syno/bin/scemd; do
cp "$file" /run/extract/
done
if [ "$ARCH" != "amd64" ]; then
mkdir -p /lib64/
cp "$TMP/usr/lib/libc.so.6" /lib64/
cp "$TMP/usr/lib/libpthread.so.0" /lib64/
cp "$TMP/usr/lib/ld-linux-x86-64.so.2" /lib64/
fi
mv /run/extract/scemd /run/extract/syno_extract_system_patch
chmod +x /run/extract/syno_extract_system_patch
fi
rm -rf "$TMP" && mkdir -p "$TMP"
info "Install: Downloading $BASE.pat..."
MSG="Downloading DSM"
ERR="Failed to download $URL"
html "$MSG..." html "$MSG..."
PAT="/$BASE.pat" PAT="/$BASE.pat"
@@ -240,6 +150,10 @@ rm -f "$PAT"
if [[ "$URL" == "file://"* ]]; then if [[ "$URL" == "file://"* ]]; then
if [ ! -f "${URL:7}" ]; then
error "File '${URL:7}' does not exist!" && exit 65
fi
cp "${URL:7}" "$PAT" cp "${URL:7}" "$PAT"
else else
@@ -251,7 +165,7 @@ else
/run/progress.sh "$PAT" "$SIZE" "$MSG ([P])..." & /run/progress.sh "$PAT" "$SIZE" "$MSG ([P])..." &
{ wget "$URL" -O "$PAT" -q --no-check-certificate --timeout=10 --show-progress "$PROGRESS"; rc=$?; } || : { wget "$URL" -O "$PAT" -q --no-check-certificate --timeout=10 --no-http-keep-alive --show-progress "$PROGRESS"; rc=$?; } || :
fKill "progress.sh" fKill "progress.sh"
@@ -270,7 +184,7 @@ if ((SIZE<250000000)); then
error "The specified PAT file is probably an update pack as it's too small." && exit 62 error "The specified PAT file is probably an update pack as it's too small." && exit 62
fi fi
MSG="Extracting downloaded image..." MSG="Extracting installation image..."
info "Install: $MSG" && html "$MSG" info "Install: $MSG" && html "$MSG"
if { tar tf "$PAT"; } >/dev/null 2>&1; then if { tar tf "$PAT"; } >/dev/null 2>&1; then
@@ -279,30 +193,24 @@ if { tar tf "$PAT"; } >/dev/null 2>&1; then
else else
export LD_LIBRARY_PATH="/run/extract" { (cd "$TMP" && python3 /run/extract.py -i "$PAT" -d 2>/run/extract.log); rc=$?; } || :
if [ "$ARCH" == "amd64" ]; then if (( rc != 0 )); then
{ /run/extract/syno_extract_system_patch "$PAT" "$TMP/."; rc=$?; } || : cat /run/extract.log
else error "Failed to extract PAT file, reason $rc" && exit 63
{ qemu-x86_64 /run/extract/syno_extract_system_patch "$PAT" "$TMP/."; rc=$?; } || :
fi fi
export LD_LIBRARY_PATH=""
(( rc != 0 )) && error "Failed to extract PAT file, reason $rc" && exit 63
fi fi
rm -rf /run/extract
MSG="Preparing system partition..." MSG="Preparing system partition..."
info "Install: $MSG" && html "$MSG" info "Install: $MSG" && html "$MSG"
BOOT=$(find "$TMP" -name "*.bin.zip") BOOT=$(find "$TMP" -name "*.bin.zip" -print -quit)
[ ! -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"
@@ -310,16 +218,18 @@ rm -f "$SYSTEM"
# Check free diskspace # Check free diskspace
SYSTEM_SIZE=10738466816 SYSTEM_SIZE=10738466816
SPACE=$(df --output=avail -B 1 "$STORAGE" | tail -n 1) SPACE=$(df --output=avail -B 1 "$STORAGE" | tail -n 1)
SPACE_MB=$(( (SPACE + 1048575)/1048576 )) SPACE_MB=$(formatBytes "$SPACE")
if (( SYSTEM_SIZE > SPACE )); then if (( SYSTEM_SIZE > SPACE )); then
error "Not enough free space in $STORAGE to create a 10 GB system disk, have only $SPACE_MB MB available." && exit 97 error "Not enough free space in $STORAGE to create a 10 GB system disk, have only $SPACE_MB available." && exit 97
fi fi
if ! touch "$SYSTEM"; then if ! touch "$SYSTEM"; then
error "Could not create file $SYSTEM for the system disk." && exit 98 error "Could not create file $SYSTEM for the system disk." && exit 98
fi fi
! setOwner "$SYSTEM" && error "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")
@@ -328,10 +238,12 @@ if [[ "${FS,,}" == "btrfs" ]]; then
fi fi
fi fi
if ! fallocate -l "$SYSTEM_SIZE" "$SYSTEM"; then if ! fallocate -l "$SYSTEM_SIZE" "$SYSTEM" &>/dev/null; then
if ! truncate -s "$SYSTEM_SIZE" "$SYSTEM"; then if ! fallocate -l -x "$SYSTEM_SIZE" "$SYSTEM"; then
rm -f "$SYSTEM" if ! truncate -s "$SYSTEM_SIZE" "$SYSTEM"; then
error "Could not allocate file $SYSTEM for the system disk." && exit 98 rm -f "$SYSTEM"
error "Could not allocate file $SYSTEM for the system disk." && exit 98
fi
fi fi
fi fi
@@ -350,7 +262,11 @@ PART="$TMP/partition.fdisk"
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"
@@ -366,12 +282,10 @@ 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-"*
[ -s "$HDP.txz" ] && tar xpfJ "$HDP.txz" --absolute-names -C "$MOUNT/" 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"
tar xpfJ "$IDB.txz" --absolute-names -C "$INDEX_DB"
fi fi
LABEL="1.44.1-42218" LABEL="1.44.1-42218"
@@ -379,25 +293,16 @@ OFFSET="1048576" # 2048 * 512
NUMBLOCKS="2097152" # (16777216 * 512) / 4096 NUMBLOCKS="2097152" # (16777216 * 512) / 4096
MSG="Installing system partition..." MSG="Installing system partition..."
if [[ "$ROOT" != [Nn]* ]]; then fakeroot -- bash -c "set -Eeu;\
[ -s $HDP.txz ] && tar xpfJ $HDP.txz --absolute-names -C $MOUNT/;\
tar xpfJ "$HDA.txz" --absolute-names --skip-old-files -C "$MOUNT/" [ -s $IDB.txz ] && tar xpfJ $IDB.txz --absolute-names -C $INDEX_DB/;\
tar xpfJ $HDA.txz --absolute-names --skip-old-files -C $MOUNT/;\
info "Install: $MSG" && html "$MSG" printf '%b%s%b' '\E[1;34m \E[1;36m' 'Install: $MSG' '\E[0m\n';\
mke2fs -q -t ext4 -b 4096 -d $MOUNT/ -L $LABEL -F -E offset=$OFFSET $SYSTEM $NUMBLOCKS"
mke2fs -q -t ext4 -b 4096 -d "$MOUNT/" -L "$LABEL" -F -E "offset=$OFFSET" "$SYSTEM" "$NUMBLOCKS"
else
fakeroot -- bash -c "set -Eeu;\
tar xpfJ $HDA.txz --absolute-names --skip-old-files -C $MOUNT/;\
printf '%b%s%b' '\E[1;34m \E[1;36m' 'Install: $MSG' '\E[0m\n';\
mke2fs -q -t ext4 -b 4096 -d $MOUNT/ -L $LABEL -F -E offset=$OFFSET $SYSTEM $NUMBLOCKS"
fi
rm -rf "$MOUNT" rm -rf "$MOUNT"
echo "$BASE" > "$STORAGE/dsm.ver" echo "$BASE" > "$STORAGE/dsm.ver"
! setOwner "$STORAGE/dsm.ver" && error "Failed to set the owner for \"$STORAGE/dsm.ver\" !"
if [[ "$URL" == "file://$STORAGE/$BASE.pat" ]]; then if [[ "$URL" == "file://$STORAGE/$BASE.pat" ]]; then
rm -f "$PAT" rm -f "$PAT"
@@ -405,10 +310,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" && error "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" && error "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
+111
View File
@@ -0,0 +1,111 @@
#!/usr/bin/env bash
set -Eeuo pipefail
msg="Checking memory..."
enabled "$DEBUG" && echo "$msg"
RAM_AVAIL=$(free -b | grep -m 1 Mem: | awk '{print $7}')
AVAIL_MEM=$(formatBytes "$RAM_AVAIL")
checkConfiguredMemory() {
local wanted msg
if disabled "$RAM_CHECK" || [[ "${RAM_SIZE,,}" == "max" || "${RAM_SIZE,,}" == "half" ]]; then
return 0
fi
wanted=$(numfmt --from=iec "$RAM_SIZE")
if (( (wanted + RAM_SPARE) > RAM_AVAIL )); then
msg="Your configured RAM_SIZE of ${RAM_SIZE/G/ GB} is too high for the $AVAIL_MEM of free memory available,"
if [[ "${FS,,}" == "zfs" ]]; then
info "$msg but since ZFS is active this will be ignored."
else
RAM_SIZE="max"
warn "$msg it will automatically be adjusted to a lower amount."
fi
else
if (( (wanted + (RAM_SPARE * 3)) > RAM_AVAIL )); then
msg="your configured RAM_SIZE of ${RAM_SIZE/G/ GB} is very close to the $AVAIL_MEM of free memory available,"
if [[ "${FS,,}" == "zfs" ]]; then
info "$msg but since ZFS is active this will be ignored."
else
warn "$msg please consider a lower amount."
fi
fi
fi
return 0
}
configureHalfMemory() {
local wanted
if [[ "${RAM_SIZE,,}" != "half" ]]; then
return 0
fi
if (( (RAM_AVAIL / 2) > RAM_SPARE )); then
wanted=$(( (RAM_AVAIL / 2) / 1048577 ))
RAM_SIZE="${wanted}M"
info "Allocated $wanted MB of RAM for the virtual machine."
else
RAM_SIZE="max"
fi
return 0
}
configureMaxMemory() {
local wanted
if [[ "${RAM_SIZE,,}" != "max" ]]; then
return 0
fi
if (( RAM_AVAIL < (RAM_SPARE * 2) )); then
wanted=$(( RAM_AVAIL / 2 ))
else
wanted=$(( RAM_AVAIL - (RAM_SPARE * 3) ))
if (( wanted < (RAM_SPARE * 6) )); then
wanted=$(( RAM_AVAIL - RAM_SPARE ))
fi
fi
wanted=$(( wanted / 1048577 ))
RAM_SIZE="${wanted}M"
info "Allocated $wanted MB of RAM for the virtual machine."
return 0
}
checkMinimumMemory() {
local wanted
wanted=$(numfmt --from=iec "$RAM_SIZE")
if [ "$wanted" -lt "$RAM_MINIMUM" ]; then
wanted=$(( wanted / 1048577 ))
error "Not enough memory available, there is only $wanted MB left!"
exit 16
fi
return 0
}
checkConfiguredMemory
configureHalfMemory
configureMaxMemory
checkMinimumMemory
return 0
+1318 -211
View File
File diff suppressed because it is too large Load Diff
+194 -122
View File
@@ -1,135 +1,131 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -Eeuo pipefail set -Eeuo pipefail
: "${SHUTDOWN:="Y"}" # Graceful ACPI shutdown
: "${TIMEOUT:="115"}" # QEMU termination timeout
: "${API_TIMEOUT:="90"}" # External API call timeout
# Configure QEMU for graceful shutdown # Configure QEMU for graceful shutdown
API_CMD=6 API_CMD=6
API_TIMEOUT=50 API_HOST="127.0.0.1:$COM_PORT"
API_HOST="127.0.0.1:2210"
# Configure QEMU for graceful shutdown
QEMU_TERM=""
QEMU_PORT=7100
QEMU_TIMEOUT=50
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"
if [[ "$KVM" == [Nn]* ]]; then
API_TIMEOUT=$(( API_TIMEOUT*2 ))
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
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"
if [ ! -s "$QEMU_PID" ] || ! read -r _pid <"$QEMU_PID"; then
return 1
fi
return 0
}
forceKillQemu() {
local reason="$1"
local pid=""
local display
! readQemuPid pid && return 0
! isAlive "$pid" && return 0
display=$(displayReason "$reason")
error "Forcefully terminating $(app), reason: $display..."
{ disown "$pid" || :; kill -9 -- "$pid" || :; } 2>/dev/null
return 0
}
cleanupHelpers() {
local pids=( "${HOST_PID:-}" "${WSD_PID:-}" \
"${WEB_PID:-}" "${PASST_PID:-}" "${DNSMASQ_PID:-}" )
mKill "${pids[@]}"
fKill "print.sh"
closeNetwork
return 0
} }
finish() { finish() {
local pid
local reason=$1 local reason=$1
touch "$QEMU_END" touch "$QEMU_END"
if [ -s "$QEMU_PID" ]; then forceKillQemu "$reason"
cleanupHelpers
pid=$(<"$QEMU_PID") if ! waitPidFile "$QEMU_PID" 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" (( reason != 1 )) && echo && echo " Shutdown completed!"
fKill "host.bin"
closeNetwork
sleep 1
echo && echo " Shutdown completed!"
exit "$reason" exit "$reason"
} }
terminal() { sendGuestShutdown() {
local dev="" local pid="$1"
local response
if [ -s "$QEMU_OUT" ]; then local url
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
API_TIMEOUT=$(strip "$API_TIMEOUT")
url="http://$API_HOST/read?command=$API_CMD&timeout=$API_TIMEOUT" url="http://$API_HOST/read?command=$API_CMD&timeout=$API_TIMEOUT"
response=$(curl -sk -m "$(( API_TIMEOUT+2 ))" -S "$url" 2>&1) response=$(curl -sk -m "$(( API_TIMEOUT+2 ))" -S "$url" 2>&1)
@@ -141,45 +137,121 @@ _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
local elapsed
local timeout_left
local min
TIMEOUT=$(strip "$TIMEOUT")
if [[ ! "$TIMEOUT" =~ ^[0-9]+$ ]]; then
TIMEOUT=115
fi
if (( TIMEOUT >= 30 )); then
term_grace=5
cleanup_grace=5
elif (( TIMEOUT >= 15 )); then
term_grace=4
cleanup_grace=4
fi
elapsed=$((SECONDS - start))
timeout_left=$((TIMEOUT - elapsed))
min=$((term_grace + cleanup_grace + 1))
(( timeout_left < min )) && timeout_left=$min
wait_until=$((timeout_left - cleanup_grace))
sigterm_at=$((wait_until - term_grace))
return 0
}
waitForShutdown() {
local cnt=0 local cnt=0
local pid="$1"
local name="$APP"
local slp
while [ "$cnt" -lt "$QEMU_TIMEOUT" ]; do while (( cnt <= wait_until )); do
sleep 1 &
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))
[[ "$DEBUG" == [Yy1]* ]] && info "Shutting down, waiting... ($cnt/$QEMU_TIMEOUT)"
# Workaround for zombie pid
[ ! -s "$QEMU_PID" ] && break [ ! -s "$QEMU_PID" ] && break
if (( cnt == sigterm_at )); then
info "${name^} is still running, sending SIGTERM... ($cnt/$wait_until)"
kill -15 -- "$pid" 2>/dev/null || :
elif (( cnt > 0 )) && enabled "${DEBUG:-}"; then
info "Waiting for $name to shut down... ($cnt/$wait_until)"
fi
wait "$slp"
(( cnt++ ))
done done
if [ "$cnt" -ge "$QEMU_TIMEOUT" ]; then return 0
echo && error "Shutdown timeout reached, aborting..."
fi
finish "$code" && return "$code"
} }
MON_OPTS="\ graceful_shutdown() {
-pidfile $QEMU_PID \
-name $PROCESS,process=$PROCESS,debug-threads=on \
-monitor telnet:localhost:$QEMU_PORT,server,nowait,nodelay"
if [[ "$CONSOLE" != [Yy]* ]]; then local sig="$1"
local pid=""
local code=0
MON_OPTS+=" -daemonize -D $QEMU_LOG" [[ $BASHPID != "$TRAP_PID" ]] && return
_trap _graceful_shutdown SIGTERM SIGHUP SIGINT SIGABRT SIGQUIT code=$(signalCode "$sig")
fi if [ -f "$QEMU_END" ]; then
echo && info "Received $1 signal while already shutting down..."
return
fi
set +e
start=$SECONDS
touch "$QEMU_END"
echo && info "Received $1 signal, sending shutdown command..."
if ! readQemuPid pid; then
warn "QEMU PID file ($QEMU_PID) does not exist?"
finish "$code"
fi
if ! isAlive "$pid"; then
warn "QEMU process with PID $pid does not exist?"
finish "$code"
fi
sendGuestShutdown "$pid"
normalizeTimeout
waitForShutdown "$pid"
finish "$code"
}
! enabled "$SHUTDOWN" && return 0
[ -n "${QEMU_TIMEOUT:-}" ] && TIMEOUT="$QEMU_TIMEOUT"
_trap graceful_shutdown SIGTERM SIGHUP SIGABRT SIGQUIT
return 0 return 0
+147 -50
View File
@@ -1,16 +1,21 @@
#!/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"
@@ -21,88 +26,180 @@ 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
}
queryGuest() {
local rc
{ json=$(curl -m 20 -sk "$url"); rc=$?; } || : { json=$(curl -m 20 -sk "$url"); rc=$?; } || :
[ -f "$shutdown" ] && exit 1 exitIfShuttingDown
(( rc != 0 )) && error "$curl_err $rc" && continue
{ result=$(echo "$json" | jq -r '.status'); rc=$?; } || : if (( rc != 0 )); then
(( rc != 0 )) && error "$jq_err $rc ( $json )" && continue error "$curl_err $rc"
[[ "$result" == "null" ]] && error "$resp_err $json" && continue return 1
if [[ "$result" != "success" ]] ; then
{ msg=$(echo "$json" | jq -r '.message'); rc=$?; } || :
error "Guest replied $result: $msg" && continue
fi fi
{ port=$(echo "$json" | jq -r '.data.data.dsm_setting.data.http_port'); rc=$?; } || : return 0
(( rc != 0 )) && error "$jq_err $rc ( $json )" && continue }
[[ "$port" == "null" ]] && error "$resp_err $json" && continue
[ -z "$port" ] && continue
{ ip=$(echo "$json" | jq -r '.data.data.ip.data[] | select((.name=="eth0") and has("ip")).ip'); rc=$?; } || : readJsonField() {
(( rc != 0 )) && error "$jq_err $rc ( $json )" && continue
[[ "$ip" == "null" ]] && error "$resp_err $json" && continue
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
+158 -88
View File
@@ -3,81 +3,74 @@ set -Eeuo pipefail
# Docker environment variables # Docker environment variables
: "${KVM:="Y"}"
: "${HOST_CPU:=""}" : "${HOST_CPU:=""}"
: "${CPU_FLAGS:=""}" : "${CPU_FLAGS:=""}"
: "${CPU_MODEL:=""}" : "${CPU_MODEL:=""}"
: "${DEF_MODEL:="qemu64"}"
if [[ "${ARCH,,}" != "amd64" ]]; then HOST_CPU=$(strip "$HOST_CPU")
KVM="N" CPU_FLAGS=$(strip "$CPU_FLAGS")
warn "your CPU architecture is ${ARCH^^} and cannot provide KVM acceleration for x64 instructions, this will cause a major loss of performance." CPU_MODEL=$(strip "$CPU_MODEL")
fi
if [[ "$KVM" != [Nn]* ]]; then selectClocksource() {
KVM_ERR="" CLOCKSOURCE="tsc"
[[ "${ARCH,,}" == "arm64" ]] && CLOCKSOURCE="arch_sys_counter"
if [ ! -e /dev/kvm ]; then
KVM_ERR="(device file missing)"
else
if ! sh -c 'echo -n > /dev/kvm' &> /dev/null; then
KVM_ERR="(no write access)"
else
flags=$(sed -ne '/^flags/s/^.*: //p' /proc/cpuinfo)
if ! grep -qw "vmx\|svm" <<< "$flags"; then
KVM_ERR="(vmx/svm disabled)"
fi
fi
fi
if [ -n "$KVM_ERR" ]; then
KVM="N"
if [[ "$OSTYPE" =~ ^darwin ]]; then
warn "you are using MacOS which has no KVM support, this will cause a major loss of performance."
else
if grep -qi Microsoft /proc/version; then
warn "you are using Windows 10 which has no KVM support, this will cause a major loss of performance."
else
error "KVM acceleration not available $KVM_ERR, this will cause a major loss of performance."
error "See the FAQ on how to diagnose the cause, or continue without KVM by setting KVM=N (not recommended)."
[[ "$DEBUG" != [Yy1]* ]] && exit 88
fi
fi
fi
fi
if [[ "$KVM" != [Nn]* ]]; then
CPU_FEATURES="kvm=on,l3-cache=on,+hypervisor"
CLOCK="/sys/devices/system/clocksource/clocksource0/current_clocksource" CLOCK="/sys/devices/system/clocksource/clocksource0/current_clocksource"
KVM_OPTS=",accel=kvm -enable-kvm -global kvm-pit.lost_tick_policy=discard"
return 0
}
checkClocksource() {
local result
if [ ! -f "$CLOCK" ]; then
warn "file \"$CLOCK\" cannot be found?"
return 0
fi
result=$(<"$CLOCK")
result="${result//[![:print:]]/}"
case "${result,,}" in
"${CLOCKSOURCE,,}" ) ;;
"kvm-clock" ) info "Nested KVM virtualization detected.." ;;
"hyperv_clocksource_tsc_page" ) info "Nested Hyper-V virtualization detected.." ;;
"hpet" ) warn "unsupported clock source detected: '$result'. Please set host clock source to '$CLOCKSOURCE'." ;;
*) warn "unexpected clock source detected: '$result'. Please set host clock source to '$CLOCKSOURCE'." ;;
esac
return 0
}
checkSse42() {
if ! grep -qw "sse4_2" <<< "$flags"; then 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..." error "Your CPU does not have the SSE4 instruction set that Virtual DSM requires!"
[ -z "$CPU_MODEL" ] && CPU_MODEL="$DEF_MODEL" ! enabled "$DEBUG" && exit 88
CPU_FEATURES+=",+ssse3,+sse4.1,+sse4.2"
fi fi
return 0
}
configureKvmCpuModel() {
CPU_FEATURES="kvm=on,l3-cache=on,+hypervisor"
KVM_OPTS=",accel=kvm -enable-kvm -global kvm-pit.lost_tick_policy=discard"
if [ -z "$CPU_MODEL" ]; then if [ -z "$CPU_MODEL" ]; then
CPU_MODEL="host" CPU_MODEL="host"
CPU_FEATURES+=",migratable=no" CPU_FEATURES+=",migratable=no"
fi fi
if [ -f "$CLOCK" ]; then return 0
CLOCK=$(<"$CLOCK") }
if [[ "${CLOCK,,}" != "tsc" ]]; then
warn "unexpected clocksource: $CLOCK" appendKvmInvtscFeature() {
fi
else
warn "file \"$CLOCK\" cannot not found?"
fi
if grep -qw "svm" <<< "$flags"; then if grep -qw "svm" <<< "$flags"; then
# AMD processor # AMD processor
if grep -qw "tsc_scale" <<< "$flags"; then if grep -qw "tsc_scale" <<< "$flags"; then
CPU_FEATURES+=",+invtsc" CPU_FEATURES+=",+invtsc"
fi fi
@@ -85,7 +78,7 @@ if [[ "$KVM" != [Nn]* ]]; then
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
@@ -94,7 +87,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"
@@ -103,46 +124,95 @@ 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
}
extractHostCpuArgument() {
local args prefix suffix param
if [[ "$ARGUMENTS" == *"-cpu host,"* ]]; then
args="${ARGUMENTS} "
prefix="${args/-cpu host,*/}"
suffix="${args/*-cpu host,/}"
param="${suffix%% *}"
suffix="${suffix#* }"
args="${prefix}${suffix}"
ARGUMENTS="${args::-1}"
if [ -z "$CPU_FLAGS" ]; then
CPU_FLAGS="$param"
else else
CPU_MODEL="$DEF_MODEL" CPU_FLAGS+=",$param"
fi
else
if [[ "$ARGUMENTS" == *"-cpu host"* ]]; then
ARGUMENTS="${ARGUMENTS//-cpu host/}"
fi
fi
return 0
}
composeCpuFlags() {
if [ -z "$CPU_FLAGS" ]; then
if [ -z "$CPU_FEATURES" ]; then
CPU_FLAGS="$CPU_MODEL"
else
CPU_FLAGS="$CPU_MODEL,$CPU_FEATURES"
fi
else
if [ -z "$CPU_FEATURES" ]; then
CPU_FLAGS="$CPU_MODEL,$CPU_FLAGS"
else
CPU_FLAGS="$CPU_MODEL,$CPU_FEATURES,$CPU_FLAGS"
fi fi
fi fi
CPU_FEATURES+=",+ssse3,+sse4.1,+sse4.2" return 0
}
fi configureHostCpuName() {
if [ -z "$CPU_FLAGS" ]; then if [ -z "$HOST_CPU" ]; then
if [ -z "$CPU_FEATURES" ]; then [[ "${CPU,,}" != "unknown" ]] && HOST_CPU="$CPU"
CPU_FLAGS="$CPU_MODEL"
else
CPU_FLAGS="$CPU_MODEL,$CPU_FEATURES"
fi fi
if [ -n "$HOST_CPU" ]; then
HOST_CPU="${HOST_CPU%%,*},,"
else
HOST_CPU="QEMU, Virtual CPU,"
if [ "$ARCH" == "amd64" ]; then
HOST_CPU+=" X86_64"
else
HOST_CPU+=" $ARCH"
fi
fi
return 0
}
selectClocksource
checkClocksource
flags=$(sed -ne '/^flags/s/^.*: //p' /proc/cpuinfo)
if ! disabled "$KVM"; then
configureKvm
else else
if [ -z "$CPU_FEATURES" ]; then configureTcg
CPU_FLAGS="$CPU_MODEL,$CPU_FLAGS"
else
CPU_FLAGS="$CPU_MODEL,$CPU_FEATURES,$CPU_FLAGS"
fi
fi fi
if [ -z "$HOST_CPU" ]; then extractHostCpuArgument
HOST_CPU=$(lscpu | grep -m 1 'Model name' | cut -f 2 -d ":" | awk '{$1=$1}1' | sed 's# @.*##g' | sed s/"(R)"//g | sed 's/[^[:alnum:] ]\+/ /g' | sed 's/ */ /g') composeCpuFlags
fi configureHostCpuName
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
+21 -14
View File
@@ -1,6 +1,8 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -Eeuo pipefail set -Eeuo pipefail
info="/run/shm/msg.html"
escape () { escape () {
local s local s
s=${1//&/\&amp;} s=${1//&/\&amp;}
@@ -11,28 +13,33 @@ escape () {
return 0 return 0
} }
file="$1" path="$1"
total="$2" total="$2"
body=$(escape "$3") body=$(escape "$3")
info="/run/shm/msg.html"
if [[ "$body" == *"..." ]]; then if [[ "$body" == *"..." ]]; then
body="<p class=\"loading\">${body/.../}</p>" body="<p class=\"loading\">${body::-3}</p>"
fi fi
while true while true
do do
if [ -s "$file" ]; then
bytes=$(du -sb "$file" | cut -f1) if [ ! -s "$path" ] && [ ! -d "$path" ]; then
if (( bytes > 1000 )); then bytes="0"
if [ -z "$total" ] || [[ "$total" == "0" ]]; then else
size=$(numfmt --to=iec --suffix=B "$bytes" | sed -r 's/([A-Z])/ \1/') bytes=$(du -sb "$path" | cut -f1)
else
size="$(echo "$bytes" "$total" | awk '{printf "%.1f", $1 * 100 / $2}')"
size="$size%"
fi
echo "${body//(\[P\])/($size)}"> "$info"
fi
fi fi
if (( bytes > 4096 )); then
if [ -z "$total" ] || [[ "$total" == "0" ]] || [ "$bytes" -gt "$total" ]; then
size=$(numfmt --to=iec --suffix=B "$bytes" | sed -r 's/([A-Z])/ \1/')
else
size="$(echo "$bytes" "$total" | awk '{printf "%.1f", $1 * 100 / $2}')"
size="$size%"
fi
[[ "$size" != "0.0%" ]] && echo "${body//(\[P\])/($size)}"> "$info"
fi
sleep 1 & wait $! sleep 1 & wait $!
done done
+246 -227
View File
@@ -1,270 +1,289 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -Eeuo pipefail set -Eeuo pipefail
info () { printf "%b%s%b" "\E[1;34m \E[1;36m" "${1:-}" "\E[0m\n"; }
error () { printf "%b%s%b" "\E[1;31m " "ERROR: ${1:-}" "\E[0m\n" >&2; }
warn () { printf "%b%s%b" "\E[1;31m " "Warning: ${1:-}" "\E[0m\n" >&2; }
trap 'error "Status $? while: $BASH_COMMAND (line $LINENO/$BASH_LINENO)"' ERR trap 'error "Status $? while: $BASH_COMMAND (line $LINENO/$BASH_LINENO)"' ERR
enabled "${TRACE:-}" && set -o functrace && trap 'echo "# $BASH_COMMAND" >&2' DEBUG
[ ! -f "/run/entry.sh" ] && error "Script must run inside Docker container!" && exit 11 [ ! -f "/run/entry.sh" ] && error "Script must be run inside the container!" && exit 11
[ "$(id -u)" -ne "0" ] && error "Script must be executed with root privileges." && exit 12 [ "$(id -u)" -ne "0" ] && error "Script must be executed with root privileges." && exit 12
echo " Starting $APP for Docker v$(</run/version)..."
echo " For support visit $SUPPORT"
# Docker environment variables # Docker environment variables
: "${TZ:=""}" # System local timezone : "${TZ:=""}" # System timezone
: "${DEBUG:="N"}" # Disable debugging mode : "${KVM:="Y"}" # KVM acceleration
: "${COUNTRY:=""}" # Country code for mirror : "${DEBUG:="N"}" # Disable debugging mode
: "${CONSOLE:="N"}" # Disable console mode : "${COUNTRY:=""}" # Country code for mirror
: "${ALLOCATE:=""}" # Preallocate diskspace : "${ALLOCATE:=""}" # Preallocate diskspace
: "${ARGUMENTS:=""}" # Extra QEMU parameters : "${ARGUMENTS:=""}" # Extra QEMU parameters
: "${CPU_CORES:="1"}" # Amount of CPU cores : "${CPU_CORES:="2"}" # Amount of CPU cores
: "${RAM_SIZE:="1G"}" # Maximum RAM amount : "${RAM_SIZE:="2G"}" # Maximum RAM amount
: "${RAM_CHECK:="Y"}" # Check available RAM : "${RAM_CHECK:="Y"}" # Check available RAM
: "${DISK_SIZE:="16G"}" # Initial data disk size : "${DISK_SIZE:="16G"}" # Initial data disk size
: "${STORAGE:="/storage"}" # Storage folder location
detectEngine() {
if [ -f "/run/.containerenv" ]; then
ENGINE="${container:-}"
if [[ "${ENGINE,,}" == *"podman"* ]]; then
ROOTLESS="Y"
ENGINE="Podman"
else
[ -z "$ENGINE" ] && ENGINE="Kubernetes"
fi
fi
return 0
}
checkPrivileged() {
local cap_bnd
local last_cap
local max_cap
# Get the capability bounding set
cap_bnd=$(grep '^CapBnd:' /proc/$$/status | awk '{print $2}')
cap_bnd=$(printf "%d" "0x${cap_bnd}")
# Get the last capability number
last_cap=$(cat /proc/sys/kernel/cap_last_cap)
# Calculate the maximum capability value
max_cap=$(((1 << (last_cap + 1)) - 1))
if [ "$cap_bnd" -eq "$max_cap" ]; then
ROOTLESS="N"
PRIVILEGED="Y"
fi
return 0
}
normalizeCpuCores() {
CPU_CORES=$(strip "$CPU_CORES")
[ -z "$CPU_CORES" ] && CPU_CORES=2
[[ "${CPU_CORES,,}" == "max" ]] && CPU_CORES="$CORES"
[[ "${CPU_CORES,,}" == "half" ]] && CPU_CORES=$(( CORES / 2 ))
[ -z "${CPU_CORES##*[!0-9]*}" ] && error "Invalid amount of CPU_CORES: $CPU_CORES" && exit 15
[ "$CPU_CORES" -lt "1" ] && CPU_CORES=1
if [ "$CPU_CORES" -gt "$CORES" ]; then
warn "The amount for CPU_CORES (${CPU_CORES}) exceeds the amount of logical cores available (${CORES}) and will be limited."
CPU_CORES="$CORES"
fi
return 0
}
checkStorage() {
# Check system
QEMU_DIR="/run/shm"
if [ ! -d "/dev/shm" ]; then
error "Directory /dev/shm not found!" && exit 14
else
[ ! -d "$QEMU_DIR" ] && ln -s /dev/shm "$QEMU_DIR"
fi
QEMU_PID="$QEMU_DIR/qemu.pid"
# Check folder
if [[ "${STORAGE,,}" != "/storage" ]]; then
mkdir -p "$STORAGE"
fi
if [ ! -d "$STORAGE" ]; then
error "Storage folder ($STORAGE) not found!" && exit 13
fi
if [ ! -w "$STORAGE" ]; then
msg="Storage folder ($STORAGE) is not writeable!"
msg+=" If SELinux is active, you need to add the \":Z\" flag to the bind mount."
error "$msg" && exit 13
fi
return 0
}
checkFilesystem() {
# Check filesystem
FS=$(stat -f -c %T "$STORAGE")
if [[ "${FS,,}" == "ecryptfs" || "${FS,,}" == "tmpfs" ]]; then
DISK_IO="threads"
DISK_CACHE="writeback"
fi
return 0
}
normalizeRamSize() {
# Read memory
RAM_AVAIL=$(free -b | grep -m 1 Mem: | awk '{print $7}')
RAM_TOTAL=$(free -b | grep -m 1 Mem: | awk '{print $2}')
RAM_SPARE=500000000
RAM_MINIMUM=136314880
RAM_SIZE=$(strip "$RAM_SIZE")
RAM_SIZE="${RAM_SIZE// /}"
[ -z "$RAM_SIZE" ] && RAM_SIZE="2G"
if [[ "${RAM_SIZE,,}" != "max" && "${RAM_SIZE,,}" != "half" ]]; then
if [ -z "${RAM_SIZE//[0-9. ]}" ]; then
[ "${RAM_SIZE%%.*}" -lt "130" ] && RAM_SIZE="${RAM_SIZE}G" || RAM_SIZE="${RAM_SIZE}M"
fi
RAM_SIZE=$(echo "${RAM_SIZE^^}" | sed 's/MB/M/g;s/GB/G/g;s/TB/T/g')
! numfmt --from=iec "$RAM_SIZE" &>/dev/null && error "Invalid RAM_SIZE: $RAM_SIZE" && exit 16
wanted=$(numfmt --from=iec "$RAM_SIZE")
[ "$wanted" -lt "$RAM_MINIMUM" ] && error "RAM_SIZE is too low: $RAM_SIZE" && exit 16
fi
return 0
}
checkKvm() {
# Check KVM support
if [[ "${PLATFORM,,}" == "x64" ]]; then
TARGET="amd64"
else
TARGET="arm64"
fi
if disabled "$KVM"; then
warn "KVM acceleration is disabled, this will cause the machine to run about 10 times slower!"
else
if [[ "${ARCH,,}" != "$TARGET" ]]; then
KVM="N"
warn "your CPU architecture is ${ARCH^^} and cannot provide KVM acceleration for ${PLATFORM^^} instructions, so the machine will run about 10 times slower."
fi
fi
if ! disabled "$KVM"; then
KVM_ERR=""
if [ ! -e /dev/kvm ]; then
KVM_ERR="(/dev/kvm is missing)"
else
if ! sh -c 'echo -n > /dev/kvm' &> /dev/null; then
KVM_ERR="(/dev/kvm is unwriteable)"
else
if [[ "${PLATFORM,,}" == "x64" ]]; then
flags=$(sed -ne '/^flags/s/^.*: //p' /proc/cpuinfo)
if ! grep -qw "vmx\|svm" <<< "$flags"; then
KVM_ERR="(not enabled in BIOS)"
fi
if ! grep -qw "sse4_2" <<< "$flags"; then
error "Your CPU does not have the SSE4 instruction set that Virtual DSM requires!"
! enabled "$DEBUG" && exit 88
fi
fi
fi
fi
if [ -n "$KVM_ERR" ]; then
KVM="N"
if [[ "$OSTYPE" =~ ^darwin ]]; then
warn "you are using macOS which has no KVM support, so the machine will run about 10 times slower."
else
kernel=$(uname -a)
case "${kernel,,}" in
*"microsoft"* )
error "Please bind '/dev/kvm' as a volume in the optional container settings when using Docker Desktop." ;;
*"synology"* )
error "Please make sure that Synology VMM (Virtual Machine Manager) is installed and that '/dev/kvm' is binded to this container." ;;
*)
error "KVM acceleration is not available $KVM_ERR, this will cause the machine to run about 10 times slower."
error "See the FAQ for possible causes, or disable acceleration by adding the \"KVM=N\" variable (not recommended)." ;;
esac
! enabled "$DEBUG" && exit 88
fi
fi
fi
return 0
}
# Sanitize variables
TZ=$(strip "$TZ")
STORAGE=$(strip "$STORAGE")
COUNTRY=$(strip "$COUNTRY")
DISK_SIZE=$(strip "$DISK_SIZE")
# Helper variables # Helper variables
ROOTLESS="N"
PRIVILEGED="N"
ENGINE="Docker"
PROCESS="${APP,,}" PROCESS="${APP,,}"
PROCESS="${PROCESS// /-}" PROCESS="${PROCESS// /-}"
STORAGE="/storage" detectEngine
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>"
CPI=$(lscpu) SOCKETS=1
CPU=$(cpu)
SYS=$(uname -r) SYS=$(uname -r)
HOST=$(hostname -s)
KERNEL=$(echo "$SYS" | cut -b 1) KERNEL=$(echo "$SYS" | cut -b 1)
MINOR=$(echo "$SYS" | cut -d '.' -f2) MINOR=$(echo "$SYS" | cut -d '.' -f2)
ARCH=$(dpkg --print-architecture) ARCH=$(dpkg --print-architecture)
CORES=$(grep -c '^processor' /proc/cpuinfo) CORES=$(grep -c '^processor' /proc/cpuinfo)
if ! grep -qi "socket(s)" <<< "$CPI"; 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=$(echo "$CPI" | grep -m 1 -i 'socket(s)' | awk '{print $(2)}') [ "$SOCKETS" -lt "1" ] && SOCKETS=1
fi fi
if ! grep -qi "model name" <<< "$CPI"; then normalizeCpuCores
CPU="Unknown" checkStorage
else checkFilesystem
CPU=$(echo "$CPI" | grep -m 1 -i 'model name' | cut -f 2 -d ":" | awk '{$1=$1}1' | sed 's# @.*##g' | sed s/"(R)"//g | sed 's/[^[:alnum:] ]\+/ /g' | sed 's/ */ /g') normalizeRamSize
CPU="${CPU// with Radeon Graphics/}"
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 [ ! -d "$STORAGE" ]; then
error "Storage folder ($STORAGE) not found!" && 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=$(echo "${RAM_SIZE^^}" | sed 's/MB/M/g;s/GB/G/g;s/TB/T/g')
RAM_WANTED=$(numfmt --from=iec "$RAM_SIZE")
AVAIL_GB=$(( RAM_AVAIL/1073741824 ))
TOTAL_GB=$(( (RAM_TOTAL + 1073741823)/1073741824 ))
WANTED_GB=$(( (RAM_WANTED + 1073741823)/1073741824 ))
# Print system info # Print system info
SYS="${SYS/-generic/}" SYS="${SYS/-generic/}"
FS="${FS/UNKNOWN //}"
FS="${FS/ext2\/ext3/ext4}" FS="${FS/ext2\/ext3/ext4}"
FS=$(echo "$FS" | sed 's/[)(]//g')
SPACE=$(df --output=avail -B 1 "$STORAGE" | tail -n 1) SPACE=$(df --output=avail -B 1 "$STORAGE" | tail -n 1)
SPACE_GB=$(( (SPACE + 1073741823)/1073741824 )) SPACE_GB=$(formatBytes "$SPACE" "down")
AVAIL_MEM=$(formatBytes "$RAM_AVAIL" "down")
TOTAL_MEM=$(formatBytes "$RAM_TOTAL" "up")
echo " CPU: ${CPU} | RAM: $AVAIL_GB/$TOTAL_GB GB | DISK: $SPACE_GB GB (${FS}) | HOST: ${SYS}..." echo " CPU: ${CPU} | RAM: ${AVAIL_MEM/ GB/}/$TOTAL_MEM | DISK: $SPACE_GB (${FS}) | KERNEL: ${SYS}..."
echo echo
# Check memory checkKvm
if [[ "$RAM_CHECK" != [Nn]* ]]; then
if (( (RAM_WANTED + RAM_SPARE) > RAM_AVAIL )); then
error "Your configured RAM_SIZE of $WANTED_GB GB is too high for the $AVAIL_GB GB of memory available, please set a lower value."
exit 17
fi
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.* "$QEMU_DIR"/*.pid "$QEMU_DIR"/*.sock
# Cleanup dirs # Cleanup dirs
rm -rf /tmp/dsm rm -rf /tmp/dsm
rm -rf "$STORAGE/tmp" rm -rf "$STORAGE/tmp"
# Helper functions
isAlive() {
local pid=$1
if kill -0 "$pid" 2>/dev/null; then
return 0
fi
return 1
}
pKill() {
local pid=$1
{ kill -15 "$pid" || true; } 2>/dev/null
while isAlive "$pid"; do
sleep 0.2
done
return 0
}
fWait() {
local name=$1
while pgrep -f -l "$name" >/dev/null; do
sleep 0.2
done
return 0
}
fKill() {
local name=$1
{ pkill -f "$name" || true; } 2>/dev/null
fWait "$name"
return 0
}
escape () {
local s
s=${1//&/\&amp;}
s=${s//</\&lt;}
s=${s//>/\&gt;}
s=${s//'"'/\&quot;}
printf -- %s "$s"
return 0
}
html()
{
local title
local body
local script
local footer
title=$(escape "$APP")
title="<title>$title</title>"
footer=$(escape "$FOOTER1")
body=$(escape "$1")
if [[ "$body" == *"..." ]]; then
body="<p class=\"loading\">${body/.../}</p>"
fi
[ -n "${2:-}" ] && script="$2" || script=""
local HTML
HTML=$(<"$TEMPLATE")
HTML="${HTML/\[1\]/$title}"
HTML="${HTML/\[2\]/$script}"
HTML="${HTML/\[3\]/$body}"
HTML="${HTML/\[4\]/$footer}"
HTML="${HTML/\[5\]/$FOOTER2}"
echo "$HTML" > "$PAGE"
echo "$body" > "$INFO"
return 0
}
getCountry() {
local url=$1
local query=$2
local rc json result
{ json=$(curl -m 5 -H "Accept: application/json" -sfk "$url"); rc=$?; } || :
(( rc != 0 )) && return 0
{ result=$(echo "$json" | jq -r "$query" 2> /dev/null); rc=$?; } || :
(( rc != 0 )) && return 0
[[ ${#result} -ne 2 ]] && return 0
[[ "${result^^}" == "XX" ]] && return 0
COUNTRY="${result^^}"
return 0
}
setCountry() {
[[ "${TZ,,}" == "asia/harbin" ]] && COUNTRY="CN"
[[ "${TZ,,}" == "asia/beijing" ]] && COUNTRY="CN"
[[ "${TZ,,}" == "asia/urumqi" ]] && COUNTRY="CN"
[[ "${TZ,,}" == "asia/kashgar" ]] && COUNTRY="CN"
[[ "${TZ,,}" == "asia/shanghai" ]] && COUNTRY="CN"
[[ "${TZ,,}" == "asia/chongqing" ]] && COUNTRY="CN"
[ -z "$COUNTRY" ] && getCountry "https://api.ipapi.is" ".location.country_code"
[ -z "$COUNTRY" ] && getCountry "https://ifconfig.co/json" ".country_iso"
[ -z "$COUNTRY" ] && getCountry "https://api.ip2location.io" ".country_code"
[ -z "$COUNTRY" ] && getCountry "https://ipinfo.io/json" ".country"
[ -z "$COUNTRY" ] && getCountry "https://api.myip.com" ".cc"
return 0
}
addPackage() {
local pkg=$1
local desc=$2
if apt-mark showinstall | grep -qx "$pkg"; then
return 0
fi
MSG="Installing $desc..."
info "$MSG" && html "$MSG"
[ -z "$COUNTRY" ] && setCountry
if [[ "${COUNTRY^^}" == "CN" ]]; then
sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list.d/debian.sources
fi
DEBIAN_FRONTEND=noninteractive apt-get -qq update
DEBIAN_FRONTEND=noninteractive apt-get -qq --no-install-recommends -y install "$pkg" > /dev/null
return 0
}
# Start webserver
cp -r /var/www/* /run/shm
html "Starting $APP for Docker..."
nginx -e stderr
return 0 return 0
+73 -39
View File
@@ -5,11 +5,22 @@ 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")
validateHostMac() {
local m
if [ -z "$HOST_MAC" ]; then
return 0
fi
HOST_MAC="${HOST_MAC//-/:}" HOST_MAC="${HOST_MAC//-/:}"
@@ -22,54 +33,77 @@ 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")
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 if enabled "$HOST_DEBUG"; then
set -x
./host.bin "${HOST_ARGS[@]}" &
{ set +x; } 2>/dev/null
echo "$!" > "$HOST_PID"
echo
else
./host.bin "${HOST_ARGS[@]}" >/dev/null &
echo "$!" > "$HOST_PID"
fi
while ! nc -z -w2 127.0.0.1 12345 > /dev/null 2>&1; do return 0
sleep 0.1 }
cnt=$((cnt + 1))
(( cnt > 50 )) && error "Failed to connect to qemu-host.." && exit 59
done
# Configure serial ports waitForPort() {
if [[ "$CONSOLE" != [Yy]* ]]; then local port="$1"
SERIAL_OPTS="-serial pty" local exit_code="$2"
else local cnt=0
SERIAL_OPTS="-serial mon:stdio"
fi
SERIAL_OPTS+=" \ while ! nc -z -w2 127.0.0.1 "$port" > /dev/null 2>&1; do
sleep 0.1
cnt=$((cnt + 1))
(( cnt > 50 )) && error "Failed to connect to qemu-host.." && exit "$exit_code"
done
return 0
}
configureSerialPorts() {
# Configure serial ports
SERIAL_OPTS="-serial mon:stdio \
-device virtio-serial-pci,id=virtio-serial0,bus=pcie.0,addr=0x3 \ -device virtio-serial-pci,id=virtio-serial0,bus=pcie.0,addr=0x3 \
-chardev socket,id=charchannel0,host=127.0.0.1,port=12345,reconnect=10 \ -chardev socket,id=charchannel0,host=127.0.0.1,port=$CHR_PORT,reconnect=10 \
-device virtserialport,bus=virtio-serial0.0,nr=1,chardev=charchannel0,id=channel0,name=vchannel" -device virtserialport,bus=virtio-serial0.0,nr=1,chardev=charchannel0,id=channel0,name=vchannel"
return 0
}
validateHostMac
HOST_PID="$QEMU_DIR/host.pid"
buildHostArguments
startHostBinary
sleep 0.2
waitForPort "$COM_PORT" 58
waitForPort "$CHR_PORT" 59
configureSerialPorts
return 0 return 0
+76
View File
@@ -0,0 +1,76 @@
#!/usr/bin/env bash
set -Eeuo pipefail
: "${COM_PORT:="2210"}" # Comm port
: "${WEB_PORT:="5000"}" # Webserver port
: "${CHR_PORT:="12345"}" # Character port
: "${WSD_PORT:="8004"}" # Websockets port
# Sanitize port variables
COM_PORT=$(strip "$COM_PORT")
WEB_PORT=$(strip "$WEB_PORT")
CHR_PORT=$(strip "$CHR_PORT")
WSD_PORT=$(strip "$WSD_PORT")
WEB_PID="/run/nginx.pid"
WSD_PID="$QEMU_DIR/websocketd.pid"
prepareWebFiles() {
cp -r /var/www/* "$QEMU_DIR"
rm -f "$WSD_PID" "$WEB_PID"
return 0
}
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" ]] && [ -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
return 0
}
configureWebServer() {
mkdir -p /etc/nginx/sites-enabled
cp /etc/nginx/default.conf /etc/nginx/sites-enabled/web.conf
configureWebPorts
configureIpv6Listen
return 0
}
startWebServer() {
# Start webserver
nginx -e stderr
}
startWebsocketServer() {
# Start websocket server
websocketd --address 127.0.0.1 --port="$WSD_PORT" /run/socket.sh >/var/log/websocketd.log &
echo "$!" > "$WSD_PID"
}
prepareWebFiles
html "Starting $APP for $ENGINE..."
if ! disabled "${WEB:-}"; then
configureWebServer
startWebServer
startWebsocketServer
fi
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")
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
+370
View File
@@ -0,0 +1,370 @@
#!/usr/bin/env bash
set -Eeuo pipefail
# Helper functions
info () { printf "%b%s%b" "\E[1;34m \E[1;36m" "${1:-}" "\E[0m\n"; }
error () { printf "%b%s%b" "\E[1;31m " "ERROR: ${1:-}" "\E[0m\n" >&2; }
warn () { printf "%b%s%b" "\E[1;31m " "Warning: ${1:-}" "\E[0m\n" >&2; }
strip() {
local value="${1:-}"
# Remove surrounding whitespace
value="${value#"${value%%[![:space:]]*}"}"
value="${value%"${value##*[![:space:]]}"}"
# Remove leading/trailing single/double quotes
value="${value%\"}"
value="${value#\"}"
value="${value%\'}"
value="${value#\'}"
# Remove surrounding whitespace again
value="${value#"${value%%[![:space:]]*}"}"
value="${value%"${value##*[![:space:]]}"}"
printf '%s' "$value"
}
enabled() {
case "$(strip "${1:-}")" in
Y|y|YES|Yes|yes|TRUE|True|true|1|ON|On|on) return 0 ;;
*) return 1 ;;
esac
}
disabled() {
case "$(strip "${1:-}")" in
N|n|NO|No|no|FALSE|False|false|0|OFF|Off|off) return 0 ;;
*) return 1 ;;
esac
}
formatBytes() {
local result
result=$(numfmt --to=iec --suffix=B "$1" | sed -r 's/([A-Z])/ \1/' | sed 's/ B/ bytes/g;')
local unit="${result//[0-9. ]}"
result="${result//[a-zA-Z ]/}"
if [[ "${2:-}" == "up" ]]; then
if [[ "$result" == *"."* ]]; then
result="${result%%.*}"
result=$((result+1))
fi
else
if [[ "${2:-}" == "down" ]]; then
result="${result%%.*}"
fi
fi
echo "$result $unit"
return 0
}
isAlive() {
local pid="$1"
[ -z "$pid" ] && return 1
if kill -0 "$pid" 2>/dev/null; then
return 0
fi
return 1
}
waitPid() {
local i=0
local pid="$1"
local timeout="${2:-10}"
while [ -n "$pid" ] && isAlive "$pid"; do
sleep 0.2
i=$((i + 1))
(( i >= timeout * 5 )) && return 1
done
return 0
}
waitPidFile() {
local i=0
local pid=""
local file="$1"
local timeout="${2:-10}"
[ ! -s "$file" ] && return 0
! read -r pid <"$file" && return 0
[ -z "$pid" ] && return 0
while [ -s "$file" ] && isAlive "$pid"; do
sleep 0.2
i=$((i + 1))
(( i >= timeout * 5 )) && return 1
done
rm -f -- "$file"
return 0
}
pKill() {
local pid="$1"
local timeout="${2:-10}"
{ kill -15 -- "$pid" || :; } 2>/dev/null
if ! waitPid "$pid" "$timeout"; then
warn "Timed out while waiting for PID $pid"
fi
return 0
}
fWait() {
local i=0
local name="$1"
local timeout="${2:-10}"
[ -z "$name" ] && return 0
while pgrep -f -l "$name" >/dev/null; do
sleep 0.2
i=$((i + 1))
if (( i >= timeout * 5 )); then
warn "Timed out while waiting for process: $name"
break
fi
done
return 0
}
fKill() {
local name="$1"
local timeout="${2:-10}"
[ -z "$name" ] && return 0
{ pkill -f "$name" || :; } 2>/dev/null
fWait "$name" "$timeout"
return 0
}
sKill() {
local pid=""
local file="$1"
[ ! -s "$file" ] && return 0
! read -r pid <"$file" && return 0
[ -z "$pid" ] && return 0
if isAlive "$pid"; then
{ kill -15 -- "$pid" || :; } 2>/dev/null
fi
return 0
}
mKill() {
local timeout=10
local files=("$@")
for file in "${files[@]}"; do
sKill "$file"
done
for file in "${files[@]}"; do
if ! waitPidFile "$file" "$timeout"; then
warn "Timed out while waiting for PID file: $file"
fi
done
return 0
}
setOwner() {
local file="$1"
local dir uid gid
[ ! -f "$file" ] && return 1
dir=$(dirname -- "$file")
uid=$(stat -c '%u' "$dir")
gid=$(stat -c '%g' "$dir")
! chown "$uid:$gid" "$file" && return 1
return 0
}
makeDir() {
local path="$1"
local dir uid gid
[ -d "$path" ] && return 0
! mkdir -p "$path" && return 1
dir=$(dirname -- "$path")
uid=$(stat -c '%u' "$dir")
gid=$(stat -c '%g' "$dir")
! chown "$uid:$gid" "$path" && return 1
return 0
}
escape () {
local s
s=${1//&/\&amp;}
s=${s//</\&lt;}
s=${s//>/\&gt;}
s=${s//'"'/\&quot;}
printf -- %s "$s"
return 0
}
html() {
local title
local body
local script
local footer
title=$(escape "$APP")
title="<title>$title</title>"
footer=$(escape "$FOOTER1")
body=$(escape "$1")
if [[ "$body" == *"..." ]]; then
body="<p class=\"loading\">${body/.../}</p>"
fi
[ -n "${2:-}" ] && script="$2" || script=""
local HTML
HTML=$(<"$TEMPLATE")
HTML="${HTML/\[1\]/$title}"
HTML="${HTML/\[2\]/$script}"
HTML="${HTML/\[3\]/$body}"
HTML="${HTML/\[4\]/$footer}"
HTML="${HTML/\[5\]/$FOOTER2}"
echo "$HTML" > "$PAGE"
echo "$body" > "$INFO"
return 0
}
cpu() {
local ret
local cpu=""
ret=$(lscpu)
if grep -qi "model name" <<< "$ret"; then
cpu=$(echo "$ret" | grep -m 1 -i 'model name' | cut -f 2 -d ":" | awk '{$1=$1}1' | sed 's# @.*##g' | sed s/"(R)"//g | sed 's/[^[:alnum:] ]\+/ /g' | sed 's/ */ /g')
fi
if [ -z "${cpu// /}" ] && grep -qi "model:" <<< "$ret"; then
cpu=$(echo "$ret" | grep -m 1 -i 'model:' | cut -f 2 -d ":" | awk '{$1=$1}1' | sed 's# @.*##g' | sed s/"(R)"//g | sed 's/[^[:alnum:] ]\+/ /g' | sed 's/ */ /g')
fi
cpu="${cpu// CPU/}"
cpu="${cpu// [0-9][0-9][0-9] Core}"
cpu="${cpu// [0-9][0-9] Core}"
cpu="${cpu// [0-9] Core}"
cpu="${cpu//[0-9][0-9]th Gen }"
cpu="${cpu//[0-9]th Gen }"
cpu="${cpu// Processor/}"
cpu="${cpu// Quad core/}"
cpu="${cpu// Dual core/}"
cpu="${cpu// Octa core/}"
cpu="${cpu// Hexa core/}"
cpu="${cpu// Core TM/ Core}"
cpu="${cpu// with Radeon Graphics/}"
cpu="${cpu// with Radeon Vega Graphics/}"
cpu="${cpu// with Radeon Vega Mobile Gfx/}"
cpu="${cpu// w Radeon [0-9][0-9][0-9]M Graphics/}"
[ -z "${cpu// /}" ] && cpu="Unknown"
echo "$cpu"
return 0
}
hasDisk() {
enabled "${DISK_DISABLE:-}" && return 1
[ -b "/disk" ] && return 0
[ -b "/disk1" ] && return 0
[ -b "/dev/disk1" ] && return 0
[ -b "${DEVICE:-}" ] && return 0
[ -z "${DISK_NAME:-}" ] && DISK_NAME="data"
[ -s "$STORAGE/$DISK_NAME.img" ] && return 0
[ -s "$STORAGE/$DISK_NAME.qcow2" ] && return 0
return 1
}
getCountry() {
local url=$1
local query=$2
local rc json result
{ json=$(curl -m 5 -H "Accept: application/json" -sfk "$url"); rc=$?; } || :
(( rc != 0 )) && return 0
{ result=$(echo "$json" | jq -r "$query" 2> /dev/null); rc=$?; } || :
(( rc != 0 )) && return 0
[[ ${#result} -ne 2 ]] && return 0
[[ "${result^^}" == "XX" ]] && return 0
COUNTRY="${result^^}"
return 0
}
setCountry() {
[[ "${TZ,,}" == "asia/harbin" ]] && COUNTRY="CN"
[[ "${TZ,,}" == "asia/beijing" ]] && COUNTRY="CN"
[[ "${TZ,,}" == "asia/urumqi" ]] && COUNTRY="CN"
[[ "${TZ,,}" == "asia/kashgar" ]] && COUNTRY="CN"
[[ "${TZ,,}" == "asia/shanghai" ]] && COUNTRY="CN"
[[ "${TZ,,}" == "asia/chongqing" ]] && COUNTRY="CN"
[ -z "$COUNTRY" ] && getCountry "https://api.ipapi.is" ".location.country_code"
[ -z "$COUNTRY" ] && getCountry "https://ifconfig.co/json" ".country_iso"
[ -z "$COUNTRY" ] && getCountry "https://api.ip2location.io" ".country_code"
[ -z "$COUNTRY" ] && getCountry "https://ipinfo.io/json" ".country"
[ -z "$COUNTRY" ] && getCountry "https://api.ipquery.io/?format=json" ".location.country_code"
[ -z "$COUNTRY" ] && getCountry "https://api.myip.com" ".cc"
return 0
}
addPackage() {
local pkg=$1
local desc=$2
if apt-mark showinstall | grep -qx "$pkg"; then
return 0
fi
MSG="Installing $desc..."
info "$MSG" && html "$MSG"
[ -z "$COUNTRY" ] && setCountry
if [[ "${COUNTRY^^}" == "CN" ]]; then
sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list.d/debian.sources
fi
DEBIAN_FRONTEND=noninteractive apt-get -qq update
DEBIAN_FRONTEND=noninteractive apt-get -qq --no-install-recommends -y install "$pkg" > /dev/null
return 0
}
return 0
+14 -1
View File
@@ -1,5 +1,4 @@
server { server {
listen 80;
listen 5000 default_server; listen 5000 default_server;
autoindex on; autoindex on;
@@ -28,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/;
}
} }
+3 -3
View File
@@ -5,8 +5,8 @@
[1] [1]
<meta http-equiv="Cache-Control" content="no-cache" /> <meta http-equiv="Cache-Control" content="no-cache" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" type="text/css" href="/css/style.css" /> <link rel="stylesheet" type="text/css" href="css/style.css" />
<link rel="icon" href="/img/favicon.svg" type="image/x-icon"> <link rel="icon" href="img/favicon.svg" type="image/x-icon">
[2] [2]
</head> </head>
@@ -28,7 +28,7 @@
[5] [5]
</footer> </footer>
</div> </div>
<script type="text/javascript" src="/js/script.js"></script> <script type="text/javascript" src="js/script.js"></script>
</body> </body>
</html> </html>
+111 -33
View File
@@ -1,16 +1,20 @@
var request; var request;
var booting = false;
var interval = 1000; var interval = 1000;
function getInfo() { 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();