Compare commits

...
69 Commits
Author SHA1 Message Date
KroeseandGitHub c9a2a6966b feat: Always show network info (#1233) 2026-07-07 19:37:59 +02:00
KroeseandGitHub 498248f43e feat: Display guest info before mode warning (#1232) 2026-07-07 12:21:25 +02:00
KroeseandGitHub 8963228b1c fix: Set returnvalue for waitForPort (#1231) 2026-07-07 12:13:13 +02:00
KroeseandGitHub 827d6d9e10 fix: Status monitor exited early (#1230) 2026-07-07 12:03:58 +02:00
KroeseandGitHub 1c577abef4 fix: Guest IP was not retrieved correctly (#1229) 2026-07-07 11:16:22 +02:00
KroeseandGitHub 20fd1a975c fix: Read QEMU pid correctly (#1228) 2026-07-07 10:53:53 +02:00
KroeseandGitHub 770774680c feat: Improve shutdown logic (#1226) 2026-07-07 03:16:35 +02:00
KroeseandGitHub 0c75aa81fe fix: Detect guest IP without assuming eth0 (#1225) 2026-07-06 23:54:24 +02:00
KroeseandGitHub 36ec57ac15 fix: Initialize indexdb path before extraction (#1224) 2026-07-06 23:50:26 +02:00
KroeseandGitHub 9c488c52b9 feat: Normalize disk image format (#1223)
*
2026-07-06 23:44:14 +02:00
KroeseandGitHub 2ba756260f fix: Sanitize guest hostname (#1222) 2026-07-06 23:33:34 +02:00
KroeseandGitHub b306012743 feat: Normalize discard mode for qemu drives (#1221) 2026-07-06 23:21:25 +02:00
KroeseandGitHub 61b3ee7e01 feat: Read qcow2 virtual size from qemu-img json (#1220) 2026-07-06 23:15:27 +02:00
KroeseandGitHub 245036f844 fix: Preserve port protocols in passt mode (#1219) 2026-07-06 23:11:34 +02:00
KroeseandGitHub 68cadfe035 fix: Tolerate missing sector-size output (#1218) 2026-07-06 22:56:00 +02:00
KroeseandGitHub 96102832b1 fix: Tolerate missing sector-size output (#1217) 2026-07-06 22:38:58 +02:00
KroeseandGitHub 211a56bf73 feat: Show additional guest info in debug mode (#1216) 2026-07-06 22:11:03 +02:00
KroeseandGitHub 506fb7b114 fix: Parse Debian ipcalc output correctly (#1215) 2026-07-06 21:58:11 +02:00
KroeseandGitHub bcc3393b7f fix: Shutdown Passt after DNS failure (#1214) 2026-07-06 19:21:48 +02:00
KroeseandGitHub 45c0f91d6e feat: Refactor networking code (#1213) 2026-07-06 17:47:29 +02:00
KroeseandGitHub a1910546a6 feat: Check file operation results (#1212) 2026-07-06 07:44:41 +02:00
KroeseandGitHub 637ed0e3a6 fix: Do not match multiple files (#1211) 2026-07-06 05:09:29 +02:00
KroeseandGitHub 954a6076ad feat: Add returnvalues to functions (#1210) 2026-07-06 03:48:39 +02:00
KroeseandGitHub 415aaa3038 feat: Refactor server code (#1209) 2026-07-05 23:35:56 +02:00
KroeseandGitHub c87e45719b feat: Refactor serial code (#1208) 2026-07-05 23:24:43 +02:00
KroeseandGitHub 102945ab48 feat: Refactor memory check (#1207) 2026-07-05 23:17:53 +02:00
KroeseandGitHub 1c3d490303 feat: Refactor status monitor (#1206) 2026-07-05 23:02:51 +02:00
KroeseandGitHub 73fa70f52f feat: Refactor CPU logic (#1205) 2026-07-05 22:46:39 +02:00
KroeseandGitHub 7b095702fc feat: Refactor shutdown logic (#1204) 2026-07-05 22:09:49 +02:00
KroeseandGitHub f82b2769d3 feat: Refactor entrypoint (#1203) 2026-07-05 21:43:10 +02:00
Peng LiuandGitHub cafe619028 fix: Parse quoted values consistently (#1202) 2026-07-05 21:25:33 +02:00
KroeseandGitHub e94a0c4a52 feat: Refactor disk code (#1201) 2026-07-05 20:09:47 +02:00
KroeseandGitHub d6a353df0e feat: Refactor network code (#1200) 2026-07-05 19:58:36 +02:00
KroeseandGitHub 050812e996 fix: Filter IPv6 DNS records when disabled (#1199) 2026-07-05 19:30:41 +02:00
KroeseandGitHub 75280e9aa9 feat: Improve bus detection (#1198) 2026-07-05 18:51:56 +02:00
KroeseandGitHub 8dcc8ccee1 fix: Move MTU check to finish (#1197) 2026-07-05 18:34:55 +02:00
KroeseandGitHub ba866e9e75 feat: Improve support for non-standard MTU sizes (#1196) 2026-07-05 17:10:59 +02:00
KroeseandGitHub 06e7a8edb9 docs: Readme (#1195) 2026-07-05 14:49:47 +02:00
KroeseandGitHub 495c67734a fix: Check adapter before setting MTU (#1194) 2026-07-05 03:13:14 +02:00
KroeseandGitHub 1557735331 build: Use heredoc for Dockerfile commands (#1193) 2026-07-04 20:34:32 +02:00
KroeseandGitHub f45b308c78 feat: Sanitize environment variables (#1192) 2026-07-03 17:14:59 +02:00
CopilotandGitHub d5821cd782 feat: Normalize boolean option handling (#1191) 2026-07-03 15:17:14 +02:00
KroeseandGitHub 312816b18e feat: Implement strip() function (#1190)
Added strip function to remove whitespace and quotes.
2026-07-03 14:48:58 +02:00
KroeseandGitHub f42a3f1662 feat: Add enabled() function (#1189) 2026-07-03 14:06:42 +02:00
KroeseandGitHub 558e5022ca docs: Readme (#1188) 2026-07-02 20:26:15 +02:00
KroeseandGitHub e03822f26e feat: Improve shutdown logic (#1187) 2026-07-02 20:14:05 +02:00
KroeseandGitHub 7b90ca30ba feat: Improve socket cleanup (#1186) 2026-07-02 18:51:40 +02:00
KroeseandGitHub 22c01bf2f1 docs: Readme (#1185) 2026-07-02 12:28:35 +02:00
KroeseandGitHub 1f601db8b0 build: Update Passt to v2026_06_11 (#1183) 2026-06-28 12:43:44 +02:00
KroeseandGitHub e7e2c35cb7 build: Run review steps in parallel (#1182) 2026-06-25 22:31:34 +02:00
KroeseandGitHub 50089112c0 docs: Update stargazers chart (#1181) 2026-06-25 14:21:53 +02:00
KroeseandGitHub f9538ce53b feat: Improve IPv6 check (#1180) 2026-06-19 13:24:54 +02:00
renovate[bot]andGitHub 49d25a13d0 chore(deps): update actions/checkout action to v7 (#1179) 2026-06-19 11:48:11 +02:00
KroeseandGitHub 7c9d2b1fd5 feat: Improved start and stop logic (#1178) 2026-06-05 02:37:20 +02:00
KroeseandGitHub b68d243b6a fix: Add fail_level to reviewdog actions (#1177) 2026-06-04 23:45:01 +02:00
KroeseandGitHub 79e85f9fd1 fix: Provide exitcode on shutdown (#1176) 2026-05-31 00:32:49 +02:00
KroeseandGitHub 476048a4ee fix: Update paths for QEMU files (#1175) 2026-05-30 23:50:39 +02:00
KroeseandGitHub f983dc3e46 fix: Remove stale QEMU files (#1174) 2026-05-30 23:05:16 +02:00
KroeseandGitHub 38f9c49200 feat: Added finishDisks function (#1173) 2026-05-30 20:51:58 +02:00
KroeseandGitHub 7157717a85 feat: Added option to disable data disks (#1171) 2026-05-30 19:52:54 +02:00
KroeseandGitHub 031674424d fix: Handle iptables-save errors better (#1170) 2026-05-30 18:58:12 +02:00
KroeseandGitHub 28e6f8da78 fix: IP address parsing (#1168) 2026-05-29 17:13:47 +02:00
KroeseandGitHub d5802607b5 fix: Throw object for XMLHttpRequest (#1166) 2026-05-29 16:54:45 +02:00
KroeseandGitHub 7168f2f843 feat: Update version file path (#1165) 2026-05-29 16:08:23 +02:00
KroeseandGitHub 819dd29025 fix: Network interface detection (#1164) 2026-05-29 14:34:34 +02:00
KroeseandGitHub 48ef47b85b fix: Previous XHR not aborted (#1163) 2026-05-29 14:33:23 +02:00
KroeseandGitHub 10c902bc9b build: Update Passt to v2026_05_26 (#1162) 2026-05-29 12:52:20 +02:00
KroeseandGitHub d6de10efe1 feat: Flush iptables on container restarts (#1161) 2026-05-29 05:00:56 +02:00
KroeseandGitHub 6d1d9d92b4 feat: Improve iptable rules (#1160) 2026-05-28 23:00:17 +02:00
23 changed files with 2435 additions and 1135 deletions
+1 -1
View File
@@ -22,7 +22,7 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v7
with: with:
fetch-depth: 0 fetch-depth: 0
- -
+4 -3
View File
@@ -7,9 +7,10 @@ jobs:
name: shellcheck name: shellcheck
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v7
- parallel:
- -
name: Run ShellCheck name: Run ShellCheck
uses: ludeeus/action-shellcheck@master uses: ludeeus/action-shellcheck@master
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
steps: steps:
- -
name: Checkout repo name: Checkout repo
uses: actions/checkout@v6 uses: actions/checkout@v7
- -
name: Docker Hub Description name: Docker Hub Description
uses: peter-evans/dockerhub-description@v5 uses: peter-evans/dockerhub-description@v5
+9 -4
View File
@@ -13,9 +13,10 @@ jobs:
name: review name: review
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v7
- parallel:
- -
name: Spelling name: Spelling
uses: reviewdog/action-misspell@v1 uses: reviewdog/action-misspell@v1
@@ -32,6 +33,7 @@ jobs:
uses: reviewdog/action-hadolint@v1 uses: reviewdog/action-hadolint@v1
with: with:
level: warning level: warning
fail_level: error
reporter: github-pr-review reporter: github-pr-review
hadolint_ignore: DL3008 DL3003 DL3006 DL3013 hadolint_ignore: DL3008 DL3003 DL3006 DL3013
github_token: ${{ secrets.GITHUB_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }}
@@ -52,8 +54,10 @@ jobs:
- -
name: Shellformat name: Shellformat
uses: reviewdog/action-shfmt@v1 uses: reviewdog/action-shfmt@v1
if: false
with: with:
level: warning level: warning
fail_on_error: "true"
shfmt_flags: "-i 2 -ci -bn" shfmt_flags: "-i 2 -ci -bn"
github_token: ${{ secrets.GITHUB_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }}
- -
@@ -61,6 +65,7 @@ jobs:
uses: reviewdog/action-shellcheck@v1 uses: reviewdog/action-shellcheck@v1
with: with:
level: warning level: warning
fail_level: error
reporter: github-pr-review reporter: github-pr-review
shellcheck_flags: -x -e SC2001 -e SC2034 -e SC2064 -e SC2317 -e SC2153 -e SC2028 shellcheck_flags: -x -e SC1091 -e SC2001 -e SC2034 -e SC2064 -e SC2317 -e SC2153 -e SC2028
github_token: ${{ secrets.GITHUB_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }}
+58 -50
View File
@@ -1,66 +1,74 @@
# syntax=docker/dockerfile:1 # syntax=docker/dockerfile:1
FROM qemux/qemu-host:2.05 AS builder FROM qemux/qemu-host:2.05 AS builder
# FROM golang as builder
# WORKDIR /
# RUN git clone https://github.com/qemus/qemu-host.git
# WORKDIR /qemu-host/src
# RUN go mod download
# RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o /qemu-host.bin .
FROM debian:trixie-slim FROM debian:trixie-slim
ARG TARGETARCH ARG TARGETARCH
ARG TARGETPLATFORM ARG TARGETPLATFORM
ARG VERSION_ARG="0.0" ARG VERSION_ARG="0.0"
ARG VERSION_PASST="2026_05_07" ARG VERSION_PASST="2026_06_11"
ARG DEBCONF_NOWARNINGS="yes" ARG DEBCONF_NOWARNINGS="yes"
ARG DEBIAN_FRONTEND="noninteractive" ARG DEBIAN_FRONTEND="noninteractive"
ARG DEBCONF_NONINTERACTIVE_SEEN="true" ARG DEBCONF_NONINTERACTIVE_SEEN="true"
RUN set -eu && \ 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" > /run/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/
+30 -17
View File
@@ -16,13 +16,17 @@ Virtual DSM in a Docker container.
## Features ✨ ## Features ✨
- Multiple disks - Runs Virtual DSM inside a Docker container
- KVM acceleration - Automatic download of the installation files
- Upgrades supported - Web-based access to the DSM desktop
- Near-native performance with KVM acceleration
- Customizable CPU, memory, and storage allocation
- Supports multiple disks and physical disk passthrough
- Supports bridge, user-mode, macvlan, and macvtap networking
## Usage 🐳 ## Usage 🐳
##### Via Docker Compose: ##### Docker Compose:
```yaml ```yaml
services: services:
@@ -44,22 +48,31 @@ services:
stop_grace_period: 2m stop_grace_period: 2m
``` ```
##### Via Docker CLI: ##### Docker CLI:
```bash ```bash
docker run -it --rm --name dsm -e "DISK_SIZE=256G" -p 5000:5000 --device=/dev/kvm --device=/dev/net/tun --cap-add NET_ADMIN -v "${PWD:-.}/dsm:/storage" --stop-timeout 120 docker.io/vdsm/virtual-dsm docker run -it --rm --name dsm -e "DISK_SIZE=256G" -p 5000:5000 --device=/dev/kvm --device=/dev/net/tun --cap-add NET_ADMIN -v "${PWD:-.}/dsm:/storage" --stop-timeout 120 docker.io/vdsm/virtual-dsm
``` ```
##### Via Kubernetes: ##### Kubernetes:
```shell ```shell
kubectl apply -f https://raw.githubusercontent.com/vdsm/virtual-dsm/refs/heads/master/kubernetes.yml kubectl apply -f https://raw.githubusercontent.com/vdsm/virtual-dsm/refs/heads/master/kubernetes.yml
``` ```
##### Via Github Codespaces: ##### GitHub Codespaces:
[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/vdsm/virtual-dsm) [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/vdsm/virtual-dsm)
## Requirements ⚙️
- A Linux host with KVM support, or Docker Desktop / Podman on Windows 11 with nested virtualization enabled.
- At least 2 GB of RAM available.
- At least 32 GB of free disk space.
> [!NOTE]
> Docker Desktop on macOS and Windows 10 do not currently provide the required KVM support for this image.
## FAQ 💬 ## FAQ 💬
### How do I use it? ### How do I use it?
@@ -70,7 +83,7 @@ kubectl apply -f https://raw.githubusercontent.com/vdsm/virtual-dsm/refs/heads/m
- Wait until DSM finishes its installation - Wait until DSM finishes its installation
- Choose an username and password, and you will be taken to the desktop. - Choose a username and password, and you will be taken to the desktop.
Enjoy your brand new NAS, and don't forget to star this repo! Enjoy your brand new NAS, and don't forget to star this repo!
@@ -95,7 +108,7 @@ kubectl apply -f https://raw.githubusercontent.com/vdsm/virtual-dsm/refs/heads/m
``` ```
> [!TIP] > [!TIP]
> This can also be used to resize the existing disk to a larger capacity without any data loss. > This can also be used to resize an existing disk to a larger capacity without any data loss.
### How do I add multiple disks? ### How do I add multiple disks?
@@ -110,9 +123,9 @@ kubectl apply -f https://raw.githubusercontent.com/vdsm/virtual-dsm/refs/heads/m
- ./example3:/storage3 - ./example3:/storage3
``` ```
### How do I pass-through a disk? ### How do I pass through a disk?
It is possible to pass-through disk devices or partitions directly by adding them to your compose file in this way: You can pass through disk devices or partitions directly by adding them to your compose file in this way:
```yaml ```yaml
devices: devices:
@@ -158,7 +171,7 @@ kubectl apply -f https://raw.githubusercontent.com/vdsm/virtual-dsm/refs/heads/m
- you enabled "nested virtualization" if you are running the container inside a virtual machine. - you enabled "nested virtualization" if you are running the container inside a virtual machine.
- you are not using a cloud provider, as most of them do not allow nested virtualization for their VPS's. - you are not using a cloud provider, as most of them do not allow nested virtualization for their VPSs.
If you 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. 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.
@@ -214,9 +227,9 @@ kubectl apply -f https://raw.githubusercontent.com/vdsm/virtual-dsm/refs/heads/m
- 'c *:* rwm' - 'c *:* rwm'
``` ```
### How do I pass-through the GPU? ### How do I pass through the GPU?
To pass-through your Intel GPU, add the following lines to your compose file: To pass through your Intel GPU, add the following lines to your compose file:
```yaml ```yaml
environment: environment:
@@ -254,12 +267,12 @@ kubectl apply -f https://raw.githubusercontent.com/vdsm/virtual-dsm/refs/heads/m
### Is this project legal? ### Is this project legal?
Yes, this project contains only open-source code and does not distribute any copyrighted material. Neither does it try to circumvent any copyright protection measures. So under all applicable laws, this project will be considered legal. Yes, this project contains only open-source code and does not distribute any material owned by Synology. Neither does it try to circumvent any copyright protection measures.
However, by installing Synology's Virtual DSM, you must accept their end-user license agreement, which does not permit installation on non-Synology hardware. So only run this container on an official Synology NAS, as any other use will be a violation of their terms and conditions. However, by installing Synology's Virtual DSM, you must accept their end-user license agreement, which does not permit installation on non-Synology hardware. So only run this container on an official Synology NAS, as any other use will be a violation of their terms and conditions.
## Stars 🌟 ## Stars 🌟
[![Stars](https://starchart.cc/vdsm/virtual-dsm.svg?variant=adaptive)](https://starchart.cc/vdsm/virtual-dsm) [![Stargazers](https://raw.githubusercontent.com/star-stats/stars/refs/heads/data/charts/vdsm-virtual-dsm.svg)](https://github.com/vdsm/virtual-dsm/stargazers)
## Disclaimer ⚖️ ## Disclaimer ⚖️
+8 -5
View File
@@ -1,24 +1,27 @@
#!/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"
gateway="/run/shm/qemu.gw" gateway="/run/shm/qemu.gw"
[ ! -s "$file" ] && echo "DSM has not enabled networking yet.." && exit 1 [ ! -s "$file" ] && echo "DSM has not enabled networking yet..." && exit 1
location=$(<"$file") location=$(<"$file")
if ! curl -m 20 -ILfSs "http://$location/" > /dev/null; then if ! curl -m 20 -ILfSs "http://$location/" > /dev/null; then
if [[ "$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
+1
View File
@@ -3,6 +3,7 @@ set -Eeuo pipefail
DEF_OPTS="-nodefaults -boot strict=on" DEF_OPTS="-nodefaults -boot strict=on"
RAM_OPTS=$(echo "-m ${RAM_SIZE^^}" | sed 's/MB/M/g;s/GB/G/g;s/TB/T/g') RAM_OPTS=$(echo "-m ${RAM_SIZE^^}" | sed 's/MB/M/g;s/GB/G/g;s/TB/T/g')
MON_OPTS="-name $PROCESS,process=$PROCESS,debug-threads=on -pidfile $QEMU_PID"
CPU_OPTS="-cpu $CPU_FLAGS -smp $CPU_CORES,sockets=1,dies=1,cores=$CPU_CORES,threads=1" CPU_OPTS="-cpu $CPU_FLAGS -smp $CPU_CORES,sockets=1,dies=1,cores=$CPU_CORES,threads=1"
MAC_OPTS="-machine type=q35,smm=off,usb=off,vmport=off,dump-guest-core=off,hpet=off${KVM_OPTS}" MAC_OPTS="-machine type=q35,smm=off,usb=off,vmport=off,dump-guest-core=off,hpet=off${KVM_OPTS}"
DEV_OPTS="-device virtio-balloon-pci,id=balloon0,bus=pcie.0,addr=0x4" DEV_OPTS="-device virtio-balloon-pci,id=balloon0,bus=pcie.0,addr=0x4"
+230 -149
View File
@@ -8,9 +8,18 @@ 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
DISK_IO=$(strip "$DISK_IO")
DISK_FMT=$(strip "$DISK_FMT")
DISK_TYPE=$(strip "$DISK_TYPE")
DISK_FLAGS=$(strip "$DISK_FLAGS")
DISK_CACHE=$(strip "$DISK_CACHE")
DISK_DISCARD=$(strip "$DISK_DISCARD")
DISK_ROTATION=$(strip "$DISK_ROTATION")
BOOT="$STORAGE/$BASE.boot.img" BOOT="$STORAGE/$BASE.boot.img"
SYSTEM="$STORAGE/$BASE.system.img" SYSTEM="$STORAGE/$BASE.system.img"
@@ -29,15 +38,9 @@ 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
} }
@@ -45,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")
@@ -68,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
} }
@@ -97,6 +103,82 @@ 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
}
createDisk() { createDisk() {
local DISK_FILE="$1" local DISK_FILE="$1"
@@ -110,7 +192,7 @@ createDisk() {
DATA_SIZE=$(numfmt --from=iec "$DISK_SPACE") DATA_SIZE=$(numfmt --from=iec "$DISK_SPACE")
if [[ "$ALLOCATE" != [Nn]* ]]; then if ! disabled "$ALLOCATE"; then
# Check free diskspace # Check free diskspace
DIR=$(dirname "$DISK_FILE") DIR=$(dirname "$DISK_FILE")
@@ -139,33 +221,15 @@ createDisk() {
{ chattr +C "$DISK_FILE"; } || : { chattr +C "$DISK_FILE"; } || :
fi fi
if [[ "$ALLOCATE" == [Nn]* ]]; then if ! allocateRaw "$DISK_FILE" "$DATA_SIZE"; then
rm -f "$DISK_FILE"
# Create an empty file error "$FAIL" && exit 77
if ! truncate -s "$DATA_SIZE" "$DISK_FILE"; then
rm -f "$DISK_FILE"
error "$FAIL" && exit 77
fi
else
# Create an empty file
if ! fallocate -l "$DATA_SIZE" "$DISK_FILE" &>/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"
@@ -193,12 +257,12 @@ resizeDisk() {
local FS="$5" local FS="$5"
local CUR_SIZE DATA_SIZE DIR SPACE GB local CUR_SIZE DATA_SIZE DIR SPACE GB
CUR_SIZE=$(getSize "$DISK_FILE") CUR_SIZE=$(getSize "$DISK_FILE") || exit 71
DATA_SIZE=$(numfmt --from=iec "$DISK_SPACE") DATA_SIZE=$(numfmt --from=iec "$DISK_SPACE")
local REQ=$(( DATA_SIZE - CUR_SIZE )) local REQ=$(( DATA_SIZE - CUR_SIZE ))
(( REQ < 1 )) && error "Shrinking disks is not supported yet, please increase ${DISK_DESC^^}_SIZE." && exit 71 (( REQ < 1 )) && error "Shrinking disks is not supported yet, please increase ${DISK_DESC^^}_SIZE." && exit 71
if [[ "$ALLOCATE" != [Nn]* ]]; then if ! disabled "$ALLOCATE"; then
# Check free diskspace # Check free diskspace
DIR=$(dirname "$DISK_FILE") DIR=$(dirname "$DISK_FILE")
@@ -221,24 +285,8 @@ resizeDisk() {
case "${DISK_FMT,,}" in case "${DISK_FMT,,}" in
raw) raw)
if [[ "$ALLOCATE" == [Nn]* ]]; then if ! allocateRaw "$DISK_FILE" "$DATA_SIZE"; then
error "$FAIL" && exit 75
# Resize file by changing its length
if ! truncate -s "$DATA_SIZE" "$DISK_FILE"; then
error "$FAIL" && exit 75
fi
else
# Resize file by allocating more space
if ! fallocate -l "$DATA_SIZE" "$DISK_FILE" &>/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)
@@ -264,20 +312,20 @@ convertDisk() {
local FS="$7" local FS="$7"
[ -f "$DST_FILE" ] && error "Conversion failed, destination file $DST_FILE already exists?" && exit 79 [ -f "$DST_FILE" ] && error "Conversion failed, destination file $DST_FILE already exists?" && exit 79
[ ! -f "$SOURCE_FILE" ] && error "Conversion failed, source file $SOURCE_FILE does not exists?" && exit 79 [ ! -f "$SOURCE_FILE" ] && error "Conversion failed, source file $SOURCE_FILE does not exist?" && exit 79
local TMP_FILE="$DISK_BASE.tmp" local TMP_FILE="$DISK_BASE.tmp"
rm -f "$TMP_FILE" rm -f "$TMP_FILE"
local DIR local DIR FA
DIR=$(dirname "$TMP_FILE") DIR=$(dirname "$TMP_FILE")
if [[ "$ALLOCATE" != [Nn]* ]]; 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) SPACE=$(df --output=avail -B 1 "$DIR" | tail -n 1)
if (( CUR_SIZE > SPACE )); then if (( CUR_SIZE > SPACE )); then
@@ -293,14 +341,13 @@ convertDisk() {
info "$msg, please wait until completed..." info "$msg, please wait until completed..."
local CONV_FLAGS="-p" local CONV_FLAGS="-p"
local DISK_PARAM="$DISK_ALLOC" local DISK_PARAM
isCow "$FS" && DISK_PARAM+=",nocow=on" DISK_PARAM=$(getDiskOptions "$FS" "$DST_FMT")
if [[ "$DST_FMT" != "raw" ]]; then if [[ "$DST_FMT" != "raw" ]]; then
if [[ "$ALLOCATE" == [Nn]* ]]; then if disabled "$ALLOCATE"; then
CONV_FLAGS+=" -c" CONV_FLAGS+=" -c"
fi fi
[ -n "$DISK_FLAGS" ] && DISK_PARAM+=",$DISK_FLAGS"
fi fi
# shellcheck disable=SC2086 # shellcheck disable=SC2086
@@ -310,7 +357,7 @@ convertDisk() {
fi fi
if [[ "$DST_FMT" == "raw" ]]; then if [[ "$DST_FMT" == "raw" ]]; then
if [[ "$ALLOCATE" != [Nn]* ]]; then if ! disabled "$ALLOCATE"; then
# Work around qemu-img bug # Work around qemu-img bug
CUR_SIZE=$(stat -c%s "$TMP_FILE") CUR_SIZE=$(stat -c%s "$TMP_FILE")
if ! fallocate -l "$CUR_SIZE" "$TMP_FILE" &>/dev/null; then if ! fallocate -l "$CUR_SIZE" "$TMP_FILE" &>/dev/null; then
@@ -321,8 +368,17 @@ convertDisk() {
fi fi
fi fi
rm -f "$SOURCE_FILE" if ! rm -f "$SOURCE_FILE"; then
mv "$TMP_FILE" "$DST_FILE" rm -f "$TMP_FILE"
error "Failed to remove old $DISK_DESC image $SOURCE_FILE."
exit 79
fi
if ! mv "$TMP_FILE" "$DST_FILE"; then
rm -f "$TMP_FILE"
error "Failed to move converted $DISK_DESC image to $DST_FILE."
exit 79
fi
if isCow "$FS"; then if isCow "$FS"; then
FA=$(lsattr "$DST_FILE") FA=$(lsattr "$DST_FILE")
@@ -426,6 +482,20 @@ createDevice () {
return 0 return 0
} }
finishDisks () {
case "${DISK_TYPE,,}" in
"blk" | "scsi" | "virtio-blk" | "virtio-scsi" )
DISK_OPTS+=" -object iothread,id=io2" ;;
esac
if ! enabled "$DISK_DISABLE"; then
html "Initialized disks successfully..."
fi
return 0
}
addDisk () { addDisk () {
local DISK_BASE="$1" local DISK_BASE="$1"
@@ -437,7 +507,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"
@@ -445,38 +515,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 $?
@@ -503,7 +544,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
@@ -526,15 +567,17 @@ addDisk () {
fi fi
if [ -f "$DISK_FILE" ] && [[ "$ALLOCATE" == [Nn]* ]]; 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
GB=$(formatBytes "$FREE") GB=$(formatBytes "$FREE")
LEFT=$(formatBytes "$LEFT") LEFT=$(formatBytes "$LEFT")
CUR_SIZE=$(formatBytes "$CUR_SIZE") CUR_SIZE=$(formatBytes "$CUR_SIZE")
@@ -580,18 +623,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
@@ -600,24 +647,61 @@ addDevice () {
return 0 return 0
} }
msg="Initializing disks..."
html "$msg"
[[ "$DEBUG" == [Yy1]* ]] && echo "$msg"
[ -z "${DISK_OPTS:-}" ] && DISK_OPTS="" [ -z "${DISK_OPTS:-}" ] && DISK_OPTS=""
[ -z "${DISK_TYPE:-}" ] && DISK_TYPE="scsi" [ -z "${DISK_TYPE:-}" ] && DISK_TYPE="scsi"
[ -z "${DISK_NAME:-}" ] && DISK_NAME="data" [ -z "${DISK_NAME:-}" ] && DISK_NAME="data"
[ -z "${DISK_DISABLE:-}" ] && DISK_DISABLE=""
if ! enabled "$DISK_DISABLE"; then
msg="Initializing disks..."
html "$msg"
enabled "$DEBUG" && echo "$msg"
fi
if [[ "${DISK_IO,,}" == "native" && "${DISK_CACHE,,}" != "none" && "${DISK_CACHE,,}" != "directsync" ]]; then
warn "DISK_IO=native requires direct I/O caching, using DISK_IO=threads with DISK_CACHE=$DISK_CACHE."
DISK_IO="threads"
fi
case "${DISK_DISCARD,,}" in
"y" | "yes" | "true" | "1" | "on" | "unmap" )
DISK_DISCARD="unmap" ;;
"n" | "no" | "false" | "0" | "off" | "ignore" )
DISK_DISCARD="ignore" ;;
* )
warn "Invalid DISK_DISCARD value '$DISK_DISCARD', using 'unmap'."
DISK_DISCARD="unmap" ;;
esac
if [[ ! "$DISK_ROTATION" =~ ^[0-9]+$ ]]; then
warn "Invalid DISK_ROTATION value '$DISK_ROTATION', using 1."
DISK_ROTATION="1"
fi
DISK_FMT="${DISK_FMT,,}"
case "$DISK_FMT" in
"raw" | "qcow2" ) ;;
* ) error "Invalid DISK_FMT specified, value \"$DISK_FMT\" is not recognized!" && exit 78 ;;
esac
case "${DISK_TYPE,,}" in case "${DISK_TYPE,,}" in
"ide" | "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
if [[ "$ALLOCATE" == [Nn]* ]]; then if disabled "$ALLOCATE"; then
DISK_STYLE="growable" DISK_STYLE="growable"
DISK_ALLOC="preallocation=off" DISK_ALLOC="preallocation=off"
else else
@@ -628,6 +712,11 @@ fi
DISK_OPTS+=$(createDevice "$BOOT" "$DISK_TYPE" "1" "0xa" "raw" "$DISK_IO" "$DISK_CACHE" "" "") DISK_OPTS+=$(createDevice "$BOOT" "$DISK_TYPE" "1" "0xa" "raw" "$DISK_IO" "$DISK_CACHE" "" "")
DISK_OPTS+=$(createDevice "$SYSTEM" "$DISK_TYPE" "2" "0xb" "raw" "$DISK_IO" "$DISK_CACHE" "" "") DISK_OPTS+=$(createDevice "$SYSTEM" "$DISK_TYPE" "2" "0xb" "raw" "$DISK_IO" "$DISK_CACHE" "" "")
if enabled "$DISK_DISABLE"; then
finishDisks
return 0
fi
DISK1_FILE="$STORAGE/${DISK_NAME}" DISK1_FILE="$STORAGE/${DISK_NAME}"
DISK2_FILE="/storage2/${DISK_NAME}2" DISK2_FILE="/storage2/${DISK_NAME}2"
DISK3_FILE="/storage3/${DISK_NAME}3" DISK3_FILE="/storage3/${DISK_NAME}3"
@@ -653,31 +742,23 @@ DISK4_FILE="/storage4/${DISK_NAME}4"
[ -z "$DEVICE3" ] && [ -b "/dev/disk3" ] && DEVICE3="/dev/disk3" [ -z "$DEVICE3" ] && [ -b "/dev/disk3" ] && DEVICE3="/dev/disk3"
[ -z "$DEVICE4" ] && [ -b "/dev/disk4" ] && DEVICE4="/dev/disk4" [ -z "$DEVICE4" ] && [ -b "/dev/disk4" ] && DEVICE4="/dev/disk4"
if [ -n "$DEVICE" ]; then DISK_FILES=( "$DISK1_FILE" "$DISK2_FILE" "$DISK3_FILE" "$DISK4_FILE" )
addDevice "$DEVICE" "$DISK_TYPE" "3" "0xc" || exit $? DISK_DESCS=( "disk" "disk2" "disk3" "disk4" )
else DISK_SIZES=( "$DISK_SIZE" "$DISK2_SIZE" "$DISK3_SIZE" "$DISK4_SIZE" )
addDisk "$DISK1_FILE" "$DISK_TYPE" "disk" "$DISK_SIZE" "3" "0xc" "$DISK_FMT" "$DISK_IO" "$DISK_CACHE" || exit $? DISK_DEVICES=( "$DEVICE" "$DEVICE2" "$DEVICE3" "$DEVICE4" )
fi DISK_INDEXES=( "3" "4" "5" "6" )
DISK_ADDRESSES=( "0xc" "0xd" "0xe" "0xf" )
if [ -n "$DEVICE2" ]; then for i in "${!DISK_FILES[@]}"; do
addDevice "$DEVICE2" "$DISK_TYPE" "4" "0xd" || exit $?
else
addDisk "$DISK2_FILE" "$DISK_TYPE" "disk2" "$DISK2_SIZE" "4" "0xd" "$DISK_FMT" "$DISK_IO" "$DISK_CACHE" || exit $?
fi
if [ -n "$DEVICE3" ]; then if [ -n "${DISK_DEVICES[i]}" ]; then
addDevice "$DEVICE3" "$DISK_TYPE" "5" "0xe" || exit $? addDevice "${DISK_DEVICES[i]}" "$DISK_TYPE" "${DISK_INDEXES[i]}" "${DISK_ADDRESSES[i]}" || exit $?
else else
addDisk "$DISK3_FILE" "$DISK_TYPE" "disk3" "$DISK3_SIZE" "5" "0xe" "$DISK_FMT" "$DISK_IO" "$DISK_CACHE" || exit $? addDisk "${DISK_FILES[i]}" "$DISK_TYPE" "${DISK_DESCS[i]}" "${DISK_SIZES[i]}" "${DISK_INDEXES[i]}" "${DISK_ADDRESSES[i]}" "$DISK_FMT" "$DISK_IO" "$DISK_CACHE" || exit $?
fi fi
if [ -n "$DEVICE4" ]; then done
addDevice "$DEVICE4" "$DISK_TYPE" "6" "0xf" || exit $?
else
addDisk "$DISK4_FILE" "$DISK_TYPE" "disk4" "$DISK4_SIZE" "6" "0xf" "$DISK_FMT" "$DISK_IO" "$DISK_CACHE" || exit $?
fi
DISK_OPTS+=" -object iothread,id=io2" finishDisks
html "Initialized disks successfully..."
return 0 return 0
+7 -2
View File
@@ -8,9 +8,14 @@ set -Eeuo pipefail
: "${DISPLAY:="none"}" # Display type : "${DISPLAY:="none"}" # Display type
: "${RENDERNODE:="/dev/dri/renderD128"}" # Render node : "${RENDERNODE:="/dev/dri/renderD128"}" # Render node
# Sanitize variables
VGA=$(strip "$VGA")
DISPLAY=$(strip "$DISPLAY")
RENDERNODE=$(strip "$RENDERNODE")
CPU_VENDOR=$(lscpu | awk '/Vendor ID/{print $3}') CPU_VENDOR=$(lscpu | awk '/Vendor ID/{print $3}')
if [[ "$GPU" != [Yy1]* || "$CPU_VENDOR" != "GenuineIntel" || "$ARCH" != "amd64" ]]; then if ! enabled "$GPU" || [[ "$CPU_VENDOR" != "GenuineIntel" || "$ARCH" != "amd64" ]]; then
[[ "${DISPLAY,,}" == "none" ]] && VGA="none" [[ "${DISPLAY,,}" == "none" ]] && VGA="none"
DISPLAY_OPTS="-display $DISPLAY -vga $VGA" DISPLAY_OPTS="-display $DISPLAY -vga $VGA"
@@ -20,7 +25,7 @@ fi
msg="Configuring display drivers..." msg="Configuring display drivers..."
html "$msg" html "$msg"
[[ "$DEBUG" == [Yy1]* ]] && echo "$msg" enabled "$DEBUG" && echo "$msg"
DISPLAY_OPTS="-display egl-headless,rendernode=$RENDERNODE" DISPLAY_OPTS="-display egl-headless,rendernode=$RENDERNODE"
DISPLAY_OPTS+=" -vga $VGA" DISPLAY_OPTS+=" -vga $VGA"
+13 -9
View File
@@ -24,19 +24,23 @@ cd /run
trap - ERR trap - ERR
version=$(qemu-system-x86_64 --version | head -n 1 | cut -d '(' -f 1 | awk '{ print $NF }') cmd=(qemu-system-x86_64)
version=$("${cmd[@]}" --version | awk 'NR==1 { print $4 }')
info "Booting $APP using QEMU v$version..." info "Booting $APP using QEMU v$version..."
if [[ "$CONSOLE" == [Yy]* ]]; then if ! enabled "$SHUTDOWN"; then
exec qemu-system-x86_64 ${ARGS:+ $ARGS} exec "${cmd[@]}" ${ARGS:+ $ARGS}
fi fi
{ qemu-system-x86_64 ${ARGS:+ $ARGS} >"$QEMU_OUT" 2>"$QEMU_LOG"; rc=$?; } || : if [ ! -t 1 ] || [ ! -c /dev/tty ]; then
(( rc != 0 )) && error "$(<"$QEMU_LOG")" && exit 15 "${cmd[@]}" ${ARGS:+ $ARGS} &
else
"${cmd[@]}" ${ARGS:+ $ARGS} </dev/tty >/dev/tty &
fi
terminal rc=0
tail -fn +0 "$QEMU_LOG" --pid=$$ 2>/dev/null & wait $! || rc=$?
cat "$QEMU_TERM" 2>/dev/null & wait $! || : [ -f "$QEMU_END" ] && exit "$rc"
sleep 1 & wait $! sleep 1 & wait $!
[ ! -f "$QEMU_END" ] && finish 0 finish "$rc"
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -Eeuo pipefail set -Eeuo pipefail
if [[ "$DEBUG" == [Yy1]* ]]; then if enabled "$DEBUG"; then
printf "QEMU arguments:\n\n%s\n\n" "${ARGS// -/$'\n-'}" printf "QEMU arguments:\n\n%s\n\n" "${ARGS// -/$'\n-'}"
fi fi
+9 -5
View File
@@ -27,6 +27,8 @@ FILE=$(find / -maxdepth 1 -type f -iname "$FN" -print -quit)
[ ! -s "$FILE" ] && FILE=$(find "$STORAGE" -maxdepth 1 -type f -iname "$FN" -print -quit) [ ! -s "$FILE" ] && FILE=$(find "$STORAGE" -maxdepth 1 -type f -iname "$FN" -print -quit)
[ -s "$FILE" ] && BASE="DSM_VirtualDSM" && URL="file://$FILE" [ -s "$FILE" ] && BASE="DSM_VirtualDSM" && URL="file://$FILE"
URL=$(strip "$URL")
if [ -n "$URL" ] && [ ! -s "$FILE" ] && [ ! -d "$DIR" ]; then if [ -n "$URL" ] && [ ! -s "$FILE" ] && [ ! -d "$DIR" ]; then
BASE=$(basename "$URL" .pat) BASE=$(basename "$URL" .pat)
if [ ! -s "$STORAGE/$BASE.system.img" ]; then if [ ! -s "$STORAGE/$BASE.system.img" ]; then
@@ -203,11 +205,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"
@@ -279,8 +282,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
+46 -13
View File
@@ -2,12 +2,18 @@
set -Eeuo pipefail set -Eeuo pipefail
msg="Checking memory..." msg="Checking memory..."
[[ "$DEBUG" == [Yy1]* ]] && echo "$msg" enabled "$DEBUG" && echo "$msg"
RAM_AVAIL=$(free -b | grep -m 1 Mem: | awk '{print $7}') RAM_AVAIL=$(free -b | grep -m 1 Mem: | awk '{print $7}')
AVAIL_MEM=$(formatBytes "$RAM_AVAIL") AVAIL_MEM=$(formatBytes "$RAM_AVAIL")
if [[ "$RAM_CHECK" != [Nn]* && "${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,9 +36,16 @@ if [[ "$RAM_CHECK" != [Nn]* && "${RAM_SIZE,,}" != "max" && "${RAM_SIZE,,}" != "h
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 ))
@@ -42,9 +55,16 @@ if [[ "${RAM_SIZE,,}" == "half" ]]; then
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
@@ -65,14 +85,27 @@ if [[ "${RAM_SIZE,,}" == "max" ]]; then
info "Allocated $wanted MB of RAM for the virtual machine." info "Allocated $wanted MB of RAM for the virtual machine."
fi return 0
}
wanted=$(numfmt --from=iec "$RAM_SIZE") checkMinimumMemory() {
if [ "$wanted" -lt "$RAM_MINIMUM" ]; then local wanted
wanted=$(( wanted / 1048577 ))
error "Not enough memory available, there is only $wanted MB left!" wanted=$(numfmt --from=iec "$RAM_SIZE")
exit 16
fi if [ "$wanted" -lt "$RAM_MINIMUM" ]; then
wanted=$(( wanted / 1048577 ))
error "Not enough memory available, there is only $wanted MB left!"
exit 16
fi
return 0
}
checkConfiguredMemory
configureHalfMemory
configureMaxMemory
checkMinimumMemory
return 0 return 0
+1067 -410
View File
File diff suppressed because it is too large Load Diff
+192 -129
View File
@@ -1,144 +1,131 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -Eeuo pipefail set -Eeuo pipefail
: "${API_TIMEOUT:="50"}" # API Call timeout : "${SHUTDOWN:="Y"}" # Graceful ACPI shutdown
: "${QEMU_TIMEOUT:="50"}" # QEMU Termination timeout : "${TIMEOUT:="115"}" # QEMU termination timeout
: "${API_TIMEOUT:="90"}" # External API call timeout
# Configure QEMU for graceful shutdown # Configure QEMU for graceful shutdown
API_CMD=6 API_CMD=6
API_HOST="127.0.0.1:$COM_PORT" API_HOST="127.0.0.1:$COM_PORT"
QEMU_TERM="" # Configure QEMU for graceful shutdown
QEMU_LOG="$QEMU_DIR/qemu.log"
QEMU_OUT="$QEMU_DIR/qemu.out"
QEMU_END="$QEMU_DIR/qemu.end" QEMU_END="$QEMU_DIR/qemu.end"
if [[ "$KVM" == [Nn]* ]]; then
API_TIMEOUT=$(( API_TIMEOUT*2 ))
QEMU_TIMEOUT=$(( QEMU_TIMEOUT*2 ))
fi
touch "$QEMU_LOG"
_trap() { _trap() {
func="$1" ; shift
for sig ; do local func="$1"; shift
local sig
TRAP_PID=$BASHPID
for sig; do
trap "$func $sig" "$sig" trap "$func $sig" "$sig"
done done
return 0
}
signalCode() {
local sig="$1"
case "$sig" in
SIGHUP) echo 129 ;;
SIGINT) echo 130 ;;
SIGQUIT) echo 131 ;;
SIGABRT) echo 134 ;;
SIGTERM) echo 143 ;;
*) echo 0 ;;
esac
return 0
}
displayReason() {
local reason="$1"
case "$reason" in
129 ) echo "SIGHUP" ;;
130 ) echo "SIGINT" ;;
131 ) echo "SIGQUIT" ;;
134 ) echo "SIGABRT" ;;
143 ) echo "SIGTERM" ;;
* ) echo "$reason" ;;
esac
return 0
}
readQemuPid() {
local -n _pid="$1"
if [ ! -s "$QEMU_PID" ] || ! read -r _pid <"$QEMU_PID"; then
return 1
fi
return 0
}
forceKillQemu() {
local reason="$1"
local pid=""
local display
! readQemuPid pid && return 0
! isAlive "$pid" && return 0
display=$(displayReason "$reason")
error "Forcefully terminating $(app), reason: $display..."
{ disown "$pid" || :; kill -9 -- "$pid" || :; } 2>/dev/null
return 0
}
cleanupHelpers() {
local pids=( "${HOST_PID:-}" "${WSD_PID:-}" \
"${WEB_PID:-}" "${PASST_PID:-}" "${DNSMASQ_PID:-}" )
mKill "${pids[@]}"
fKill "print.sh"
closeNetwork
return 0
} }
finish() { finish() {
local pid
local cnt=0
local reason=$1 local reason=$1
touch "$QEMU_END" touch "$QEMU_END"
if [ -s "$QEMU_PID" ]; then forceKillQemu "$reason"
cleanupHelpers
pid=$(<"$QEMU_PID")
echo && error "Forcefully terminating Virtual DSM, reason: $reason..."
{ kill -15 "$pid" || true; } 2>/dev/null
while isAlive "$pid"; do
sleep 1
cnt=$((cnt+1))
# Workaround for zombie pid
[ ! -s "$QEMU_PID" ] && break
if [ "$cnt" -eq 5 ]; then
echo && error "QEMU did not terminate itself, forcefully killing process..."
{ kill -9 "$pid" || true; } 2>/dev/null
fi
done
if ! waitPidFile "$QEMU_PID" 10; then
warn "Timed out while waiting for $(app) to exit!"
fi fi
fKill "print.sh" (( reason != 1 )) && echo && echo " Shutdown completed!"
fKill "host.bin"
closeNetwork
sleep 1
echo && echo " Shutdown completed!"
exit "$reason" exit "$reason"
} }
terminal() { sendGuestShutdown() {
local dev="" local pid="$1"
local response
if [ -s "$QEMU_OUT" ]; then local url
local msg
msg=$(<"$QEMU_OUT")
if [ -n "$msg" ]; then
if [[ "${msg,,}" != "char"* || "$msg" != *"serial0)" ]]; then
echo "$msg"
fi
dev="${msg#*/dev/p}"
dev="/dev/p${dev%% *}"
fi
fi
if [ ! -c "$dev" ]; then
dev=$(echo 'info chardev' | nc -q 1 -w 1 localhost "$MON_PORT" | tr -d '\000')
dev="${dev#*serial0}"
dev="${dev#*pty:}"
dev="${dev%%$'\n'*}"
dev="${dev%%$'\r'*}"
fi
if [ ! -c "$dev" ]; then
error "Device '$dev' not found!"
finish 34 && return 34
fi
QEMU_TERM="$dev"
return 0
}
_graceful_shutdown() {
local code=$?
local pid url response
set +e
if [ -f "$QEMU_END" ]; then
echo && info "Received $1 signal while already shutting down..."
return
fi
touch "$QEMU_END"
echo && info "Received $1 signal, sending shutdown command..."
if [ ! -s "$QEMU_PID" ]; then
echo && error "QEMU PID file does not exist?"
finish "$code" && return "$code"
fi
pid=$(<"$QEMU_PID")
if ! isAlive "$pid"; then
echo && error "QEMU process does not exist?"
finish "$code" && return "$code"
fi
# Don't send the powerdown signal because vDSM ignores ACPI signals # Don't send the powerdown signal because vDSM ignores ACPI signals
# echo 'system_powerdown' | nc -q 1 -w 1 localhost "$MON_PORT" > /dev/null # nc -q 1 -w 1 -U "$QEMU_DIR/monitor.sock" &> /dev/null <<<'system_powerdown' || :
# Send shutdown command to guest agent via serial port # Send shutdown command to guest agent via serial port
API_TIMEOUT=$(strip "$API_TIMEOUT")
url="http://$API_HOST/read?command=$API_CMD&timeout=$API_TIMEOUT" url="http://$API_HOST/read?command=$API_CMD&timeout=$API_TIMEOUT"
response=$(curl -sk -m "$(( API_TIMEOUT+2 ))" -S "$url" 2>&1) response=$(curl -sk -m "$(( API_TIMEOUT+2 ))" -S "$url" 2>&1)
@@ -150,45 +137,121 @@ _graceful_shutdown() {
response="${response#*message\"\: \"}" response="${response#*message\"\: \"}"
[ -z "$response" ] && response="second signal" [ -z "$response" ] && response="second signal"
echo && error "Forcefully terminating because of: ${response%%\"*}" echo && error "Forcefully terminating because of: ${response%%\"*}"
{ kill -15 "$pid" || true; } 2>/dev/null kill -15 -- "$pid" 2>/dev/null || :
fi fi
return 0
}
normalizeTimeout() {
local term_grace=3 # seconds before loop ends to send SIGTERM
local cleanup_grace=3 # seconds reserved after the loop for cleanup
local elapsed
local timeout_left
local min
TIMEOUT=$(strip "$TIMEOUT")
if [[ ! "$TIMEOUT" =~ ^[0-9]+$ ]]; then
TIMEOUT=115
fi
if (( TIMEOUT >= 30 )); then
term_grace=5
cleanup_grace=5
elif (( TIMEOUT >= 15 )); then
term_grace=4
cleanup_grace=4
fi
elapsed=$((SECONDS - start))
timeout_left=$((TIMEOUT - elapsed))
min=$((term_grace + cleanup_grace + 1))
(( timeout_left < min )) && timeout_left=$min
wait_until=$((timeout_left - cleanup_grace))
sigterm_at=$((wait_until - term_grace))
return 0
}
waitForShutdown() {
local cnt=0 local cnt=0
local pid="$1"
local name="$APP"
local slp
while [ "$cnt" -lt "$QEMU_TIMEOUT" ]; do while (( cnt <= wait_until )); do
sleep 1 &
slp=$!
# Stop waiting if the process has exited
! isAlive "$pid" && break ! isAlive "$pid" && break
sleep 1 # Workaround for stale/zombie QEMU pid file
cnt=$((cnt+1))
[[ "$DEBUG" == [Yy1]* ]] && info "Shutting down, waiting... ($cnt/$QEMU_TIMEOUT)"
# Workaround for zombie pid
[ ! -s "$QEMU_PID" ] && break [ ! -s "$QEMU_PID" ] && break
if (( cnt == sigterm_at )); then
info "${name^} is still running, sending SIGTERM... ($cnt/$wait_until)"
kill -15 -- "$pid" 2>/dev/null || :
elif (( cnt > 0 )) && enabled "${DEBUG:-}"; then
info "Waiting for $name to shut down... ($cnt/$wait_until)"
fi
wait "$slp"
(( cnt++ ))
done done
if [ "$cnt" -ge "$QEMU_TIMEOUT" ]; then return 0
echo && error "Shutdown timeout reached, aborting..."
fi
finish "$code" && return "$code"
} }
MON_OPTS="\ graceful_shutdown() {
-pidfile $QEMU_PID \
-name $PROCESS,process=$PROCESS,debug-threads=on \
-monitor telnet:localhost:$MON_PORT,server,nowait,nodelay"
if [[ "$CONSOLE" != [Yy]* ]]; then local sig="$1"
local pid=""
local code=0
MON_OPTS+=" -daemonize -D $QEMU_LOG" [[ $BASHPID != "$TRAP_PID" ]] && return
_trap _graceful_shutdown SIGTERM SIGHUP SIGINT SIGABRT SIGQUIT code=$(signalCode "$sig")
fi if [ -f "$QEMU_END" ]; then
echo && info "Received $1 signal while already shutting down..."
return
fi
set +e
start=$SECONDS
touch "$QEMU_END"
echo && info "Received $1 signal, sending shutdown command..."
if ! readQemuPid pid; then
warn "QEMU PID file ($QEMU_PID) does not exist?"
finish "$code"
fi
if ! isAlive "$pid"; then
warn "QEMU process with PID $pid does not exist?"
finish "$code"
fi
sendGuestShutdown "$pid"
normalizeTimeout
waitForShutdown "$pid"
finish "$code"
}
! enabled "$SHUTDOWN" && return 0
[ -n "${QEMU_TIMEOUT:-}" ] && TIMEOUT="$QEMU_TIMEOUT"
_trap graceful_shutdown SIGTERM SIGHUP SIGABRT SIGQUIT
return 0 return 0
+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
+148 -70
View File
@@ -7,15 +7,31 @@ set -Eeuo pipefail
: "${CPU_FLAGS:=""}" : "${CPU_FLAGS:=""}"
: "${CPU_MODEL:=""}" : "${CPU_MODEL:=""}"
CLOCKSOURCE="tsc" HOST_CPU=$(strip "$HOST_CPU")
[[ "${ARCH,,}" == "arm64" ]] && CLOCKSOURCE="arch_sys_counter" CPU_FLAGS=$(strip "$CPU_FLAGS")
CLOCK="/sys/devices/system/clocksource/clocksource0/current_clocksource" CPU_MODEL=$(strip "$CPU_MODEL")
selectClocksource() {
CLOCKSOURCE="tsc"
[[ "${ARCH,,}" == "arm64" ]] && CLOCKSOURCE="arch_sys_counter"
CLOCK="/sys/devices/system/clocksource/clocksource0/current_clocksource"
return 0
}
checkClocksource() {
local result
if [ ! -f "$CLOCK" ]; then
warn "file \"$CLOCK\" cannot be found?"
return 0
fi
if [ ! -f "$CLOCK" ]; then
warn "file \"$CLOCK\" cannot 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.." ;;
@@ -23,29 +39,38 @@ 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 [[ "$KVM" != [Nn]* ]]; 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!"
[[ "$DEBUG" != [Yy1]* ]] && exit 88 ! enabled "$DEBUG" && exit 88
fi fi
return 0
}
configureKvmCpuModel() {
CPU_FEATURES="kvm=on,l3-cache=on,+hypervisor"
KVM_OPTS=",accel=kvm -enable-kvm -global kvm-pit.lost_tick_policy=discard"
if [ -z "$CPU_MODEL" ]; then if [ -z "$CPU_MODEL" ]; then
CPU_MODEL="host" CPU_MODEL="host"
CPU_FEATURES+=",migratable=no" CPU_FEATURES+=",migratable=no"
fi fi
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
@@ -53,7 +78,7 @@ if [[ "$KVM" != [Nn]* ]]; then
else else
# Intel processor # Intel processor
local vmx
vmx=$(sed -ne '/^vmx flags/s/^.*: //p' /proc/cpuinfo) vmx=$(sed -ne '/^vmx flags/s/^.*: //p' /proc/cpuinfo)
if grep -qw "tsc_scaling" <<< "$vmx"; then if grep -qw "tsc_scaling" <<< "$vmx"; then
@@ -62,7 +87,35 @@ if [[ "$KVM" != [Nn]* ]]; then
fi fi
else return 0
}
configureKvm() {
configureKvmCpuModel
checkSse42
appendKvmInvtscFeature
return 0
}
configureTcgCpuModel() {
if [ -n "$CPU_MODEL" ]; then
return 0
fi
if [[ "$ARCH" == "amd64" ]]; then
CPU_MODEL="max"
CPU_FEATURES+=",migratable=no"
else
CPU_MODEL="qemu64"
fi
return 0
}
configureTcg() {
KVM_OPTS="" KVM_OPTS=""
CPU_FEATURES="l3-cache=on,+hypervisor" CPU_FEATURES="l3-cache=on,+hypervisor"
@@ -71,70 +124,95 @@ else
KVM_OPTS=" -accel tcg,thread=multi" KVM_OPTS=" -accel tcg,thread=multi"
fi fi
if [ -z "$CPU_MODEL" ]; then configureTcgCpuModel
if [[ "$ARCH" == "amd64" ]]; then CPU_FEATURES+=",+ssse3,+sse4.1,+sse4.2"
CPU_MODEL="max"
CPU_FEATURES+=",migratable=no" return 0
}
extractHostCpuArgument() {
local args prefix suffix param
if [[ "$ARGUMENTS" == *"-cpu host,"* ]]; then
args="${ARGUMENTS} "
prefix="${args/-cpu host,*/}"
suffix="${args/*-cpu host,/}"
param="${suffix%% *}"
suffix="${suffix#* }"
args="${prefix}${suffix}"
ARGUMENTS="${args::-1}"
if [ -z "$CPU_FLAGS" ]; then
CPU_FLAGS="$param"
else else
CPU_MODEL="qemu64" CPU_FLAGS+=",$param"
fi
else
if [[ "$ARGUMENTS" == *"-cpu host"* ]]; then
ARGUMENTS="${ARGUMENTS//-cpu host/}"
fi
fi
return 0
}
composeCpuFlags() {
if [ -z "$CPU_FLAGS" ]; then
if [ -z "$CPU_FEATURES" ]; then
CPU_FLAGS="$CPU_MODEL"
else
CPU_FLAGS="$CPU_MODEL,$CPU_FEATURES"
fi
else
if [ -z "$CPU_FEATURES" ]; then
CPU_FLAGS="$CPU_MODEL,$CPU_FLAGS"
else
CPU_FLAGS="$CPU_MODEL,$CPU_FEATURES,$CPU_FLAGS"
fi fi
fi fi
CPU_FEATURES+=",+ssse3,+sse4.1,+sse4.2" return 0
}
fi configureHostCpuName() {
if [[ "$ARGUMENTS" == *"-cpu host,"* ]]; then if [ -z "$HOST_CPU" ]; then
[[ "${CPU,,}" != "unknown" ]] && HOST_CPU="$CPU"
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 fi
if [ -n "$HOST_CPU" ]; then
HOST_CPU="${HOST_CPU%%,*},,"
else
HOST_CPU="QEMU, Virtual CPU,"
if [ "$ARCH" == "amd64" ]; then
HOST_CPU+=" X86_64"
else
HOST_CPU+=" $ARCH"
fi
fi
return 0
}
selectClocksource
checkClocksource
flags=$(sed -ne '/^flags/s/^.*: //p' /proc/cpuinfo)
if ! disabled "$KVM"; then
configureKvm
else else
configureTcg
if [[ "$ARGUMENTS" == *"-cpu host"* ]]; then
ARGUMENTS="${ARGUMENTS//-cpu host/}"
fi
fi fi
if [ -z "$CPU_FLAGS" ]; then extractHostCpuArgument
if [ -z "$CPU_FEATURES" ]; then composeCpuFlags
CPU_FLAGS="$CPU_MODEL" configureHostCpuName
else
CPU_FLAGS="$CPU_MODEL,$CPU_FEATURES"
fi
else
if [ -z "$CPU_FEATURES" ]; then
CPU_FLAGS="$CPU_MODEL,$CPU_FLAGS"
else
CPU_FLAGS="$CPU_MODEL,$CPU_FEATURES,$CPU_FLAGS"
fi
fi
if [ -z "$HOST_CPU" ]; then
[[ "${CPU,,}" != "unknown" ]] && HOST_CPU="$CPU"
fi
if [ -n "$HOST_CPU" ]; then
HOST_CPU="${HOST_CPU%%,*},,"
else
HOST_CPU="QEMU, Virtual CPU,"
if [ "$ARCH" == "amd64" ]; then
HOST_CPU+=" X86_64"
else
HOST_CPU+=" $ARCH"
fi
fi
return 0 return 0
+215 -161
View File
@@ -2,7 +2,7 @@
set -Eeuo pipefail set -Eeuo pipefail
trap 'error "Status $? while: $BASH_COMMAND (line $LINENO/$BASH_LINENO)"' ERR trap 'error "Status $? while: $BASH_COMMAND (line $LINENO/$BASH_LINENO)"' ERR
[[ "${TRACE:-}" == [Yy1]* ]] && set -o functrace && trap 'echo "# $BASH_COMMAND" >&2' DEBUG enabled "${TRACE:-}" && set -o functrace && trap 'echo "# $BASH_COMMAND" >&2' DEBUG
[ ! -f "/run/entry.sh" ] && error "Script must be run inside the container!" && exit 11 [ ! -f "/run/entry.sh" ] && error "Script must be run inside the container!" && exit 11
[ "$(id -u)" -ne "0" ] && error "Script must be executed with root privileges." && exit 12 [ "$(id -u)" -ne "0" ] && error "Script must be executed with root privileges." && exit 12
@@ -13,7 +13,6 @@ trap 'error "Status $? while: $BASH_COMMAND (line $LINENO/$BASH_LINENO)"' ERR
: "${KVM:="Y"}" # KVM acceleration : "${KVM:="Y"}" # KVM acceleration
: "${DEBUG:="N"}" # Disable debugging mode : "${DEBUG:="N"}" # Disable debugging mode
: "${COUNTRY:=""}" # Country code for mirror : "${COUNTRY:=""}" # Country code for mirror
: "${CONSOLE:="N"}" # Disable console mode
: "${ALLOCATE:=""}" # Preallocate diskspace : "${ALLOCATE:=""}" # Preallocate diskspace
: "${ARGUMENTS:=""}" # Extra QEMU parameters : "${ARGUMENTS:=""}" # Extra QEMU parameters
: "${CPU_CORES:="2"}" # Amount of CPU cores : "${CPU_CORES:="2"}" # Amount of CPU cores
@@ -22,52 +21,232 @@ trap 'error "Status $? while: $BASH_COMMAND (line $LINENO/$BASH_LINENO)"' ERR
: "${DISK_SIZE:="16G"}" # Initial data disk size : "${DISK_SIZE:="16G"}" # Initial data disk size
: "${STORAGE:="/storage"}" # Storage folder location : "${STORAGE:="/storage"}" # Storage folder location
# Helper variables detectEngine() {
if [ -f "/run/.containerenv" ]; then
ENGINE="${container:-}"
if [[ "${ENGINE,,}" == *"podman"* ]]; then
ROOTLESS="Y"
ENGINE="Podman"
else
[ -z "$ENGINE" ] && ENGINE="Kubernetes"
fi
fi
return 0
}
checkPrivileged() {
local cap_bnd
local last_cap
local max_cap
# Get the capability bounding set
cap_bnd=$(grep '^CapBnd:' /proc/$$/status | awk '{print $2}')
cap_bnd=$(printf "%d" "0x${cap_bnd}")
# Get the last capability number
last_cap=$(cat /proc/sys/kernel/cap_last_cap)
# Calculate the maximum capability value
max_cap=$(((1 << (last_cap + 1)) - 1))
if [ "$cap_bnd" -eq "$max_cap" ]; then
ROOTLESS="N"
PRIVILEGED="Y"
fi
return 0
}
normalizeCpuCores() {
CPU_CORES=$(strip "$CPU_CORES")
[ -z "$CPU_CORES" ] && CPU_CORES=2
[[ "${CPU_CORES,,}" == "max" ]] && CPU_CORES="$CORES"
[[ "${CPU_CORES,,}" == "half" ]] && CPU_CORES=$(( CORES / 2 ))
[ -z "${CPU_CORES##*[!0-9]*}" ] && error "Invalid amount of CPU_CORES: $CPU_CORES" && exit 15
[ "$CPU_CORES" -lt "1" ] && CPU_CORES=1
if [ "$CPU_CORES" -gt "$CORES" ]; then
warn "The amount for CPU_CORES (${CPU_CORES}) exceeds the amount of logical cores available (${CORES}) and will be limited."
CPU_CORES="$CORES"
fi
return 0
}
checkStorage() {
# Check system
QEMU_DIR="/run/shm"
if [ ! -d "/dev/shm" ]; then
error "Directory /dev/shm not found!" && exit 14
else
[ ! -d "$QEMU_DIR" ] && ln -s /dev/shm "$QEMU_DIR"
fi
QEMU_PID="$QEMU_DIR/qemu.pid"
# Check folder
if [[ "${STORAGE,,}" != "/storage" ]]; then
mkdir -p "$STORAGE"
fi
if [ ! -d "$STORAGE" ]; then
error "Storage folder ($STORAGE) not found!" && exit 13
fi
if [ ! -w "$STORAGE" ]; then
msg="Storage folder ($STORAGE) is not writeable!"
msg+=" If SELinux is active, you need to add the \":Z\" flag to the bind mount."
error "$msg" && exit 13
fi
return 0
}
checkFilesystem() {
# Check filesystem
FS=$(stat -f -c %T "$STORAGE")
if [[ "${FS,,}" == "ecryptfs" || "${FS,,}" == "tmpfs" ]]; then
DISK_IO="threads"
DISK_CACHE="writeback"
fi
return 0
}
normalizeRamSize() {
# Read memory
RAM_AVAIL=$(free -b | grep -m 1 Mem: | awk '{print $7}')
RAM_TOTAL=$(free -b | grep -m 1 Mem: | awk '{print $2}')
RAM_SPARE=500000000
RAM_MINIMUM=136314880
RAM_SIZE=$(strip "$RAM_SIZE")
RAM_SIZE="${RAM_SIZE// /}"
[ -z "$RAM_SIZE" ] && RAM_SIZE="2G"
if [[ "${RAM_SIZE,,}" != "max" && "${RAM_SIZE,,}" != "half" ]]; then
if [ -z "${RAM_SIZE//[0-9. ]}" ]; then
[ "${RAM_SIZE%%.*}" -lt "130" ] && RAM_SIZE="${RAM_SIZE}G" || RAM_SIZE="${RAM_SIZE}M"
fi
RAM_SIZE=$(echo "${RAM_SIZE^^}" | sed 's/MB/M/g;s/GB/G/g;s/TB/T/g')
! numfmt --from=iec "$RAM_SIZE" &>/dev/null && error "Invalid RAM_SIZE: $RAM_SIZE" && exit 16
wanted=$(numfmt --from=iec "$RAM_SIZE")
[ "$wanted" -lt "$RAM_MINIMUM" ] && error "RAM_SIZE is too low: $RAM_SIZE" && exit 16
fi
return 0
}
checkKvm() {
# Check KVM support
if [[ "${PLATFORM,,}" == "x64" ]]; then
TARGET="amd64"
else
TARGET="arm64"
fi
if disabled "$KVM"; then
warn "KVM acceleration is disabled, this will cause the machine to run about 10 times slower!"
else
if [[ "${ARCH,,}" != "$TARGET" ]]; then
KVM="N"
warn "your CPU architecture is ${ARCH^^} and cannot provide KVM acceleration for ${PLATFORM^^} instructions, so the machine will run about 10 times slower."
fi
fi
if ! disabled "$KVM"; then
KVM_ERR=""
if [ ! -e /dev/kvm ]; then
KVM_ERR="(/dev/kvm is missing)"
else
if ! sh -c 'echo -n > /dev/kvm' &> /dev/null; then
KVM_ERR="(/dev/kvm is unwriteable)"
else
if [[ "${PLATFORM,,}" == "x64" ]]; then
flags=$(sed -ne '/^flags/s/^.*: //p' /proc/cpuinfo)
if ! grep -qw "vmx\|svm" <<< "$flags"; then
KVM_ERR="(not enabled in BIOS)"
fi
if ! grep -qw "sse4_2" <<< "$flags"; then
error "Your CPU does not have the SSE4 instruction set that Virtual DSM requires!"
! enabled "$DEBUG" && exit 88
fi
fi
fi
fi
if [ -n "$KVM_ERR" ]; then
KVM="N"
if [[ "$OSTYPE" =~ ^darwin ]]; then
warn "you are using macOS which has no KVM support, so the machine will run about 10 times slower."
else
kernel=$(uname -a)
case "${kernel,,}" in
*"microsoft"* )
error "Please bind '/dev/kvm' as a volume in the optional container settings when using Docker Desktop." ;;
*"synology"* )
error "Please make sure that Synology VMM (Virtual Machine Manager) is installed and that '/dev/kvm' is binded to this container." ;;
*)
error "KVM acceleration is not available $KVM_ERR, this will cause the machine to run about 10 times slower."
error "See the FAQ for possible causes, or disable acceleration by adding the \"KVM=N\" variable (not recommended)." ;;
esac
! enabled "$DEBUG" && exit 88
fi
fi
fi
return 0
}
# Sanitize variables
TZ=$(strip "$TZ")
STORAGE=$(strip "$STORAGE")
COUNTRY=$(strip "$COUNTRY")
DISK_SIZE=$(strip "$DISK_SIZE")
# Helper variables
ROOTLESS="N" ROOTLESS="N"
PRIVILEGED="N" PRIVILEGED="N"
ENGINE="Docker" ENGINE="Docker"
PROCESS="${APP,,}" PROCESS="${APP,,}"
PROCESS="${PROCESS// /-}" PROCESS="${PROCESS// /-}"
if [ -f "/run/.containerenv" ]; then detectEngine
ENGINE="${container:-}"
if [[ "${ENGINE,,}" == *"podman"* ]]; then
ROOTLESS="Y"
ENGINE="Podman"
else
[ -z "$ENGINE" ] && ENGINE="Kubernetes"
fi
fi
echo " Starting $APP for $ENGINE v$(</run/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"
TEMPLATE="/var/www/index.html" TEMPLATE="/var/www/index.html"
FOOTER1="$APP for $ENGINE v$(</run/version)" FOOTER1="$APP for $ENGINE v$(</etc/version)"
FOOTER2="<a href='$SUPPORT'>$SUPPORT</a>" 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)
@@ -79,76 +258,10 @@ if grep -qi "socket(s)" <<< "$(lscpu)"; then
[ "$SOCKETS" -lt "1" ] && SOCKETS=1 [ "$SOCKETS" -lt "1" ] && SOCKETS=1
fi fi
CPU_CORES="${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"
rm -f "$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="${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/}"
@@ -163,70 +276,11 @@ 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 [[ "$KVM" == [Nn]* ]]; 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 [[ "$KVM" != [Nn]* ]]; 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!"
[[ "$DEBUG" != [Yy1]* ]] && 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
[[ "$DEBUG" != [Yy1]* ]] && exit 88
fi
fi
fi
# Cleanup files # Cleanup files
rm -f /run/shm/qemu.* rm -f "$QEMU_DIR"/dsm.url
rm -f /run/shm/dsm.url rm -f "$QEMU_DIR"/qemu.* "$QEMU_DIR"/*.pid "$QEMU_DIR"/*.sock
# Cleanup dirs # Cleanup dirs
rm -rf /tmp/dsm rm -rf /tmp/dsm
+72 -38
View File
@@ -5,11 +5,22 @@ set -Eeuo pipefail
: "${HOST_MAC:=""}" : "${HOST_MAC:=""}"
: "${HOST_DEBUG:=""}" : "${HOST_DEBUG:=""}"
: "${HOST_SERIAL:=""}"
: "${HOST_MODEL:=""}" : "${HOST_MODEL:=""}"
: "${HOST_SERIAL:=""}"
: "${GUEST_SERIAL:=""}" : "${GUEST_SERIAL:=""}"
if [ -n "$HOST_MAC" ]; then # Sanitize variables
HOST_MAC=$(strip "$HOST_MAC")
HOST_MODEL=$(strip "$HOST_MODEL")
HOST_SERIAL=$(strip "$HOST_SERIAL")
GUEST_SERIAL=$(strip "$GUEST_SERIAL")
validateHostMac() {
local m
if [ -z "$HOST_MAC" ]; then
return 0
fi
HOST_MAC="${HOST_MAC//-/:}" HOST_MAC="${HOST_MAC//-/:}"
@@ -22,54 +33,77 @@ if [ -n "$HOST_MAC" ]; then
error "Invalid HOST_MAC address: '$HOST_MAC', should be 12 or 17 digits long!" && exit 28 error "Invalid HOST_MAC address: '$HOST_MAC', should be 12 or 17 digits long!" && exit 28
fi fi
fi return 0
}
HOST_ARGS=() buildHostArguments() {
HOST_ARGS+=("-cpu=$CPU_CORES")
HOST_ARGS+=("-cpu_arch=$HOST_CPU")
[ -n "$HOST_MAC" ] && HOST_ARGS+=("-mac=$HOST_MAC") HOST_ARGS=()
[ -n "$HOST_MODEL" ] && HOST_ARGS+=("-model=$HOST_MODEL") HOST_ARGS+=("-cpu=$CPU_CORES")
[ -n "$HOST_SERIAL" ] && HOST_ARGS+=("-hostsn=$HOST_SERIAL") HOST_ARGS+=("-cpu_arch=$HOST_CPU")
[ -n "$GUEST_SERIAL" ] && HOST_ARGS+=("-guestsn=$GUEST_SERIAL")
if [[ "$HOST_DEBUG" == [Yy1]* ]]; then [ -n "$HOST_MAC" ] && HOST_ARGS+=("-mac=$HOST_MAC")
set -x [ -n "$HOST_MODEL" ] && HOST_ARGS+=("-model=$HOST_MODEL")
./host.bin "${HOST_ARGS[@]}" & [ -n "$HOST_SERIAL" ] && HOST_ARGS+=("-hostsn=$HOST_SERIAL")
{ set +x; } 2>/dev/null [ -n "$GUEST_SERIAL" ] && HOST_ARGS+=("-guestsn=$GUEST_SERIAL")
echo
else
./host.bin "${HOST_ARGS[@]}" >/dev/null &
fi
cnt=0 return 0
sleep 0.2 }
while ! nc -z -w2 127.0.0.1 "$COM_PORT" > /dev/null 2>&1; do startHostBinary() {
sleep 0.1
cnt=$((cnt + 1))
(( cnt > 50 )) && error "Failed to connect to qemu-host.." && exit 58
done
cnt=0 if enabled "$HOST_DEBUG"; then
set -x
./host.bin "${HOST_ARGS[@]}" &
{ set +x; } 2>/dev/null
echo "$!" > "$HOST_PID"
echo
else
./host.bin "${HOST_ARGS[@]}" >/dev/null &
echo "$!" > "$HOST_PID"
fi
while ! nc -z -w2 127.0.0.1 "$CHR_PORT" > /dev/null 2>&1; do return 0
sleep 0.1 }
cnt=$((cnt + 1))
(( cnt > 50 )) && error "Failed to connect to qemu-host.." && exit 59
done
# Configure serial ports waitForPort() {
if [[ "$CONSOLE" != [Yy]* ]]; then local port="$1"
SERIAL_OPTS="-serial pty" local exit_code="$2"
else local cnt=0
SERIAL_OPTS="-serial mon:stdio"
fi
SERIAL_OPTS+=" \ while ! nc -z -w2 127.0.0.1 "$port" > /dev/null 2>&1; do
sleep 0.1
cnt=$((cnt + 1))
(( cnt > 50 )) && error "Failed to connect to qemu-host.." && exit "$exit_code"
done
return 0
}
configureSerialPorts() {
# Configure serial ports
SERIAL_OPTS="-serial mon:stdio \
-device virtio-serial-pci,id=virtio-serial0,bus=pcie.0,addr=0x3 \ -device virtio-serial-pci,id=virtio-serial0,bus=pcie.0,addr=0x3 \
-chardev socket,id=charchannel0,host=127.0.0.1,port=$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
+50 -13
View File
@@ -2,38 +2,75 @@
set -Eeuo pipefail set -Eeuo pipefail
: "${COM_PORT:="2210"}" # Comm port : "${COM_PORT:="2210"}" # Comm port
: "${MON_PORT:="7100"}" # Monitor port
: "${WEB_PORT:="5000"}" # Webserver port : "${WEB_PORT:="5000"}" # Webserver port
: "${CHR_PORT:="12345"}" # Character port : "${CHR_PORT:="12345"}" # Character port
: "${WSD_PORT:="8004"}" # Websockets port : "${WSD_PORT:="8004"}" # Websockets port
cp -r /var/www/* /run/shm # Sanitize port variables
rm -f /var/run/websocketd.pid COM_PORT=$(strip "$COM_PORT")
WEB_PORT=$(strip "$WEB_PORT")
CHR_PORT=$(strip "$CHR_PORT")
WSD_PORT=$(strip "$WSD_PORT")
html "Starting $APP for $ENGINE..." WEB_PID="/run/nginx.pid"
WSD_PID="$QEMU_DIR/websocketd.pid"
if [[ "${WEB:-}" != [Nn]* ]]; then prepareWebFiles() {
cp -r /var/www/* "$QEMU_DIR"
rm -f "$WSD_PID" "$WEB_PID"
return 0
}
configureWebPorts() {
sed -i "s/listen 5000 default_server;/listen $WEB_PORT default_server;/g" /etc/nginx/sites-enabled/web.conf
sed -i "s/proxy_pass http:\/\/127.0.0.1:8004\/;/proxy_pass http:\/\/127.0.0.1:$WSD_PORT\/;/g" /etc/nginx/sites-enabled/web.conf
return 0
}
configureIpv6Listen() {
# shellcheck disable=SC2143
if [ -f /proc/net/if_inet6 ] && [[ "$(cat /proc/sys/net/ipv6/conf/all/disable_ipv6 2>/dev/null)" != "1" ]] && [ -n "$(ifconfig -a | grep inet6)" ]; then
sed -i "s/listen $WEB_PORT default_server;/listen [::]:$WEB_PORT default_server ipv6only=off;/g" /etc/nginx/sites-enabled/web.conf
fi
return 0
}
configureWebServer() {
mkdir -p /etc/nginx/sites-enabled mkdir -p /etc/nginx/sites-enabled
cp /etc/nginx/default.conf /etc/nginx/sites-enabled/web.conf 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 configureWebPorts
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 configureIpv6Listen
# shellcheck disable=SC2143 return 0
if [ -f /proc/net/if_inet6 ] && [ -n "$(ifconfig -a | grep inet6)" ]; then }
sed -i "s/listen $WEB_PORT default_server;/listen [::]:$WEB_PORT default_server ipv6only=off;/g" /etc/nginx/sites-enabled/web.conf startWebServer() {
fi
# Start webserver # Start webserver
nginx -e stderr nginx -e stderr
}
startWebsocketServer() {
# 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 >/var/log/websocketd.log &
echo "$!" > /var/run/websocketd.pid echo "$!" > "$WSD_PID"
}
prepareWebFiles
html "Starting $APP for $ENGINE..."
if ! disabled "${WEB:-}"; then
configureWebServer
startWebServer
startWebsocketServer
fi fi
return 0 return 0
+122 -6
View File
@@ -7,6 +7,40 @@ info () { printf "%b%s%b" "\E[1;34m \E[1;36m" "${1:-}" "\E[0m\n"; }
error () { printf "%b%s%b" "\E[1;31m " "ERROR: ${1:-}" "\E[0m\n" >&2; } error () { printf "%b%s%b" "\E[1;31m " "ERROR: ${1:-}" "\E[0m\n" >&2; }
warn () { printf "%b%s%b" "\E[1;31m " "Warning: ${1:-}" "\E[0m\n" >&2; } warn () { printf "%b%s%b" "\E[1;31m " "Warning: ${1:-}" "\E[0m\n" >&2; }
strip() {
local value="${1:-}"
# Remove surrounding whitespace
value="${value#"${value%%[![:space:]]*}"}"
value="${value%"${value##*[![:space:]]}"}"
# Remove leading/trailing single/double quotes
value="${value%\"}"
value="${value#\"}"
value="${value%\'}"
value="${value#\'}"
# Remove surrounding whitespace again
value="${value#"${value%%[![:space:]]*}"}"
value="${value%"${value##*[![:space:]]}"}"
printf '%s' "$value"
}
enabled() {
case "$(strip "${1:-}")" in
Y|y|YES|Yes|yes|TRUE|True|true|1|ON|On|on) return 0 ;;
*) return 1 ;;
esac
}
disabled() {
case "$(strip "${1:-}")" in
N|n|NO|No|no|FALSE|False|false|0|OFF|Off|off) return 0 ;;
*) return 1 ;;
esac
}
formatBytes() { formatBytes() {
local result local result
result=$(numfmt --to=iec --suffix=B "$1" | sed -r 's/([A-Z])/ \1/' | sed 's/ B/ bytes/g;') result=$(numfmt --to=iec --suffix=B "$1" | sed -r 's/([A-Z])/ \1/' | sed 's/ B/ bytes/g;')
@@ -28,6 +62,7 @@ formatBytes() {
isAlive() { isAlive() {
local pid="$1" local pid="$1"
[ -z "$pid" ] && return 1
if kill -0 "$pid" 2>/dev/null; then if kill -0 "$pid" 2>/dev/null; then
return 0 return 0
@@ -36,23 +71,67 @@ isAlive() {
return 1 return 1
} }
pKill() { waitPid() {
local i=0
local pid="$1" local pid="$1"
local timeout="${2:-10}"
{ kill -15 "$pid" || true; } 2>/dev/null while [ -n "$pid" ] && isAlive "$pid"; do
while isAlive "$pid"; do
sleep 0.2 sleep 0.2
i=$((i + 1))
(( i >= timeout * 5 )) && return 1
done done
return 0 return 0
} }
waitPidFile() {
local i=0
local pid=""
local file="$1"
local timeout="${2:-10}"
[ ! -s "$file" ] && return 0
! read -r pid <"$file" && return 0
[ -z "$pid" ] && return 0
while [ -s "$file" ] && isAlive "$pid"; do
sleep 0.2
i=$((i + 1))
(( i >= timeout * 5 )) && return 1
done
rm -f -- "$file"
return 0
}
pKill() {
local pid="$1"
local timeout="${2:-10}"
{ kill -15 -- "$pid" || :; } 2>/dev/null
if ! waitPid "$pid" "$timeout"; then
warn "Timed out while waiting for PID $pid"
fi
return 0
}
fWait() { fWait() {
local i=0
local name="$1" local name="$1"
local timeout="${2:-10}"
[ -z "$name" ] && return 0
while pgrep -f -l "$name" >/dev/null; do while pgrep -f -l "$name" >/dev/null; do
sleep 0.2 sleep 0.2
i=$((i + 1))
if (( i >= timeout * 5 )); then
warn "Timed out while waiting for process: $name"
break
fi
done done
return 0 return 0
@@ -60,9 +139,44 @@ fWait() {
fKill() { fKill() {
local name="$1" local name="$1"
local timeout="${2:-10}"
{ pkill -f "$name" || true; } 2>/dev/null [ -z "$name" ] && return 0
fWait "$name"
{ pkill -f "$name" || :; } 2>/dev/null
fWait "$name" "$timeout"
return 0
}
sKill() {
local pid=""
local file="$1"
[ ! -s "$file" ] && return 0
! read -r pid <"$file" && return 0
[ -z "$pid" ] && return 0
if isAlive "$pid"; then
{ kill -15 -- "$pid" || :; } 2>/dev/null
fi
return 0
}
mKill() {
local timeout=10
local files=("$@")
for file in "${files[@]}"; do
sKill "$file"
done
for file in "${files[@]}"; do
if ! waitPidFile "$file" "$timeout"; then
warn "Timed out while waiting for PID file: $file"
fi
done
return 0 return 0
} }
@@ -178,6 +292,8 @@ cpu() {
hasDisk() { hasDisk() {
enabled "${DISK_DISABLE:-}" && return 1
[ -b "/disk" ] && return 0 [ -b "/disk" ] && return 0
[ -b "/disk1" ] && return 0 [ -b "/disk1" ] && return 0
[ -b "/dev/disk1" ] && return 0 [ -b "/dev/disk1" ] && return 0
+5 -1
View File
@@ -7,10 +7,14 @@ function getInfo() {
var url = "msg.html"; var url = "msg.html";
try { try {
if (request) {
request.abort();
}
if (window.XMLHttpRequest) { if (window.XMLHttpRequest) {
request = new XMLHttpRequest(); request = new XMLHttpRequest();
} else { } else {
throw "XMLHttpRequest not available!"; throw new Error("XMLHttpRequest not available!");
} }
request.onreadystatechange = processInfo; request.onreadystatechange = processInfo;