Compare commits

...
74 Commits
Author SHA1 Message Date
KroeseandGitHub 6bcacf5e59 feat: Improve boolean helpers (#1268) 2026-07-15 19:35:16 +02:00
KroeseandGitHub 3bea87f087 feat: Suppress empty iptables debug sections (#1267) 2026-07-15 19:21:43 +02:00
KroeseandGitHub f01e1564f7 fix: Make setting owner non-fatal (#1266) 2026-07-15 18:00:24 +02:00
KroeseandGitHub cf8cdbf0c1 fix: Make failed chown non-fatal (#1265) 2026-07-15 17:39:05 +02:00
KroeseandGitHub 0813494ee0 feat: Use cgroup when calculating RAM (#1264) 2026-07-15 15:40:51 +02:00
KroeseandGitHub 0d71e30f50 feat: Add state helpers to utils (#1263) 2026-07-15 15:28:42 +02:00
KroeseandGitHub c203deab9a feat: Improve webserver configuration (#1262) 2026-07-15 15:27:24 +02:00
KroeseandGitHub 9cf31df266 fix: Validate subnet mask (#1261) 2026-07-15 14:17:01 +02:00
KroeseandGitHub 0cd08f2ee1 fix: Improve NAT port forwarding (#1260) 2026-07-15 12:24:25 +02:00
KroeseandGitHub a32d215e90 feat: Improve wget error reporting (#1259) 2026-07-15 11:34:55 +02:00
KroeseandGitHub c1f9a690f9 fix: Remove dots from info line (#1258) 2026-07-15 09:00:14 +02:00
KroeseandGitHub 5de504deb3 feat: Improve rootless container detection (#1256) 2026-07-15 06:02:12 +02:00
KroeseandGitHub 250eb18238 docs: Readme (#1255) 2026-07-14 12:50:07 +02:00
KroeseandGitHub ca80da59bd docs: Environment variables (#1254) 2026-07-14 11:29:10 +02:00
KroeseandGitHub 9cd86dfe82 fix: Use exec form for health check (#1253) 2026-07-13 19:40:30 +02:00
KroeseandGitHub 5bb93651f7 docs: Readme (#1252) 2026-07-13 00:38:58 +02:00
KroeseandGitHub 0c268308a4 feat: Preserve source image until conversion completes (#1251) 2026-07-12 23:33:10 +02:00
KroeseandGitHub 7b741d3e4b feat: Warn about UDP host ports (#1250) 2026-07-12 22:14:11 +02:00
KroeseandGitHub f1d29212ae feat: Improve iptables rule cleanup (#1249) 2026-07-12 21:58:49 +02:00
KroeseandGitHub fdee9f89a3 feat: Improve iptables backend selection (#1248) 2026-07-12 21:36:52 +02:00
KroeseandGitHub 210a360797 fix: Restore compatible NAT forwarding rules (#1247) 2026-07-12 13:18:52 +03:00
KroeseandGitHub 991bb2627a fix: Prefer legacy iptables backend for NAT (#1246) 2026-07-12 12:44:00 +03:00
KroeseandGitHub 764a186836 feat: Allow all forwarded NAT traffic (#1245) 2026-07-11 11:58:35 +03:00
KroeseandGitHub 726786a98a feat: Default host and user ports to TCP only (#1244) 2026-07-09 11:06:01 +02:00
KroeseandGitHub 2caf97d407 docs: Network modes (#1243) 2026-07-09 00:39:52 +02:00
KroeseandGitHub 0e67286599 fix: Improve file handling (#1242) 2026-07-08 23:55:02 +02:00
KroeseandGitHub 2b9fc80275 feat: Validate MAC file writes (#1241) 2026-07-08 23:28:56 +02:00
KroeseandGitHub 0d88774bd3 feat: Display base directory in warnings (#1240) 2026-07-08 23:16:20 +02:00
KroeseandGitHub fa23052ed4 fix: Set default DHCP lease time to 1 hour (#1239) 2026-07-08 22:46:47 +02:00
KroeseandGitHub cc3267f258 docs: Correct hostname (#1238) 2026-07-08 18:57:55 +02:00
KroeseandGitHub fd002564a6 feat: Validate custom QEMU CPU arguments (#1237) 2026-07-08 18:50:31 +02:00
KroeseandGitHub 2703a97cfa docs: Refer to environment documentation (#1236) 2026-07-08 18:44:04 +02:00
KroeseandGitHub 29e2d034bf docs: Document all environment variables (#1235) 2026-07-08 17:11:10 +02:00
KroeseandGitHub cbe3644335 fix: Add extra line after boot (#1234) 2026-07-08 16:50:21 +02:00
KroeseandGitHub c9a2a6966b feat: Always show network info (#1233) 2026-07-07 19:37:59 +02:00
KroeseandGitHub 498248f43e feat: Display guest info before mode warning (#1232) 2026-07-07 12:21:25 +02:00
KroeseandGitHub 8963228b1c fix: Set returnvalue for waitForPort (#1231) 2026-07-07 12:13:13 +02:00
KroeseandGitHub 827d6d9e10 fix: Status monitor exited early (#1230) 2026-07-07 12:03:58 +02:00
KroeseandGitHub 1c577abef4 fix: Guest IP was not retrieved correctly (#1229) 2026-07-07 11:16:22 +02:00
KroeseandGitHub 20fd1a975c fix: Read QEMU pid correctly (#1228) 2026-07-07 10:53:53 +02:00
KroeseandGitHub 770774680c feat: Improve shutdown logic (#1226) 2026-07-07 03:16:35 +02:00
KroeseandGitHub 0c75aa81fe fix: Detect guest IP without assuming eth0 (#1225) 2026-07-06 23:54:24 +02:00
KroeseandGitHub 36ec57ac15 fix: Initialize indexdb path before extraction (#1224) 2026-07-06 23:50:26 +02:00
KroeseandGitHub 9c488c52b9 feat: Normalize disk image format (#1223)
*
2026-07-06 23:44:14 +02:00
KroeseandGitHub 2ba756260f fix: Sanitize guest hostname (#1222) 2026-07-06 23:33:34 +02:00
KroeseandGitHub b306012743 feat: Normalize discard mode for qemu drives (#1221) 2026-07-06 23:21:25 +02:00
KroeseandGitHub 61b3ee7e01 feat: Read qcow2 virtual size from qemu-img json (#1220) 2026-07-06 23:15:27 +02:00
KroeseandGitHub 245036f844 fix: Preserve port protocols in passt mode (#1219) 2026-07-06 23:11:34 +02:00
KroeseandGitHub 68cadfe035 fix: Tolerate missing sector-size output (#1218) 2026-07-06 22:56:00 +02:00
KroeseandGitHub 96102832b1 fix: Tolerate missing sector-size output (#1217) 2026-07-06 22:38:58 +02:00
KroeseandGitHub 211a56bf73 feat: Show additional guest info in debug mode (#1216) 2026-07-06 22:11:03 +02:00
KroeseandGitHub 506fb7b114 fix: Parse Debian ipcalc output correctly (#1215) 2026-07-06 21:58:11 +02:00
KroeseandGitHub bcc3393b7f fix: Shutdown Passt after DNS failure (#1214) 2026-07-06 19:21:48 +02:00
KroeseandGitHub 45c0f91d6e feat: Refactor networking code (#1213) 2026-07-06 17:47:29 +02:00
KroeseandGitHub a1910546a6 feat: Check file operation results (#1212) 2026-07-06 07:44:41 +02:00
KroeseandGitHub 637ed0e3a6 fix: Do not match multiple files (#1211) 2026-07-06 05:09:29 +02:00
KroeseandGitHub 954a6076ad feat: Add returnvalues to functions (#1210) 2026-07-06 03:48:39 +02:00
KroeseandGitHub 415aaa3038 feat: Refactor server code (#1209) 2026-07-05 23:35:56 +02:00
KroeseandGitHub c87e45719b feat: Refactor serial code (#1208) 2026-07-05 23:24:43 +02:00
KroeseandGitHub 102945ab48 feat: Refactor memory check (#1207) 2026-07-05 23:17:53 +02:00
KroeseandGitHub 1c3d490303 feat: Refactor status monitor (#1206) 2026-07-05 23:02:51 +02:00
KroeseandGitHub 73fa70f52f feat: Refactor CPU logic (#1205) 2026-07-05 22:46:39 +02:00
KroeseandGitHub 7b095702fc feat: Refactor shutdown logic (#1204) 2026-07-05 22:09:49 +02:00
KroeseandGitHub f82b2769d3 feat: Refactor entrypoint (#1203) 2026-07-05 21:43:10 +02:00
Peng LiuandGitHub cafe619028 fix: Parse quoted values consistently (#1202) 2026-07-05 21:25:33 +02:00
KroeseandGitHub e94a0c4a52 feat: Refactor disk code (#1201) 2026-07-05 20:09:47 +02:00
KroeseandGitHub d6a353df0e feat: Refactor network code (#1200) 2026-07-05 19:58:36 +02:00
KroeseandGitHub 050812e996 fix: Filter IPv6 DNS records when disabled (#1199) 2026-07-05 19:30:41 +02:00
KroeseandGitHub 75280e9aa9 feat: Improve bus detection (#1198) 2026-07-05 18:51:56 +02:00
KroeseandGitHub 8dcc8ccee1 fix: Move MTU check to finish (#1197) 2026-07-05 18:34:55 +02:00
KroeseandGitHub ba866e9e75 feat: Improve support for non-standard MTU sizes (#1196) 2026-07-05 17:10:59 +02:00
KroeseandGitHub 06e7a8edb9 docs: Readme (#1195) 2026-07-05 14:49:47 +02:00
KroeseandGitHub 495c67734a fix: Check adapter before setting MTU (#1194) 2026-07-05 03:13:14 +02:00
KroeseandGitHub 1557735331 build: Use heredoc for Dockerfile commands (#1193) 2026-07-04 20:34:32 +02:00
18 changed files with 2806 additions and 1097 deletions
+58 -42
View File
@@ -12,47 +12,63 @@ ARG DEBCONF_NOWARNINGS="yes"
ARG DEBIAN_FRONTEND="noninteractive" ARG DEBIAN_FRONTEND="noninteractive"
ARG DEBCONF_NONINTERACTIVE_SEEN="true" ARG DEBCONF_NONINTERACTIVE_SEEN="true"
RUN set -eu && \ RUN <<EOF
apt-get update && \ set -eu
apt-get --no-install-recommends -y install \
jq \ apt-get update
tini \ apt-get --no-install-recommends -y install \
curl \ jq \
wget \ tini \
fdisk \ curl \
unzip \ wget \
nginx \ fdisk \
procps \ unzip \
ethtool \ nginx \
python3 \ procps \
python3-pip \ ipcalc \
python3-msgpack \ ethtool \
python3-pysodium \ python3 \
xz-utils \ python3-pip \
iptables \ python3-msgpack \
iproute2 \ python3-pysodium \
dnsmasq \ xz-utils \
fakeroot \ iptables \
apt-utils \ iproute2 \
net-tools \ dnsmasq \
e2fsprogs \ fakeroot \
qemu-utils \ apt-utils \
websocketd \ net-tools \
iputils-ping \ e2fsprogs \
inotify-tools \ qemu-utils \
ca-certificates \ websocketd \
netcat-openbsd \ iputils-ping \
qemu-system-x86 && \ inotify-tools \
wget "https://github.com/qemus/passt/releases/download/v${VERSION_PASST}/passt_${VERSION_PASST}_${TARGETARCH}.deb" -O /tmp/passt.deb -q --timeout=10 && \ ca-certificates \
dpkg -i /tmp/passt.deb && \ netcat-openbsd \
apt-get clean && \ qemu-system-x86
pip3 install --no-cache-dir --break-system-packages --root-user-action=ignore dissect.cstruct && \
mkdir -p /etc/qemu && \ # Install Passt package
echo "allow br0" > /etc/qemu/bridge.conf && \ wget "https://github.com/qemus/passt/releases/download/v${VERSION_PASST}/passt_${VERSION_PASST}_${TARGETARCH}.deb" -O /tmp/passt.deb -q --timeout=10
unlink /etc/nginx/sites-enabled/default && \ dpkg -i /tmp/passt.deb
sed -i 's/^worker_processes.*/worker_processes 1;/' /etc/nginx/nginx.conf && \
echo "$VERSION_ARG" > /etc/version && \ apt-get clean
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
# 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/
@@ -67,6 +83,6 @@ ENV RAM_SIZE="2G"
ENV CPU_CORES="2" ENV CPU_CORES="2"
ENV DISK_SIZE="256G" ENV DISK_SIZE="256G"
HEALTHCHECK --interval=60s --start-period=45s --retries=2 CMD /run/check.sh HEALTHCHECK --interval=60s --start-period=45s --retries=2 CMD ["/run/check.sh"]
ENTRYPOINT ["/usr/bin/tini", "-s", "/run/entry.sh"] ENTRYPOINT ["/usr/bin/tini", "-s", "/run/entry.sh"]
+93
View File
@@ -0,0 +1,93 @@
# Environment Variables
This page lists all the environment variables that can be used to configure the container.
An empty default means the variable is unset and its value is determined automatically when applicable.
## 💽 Virtual DSM
| Variable | Default | Description |
|---|---|---|
| `URL` | | URL or local path to the DSM `.pat` installation file. Downloads the default Virtual DSM image automatically when unset. |
| `HOST_MAC` | | MAC address reported to DSM. |
| `HOST_MODEL` | | Synology host model reported to DSM. |
| `HOST_SERIAL` | | Synology host serial number reported to DSM. |
| `GUEST_SERIAL` | | Synology guest serial number reported to DSM. |
## 🧠 CPU and Memory
| Variable | Default | Description |
|---|---|---|
| `CPU_CORES` | `2` | Number of virtual CPU cores, such as `4`, `half`, or `max`. |
| `CPU_MODEL` | `host` | QEMU CPU model. |
| `CPU_FLAGS` | | Additional QEMU CPU flags. |
| `HOST_CPU` | | CPU name reported to DSM. Selected automatically when unset. |
| `KVM` | `Y` | Enables KVM hardware acceleration. |
| `RAM_SIZE` | `2G` | Amount of RAM assigned to DSM, such as `2G`, `4G`, `half`, or `max`. |
| `RAM_CHECK` | `Y` | Checks whether enough host memory is available before starting DSM. |
## 💾 Storage
| Variable | Default | Description |
|---|---|---|
| `DISK_SIZE` | `256G` | Size of the main data disk. |
| `DISK_FMT` | `raw` | Disk image format: `raw` or `qcow2`. |
| `DISK_TYPE` | `scsi` | Disk device type, such as `sata`, `scsi`, `nvme`, or `blk`. |
| `DISK_CACHE` | `none` | Disk cache mode, such as `none` or `writeback`. |
| `DISK_IO` | `native` | Disk I/O mode, such as `native`, `threads`, or `io_uring`. |
| `DISK_DISCARD` | `unmap` | Discard/TRIM mode for the primary disk. |
| `DISK_ROTATION` | `1` | Rotation rate reported to the guest. Use `1` to identify the disk as an SSD. |
| `DISK_FLAGS` | | Additional options used when creating `qcow2` disks. |
| `ALLOCATE` | `N` | Preallocates space for the data disks. |
| `STORAGE` | `/storage` | Storage directory used for disks, settings, and downloads. |
## 🌐 Networking
| Variable | Default | Description |
|---|---|---|
| `NETWORK` | | Network mode, such as `nat`, `passt`, `slirp`, or `N` to disable networking. |
| `DHCP` | `N` | Enables macvtap networking so DSM receives an address from the external LAN through DHCP. |
| `HOST` | `VirtualDSM` | Hostname assigned to DSM. |
| `IP` | | Overrides the automatically selected guest IPv4 address. |
| `MAC` | | Guest network adapter MAC address. |
| `ADAPTER` | `virtio-net-pci` | QEMU network adapter model. |
| `DEV` | `eth0` | Container network interface used as the uplink. |
| `MTU` | | MTU assigned to the guest network interface. |
| `MASK` | `255.255.255.0` | IPv4 netmask. |
| `TAP` | `dsm` | TAP or macvtap interface name. |
| `BRIDGE` | `docker` | Bridge name used for NAT networking. |
| `HOST_PORTS` | | Ports excluded from guest forwarding. |
| `USER_PORTS` | | Additional ports to forward to DSM when using user-mode networking. |
| `DNSMASQ_OPTS` | | Additional options passed to dnsmasq. |
| `DNSMASQ_DEBUG` | `N` | Enables dnsmasq debug output. |
| `DNSMASQ_DISABLE` | `N` | Disables the internal dnsmasq resolver. |
| `PASST_OPTS` | | Additional options passed to passt. |
| `PASST_DEBUG` | `N` | Enables passt debug output. |
## 🖥️ Display
| Variable | Default | Description |
|---|---|---|
| `DISPLAY` | `none` | Display backend, such as `vnc`, `disabled`, or `none`. |
| `VGA` | `none` | QEMU video adapter model. |
| `GPU` | `N` | Enables Intel iGPU acceleration. |
| `RENDERNODE` | `/dev/dri/renderD128` | Render node used for GPU acceleration. |
## 🔌 Shutdown
| Variable | Default | Description |
|---|---|---|
| `SHUTDOWN` | `Y` | Enables graceful shutdown. |
| `TIMEOUT` | `115` | Maximum time, in seconds, to wait before forcing DSM to stop. |
| `API_TIMEOUT` | `90` | Maximum time, in seconds, to wait for the shutdown API call. |
## 🐞 Debugging
| Variable | Default | Description |
|---|---|---|
| `DEBUG` | `N` | Enables verbose debug output. |
| `TRACE` | `N` | Enables shell command tracing. |
| `COM_PORT` | `2210` | Internal communication port used by the DSM host helper. |
| `CHR_PORT` | `12345` | Internal character device port used by the DSM host helper. |
| `HOST_DEBUG` | `N` | Enables debug output for the DSM host helper. |
| `ARGUMENTS` | | Additional raw arguments appended to the QEMU command line. |
+48 -36
View File
@@ -16,9 +16,13 @@ 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
- Automatic download - Web-based access to the DSM desktop
- Near-native performance with KVM acceleration
- Customizable CPU, memory, and storage allocation
- Supports multiple disks and physical disk passthrough
- Supports NAT, user-mode, macvlan, and macvtap networking
## Usage 🐳 ## Usage 🐳
@@ -62,12 +66,13 @@ kubectl apply -f https://raw.githubusercontent.com/vdsm/virtual-dsm/refs/heads/m
## Requirements ⚙️ ## Requirements ⚙️
- A Linux host with KVM support, or Docker Desktop / Podman on Windows 11 with nested virtualization enabled. - Docker or Podman on a Linux host with KVM support.
- At least 2 GB of RAM available. - Docker Desktop or Podman (Desktop) on Windows 11 with nested virtualization enabled.
- At least 32 GB of free disk space. - At least 2 GB of available RAM.
- At least 32 GB of free disk space.
> [!NOTE] > [!NOTE]
> Docker Desktop on macOS and Windows 10 do not currently provide the required KVM support for this image. > Docker Desktop on Linux, macOS, and Windows 10 does not currently provide KVM access to containers and is therefore not supported.
## FAQ 💬 ## FAQ 💬
@@ -79,7 +84,7 @@ kubectl apply -f https://raw.githubusercontent.com/vdsm/virtual-dsm/refs/heads/m
- Wait until DSM finishes its installation - Wait until DSM finishes its installation
- Choose an username and password, and you will be taken to the desktop. - Choose a username and password, and you will be taken to the desktop.
Enjoy your brand new NAS, and don't forget to star this repo! Enjoy your brand new NAS, and don't forget to star this repo!
@@ -143,34 +148,6 @@ kubectl apply -f https://raw.githubusercontent.com/vdsm/virtual-dsm/refs/heads/m
CPU_CORES: "4" CPU_CORES: "4"
``` ```
### How do I verify if my system supports KVM?
First check if your software is compatible using this chart:
| **Product** | **Linux** | **Win11** | **Win10** | **macOS** |
|---|---|---|---|---|
| Docker CLI | ✅ | ✅ | ❌ | ❌ |
| Docker Desktop | ❌ | ✅ | ❌ | ❌ |
| Podman CLI | ✅ | ✅ | ❌ | ❌ |
| Podman Desktop | ✅ | ✅ | ❌ | ❌ |
After that you can run the following commands in Linux to check your system:
```bash
sudo apt install cpu-checker
sudo kvm-ok
```
If you receive an error from `kvm-ok` indicating that KVM cannot be used, please check whether:
- the virtualization extensions (`Intel VT-x` or `AMD SVM`) are enabled in your BIOS.
- you enabled "nested virtualization" if you are running the container inside a virtual machine.
- you are not using a cloud provider, as most of them do not allow nested virtualization for their VPSs.
If you did not receive any error from `kvm-ok` but the container still complains about a missing KVM device, it could help to add `privileged: true` to your compose file (or `sudo` to your `docker` command) to rule out any permission issue.
### How do I assign an individual IP address to the container? ### How do I assign an individual IP address to the container?
By default, the container uses bridge networking, which shares the IP address with the host. By default, the container uses bridge networking, which shares the IP address with the host.
@@ -257,6 +234,41 @@ kubectl apply -f https://raw.githubusercontent.com/vdsm/virtual-dsm/refs/heads/m
Replace the example path `./DSM_VirtualDSM_42218.pat` with the filename of your desired `.pat` file. The value of `URL` will be ignored in this case. Replace the example path `./DSM_VirtualDSM_42218.pat` with the filename of your desired `.pat` file. The value of `URL` will be ignored in this case.
### Are these all available options?
No. For a complete overview of all supported settings, see the [environment variables](docs/environment.md) page.
### How do I verify that KVM is available?
First, make sure your platform and container runtime meet the [requirements](#requirements-) listed above.
On a Linux host, install `cpu-checker` and run:
```bash
sudo apt install cpu-checker
sudo kvm-ok
```
A working configuration should report:
```text
KVM acceleration can be used
```
You can also verify that the KVM device exists:
```bash
ls -l /dev/kvm
```
If KVM is unavailable, check whether:
- Hardware virtualization (`Intel VT-x` or `AMD-V`) is enabled in your BIOS or UEFI.
- Nested virtualization is enabled when the host itself is a virtual machine.
- Your VPS or cloud provider supports nested virtualization.
If `kvm-ok` succeeds but the container still reports that KVM is unavailable, you can temporarily add `privileged: true` to your Compose file to rule out a permission or device-access issue.
### What are the differences compared to the standard DSM? ### What are the differences compared to the standard DSM?
There are only two minor differences: the Virtual Machine Manager package is not available, and Surveillance Station will not include any free licenses. There are only two minor differences: the Virtual Machine Manager package is not available, and Surveillance Station will not include any free licenses.
+5 -2
View File
@@ -1,12 +1,15 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -Eeuo pipefail set -Eeuo pipefail
cd /run
. utils.sh # Load functions
: "${DHCP:="N"}" : "${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"
@@ -18,7 +21,7 @@ location=$(<"$file")
if ! curl -m 20 -ILfSs "http://$location/" > /dev/null; then if ! curl -m 20 -ILfSs "http://$location/" > /dev/null; then
if [[ "$DHCP" == [Yy1]* ]]; then if enabled "$DHCP"; then
ip=$(<"$address") ip=$(<"$address")
echo "Failed to reach DSM at http://$location" echo "Failed to reach DSM at http://$location"
else else
+242 -155
View File
@@ -8,7 +8,7 @@ set -Eeuo pipefail
: "${DISK_TYPE:=""}" # Device type to be used, "sata", "nvme", "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 # Sanitize all variables
@@ -27,26 +27,20 @@ SYSTEM="$STORAGE/$BASE.system.img"
[ ! -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 if ! setOwner "$BOOT"; then
error "Failed to set the owner for \"$BOOT\" !" warn "failed to set the owner for \"$BOOT\" !"
fi fi
if ! setOwner "$SYSTEM"; then if ! setOwner "$SYSTEM"; then
error "Failed to set the owner for \"$SYSTEM\" !" warn "failed to set the owner for \"$SYSTEM\" !"
fi 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
} }
@@ -54,21 +48,16 @@ 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_FILE="$1"
local DISK_EXT DISK_FMT 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")
@@ -77,11 +66,19 @@ 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
} }
@@ -106,6 +103,98 @@ supportsDirect() {
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
}
baseDir() {
local path="${1%/}"
[[ -z "$path" || "$path" == "/" ]] && {
echo "/"
return 0
}
path="${path#/}"
path="${path%%/*}"
echo "/$path"
return 0
}
createDisk() { createDisk() {
local DISK_FILE="$1" local DISK_FILE="$1"
@@ -113,7 +202,7 @@ createDisk() {
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 GB FA local DATA_SIZE DIR BASE_DIR SPACE GB FA
rm -f "$DISK_FILE" rm -f "$DISK_FILE"
@@ -123,11 +212,16 @@ createDisk() {
# Check free diskspace # Check free diskspace
DIR=$(dirname "$DISK_FILE") DIR=$(dirname "$DISK_FILE")
SPACE=$(df --output=avail -B 1 "$DIR" | tail -n 1) BASE_DIR=$(baseDir "$DIR")
if ! SPACE=$(df --output=avail -B 1 "$DIR" | tail -n 1); then
error "Failed to check free space in $BASE_DIR."
exit 76
fi
if (( DATA_SIZE > SPACE )); then if (( DATA_SIZE > SPACE )); then
GB=$(formatBytes "$SPACE") GB=$(formatBytes "$SPACE")
error "Not enough free space to create a $DISK_DESC of ${DISK_SPACE/G/ GB} in $DIR, it has only $GB available..." error "Not enough free space to create a $DISK_DESC of ${DISK_SPACE/G/ GB} in $BASE_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
@@ -148,33 +242,15 @@ createDisk() {
{ chattr +C "$DISK_FILE"; } || : { chattr +C "$DISK_FILE"; } || :
fi fi
if disabled "$ALLOCATE"; 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" &>/dev/null; then
if ! fallocate -l -x "$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 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"
@@ -200,9 +276,9 @@ resizeDisk() {
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 GB local CUR_SIZE DATA_SIZE DIR BASE_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
@@ -211,11 +287,16 @@ resizeDisk() {
# Check free diskspace # Check free diskspace
DIR=$(dirname "$DISK_FILE") DIR=$(dirname "$DISK_FILE")
SPACE=$(df --output=avail -B 1 "$DIR" | tail -n 1) BASE_DIR=$(baseDir "$DIR")
if ! SPACE=$(df --output=avail -B 1 "$DIR" | tail -n 1); then
error "Failed to check free space in $BASE_DIR."
exit 76
fi
if (( REQ > SPACE )); then if (( REQ > SPACE )); then
GB=$(formatBytes "$SPACE") GB=$(formatBytes "$SPACE")
error "Not enough free space to resize $DISK_DESC to ${DISK_SPACE/G/ GB} in $DIR, it has only $GB available.." error "Not enough free space to resize $DISK_DESC to ${DISK_SPACE/G/ GB} in $BASE_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
@@ -230,24 +311,8 @@ resizeDisk() {
case "${DISK_FMT,,}" in case "${DISK_FMT,,}" in
raw) raw)
if disabled "$ALLOCATE"; 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" &>/dev/null; then
if ! fallocate -l -x "$DATA_SIZE" "$DISK_FILE"; then
if ! truncate -s "$DATA_SIZE" "$DISK_FILE"; then
error "$FAIL" && exit 75
fi
fi
fi
fi fi
;; ;;
qcow2) qcow2)
@@ -278,20 +343,25 @@ convertDisk() {
local TMP_FILE="$DISK_BASE.tmp" local TMP_FILE="$DISK_BASE.tmp"
rm -f "$TMP_FILE" rm -f "$TMP_FILE"
local DIR FA local DIR BASE_DIR FA
DIR=$(dirname "$TMP_FILE") DIR=$(dirname "$TMP_FILE")
BASE_DIR=$(baseDir "$DIR")
if ! disabled "$ALLOCATE"; then if ! disabled "$ALLOCATE"; then
local CUR_SIZE SPACE GB local CUR_SIZE SPACE GB
# Check free diskspace # Check free diskspace
CUR_SIZE=$(getSize "$SOURCE_FILE") CUR_SIZE=$(getSize "$SOURCE_FILE") || exit 79
SPACE=$(df --output=avail -B 1 "$DIR" | tail -n 1)
if ! SPACE=$(df --output=avail -B 1 "$DIR" | tail -n 1); then
error "Failed to check free space in $BASE_DIR."
exit 76
fi
if (( CUR_SIZE > SPACE )); then if (( CUR_SIZE > SPACE )); then
GB=$(formatBytes "$SPACE") GB=$(formatBytes "$SPACE")
error "Not enough free space to convert $DISK_DESC to $DST_FMT in $DIR, it has only $GB available..." error "Not enough free space to convert $DISK_DESC to $DST_FMT in $BASE_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
@@ -302,26 +372,30 @@ 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 disabled "$ALLOCATE"; 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
if ! qemu-img convert -f "$SOURCE_FMT" $CONV_FLAGS -o "$DISK_PARAM" -O "$DST_FMT" -- "$SOURCE_FILE" "$TMP_FILE"; then if ! qemu-img convert -f "$SOURCE_FMT" $CONV_FLAGS -o "$DISK_PARAM" -O "$DST_FMT" -- "$SOURCE_FILE" "$TMP_FILE"; then
rm -f "$TMP_FILE" rm -f "$TMP_FILE"
error "Failed to convert $DISK_STYLE $DISK_DESC image to $DST_FMT format in $DIR, is there enough space available?" && exit 79 error "Failed to convert $DISK_STYLE $DISK_DESC image to $DST_FMT format in $BASE_DIR, is there enough space available?" && exit 79
fi fi
if [[ "$DST_FMT" == "raw" ]]; then if [[ "$DST_FMT" == "raw" ]]; then
if ! disabled "$ALLOCATE"; then if ! disabled "$ALLOCATE"; then
# Work around qemu-img bug # Work around qemu-img bug
CUR_SIZE=$(stat -c%s "$TMP_FILE") if ! CUR_SIZE=$(stat -c%s "$TMP_FILE"); then
error "Failed to determine converted image size: $TMP_FILE"
exit 79
fi
if ! fallocate -l "$CUR_SIZE" "$TMP_FILE" &>/dev/null; then if ! fallocate -l "$CUR_SIZE" "$TMP_FILE" &>/dev/null; then
if ! fallocate -l -x "$CUR_SIZE" "$TMP_FILE"; 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"
@@ -330,8 +404,15 @@ convertDisk() {
fi fi
fi fi
rm -f "$SOURCE_FILE" if ! mv "$TMP_FILE" "$DST_FILE"; then
mv "$TMP_FILE" "$DST_FILE" error "Failed to move converted $DISK_DESC image to $DST_FILE."
exit 79
fi
if ! rm -f "$SOURCE_FILE"; then
error "Failed to remove old $DISK_DESC image $SOURCE_FILE."
exit 79
fi
if isCow "$FS"; then if isCow "$FS"; then
FA=$(lsattr "$DST_FILE") FA=$(lsattr "$DST_FILE")
@@ -352,21 +433,22 @@ 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 BASE_DIR FA
DIR=$(dirname "$DISK_FILE") DIR=$(dirname "$DISK_FILE")
BASE_DIR=$(baseDir "$DIR")
[ ! -d "$DIR" ] && return 0 [ ! -d "$DIR" ] && return 0
if [[ "${FS,,}" == "overlay"* && "${ENGINE,,}" == "docker" ]]; then if [[ "${FS,,}" == "overlay"* && "${ENGINE,,}" == "docker" ]]; then
warn "the filesystem of $DIR is OverlayFS, this usually means it was binded to an invalid path!" warn "the filesystem of $BASE_DIR is OverlayFS, this usually means it was binded to an invalid path!"
fi fi
if [[ "${FS,,}" == "fuse"* ]]; then if [[ "${FS,,}" == "fuse"* ]]; then
warn "the filesystem of $DIR is FUSE, this extra layer will negatively affect performance!" warn "the filesystem of $BASE_DIR is FUSE, this extra layer will negatively affect performance!"
fi fi
if ! supportsDirect "$FS"; then if ! supportsDirect "$FS"; then
warn "the filesystem of $DIR is $FS, which does not support O_DIRECT mode, adjusting settings..." warn "the filesystem of $BASE_DIR is $FS, which does not support O_DIRECT mode, adjusting settings..."
fi fi
if isCow "$FS"; then if isCow "$FS"; then
@@ -439,7 +521,7 @@ finishDisks () {
case "${DISK_TYPE,,}" in case "${DISK_TYPE,,}" in
"blk" | "scsi" | "virtio-blk" | "virtio-scsi" ) "blk" | "scsi" | "virtio-blk" | "virtio-scsi" )
DISK_OPTS+=" -object iothread,id=io2" ;; [[ "$DISK_OPTS" != *" -object iothread,id=io2"* ]] && DISK_OPTS+=" -object iothread,id=io2" ;;
esac esac
if ! enabled "$DISK_DISABLE"; then if ! enabled "$DISK_DISABLE"; then
@@ -460,7 +542,7 @@ addDisk () {
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 SPACE GB DATA_SIZE FS PREV_FMT PREV_EXT CUR_SIZE LEFT FREE USED 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"
@@ -468,38 +550,9 @@ addDisk () {
DIR=$(dirname "$DISK_FILE") DIR=$(dirname "$DISK_FILE")
[ ! -d "$DIR" ] && return 0 [ ! -d "$DIR" ] && return 0
if [[ "${DISK_SPACE,,}" == "max" || "${DISK_SPACE,,}" == "half" ]]; then SPACE=$(normalizeSize "$DISK_SPACE" "$DISK_DESC" "$DIR")
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") 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
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 $?
@@ -526,7 +579,7 @@ addDisk () {
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
@@ -551,14 +604,17 @@ addDisk () {
if [ -f "$DISK_FILE" ] && disabled "$ALLOCATE"; then if [ -f "$DISK_FILE" ] && disabled "$ALLOCATE"; then
CUR_SIZE=$(getSize "$DISK_FILE") CUR_SIZE=$(getSize "$DISK_FILE") || exit 73
USED=$(du -sB 1 "$DISK_FILE" | cut -f1) USED=$(du -sB 1 "$DISK_FILE" | cut -f1)
FREE=$(df --output=avail -B 1 "$DIR" | tail -n 1) FREE=$(df --output=avail -B 1 "$DIR" | tail -n 1)
LEFT=$(( CUR_SIZE - USED - FREE )) LEFT=$(( CUR_SIZE - USED - FREE ))
(( LEFT < 0 )) && LEFT=0
if (( LEFT > 0 )); then if (( LEFT > 0 )); then
local GB BASE_DIR
GB=$(formatBytes "$FREE") GB=$(formatBytes "$FREE")
BASE_DIR=$(baseDir "$DIR")
LEFT=$(formatBytes "$LEFT") LEFT=$(formatBytes "$LEFT")
CUR_SIZE=$(formatBytes "$CUR_SIZE") CUR_SIZE=$(formatBytes "$CUR_SIZE")
msg="The virtual size of the ${DISK_DESC,,} is $CUR_SIZE" msg="The virtual size of the ${DISK_DESC,,} is $CUR_SIZE"
@@ -568,8 +624,8 @@ addDisk () {
msg+=" (of which $USED is used)" msg+=" (of which $USED is used)"
fi fi
info "$msg, but there is only $GB of free space remaining in $DIR now." info "$msg, but there is only $GB of free space remaining in $BASE_DIR now."
info "Please consider making at least $LEFT more space available in $DIR for future expansions." info "Please consider making at least $LEFT more space available in $BASE_DIR for future expansions."
fi fi
@@ -577,7 +633,7 @@ addDisk () {
if [ -f "$DISK_FILE" ]; then if [ -f "$DISK_FILE" ]; then
if ! setOwner "$DISK_FILE"; then if ! setOwner "$DISK_FILE"; then
error "Failed to set the owner for \"$DISK_FILE\" !" warn "failed to set the owner for \"$DISK_FILE\" !"
fi fi
fi fi
@@ -603,18 +659,22 @@ addDevice () {
# Only detect and apply sector sizes for partitions, not whole disks # 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 # Whole disk passthrough with explicit sector sizes causes DSM not to recognize the disk
if [[ "$dev_type" == "part" ]]; then if [[ "$dev_type" == "part" ]]; then
local result logical physical local result=""
result=$(fdisk -l "$DISK_DEV" | grep -m 1 -o "(logical/physical): .*" | cut -c 21-) local logical=""
logical="${result%% *}" local physical=""
physical=$(echo "$result" | grep -m 1 -o "/ .*" | cut -c 3-)
physical="${physical%% *}"
if [ -n "$physical" ]; then result=$(fdisk -l "$DISK_DEV" 2>/dev/null | grep -m 1 -o "(logical/physical): .*" | cut -c 21- || true)
if [[ "$physical" != "512" ]]; then
sectors=",logical_block_size=$logical,physical_block_size=$physical" if [ -n "$result" ]; then
fi logical="${result%% *}"
else 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" 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
fi fi
@@ -634,11 +694,45 @@ if ! enabled "$DISK_DISABLE"; then
enabled "$DEBUG" && echo "$msg" enabled "$DEBUG" && echo "$msg"
fi 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" | "sata" | "nvme" | "usb" | "scsi" | "blk" | "auto" | "none" ) ;; "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 [[ "$DISK_FLAGS" =~ [[:space:]] ]]; then
error "Invalid DISK_FLAGS value '$DISK_FLAGS', spaces are not allowed."
exit 78
fi
if [ -z "$ALLOCATE" ]; then if [ -z "$ALLOCATE" ]; then
ALLOCATE="N" ALLOCATE="N"
fi fi
@@ -684,29 +778,22 @@ 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
finishDisks finishDisks
+1 -1
View File
@@ -26,7 +26,7 @@ trap - ERR
cmd=(qemu-system-x86_64) cmd=(qemu-system-x86_64)
version=$("${cmd[@]}" --version | awk 'NR==1 { print $4 }') version=$("${cmd[@]}" --version | awk 'NR==1 { print $4 }')
info "Booting $APP using QEMU v$version..." info "Booting $APP using QEMU v$version..." && echo
if ! enabled "$SHUTDOWN"; then if ! enabled "$SHUTDOWN"; then
exec "${cmd[@]}" ${ARGS:+ $ARGS} exec "${cmd[@]}" ${ARGS:+ $ARGS}
+52 -25
View File
@@ -17,7 +17,7 @@ DIR=$(find / -maxdepth 1 -type d -iname "$FN" -print -quit)
[ ! -d "$DIR" ] && DIR=$(find "$STORAGE" -maxdepth 1 -type d -iname "$FN" -print -quit) [ ! -d "$DIR" ] && DIR=$(find "$STORAGE" -maxdepth 1 -type d -iname "$FN" -print -quit)
if [ -d "$DIR" ]; then if [ -d "$DIR" ]; then
BASE="DSM_VirtualDSM" && URL="file://$DIR" BASE="DSM_VirtualDSM" && URL="file://$DIR"
if [[ ! -s "$STORAGE/$BASE.boot.img" || ! -s "$STORAGE/$BASE.system.img" ]]; then if [[ ! -s "$STORAGE/$BASE.boot.img" || ! -s "$STORAGE/$BASE.system.img" ]]; then
error "The bind $DIR maps to a file that does not exist!" && exit 65 error "The bind $DIR maps to a file that does not exist!" && exit 65
fi fi
@@ -25,7 +25,7 @@ fi
FILE=$(find / -maxdepth 1 -type f -iname "$FN" -print -quit) FILE=$(find / -maxdepth 1 -type f -iname "$FN" -print -quit)
[ ! -s "$FILE" ] && FILE=$(find "$STORAGE" -maxdepth 1 -type f -iname "$FN" -print -quit) [ ! -s "$FILE" ] && FILE=$(find "$STORAGE" -maxdepth 1 -type f -iname "$FN" -print -quit)
[ -s "$FILE" ] && BASE="DSM_VirtualDSM" && URL="file://$FILE" [ -s "$FILE" ] && BASE="DSM_VirtualDSM" && URL="file://$FILE"
URL=$(strip "$URL") URL=$(strip "$URL")
@@ -36,7 +36,7 @@ if [ -n "$URL" ] && [ ! -s "$FILE" ] && [ ! -d "$DIR" ]; then
printf -v BASE '%b' "${BASE//%/\\x}" printf -v BASE '%b' "${BASE//%/\\x}"
BASE="${BASE//[!A-Za-z0-9._-]/_}" BASE="${BASE//[!A-Za-z0-9._-]/_}"
fi fi
if [[ "${URL,,}" != "http"* && "${URL,,}" != "file:"* ]] ; then if [[ "${URL,,}" != "http"* && "${URL,,}" != "file:"* ]]; then
[ ! -s "$STORAGE/$BASE.pat" ] && error "Invalid URL: $URL" && exit 65 [ ! -s "$STORAGE/$BASE.pat" ] && error "Invalid URL: $URL" && exit 65
URL="file://$STORAGE/$BASE.pat" URL="file://$STORAGE/$BASE.pat"
fi fi
@@ -159,20 +159,44 @@ if [[ "$URL" == "file://"* ]]; then
else else
SIZE=0 SIZE=0
REASON=""
LOG=$(mktemp)
[[ "${URL,,}" == *"_72806.pat" ]] && SIZE=361010261 [[ "${URL,,}" == *"_72806.pat" ]] && SIZE=361010261
[[ "${URL,,}" == *"_69057.pat" ]] && SIZE=363837333 [[ "${URL,,}" == *"_69057.pat" ]] && SIZE=363837333
[[ "${URL,,}" == *"_42218.pat" ]] && SIZE=379637760 [[ "${URL,,}" == *"_42218.pat" ]] && SIZE=379637760
/run/progress.sh "$PAT" "$SIZE" "$MSG ([P])..." & /run/progress.sh "$PAT" "$SIZE" "$MSG ([P])..." &
{ wget "$URL" -O "$PAT" -q --no-check-certificate --timeout=10 --no-http-keep-alive --show-progress "$PROGRESS"; rc=$?; } || : {
LC_ALL=C wget "$URL" -O "$PAT" --no-verbose --no-check-certificate \
--timeout=30 --no-http-keep-alive --show-progress "$PROGRESS" \
--output-file="$LOG"
rc=$?
} || :
fKill "progress.sh" fKill "progress.sh"
(( rc == 3 )) && error "$ERR , cannot write file (disk full?)" && exit 69 if (( rc != 0 )); then
(( rc == 4 )) && error "$ERR , network failure!" && exit 69 REASON=$(sed -n \
(( rc == 8 )) && error "$ERR , server issued an error response!" && exit 69 -e 's/^wget: //p' \
(( rc != 0 )) && error "$ERR , reason: $rc" && exit 69 -e 's/^[0-9-]\{10\} [0-9:]\{8\} ERROR //p' \
"$LOG" | tail -n 1)
fi
rm -f "$LOG"
if (( rc == 3 )); then
error "$ERR because the file could not be written (disk full?)."
exit 69
elif (( rc != 0 )); then
if [ -n "$REASON" ]; then
error "$ERR: ${REASON%.}."
else
error "$ERR with exit status $rc."
fi
exit 69
fi
fi fi
@@ -205,11 +229,12 @@ fi
MSG="Preparing system partition..." MSG="Preparing system partition..."
info "Install: $MSG" && html "$MSG" info "Install: $MSG" && html "$MSG"
BOOT=$(find "$TMP" -name "*.bin.zip") BOOT=$(find "$TMP" -name "*.bin.zip" -print -quit)
[ ! -s "$BOOT" ] && error "The PAT file contains no boot image." && exit 67 [ -z "$BOOT" ] && error "The PAT file contains no boot image." && exit 67
[ ! -s "$BOOT" ] && error "The PAT boot image archive is empty." && exit 67
BOOT=$(echo "$BOOT" | head -c -5) unzip -q -o "$BOOT" -d "$TMP"
unzip -q -o "$BOOT".zip -d "$TMP" BOOT="${BOOT%.zip}"
SYSTEM="$STORAGE/$BASE.system.img" SYSTEM="$STORAGE/$BASE.system.img"
rm -f "$SYSTEM" rm -f "$SYSTEM"
@@ -227,7 +252,7 @@ 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\" !" ! setOwner "$SYSTEM" && warn "failed to set the owner for \"$SYSTEM\" !"
if [[ "${FS,,}" == "btrfs" ]]; then if [[ "${FS,,}" == "btrfs" ]]; then
{ chattr +C "$SYSTEM"; } || : { chattr +C "$SYSTEM"; } || :
@@ -248,14 +273,15 @@ fi
PART="$TMP/partition.fdisk" PART="$TMP/partition.fdisk"
{ echo "label: dos" {
echo "label-id: 0x6f9ee2e9" echo "label: dos"
echo "device: $SYSTEM" echo "label-id: 0x6f9ee2e9"
echo "unit: sectors" echo "device: $SYSTEM"
echo "sector-size: 512" echo "unit: sectors"
echo "" echo "sector-size: 512"
echo "${SYSTEM}1 : start= 2048, size= 16777216, type=83" echo ""
echo "${SYSTEM}2 : start= 16779264, size= 4194304, type=82" echo "${SYSTEM}1 : start= 2048, size= 16777216, type=83"
echo "${SYSTEM}2 : start= 16779264, size= 4194304, type=82"
} > "$PART" } > "$PART"
sfdisk -q "$SYSTEM" < "$PART" sfdisk -q "$SYSTEM" < "$PART"
@@ -281,8 +307,9 @@ mv "$HDA.tgz" "$HDA.txz"
[ -d "$PKG" ] && mv "$PKG/" "$MOUNT/.SynoUpgradePackages/" [ -d "$PKG" ] && mv "$PKG/" "$MOUNT/.SynoUpgradePackages/"
rm -f "$MOUNT/.SynoUpgradePackages/ActiveInsight-"* rm -f "$MOUNT/.SynoUpgradePackages/ActiveInsight-"*
INDEX_DB="$MOUNT/usr/syno/synoman/indexdb"
if [ -s "$IDB.txz" ]; then if [ -s "$IDB.txz" ]; then
INDEX_DB="$MOUNT/usr/syno/synoman/indexdb"
mkdir -p "$INDEX_DB" mkdir -p "$INDEX_DB"
fi fi
@@ -300,7 +327,7 @@ fakeroot -- bash -c "set -Eeu;\
rm -rf "$MOUNT" rm -rf "$MOUNT"
echo "$BASE" > "$STORAGE/dsm.ver" echo "$BASE" > "$STORAGE/dsm.ver"
! setOwner "$STORAGE/dsm.ver" && error "Failed to set the owner for \"$STORAGE/dsm.ver\" !" ! setOwner "$STORAGE/dsm.ver" && warn "failed to set the owner for \"$STORAGE/dsm.ver\" !"
if [[ "$URL" == "file://$STORAGE/$BASE.pat" ]]; then if [[ "$URL" == "file://$STORAGE/$BASE.pat" ]]; then
rm -f "$PAT" rm -f "$PAT"
@@ -309,11 +336,11 @@ else
fi fi
if [ -f "$STORAGE/$BASE.pat" ]; then if [ -f "$STORAGE/$BASE.pat" ]; then
! setOwner "$STORAGE/$BASE.pat" && error "Failed to set the owner for \"$STORAGE/$BASE.pat\" !" ! setOwner "$STORAGE/$BASE.pat" && warn "failed to set the owner for \"$STORAGE/$BASE.pat\" !"
fi 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\" !" ! setOwner "$STORAGE/$BASE.boot.img" && warn "failed to set the owner for \"$STORAGE/$BASE.boot.img\" !"
rm -rf "$TMP" rm -rf "$TMP"
+50 -16
View File
@@ -4,10 +4,17 @@ set -Eeuo pipefail
msg="Checking memory..." msg="Checking memory..."
enabled "$DEBUG" && echo "$msg" enabled "$DEBUG" && echo "$msg"
RAM_AVAIL=$(free -b | grep -m 1 Mem: | awk '{print $7}') app() {
AVAIL_MEM=$(formatBytes "$RAM_AVAIL") echo "Virtual DSM"
return 0
}
if ! disabled "$RAM_CHECK" && [[ "${RAM_SIZE,,}" != "max" && "${RAM_SIZE,,}" != "half" ]]; then checkConfiguredMemory() {
local wanted msg
if disabled "$RAM_CHECK" || [[ "${RAM_SIZE,,}" == "max" || "${RAM_SIZE,,}" == "half" ]]; then
return 0
fi
wanted=$(numfmt --from=iec "$RAM_SIZE") wanted=$(numfmt --from=iec "$RAM_SIZE")
@@ -30,21 +37,33 @@ if ! disabled "$RAM_CHECK" && [[ "${RAM_SIZE,,}" != "max" && "${RAM_SIZE,,}" !=
fi fi
fi fi
fi return 0
}
if [[ "${RAM_SIZE,,}" == "half" ]]; then configureHalfMemory() {
local wanted
if [[ "${RAM_SIZE,,}" != "half" ]]; then
return 0
fi
if (( (RAM_AVAIL / 2) > RAM_SPARE )); then if (( (RAM_AVAIL / 2) > RAM_SPARE )); then
wanted=$(( (RAM_AVAIL / 2) / 1048577 )) wanted=$(( (RAM_AVAIL / 2) / 1048577 ))
RAM_SIZE="${wanted}M" RAM_SIZE="${wanted}M"
info "Allocated $wanted MB of RAM for the virtual machine." info "Allocated $wanted MB of RAM for $(app)."
else else
RAM_SIZE="max" RAM_SIZE="max"
fi fi
fi return 0
}
if [[ "${RAM_SIZE,,}" == "max" ]]; then configureMaxMemory() {
local wanted
if [[ "${RAM_SIZE,,}" != "max" ]]; then
return 0
fi
if (( RAM_AVAIL < (RAM_SPARE * 2) )); then if (( RAM_AVAIL < (RAM_SPARE * 2) )); then
@@ -63,16 +82,31 @@ if [[ "${RAM_SIZE,,}" == "max" ]]; then
wanted=$(( wanted / 1048577 )) wanted=$(( wanted / 1048577 ))
RAM_SIZE="${wanted}M" RAM_SIZE="${wanted}M"
info "Allocated $wanted MB of RAM for the virtual machine." info "Allocated $wanted MB of RAM for $(app)."
fi return 0
}
wanted=$(numfmt --from=iec "$RAM_SIZE") checkMinimumMemory() {
local wanted
if [ "$wanted" -lt "$RAM_MINIMUM" ]; then wanted=$(numfmt --from=iec "$RAM_SIZE")
wanted=$(( wanted / 1048577 ))
error "Not enough memory available, there is only $wanted MB left!" if [ "$wanted" -lt "$RAM_MINIMUM" ]; then
exit 16 wanted=$(( wanted / 1048577 ))
fi error "Not enough memory available, there is only $wanted MB left!"
exit 16
fi
return 0
}
getMemoryInfo
AVAIL_MEM=$(formatBytes "$RAM_AVAIL")
checkConfiguredMemory
configureHalfMemory
configureMaxMemory
checkMinimumMemory
return 0 return 0
+1299 -418
View File
File diff suppressed because it is too large Load Diff
+131 -64
View File
@@ -15,6 +15,7 @@ API_HOST="127.0.0.1:$COM_PORT"
QEMU_END="$QEMU_DIR/qemu.end" QEMU_END="$QEMU_DIR/qemu.end"
_trap() { _trap() {
local func="$1"; shift local func="$1"; shift
local sig local sig
TRAP_PID=$BASHPID TRAP_PID=$BASHPID
@@ -22,88 +23,103 @@ _trap() {
for sig; do for sig; do
trap "$func $sig" "$sig" trap "$func $sig" "$sig"
done done
return 0
} }
app() { signalCode() {
echo "$APP" && return 0
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
} }
finish() { displayReason() {
local i=0 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 pid=""
local reason=$1 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:-}" \ local pids=( "${HOST_PID:-}" "${WSD_PID:-}" \
"${WEB_PID:-}" "${PASST_PID:-}" "${DNSMASQ_PID:-}" ) "${WEB_PID:-}" "${PASST_PID:-}" "${DNSMASQ_PID:-}" )
touch "$QEMU_END"
if [ -s "$QEMU_PID" ]; then
if read -r pid <"$QEMU_PID"; then
if [ -n "$pid" ] && isAlive "$pid"; then
local display="$reason"
case "$reason" in
129 ) display="SIGHUP" ;;
130 ) display="SIGINT" ;;
131 ) display="SIGQUIT" ;;
134 ) display="SIGABRT" ;;
143 ) display="SIGTERM" ;;
esac
error "Forcefully terminating $(app), reason: $display..."
{ disown "$pid" || :; kill -9 -- "$pid" || :; } 2>/dev/null
fi
fi
fi
mKill "${pids[@]}" mKill "${pids[@]}"
fKill "print.sh" fKill "print.sh"
closeNetwork closeNetwork
return 0
}
finish() {
local reason=$1
touch "$QEMU_END"
forceKillQemu "$reason"
cleanupHelpers
if ! waitPidFile "$QEMU_PID" 10; then if ! waitPidFile "$QEMU_PID" 10; then
warn "Timed out while waiting for $(app) to exit!" warn "Timed out while waiting for $(app) to exit!"
fi fi
(( reason != 1 )) && echo && echo " Shutdown completed!" (( reason != 1 )) && echo && echo " Shutdown completed!"
exit "$reason" exit "$reason"
} }
graceful_shutdown() { sendGuestShutdown() {
local sig="$1" local pid="$1"
local pid="" local response
local code=0 local url
local start url response elapsed
[[ $BASHPID != "$TRAP_PID" ]] && return
case "$sig" in
SIGHUP) code=129 ;;
SIGINT) code=130 ;;
SIGQUIT) code=131 ;;
SIGABRT) code=134 ;;
SIGTERM) code=143 ;;
esac
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 [ ! -s "$QEMU_PID" ] || ! read -r pid <"$QEMU_PID"; then
warn "QEMU PID file ($QEMU_PID) does not exist?"
finish "$code"
fi
if [ -z "$pid" ] || ! isAlive "$pid"; then
warn "QEMU process with PID $pid does not exist?"
finish "$code"
fi
# Don't send the powerdown signal because vDSM ignores ACPI signals # Don't send the powerdown signal because vDSM ignores ACPI signals
# nc -q 1 -w 1 -U "$QEMU_DIR/monitor.sock" &> /dev/null <<<'system_powerdown' || : # nc -q 1 -w 1 -U "$QEMU_DIR/monitor.sock" &> /dev/null <<<'system_powerdown' || :
@@ -121,16 +137,22 @@ 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" 2>/dev/null || : kill -15 -- "$pid" 2>/dev/null || :
fi fi
local name return 0
name="$(app)" }
normalizeTimeout() {
local term_grace=3 # seconds before loop ends to send SIGTERM local term_grace=3 # seconds before loop ends to send SIGTERM
local cleanup_grace=3 # seconds reserved after the loop for cleanup local cleanup_grace=3 # seconds reserved after the loop for cleanup
local elapsed
local timeout_left
local min
TIMEOUT=$(strip "$TIMEOUT") TIMEOUT=$(strip "$TIMEOUT")
if [[ ! "$TIMEOUT" =~ ^[0-9]+$ ]]; then if [[ ! "$TIMEOUT" =~ ^[0-9]+$ ]]; then
@@ -145,8 +167,6 @@ graceful_shutdown() {
cleanup_grace=4 cleanup_grace=4
fi fi
local cnt=0 sigterm_at=0 min wait_until elapsed timeout_left
elapsed=$((SECONDS - start)) elapsed=$((SECONDS - start))
timeout_left=$((TIMEOUT - elapsed)) timeout_left=$((TIMEOUT - elapsed))
@@ -156,10 +176,20 @@ graceful_shutdown() {
wait_until=$((timeout_left - cleanup_grace)) wait_until=$((timeout_left - cleanup_grace))
sigterm_at=$((wait_until - term_grace)) sigterm_at=$((wait_until - term_grace))
return 0
}
waitForShutdown() {
local cnt=0
local pid="$1"
local name="$APP"
local slp
while (( cnt <= wait_until )); do while (( cnt <= wait_until )); do
sleep 1 & sleep 1 &
local slp=$! slp=$!
# Stop waiting if the process has exited # Stop waiting if the process has exited
! isAlive "$pid" && break ! isAlive "$pid" && break
@@ -179,6 +209,43 @@ graceful_shutdown() {
done done
return 0
}
graceful_shutdown() {
local sig="$1"
local pid=""
local code=0
[[ $BASHPID != "$TRAP_PID" ]] && return
code=$(signalCode "$sig")
if [ -f "$QEMU_END" ]; then
echo && info "Received $1 signal while already shutting down..."
return
fi
set +e
start=$SECONDS
touch "$QEMU_END"
echo && info "Received $1 signal, sending shutdown command..."
if ! readQemuPid pid; then
warn "QEMU PID file ($QEMU_PID) does not exist?"
finish "$code"
fi
if ! isAlive "$pid"; then
warn "QEMU process with PID $pid does not exist?"
finish "$code"
fi
sendGuestShutdown "$pid"
normalizeTimeout
waitForShutdown "$pid"
finish "$code" finish "$code"
} }
+146 -47
View File
@@ -1,16 +1,20 @@
#!/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" 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"
@@ -22,68 +26,144 @@ 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
[ -z "$ip" ] && continue local query="$1"
echo "$ip:$port" > $file local result
local rc
done { result=$(jq -r "$query" <<< "$json"); rc=$?; } || :
[ -f "$shutdown" ] && exit 1 if (( rc != 0 )); then
error "$jq_err $rc ( $json )"
return 1
fi
location=$(<"$file") if [[ "$result" == "null" ]]; then
error "$resp_err $json"
return 1
fi
if [[ "$DHCP" == [Yy1]* ]]; then printf '%s\n' "$result"
return 0
}
readGuestStatus() {
local result msg rc
result=$(readJsonField '.status') || return 1
if [[ "$result" != "success" ]]; then
{ msg=$(jq -r '.message // empty' <<< "$json"); rc=$?; } || :
if (( rc != 0 )); then
error "$jq_err $rc ( $json )"
return 1
fi
error "Guest replied $result: $msg"
return 1
fi
return 0
}
readGuestPort() {
port=$(readJsonField '.data.data.dsm_setting.data.http_port') || return 1
[ -z "$port" ] && return 1
return 0
}
readGuestIp() {
ip=$(readJsonField '.data.data.ip.data[] | select(.name=="eth0" and has("ip")) | .ip | select(test("^[0-9]+\\."))') || return 1
[ -z "$ip" ] && return 1
return 0
}
writeDsmLocation() {
echo "$ip:$port" > "$file"
return 0
}
pollGuestLocation() {
while [ ! -s "$file" ]; do
# Check if not shutting down
exitIfShuttingDown
sleep 3
exitIfShuttingDown
[ -s "$file" ] && break
# Retrieve network info from guest VM
queryGuest || continue
readGuestStatus || continue
readGuestPort || continue
readGuestIp || continue
writeDsmLocation
done
return 0
}
writeDhcpPage() {
local title body script html
msg="http://$location" msg="http://$location"
title="<title>Virtual DSM</title>" 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") nic=$(<"$driver")
ip=$(<"$address") ip=$(<"$address")
@@ -95,12 +175,31 @@ 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
+127 -70
View File
@@ -11,15 +11,27 @@ HOST_CPU=$(strip "$HOST_CPU")
CPU_FLAGS=$(strip "$CPU_FLAGS") CPU_FLAGS=$(strip "$CPU_FLAGS")
CPU_MODEL=$(strip "$CPU_MODEL") CPU_MODEL=$(strip "$CPU_MODEL")
CLOCKSOURCE="tsc" selectClocksource() {
[[ "${ARCH,,}" == "arm64" ]] && CLOCKSOURCE="arch_sys_counter"
CLOCK="/sys/devices/system/clocksource/clocksource0/current_clocksource" CLOCKSOURCE="tsc"
[[ "${ARCH,,}" == "arm64" ]] && CLOCKSOURCE="arch_sys_counter"
CLOCK="/sys/devices/system/clocksource/clocksource0/current_clocksource"
return 0
}
checkClocksource() {
local result
if [ ! -f "$CLOCK" ]; then
warn "file \"$CLOCK\" cannot be found?"
return 0
fi
if [ ! -f "$CLOCK" ]; then
warn "file \"$CLOCK\" cannot be found?"
else
result=$(<"$CLOCK") result=$(<"$CLOCK")
result="${result//[![:print:]]/}" result="${result//[![:print:]]/}"
case "${result,,}" in case "${result,,}" in
"${CLOCKSOURCE,,}" ) ;; "${CLOCKSOURCE,,}" ) ;;
"kvm-clock" ) info "Nested KVM virtualization detected.." ;; "kvm-clock" ) info "Nested KVM virtualization detected.." ;;
@@ -27,29 +39,64 @@ else
"hpet" ) warn "unsupported clock source detected: '$result'. Please set host clock source to '$CLOCKSOURCE'." ;; "hpet" ) warn "unsupported clock source detected: '$result'. Please set host clock source to '$CLOCKSOURCE'." ;;
*) warn "unexpected clock source detected: '$result'. Please set host clock source to '$CLOCKSOURCE'." ;; *) warn "unexpected clock source detected: '$result'. Please set host clock source to '$CLOCKSOURCE'." ;;
esac esac
fi
flags=$(sed -ne '/^flags/s/^.*: //p' /proc/cpuinfo) return 0
}
if ! disabled "$KVM"; then checkSse42() {
CPU_FEATURES="kvm=on,l3-cache=on,+hypervisor"
KVM_OPTS=",accel=kvm -enable-kvm -global kvm-pit.lost_tick_policy=discard"
if ! grep -qw "sse4_2" <<< "$flags"; then if ! grep -qw "sse4_2" <<< "$flags"; then
error "Your CPU does not have the SSE4 instruction set that Virtual DSM requires!" error "Your CPU does not have the SSE4 instruction set that Virtual DSM requires!"
! enabled "$DEBUG" && exit 88 ! enabled "$DEBUG" && exit 88
fi fi
return 0
}
trimSpaces() {
local value="$1"
value="${value#"${value%%[![:space:]]*}"}"
value="${value%"${value##*[![:space:]]}"}"
echo "$value"
return 0
}
removeCpuArgument() {
local args=" ${ARGUMENTS:-} "
while [[ "$args" =~ [[:space:]]-cpu([[:space:]][^[:space:]]+|=[^[:space:]]+)? ]]; do
local cpu="${BASH_REMATCH[0]}"
args="${args/$cpu/ }"
warn "Ignoring '${cpu#" "}' from ARGUMENTS, use CPU_MODEL and CPU_FLAGS instead."
done
ARGUMENTS=$(trimSpaces "$args")
return 0
}
configureKvmCpuModel() {
CPU_FEATURES="kvm=on,l3-cache=on,+hypervisor"
KVM_OPTS=",accel=kvm -enable-kvm -global kvm-pit.lost_tick_policy=discard"
if [ -z "$CPU_MODEL" ]; then if [ -z "$CPU_MODEL" ]; then
CPU_MODEL="host" CPU_MODEL="host"
CPU_FEATURES+=",migratable=no" CPU_FEATURES+=",migratable=no"
fi fi
return 0
}
appendKvmInvtscFeature() {
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
@@ -57,7 +104,7 @@ if ! disabled "$KVM"; 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
@@ -66,7 +113,35 @@ if ! disabled "$KVM"; 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"
@@ -75,70 +150,52 @@ else
KVM_OPTS=" -accel tcg,thread=multi" KVM_OPTS=" -accel tcg,thread=multi"
fi fi
if [ -z "$CPU_MODEL" ]; then configureTcgCpuModel
if [[ "$ARCH" == "amd64" ]]; then CPU_FEATURES+=",+ssse3,+sse4.1,+sse4.2"
CPU_MODEL="max"
CPU_FEATURES+=",migratable=no" return 0
}
composeCpuFlags() {
CPU_FLAGS="${CPU_MODEL}${CPU_FEATURES:+,$CPU_FEATURES}${CPU_FLAGS:+,$CPU_FLAGS}"
return 0
}
configureHostCpuName() {
if [ -z "$HOST_CPU" ]; then
[[ "${CPU,,}" != "unknown" ]] && HOST_CPU="$CPU"
fi
if [ -n "$HOST_CPU" ]; then
HOST_CPU="${HOST_CPU%%,*},,"
else
HOST_CPU="QEMU, Virtual CPU,"
if [ "$ARCH" == "amd64" ]; then
HOST_CPU+=" X86_64"
else else
CPU_MODEL="qemu64" HOST_CPU+=" $ARCH"
fi fi
fi fi
CPU_FEATURES+=",+ssse3,+sse4.1,+sse4.2" return 0
}
fi selectClocksource
checkClocksource
if [[ "$ARGUMENTS" == *"-cpu host,"* ]]; then flags=$(sed -ne '/^flags/s/^.*: //p' /proc/cpuinfo)
args="${ARGUMENTS} "
prefix="${args/-cpu host,*/}"
suffix="${args/*-cpu host,/}"
param="${suffix%% *}"
suffix="${suffix#* }"
args="${prefix}${suffix}"
ARGUMENTS="${args::-1}"
if [ -z "$CPU_FLAGS" ]; then
CPU_FLAGS="$param"
else
CPU_FLAGS+=",$param"
fi
if ! disabled "$KVM"; then
configureKvm
else else
configureTcg
if [[ "$ARGUMENTS" == *"-cpu host"* ]]; then
ARGUMENTS="${ARGUMENTS//-cpu host/}"
fi
fi fi
if [ -z "$CPU_FLAGS" ]; then removeCpuArgument
if [ -z "$CPU_FEATURES" ]; then composeCpuFlags
CPU_FLAGS="$CPU_MODEL" configureHostCpuName
else
CPU_FLAGS="$CPU_MODEL,$CPU_FEATURES"
fi
else
if [ -z "$CPU_FEATURES" ]; then
CPU_FLAGS="$CPU_MODEL,$CPU_FLAGS"
else
CPU_FLAGS="$CPU_MODEL,$CPU_FEATURES,$CPU_FLAGS"
fi
fi
if [ -z "$HOST_CPU" ]; then
[[ "${CPU,,}" != "unknown" ]] && HOST_CPU="$CPU"
fi
if [ -n "$HOST_CPU" ]; then
HOST_CPU="${HOST_CPU%%,*},,"
else
HOST_CPU="QEMU, Virtual CPU,"
if [ "$ARCH" == "amd64" ]; then
HOST_CPU+=" X86_64"
else
HOST_CPU+=" $ARCH"
fi
fi
return 0 return 0
+2 -2
View File
@@ -27,12 +27,12 @@ do
if [ ! -s "$path" ] && [ ! -d "$path" ]; then if [ ! -s "$path" ] && [ ! -d "$path" ]; then
bytes="0" bytes="0"
else else
bytes=$(du -sb "$path" | cut -f1) bytes=$(du -sb "$path" 2>/dev/null | cut -f1) || bytes="0"
fi fi
if (( bytes > 4096 )); then if (( bytes > 4096 )); then
if [ -z "$total" ] || [[ "$total" == "0" ]] || [ "$bytes" -gt "$total" ]; then if [ -z "$total" ] || [[ "$total" == "0" ]] || [ "$bytes" -gt "$total" ]; then
size=$(numfmt --to=iec --suffix=B "$bytes" | sed -r 's/([A-Z])/ \1/') size=$(numfmt --to=iec --suffix=B "$bytes" | sed -r 's/([A-Z])/ \1/') || size="${bytes} bytes"
else else
size="$(echo "$bytes" "$total" | awk '{printf "%.1f", $1 * 100 / $2}')" size="$(echo "$bytes" "$total" | awk '{printf "%.1f", $1 * 100 / $2}')"
size="$size%" size="$size%"
+278 -156
View File
@@ -21,6 +21,275 @@ enabled "${TRACE:-}" && set -o functrace && trap 'echo "# $BASH_COMMAND" >&2' DE
: "${DISK_SIZE:="16G"}" # Initial data disk size : "${DISK_SIZE:="16G"}" # Initial data disk size
: "${STORAGE:="/storage"}" # Storage folder location : "${STORAGE:="/storage"}" # Storage folder location
detectEngine() {
if [ -f "/run/.containerenv" ]; then
ENGINE="${container:-}"
if [[ "${ENGINE,,}" == *"podman"* ]]; then
ENGINE="Podman"
else
[ -z "$ENGINE" ] && ENGINE="Kubernetes"
fi
elif [ -f "/.dockerenv" ]; then
ENGINE="Docker"
fi
return 0
}
detectRootless() {
local uid_map=""
uid_map=$(awk '{$1=$1; print}' /proc/self/uid_map 2>/dev/null || true)
if [[ "$uid_map" == "0 0 4294967295" ]]; then
ROOTLESS="N"
else
ROOTLESS="Y"
fi
return 0
}
checkPrivileged() {
local cap_bnd
local last_cap
local max_cap
# Get the capability bounding set
cap_bnd=$(grep '^CapBnd:' /proc/$$/status | awk '{print $2}')
cap_bnd=$(printf "%d" "0x${cap_bnd}")
# Get the last capability number
last_cap=$(cat /proc/sys/kernel/cap_last_cap)
# Calculate the maximum capability value
max_cap=$(((1 << (last_cap + 1)) - 1))
if [ "$cap_bnd" -eq "$max_cap" ]; then
PRIVILEGED="Y"
fi
return 0
}
normalizeCpuCores() {
CPU_CORES=$(strip "$CPU_CORES")
[ -z "$CPU_CORES" ] && CPU_CORES=2
[[ "${CPU_CORES,,}" == "max" ]] && CPU_CORES="$CORES"
[[ "${CPU_CORES,,}" == "half" ]] && CPU_CORES=$(( CORES / 2 ))
[ -z "${CPU_CORES##*[!0-9]*}" ] && error "Invalid amount of CPU_CORES: $CPU_CORES" && exit 15
[ "$CPU_CORES" -lt "1" ] && CPU_CORES=1
if [ "$CPU_CORES" -gt "$CORES" ]; then
warn "The amount for CPU_CORES (${CPU_CORES}) exceeds the amount of logical cores available (${CORES}) and will be limited."
CPU_CORES="$CORES"
fi
return 0
}
checkStorage() {
# Check system
QEMU_DIR="/run/shm"
if [ ! -d "/dev/shm" ]; then
error "Directory /dev/shm not found!" && exit 14
else
[ ! -d "$QEMU_DIR" ] && ln -s /dev/shm "$QEMU_DIR"
fi
QEMU_PID="$QEMU_DIR/qemu.pid"
# Check folder
if [[ "${STORAGE,,}" != "/storage" ]]; then
mkdir -p "$STORAGE"
fi
if [ ! -d "$STORAGE" ]; then
error "Storage folder ($STORAGE) not found!" && exit 13
fi
if [ ! -w "$STORAGE" ]; then
msg="Storage folder ($STORAGE) is not writeable!"
msg+=" If SELinux is active, you need to add the \":Z\" flag to the bind mount."
error "$msg" && exit 13
fi
return 0
}
checkFilesystem() {
# Check filesystem
FS=$(stat -f -c %T "$STORAGE")
if [[ "${FS,,}" == "ecryptfs" || "${FS,,}" == "tmpfs" ]]; then
DISK_IO="threads"
DISK_CACHE="writeback"
fi
return 0
}
finiteMemoryLimit() {
local limit="$1"
local sentinel="4611686018427387904"
local i=0
local left=""
local right=""
[[ "$limit" =~ ^[0-9]+$ ]] || return 1
(( ${#limit} < ${#sentinel} )) && return 0
(( ${#limit} > ${#sentinel} )) && return 1
for (( i=0; i<${#sentinel}; i++ )); do
left="${limit:i:1}"
right="${sentinel:i:1}"
(( left < right )) && return 0
(( left > right )) && return 1
done
return 1
}
getMemoryInfo() {
local host_total=""
local host_avail=""
local limit=""
local current=""
local available=""
host_total=$(free -b | awk '/^Mem:/ {print $2; exit}')
host_avail=$(free -b | awk '/^Mem:/ {print $7; exit}')
RAM_TOTAL="$host_total"
RAM_AVAIL="$host_avail"
if [ -r /sys/fs/cgroup/memory.max ] && [ -r /sys/fs/cgroup/memory.current ]; then
limit=$(< /sys/fs/cgroup/memory.max)
current=$(< /sys/fs/cgroup/memory.current)
elif [ -r /sys/fs/cgroup/memory/memory.limit_in_bytes ] && [ -r /sys/fs/cgroup/memory/memory.usage_in_bytes ]; then
limit=$(< /sys/fs/cgroup/memory/memory.limit_in_bytes)
current=$(< /sys/fs/cgroup/memory/memory.usage_in_bytes)
fi
if finiteMemoryLimit "$limit" && [[ "$current" =~ ^[0-9]+$ ]]; then
(( limit < RAM_TOTAL )) && RAM_TOTAL="$limit"
available=$(( limit - current ))
(( available < 0 )) && available=0
(( available < RAM_AVAIL )) && RAM_AVAIL="$available"
fi
return 0
}
normalizeRamSize() {
# Read host and container memory limits.
getMemoryInfo
RAM_SPARE=500000000
RAM_MINIMUM=136314880
RAM_SIZE=$(strip "$RAM_SIZE")
RAM_SIZE="${RAM_SIZE// /}"
[ -z "$RAM_SIZE" ] && RAM_SIZE="2G"
if [[ "${RAM_SIZE,,}" != "max" && "${RAM_SIZE,,}" != "half" ]]; then
if [ -z "${RAM_SIZE//[0-9. ]}" ]; then
[ "${RAM_SIZE%%.*}" -lt "130" ] && RAM_SIZE="${RAM_SIZE}G" || RAM_SIZE="${RAM_SIZE}M"
fi
RAM_SIZE=$(echo "${RAM_SIZE^^}" | sed 's/MB/M/g;s/GB/G/g;s/TB/T/g')
! numfmt --from=iec "$RAM_SIZE" &>/dev/null && error "Invalid RAM_SIZE: $RAM_SIZE" && exit 16
wanted=$(numfmt --from=iec "$RAM_SIZE")
[ "$wanted" -lt "$RAM_MINIMUM" ] && error "RAM_SIZE is too low: $RAM_SIZE" && exit 16
fi
return 0
}
checkKvm() {
# Check KVM support
if [[ "${PLATFORM,,}" == "x64" ]]; then
TARGET="amd64"
else
TARGET="arm64"
fi
if disabled "$KVM"; then
warn "KVM acceleration is disabled, this will cause the machine to run about 10 times slower!"
else
if [[ "${ARCH,,}" != "$TARGET" ]]; then
KVM="N"
warn "your CPU architecture is ${ARCH^^} and cannot provide KVM acceleration for ${PLATFORM^^} instructions, so the machine will run about 10 times slower."
fi
fi
if ! disabled "$KVM"; then
KVM_ERR=""
if [ ! -e /dev/kvm ]; then
KVM_ERR="(/dev/kvm is missing)"
else
if ! sh -c 'echo -n > /dev/kvm' &> /dev/null; then
KVM_ERR="(/dev/kvm is unwriteable)"
else
if [[ "${PLATFORM,,}" == "x64" ]]; then
flags=$(sed -ne '/^flags/s/^.*: //p' /proc/cpuinfo)
if ! grep -qw "vmx\|svm" <<< "$flags"; then
KVM_ERR="(not enabled in BIOS)"
fi
if ! grep -qw "sse4_2" <<< "$flags"; then
error "Your CPU does not have the SSE4 instruction set that Virtual DSM requires!"
! enabled "$DEBUG" && exit 88
fi
fi
fi
fi
if [ -n "$KVM_ERR" ]; then
KVM="N"
if [[ "$OSTYPE" =~ ^darwin ]]; then
warn "you are using macOS which has no KVM support, so the machine will run about 10 times slower."
else
kernel=$(uname -a)
case "${kernel,,}" in
*"microsoft"* )
error "Please bind '/dev/kvm' as a volume in the optional container settings when using Docker Desktop." ;;
*"synology"* )
error "Please make sure that Synology VMM (Virtual Machine Manager) is installed and that '/dev/kvm' is binded to this container." ;;
*)
error "KVM acceleration is not available $KVM_ERR, this will cause the machine to run about 10 times slower."
error "See the FAQ for possible causes, or disable acceleration by adding the \"KVM=N\" variable (not recommended)." ;;
esac
! enabled "$DEBUG" && exit 88
fi
fi
fi
return 0
}
# Sanitize variables # Sanitize variables
TZ=$(strip "$TZ") TZ=$(strip "$TZ")
STORAGE=$(strip "$STORAGE") STORAGE=$(strip "$STORAGE")
@@ -34,33 +303,13 @@ ENGINE="Docker"
PROCESS="${APP,,}" PROCESS="${APP,,}"
PROCESS="${PROCESS// /-}" PROCESS="${PROCESS// /-}"
if [ -f "/run/.containerenv" ]; then detectEngine
ENGINE="${container:-}" detectRootless
if [[ "${ENGINE,,}" == *"podman"* ]]; then
ROOTLESS="Y"
ENGINE="Podman"
else
[ -z "$ENGINE" ] && ENGINE="Kubernetes"
fi
fi
echo " Starting $APP for $ENGINE v$(</etc/version)..." echo " Starting $APP for $ENGINE v$(</etc/version)..."
echo " For support visit $SUPPORT" echo " For support visit $SUPPORT"
# Get the capability bounding set checkPrivileged
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
INFO="/run/shm/msg.html" INFO="/run/shm/msg.html"
PAGE="/run/shm/index.html" PAGE="/run/shm/index.html"
@@ -71,7 +320,6 @@ FOOTER2="<a href='$SUPPORT'>$SUPPORT</a>"
SOCKETS=1 SOCKETS=1
CPU=$(cpu) 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)
@@ -83,77 +331,10 @@ if grep -qi "socket(s)" <<< "$(lscpu)"; then
[ "$SOCKETS" -lt "1" ] && SOCKETS=1 [ "$SOCKETS" -lt "1" ] && SOCKETS=1
fi fi
CPU_CORES=$(strip "$CPU_CORES") normalizeCpuCores
[ -z "$CPU_CORES" ] && CPU_CORES=2 checkStorage
[[ "${CPU_CORES,,}" == "max" ]] && CPU_CORES="$CORES" checkFilesystem
[[ "${CPU_CORES,,}" == "half" ]] && CPU_CORES=$(( CORES / 2 )) normalizeRamSize
[ -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
# 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
# Check filesystem
FS=$(stat -f -c %T "$STORAGE")
if [[ "${FS,,}" == "ecryptfs" || "${FS,,}" == "tmpfs" ]]; then
DISK_IO="threads"
DISK_CACHE="writeback"
fi
# 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
# Print system info # Print system info
SYS="${SYS/-generic/}" SYS="${SYS/-generic/}"
@@ -165,69 +346,10 @@ SPACE_GB=$(formatBytes "$SPACE" "down")
AVAIL_MEM=$(formatBytes "$RAM_AVAIL" "down") AVAIL_MEM=$(formatBytes "$RAM_AVAIL" "down")
TOTAL_MEM=$(formatBytes "$RAM_TOTAL" "up") TOTAL_MEM=$(formatBytes "$RAM_TOTAL" "up")
echo " CPU: ${CPU} | RAM: ${AVAIL_MEM/ GB/}/$TOTAL_MEM | DISK: $SPACE_GB (${FS}) | KERNEL: ${SYS}..." echo " CPU: ${CPU} | RAM: ${AVAIL_MEM/ GB/}/$TOTAL_MEM | DISK: $SPACE_GB (${FS}) | KERNEL: ${SYS}"
echo echo
# Check KVM support checkKvm
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
# Cleanup files # Cleanup files
rm -f "$QEMU_DIR"/dsm.url rm -f "$QEMU_DIR"/dsm.url
+66 -35
View File
@@ -15,7 +15,12 @@ HOST_MODEL=$(strip "$HOST_MODEL")
HOST_SERIAL=$(strip "$HOST_SERIAL") HOST_SERIAL=$(strip "$HOST_SERIAL")
GUEST_SERIAL=$(strip "$GUEST_SERIAL") GUEST_SERIAL=$(strip "$GUEST_SERIAL")
if [ -n "$HOST_MAC" ]; then validateHostMac() {
local m
if [ -z "$HOST_MAC" ]; then
return 0
fi
HOST_MAC="${HOST_MAC//-/:}" HOST_MAC="${HOST_MAC//-/:}"
@@ -28,51 +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_PID="$QEMU_DIR/host.pid" buildHostArguments() {
HOST_ARGS=() HOST_ARGS=()
HOST_ARGS+=("-cpu=$CPU_CORES") HOST_ARGS+=("-cpu=$CPU_CORES")
HOST_ARGS+=("-cpu_arch=$HOST_CPU") HOST_ARGS+=("-cpu_arch=$HOST_CPU")
[ -n "$HOST_MAC" ] && HOST_ARGS+=("-mac=$HOST_MAC") [ -n "$HOST_MAC" ] && HOST_ARGS+=("-mac=$HOST_MAC")
[ -n "$HOST_MODEL" ] && HOST_ARGS+=("-model=$HOST_MODEL") [ -n "$HOST_MODEL" ] && HOST_ARGS+=("-model=$HOST_MODEL")
[ -n "$HOST_SERIAL" ] && HOST_ARGS+=("-hostsn=$HOST_SERIAL") [ -n "$HOST_SERIAL" ] && HOST_ARGS+=("-hostsn=$HOST_SERIAL")
[ -n "$GUEST_SERIAL" ] && HOST_ARGS+=("-guestsn=$GUEST_SERIAL") [ -n "$GUEST_SERIAL" ] && HOST_ARGS+=("-guestsn=$GUEST_SERIAL")
if enabled "$HOST_DEBUG"; then return 0
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
cnt=0 startHostBinary() {
sleep 0.2
while ! nc -z -w2 127.0.0.1 "$COM_PORT" > /dev/null 2>&1; do if enabled "$HOST_DEBUG"; then
sleep 0.1 set -x
cnt=$((cnt + 1)) ./host.bin "${HOST_ARGS[@]}" &
(( cnt > 50 )) && error "Failed to connect to qemu-host.." && exit 58 { set +x; } 2>/dev/null
done echo "$!" > "$HOST_PID"
echo
else
./host.bin "${HOST_ARGS[@]}" >/dev/null &
echo "$!" > "$HOST_PID"
fi
cnt=0 return 0
}
while ! nc -z -w2 127.0.0.1 "$CHR_PORT" > /dev/null 2>&1; do waitForPort() {
sleep 0.1
cnt=$((cnt + 1))
(( cnt > 50 )) && error "Failed to connect to qemu-host.." && exit 59
done
# Configure serial ports local port="$1"
SERIAL_OPTS="-serial mon:stdio \ local exit_code="$2"
local cnt=0
while ! nc -z -w2 127.0.0.1 "$port" > /dev/null 2>&1; do
sleep 0.1
cnt=$((cnt + 1))
(( cnt > 50 )) && error "Failed to connect to qemu-host.." && exit "$exit_code"
done
return 0
}
configureSerialPorts() {
# Configure serial ports
SERIAL_OPTS="-serial mon:stdio \
-device virtio-serial-pci,id=virtio-serial0,bus=pcie.0,addr=0x3 \ -device virtio-serial-pci,id=virtio-serial0,bus=pcie.0,addr=0x3 \
-chardev socket,id=charchannel0,host=127.0.0.1,port=$CHR_PORT,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
+66 -13
View File
@@ -15,33 +15,86 @@ WSD_PORT=$(strip "$WSD_PORT")
WEB_PID="/run/nginx.pid" WEB_PID="/run/nginx.pid"
WSD_PID="$QEMU_DIR/websocketd.pid" WSD_PID="$QEMU_DIR/websocketd.pid"
cp -r /var/www/* "$QEMU_DIR" prepareWebFiles() {
rm -f "$WSD_PID" "$WEB_PID"
html "Starting $APP for $ENGINE..." cp -r /var/www/* "$QEMU_DIR" || return 1
rm -f "$WSD_PID" "$WEB_PID" || return 1
if ! disabled "${WEB:-}"; then return 0
}
mkdir -p /etc/nginx/sites-enabled configureWebPorts() {
cp /etc/nginx/default.conf /etc/nginx/sites-enabled/web.conf
sed -i "s/listen 5000 default_server;/listen $WEB_PORT default_server;/g" /etc/nginx/sites-enabled/web.conf 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 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 # shellcheck disable=SC2143
if [ -f /proc/net/if_inet6 ] && [[ "$(cat /proc/sys/net/ipv6/conf/all/disable_ipv6 2>/dev/null)" != "1" ]] && [ -n "$(ifconfig -a | grep inet6)" ]; then if [ -f /proc/net/if_inet6 ] && [[ "$(cat /proc/sys/net/ipv6/conf/all/disable_ipv6 2>/dev/null)" != "1" ]]; then
sed -i "s/listen $WEB_PORT default_server;/listen [::]:$WEB_PORT default_server ipv6only=off;/g" /etc/nginx/sites-enabled/web.conf sed -i "s/listen $WEB_PORT default_server;/listen [::]:$WEB_PORT default_server ipv6only=off;/g" /etc/nginx/sites-enabled/web.conf
fi fi
return 0
}
configureWebServer() {
mkdir -p /etc/nginx/sites-enabled
cp /etc/nginx/default.conf /etc/nginx/sites-enabled/web.conf
configureWebPorts || return 1
configureIpv6Listen || return 1
return 0
}
startWebServer() {
# Start webserver # Start webserver
nginx -e stderr nginx -e stderr || return 1
return 0
}
startWebsocketServer() {
local log="/var/log/websocketd.log"
rm -f "$log"
# Start websocket server # Start websocket server
websocketd --address 127.0.0.1 --port="$WSD_PORT" /run/socket.sh >/var/log/websocketd.log & websocketd --address 127.0.0.1 --port="$WSD_PORT" /run/socket.sh > "$log" 2>&1 &
echo "$!" > "$WSD_PID" local pid=$!
fi if ! echo "$pid" > "$WSD_PID"; then
kill "$pid" 2>/dev/null || :
return 1
fi
sleep 0.1
if ! isAlive "$pid"; then
rm -f "$WSD_PID"
[ -s "$log" ] && cat "$log" >&2
error "Failed to start websocket server!"
return 1
fi
return 0
}
prepareWebFiles || return 1
html "Starting $APP for $ENGINE..."
disabled "${WEB:-}" && return 0
configureWebServer || return 1
startWebServer || return 1
startWebsocketServer || return 1
return 0 return 0
+1 -1
View File
@@ -9,7 +9,7 @@ refresh() {
[ ! -f "$path" ] && return 0 [ ! -f "$path" ] && return 0
[ ! -s "$path" ] && return 0 [ ! -s "$path" ] && return 0
msg=$(< "$path") msg=$(< "$path") || return 0
msg="${msg%$'\n'}" msg="${msg%$'\n'}"
[ -z "$msg" ] && return 0 [ -z "$msg" ] && return 0
+141 -14
View File
@@ -8,6 +8,7 @@ error () { printf "%b%s%b" "\E[1;31m " "ERROR: ${1:-}" "\E[0m\n" >&2; }
warn () { printf "%b%s%b" "\E[1;31m " "Warning: ${1:-}" "\E[0m\n" >&2; } warn () { printf "%b%s%b" "\E[1;31m " "Warning: ${1:-}" "\E[0m\n" >&2; }
strip() { strip() {
local value="${1:-}" local value="${1:-}"
# Remove surrounding whitespace # Remove surrounding whitespace
@@ -28,24 +29,38 @@ strip() {
} }
enabled() { enabled() {
case "$(strip "${1:-}")" in
Y|y|YES|Yes|yes|TRUE|True|true|1|ON|On|on) return 0 ;; local value
value=$(strip "${1:-}")
case "${value,,}" in
y|yes|true|1|on|enable|enabled) return 0 ;;
*) return 1 ;; *) return 1 ;;
esac esac
} }
disabled() { disabled() {
case "$(strip "${1:-}")" in
N|n|NO|No|no|FALSE|False|false|0|OFF|Off|off) return 0 ;; local value
value=$(strip "${1:-}")
case "${value,,}" in
n|no|none|false|0|off|disable|disabled) return 0 ;;
*) return 1 ;; *) return 1 ;;
esac esac
} }
formatBytes() { formatBytes() {
local result local result
result=$(numfmt --to=iec --suffix=B "$1" | sed -r 's/([A-Z])/ \1/' | sed 's/ B/ bytes/g;')
if ! result=$(numfmt --to=iec --suffix=B "$1" | sed -r 's/([A-Z])/ \1/' | sed 's/ B/ bytes/g;'); then
return 1
fi
local unit="${result//[0-9. ]}" local unit="${result//[0-9. ]}"
result="${result//[a-zA-Z ]/}" result="${result//[a-zA-Z ]/}"
if [[ "${2:-}" == "up" ]]; then if [[ "${2:-}" == "up" ]]; then
if [[ "$result" == *"."* ]]; then if [[ "$result" == *"."* ]]; then
result="${result%%.*}" result="${result%%.*}"
@@ -56,11 +71,13 @@ formatBytes() {
result="${result%%.*}" result="${result%%.*}"
fi fi
fi fi
echo "$result $unit" echo "$result $unit"
return 0 return 0
} }
isAlive() { isAlive() {
local pid="$1" local pid="$1"
[ -z "$pid" ] && return 1 [ -z "$pid" ] && return 1
@@ -72,6 +89,7 @@ isAlive() {
} }
waitPid() { waitPid() {
local i=0 local i=0
local pid="$1" local pid="$1"
local timeout="${2:-10}" local timeout="${2:-10}"
@@ -86,6 +104,7 @@ waitPid() {
} }
waitPidFile() { waitPidFile() {
local i=0 local i=0
local pid="" local pid=""
local file="$1" local file="$1"
@@ -106,6 +125,7 @@ waitPidFile() {
} }
pKill() { pKill() {
local pid="$1" local pid="$1"
local timeout="${2:-10}" local timeout="${2:-10}"
@@ -119,6 +139,7 @@ pKill() {
} }
fWait() { fWait() {
local i=0 local i=0
local name="$1" local name="$1"
local timeout="${2:-10}" local timeout="${2:-10}"
@@ -138,6 +159,7 @@ fWait() {
} }
fKill() { fKill() {
local name="$1" local name="$1"
local timeout="${2:-10}" local timeout="${2:-10}"
@@ -150,6 +172,7 @@ fKill() {
} }
sKill() { sKill() {
local pid="" local pid=""
local file="$1" local file="$1"
@@ -165,6 +188,7 @@ sKill() {
} }
mKill() { mKill() {
local timeout=10 local timeout=10
local files=("$@") local files=("$@")
@@ -182,14 +206,15 @@ mKill() {
} }
setOwner() { setOwner() {
local file="$1" local file="$1"
local dir uid gid local dir uid gid
[ ! -f "$file" ] && return 1 [ ! -f "$file" ] && return 1
dir=$(dirname -- "$file") dir=$(dirname -- "$file")
uid=$(stat -c '%u' "$dir") uid=$(stat -c '%u' "$dir") || return 1
gid=$(stat -c '%g' "$dir") gid=$(stat -c '%g' "$dir") || return 1
! chown "$uid:$gid" "$file" && return 1 ! chown "$uid:$gid" "$file" && return 1
@@ -197,6 +222,7 @@ setOwner() {
} }
makeDir() { makeDir() {
local path="$1" local path="$1"
local dir uid gid local dir uid gid
@@ -204,25 +230,123 @@ makeDir() {
! mkdir -p "$path" && return 1 ! mkdir -p "$path" && return 1
dir=$(dirname -- "$path") dir=$(dirname -- "$path")
uid=$(stat -c '%u' "$dir")
gid=$(stat -c '%g' "$dir")
! chown "$uid:$gid" "$path" && return 1 if ! uid=$(stat -c '%u' "$dir") || ! gid=$(stat -c '%g' "$dir"); then
warn "failed to determine the owner for \"$path\"."
return 0
fi
if ! chown "$uid:$gid" "$path"; then
warn "failed to set the owner for \"$path\"."
return 0
fi
return 0 return 0
} }
stateFile() {
local name="$1"
local prefix="${2:-$PROCESS}"
[[ "$name" == */* ]] && printf '%s\n' "$name" && return 0
printf '%s/%s.%s\n' "$STORAGE" "$prefix" "$name"
return 0
}
writeFile() {
local txt="$1"
local path="$2"
if ! printf '%s\n' "$txt" > "$path"; then
error "Failed to write file \"$path\" !"
return 1
fi
if ! setOwner "$path"; then
warn "failed to set the owner for \"$path\"."
fi
return 0
}
readFile() {
local path="$1"
local value
[ -s "$path" ] || return 0
value=$(<"$path") || return 1
value="${value//[![:print:]]/}"
printf '%s\n' "$value"
return 0
}
writeState() {
local name="$1"
local value="$2"
local prefix="${3:-$PROCESS}"
local path
[ -z "$value" ] && return 0
path=$(stateFile "$name" "$prefix") || return 1
writeFile "$value" "$path"
return $?
}
readState() {
local name="$1"
local prefix="${2:-$PROCESS}"
local path
path=$(stateFile "$name" "$prefix") || return 1
readFile "$path"
return $?
}
restoreState() {
local var="$1"
local name="$2"
local force="${3:-N}"
local prefix="${4:-$PROCESS}"
local value
if ! enabled "$force"; then
[ -z "${!var:-}" ] || return 0
fi
value=$(readState "$name" "$prefix") || return 1
[ -n "$value" ] || return 0
printf -v "$var" '%s' "$value" || return 1
return 0
}
escape () { escape () {
local s local s
s=${1//&/\&amp;} s=${1//&/\&amp;}
s=${s//</\&lt;} s=${s//</\&lt;}
s=${s//>/\&gt;} s=${s//>/\&gt;}
s=${s//'"'/\&quot;} s=${s//'"'/\&quot;}
printf -- %s "$s" printf -- %s "$s"
return 0 return 0
} }
html() { html() {
local title local title
local body local body
local script local script
@@ -247,13 +371,14 @@ html() {
HTML="${HTML/\[4\]/$footer}" HTML="${HTML/\[4\]/$footer}"
HTML="${HTML/\[5\]/$FOOTER2}" HTML="${HTML/\[5\]/$FOOTER2}"
echo "$HTML" > "$PAGE" echo "$HTML" > "$PAGE" || return 1
echo "$body" > "$INFO" echo "$body" > "$INFO" || return 1
return 0 return 0
} }
cpu() { cpu() {
local ret local ret
local cpu="" local cpu=""
@@ -307,6 +432,7 @@ hasDisk() {
} }
getCountry() { getCountry() {
local url=$1 local url=$1
local query=$2 local query=$2
local rc json result local rc json result
@@ -345,6 +471,7 @@ setCountry() {
} }
addPackage() { addPackage() {
local pkg=$1 local pkg=$1
local desc=$2 local desc=$2
@@ -361,8 +488,8 @@ addPackage() {
sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list.d/debian.sources sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list.d/debian.sources
fi fi
DEBIAN_FRONTEND=noninteractive apt-get -qq update DEBIAN_FRONTEND=noninteractive apt-get -qq update || return 1
DEBIAN_FRONTEND=noninteractive apt-get -qq --no-install-recommends -y install "$pkg" > /dev/null DEBIAN_FRONTEND=noninteractive apt-get -qq --no-install-recommends -y install "$pkg" > /dev/null || return 1
return 0 return 0
} }