Compare commits

...
34 Commits
Author SHA1 Message Date
KroeseandGitHub 6bcacf5e59 feat: Improve boolean helpers (#1268) 2026-07-15 19:35:16 +02:00
KroeseandGitHub 3bea87f087 feat: Suppress empty iptables debug sections (#1267) 2026-07-15 19:21:43 +02:00
KroeseandGitHub f01e1564f7 fix: Make setting owner non-fatal (#1266) 2026-07-15 18:00:24 +02:00
KroeseandGitHub cf8cdbf0c1 fix: Make failed chown non-fatal (#1265) 2026-07-15 17:39:05 +02:00
KroeseandGitHub 0813494ee0 feat: Use cgroup when calculating RAM (#1264) 2026-07-15 15:40:51 +02:00
KroeseandGitHub 0d71e30f50 feat: Add state helpers to utils (#1263) 2026-07-15 15:28:42 +02:00
KroeseandGitHub c203deab9a feat: Improve webserver configuration (#1262) 2026-07-15 15:27:24 +02:00
KroeseandGitHub 9cf31df266 fix: Validate subnet mask (#1261) 2026-07-15 14:17:01 +02:00
KroeseandGitHub 0cd08f2ee1 fix: Improve NAT port forwarding (#1260) 2026-07-15 12:24:25 +02:00
KroeseandGitHub a32d215e90 feat: Improve wget error reporting (#1259) 2026-07-15 11:34:55 +02:00
KroeseandGitHub c1f9a690f9 fix: Remove dots from info line (#1258) 2026-07-15 09:00:14 +02:00
KroeseandGitHub 5de504deb3 feat: Improve rootless container detection (#1256) 2026-07-15 06:02:12 +02:00
KroeseandGitHub 250eb18238 docs: Readme (#1255) 2026-07-14 12:50:07 +02:00
KroeseandGitHub ca80da59bd docs: Environment variables (#1254) 2026-07-14 11:29:10 +02:00
KroeseandGitHub 9cd86dfe82 fix: Use exec form for health check (#1253) 2026-07-13 19:40:30 +02:00
KroeseandGitHub 5bb93651f7 docs: Readme (#1252) 2026-07-13 00:38:58 +02:00
KroeseandGitHub 0c268308a4 feat: Preserve source image until conversion completes (#1251) 2026-07-12 23:33:10 +02:00
KroeseandGitHub 7b741d3e4b feat: Warn about UDP host ports (#1250) 2026-07-12 22:14:11 +02:00
KroeseandGitHub f1d29212ae feat: Improve iptables rule cleanup (#1249) 2026-07-12 21:58:49 +02:00
KroeseandGitHub fdee9f89a3 feat: Improve iptables backend selection (#1248) 2026-07-12 21:36:52 +02:00
KroeseandGitHub 210a360797 fix: Restore compatible NAT forwarding rules (#1247) 2026-07-12 13:18:52 +03:00
KroeseandGitHub 991bb2627a fix: Prefer legacy iptables backend for NAT (#1246) 2026-07-12 12:44:00 +03:00
KroeseandGitHub 764a186836 feat: Allow all forwarded NAT traffic (#1245) 2026-07-11 11:58:35 +03:00
KroeseandGitHub 726786a98a feat: Default host and user ports to TCP only (#1244) 2026-07-09 11:06:01 +02:00
KroeseandGitHub 2caf97d407 docs: Network modes (#1243) 2026-07-09 00:39:52 +02:00
KroeseandGitHub 0e67286599 fix: Improve file handling (#1242) 2026-07-08 23:55:02 +02:00
KroeseandGitHub 2b9fc80275 feat: Validate MAC file writes (#1241) 2026-07-08 23:28:56 +02:00
KroeseandGitHub 0d88774bd3 feat: Display base directory in warnings (#1240) 2026-07-08 23:16:20 +02:00
KroeseandGitHub fa23052ed4 fix: Set default DHCP lease time to 1 hour (#1239) 2026-07-08 22:46:47 +02:00
KroeseandGitHub cc3267f258 docs: Correct hostname (#1238) 2026-07-08 18:57:55 +02:00
KroeseandGitHub fd002564a6 feat: Validate custom QEMU CPU arguments (#1237) 2026-07-08 18:50:31 +02:00
KroeseandGitHub 2703a97cfa docs: Refer to environment documentation (#1236) 2026-07-08 18:44:04 +02:00
KroeseandGitHub 29e2d034bf docs: Document all environment variables (#1235) 2026-07-08 17:11:10 +02:00
KroeseandGitHub cbe3644335 fix: Add extra line after boot (#1234) 2026-07-08 16:50:21 +02:00
14 changed files with 933 additions and 306 deletions
+1 -1
View File
@@ -83,6 +83,6 @@ ENV RAM_SIZE="2G"
ENV CPU_CORES="2" ENV CPU_CORES="2"
ENV DISK_SIZE="256G" ENV DISK_SIZE="256G"
HEALTHCHECK --interval=60s --start-period=45s --retries=2 CMD /run/check.sh HEALTHCHECK --interval=60s --start-period=45s --retries=2 CMD ["/run/check.sh"]
ENTRYPOINT ["/usr/bin/tini", "-s", "/run/entry.sh"] ENTRYPOINT ["/usr/bin/tini", "-s", "/run/entry.sh"]
+93
View File
@@ -0,0 +1,93 @@
# Environment Variables
This page lists all the environment variables that can be used to configure the container.
An empty default means the variable is unset and its value is determined automatically when applicable.
## 💽 Virtual DSM
| Variable | Default | Description |
|---|---|---|
| `URL` | | URL or local path to the DSM `.pat` installation file. Downloads the default Virtual DSM image automatically when unset. |
| `HOST_MAC` | | MAC address reported to DSM. |
| `HOST_MODEL` | | Synology host model reported to DSM. |
| `HOST_SERIAL` | | Synology host serial number reported to DSM. |
| `GUEST_SERIAL` | | Synology guest serial number reported to DSM. |
## 🧠 CPU and Memory
| Variable | Default | Description |
|---|---|---|
| `CPU_CORES` | `2` | Number of virtual CPU cores, such as `4`, `half`, or `max`. |
| `CPU_MODEL` | `host` | QEMU CPU model. |
| `CPU_FLAGS` | | Additional QEMU CPU flags. |
| `HOST_CPU` | | CPU name reported to DSM. Selected automatically when unset. |
| `KVM` | `Y` | Enables KVM hardware acceleration. |
| `RAM_SIZE` | `2G` | Amount of RAM assigned to DSM, such as `2G`, `4G`, `half`, or `max`. |
| `RAM_CHECK` | `Y` | Checks whether enough host memory is available before starting DSM. |
## 💾 Storage
| Variable | Default | Description |
|---|---|---|
| `DISK_SIZE` | `256G` | Size of the main data disk. |
| `DISK_FMT` | `raw` | Disk image format: `raw` or `qcow2`. |
| `DISK_TYPE` | `scsi` | Disk device type, such as `sata`, `scsi`, `nvme`, or `blk`. |
| `DISK_CACHE` | `none` | Disk cache mode, such as `none` or `writeback`. |
| `DISK_IO` | `native` | Disk I/O mode, such as `native`, `threads`, or `io_uring`. |
| `DISK_DISCARD` | `unmap` | Discard/TRIM mode for the primary disk. |
| `DISK_ROTATION` | `1` | Rotation rate reported to the guest. Use `1` to identify the disk as an SSD. |
| `DISK_FLAGS` | | Additional options used when creating `qcow2` disks. |
| `ALLOCATE` | `N` | Preallocates space for the data disks. |
| `STORAGE` | `/storage` | Storage directory used for disks, settings, and downloads. |
## 🌐 Networking
| Variable | Default | Description |
|---|---|---|
| `NETWORK` | | Network mode, such as `nat`, `passt`, `slirp`, or `N` to disable networking. |
| `DHCP` | `N` | Enables macvtap networking so DSM receives an address from the external LAN through DHCP. |
| `HOST` | `VirtualDSM` | Hostname assigned to DSM. |
| `IP` | | Overrides the automatically selected guest IPv4 address. |
| `MAC` | | Guest network adapter MAC address. |
| `ADAPTER` | `virtio-net-pci` | QEMU network adapter model. |
| `DEV` | `eth0` | Container network interface used as the uplink. |
| `MTU` | | MTU assigned to the guest network interface. |
| `MASK` | `255.255.255.0` | IPv4 netmask. |
| `TAP` | `dsm` | TAP or macvtap interface name. |
| `BRIDGE` | `docker` | Bridge name used for NAT networking. |
| `HOST_PORTS` | | Ports excluded from guest forwarding. |
| `USER_PORTS` | | Additional ports to forward to DSM when using user-mode networking. |
| `DNSMASQ_OPTS` | | Additional options passed to dnsmasq. |
| `DNSMASQ_DEBUG` | `N` | Enables dnsmasq debug output. |
| `DNSMASQ_DISABLE` | `N` | Disables the internal dnsmasq resolver. |
| `PASST_OPTS` | | Additional options passed to passt. |
| `PASST_DEBUG` | `N` | Enables passt debug output. |
## 🖥️ Display
| Variable | Default | Description |
|---|---|---|
| `DISPLAY` | `none` | Display backend, such as `vnc`, `disabled`, or `none`. |
| `VGA` | `none` | QEMU video adapter model. |
| `GPU` | `N` | Enables Intel iGPU acceleration. |
| `RENDERNODE` | `/dev/dri/renderD128` | Render node used for GPU acceleration. |
## 🔌 Shutdown
| Variable | Default | Description |
|---|---|---|
| `SHUTDOWN` | `Y` | Enables graceful shutdown. |
| `TIMEOUT` | `115` | Maximum time, in seconds, to wait before forcing DSM to stop. |
| `API_TIMEOUT` | `90` | Maximum time, in seconds, to wait for the shutdown API call. |
## 🐞 Debugging
| Variable | Default | Description |
|---|---|---|
| `DEBUG` | `N` | Enables verbose debug output. |
| `TRACE` | `N` | Enables shell command tracing. |
| `COM_PORT` | `2210` | Internal communication port used by the DSM host helper. |
| `CHR_PORT` | `12345` | Internal character device port used by the DSM host helper. |
| `HOST_DEBUG` | `N` | Enables debug output for the DSM host helper. |
| `ARGUMENTS` | | Additional raw arguments appended to the QEMU command line. |
+41 -33
View File
@@ -22,7 +22,7 @@ Virtual DSM in a Docker container.
- Near-native performance with KVM acceleration - Near-native performance with KVM acceleration
- Customizable CPU, memory, and storage allocation - Customizable CPU, memory, and storage allocation
- Supports multiple disks and physical disk passthrough - Supports multiple disks and physical disk passthrough
- Supports bridge, user-mode, macvlan, and macvtap networking - Supports NAT, user-mode, macvlan, and macvtap networking
## Usage 🐳 ## Usage 🐳
@@ -66,12 +66,13 @@ kubectl apply -f https://raw.githubusercontent.com/vdsm/virtual-dsm/refs/heads/m
## Requirements ⚙️ ## Requirements ⚙️
- A Linux host with KVM support, or Docker Desktop / Podman on Windows 11 with nested virtualization enabled. - Docker or Podman on a Linux host with KVM support.
- At least 2 GB of RAM available. - Docker Desktop or Podman (Desktop) on Windows 11 with nested virtualization enabled.
- At least 32 GB of free disk space. - At least 2 GB of available RAM.
- At least 32 GB of free disk space.
> [!NOTE] > [!NOTE]
> Docker Desktop on macOS and Windows 10 do not currently provide the required KVM support for this image. > Docker Desktop on Linux, macOS, and Windows 10 does not currently provide KVM access to containers and is therefore not supported.
## FAQ 💬 ## FAQ 💬
@@ -147,34 +148,6 @@ kubectl apply -f https://raw.githubusercontent.com/vdsm/virtual-dsm/refs/heads/m
CPU_CORES: "4" CPU_CORES: "4"
``` ```
### How do I verify if my system supports KVM?
First check if your software is compatible using this chart:
| **Product** | **Linux** | **Win11** | **Win10** | **macOS** |
|---|---|---|---|---|
| Docker CLI | ✅ | ✅ | ❌ | ❌ |
| Docker Desktop | ❌ | ✅ | ❌ | ❌ |
| Podman CLI | ✅ | ✅ | ❌ | ❌ |
| Podman Desktop | ✅ | ✅ | ❌ | ❌ |
After that you can run the following commands in Linux to check your system:
```bash
sudo apt install cpu-checker
sudo kvm-ok
```
If you receive an error from `kvm-ok` indicating that KVM cannot be used, please check whether:
- the virtualization extensions (`Intel VT-x` or `AMD SVM`) are enabled in your BIOS.
- you enabled "nested virtualization" if you are running the container inside a virtual machine.
- you are not using a cloud provider, as most of them do not allow nested virtualization for their VPSs.
If you did not receive any error from `kvm-ok` but the container still complains about a missing KVM device, it could help to add `privileged: true` to your compose file (or `sudo` to your `docker` command) to rule out any permission issue.
### How do I assign an individual IP address to the container? ### How do I assign an individual IP address to the container?
By default, the container uses bridge networking, which shares the IP address with the host. By default, the container uses bridge networking, which shares the IP address with the host.
@@ -261,6 +234,41 @@ kubectl apply -f https://raw.githubusercontent.com/vdsm/virtual-dsm/refs/heads/m
Replace the example path `./DSM_VirtualDSM_42218.pat` with the filename of your desired `.pat` file. The value of `URL` will be ignored in this case. Replace the example path `./DSM_VirtualDSM_42218.pat` with the filename of your desired `.pat` file. The value of `URL` will be ignored in this case.
### Are these all available options?
No. For a complete overview of all supported settings, see the [environment variables](docs/environment.md) page.
### How do I verify that KVM is available?
First, make sure your platform and container runtime meet the [requirements](#requirements-) listed above.
On a Linux host, install `cpu-checker` and run:
```bash
sudo apt install cpu-checker
sudo kvm-ok
```
A working configuration should report:
```text
KVM acceleration can be used
```
You can also verify that the KVM device exists:
```bash
ls -l /dev/kvm
```
If KVM is unavailable, check whether:
- Hardware virtualization (`Intel VT-x` or `AMD-V`) is enabled in your BIOS or UEFI.
- Nested virtualization is enabled when the host itself is a virtual machine.
- Your VPS or cloud provider supports nested virtualization.
If `kvm-ok` succeeds but the container still reports that KVM is unavailable, you can temporarily add `privileged: true` to your Compose file to rule out a permission or device-access issue.
### What are the differences compared to the standard DSM? ### What are the differences compared to the standard DSM?
There are only two minor differences: the Virtual Machine Manager package is not available, and Surveillance Station will not include any free licenses. There are only two minor differences: the Virtual Machine Manager package is not available, and Surveillance Station will not include any free licenses.
+64 -28
View File
@@ -27,11 +27,11 @@ SYSTEM="$STORAGE/$BASE.system.img"
[ ! -s "$SYSTEM" ] && error "Virtual DSM system-image does not exist ($SYSTEM)" && exit 82 [ ! -s "$SYSTEM" ] && error "Virtual DSM system-image does not exist ($SYSTEM)" && exit 82
if ! setOwner "$BOOT"; then if ! setOwner "$BOOT"; then
error "Failed to set the owner for \"$BOOT\" !" warn "failed to set the owner for \"$BOOT\" !"
fi fi
if ! setOwner "$SYSTEM"; then if ! setOwner "$SYSTEM"; then
error "Failed to set the owner for \"$SYSTEM\" !" warn "failed to set the owner for \"$SYSTEM\" !"
fi fi
fmt2ext() { fmt2ext() {
@@ -179,6 +179,22 @@ normalizeSize() {
return 0 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"
@@ -186,7 +202,7 @@ createDisk() {
local DISK_DESC="$3" local DISK_DESC="$3"
local DISK_FMT="$4" local DISK_FMT="$4"
local FS="$5" local FS="$5"
local DATA_SIZE DIR SPACE GB FA local DATA_SIZE DIR BASE_DIR SPACE GB FA
rm -f "$DISK_FILE" rm -f "$DISK_FILE"
@@ -196,11 +212,16 @@ createDisk() {
# Check free diskspace # Check free diskspace
DIR=$(dirname "$DISK_FILE") DIR=$(dirname "$DISK_FILE")
SPACE=$(df --output=avail -B 1 "$DIR" | tail -n 1) BASE_DIR=$(baseDir "$DIR")
if ! SPACE=$(df --output=avail -B 1 "$DIR" | tail -n 1); then
error "Failed to check free space in $BASE_DIR."
exit 76
fi
if (( DATA_SIZE > SPACE )); then if (( DATA_SIZE > SPACE )); then
GB=$(formatBytes "$SPACE") GB=$(formatBytes "$SPACE")
error "Not enough free space to create a $DISK_DESC of ${DISK_SPACE/G/ GB} in $DIR, it has only $GB available..." error "Not enough free space to create a $DISK_DESC of ${DISK_SPACE/G/ GB} in $BASE_DIR, it has only $GB available..."
error "Please specify a smaller ${DISK_DESC^^}_SIZE or disable preallocation by setting ALLOCATE=N." && exit 76 error "Please specify a smaller ${DISK_DESC^^}_SIZE or disable preallocation by setting ALLOCATE=N." && exit 76
fi fi
@@ -255,7 +276,7 @@ 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") || exit 71 CUR_SIZE=$(getSize "$DISK_FILE") || exit 71
DATA_SIZE=$(numfmt --from=iec "$DISK_SPACE") DATA_SIZE=$(numfmt --from=iec "$DISK_SPACE")
@@ -266,11 +287,16 @@ resizeDisk() {
# Check free diskspace # Check free diskspace
DIR=$(dirname "$DISK_FILE") DIR=$(dirname "$DISK_FILE")
SPACE=$(df --output=avail -B 1 "$DIR" | tail -n 1) BASE_DIR=$(baseDir "$DIR")
if ! SPACE=$(df --output=avail -B 1 "$DIR" | tail -n 1); then
error "Failed to check free space in $BASE_DIR."
exit 76
fi
if (( REQ > SPACE )); then if (( REQ > SPACE )); then
GB=$(formatBytes "$SPACE") GB=$(formatBytes "$SPACE")
error "Not enough free space to resize $DISK_DESC to ${DISK_SPACE/G/ GB} in $DIR, it has only $GB available.." error "Not enough free space to resize $DISK_DESC to ${DISK_SPACE/G/ GB} in $BASE_DIR, it has only $GB available.."
error "Please specify a smaller ${DISK_DESC^^}_SIZE or disable preallocation by setting ALLOCATE=N." && exit 74 error "Please specify a smaller ${DISK_DESC^^}_SIZE or disable preallocation by setting ALLOCATE=N." && exit 74
fi fi
@@ -317,8 +343,9 @@ convertDisk() {
local TMP_FILE="$DISK_BASE.tmp" local TMP_FILE="$DISK_BASE.tmp"
rm -f "$TMP_FILE" rm -f "$TMP_FILE"
local DIR FA local DIR BASE_DIR FA
DIR=$(dirname "$TMP_FILE") DIR=$(dirname "$TMP_FILE")
BASE_DIR=$(baseDir "$DIR")
if ! disabled "$ALLOCATE"; then if ! disabled "$ALLOCATE"; then
@@ -326,11 +353,15 @@ convertDisk() {
# Check free diskspace # Check free diskspace
CUR_SIZE=$(getSize "$SOURCE_FILE") || exit 79 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
@@ -353,13 +384,18 @@ convertDisk() {
# shellcheck disable=SC2086 # shellcheck disable=SC2086
if ! qemu-img convert -f "$SOURCE_FMT" $CONV_FLAGS -o "$DISK_PARAM" -O "$DST_FMT" -- "$SOURCE_FILE" "$TMP_FILE"; then if ! qemu-img convert -f "$SOURCE_FMT" $CONV_FLAGS -o "$DISK_PARAM" -O "$DST_FMT" -- "$SOURCE_FILE" "$TMP_FILE"; then
rm -f "$TMP_FILE" rm -f "$TMP_FILE"
error "Failed to convert $DISK_STYLE $DISK_DESC image to $DST_FMT format in $DIR, is there enough space available?" && exit 79 error "Failed to convert $DISK_STYLE $DISK_DESC image to $DST_FMT format in $BASE_DIR, is there enough space available?" && exit 79
fi fi
if [[ "$DST_FMT" == "raw" ]]; then if [[ "$DST_FMT" == "raw" ]]; then
if ! disabled "$ALLOCATE"; then if ! disabled "$ALLOCATE"; then
# Work around qemu-img bug # Work around qemu-img bug
CUR_SIZE=$(stat -c%s "$TMP_FILE") if ! CUR_SIZE=$(stat -c%s "$TMP_FILE"); then
error "Failed to determine converted image size: $TMP_FILE"
exit 79
fi
if ! fallocate -l "$CUR_SIZE" "$TMP_FILE" &>/dev/null; then if ! fallocate -l "$CUR_SIZE" "$TMP_FILE" &>/dev/null; then
if ! fallocate -l -x "$CUR_SIZE" "$TMP_FILE"; then if ! fallocate -l -x "$CUR_SIZE" "$TMP_FILE"; then
error "Failed to allocate $CUR_SIZE bytes for $DISK_DESC image $TMP_FILE" error "Failed to allocate $CUR_SIZE bytes for $DISK_DESC image $TMP_FILE"
@@ -368,15 +404,13 @@ convertDisk() {
fi fi
fi fi
if ! rm -f "$SOURCE_FILE"; then if ! mv "$TMP_FILE" "$DST_FILE"; then
rm -f "$TMP_FILE" error "Failed to move converted $DISK_DESC image to $DST_FILE."
error "Failed to remove old $DISK_DESC image $SOURCE_FILE."
exit 79 exit 79
fi fi
if ! mv "$TMP_FILE" "$DST_FILE"; then if ! rm -f "$SOURCE_FILE"; then
rm -f "$TMP_FILE" error "Failed to remove old $DISK_DESC image $SOURCE_FILE."
error "Failed to move converted $DISK_DESC image to $DST_FILE."
exit 79 exit 79
fi fi
@@ -399,21 +433,22 @@ checkFS () {
local FS="$1" local FS="$1"
local DISK_FILE="$2" local DISK_FILE="$2"
local DISK_DESC="$3" local DISK_DESC="$3"
local DIR FA local DIR BASE_DIR FA
DIR=$(dirname "$DISK_FILE") DIR=$(dirname "$DISK_FILE")
BASE_DIR=$(baseDir "$DIR")
[ ! -d "$DIR" ] && return 0 [ ! -d "$DIR" ] && return 0
if [[ "${FS,,}" == "overlay"* && "${ENGINE,,}" == "docker" ]]; then if [[ "${FS,,}" == "overlay"* && "${ENGINE,,}" == "docker" ]]; then
warn "the filesystem of $DIR is OverlayFS, this usually means it was binded to an invalid path!" warn "the filesystem of $BASE_DIR is OverlayFS, this usually means it was binded to an invalid path!"
fi fi
if [[ "${FS,,}" == "fuse"* ]]; then if [[ "${FS,,}" == "fuse"* ]]; then
warn "the filesystem of $DIR is FUSE, this extra layer will negatively affect performance!" warn "the filesystem of $BASE_DIR is FUSE, this extra layer will negatively affect performance!"
fi fi
if ! supportsDirect "$FS"; then if ! supportsDirect "$FS"; then
warn "the filesystem of $DIR is $FS, which does not support O_DIRECT mode, adjusting settings..." warn "the filesystem of $BASE_DIR is $FS, which does not support O_DIRECT mode, adjusting settings..."
fi fi
if isCow "$FS"; then if isCow "$FS"; then
@@ -486,7 +521,7 @@ finishDisks () {
case "${DISK_TYPE,,}" in case "${DISK_TYPE,,}" in
"blk" | "scsi" | "virtio-blk" | "virtio-scsi" ) "blk" | "scsi" | "virtio-blk" | "virtio-scsi" )
DISK_OPTS+=" -object iothread,id=io2" ;; [[ "$DISK_OPTS" != *" -object iothread,id=io2"* ]] && DISK_OPTS+=" -object iothread,id=io2" ;;
esac esac
if ! enabled "$DISK_DISABLE"; then if ! enabled "$DISK_DISABLE"; then
@@ -577,8 +612,9 @@ addDisk () {
if (( LEFT > 0 )); then if (( LEFT > 0 )); then
local GB 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"
@@ -588,8 +624,8 @@ addDisk () {
msg+=" (of which $USED is used)" msg+=" (of which $USED is used)"
fi fi
info "$msg, but there is only $GB of free space remaining in $DIR now." info "$msg, but there is only $GB of free space remaining in $BASE_DIR now."
info "Please consider making at least $LEFT more space available in $DIR for future expansions." info "Please consider making at least $LEFT more space available in $BASE_DIR for future expansions."
fi fi
@@ -597,7 +633,7 @@ addDisk () {
if [ -f "$DISK_FILE" ]; then if [ -f "$DISK_FILE" ]; then
if ! setOwner "$DISK_FILE"; then if ! setOwner "$DISK_FILE"; then
error "Failed to set the owner for \"$DISK_FILE\" !" warn "failed to set the owner for \"$DISK_FILE\" !"
fi fi
fi fi
+1 -1
View File
@@ -26,7 +26,7 @@ trap - ERR
cmd=(qemu-system-x86_64) cmd=(qemu-system-x86_64)
version=$("${cmd[@]}" --version | awk 'NR==1 { print $4 }') version=$("${cmd[@]}" --version | awk 'NR==1 { print $4 }')
info "Booting $APP using QEMU v$version..." info "Booting $APP using QEMU v$version..." && echo
if ! enabled "$SHUTDOWN"; then if ! enabled "$SHUTDOWN"; then
exec "${cmd[@]}" ${ARGS:+ $ARGS} exec "${cmd[@]}" ${ARGS:+ $ARGS}
+43 -18
View File
@@ -36,7 +36,7 @@ if [ -n "$URL" ] && [ ! -s "$FILE" ] && [ ! -d "$DIR" ]; then
printf -v BASE '%b' "${BASE//%/\\x}" printf -v BASE '%b' "${BASE//%/\\x}"
BASE="${BASE//[!A-Za-z0-9._-]/_}" BASE="${BASE//[!A-Za-z0-9._-]/_}"
fi fi
if [[ "${URL,,}" != "http"* && "${URL,,}" != "file:"* ]] ; then if [[ "${URL,,}" != "http"* && "${URL,,}" != "file:"* ]]; then
[ ! -s "$STORAGE/$BASE.pat" ] && error "Invalid URL: $URL" && exit 65 [ ! -s "$STORAGE/$BASE.pat" ] && error "Invalid URL: $URL" && exit 65
URL="file://$STORAGE/$BASE.pat" URL="file://$STORAGE/$BASE.pat"
fi fi
@@ -159,20 +159,44 @@ if [[ "$URL" == "file://"* ]]; then
else else
SIZE=0 SIZE=0
REASON=""
LOG=$(mktemp)
[[ "${URL,,}" == *"_72806.pat" ]] && SIZE=361010261 [[ "${URL,,}" == *"_72806.pat" ]] && SIZE=361010261
[[ "${URL,,}" == *"_69057.pat" ]] && SIZE=363837333 [[ "${URL,,}" == *"_69057.pat" ]] && SIZE=363837333
[[ "${URL,,}" == *"_42218.pat" ]] && SIZE=379637760 [[ "${URL,,}" == *"_42218.pat" ]] && SIZE=379637760
/run/progress.sh "$PAT" "$SIZE" "$MSG ([P])..." & /run/progress.sh "$PAT" "$SIZE" "$MSG ([P])..." &
{ wget "$URL" -O "$PAT" -q --no-check-certificate --timeout=10 --no-http-keep-alive --show-progress "$PROGRESS"; rc=$?; } || : {
LC_ALL=C wget "$URL" -O "$PAT" --no-verbose --no-check-certificate \
--timeout=30 --no-http-keep-alive --show-progress "$PROGRESS" \
--output-file="$LOG"
rc=$?
} || :
fKill "progress.sh" fKill "progress.sh"
(( rc == 3 )) && error "$ERR , cannot write file (disk full?)" && exit 69 if (( rc != 0 )); then
(( rc == 4 )) && error "$ERR , network failure!" && exit 69 REASON=$(sed -n \
(( rc == 8 )) && error "$ERR , server issued an error response!" && exit 69 -e 's/^wget: //p' \
(( rc != 0 )) && error "$ERR , reason: $rc" && exit 69 -e 's/^[0-9-]\{10\} [0-9:]\{8\} ERROR //p' \
"$LOG" | tail -n 1)
fi
rm -f "$LOG"
if (( rc == 3 )); then
error "$ERR because the file could not be written (disk full?)."
exit 69
elif (( rc != 0 )); then
if [ -n "$REASON" ]; then
error "$ERR: ${REASON%.}."
else
error "$ERR with exit status $rc."
fi
exit 69
fi
fi fi
@@ -228,7 +252,7 @@ if ! touch "$SYSTEM"; then
error "Could not create file $SYSTEM for the system disk." && exit 98 error "Could not create file $SYSTEM for the system disk." && exit 98
fi fi
! setOwner "$SYSTEM" && error "Failed to set the owner for \"$SYSTEM\" !" ! setOwner "$SYSTEM" && warn "failed to set the owner for \"$SYSTEM\" !"
if [[ "${FS,,}" == "btrfs" ]]; then if [[ "${FS,,}" == "btrfs" ]]; then
{ chattr +C "$SYSTEM"; } || : { chattr +C "$SYSTEM"; } || :
@@ -249,14 +273,15 @@ fi
PART="$TMP/partition.fdisk" PART="$TMP/partition.fdisk"
{ echo "label: dos" {
echo "label-id: 0x6f9ee2e9" echo "label: dos"
echo "device: $SYSTEM" echo "label-id: 0x6f9ee2e9"
echo "unit: sectors" echo "device: $SYSTEM"
echo "sector-size: 512" echo "unit: sectors"
echo "" echo "sector-size: 512"
echo "${SYSTEM}1 : start= 2048, size= 16777216, type=83" echo ""
echo "${SYSTEM}2 : start= 16779264, size= 4194304, type=82" echo "${SYSTEM}1 : start= 2048, size= 16777216, type=83"
echo "${SYSTEM}2 : start= 16779264, size= 4194304, type=82"
} > "$PART" } > "$PART"
sfdisk -q "$SYSTEM" < "$PART" sfdisk -q "$SYSTEM" < "$PART"
@@ -302,7 +327,7 @@ fakeroot -- bash -c "set -Eeu;\
rm -rf "$MOUNT" rm -rf "$MOUNT"
echo "$BASE" > "$STORAGE/dsm.ver" echo "$BASE" > "$STORAGE/dsm.ver"
! setOwner "$STORAGE/dsm.ver" && error "Failed to set the owner for \"$STORAGE/dsm.ver\" !" ! setOwner "$STORAGE/dsm.ver" && warn "failed to set the owner for \"$STORAGE/dsm.ver\" !"
if [[ "$URL" == "file://$STORAGE/$BASE.pat" ]]; then if [[ "$URL" == "file://$STORAGE/$BASE.pat" ]]; then
rm -f "$PAT" rm -f "$PAT"
@@ -311,11 +336,11 @@ else
fi fi
if [ -f "$STORAGE/$BASE.pat" ]; then if [ -f "$STORAGE/$BASE.pat" ]; then
! setOwner "$STORAGE/$BASE.pat" && error "Failed to set the owner for \"$STORAGE/$BASE.pat\" !" ! setOwner "$STORAGE/$BASE.pat" && warn "failed to set the owner for \"$STORAGE/$BASE.pat\" !"
fi fi
mv -f "$BOOT" "$STORAGE/$BASE.boot.img" mv -f "$BOOT" "$STORAGE/$BASE.boot.img"
! setOwner "$STORAGE/$BASE.boot.img" && error "Failed to set the owner for \"$STORAGE/$BASE.boot.img\" !" ! setOwner "$STORAGE/$BASE.boot.img" && warn "failed to set the owner for \"$STORAGE/$BASE.boot.img\" !"
rm -rf "$TMP" rm -rf "$TMP"
+9 -8
View File
@@ -4,11 +4,12 @@ set -Eeuo pipefail
msg="Checking memory..." msg="Checking memory..."
enabled "$DEBUG" && echo "$msg" enabled "$DEBUG" && echo "$msg"
RAM_AVAIL=$(free -b | grep -m 1 Mem: | awk '{print $7}') app() {
AVAIL_MEM=$(formatBytes "$RAM_AVAIL") echo "Virtual DSM"
return 0
}
checkConfiguredMemory() { checkConfiguredMemory() {
local wanted msg local wanted msg
if disabled "$RAM_CHECK" || [[ "${RAM_SIZE,,}" == "max" || "${RAM_SIZE,,}" == "half" ]]; then if disabled "$RAM_CHECK" || [[ "${RAM_SIZE,,}" == "max" || "${RAM_SIZE,,}" == "half" ]]; then
@@ -40,7 +41,6 @@ checkConfiguredMemory() {
} }
configureHalfMemory() { configureHalfMemory() {
local wanted local wanted
if [[ "${RAM_SIZE,,}" != "half" ]]; then if [[ "${RAM_SIZE,,}" != "half" ]]; then
@@ -50,7 +50,7 @@ configureHalfMemory() {
if (( (RAM_AVAIL / 2) > RAM_SPARE )); then if (( (RAM_AVAIL / 2) > RAM_SPARE )); then
wanted=$(( (RAM_AVAIL / 2) / 1048577 )) wanted=$(( (RAM_AVAIL / 2) / 1048577 ))
RAM_SIZE="${wanted}M" RAM_SIZE="${wanted}M"
info "Allocated $wanted MB of RAM for the virtual machine." info "Allocated $wanted MB of RAM for $(app)."
else else
RAM_SIZE="max" RAM_SIZE="max"
fi fi
@@ -59,7 +59,6 @@ configureHalfMemory() {
} }
configureMaxMemory() { configureMaxMemory() {
local wanted local wanted
if [[ "${RAM_SIZE,,}" != "max" ]]; then if [[ "${RAM_SIZE,,}" != "max" ]]; then
@@ -83,13 +82,12 @@ configureMaxMemory() {
wanted=$(( wanted / 1048577 )) wanted=$(( wanted / 1048577 ))
RAM_SIZE="${wanted}M" RAM_SIZE="${wanted}M"
info "Allocated $wanted MB of RAM for the virtual machine." info "Allocated $wanted MB of RAM for $(app)."
return 0 return 0
} }
checkMinimumMemory() { checkMinimumMemory() {
local wanted local wanted
wanted=$(numfmt --from=iec "$RAM_SIZE") wanted=$(numfmt --from=iec "$RAM_SIZE")
@@ -103,6 +101,9 @@ checkMinimumMemory() {
return 0 return 0
} }
getMemoryInfo
AVAIL_MEM=$(formatBytes "$RAM_AVAIL")
checkConfiguredMemory checkConfiguredMemory
configureHalfMemory configureHalfMemory
configureMaxMemory configureMaxMemory
+389 -132
View File
@@ -52,7 +52,7 @@ ADD_ERR="Please add the following setting to your container:"
isNAT() { isNAT() {
case "${NETWORK,,}" in case "${NETWORK,,}" in
"tap" | "tun" | "tuntap" | "y" | "" ) "nat" | "tap" | "tun" | "tuntap" | "y" | "" )
return 0 ;; return 0 ;;
*) *)
return 1 ;; return 1 ;;
@@ -126,6 +126,11 @@ maskToCIDR() {
local mask="$1" local mask="$1"
local prefix="" local prefix=""
if ! command -v ipcalc > /dev/null 2>&1; then
error "Required command 'ipcalc' is not installed!"
return 1
fi
prefix=$(ipcalc -n -b "0.0.0.0/$mask" 2>/dev/null | awk ' prefix=$(ipcalc -n -b "0.0.0.0/$mask" 2>/dev/null | awk '
/^Netmask:/ { /^Netmask:/ {
for (i = 1; i <= NF; i++) { for (i = 1; i <= NF; i++) {
@@ -137,7 +142,7 @@ maskToCIDR() {
} }
') ')
if [[ ! "$prefix" =~ ^[0-9]+$ ]] || (( prefix < 1 || prefix > 30 )); then if [[ ! "$prefix" =~ ^[0-9]+$ ]] || (( prefix < 0 || prefix > 32 )); then
error "Invalid MASK: '$mask'" error "Invalid MASK: '$mask'"
return 1 return 1
fi fi
@@ -347,7 +352,7 @@ configureDNS() {
# Set DHCP range and host # Set DHCP range and host
arguments+=" --dhcp-range=$ip,$ip" arguments+=" --dhcp-range=$ip,$ip"
arguments+=" --dhcp-host=$mac,,$ip,$host,infinite" arguments+=" --dhcp-host=$mac,,$ip,$host,1h"
# Set DNS server and gateway # Set DNS server and gateway
arguments+=" --dhcp-option=option:netmask,$mask" arguments+=" --dhcp-option=option:netmask,$mask"
@@ -365,9 +370,6 @@ configureDNS() {
arguments+=" --interface=$fa" arguments+=" --interface=$fa"
arguments+=" --bind-interfaces" arguments+=" --bind-interfaces"
# Set pid file
arguments+=" --pid-file=$DNSMASQ_PID"
# Workaround NET_RAW capability # Workaround NET_RAW capability
arguments+=" --no-ping" arguments+=" --no-ping"
@@ -382,6 +384,9 @@ configureDNS() {
# Set local dns resolver to dnsmasq when needed # Set local dns resolver to dnsmasq when needed
[ -f /etc/resolv.dnsmasq ] && arguments+=" --resolv-file=/etc/resolv.dnsmasq" [ -f /etc/resolv.dnsmasq ] && arguments+=" --resolv-file=/etc/resolv.dnsmasq"
# Set pid file
arguments+=" --pid-file=$DNSMASQ_PID"
# Enable logging to file # Enable logging to file
local log="/var/log/dnsmasq.log" local log="/var/log/dnsmasq.log"
rm -f "$log" rm -f "$log"
@@ -413,14 +418,38 @@ configureDNS() {
getHostPorts() { getHostPorts() {
local list="${HOST_PORTS// /}," local num=""
local ports=""
local port="" local port=""
local ports=""
local mode="${1:-tcp}"
local list="${HOST_PORTS// /},"
for port in ${list//,/ }; do for port in ${list//,/ }; do
port="${port%/tcp}"
port="${port%/udp}" [ -z "$port" ] && continue
[ -n "$port" ] && ports+="$port,"
case "$mode" in
"tcp" )
[[ "$port" == *"/udp" ]] && continue
num="${port%/tcp}"
;;
"all" )
if [[ "$port" == *"/udp" ]]; then
num="${port%/udp}"
[ -n "$num" ] && ports+="$num/udp,"
else
num="${port%/tcp}"
[ -n "$num" ] && ports+="$num/tcp,"
fi
continue
;;
*)
return 1
;;
esac
[ -n "$num" ] && ports+="$num,"
done done
# Remove duplicates # Remove duplicates
@@ -432,44 +461,55 @@ getHostPorts() {
getUserPorts() { getUserPorts() {
local ssh="22" local defaults="22/tcp,5000/tcp,5001/tcp"
local dsm="5000,5001" local list="$defaults,${USER_PORTS// /},"
local list="$ssh,$dsm,"
list+="${USER_PORTS// /},"
local exclude
exclude=$(getHostPorts)
local num=""
local ports="" local ports=""
local proto=""
local userport="" local userport=""
local hostport="" local hostport=""
local exclude=""
exclude=$(getHostPorts "all")
for userport in ${list//,/ }; do for userport in ${list//,/ }; do
local num="${userport///tcp}" proto="tcp"
num="${num///udp}" num="$userport"
if [[ "$userport" == *"/udp" ]]; then
proto="udp"
num="${userport%/udp}"
elif [[ "$userport" == *"/tcp" ]]; then
proto="tcp"
num="${userport%/tcp}"
fi
[ -z "$num" ] && continue
for hostport in ${exclude//,/ }; do for hostport in ${exclude//,/ }; do
local port="${hostport///tcp}" if [[ "$num/$proto" == "$hostport" ]]; then
port="${port///udp}"
if [[ "$num" == "$port" ]]; then
num="" num=""
if [[ "$port" != "${WEB_PORT:-}" ]]; then
warn "Could not assign port $port to \"USER_PORTS\" because it is already in \"HOST_PORTS\"!" if [[ "$hostport" != "${WEB_PORT:-}/tcp" ]]; then
warn "Could not assign port $hostport to \"USER_PORTS\" because it is already in \"HOST_PORTS\"!"
fi fi
break
fi fi
done done
[ -n "$num" ] && ports+="$userport," [ -n "$num" ] && ports+="$num/$proto,"
done done
# Remove duplicates # Remove duplicates
echo "${ports//,,/,}," | awk 'BEGIN{RS=ORS=","} !seen[$0]++' | sed 's/,*$//g' echo "${ports//,,/,}," | awk 'BEGIN{RS=ORS=","} !seen[$0]++' | sed 's/,*$//g'
return 0 return 0
} }
@@ -490,10 +530,6 @@ getSlirp() {
if [[ "$port" == *"/udp" ]]; then if [[ "$port" == *"/udp" ]]; then
proto="udp" proto="udp"
num="${port%/udp}" num="${port%/udp}"
elif [[ "$port" != *"/tcp" ]]; then
args+="hostfwd=$proto::$num-$ip:$num,"
proto="udp"
num="${port%/udp}"
fi fi
args+="hostfwd=$proto::$num-$ip:$num," args+="hostfwd=$proto::$num-$ip:$num,"
@@ -531,7 +567,6 @@ getPasst() {
else else
tcp+="$port," tcp+="$port,"
udp+="$port,"
fi fi
@@ -655,7 +690,10 @@ configureSlirp() {
[ -n "$forward" ] && NET_OPTS+=",$forward" [ -n "$forward" ] && NET_OPTS+=",$forward"
if enabled "${DNSMASQ_DISABLE:-}"; then if enabled "${DNSMASQ_DISABLE:-}"; then
echo "$gateway" > /run/shm/qemu.gw || warn "Failed to write gateway file." if ! echo "$gateway" > /run/shm/qemu.gw; then
error "Failed to write gateway file."
return 1
fi
else else
if [ ! -f /etc/resolv.dnsmasq ] && ! cp /etc/resolv.conf /etc/resolv.dnsmasq; then if [ ! -f /etc/resolv.dnsmasq ] && ! cp /etc/resolv.conf /etc/resolv.dnsmasq; then
error "Failed to backup /etc/resolv.conf." error "Failed to backup /etc/resolv.conf."
@@ -775,14 +813,16 @@ configurePasst() {
createBridge() { createBridge() {
local gateway="$1" local gateway="$1"
local rc local rc msg=""
# Create a bridge with a static IP for the VM guest # Create a bridge with a static IP for the VM guest
{ ip link add dev "$BRIDGE" type bridge; rc=$?; } || : { msg=$(ip link add dev "$BRIDGE" type bridge 2>&1); rc=$?; } || :
if (( rc != 0 )); then if (( rc != 0 )); then
enabled "$ROOTLESS" && ! enabled "$DEBUG" && return 1 enabled "$ROOTLESS" && ! enabled "$DEBUG" && return 1
warn "failed to create bridge. $ADD_ERR --cap-add NET_ADMIN" && return 1 [ -n "$msg" ] && echo "$msg" >&2
warn "failed to create bridge. $ADD_ERR --cap-add NET_ADMIN"
return 1
fi fi
if [[ "$GUEST_MTU" != "0" ]]; then if [[ "$GUEST_MTU" != "0" ]]; then
@@ -807,11 +847,16 @@ createBridge() {
createTap() { createTap() {
local tuntap="$1" local tuntap="$1"
local rc msg=""
# Set tap to the bridge created # Set tap to the bridge created
if ! ip tuntap add dev "$TAP" mode tap; then { msg=$(ip tuntap add dev "$TAP" mode tap 2>&1); rc=$?; } || :
if (( rc != 0 )); then
enabled "$ROOTLESS" && ! enabled "$DEBUG" && return 1 enabled "$ROOTLESS" && ! enabled "$DEBUG" && return 1
warn "$tuntap" && return 1 [ -n "$msg" ] && echo "$msg" >&2
warn "$tuntap"
return 1
fi fi
if [[ "$GUEST_MTU" != "0" ]]; then if [[ "$GUEST_MTU" != "0" ]]; then
@@ -837,64 +882,155 @@ createTap() {
return 0 return 0
} }
showRules() {
local table="$1"
local chain="$2"
local label="$3"
local rules=""
rules=$(iptables -t "$table" -S "$chain" 2>/dev/null |
awk '$1 == "-A"' || true)
[ -n "$rules" ] || return 0
printf "Existing %s rules:\n\n%s\n\n" "$label" "$rules"
return 0
}
checkExistingTables() {
local msg=""
local rules=""
local conflicts=""
rules=$(iptables -t nat -S PREROUTING 2>/dev/null |
awk '$1 == "-A"' || true)
conflicts=$(grep -E -- \
'^-A PREROUTING .*(-j DNAT|-j REDIRECT)( |$)' \
<<< "$rules" || true)
if [ -n "$conflicts" ]; then
msg="your existing NAT rules may take precedence over VM port forwarding"
if enabled "$DEBUG"; then
warn "${msg}."
else
warn "${msg}; enable DEBUG=Y to inspect them."
fi
fi
rules=$(iptables -t filter -S FORWARD 2>/dev/null |
awk '$1 == "-A"' || true)
conflicts=$(grep -E -- \
'^-A FORWARD .*(-j DROP|-j REJECT)( |$)' \
<<< "$rules" || true)
if [ -n "$conflicts" ]; then
msg="your existing firewall rules may block traffic forwarded to or from the VM"
if enabled "$DEBUG"; then
warn "${msg}."
else
warn "${msg}; enable DEBUG=Y to inspect them."
fi
fi
if enabled "$DEBUG"; then
showRules nat PREROUTING "NAT PREROUTING"
showRules filter FORWARD "filter FORWARD"
showRules nat POSTROUTING "NAT POSTROUTING"
showRules mangle FORWARD "mangle FORWARD"
showRules mangle POSTROUTING "mangle POSTROUTING"
fi
return 0
}
configureTables() { configureTables() {
local ip="$1" local ip="$1"
local subnet="$2" local subnet="$2"
local exclude="$3" local exclude=""
local port=""
local dnat_chain="QEMU_DNAT"
local rule_tag="remove" local rule_tag="remove"
local tables_err="failed to configure IP tables!" local tables_err="failed to configure IP tables!"
local tables="the 'ip_tables' kernel module is not loaded. Try this command: sudo modprobe ip_tables iptable_nat" local tables="the 'ip_tables' kernel module is not loaded. Try this command: sudo modprobe ip_tables iptable_nat"
clearTables if ! clearTables; then
enabled "$ROOTLESS" && ! enabled "$DEBUG" && return 1
if [ -n "$exclude" ]; then warn "failed to select a working IP tables backend!"
if [[ "$exclude" != *","* ]]; then return 1
exclude=" ! --dport $exclude"
else
exclude=" -m multiport ! --dports $exclude"
fi
fi fi
# NAT traffic from bridge subnet to Docker uplink checkExistingTables
exclude=$(getHostPorts)
# NAT traffic from the VM subnet leaving through any external interface.
if ! iptables -t nat -A POSTROUTING \ if ! iptables -t nat -A POSTROUTING \
-o "$DEV" \ ! -o "$BRIDGE" \
-s "$subnet" \ -s "$subnet" \
! -d "$subnet" \ ! -d "$subnet" \
-m comment --comment "$rule_tag" \ -m comment --comment "$rule_tag" \
-j MASQUERADE > /dev/null 2>&1; then -j MASQUERADE > /dev/null 2>&1; then
enabled "$ROOTLESS" && ! enabled "$DEBUG" && return 1 enabled "$ROOTLESS" && ! enabled "$DEBUG" && return 1
if ! iptables -t nat -A POSTROUTING \ if ! iptables -t nat -A POSTROUTING \
-o "$DEV" \ ! -o "$BRIDGE" \
-s "$subnet" \ -s "$subnet" \
! -d "$subnet" \ ! -d "$subnet" \
-m comment --comment "$rule_tag" \ -m comment --comment "$rule_tag" \
-j MASQUERADE; then -j MASQUERADE; then
warn "$tables" && return 1 warn "$tables"
return 1
fi fi
fi fi
# shellcheck disable=SC2086 # Use a dedicated chain so protected TCP ports do not depend on multiport support.
if ! iptables -t nat -A PREROUTING \ if ! iptables -t nat -N "$dnat_chain"; then
-i "$DEV" \ warn "$tables_err"
-d "$UPLINK" \ return 1
-p tcp${exclude} \
-m comment --comment "$rule_tag" \
-j DNAT --to "$ip"; then
warn "$tables_err" && return 1
fi fi
if ! iptables -t nat -A PREROUTING \ # Keep container-owned TCP ports handled by the container.
-i "$DEV" \ for port in ${exclude//,/ }; do
-d "$UPLINK" \
-p udp \ [ -z "$port" ] && continue
if ! iptables -t nat -A "$dnat_chain" \
-p tcp \
--dport "$port" \
-m comment --comment "$rule_tag" \
-j RETURN; then
warn "$tables_err"
return 1
fi
done
# Forward every remaining protocol and port to the VM.
if ! iptables -t nat -A "$dnat_chain" \
-m comment --comment "$rule_tag" \ -m comment --comment "$rule_tag" \
-j DNAT --to "$ip"; then -j DNAT --to "$ip"; then
warn "$tables_err" && return 1 warn "$tables_err"
return 1
fi
# Process incoming traffic addressed to the container through the VM chain.
if ! iptables -t nat -A PREROUTING \
! -i "$BRIDGE" \
-m addrtype --dst-type LOCAL \
-m comment --comment "$rule_tag" \
-j "$dnat_chain"; then
warn "$tables_err"
return 1
fi fi
if (( KERNEL > 4 )); then if (( KERNEL > 4 )); then
# Hack for guest VMs complaining about "bad udp checksums in 5 packets" # Hack for guest VMs complaining about "bad udp checksums in 5 packets".
iptables -t mangle -A POSTROUTING \ iptables -t mangle -A POSTROUTING \
-s "$subnet" \ -s "$subnet" \
-p udp \ -p udp \
@@ -918,23 +1054,26 @@ configureTables() {
-m comment --comment "$rule_tag" \ -m comment --comment "$rule_tag" \
-j TCPMSS --clamp-mss-to-pmtu > /dev/null 2>&1 || true -j TCPMSS --clamp-mss-to-pmtu > /dev/null 2>&1 || true
# Allow forwarding from bridge -> dev # Allow forwarding from the VM bridge to external interfaces.
if ! iptables -A FORWARD \ if ! iptables -A FORWARD \
-i "$BRIDGE" \ -i "$BRIDGE" \
-o "$DEV" \ ! -o "$BRIDGE" \
-s "$subnet" \
-m comment --comment "$rule_tag" \ -m comment --comment "$rule_tag" \
-j ACCEPT; then -j ACCEPT; then
warn "$tables_err" && return 1 warn "$tables_err"
return 1
fi fi
# Allow return traffic # Allow forwarding from external interfaces to the VM.
if ! iptables -A FORWARD \ if ! iptables -A FORWARD \
-i "$DEV" \ ! -i "$BRIDGE" \
-o "$BRIDGE" \ -o "$BRIDGE" \
-m conntrack --ctstate RELATED,ESTABLISHED \ -d "$ip" \
-m comment --comment "$rule_tag" \ -m comment --comment "$rule_tag" \
-j ACCEPT; then -j ACCEPT; then
warn "$tables_err" && return 1 warn "$tables_err"
return 1
fi fi
return 0 return 0
@@ -943,15 +1082,21 @@ configureTables() {
configureNAT() { configureNAT() {
local tuntap="TUN device is missing. $ADD_ERR --device /dev/net/tun" local tuntap="TUN device is missing. $ADD_ERR --device /dev/net/tun"
local rc local msg=""
local rc ip subnet forwarding=""
enabled "$DEBUG" && echo "Configuring NAT networking..." enabled "$DEBUG" && echo "Configuring NAT networking..."
# Create the necessary file structure for /dev/net/tun # Create the necessary file structure for /dev/net/tun
if [ ! -c /dev/net/tun ]; then if [ ! -c /dev/net/tun ]; then
[ ! -d /dev/net ] && mkdir -m 755 /dev/net [ ! -d /dev/net ] && mkdir -m 755 /dev/net > /dev/null 2>&1 || :
if mknod /dev/net/tun c 10 200; then
{ msg=$(mknod /dev/net/tun c 10 200 2>&1); rc=$?; } || :
if (( rc == 0 )); then
chmod 666 /dev/net/tun chmod 666 /dev/net/tun
elif ! enabled "$ROOTLESS" || enabled "$DEBUG"; then
[ -n "$msg" ] && echo "$msg" >&2
fi fi
fi fi
@@ -961,7 +1106,10 @@ configureNAT() {
fi fi
# Check port forwarding flag # Check port forwarding flag
if [[ $(< /proc/sys/net/ipv4/ip_forward) -eq 0 ]]; then [ -r /proc/sys/net/ipv4/ip_forward ] &&
forwarding=$(< /proc/sys/net/ipv4/ip_forward)
if [[ "$forwarding" != "1" ]]; then
{ sysctl -w net.ipv4.ip_forward=1 > /dev/null 2>&1; rc=$?; } || : { sysctl -w net.ipv4.ip_forward=1 > /dev/null 2>&1; rc=$?; } || :
if (( rc != 0 )) || [[ $(< /proc/sys/net/ipv4/ip_forward) -eq 0 ]]; then if (( rc != 0 )) || [[ $(< /proc/sys/net/ipv4/ip_forward) -eq 0 ]]; then
enabled "$ROOTLESS" && ! enabled "$DEBUG" && return 1 enabled "$ROOTLESS" && ! enabled "$DEBUG" && return 1
@@ -970,8 +1118,6 @@ configureNAT() {
fi fi
fi fi
local ip exclude subnet
if [ -n "$IP" ]; then if [ -n "$IP" ]; then
ip=$(guestIP "$IP" 2) ip=$(guestIP "$IP" 2)
else else
@@ -994,8 +1140,7 @@ configureNAT() {
GUEST_MTU=$(minMTU "$GUEST_MTU" "$(getMTU "$BRIDGE")" "$(getMTU "$TAP")") GUEST_MTU=$(minMTU "$GUEST_MTU" "$(getMTU "$BRIDGE")" "$(getMTU "$TAP")")
fi fi
exclude=$(getHostPorts) configureTables "$ip" "$subnet" || return 1
configureTables "$ip" "$subnet" "$exclude" || return 1
NET_OPTS="-netdev tap,id=hostnet0,ifname=$TAP" NET_OPTS="-netdev tap,id=hostnet0,ifname=$TAP"
@@ -1013,65 +1158,150 @@ configureNAT() {
} }
# ###################################### # ######################################
# Cleanup # IP Tables
# ###################################### # ######################################
clearTables() { setTables() {
local table="" line rules local mode="$1"
local rule_tag="remove" local path=""
local re="--comment[[:space:]]+\"?$rule_tag\"?([[:space:]]|\$)"
# Choose between iptables or nftables path=$(command -v "iptables-$mode" 2>/dev/null || true)
if command -v iptables-nft >/dev/null 2>&1 && iptables-nft -V >/dev/null 2>&1; then [ -z "$path" ] && return 1
update-alternatives --set iptables /usr/sbin/iptables-nft > /dev/null
update-alternatives --set ip6tables /usr/sbin/ip6tables-nft > /dev/null
else
update-alternatives --set iptables /usr/sbin/iptables-legacy > /dev/null
update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy > /dev/null
fi
# Store the current iptables ruleset update-alternatives --set iptables "$path" > /dev/null 2>&1
! rules=$(iptables-save 2> /dev/null) && return 0 }
[ -z "$rules" ] && return 0
# Delete every rule tagged with our unique identifier, leaving all other rules intact. testTables() {
while IFS= read -r line; do
case "$line" in local table=""
\*nat) table="nat" ;;
\*filter) table="filter" ;; # Test every table required by the networking rules.
\*mangle) table="mangle" ;; for table in nat filter; do
\*raw) table="raw" ;; iptables -t "$table" -S > /dev/null 2>&1 || return 1
esac iptables-save -t "$table" > /dev/null 2>&1 || return 1
if [[ "$line" == -A* ]]; then done
if [[ "$line" =~ $re ]]; then
read -ra args <<< "${line/-A /-D }"
iptables -t "$table" "${args[@]}" &> /dev/null || :
fi
fi
done <<< "$rules"
return 0 return 0
} }
closeBridge() { selectTables() {
local mode=""
local current=""
local modes=()
# Keep the currently selected backend when it is fully functional.
if testTables; then
return 0
fi
current=$(iptables --version 2>/dev/null || true)
if [[ "$current" == *"nf_tables"* ]]; then
modes=( "legacy" )
elif [[ "$current" == *"legacy"* ]]; then
modes=( "nft" )
elif [[ "${ENGINE,,}" == "docker" ]]; then
modes=( "legacy" "nft" )
else
modes=( "nft" "legacy" )
fi
for mode in "${modes[@]}"; do
command -v "iptables-$mode" > /dev/null 2>&1 || continue
setTables "$mode" && testTables && return 0
done
return 1
}
clearTables() {
local table=""
local line=""
local rules=""
local failed="N"
local dnat_chain="QEMU_DNAT"
local rule_tag="remove"
local re="--comment[[:space:]]+\"?$rule_tag\"?([[:space:]]|\$)"
selectTables || return 1
# Store the current iptables ruleset.
! rules=$(iptables-save 2> /dev/null) && return 1
if [ -n "$rules" ]; then
# Delete every rule tagged with our unique identifier,
# leaving all other rules intact.
while IFS= read -r line; do
case "$line" in
\*nat ) table="nat" ;;
\*filter ) table="filter" ;;
\*mangle ) table="mangle" ;;
\*raw ) table="raw" ;;
esac
if [[ "$line" == -A* ]] && [[ "$line" =~ $re ]]; then
line="${line/-A /-D }"
# Parse the quoting produced by iptables-save before deleting the rule.
if ! printf '%s\n' "$line" |
xargs -r iptables -t "$table" > /dev/null 2>&1; then
failed="Y"
fi
fi
done <<< "$rules"
fi
# Remove the dedicated DNAT chain after deleting its rules and references.
if iptables -t nat -S "$dnat_chain" > /dev/null 2>&1; then
if ! iptables -t nat -F "$dnat_chain" > /dev/null 2>&1; then
failed="Y"
fi
if ! iptables -t nat -X "$dnat_chain" > /dev/null 2>&1; then
failed="Y"
fi
fi
enabled "$failed" && return 1
return 0
}
# ######################################
# Cleanup
# ######################################
closeInterfaces() {
local pids=( "$PASST_PID" "$DNSMASQ_PID" ) local pids=( "$PASST_PID" "$DNSMASQ_PID" )
mKill "${pids[@]}" mKill "${pids[@]}"
exec 30>&- 2>/dev/null || true
exec 40>&- 2>/dev/null || true
ip link set "$TAP" down promisc off &> /dev/null || : ip link set "$TAP" down promisc off &> /dev/null || :
ip link delete "$TAP" &> /dev/null || : ip link delete "$TAP" &> /dev/null || :
ip link set "$BRIDGE" down &> /dev/null || : ip link set "$BRIDGE" down &> /dev/null || :
ip link delete "$BRIDGE" &> /dev/null || : ip link delete "$BRIDGE" &> /dev/null || :
clearTables clearTables || :
return 0 return 0
} }
closeWeb() { closeWeb() {
local pids=( "$WEB_PID" "$WSD_PID" ) local pids=( "${WEB_PID:-}" "${WSD_PID:-}" )
mKill "${pids[@]}" mKill "${pids[@]}"
return 0 return 0
@@ -1085,17 +1315,14 @@ closeNetwork() {
disabled "$NETWORK" && return 0 disabled "$NETWORK" && return 0
exec 30>&- 2>/dev/null || true closeInterfaces
exec 40>&- 2>/dev/null || true
closeBridge
return 0 return 0
} }
cleanUp() { cleanUp() {
closeBridge closeInterfaces
# Clean up old files # Clean up old files
rm -f "$PASST_PID" "$PASST_SOCKET" rm -f "$PASST_PID" "$PASST_SOCKET"
@@ -1146,6 +1373,11 @@ validateMask() {
PREFIX=$(maskToCIDR "$MASK") || exit 28 PREFIX=$(maskToCIDR "$MASK") || exit 28
if (( PREFIX < 1 || PREFIX > 24 )); then
error "Unsupported MASK: '$MASK' (supported range: /1 through /24)"
exit 28
fi
return 0 return 0
} }
@@ -1163,6 +1395,15 @@ validateHost() {
return 0 return 0
} }
validateHostPorts() {
if isNAT && [[ "${HOST_PORTS,,}" == *"/udp"* ]]; then
warn "UDP ports in \"HOST_PORTS\" are not yet implemented for NAT networking."
fi
return 0
}
validateAddresses() { validateAddresses() {
# DHCP/macvtap mode can work without a detectable container IPv4 address, # DHCP/macvtap mode can work without a detectable container IPv4 address,
@@ -1255,15 +1496,26 @@ configureMAC() {
container=$(containerID) container=$(containerID)
if [ -z "$MAC" ]; then if [ -z "$MAC" ]; then
file="$STORAGE/dsm.mac" file="$STORAGE/dsm.mac"
[ -s "$file" ] && MAC=$(<"$file")
MAC="${MAC//[![:print:]]/}" if [ -s "$file" ]; then
if ! MAC=$(readFile "$file"); then
error "Failed to read MAC address from \"$file\" !"
exit 28
fi
fi
if [ -z "$MAC" ]; then if [ -z "$MAC" ]; then
# Generate a Synology-style MAC address based on a stable container identifier when possible. # Generate a Synology-style MAC address based on a stable container identifier when possible.
MAC=$(echo "$container" | md5sum | sed 's/^\(..\)\(..\)\(..\)\(..\)\(..\).*$/02:11:32:\3:\4:\5/') MAC=$(echo "$container" | md5sum | sed 's/^\(..\)\(..\)\(..\)\(..\)\(..\).*$/02:11:32:\3:\4:\5/')
echo "${MAC^^}" > "$file"
! setOwner "$file" && error "Failed to set the owner for \"$file\" !" if ! writeFile "${MAC^^}" "$file"; then
error "Failed to write MAC address to \"$file\" !"
exit 28
fi
fi fi
fi fi
@@ -1280,8 +1532,7 @@ configureMAC() {
exit 28 exit 28
fi fi
# Keep the guest-facing gateway MAC stable across runs, otherwise Windows guests # Keep the guest-facing gateway MAC stable across runs.
# may detect a new network every boot.
GATEWAY_MAC=$(gatewayMAC "$MAC") GATEWAY_MAC=$(gatewayMAC "$MAC")
return 0 return 0
@@ -1407,6 +1658,7 @@ prepareNetwork() {
validateMask validateMask
validateHost validateHost
validateHostPorts
detectAddresses detectAddresses
validateAddresses validateAddresses
@@ -1437,8 +1689,15 @@ enabled "$DEBUG" && echo "$msg"
prepareNetwork prepareNetwork
echo "$UPLINK" > "$QEMU_DIR"/qemu.ip if ! echo "$UPLINK" > "$QEMU_DIR"/qemu.ip; then
echo "$NIC" > "$QEMU_DIR"/qemu.nic error "Failed to write QEMU IP file!"
exit 24
fi
if ! echo "$NIC" > "$QEMU_DIR"/qemu.nic; then
error "Failed to write QEMU NIC file!"
exit 24
fi
cleanUp cleanUp
@@ -1467,7 +1726,7 @@ else
# Configure tap interface # Configure tap interface
if ! configureNAT; then if ! configureNAT; then
closeBridge closeInterfaces
NETWORK="user" NETWORK="user"
if ! enabled "$ROOTLESS" || enabled "$DEBUG"; then if ! enabled "$ROOTLESS" || enabled "$DEBUG"; then
@@ -1509,10 +1768,8 @@ else
showGuestInfo showGuestInfo
if [[ "${NETWORK,,}" == "passt" || "${NETWORK,,}" == "slirp" ]]; then if isUserMode && [ -z "$USER_PORTS" ]; then
if [ -z "$USER_PORTS" ]; then info "Notice: because user-mode networking is active, when you need to forward custom ports to DSM, add them to the \"USER_PORTS\" variable."
info "Notice: because user-mode networking is active, when you need to forward custom ports to DSM, add them to the \"USER_PORTS\" variable."
fi
fi fi
fi fi
+28 -45
View File
@@ -53,6 +53,32 @@ checkSse42() {
return 0 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() { configureKvmCpuModel() {
CPU_FEATURES="kvm=on,l3-cache=on,+hypervisor" CPU_FEATURES="kvm=on,l3-cache=on,+hypervisor"
@@ -130,52 +156,9 @@ configureTcg() {
return 0 return 0
} }
extractHostCpuArgument() {
local args prefix suffix param
if [[ "$ARGUMENTS" == *"-cpu host,"* ]]; then
args="${ARGUMENTS} "
prefix="${args/-cpu host,*/}"
suffix="${args/*-cpu host,/}"
param="${suffix%% *}"
suffix="${suffix#* }"
args="${prefix}${suffix}"
ARGUMENTS="${args::-1}"
if [ -z "$CPU_FLAGS" ]; then
CPU_FLAGS="$param"
else
CPU_FLAGS+=",$param"
fi
else
if [[ "$ARGUMENTS" == *"-cpu host"* ]]; then
ARGUMENTS="${ARGUMENTS//-cpu host/}"
fi
fi
return 0
}
composeCpuFlags() { composeCpuFlags() {
if [ -z "$CPU_FLAGS" ]; then CPU_FLAGS="${CPU_MODEL}${CPU_FEATURES:+,$CPU_FEATURES}${CPU_FLAGS:+,$CPU_FLAGS}"
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
return 0 return 0
} }
@@ -211,7 +194,7 @@ else
configureTcg configureTcg
fi fi
extractHostCpuArgument removeCpuArgument
composeCpuFlags composeCpuFlags
configureHostCpuName configureHostCpuName
+2 -2
View File
@@ -27,12 +27,12 @@ do
if [ ! -s "$path" ] && [ ! -d "$path" ]; then if [ ! -s "$path" ] && [ ! -d "$path" ]; then
bytes="0" bytes="0"
else else
bytes=$(du -sb "$path" | cut -f1) bytes=$(du -sb "$path" 2>/dev/null | cut -f1) || bytes="0"
fi fi
if (( bytes > 4096 )); then if (( bytes > 4096 )); then
if [ -z "$total" ] || [[ "$total" == "0" ]] || [ "$bytes" -gt "$total" ]; then if [ -z "$total" ] || [[ "$total" == "0" ]] || [ "$bytes" -gt "$total" ]; then
size=$(numfmt --to=iec --suffix=B "$bytes" | sed -r 's/([A-Z])/ \1/') size=$(numfmt --to=iec --suffix=B "$bytes" | sed -r 's/([A-Z])/ \1/') || size="${bytes} bytes"
else else
size="$(echo "$bytes" "$total" | awk '{printf "%.1f", $1 * 100 / $2}')" size="$(echo "$bytes" "$total" | awk '{printf "%.1f", $1 * 100 / $2}')"
size="$size%" size="$size%"
+79 -6
View File
@@ -25,12 +25,29 @@ detectEngine() {
if [ -f "/run/.containerenv" ]; then if [ -f "/run/.containerenv" ]; then
ENGINE="${container:-}" ENGINE="${container:-}"
if [[ "${ENGINE,,}" == *"podman"* ]]; then if [[ "${ENGINE,,}" == *"podman"* ]]; then
ROOTLESS="Y"
ENGINE="Podman" ENGINE="Podman"
else else
[ -z "$ENGINE" ] && ENGINE="Kubernetes" [ -z "$ENGINE" ] && ENGINE="Kubernetes"
fi fi
elif [ -f "/.dockerenv" ]; then
ENGINE="Docker"
fi
return 0
}
detectRootless() {
local uid_map=""
uid_map=$(awk '{$1=$1; print}' /proc/self/uid_map 2>/dev/null || true)
if [[ "$uid_map" == "0 0 4294967295" ]]; then
ROOTLESS="N"
else
ROOTLESS="Y"
fi fi
return 0 return 0
@@ -53,7 +70,6 @@ checkPrivileged() {
max_cap=$(((1 << (last_cap + 1)) - 1)) max_cap=$(((1 << (last_cap + 1)) - 1))
if [ "$cap_bnd" -eq "$max_cap" ]; then if [ "$cap_bnd" -eq "$max_cap" ]; then
ROOTLESS="N"
PRIVILEGED="Y" PRIVILEGED="Y"
fi fi
@@ -123,11 +139,67 @@ checkFilesystem() {
return 0 return 0
} }
finiteMemoryLimit() {
local limit="$1"
local sentinel="4611686018427387904"
local i=0
local left=""
local right=""
[[ "$limit" =~ ^[0-9]+$ ]] || return 1
(( ${#limit} < ${#sentinel} )) && return 0
(( ${#limit} > ${#sentinel} )) && return 1
for (( i=0; i<${#sentinel}; i++ )); do
left="${limit:i:1}"
right="${sentinel:i:1}"
(( left < right )) && return 0
(( left > right )) && return 1
done
return 1
}
getMemoryInfo() {
local host_total=""
local host_avail=""
local limit=""
local current=""
local available=""
host_total=$(free -b | awk '/^Mem:/ {print $2; exit}')
host_avail=$(free -b | awk '/^Mem:/ {print $7; exit}')
RAM_TOTAL="$host_total"
RAM_AVAIL="$host_avail"
if [ -r /sys/fs/cgroup/memory.max ] && [ -r /sys/fs/cgroup/memory.current ]; then
limit=$(< /sys/fs/cgroup/memory.max)
current=$(< /sys/fs/cgroup/memory.current)
elif [ -r /sys/fs/cgroup/memory/memory.limit_in_bytes ] && [ -r /sys/fs/cgroup/memory/memory.usage_in_bytes ]; then
limit=$(< /sys/fs/cgroup/memory/memory.limit_in_bytes)
current=$(< /sys/fs/cgroup/memory/memory.usage_in_bytes)
fi
if finiteMemoryLimit "$limit" && [[ "$current" =~ ^[0-9]+$ ]]; then
(( limit < RAM_TOTAL )) && RAM_TOTAL="$limit"
available=$(( limit - current ))
(( available < 0 )) && available=0
(( available < RAM_AVAIL )) && RAM_AVAIL="$available"
fi
return 0
}
normalizeRamSize() { normalizeRamSize() {
# Read memory # Read host and container memory limits.
RAM_AVAIL=$(free -b | grep -m 1 Mem: | awk '{print $7}') getMemoryInfo
RAM_TOTAL=$(free -b | grep -m 1 Mem: | awk '{print $2}')
RAM_SPARE=500000000 RAM_SPARE=500000000
RAM_MINIMUM=136314880 RAM_MINIMUM=136314880
@@ -232,6 +304,7 @@ PROCESS="${APP,,}"
PROCESS="${PROCESS// /-}" PROCESS="${PROCESS// /-}"
detectEngine detectEngine
detectRootless
echo " Starting $APP for $ENGINE v$(</etc/version)..." echo " Starting $APP for $ENGINE v$(</etc/version)..."
echo " For support visit $SUPPORT" echo " For support visit $SUPPORT"
@@ -273,7 +346,7 @@ SPACE_GB=$(formatBytes "$SPACE" "down")
AVAIL_MEM=$(formatBytes "$RAM_AVAIL" "down") AVAIL_MEM=$(formatBytes "$RAM_AVAIL" "down")
TOTAL_MEM=$(formatBytes "$RAM_TOTAL" "up") TOTAL_MEM=$(formatBytes "$RAM_TOTAL" "up")
echo " CPU: ${CPU} | RAM: ${AVAIL_MEM/ GB/}/$TOTAL_MEM | DISK: $SPACE_GB (${FS}) | KERNEL: ${SYS}..." echo " CPU: ${CPU} | RAM: ${AVAIL_MEM/ GB/}/$TOTAL_MEM | DISK: $SPACE_GB (${FS}) | KERNEL: ${SYS}"
echo echo
checkKvm checkKvm
+38 -14
View File
@@ -16,13 +16,15 @@ WEB_PID="/run/nginx.pid"
WSD_PID="$QEMU_DIR/websocketd.pid" WSD_PID="$QEMU_DIR/websocketd.pid"
prepareWebFiles() { prepareWebFiles() {
cp -r /var/www/* "$QEMU_DIR"
rm -f "$WSD_PID" "$WEB_PID" cp -r /var/www/* "$QEMU_DIR" || return 1
rm -f "$WSD_PID" "$WEB_PID" || return 1
return 0 return 0
} }
configureWebPorts() { configureWebPorts() {
sed -i "s/listen 5000 default_server;/listen $WEB_PORT default_server;/g" /etc/nginx/sites-enabled/web.conf sed -i "s/listen 5000 default_server;/listen $WEB_PORT default_server;/g" /etc/nginx/sites-enabled/web.conf
sed -i "s/proxy_pass http:\/\/127.0.0.1:8004\/;/proxy_pass http:\/\/127.0.0.1:$WSD_PORT\/;/g" /etc/nginx/sites-enabled/web.conf sed -i "s/proxy_pass http:\/\/127.0.0.1:8004\/;/proxy_pass http:\/\/127.0.0.1:$WSD_PORT\/;/g" /etc/nginx/sites-enabled/web.conf
@@ -32,7 +34,7 @@ configureWebPorts() {
configureIpv6Listen() { configureIpv6Listen() {
# shellcheck disable=SC2143 # shellcheck disable=SC2143
if [ -f /proc/net/if_inet6 ] && [[ "$(cat /proc/sys/net/ipv6/conf/all/disable_ipv6 2>/dev/null)" != "1" ]] && [ -n "$(ifconfig -a | grep inet6)" ]; then if [ -f /proc/net/if_inet6 ] && [[ "$(cat /proc/sys/net/ipv6/conf/all/disable_ipv6 2>/dev/null)" != "1" ]]; then
sed -i "s/listen $WEB_PORT default_server;/listen [::]:$WEB_PORT default_server ipv6only=off;/g" /etc/nginx/sites-enabled/web.conf sed -i "s/listen $WEB_PORT default_server;/listen [::]:$WEB_PORT default_server ipv6only=off;/g" /etc/nginx/sites-enabled/web.conf
fi fi
@@ -44,8 +46,8 @@ 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
configureWebPorts configureWebPorts || return 1
configureIpv6Listen configureIpv6Listen || return 1
return 0 return 0
} }
@@ -53,24 +55,46 @@ configureWebServer() {
startWebServer() { startWebServer() {
# Start webserver # Start webserver
nginx -e stderr nginx -e stderr || return 1
return 0
} }
startWebsocketServer() { startWebsocketServer() {
local log="/var/log/websocketd.log"
rm -f "$log"
# Start websocket server # Start websocket server
websocketd --address 127.0.0.1 --port="$WSD_PORT" /run/socket.sh >/var/log/websocketd.log & websocketd --address 127.0.0.1 --port="$WSD_PORT" /run/socket.sh > "$log" 2>&1 &
echo "$!" > "$WSD_PID" local pid=$!
if ! echo "$pid" > "$WSD_PID"; then
kill "$pid" 2>/dev/null || :
return 1
fi
sleep 0.1
if ! isAlive "$pid"; then
rm -f "$WSD_PID"
[ -s "$log" ] && cat "$log" >&2
error "Failed to start websocket server!"
return 1
fi
return 0
} }
prepareWebFiles prepareWebFiles || return 1
html "Starting $APP for $ENGINE..." html "Starting $APP for $ENGINE..."
if ! disabled "${WEB:-}"; then disabled "${WEB:-}" && return 0
configureWebServer
startWebServer configureWebServer || return 1
startWebsocketServer
fi startWebServer || return 1
startWebsocketServer || return 1
return 0 return 0
+1 -1
View File
@@ -9,7 +9,7 @@ refresh() {
[ ! -f "$path" ] && return 0 [ ! -f "$path" ] && return 0
[ ! -s "$path" ] && return 0 [ ! -s "$path" ] && return 0
msg=$(< "$path") msg=$(< "$path") || return 0
msg="${msg%$'\n'}" msg="${msg%$'\n'}"
[ -z "$msg" ] && return 0 [ -z "$msg" ] && return 0
+141 -14
View File
@@ -8,6 +8,7 @@ error () { printf "%b%s%b" "\E[1;31m " "ERROR: ${1:-}" "\E[0m\n" >&2; }
warn () { printf "%b%s%b" "\E[1;31m " "Warning: ${1:-}" "\E[0m\n" >&2; } warn () { printf "%b%s%b" "\E[1;31m " "Warning: ${1:-}" "\E[0m\n" >&2; }
strip() { strip() {
local value="${1:-}" local value="${1:-}"
# Remove surrounding whitespace # Remove surrounding whitespace
@@ -28,24 +29,38 @@ strip() {
} }
enabled() { enabled() {
case "$(strip "${1:-}")" in
Y|y|YES|Yes|yes|TRUE|True|true|1|ON|On|on) return 0 ;; local value
value=$(strip "${1:-}")
case "${value,,}" in
y|yes|true|1|on|enable|enabled) return 0 ;;
*) return 1 ;; *) return 1 ;;
esac esac
} }
disabled() { disabled() {
case "$(strip "${1:-}")" in
N|n|NO|No|no|FALSE|False|false|0|OFF|Off|off) return 0 ;; local value
value=$(strip "${1:-}")
case "${value,,}" in
n|no|none|false|0|off|disable|disabled) return 0 ;;
*) return 1 ;; *) return 1 ;;
esac esac
} }
formatBytes() { formatBytes() {
local result local result
result=$(numfmt --to=iec --suffix=B "$1" | sed -r 's/([A-Z])/ \1/' | sed 's/ B/ bytes/g;')
if ! result=$(numfmt --to=iec --suffix=B "$1" | sed -r 's/([A-Z])/ \1/' | sed 's/ B/ bytes/g;'); then
return 1
fi
local unit="${result//[0-9. ]}" local unit="${result//[0-9. ]}"
result="${result//[a-zA-Z ]/}" result="${result//[a-zA-Z ]/}"
if [[ "${2:-}" == "up" ]]; then if [[ "${2:-}" == "up" ]]; then
if [[ "$result" == *"."* ]]; then if [[ "$result" == *"."* ]]; then
result="${result%%.*}" result="${result%%.*}"
@@ -56,11 +71,13 @@ formatBytes() {
result="${result%%.*}" result="${result%%.*}"
fi fi
fi fi
echo "$result $unit" echo "$result $unit"
return 0 return 0
} }
isAlive() { isAlive() {
local pid="$1" local pid="$1"
[ -z "$pid" ] && return 1 [ -z "$pid" ] && return 1
@@ -72,6 +89,7 @@ isAlive() {
} }
waitPid() { waitPid() {
local i=0 local i=0
local pid="$1" local pid="$1"
local timeout="${2:-10}" local timeout="${2:-10}"
@@ -86,6 +104,7 @@ waitPid() {
} }
waitPidFile() { waitPidFile() {
local i=0 local i=0
local pid="" local pid=""
local file="$1" local file="$1"
@@ -106,6 +125,7 @@ waitPidFile() {
} }
pKill() { pKill() {
local pid="$1" local pid="$1"
local timeout="${2:-10}" local timeout="${2:-10}"
@@ -119,6 +139,7 @@ pKill() {
} }
fWait() { fWait() {
local i=0 local i=0
local name="$1" local name="$1"
local timeout="${2:-10}" local timeout="${2:-10}"
@@ -138,6 +159,7 @@ fWait() {
} }
fKill() { fKill() {
local name="$1" local name="$1"
local timeout="${2:-10}" local timeout="${2:-10}"
@@ -150,6 +172,7 @@ fKill() {
} }
sKill() { sKill() {
local pid="" local pid=""
local file="$1" local file="$1"
@@ -165,6 +188,7 @@ sKill() {
} }
mKill() { mKill() {
local timeout=10 local timeout=10
local files=("$@") local files=("$@")
@@ -182,14 +206,15 @@ mKill() {
} }
setOwner() { setOwner() {
local file="$1" local file="$1"
local dir uid gid local dir uid gid
[ ! -f "$file" ] && return 1 [ ! -f "$file" ] && return 1
dir=$(dirname -- "$file") dir=$(dirname -- "$file")
uid=$(stat -c '%u' "$dir") uid=$(stat -c '%u' "$dir") || return 1
gid=$(stat -c '%g' "$dir") gid=$(stat -c '%g' "$dir") || return 1
! chown "$uid:$gid" "$file" && return 1 ! chown "$uid:$gid" "$file" && return 1
@@ -197,6 +222,7 @@ setOwner() {
} }
makeDir() { makeDir() {
local path="$1" local path="$1"
local dir uid gid local dir uid gid
@@ -204,25 +230,123 @@ makeDir() {
! mkdir -p "$path" && return 1 ! mkdir -p "$path" && return 1
dir=$(dirname -- "$path") dir=$(dirname -- "$path")
uid=$(stat -c '%u' "$dir")
gid=$(stat -c '%g' "$dir")
! chown "$uid:$gid" "$path" && return 1 if ! uid=$(stat -c '%u' "$dir") || ! gid=$(stat -c '%g' "$dir"); then
warn "failed to determine the owner for \"$path\"."
return 0
fi
if ! chown "$uid:$gid" "$path"; then
warn "failed to set the owner for \"$path\"."
return 0
fi
return 0 return 0
} }
stateFile() {
local name="$1"
local prefix="${2:-$PROCESS}"
[[ "$name" == */* ]] && printf '%s\n' "$name" && return 0
printf '%s/%s.%s\n' "$STORAGE" "$prefix" "$name"
return 0
}
writeFile() {
local txt="$1"
local path="$2"
if ! printf '%s\n' "$txt" > "$path"; then
error "Failed to write file \"$path\" !"
return 1
fi
if ! setOwner "$path"; then
warn "failed to set the owner for \"$path\"."
fi
return 0
}
readFile() {
local path="$1"
local value
[ -s "$path" ] || return 0
value=$(<"$path") || return 1
value="${value//[![:print:]]/}"
printf '%s\n' "$value"
return 0
}
writeState() {
local name="$1"
local value="$2"
local prefix="${3:-$PROCESS}"
local path
[ -z "$value" ] && return 0
path=$(stateFile "$name" "$prefix") || return 1
writeFile "$value" "$path"
return $?
}
readState() {
local name="$1"
local prefix="${2:-$PROCESS}"
local path
path=$(stateFile "$name" "$prefix") || return 1
readFile "$path"
return $?
}
restoreState() {
local var="$1"
local name="$2"
local force="${3:-N}"
local prefix="${4:-$PROCESS}"
local value
if ! enabled "$force"; then
[ -z "${!var:-}" ] || return 0
fi
value=$(readState "$name" "$prefix") || return 1
[ -n "$value" ] || return 0
printf -v "$var" '%s' "$value" || return 1
return 0
}
escape () { escape () {
local s local s
s=${1//&/\&amp;} s=${1//&/\&amp;}
s=${s//</\&lt;} s=${s//</\&lt;}
s=${s//>/\&gt;} s=${s//>/\&gt;}
s=${s//'"'/\&quot;} s=${s//'"'/\&quot;}
printf -- %s "$s" printf -- %s "$s"
return 0 return 0
} }
html() { html() {
local title local title
local body local body
local script local script
@@ -247,13 +371,14 @@ html() {
HTML="${HTML/\[4\]/$footer}" HTML="${HTML/\[4\]/$footer}"
HTML="${HTML/\[5\]/$FOOTER2}" HTML="${HTML/\[5\]/$FOOTER2}"
echo "$HTML" > "$PAGE" echo "$HTML" > "$PAGE" || return 1
echo "$body" > "$INFO" echo "$body" > "$INFO" || return 1
return 0 return 0
} }
cpu() { cpu() {
local ret local ret
local cpu="" local cpu=""
@@ -307,6 +432,7 @@ hasDisk() {
} }
getCountry() { getCountry() {
local url=$1 local url=$1
local query=$2 local query=$2
local rc json result local rc json result
@@ -345,6 +471,7 @@ setCountry() {
} }
addPackage() { addPackage() {
local pkg=$1 local pkg=$1
local desc=$2 local desc=$2
@@ -361,8 +488,8 @@ addPackage() {
sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list.d/debian.sources sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list.d/debian.sources
fi fi
DEBIAN_FRONTEND=noninteractive apt-get -qq update DEBIAN_FRONTEND=noninteractive apt-get -qq update || return 1
DEBIAN_FRONTEND=noninteractive apt-get -qq --no-install-recommends -y install "$pkg" > /dev/null DEBIAN_FRONTEND=noninteractive apt-get -qq --no-install-recommends -y install "$pkg" > /dev/null || return 1
return 0 return 0
} }