mirror of
https://github.com/vdsm/virtual-dsm.git
synced 2026-08-29 04:07:02 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b741d3e4b | ||
|
|
f1d29212ae | ||
|
|
fdee9f89a3 | ||
|
|
210a360797 | ||
|
|
991bb2627a | ||
|
|
764a186836 | ||
|
|
726786a98a | ||
|
|
2caf97d407 | ||
|
|
0e67286599 | ||
|
|
2b9fc80275 | ||
|
|
0d88774bd3 | ||
|
|
fa23052ed4 | ||
|
|
cc3267f258 | ||
|
|
fd002564a6 | ||
|
|
2703a97cfa | ||
|
|
29e2d034bf | ||
|
|
cbe3644335 | ||
|
|
c9a2a6966b | ||
|
|
498248f43e | ||
|
|
8963228b1c | ||
|
|
827d6d9e10 | ||
|
|
1c577abef4 | ||
|
|
20fd1a975c | ||
|
|
770774680c | ||
|
|
0c75aa81fe | ||
|
|
36ec57ac15 | ||
|
|
9c488c52b9 | ||
|
|
2ba756260f | ||
|
|
b306012743 | ||
|
|
61b3ee7e01 | ||
|
|
245036f844 | ||
|
|
68cadfe035 | ||
|
|
96102832b1 | ||
|
|
211a56bf73 | ||
|
|
506fb7b114 | ||
|
|
bcc3393b7f | ||
|
|
45c0f91d6e | ||
|
|
a1910546a6 | ||
|
|
637ed0e3a6 | ||
|
|
954a6076ad | ||
|
|
415aaa3038 | ||
|
|
c87e45719b | ||
|
|
102945ab48 | ||
|
|
1c3d490303 | ||
|
|
73fa70f52f | ||
|
|
7b095702fc | ||
|
|
f82b2769d3 | ||
|
|
cafe619028 | ||
|
|
e94a0c4a52 | ||
|
|
d6a353df0e | ||
|
|
050812e996 | ||
|
|
75280e9aa9 | ||
|
|
8dcc8ccee1 | ||
|
|
ba866e9e75 | ||
|
|
06e7a8edb9 | ||
|
|
495c67734a | ||
|
|
1557735331 | ||
|
|
f45b308c78 | ||
|
|
d5821cd782 | ||
|
|
312816b18e | ||
|
|
f42a3f1662 | ||
|
|
558e5022ca | ||
|
|
e03822f26e | ||
|
|
7b90ca30ba | ||
|
|
22c01bf2f1 | ||
|
|
1f601db8b0 | ||
|
|
e7e2c35cb7 | ||
|
|
50089112c0 | ||
|
|
f9538ce53b | ||
|
|
49d25a13d0 |
@@ -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
|
||||||
-
|
-
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ jobs:
|
|||||||
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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ jobs:
|
|||||||
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
|
||||||
|
|||||||
+29
-21
@@ -1,27 +1,21 @@
|
|||||||
# 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_26"
|
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 update
|
||||||
apt-get --no-install-recommends -y install \
|
apt-get --no-install-recommends -y install \
|
||||||
jq \
|
jq \
|
||||||
tini \
|
tini \
|
||||||
@@ -31,6 +25,7 @@ RUN set -eu && \
|
|||||||
unzip \
|
unzip \
|
||||||
nginx \
|
nginx \
|
||||||
procps \
|
procps \
|
||||||
|
ipcalc \
|
||||||
ethtool \
|
ethtool \
|
||||||
python3 \
|
python3 \
|
||||||
python3-pip \
|
python3-pip \
|
||||||
@@ -50,17 +45,30 @@ RUN set -eu && \
|
|||||||
inotify-tools \
|
inotify-tools \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
netcat-openbsd \
|
netcat-openbsd \
|
||||||
qemu-system-x86 && \
|
qemu-system-x86
|
||||||
wget "https://github.com/qemus/passt/releases/download/v${VERSION_PASST}/passt_${VERSION_PASST}_${TARGETARCH}.deb" -O /tmp/passt.deb -q --timeout=10 && \
|
|
||||||
dpkg -i /tmp/passt.deb && \
|
# Install Passt package
|
||||||
apt-get clean && \
|
wget "https://github.com/qemus/passt/releases/download/v${VERSION_PASST}/passt_${VERSION_PASST}_${TARGETARCH}.deb" -O /tmp/passt.deb -q --timeout=10
|
||||||
pip3 install --no-cache-dir --break-system-packages --root-user-action=ignore dissect.cstruct && \
|
dpkg -i /tmp/passt.deb
|
||||||
mkdir -p /etc/qemu && \
|
|
||||||
echo "allow br0" > /etc/qemu/bridge.conf && \
|
apt-get clean
|
||||||
unlink /etc/nginx/sites-enabled/default && \
|
|
||||||
sed -i 's/^worker_processes.*/worker_processes 1;/' /etc/nginx/nginx.conf && \
|
# Install Python dependencies
|
||||||
echo "$VERSION_ARG" > /etc/version && \
|
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/*
|
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/
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
# Environment Variables
|
||||||
|
|
||||||
|
This page lists all the environment variables that can be used to configure the container.
|
||||||
|
|
||||||
|
## 💽 Virtual DSM
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `URL` | | URL or local path of the DSM `.pat` installation file. When unset, the default Virtual DSM image is downloaded automatically. |
|
||||||
|
| `HOST_MAC` | | MAC address reported to DSM. |
|
||||||
|
| `HOST_MODEL` | | Synology host device 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 CPU cores assigned to the VM. Can also be set to `max` or `half`. |
|
||||||
|
| `CPU_MODEL` | `host` | QEMU CPU model to use. |
|
||||||
|
| `CPU_FLAGS` | | Additional QEMU CPU flags. |
|
||||||
|
| `HOST_CPU` | | CPU name reported to DSM. Automatically selected when unset. |
|
||||||
|
| `KVM` | `Y` | Enables KVM hardware acceleration. Set to `N` to disable. |
|
||||||
|
| `RAM_SIZE` | `2G` | Amount of RAM assigned to the VM, for example `2G`, `4G`, `max`, or `half`. |
|
||||||
|
| `RAM_CHECK` | `Y` | Checks whether enough host memory is available before starting the VM. |
|
||||||
|
|
||||||
|
## 💾 Storage
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `DISK_SIZE` | `256G` | Size of the main data disk. |
|
||||||
|
| `DISK_FMT` | `raw` | Disk image format, usually `raw` or `qcow2`. |
|
||||||
|
| `DISK_TYPE` | `scsi` | Disk controller/device type, such as `sata`, `scsi`, `nvme`, or `blk`. |
|
||||||
|
| `DISK_CACHE` | `none` | QEMU disk cache mode, for example `none` or `writeback`. |
|
||||||
|
| `DISK_IO` | `native` | QEMU disk I/O mode, for example `native`, `threads`, or `io_uring`. |
|
||||||
|
| `DISK_DISCARD` | `unmap` | Enables TRIM/unmap support for the data disk. |
|
||||||
|
| `DISK_ROTATION` | `1` | Rotation rate reported to the guest. Use `1` for SSD-like storage. |
|
||||||
|
| `DISK_FLAGS` | | Additional options used when creating qcow2 disks. |
|
||||||
|
| `ALLOCATE` | `N` | Preallocates disk space when creating the data disk. |
|
||||||
|
| `STORAGE` | `/storage` | Storage directory used for disks, firmware variables, and generated files. |
|
||||||
|
|
||||||
|
## 🌐 Networking
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `NETWORK` | `Y` | Network mode. Common values are `Y` for NAT, `passt`, `slirp`, or `N` to disable networking. |
|
||||||
|
| `DHCP` | `N` | Enables DHCP/macvtap mode so the VM receives an address from the external LAN. |
|
||||||
|
| `IP` | | Guest IP address override. |
|
||||||
|
| `MAC` | | Guest network adapter MAC address. |
|
||||||
|
| `HOST` | `VirtualDSM` | Hostname assigned to the VM. |
|
||||||
|
| `DEV` | `eth0` | Host/container network interface to use. |
|
||||||
|
| `MTU` | | Network MTU to use for the guest interface. |
|
||||||
|
| `MASK` | `255.255.255.0` | IPv4 netmask. |
|
||||||
|
| `TAP` | `dsm` | TAP/macvtap interface name. |
|
||||||
|
| `BRIDGE` | `docker` | Bridge name used for NAT networking. |
|
||||||
|
| `ADAPTER` | `virtio-net-pci` | QEMU network adapter model. |
|
||||||
|
| `HOST_PORTS` | | Ports reserved for services running on the host/container side. |
|
||||||
|
| `USER_PORTS` | | Additional ports to forward to the VM when using user-mode networking. |
|
||||||
|
| `DNSMASQ_OPTS` | | Additional dnsmasq options. |
|
||||||
|
| `DNSMASQ_DEBUG` | `N` | Enables dnsmasq log tailing. |
|
||||||
|
| `DNSMASQ_DISABLE` | `N` | Disables the internal dnsmasq resolver. |
|
||||||
|
| `PASST_OPTS` | | Additional passt options. |
|
||||||
|
| `PASST_DEBUG` | `N` | Enables passt debug output. |
|
||||||
|
|
||||||
|
## 🖥️ Display
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `DISPLAY` | `none` | QEMU display backend. Common values are `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` | Timeout used while waiting for DSM to shut down. |
|
||||||
|
| `API_TIMEOUT` | `90` | Timeout used 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 host helper. |
|
||||||
|
| `ARGUMENTS` | | Additional raw QEMU arguments appended to the generated command line. |
|
||||||
@@ -16,13 +16,17 @@ Virtual DSM in a Docker container.
|
|||||||
|
|
||||||
## Features ✨
|
## Features ✨
|
||||||
|
|
||||||
- Multiple disks
|
- Runs Virtual DSM inside a Docker container
|
||||||
- KVM acceleration
|
- Automatic download of the installation files
|
||||||
- Upgrades supported
|
- Web-based access to the DSM desktop
|
||||||
|
- Near-native performance with KVM acceleration
|
||||||
|
- Customizable CPU, memory, and storage allocation
|
||||||
|
- Supports multiple disks and physical disk passthrough
|
||||||
|
- Supports NAT, user-mode, macvlan, and macvtap networking
|
||||||
|
|
||||||
## Usage 🐳
|
## Usage 🐳
|
||||||
|
|
||||||
##### Via Docker Compose:
|
##### Docker Compose:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
services:
|
services:
|
||||||
@@ -44,22 +48,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:
|
||||||
|
|
||||||
[](https://codespaces.new/vdsm/virtual-dsm)
|
[](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:
|
||||||
@@ -248,18 +261,22 @@ 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.
|
||||||
|
|
||||||
### What are the differences compared to the standard DSM?
|
### What are the differences compared to the standard DSM?
|
||||||
|
|
||||||
There are only two minor differences: the Virtual Machine Manager package is not available, and Surveillance Station will not include any free licenses.
|
There are only two minor differences: the Virtual Machine Manager package is not available, and Surveillance Station will not include any free licenses.
|
||||||
|
|
||||||
### Is this project legal?
|
### Is this project legal?
|
||||||
|
|
||||||
Yes, this project contains only open-source code and does not distribute any copyrighted material. Neither does it try to circumvent any copyright protection measures. So under all applicable laws, this project will be considered legal.
|
Yes, this project contains only open-source code and does not distribute any material owned by Synology. Neither does it try to circumvent any copyright protection measures.
|
||||||
|
|
||||||
However, by installing Synology's Virtual DSM, you must accept their end-user license agreement, which does not permit installation on non-Synology hardware. So only run this container on an official Synology NAS, as any other use will be a violation of their terms and conditions.
|
However, by installing Synology's Virtual DSM, you must accept their end-user license agreement, which does not permit installation on non-Synology hardware. So only run this container on an official Synology NAS, as any other use will be a violation of their terms and conditions.
|
||||||
|
|
||||||
## Stars 🌟
|
## Stars 🌟
|
||||||
[](https://starchart.cc/vdsm/virtual-dsm)
|
[](https://github.com/vdsm/virtual-dsm/stargazers)
|
||||||
|
|
||||||
## Disclaimer ⚖️
|
## Disclaimer ⚖️
|
||||||
|
|
||||||
|
|||||||
+5
-2
@@ -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
|
||||||
|
|||||||
+258
-159
@@ -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,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"
|
||||||
@@ -104,21 +202,26 @@ 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"
|
||||||
|
|
||||||
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")
|
||||||
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
|
||||||
|
|
||||||
@@ -139,33 +242,15 @@ createDisk() {
|
|||||||
{ chattr +C "$DISK_FILE"; } || :
|
{ chattr +C "$DISK_FILE"; } || :
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$ALLOCATE" == [Nn]* ]]; then
|
if ! allocateRaw "$DISK_FILE" "$DATA_SIZE"; then
|
||||||
|
|
||||||
# Create an empty file
|
|
||||||
if ! truncate -s "$DATA_SIZE" "$DISK_FILE"; then
|
|
||||||
rm -f "$DISK_FILE"
|
rm -f "$DISK_FILE"
|
||||||
error "$FAIL" && exit 77
|
error "$FAIL" && exit 77
|
||||||
fi
|
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
|
|
||||||
;;
|
;;
|
||||||
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"
|
||||||
@@ -191,22 +276,27 @@ 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
|
||||||
|
|
||||||
if [[ "$ALLOCATE" != [Nn]* ]]; then
|
if ! disabled "$ALLOCATE"; then
|
||||||
|
|
||||||
# Check free diskspace
|
# Check free diskspace
|
||||||
DIR=$(dirname "$DISK_FILE")
|
DIR=$(dirname "$DISK_FILE")
|
||||||
SPACE=$(df --output=avail -B 1 "$DIR" | tail -n 1)
|
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
|
||||||
|
|
||||||
@@ -221,25 +311,9 @@ resizeDisk() {
|
|||||||
case "${DISK_FMT,,}" in
|
case "${DISK_FMT,,}" in
|
||||||
raw)
|
raw)
|
||||||
|
|
||||||
if [[ "$ALLOCATE" == [Nn]* ]]; then
|
if ! allocateRaw "$DISK_FILE" "$DATA_SIZE"; then
|
||||||
|
|
||||||
# Resize file by changing its length
|
|
||||||
if ! truncate -s "$DATA_SIZE" "$DISK_FILE"; then
|
|
||||||
error "$FAIL" && exit 75
|
error "$FAIL" && exit 75
|
||||||
fi
|
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
|
|
||||||
;;
|
;;
|
||||||
qcow2)
|
qcow2)
|
||||||
|
|
||||||
@@ -269,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 [[ "$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)
|
|
||||||
|
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
|
||||||
|
|
||||||
@@ -293,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 [[ "$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
|
||||||
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 [[ "$ALLOCATE" != [Nn]* ]]; 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"
|
||||||
@@ -321,8 +404,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")
|
||||||
@@ -343,21 +435,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
|
||||||
@@ -430,10 +523,10 @@ 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 [[ "$DISK_DISABLE" != [Yy1]* ]]; then
|
if ! enabled "$DISK_DISABLE"; then
|
||||||
html "Initialized disks successfully..."
|
html "Initialized disks successfully..."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -451,7 +544,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"
|
||||||
@@ -459,38 +552,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 $?
|
||||||
|
|
||||||
@@ -517,7 +581,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
|
||||||
|
|
||||||
@@ -540,16 +604,19 @@ 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 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"
|
||||||
@@ -559,8 +626,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
|
||||||
|
|
||||||
@@ -569,6 +636,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\" !"
|
error "Failed to set the owner for \"$DISK_FILE\" !"
|
||||||
|
exit 77
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -594,18 +662,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
|
||||||
|
logical="${result%% *}"
|
||||||
|
physical=$(echo "$result" | grep -m 1 -o "/ .*" | cut -c 3- || true)
|
||||||
|
physical="${physical%% *}"
|
||||||
fi
|
fi
|
||||||
else
|
|
||||||
|
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
|
||||||
|
|
||||||
@@ -619,22 +691,56 @@ addDevice () {
|
|||||||
[ -z "${DISK_NAME:-}" ] && DISK_NAME="data"
|
[ -z "${DISK_NAME:-}" ] && DISK_NAME="data"
|
||||||
[ -z "${DISK_DISABLE:-}" ] && DISK_DISABLE=""
|
[ -z "${DISK_DISABLE:-}" ] && DISK_DISABLE=""
|
||||||
|
|
||||||
if [[ "$DISK_DISABLE" != [Yy1]* ]]; then
|
if ! enabled "$DISK_DISABLE"; then
|
||||||
msg="Initializing disks..."
|
msg="Initializing disks..."
|
||||||
html "$msg"
|
html "$msg"
|
||||||
[[ "$DEBUG" == [Yy1]* ]] && 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
|
||||||
|
|
||||||
if [[ "$ALLOCATE" == [Nn]* ]]; then
|
if disabled "$ALLOCATE"; then
|
||||||
DISK_STYLE="growable"
|
DISK_STYLE="growable"
|
||||||
DISK_ALLOC="preallocation=off"
|
DISK_ALLOC="preallocation=off"
|
||||||
else
|
else
|
||||||
@@ -645,7 +751,7 @@ 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 [[ "$DISK_DISABLE" == [Yy1]* ]]; then
|
if enabled "$DISK_DISABLE"; then
|
||||||
finishDisks
|
finishDisks
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
@@ -675,29 +781,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
|
||||||
|
|
||||||
|
|||||||
+7
-2
@@ -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"
|
||||||
|
|||||||
+2
-2
@@ -26,9 +26,9 @@ 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 [[ "$SHUTDOWN" != [Yy1]* ]]; then
|
if ! enabled "$SHUTDOWN"; then
|
||||||
exec "${cmd[@]}" ${ARGS:+ $ARGS}
|
exec "${cmd[@]}" ${ARGS:+ $ARGS}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -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
@@ -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
|
||||||
|
|
||||||
|
|||||||
+43
-10
@@ -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=$(numfmt --from=iec "$RAM_SIZE")
|
||||||
|
|
||||||
|
if [ "$wanted" -lt "$RAM_MINIMUM" ]; then
|
||||||
wanted=$(( wanted / 1048577 ))
|
wanted=$(( wanted / 1048577 ))
|
||||||
error "Not enough memory available, there is only $wanted MB left!"
|
error "Not enough memory available, there is only $wanted MB left!"
|
||||||
exit 16
|
exit 16
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
checkConfiguredMemory
|
||||||
|
configureHalfMemory
|
||||||
|
configureMaxMemory
|
||||||
|
checkMinimumMemory
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
+1130
-379
File diff suppressed because it is too large
Load Diff
+169
-84
@@ -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,93 +23,109 @@ _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
|
|
||||||
}
|
|
||||||
|
|
||||||
finish() {
|
local sig="$1"
|
||||||
|
|
||||||
local i=0
|
case "$sig" in
|
||||||
local pid=""
|
SIGHUP) echo 129 ;;
|
||||||
local reason=$1
|
SIGINT) echo 130 ;;
|
||||||
local pids=( "${HOST_PID:-}" "${WSD_PID:-}" \
|
SIGQUIT) echo 131 ;;
|
||||||
"${WEB_PID:-}" "${PASST_PID:-}" "${DNSMASQ_PID:-}" )
|
SIGABRT) echo 134 ;;
|
||||||
|
SIGTERM) echo 143 ;;
|
||||||
touch "$QEMU_END"
|
*) echo 0 ;;
|
||||||
|
|
||||||
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
|
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..."
|
error "Forcefully terminating $(app), reason: $display..."
|
||||||
{ disown "$pid" || :; kill -9 -- "$pid" || :; } 2>/dev/null
|
{ disown "$pid" || :; kill -9 -- "$pid" || :; } 2>/dev/null
|
||||||
fi
|
|
||||||
fi
|
return 0
|
||||||
fi
|
}
|
||||||
|
|
||||||
|
cleanupHelpers() {
|
||||||
|
|
||||||
|
local pids=( "${HOST_PID:-}" "${WSD_PID:-}" \
|
||||||
|
"${WEB_PID:-}" "${PASST_PID:-}" "${DNSMASQ_PID:-}" )
|
||||||
|
|
||||||
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' || :
|
||||||
|
|
||||||
# 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)
|
||||||
|
|
||||||
@@ -120,51 +137,119 @@ 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 cnt=0 abort=0 factor=3 offset=3 min max name
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
[[ "$TIMEOUT" =~ ^[0-9]+$ ]] || TIMEOUT=115
|
normalizeTimeout() {
|
||||||
[ "$TIMEOUT" -ge 15 ] && factor=4 && offset=4
|
|
||||||
[ "$TIMEOUT" -ge 30 ] && factor=5 && offset=5
|
|
||||||
min=$(( factor + offset + 1 ))
|
|
||||||
[ "$TIMEOUT" -lt "$min" ] && TIMEOUT="$min"
|
|
||||||
elapsed=$(( SECONDS - start ))
|
|
||||||
max=$(( TIMEOUT - offset - elapsed ))
|
|
||||||
[ "$max" -lt "$factor" ] && max=$(( factor + 1 ))
|
|
||||||
abort=$(( max - factor ))
|
|
||||||
name="$(app)"
|
|
||||||
|
|
||||||
while [ "$cnt" -le "$max" ]; do
|
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 pid="$1"
|
||||||
|
local name="$APP"
|
||||||
|
local slp
|
||||||
|
|
||||||
|
while (( cnt <= wait_until )); do
|
||||||
|
|
||||||
sleep 1 &
|
sleep 1 &
|
||||||
local slp=$!
|
slp=$!
|
||||||
|
|
||||||
|
# Stop waiting if the process has exited
|
||||||
! isAlive "$pid" && break
|
! isAlive "$pid" && break
|
||||||
# Workaround for zombie pid
|
|
||||||
|
# Workaround for stale/zombie QEMU pid file
|
||||||
[ ! -s "$QEMU_PID" ] && break
|
[ ! -s "$QEMU_PID" ] && break
|
||||||
|
|
||||||
if [ "$cnt" -ne "$abort" ]; then
|
if (( cnt == sigterm_at )); then
|
||||||
if [ "$cnt" -gt 0 ] && [[ "$DEBUG" == [Yy1]* ]]; then
|
info "${name^} is still running, sending SIGTERM... ($cnt/$wait_until)"
|
||||||
info "Waiting for $name to shut down... ($cnt/$max)"
|
kill -15 -- "$pid" 2>/dev/null || :
|
||||||
fi
|
elif (( cnt > 0 )) && enabled "${DEBUG:-}"; then
|
||||||
else
|
info "Waiting for $name to shut down... ($cnt/$wait_until)"
|
||||||
info "${name^} is still running, sending SIGTERM... ($cnt/$max)"
|
|
||||||
{ kill -15 -- "$pid" || :; } 2>/dev/null
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
wait $slp
|
wait "$slp"
|
||||||
(( cnt++ ))
|
(( cnt++ ))
|
||||||
|
|
||||||
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"
|
||||||
}
|
}
|
||||||
|
|
||||||
[[ "$SHUTDOWN" != [Yy1]* ]] && return 0
|
! enabled "$SHUTDOWN" && return 0
|
||||||
[ -n "${QEMU_TIMEOUT:-}" ] && TIMEOUT="$QEMU_TIMEOUT"
|
[ -n "${QEMU_TIMEOUT:-}" ] && TIMEOUT="$QEMU_TIMEOUT"
|
||||||
|
|
||||||
_trap graceful_shutdown SIGTERM SIGHUP SIGABRT SIGQUIT
|
_trap graceful_shutdown SIGTERM SIGHUP SIGABRT SIGQUIT
|
||||||
|
|||||||
+148
-49
@@ -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
|
|
||||||
|
[ -f "$shutdown" ] && exit 1
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
queryGuest() {
|
||||||
|
|
||||||
|
local rc
|
||||||
|
|
||||||
|
{ json=$(curl -m 20 -sk "$url"); rc=$?; } || :
|
||||||
|
|
||||||
|
exitIfShuttingDown
|
||||||
|
|
||||||
|
if (( rc != 0 )); then
|
||||||
|
error "$curl_err $rc"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
readJsonField() {
|
||||||
|
|
||||||
|
local query="$1"
|
||||||
|
local result
|
||||||
|
local rc
|
||||||
|
|
||||||
|
{ result=$(jq -r "$query" <<< "$json"); rc=$?; } || :
|
||||||
|
|
||||||
|
if (( rc != 0 )); then
|
||||||
|
error "$jq_err $rc ( $json )"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$result" == "null" ]]; then
|
||||||
|
error "$resp_err $json"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
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
|
# Check if not shutting down
|
||||||
[ -f "$shutdown" ] && exit 1
|
exitIfShuttingDown
|
||||||
|
|
||||||
sleep 3
|
sleep 3
|
||||||
|
|
||||||
[ -f "$shutdown" ] && exit 1
|
exitIfShuttingDown
|
||||||
[ -s "$file" ] && break
|
[ -s "$file" ] && break
|
||||||
|
|
||||||
# Retrieve network info from guest VM
|
# Retrieve network info from guest VM
|
||||||
{ json=$(curl -m 20 -sk "$url"); rc=$?; } || :
|
queryGuest || continue
|
||||||
|
readGuestStatus || continue
|
||||||
|
readGuestPort || continue
|
||||||
|
readGuestIp || continue
|
||||||
|
writeDsmLocation
|
||||||
|
|
||||||
[ -f "$shutdown" ] && exit 1
|
done
|
||||||
(( rc != 0 )) && error "$curl_err $rc" && continue
|
|
||||||
|
|
||||||
{ result=$(echo "$json" | jq -r '.status'); rc=$?; } || :
|
return 0
|
||||||
(( rc != 0 )) && error "$jq_err $rc ( $json )" && continue
|
}
|
||||||
[[ "$result" == "null" ]] && error "$resp_err $json" && continue
|
|
||||||
|
|
||||||
if [[ "$result" != "success" ]] ; then
|
writeDhcpPage() {
|
||||||
{ msg=$(echo "$json" | jq -r '.message'); rc=$?; } || :
|
|
||||||
error "Guest replied $result: $msg" && continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
{ port=$(echo "$json" | jq -r '.data.data.dsm_setting.data.http_port'); rc=$?; } || :
|
local title body script html
|
||||||
(( 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=$?; } || :
|
|
||||||
(( rc != 0 )) && error "$jq_err $rc ( $json )" && continue
|
|
||||||
[[ "$ip" == "null" ]] && error "$resp_err $json" && continue
|
|
||||||
|
|
||||||
[ -z "$ip" ] && continue
|
|
||||||
echo "$ip:$port" > $file
|
|
||||||
|
|
||||||
done
|
|
||||||
|
|
||||||
[ -f "$shutdown" ] && exit 1
|
|
||||||
|
|
||||||
location=$(<"$file")
|
|
||||||
|
|
||||||
if [[ "$DHCP" == [Yy1]* ]]; then
|
|
||||||
|
|
||||||
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
|
||||||
|
|||||||
+124
-63
@@ -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")
|
||||||
|
|
||||||
if [ ! -f "$CLOCK" ]; then
|
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?"
|
warn "file \"$CLOCK\" cannot be found?"
|
||||||
else
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
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,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 [[ "$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
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
@@ -53,7 +104,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 +113,35 @@ if [[ "$KVM" != [Nn]* ]]; then
|
|||||||
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
else
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
configureKvm() {
|
||||||
|
|
||||||
|
configureKvmCpuModel
|
||||||
|
checkSse42
|
||||||
|
appendKvmInvtscFeature
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
configureTcgCpuModel() {
|
||||||
|
|
||||||
|
if [ -n "$CPU_MODEL" ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$ARCH" == "amd64" ]]; then
|
||||||
|
CPU_MODEL="max"
|
||||||
|
CPU_FEATURES+=",migratable=no"
|
||||||
|
else
|
||||||
|
CPU_MODEL="qemu64"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
configureTcg() {
|
||||||
|
|
||||||
KVM_OPTS=""
|
KVM_OPTS=""
|
||||||
CPU_FEATURES="l3-cache=on,+hypervisor"
|
CPU_FEATURES="l3-cache=on,+hypervisor"
|
||||||
@@ -71,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_MODEL="max"
|
|
||||||
CPU_FEATURES+=",migratable=no"
|
|
||||||
else
|
|
||||||
CPU_MODEL="qemu64"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
CPU_FEATURES+=",+ssse3,+sse4.1,+sse4.2"
|
CPU_FEATURES+=",+ssse3,+sse4.1,+sse4.2"
|
||||||
|
|
||||||
fi
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
if [[ "$ARGUMENTS" == *"-cpu host,"* ]]; then
|
composeCpuFlags() {
|
||||||
|
|
||||||
args="${ARGUMENTS} "
|
CPU_FLAGS="${CPU_MODEL}${CPU_FEATURES:+,$CPU_FEATURES}${CPU_FLAGS:+,$CPU_FLAGS}"
|
||||||
prefix="${args/-cpu host,*/}"
|
|
||||||
suffix="${args/*-cpu host,/}"
|
|
||||||
param="${suffix%% *}"
|
|
||||||
suffix="${suffix#* }"
|
|
||||||
args="${prefix}${suffix}"
|
|
||||||
ARGUMENTS="${args::-1}"
|
|
||||||
|
|
||||||
if [ -z "$CPU_FLAGS" ]; then
|
return 0
|
||||||
CPU_FLAGS="$param"
|
}
|
||||||
else
|
|
||||||
CPU_FLAGS+=",$param"
|
|
||||||
fi
|
|
||||||
|
|
||||||
else
|
configureHostCpuName() {
|
||||||
|
|
||||||
if [[ "$ARGUMENTS" == *"-cpu host"* ]]; then
|
if [ -z "$HOST_CPU" ]; then
|
||||||
ARGUMENTS="${ARGUMENTS//-cpu host/}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
if [ -z "$HOST_CPU" ]; then
|
|
||||||
[[ "${CPU,,}" != "unknown" ]] && HOST_CPU="$CPU"
|
[[ "${CPU,,}" != "unknown" ]] && HOST_CPU="$CPU"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -n "$HOST_CPU" ]; then
|
if [ -n "$HOST_CPU" ]; then
|
||||||
HOST_CPU="${HOST_CPU%%,*},,"
|
HOST_CPU="${HOST_CPU%%,*},,"
|
||||||
else
|
else
|
||||||
HOST_CPU="QEMU, Virtual CPU,"
|
HOST_CPU="QEMU, Virtual CPU,"
|
||||||
if [ "$ARCH" == "amd64" ]; then
|
if [ "$ARCH" == "amd64" ]; then
|
||||||
HOST_CPU+=" X86_64"
|
HOST_CPU+=" X86_64"
|
||||||
else
|
else
|
||||||
HOST_CPU+=" $ARCH"
|
HOST_CPU+=" $ARCH"
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
selectClocksource
|
||||||
|
checkClocksource
|
||||||
|
|
||||||
|
flags=$(sed -ne '/^flags/s/^.*: //p' /proc/cpuinfo)
|
||||||
|
|
||||||
|
if ! disabled "$KVM"; then
|
||||||
|
configureKvm
|
||||||
|
else
|
||||||
|
configureTcg
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
removeCpuArgument
|
||||||
|
composeCpuFlags
|
||||||
|
configureHostCpuName
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
+2
-2
@@ -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%"
|
||||||
|
|||||||
+152
-97
@@ -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
|
||||||
@@ -21,15 +21,9 @@ 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() {
|
||||||
|
|
||||||
ROOTLESS="N"
|
if [ -f "/run/.containerenv" ]; then
|
||||||
PRIVILEGED="N"
|
|
||||||
ENGINE="Docker"
|
|
||||||
PROCESS="${APP,,}"
|
|
||||||
PROCESS="${PROCESS// /-}"
|
|
||||||
|
|
||||||
if [ -f "/run/.containerenv" ]; then
|
|
||||||
ENGINE="${container:-}"
|
ENGINE="${container:-}"
|
||||||
if [[ "${ENGINE,,}" == *"podman"* ]]; then
|
if [[ "${ENGINE,,}" == *"podman"* ]]; then
|
||||||
ROOTLESS="Y"
|
ROOTLESS="Y"
|
||||||
@@ -37,105 +31,112 @@ if [ -f "/run/.containerenv" ]; then
|
|||||||
else
|
else
|
||||||
[ -z "$ENGINE" ] && ENGINE="Kubernetes"
|
[ -z "$ENGINE" ] && ENGINE="Kubernetes"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "❯ Starting $APP for $ENGINE v$(</etc/version)..."
|
return 0
|
||||||
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
|
local cap_bnd
|
||||||
LAST_CAP=$(cat /proc/sys/kernel/cap_last_cap)
|
local last_cap
|
||||||
|
local max_cap
|
||||||
|
|
||||||
# Calculate the maximum capability value
|
# Get the capability bounding set
|
||||||
MAX_CAP=$(((1 << (LAST_CAP + 1)) - 1))
|
cap_bnd=$(grep '^CapBnd:' /proc/$$/status | awk '{print $2}')
|
||||||
|
cap_bnd=$(printf "%d" "0x${cap_bnd}")
|
||||||
|
|
||||||
if [ "${CAP_BND}" -eq "${MAX_CAP}" ]; then
|
# 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"
|
ROOTLESS="N"
|
||||||
PRIVILEGED="Y"
|
PRIVILEGED="Y"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
INFO="/run/shm/msg.html"
|
return 0
|
||||||
PAGE="/run/shm/index.html"
|
}
|
||||||
TEMPLATE="/var/www/index.html"
|
|
||||||
FOOTER1="$APP for $ENGINE v$(</etc/version)"
|
|
||||||
FOOTER2="<a href='$SUPPORT'>$SUPPORT</a>"
|
|
||||||
|
|
||||||
SOCKETS=1
|
normalizeCpuCores() {
|
||||||
CPU=$(cpu)
|
|
||||||
SYS=$(uname -r)
|
|
||||||
HOST=$(hostname -s)
|
|
||||||
KERNEL=$(echo "$SYS" | cut -b 1)
|
|
||||||
MINOR=$(echo "$SYS" | cut -d '.' -f2)
|
|
||||||
ARCH=$(dpkg --print-architecture)
|
|
||||||
CORES=$(grep -c '^processor' /proc/cpuinfo)
|
|
||||||
|
|
||||||
if grep -qi "socket(s)" <<< "$(lscpu)"; then
|
CPU_CORES=$(strip "$CPU_CORES")
|
||||||
SOCKETS=$(lscpu | grep -m 1 -i 'socket(s)' | awk '{print $2}')
|
[ -z "$CPU_CORES" ] && CPU_CORES=2
|
||||||
[ -z "${SOCKETS##*[!0-9]*}" ] && SOCKETS=1
|
[[ "${CPU_CORES,,}" == "max" ]] && CPU_CORES="$CORES"
|
||||||
[ "$SOCKETS" -lt "1" ] && SOCKETS=1
|
[[ "${CPU_CORES,,}" == "half" ]] && CPU_CORES=$(( CORES / 2 ))
|
||||||
fi
|
[ -z "${CPU_CORES##*[!0-9]*}" ] && error "Invalid amount of CPU_CORES: $CPU_CORES" && exit 15
|
||||||
|
|
||||||
CPU_CORES="${CPU_CORES// /}"
|
[ "$CPU_CORES" -lt "1" ] && CPU_CORES=1
|
||||||
[ -z "$CPU_CORES" ] && CPU_CORES=2
|
if [ "$CPU_CORES" -gt "$CORES" ]; then
|
||||||
[[ "${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."
|
warn "The amount for CPU_CORES (${CPU_CORES}) exceeds the amount of logical cores available (${CORES}) and will be limited."
|
||||||
CPU_CORES="$CORES"
|
CPU_CORES="$CORES"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check system
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
QEMU_DIR="/run/shm"
|
checkStorage() {
|
||||||
|
|
||||||
if [ ! -d "/dev/shm" ]; then
|
# Check system
|
||||||
|
|
||||||
|
QEMU_DIR="/run/shm"
|
||||||
|
|
||||||
|
if [ ! -d "/dev/shm" ]; then
|
||||||
error "Directory /dev/shm not found!" && exit 14
|
error "Directory /dev/shm not found!" && exit 14
|
||||||
else
|
else
|
||||||
[ ! -d "$QEMU_DIR" ] && ln -s /dev/shm "$QEMU_DIR"
|
[ ! -d "$QEMU_DIR" ] && ln -s /dev/shm "$QEMU_DIR"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
QEMU_PID="$QEMU_DIR/qemu.pid"
|
QEMU_PID="$QEMU_DIR/qemu.pid"
|
||||||
|
|
||||||
# Check folder
|
# Check folder
|
||||||
|
|
||||||
if [[ "${STORAGE,,}" != "/storage" ]]; then
|
if [[ "${STORAGE,,}" != "/storage" ]]; then
|
||||||
mkdir -p "$STORAGE"
|
mkdir -p "$STORAGE"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ ! -d "$STORAGE" ]; then
|
if [ ! -d "$STORAGE" ]; then
|
||||||
error "Storage folder ($STORAGE) not found!" && exit 13
|
error "Storage folder ($STORAGE) not found!" && exit 13
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ ! -w "$STORAGE" ]; then
|
if [ ! -w "$STORAGE" ]; then
|
||||||
msg="Storage folder ($STORAGE) is not writeable!"
|
msg="Storage folder ($STORAGE) is not writeable!"
|
||||||
msg+=" If SELinux is active, you need to add the \":Z\" flag to the bind mount."
|
msg+=" If SELinux is active, you need to add the \":Z\" flag to the bind mount."
|
||||||
error "$msg" && exit 13
|
error "$msg" && exit 13
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check filesystem
|
return 0
|
||||||
FS=$(stat -f -c %T "$STORAGE")
|
}
|
||||||
|
|
||||||
if [[ "${FS,,}" == "ecryptfs" || "${FS,,}" == "tmpfs" ]]; then
|
checkFilesystem() {
|
||||||
|
|
||||||
|
# Check filesystem
|
||||||
|
FS=$(stat -f -c %T "$STORAGE")
|
||||||
|
|
||||||
|
if [[ "${FS,,}" == "ecryptfs" || "${FS,,}" == "tmpfs" ]]; then
|
||||||
DISK_IO="threads"
|
DISK_IO="threads"
|
||||||
DISK_CACHE="writeback"
|
DISK_CACHE="writeback"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Read memory
|
return 0
|
||||||
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
|
normalizeRamSize() {
|
||||||
RAM_MINIMUM=136314880
|
|
||||||
RAM_SIZE="${RAM_SIZE// /}"
|
|
||||||
[ -z "$RAM_SIZE" ] && RAM_SIZE="2G"
|
|
||||||
|
|
||||||
if [[ "${RAM_SIZE,,}" != "max" && "${RAM_SIZE,,}" != "half" ]]; then
|
# 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
|
if [ -z "${RAM_SIZE//[0-9. ]}" ]; then
|
||||||
[ "${RAM_SIZE%%.*}" -lt "130" ] && RAM_SIZE="${RAM_SIZE}G" || RAM_SIZE="${RAM_SIZE}M"
|
[ "${RAM_SIZE%%.*}" -lt "130" ] && RAM_SIZE="${RAM_SIZE}G" || RAM_SIZE="${RAM_SIZE}M"
|
||||||
@@ -146,39 +147,31 @@ if [[ "${RAM_SIZE,,}" != "max" && "${RAM_SIZE,,}" != "half" ]]; then
|
|||||||
wanted=$(numfmt --from=iec "$RAM_SIZE")
|
wanted=$(numfmt --from=iec "$RAM_SIZE")
|
||||||
[ "$wanted" -lt "$RAM_MINIMUM" ] && error "RAM_SIZE is too low: $RAM_SIZE" && exit 16
|
[ "$wanted" -lt "$RAM_MINIMUM" ] && error "RAM_SIZE is too low: $RAM_SIZE" && exit 16
|
||||||
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Print system info
|
return 0
|
||||||
SYS="${SYS/-generic/}"
|
}
|
||||||
FS="${FS/UNKNOWN //}"
|
|
||||||
FS="${FS/ext2\/ext3/ext4}"
|
|
||||||
FS=$(echo "$FS" | sed 's/[)(]//g')
|
|
||||||
SPACE=$(df --output=avail -B 1 "$STORAGE" | tail -n 1)
|
|
||||||
SPACE_GB=$(formatBytes "$SPACE" "down")
|
|
||||||
AVAIL_MEM=$(formatBytes "$RAM_AVAIL" "down")
|
|
||||||
TOTAL_MEM=$(formatBytes "$RAM_TOTAL" "up")
|
|
||||||
|
|
||||||
echo "❯ CPU: ${CPU} | RAM: ${AVAIL_MEM/ GB/}/$TOTAL_MEM | DISK: $SPACE_GB (${FS}) | KERNEL: ${SYS}..."
|
checkKvm() {
|
||||||
echo
|
|
||||||
|
|
||||||
# Check KVM support
|
# Check KVM support
|
||||||
|
|
||||||
if [[ "${PLATFORM,,}" == "x64" ]]; then
|
if [[ "${PLATFORM,,}" == "x64" ]]; then
|
||||||
TARGET="amd64"
|
TARGET="amd64"
|
||||||
else
|
else
|
||||||
TARGET="arm64"
|
TARGET="arm64"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$KVM" == [Nn]* ]]; then
|
if disabled "$KVM"; then
|
||||||
warn "KVM acceleration is disabled, this will cause the machine to run about 10 times slower!"
|
warn "KVM acceleration is disabled, this will cause the machine to run about 10 times slower!"
|
||||||
else
|
else
|
||||||
if [[ "${ARCH,,}" != "$TARGET" ]]; then
|
if [[ "${ARCH,,}" != "$TARGET" ]]; then
|
||||||
KVM="N"
|
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."
|
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
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$KVM" != [Nn]* ]]; then
|
if ! disabled "$KVM"; then
|
||||||
|
|
||||||
KVM_ERR=""
|
KVM_ERR=""
|
||||||
|
|
||||||
@@ -195,7 +188,7 @@ if [[ "$KVM" != [Nn]* ]]; then
|
|||||||
fi
|
fi
|
||||||
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
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -216,16 +209,78 @@ if [[ "$KVM" != [Nn]* ]]; then
|
|||||||
error "KVM acceleration is not available $KVM_ERR, this will cause the machine to run about 10 times slower."
|
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)." ;;
|
error "See the FAQ for possible causes, or disable acceleration by adding the \"KVM=N\" variable (not recommended)." ;;
|
||||||
esac
|
esac
|
||||||
[[ "$DEBUG" != [Yy1]* ]] && exit 88
|
! enabled "$DEBUG" && exit 88
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Sanitize variables
|
||||||
|
TZ=$(strip "$TZ")
|
||||||
|
STORAGE=$(strip "$STORAGE")
|
||||||
|
COUNTRY=$(strip "$COUNTRY")
|
||||||
|
DISK_SIZE=$(strip "$DISK_SIZE")
|
||||||
|
|
||||||
|
# Helper variables
|
||||||
|
ROOTLESS="N"
|
||||||
|
PRIVILEGED="N"
|
||||||
|
ENGINE="Docker"
|
||||||
|
PROCESS="${APP,,}"
|
||||||
|
PROCESS="${PROCESS// /-}"
|
||||||
|
|
||||||
|
detectEngine
|
||||||
|
|
||||||
|
echo "❯ Starting $APP for $ENGINE v$(</etc/version)..."
|
||||||
|
echo "❯ For support visit $SUPPORT"
|
||||||
|
|
||||||
|
checkPrivileged
|
||||||
|
|
||||||
|
INFO="/run/shm/msg.html"
|
||||||
|
PAGE="/run/shm/index.html"
|
||||||
|
TEMPLATE="/var/www/index.html"
|
||||||
|
FOOTER1="$APP for $ENGINE v$(</etc/version)"
|
||||||
|
FOOTER2="<a href='$SUPPORT'>$SUPPORT</a>"
|
||||||
|
|
||||||
|
SOCKETS=1
|
||||||
|
CPU=$(cpu)
|
||||||
|
SYS=$(uname -r)
|
||||||
|
KERNEL=$(echo "$SYS" | cut -b 1)
|
||||||
|
MINOR=$(echo "$SYS" | cut -d '.' -f2)
|
||||||
|
ARCH=$(dpkg --print-architecture)
|
||||||
|
CORES=$(grep -c '^processor' /proc/cpuinfo)
|
||||||
|
|
||||||
|
if grep -qi "socket(s)" <<< "$(lscpu)"; then
|
||||||
|
SOCKETS=$(lscpu | grep -m 1 -i 'socket(s)' | awk '{print $2}')
|
||||||
|
[ -z "${SOCKETS##*[!0-9]*}" ] && SOCKETS=1
|
||||||
|
[ "$SOCKETS" -lt "1" ] && SOCKETS=1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
normalizeCpuCores
|
||||||
|
checkStorage
|
||||||
|
checkFilesystem
|
||||||
|
normalizeRamSize
|
||||||
|
|
||||||
|
# Print system info
|
||||||
|
SYS="${SYS/-generic/}"
|
||||||
|
FS="${FS/UNKNOWN //}"
|
||||||
|
FS="${FS/ext2\/ext3/ext4}"
|
||||||
|
FS=$(echo "$FS" | sed 's/[)(]//g')
|
||||||
|
SPACE=$(df --output=avail -B 1 "$STORAGE" | tail -n 1)
|
||||||
|
SPACE_GB=$(formatBytes "$SPACE" "down")
|
||||||
|
AVAIL_MEM=$(formatBytes "$RAM_AVAIL" "down")
|
||||||
|
TOTAL_MEM=$(formatBytes "$RAM_TOTAL" "up")
|
||||||
|
|
||||||
|
echo "❯ CPU: ${CPU} | RAM: ${AVAIL_MEM/ GB/}/$TOTAL_MEM | DISK: $SPACE_GB (${FS}) | KERNEL: ${SYS}..."
|
||||||
|
echo
|
||||||
|
|
||||||
|
checkKvm
|
||||||
|
|
||||||
# Cleanup files
|
# Cleanup files
|
||||||
rm -f "$QEMU_DIR"/qemu.*
|
|
||||||
rm -f "$QEMU_DIR"/*.pid
|
|
||||||
rm -f "$QEMU_DIR"/dsm.url
|
rm -f "$QEMU_DIR"/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
|
||||||
|
|||||||
+64
-27
@@ -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,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 [[ "$HOST_DEBUG" == [Yy1]* ]]; then
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
startHostBinary() {
|
||||||
|
|
||||||
|
if enabled "$HOST_DEBUG"; then
|
||||||
set -x
|
set -x
|
||||||
./host.bin "${HOST_ARGS[@]}" &
|
./host.bin "${HOST_ARGS[@]}" &
|
||||||
{ set +x; } 2>/dev/null
|
{ set +x; } 2>/dev/null
|
||||||
echo "$!" > "$HOST_PID"
|
echo "$!" > "$HOST_PID"
|
||||||
echo
|
echo
|
||||||
else
|
else
|
||||||
./host.bin "${HOST_ARGS[@]}" >/dev/null &
|
./host.bin "${HOST_ARGS[@]}" >/dev/null &
|
||||||
echo "$!" > "$HOST_PID"
|
echo "$!" > "$HOST_PID"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cnt=0
|
return 0
|
||||||
sleep 0.2
|
}
|
||||||
|
|
||||||
while ! nc -z -w2 127.0.0.1 "$COM_PORT" > /dev/null 2>&1; do
|
waitForPort() {
|
||||||
|
|
||||||
|
local port="$1"
|
||||||
|
local exit_code="$2"
|
||||||
|
local cnt=0
|
||||||
|
|
||||||
|
while ! nc -z -w2 127.0.0.1 "$port" > /dev/null 2>&1; do
|
||||||
sleep 0.1
|
sleep 0.1
|
||||||
cnt=$((cnt + 1))
|
cnt=$((cnt + 1))
|
||||||
(( cnt > 50 )) && error "Failed to connect to qemu-host.." && exit 58
|
(( cnt > 50 )) && error "Failed to connect to qemu-host.." && exit "$exit_code"
|
||||||
done
|
done
|
||||||
|
|
||||||
cnt=0
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
while ! nc -z -w2 127.0.0.1 "$CHR_PORT" > /dev/null 2>&1; do
|
configureSerialPorts() {
|
||||||
sleep 0.1
|
|
||||||
cnt=$((cnt + 1))
|
|
||||||
(( cnt > 50 )) && error "Failed to connect to qemu-host.." && exit 59
|
|
||||||
done
|
|
||||||
|
|
||||||
# Configure serial ports
|
# Configure serial ports
|
||||||
SERIAL_OPTS="-serial mon:stdio \
|
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
|
||||||
|
|||||||
+46
-12
@@ -6,37 +6,71 @@ set -Eeuo pipefail
|
|||||||
: "${CHR_PORT:="12345"}" # Character port
|
: "${CHR_PORT:="12345"}" # Character port
|
||||||
: "${WSD_PORT:="8004"}" # Websockets port
|
: "${WSD_PORT:="8004"}" # Websockets port
|
||||||
|
|
||||||
|
# Sanitize port variables
|
||||||
|
COM_PORT=$(strip "$COM_PORT")
|
||||||
|
WEB_PORT=$(strip "$WEB_PORT")
|
||||||
|
CHR_PORT=$(strip "$CHR_PORT")
|
||||||
|
WSD_PORT=$(strip "$WSD_PORT")
|
||||||
|
|
||||||
WEB_PID="/run/nginx.pid"
|
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"
|
cp -r /var/www/* "$QEMU_DIR"
|
||||||
rm -f "$WEB_PID"
|
rm -f "$WSD_PID" "$WEB_PID"
|
||||||
|
|
||||||
html "Starting $APP for $ENGINE..."
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
if [[ "${WEB:-}" != [Nn]* ]]; then
|
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 "$!" > "$WSD_PID"
|
echo "$!" > "$WSD_PID"
|
||||||
|
}
|
||||||
|
|
||||||
|
prepareWebFiles
|
||||||
|
|
||||||
|
html "Starting $APP for $ENGINE..."
|
||||||
|
|
||||||
|
if ! disabled "${WEB:-}"; then
|
||||||
|
configureWebServer
|
||||||
|
startWebServer
|
||||||
|
startWebsocketServer
|
||||||
fi
|
fi
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
+1
-1
@@ -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
|
||||||
|
|||||||
+41
-7
@@ -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;')
|
||||||
@@ -154,8 +188,8 @@ setOwner() {
|
|||||||
[ ! -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
|
||||||
|
|
||||||
@@ -170,8 +204,8 @@ makeDir() {
|
|||||||
! mkdir -p "$path" && return 1
|
! mkdir -p "$path" && return 1
|
||||||
|
|
||||||
dir=$(dirname -- "$path")
|
dir=$(dirname -- "$path")
|
||||||
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" "$path" && return 1
|
! chown "$uid:$gid" "$path" && return 1
|
||||||
|
|
||||||
@@ -213,8 +247,8 @@ 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
|
||||||
}
|
}
|
||||||
@@ -258,7 +292,7 @@ cpu() {
|
|||||||
|
|
||||||
hasDisk() {
|
hasDisk() {
|
||||||
|
|
||||||
[[ "${DISK_DISABLE:-}" == [Yy1]* ]] && return 1
|
enabled "${DISK_DISABLE:-}" && return 1
|
||||||
|
|
||||||
[ -b "/disk" ] && return 0
|
[ -b "/disk" ] && return 0
|
||||||
[ -b "/disk1" ] && return 0
|
[ -b "/disk1" ] && return 0
|
||||||
|
|||||||
Reference in New Issue
Block a user