mirror of
https://github.com/vdsm/virtual-dsm.git
synced 2026-08-29 12:17:07 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6bcacf5e59 | ||
|
|
3bea87f087 | ||
|
|
f01e1564f7 | ||
|
|
cf8cdbf0c1 | ||
|
|
0813494ee0 | ||
|
|
0d71e30f50 | ||
|
|
c203deab9a | ||
|
|
9cf31df266 | ||
|
|
0cd08f2ee1 | ||
|
|
a32d215e90 | ||
|
|
c1f9a690f9 | ||
|
|
5de504deb3 | ||
|
|
250eb18238 | ||
|
|
ca80da59bd | ||
|
|
9cd86dfe82 | ||
|
|
5bb93651f7 | ||
|
|
0c268308a4 | ||
|
|
7b741d3e4b | ||
|
|
f1d29212ae | ||
|
|
fdee9f89a3 | ||
|
|
210a360797 | ||
|
|
991bb2627a | ||
|
|
764a186836 | ||
|
|
726786a98a | ||
|
|
2caf97d407 | ||
|
|
0e67286599 | ||
|
|
2b9fc80275 | ||
|
|
0d88774bd3 | ||
|
|
fa23052ed4 | ||
|
|
cc3267f258 | ||
|
|
fd002564a6 | ||
|
|
2703a97cfa | ||
|
|
29e2d034bf | ||
|
|
cbe3644335 |
+1
-1
@@ -83,6 +83,6 @@ ENV RAM_SIZE="2G"
|
||||
ENV CPU_CORES="2"
|
||||
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"]
|
||||
|
||||
@@ -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. |
|
||||
@@ -22,7 +22,7 @@ Virtual DSM in a Docker container.
|
||||
- Near-native performance with KVM acceleration
|
||||
- Customizable CPU, memory, and storage allocation
|
||||
- Supports multiple disks and physical disk passthrough
|
||||
- Supports bridge, user-mode, macvlan, and macvtap networking
|
||||
- Supports NAT, user-mode, macvlan, and macvtap networking
|
||||
|
||||
## Usage 🐳
|
||||
|
||||
@@ -66,12 +66,13 @@ kubectl apply -f https://raw.githubusercontent.com/vdsm/virtual-dsm/refs/heads/m
|
||||
|
||||
## 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.
|
||||
- Docker or Podman on a Linux host with KVM support.
|
||||
- Docker Desktop or Podman (Desktop) on Windows 11 with nested virtualization enabled.
|
||||
- At least 2 GB of available RAM.
|
||||
- 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.
|
||||
> Docker Desktop on Linux, macOS, and Windows 10 does not currently provide KVM access to containers and is therefore not supported.
|
||||
|
||||
## FAQ 💬
|
||||
|
||||
@@ -147,34 +148,6 @@ kubectl apply -f https://raw.githubusercontent.com/vdsm/virtual-dsm/refs/heads/m
|
||||
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?
|
||||
|
||||
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.
|
||||
|
||||
### 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?
|
||||
|
||||
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
@@ -27,11 +27,11 @@ SYSTEM="$STORAGE/$BASE.system.img"
|
||||
[ ! -s "$SYSTEM" ] && error "Virtual DSM system-image does not exist ($SYSTEM)" && exit 82
|
||||
|
||||
if ! setOwner "$BOOT"; then
|
||||
error "Failed to set the owner for \"$BOOT\" !"
|
||||
warn "failed to set the owner for \"$BOOT\" !"
|
||||
fi
|
||||
|
||||
if ! setOwner "$SYSTEM"; then
|
||||
error "Failed to set the owner for \"$SYSTEM\" !"
|
||||
warn "failed to set the owner for \"$SYSTEM\" !"
|
||||
fi
|
||||
|
||||
fmt2ext() {
|
||||
@@ -179,6 +179,22 @@ normalizeSize() {
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
baseDir() {
|
||||
|
||||
local path="${1%/}"
|
||||
|
||||
[[ -z "$path" || "$path" == "/" ]] && {
|
||||
echo "/"
|
||||
return 0
|
||||
}
|
||||
|
||||
path="${path#/}"
|
||||
path="${path%%/*}"
|
||||
|
||||
echo "/$path"
|
||||
return 0
|
||||
}
|
||||
createDisk() {
|
||||
|
||||
local DISK_FILE="$1"
|
||||
@@ -186,7 +202,7 @@ createDisk() {
|
||||
local DISK_DESC="$3"
|
||||
local DISK_FMT="$4"
|
||||
local FS="$5"
|
||||
local DATA_SIZE DIR SPACE GB FA
|
||||
local DATA_SIZE DIR BASE_DIR SPACE GB FA
|
||||
|
||||
rm -f "$DISK_FILE"
|
||||
|
||||
@@ -196,11 +212,16 @@ createDisk() {
|
||||
|
||||
# Check free diskspace
|
||||
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
|
||||
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
|
||||
fi
|
||||
|
||||
@@ -255,7 +276,7 @@ resizeDisk() {
|
||||
local DISK_DESC="$3"
|
||||
local DISK_FMT="$4"
|
||||
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
|
||||
DATA_SIZE=$(numfmt --from=iec "$DISK_SPACE")
|
||||
@@ -266,11 +287,16 @@ resizeDisk() {
|
||||
|
||||
# Check free diskspace
|
||||
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
|
||||
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
|
||||
fi
|
||||
|
||||
@@ -317,8 +343,9 @@ convertDisk() {
|
||||
local TMP_FILE="$DISK_BASE.tmp"
|
||||
rm -f "$TMP_FILE"
|
||||
|
||||
local DIR FA
|
||||
local DIR BASE_DIR FA
|
||||
DIR=$(dirname "$TMP_FILE")
|
||||
BASE_DIR=$(baseDir "$DIR")
|
||||
|
||||
if ! disabled "$ALLOCATE"; then
|
||||
|
||||
@@ -326,11 +353,15 @@ convertDisk() {
|
||||
|
||||
# Check free diskspace
|
||||
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
|
||||
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
|
||||
fi
|
||||
|
||||
@@ -353,13 +384,18 @@ convertDisk() {
|
||||
# shellcheck disable=SC2086
|
||||
if ! qemu-img convert -f "$SOURCE_FMT" $CONV_FLAGS -o "$DISK_PARAM" -O "$DST_FMT" -- "$SOURCE_FILE" "$TMP_FILE"; then
|
||||
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
|
||||
|
||||
if [[ "$DST_FMT" == "raw" ]]; then
|
||||
if ! disabled "$ALLOCATE"; then
|
||||
|
||||
# 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 -x "$CUR_SIZE" "$TMP_FILE"; then
|
||||
error "Failed to allocate $CUR_SIZE bytes for $DISK_DESC image $TMP_FILE"
|
||||
@@ -368,15 +404,13 @@ convertDisk() {
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! rm -f "$SOURCE_FILE"; then
|
||||
rm -f "$TMP_FILE"
|
||||
error "Failed to remove old $DISK_DESC image $SOURCE_FILE."
|
||||
if ! mv "$TMP_FILE" "$DST_FILE"; then
|
||||
error "Failed to move converted $DISK_DESC image to $DST_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."
|
||||
if ! rm -f "$SOURCE_FILE"; then
|
||||
error "Failed to remove old $DISK_DESC image $SOURCE_FILE."
|
||||
exit 79
|
||||
fi
|
||||
|
||||
@@ -399,21 +433,22 @@ checkFS () {
|
||||
local FS="$1"
|
||||
local DISK_FILE="$2"
|
||||
local DISK_DESC="$3"
|
||||
local DIR FA
|
||||
local DIR BASE_DIR FA
|
||||
|
||||
DIR=$(dirname "$DISK_FILE")
|
||||
BASE_DIR=$(baseDir "$DIR")
|
||||
[ ! -d "$DIR" ] && return 0
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
if isCow "$FS"; then
|
||||
@@ -486,7 +521,7 @@ finishDisks () {
|
||||
|
||||
case "${DISK_TYPE,,}" in
|
||||
"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
|
||||
|
||||
if ! enabled "$DISK_DISABLE"; then
|
||||
@@ -577,8 +612,9 @@ addDisk () {
|
||||
|
||||
if (( LEFT > 0 )); then
|
||||
|
||||
local GB
|
||||
local GB BASE_DIR
|
||||
GB=$(formatBytes "$FREE")
|
||||
BASE_DIR=$(baseDir "$DIR")
|
||||
LEFT=$(formatBytes "$LEFT")
|
||||
CUR_SIZE=$(formatBytes "$CUR_SIZE")
|
||||
msg="The virtual size of the ${DISK_DESC,,} is $CUR_SIZE"
|
||||
@@ -588,8 +624,8 @@ addDisk () {
|
||||
msg+=" (of which $USED is used)"
|
||||
fi
|
||||
|
||||
info "$msg, but there is only $GB of free space remaining in $DIR now."
|
||||
info "Please consider making at least $LEFT more space available in $DIR for future expansions."
|
||||
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 $BASE_DIR for future expansions."
|
||||
|
||||
fi
|
||||
|
||||
@@ -597,7 +633,7 @@ addDisk () {
|
||||
|
||||
if [ -f "$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
|
||||
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ trap - ERR
|
||||
|
||||
cmd=(qemu-system-x86_64)
|
||||
version=$("${cmd[@]}" --version | awk 'NR==1 { print $4 }')
|
||||
info "Booting $APP using QEMU v$version..."
|
||||
info "Booting $APP using QEMU v$version..." && echo
|
||||
|
||||
if ! enabled "$SHUTDOWN"; then
|
||||
exec "${cmd[@]}" ${ARGS:+ $ARGS}
|
||||
|
||||
+45
-20
@@ -17,7 +17,7 @@ DIR=$(find / -maxdepth 1 -type d -iname "$FN" -print -quit)
|
||||
[ ! -d "$DIR" ] && DIR=$(find "$STORAGE" -maxdepth 1 -type d -iname "$FN" -print -quit)
|
||||
|
||||
if [ -d "$DIR" ]; then
|
||||
BASE="DSM_VirtualDSM" && URL="file://$DIR"
|
||||
BASE="DSM_VirtualDSM" && URL="file://$DIR"
|
||||
if [[ ! -s "$STORAGE/$BASE.boot.img" || ! -s "$STORAGE/$BASE.system.img" ]]; then
|
||||
error "The bind $DIR maps to a file that does not exist!" && exit 65
|
||||
fi
|
||||
@@ -25,7 +25,7 @@ fi
|
||||
|
||||
FILE=$(find / -maxdepth 1 -type f -iname "$FN" -print -quit)
|
||||
[ ! -s "$FILE" ] && FILE=$(find "$STORAGE" -maxdepth 1 -type f -iname "$FN" -print -quit)
|
||||
[ -s "$FILE" ] && BASE="DSM_VirtualDSM" && URL="file://$FILE"
|
||||
[ -s "$FILE" ] && BASE="DSM_VirtualDSM" && URL="file://$FILE"
|
||||
|
||||
URL=$(strip "$URL")
|
||||
|
||||
@@ -36,7 +36,7 @@ if [ -n "$URL" ] && [ ! -s "$FILE" ] && [ ! -d "$DIR" ]; then
|
||||
printf -v BASE '%b' "${BASE//%/\\x}"
|
||||
BASE="${BASE//[!A-Za-z0-9._-]/_}"
|
||||
fi
|
||||
if [[ "${URL,,}" != "http"* && "${URL,,}" != "file:"* ]] ; then
|
||||
if [[ "${URL,,}" != "http"* && "${URL,,}" != "file:"* ]]; then
|
||||
[ ! -s "$STORAGE/$BASE.pat" ] && error "Invalid URL: $URL" && exit 65
|
||||
URL="file://$STORAGE/$BASE.pat"
|
||||
fi
|
||||
@@ -159,20 +159,44 @@ if [[ "$URL" == "file://"* ]]; then
|
||||
else
|
||||
|
||||
SIZE=0
|
||||
REASON=""
|
||||
LOG=$(mktemp)
|
||||
|
||||
[[ "${URL,,}" == *"_72806.pat" ]] && SIZE=361010261
|
||||
[[ "${URL,,}" == *"_69057.pat" ]] && SIZE=363837333
|
||||
[[ "${URL,,}" == *"_42218.pat" ]] && SIZE=379637760
|
||||
|
||||
/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"
|
||||
|
||||
(( rc == 3 )) && error "$ERR , cannot write file (disk full?)" && exit 69
|
||||
(( rc == 4 )) && error "$ERR , network failure!" && exit 69
|
||||
(( rc == 8 )) && error "$ERR , server issued an error response!" && exit 69
|
||||
(( rc != 0 )) && error "$ERR , reason: $rc" && exit 69
|
||||
if (( rc != 0 )); then
|
||||
REASON=$(sed -n \
|
||||
-e 's/^wget: //p' \
|
||||
-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
|
||||
|
||||
@@ -228,7 +252,7 @@ if ! touch "$SYSTEM"; then
|
||||
error "Could not create file $SYSTEM for the system disk." && exit 98
|
||||
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
|
||||
{ chattr +C "$SYSTEM"; } || :
|
||||
@@ -249,14 +273,15 @@ fi
|
||||
|
||||
PART="$TMP/partition.fdisk"
|
||||
|
||||
{ echo "label: dos"
|
||||
echo "label-id: 0x6f9ee2e9"
|
||||
echo "device: $SYSTEM"
|
||||
echo "unit: sectors"
|
||||
echo "sector-size: 512"
|
||||
echo ""
|
||||
echo "${SYSTEM}1 : start= 2048, size= 16777216, type=83"
|
||||
echo "${SYSTEM}2 : start= 16779264, size= 4194304, type=82"
|
||||
{
|
||||
echo "label: dos"
|
||||
echo "label-id: 0x6f9ee2e9"
|
||||
echo "device: $SYSTEM"
|
||||
echo "unit: sectors"
|
||||
echo "sector-size: 512"
|
||||
echo ""
|
||||
echo "${SYSTEM}1 : start= 2048, size= 16777216, type=83"
|
||||
echo "${SYSTEM}2 : start= 16779264, size= 4194304, type=82"
|
||||
} > "$PART"
|
||||
|
||||
sfdisk -q "$SYSTEM" < "$PART"
|
||||
@@ -302,7 +327,7 @@ fakeroot -- bash -c "set -Eeu;\
|
||||
|
||||
rm -rf "$MOUNT"
|
||||
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
|
||||
rm -f "$PAT"
|
||||
@@ -311,11 +336,11 @@ else
|
||||
fi
|
||||
|
||||
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
|
||||
|
||||
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"
|
||||
|
||||
|
||||
+9
-8
@@ -4,11 +4,12 @@ set -Eeuo pipefail
|
||||
msg="Checking memory..."
|
||||
enabled "$DEBUG" && echo "$msg"
|
||||
|
||||
RAM_AVAIL=$(free -b | grep -m 1 Mem: | awk '{print $7}')
|
||||
AVAIL_MEM=$(formatBytes "$RAM_AVAIL")
|
||||
app() {
|
||||
echo "Virtual DSM"
|
||||
return 0
|
||||
}
|
||||
|
||||
checkConfiguredMemory() {
|
||||
|
||||
local wanted msg
|
||||
|
||||
if disabled "$RAM_CHECK" || [[ "${RAM_SIZE,,}" == "max" || "${RAM_SIZE,,}" == "half" ]]; then
|
||||
@@ -40,7 +41,6 @@ checkConfiguredMemory() {
|
||||
}
|
||||
|
||||
configureHalfMemory() {
|
||||
|
||||
local wanted
|
||||
|
||||
if [[ "${RAM_SIZE,,}" != "half" ]]; then
|
||||
@@ -50,7 +50,7 @@ configureHalfMemory() {
|
||||
if (( (RAM_AVAIL / 2) > RAM_SPARE )); then
|
||||
wanted=$(( (RAM_AVAIL / 2) / 1048577 ))
|
||||
RAM_SIZE="${wanted}M"
|
||||
info "Allocated $wanted MB of RAM for the virtual machine."
|
||||
info "Allocated $wanted MB of RAM for $(app)."
|
||||
else
|
||||
RAM_SIZE="max"
|
||||
fi
|
||||
@@ -59,7 +59,6 @@ configureHalfMemory() {
|
||||
}
|
||||
|
||||
configureMaxMemory() {
|
||||
|
||||
local wanted
|
||||
|
||||
if [[ "${RAM_SIZE,,}" != "max" ]]; then
|
||||
@@ -83,13 +82,12 @@ configureMaxMemory() {
|
||||
wanted=$(( wanted / 1048577 ))
|
||||
RAM_SIZE="${wanted}M"
|
||||
|
||||
info "Allocated $wanted MB of RAM for the virtual machine."
|
||||
info "Allocated $wanted MB of RAM for $(app)."
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
checkMinimumMemory() {
|
||||
|
||||
local wanted
|
||||
|
||||
wanted=$(numfmt --from=iec "$RAM_SIZE")
|
||||
@@ -103,6 +101,9 @@ checkMinimumMemory() {
|
||||
return 0
|
||||
}
|
||||
|
||||
getMemoryInfo
|
||||
AVAIL_MEM=$(formatBytes "$RAM_AVAIL")
|
||||
|
||||
checkConfiguredMemory
|
||||
configureHalfMemory
|
||||
configureMaxMemory
|
||||
|
||||
+390
-133
@@ -52,7 +52,7 @@ ADD_ERR="Please add the following setting to your container:"
|
||||
isNAT() {
|
||||
|
||||
case "${NETWORK,,}" in
|
||||
"tap" | "tun" | "tuntap" | "y" | "" )
|
||||
"nat" | "tap" | "tun" | "tuntap" | "y" | "" )
|
||||
return 0 ;;
|
||||
*)
|
||||
return 1 ;;
|
||||
@@ -126,6 +126,11 @@ maskToCIDR() {
|
||||
local mask="$1"
|
||||
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 '
|
||||
/^Netmask:/ {
|
||||
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'"
|
||||
return 1
|
||||
fi
|
||||
@@ -347,7 +352,7 @@ configureDNS() {
|
||||
|
||||
# Set DHCP range and host
|
||||
arguments+=" --dhcp-range=$ip,$ip"
|
||||
arguments+=" --dhcp-host=$mac,,$ip,$host,infinite"
|
||||
arguments+=" --dhcp-host=$mac,,$ip,$host,1h"
|
||||
|
||||
# Set DNS server and gateway
|
||||
arguments+=" --dhcp-option=option:netmask,$mask"
|
||||
@@ -365,9 +370,6 @@ configureDNS() {
|
||||
arguments+=" --interface=$fa"
|
||||
arguments+=" --bind-interfaces"
|
||||
|
||||
# Set pid file
|
||||
arguments+=" --pid-file=$DNSMASQ_PID"
|
||||
|
||||
# Workaround NET_RAW capability
|
||||
arguments+=" --no-ping"
|
||||
|
||||
@@ -382,6 +384,9 @@ configureDNS() {
|
||||
# Set local dns resolver to dnsmasq when needed
|
||||
[ -f /etc/resolv.dnsmasq ] && arguments+=" --resolv-file=/etc/resolv.dnsmasq"
|
||||
|
||||
# Set pid file
|
||||
arguments+=" --pid-file=$DNSMASQ_PID"
|
||||
|
||||
# Enable logging to file
|
||||
local log="/var/log/dnsmasq.log"
|
||||
rm -f "$log"
|
||||
@@ -413,14 +418,38 @@ configureDNS() {
|
||||
|
||||
getHostPorts() {
|
||||
|
||||
local list="${HOST_PORTS// /},"
|
||||
local ports=""
|
||||
local num=""
|
||||
local port=""
|
||||
local ports=""
|
||||
local mode="${1:-tcp}"
|
||||
local list="${HOST_PORTS// /},"
|
||||
|
||||
for port in ${list//,/ }; do
|
||||
port="${port%/tcp}"
|
||||
port="${port%/udp}"
|
||||
[ -n "$port" ] && ports+="$port,"
|
||||
|
||||
[ -z "$port" ] && continue
|
||||
|
||||
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
|
||||
|
||||
# Remove duplicates
|
||||
@@ -432,44 +461,55 @@ getHostPorts() {
|
||||
|
||||
getUserPorts() {
|
||||
|
||||
local ssh="22"
|
||||
local dsm="5000,5001"
|
||||
|
||||
local list="$ssh,$dsm,"
|
||||
list+="${USER_PORTS// /},"
|
||||
|
||||
local exclude
|
||||
exclude=$(getHostPorts)
|
||||
local defaults="22/tcp,5000/tcp,5001/tcp"
|
||||
local list="$defaults,${USER_PORTS// /},"
|
||||
|
||||
local num=""
|
||||
local ports=""
|
||||
local proto=""
|
||||
local userport=""
|
||||
local hostport=""
|
||||
|
||||
local exclude=""
|
||||
exclude=$(getHostPorts "all")
|
||||
|
||||
for userport in ${list//,/ }; do
|
||||
|
||||
local num="${userport///tcp}"
|
||||
num="${num///udp}"
|
||||
proto="tcp"
|
||||
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
|
||||
|
||||
local port="${hostport///tcp}"
|
||||
port="${port///udp}"
|
||||
if [[ "$num/$proto" == "$hostport" ]]; then
|
||||
|
||||
if [[ "$num" == "$port" ]]; then
|
||||
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
|
||||
|
||||
break
|
||||
fi
|
||||
|
||||
done
|
||||
|
||||
[ -n "$num" ] && ports+="$userport,"
|
||||
[ -n "$num" ] && ports+="$num/$proto,"
|
||||
|
||||
done
|
||||
|
||||
# Remove duplicates
|
||||
echo "${ports//,,/,}," | awk 'BEGIN{RS=ORS=","} !seen[$0]++' | sed 's/,*$//g'
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -490,10 +530,6 @@ getSlirp() {
|
||||
if [[ "$port" == *"/udp" ]]; then
|
||||
proto="udp"
|
||||
num="${port%/udp}"
|
||||
elif [[ "$port" != *"/tcp" ]]; then
|
||||
args+="hostfwd=$proto::$num-$ip:$num,"
|
||||
proto="udp"
|
||||
num="${port%/udp}"
|
||||
fi
|
||||
|
||||
args+="hostfwd=$proto::$num-$ip:$num,"
|
||||
@@ -531,7 +567,6 @@ getPasst() {
|
||||
else
|
||||
|
||||
tcp+="$port,"
|
||||
udp+="$port,"
|
||||
|
||||
fi
|
||||
|
||||
@@ -655,7 +690,10 @@ configureSlirp() {
|
||||
[ -n "$forward" ] && NET_OPTS+=",$forward"
|
||||
|
||||
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
|
||||
if [ ! -f /etc/resolv.dnsmasq ] && ! cp /etc/resolv.conf /etc/resolv.dnsmasq; then
|
||||
error "Failed to backup /etc/resolv.conf."
|
||||
@@ -775,14 +813,16 @@ configurePasst() {
|
||||
createBridge() {
|
||||
|
||||
local gateway="$1"
|
||||
local rc
|
||||
local rc msg=""
|
||||
|
||||
# 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
|
||||
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
|
||||
|
||||
if [[ "$GUEST_MTU" != "0" ]]; then
|
||||
@@ -807,11 +847,16 @@ createBridge() {
|
||||
createTap() {
|
||||
|
||||
local tuntap="$1"
|
||||
local rc msg=""
|
||||
|
||||
# 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
|
||||
warn "$tuntap" && return 1
|
||||
[ -n "$msg" ] && echo "$msg" >&2
|
||||
warn "$tuntap"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ "$GUEST_MTU" != "0" ]]; then
|
||||
@@ -837,64 +882,155 @@ createTap() {
|
||||
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() {
|
||||
|
||||
local ip="$1"
|
||||
local subnet="$2"
|
||||
local exclude="$3"
|
||||
local exclude=""
|
||||
local port=""
|
||||
local dnat_chain="QEMU_DNAT"
|
||||
local rule_tag="remove"
|
||||
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"
|
||||
|
||||
clearTables
|
||||
|
||||
if [ -n "$exclude" ]; then
|
||||
if [[ "$exclude" != *","* ]]; then
|
||||
exclude=" ! --dport $exclude"
|
||||
else
|
||||
exclude=" -m multiport ! --dports $exclude"
|
||||
fi
|
||||
if ! clearTables; then
|
||||
enabled "$ROOTLESS" && ! enabled "$DEBUG" && return 1
|
||||
warn "failed to select a working IP tables backend!"
|
||||
return 1
|
||||
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 \
|
||||
-o "$DEV" \
|
||||
! -o "$BRIDGE" \
|
||||
-s "$subnet" \
|
||||
! -d "$subnet" \
|
||||
-m comment --comment "$rule_tag" \
|
||||
-j MASQUERADE > /dev/null 2>&1; then
|
||||
enabled "$ROOTLESS" && ! enabled "$DEBUG" && return 1
|
||||
|
||||
if ! iptables -t nat -A POSTROUTING \
|
||||
-o "$DEV" \
|
||||
! -o "$BRIDGE" \
|
||||
-s "$subnet" \
|
||||
! -d "$subnet" \
|
||||
-m comment --comment "$rule_tag" \
|
||||
-j MASQUERADE; then
|
||||
warn "$tables" && return 1
|
||||
warn "$tables"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC2086
|
||||
if ! iptables -t nat -A PREROUTING \
|
||||
-i "$DEV" \
|
||||
-d "$UPLINK" \
|
||||
-p tcp${exclude} \
|
||||
-m comment --comment "$rule_tag" \
|
||||
-j DNAT --to "$ip"; then
|
||||
warn "$tables_err" && return 1
|
||||
# Use a dedicated chain so protected TCP ports do not depend on multiport support.
|
||||
if ! iptables -t nat -N "$dnat_chain"; then
|
||||
warn "$tables_err"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! iptables -t nat -A PREROUTING \
|
||||
-i "$DEV" \
|
||||
-d "$UPLINK" \
|
||||
-p udp \
|
||||
# Keep container-owned TCP ports handled by the container.
|
||||
for port in ${exclude//,/ }; do
|
||||
|
||||
[ -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" \
|
||||
-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
|
||||
|
||||
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 \
|
||||
-s "$subnet" \
|
||||
-p udp \
|
||||
@@ -918,23 +1054,26 @@ configureTables() {
|
||||
-m comment --comment "$rule_tag" \
|
||||
-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 \
|
||||
-i "$BRIDGE" \
|
||||
-o "$DEV" \
|
||||
! -o "$BRIDGE" \
|
||||
-s "$subnet" \
|
||||
-m comment --comment "$rule_tag" \
|
||||
-j ACCEPT; then
|
||||
warn "$tables_err" && return 1
|
||||
warn "$tables_err"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Allow return traffic
|
||||
# Allow forwarding from external interfaces to the VM.
|
||||
if ! iptables -A FORWARD \
|
||||
-i "$DEV" \
|
||||
! -i "$BRIDGE" \
|
||||
-o "$BRIDGE" \
|
||||
-m conntrack --ctstate RELATED,ESTABLISHED \
|
||||
-d "$ip" \
|
||||
-m comment --comment "$rule_tag" \
|
||||
-j ACCEPT; then
|
||||
warn "$tables_err" && return 1
|
||||
warn "$tables_err"
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
@@ -943,15 +1082,21 @@ configureTables() {
|
||||
configureNAT() {
|
||||
|
||||
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..."
|
||||
|
||||
# Create the necessary file structure for /dev/net/tun
|
||||
if [ ! -c /dev/net/tun ]; then
|
||||
[ ! -d /dev/net ] && mkdir -m 755 /dev/net
|
||||
if mknod /dev/net/tun c 10 200; then
|
||||
[ ! -d /dev/net ] && mkdir -m 755 /dev/net > /dev/null 2>&1 || :
|
||||
|
||||
{ msg=$(mknod /dev/net/tun c 10 200 2>&1); rc=$?; } || :
|
||||
|
||||
if (( rc == 0 )); then
|
||||
chmod 666 /dev/net/tun
|
||||
elif ! enabled "$ROOTLESS" || enabled "$DEBUG"; then
|
||||
[ -n "$msg" ] && echo "$msg" >&2
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -961,7 +1106,10 @@ configureNAT() {
|
||||
fi
|
||||
|
||||
# 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=$?; } || :
|
||||
if (( rc != 0 )) || [[ $(< /proc/sys/net/ipv4/ip_forward) -eq 0 ]]; then
|
||||
enabled "$ROOTLESS" && ! enabled "$DEBUG" && return 1
|
||||
@@ -970,8 +1118,6 @@ configureNAT() {
|
||||
fi
|
||||
fi
|
||||
|
||||
local ip exclude subnet
|
||||
|
||||
if [ -n "$IP" ]; then
|
||||
ip=$(guestIP "$IP" 2)
|
||||
else
|
||||
@@ -994,8 +1140,7 @@ configureNAT() {
|
||||
GUEST_MTU=$(minMTU "$GUEST_MTU" "$(getMTU "$BRIDGE")" "$(getMTU "$TAP")")
|
||||
fi
|
||||
|
||||
exclude=$(getHostPorts)
|
||||
configureTables "$ip" "$subnet" "$exclude" || return 1
|
||||
configureTables "$ip" "$subnet" || return 1
|
||||
|
||||
NET_OPTS="-netdev tap,id=hostnet0,ifname=$TAP"
|
||||
|
||||
@@ -1013,65 +1158,150 @@ configureNAT() {
|
||||
}
|
||||
|
||||
# ######################################
|
||||
# Cleanup
|
||||
# IP Tables
|
||||
# ######################################
|
||||
|
||||
clearTables() {
|
||||
setTables() {
|
||||
|
||||
local table="" line rules
|
||||
local rule_tag="remove"
|
||||
local re="--comment[[:space:]]+\"?$rule_tag\"?([[:space:]]|\$)"
|
||||
local mode="$1"
|
||||
local path=""
|
||||
|
||||
# Choose between iptables or nftables
|
||||
if command -v iptables-nft >/dev/null 2>&1 && iptables-nft -V >/dev/null 2>&1; then
|
||||
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
|
||||
path=$(command -v "iptables-$mode" 2>/dev/null || true)
|
||||
[ -z "$path" ] && return 1
|
||||
|
||||
# Store the current iptables ruleset
|
||||
! rules=$(iptables-save 2> /dev/null) && return 0
|
||||
[ -z "$rules" ] && return 0
|
||||
update-alternatives --set iptables "$path" > /dev/null 2>&1
|
||||
}
|
||||
|
||||
# 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* ]]; then
|
||||
if [[ "$line" =~ $re ]]; then
|
||||
read -ra args <<< "${line/-A /-D }"
|
||||
iptables -t "$table" "${args[@]}" &> /dev/null || :
|
||||
fi
|
||||
fi
|
||||
done <<< "$rules"
|
||||
testTables() {
|
||||
|
||||
local table=""
|
||||
|
||||
# Test every table required by the networking rules.
|
||||
for table in nat filter; do
|
||||
iptables -t "$table" -S > /dev/null 2>&1 || return 1
|
||||
iptables-save -t "$table" > /dev/null 2>&1 || return 1
|
||||
done
|
||||
|
||||
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" )
|
||||
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 delete "$TAP" &> /dev/null || :
|
||||
|
||||
ip link set "$BRIDGE" down &> /dev/null || :
|
||||
ip link delete "$BRIDGE" &> /dev/null || :
|
||||
|
||||
clearTables
|
||||
clearTables || :
|
||||
return 0
|
||||
}
|
||||
|
||||
closeWeb() {
|
||||
|
||||
local pids=( "$WEB_PID" "$WSD_PID" )
|
||||
local pids=( "${WEB_PID:-}" "${WSD_PID:-}" )
|
||||
mKill "${pids[@]}"
|
||||
|
||||
return 0
|
||||
@@ -1085,17 +1315,14 @@ closeNetwork() {
|
||||
|
||||
disabled "$NETWORK" && return 0
|
||||
|
||||
exec 30>&- 2>/dev/null || true
|
||||
exec 40>&- 2>/dev/null || true
|
||||
|
||||
closeBridge
|
||||
closeInterfaces
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
cleanUp() {
|
||||
|
||||
closeBridge
|
||||
closeInterfaces
|
||||
|
||||
# Clean up old files
|
||||
rm -f "$PASST_PID" "$PASST_SOCKET"
|
||||
@@ -1146,6 +1373,11 @@ validateMask() {
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1163,6 +1395,15 @@ validateHost() {
|
||||
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() {
|
||||
|
||||
# DHCP/macvtap mode can work without a detectable container IPv4 address,
|
||||
@@ -1255,15 +1496,26 @@ configureMAC() {
|
||||
container=$(containerID)
|
||||
|
||||
if [ -z "$MAC" ]; then
|
||||
|
||||
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
|
||||
|
||||
# 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/')
|
||||
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
|
||||
|
||||
@@ -1280,8 +1532,7 @@ configureMAC() {
|
||||
exit 28
|
||||
fi
|
||||
|
||||
# Keep the guest-facing gateway MAC stable across runs, otherwise Windows guests
|
||||
# may detect a new network every boot.
|
||||
# Keep the guest-facing gateway MAC stable across runs.
|
||||
GATEWAY_MAC=$(gatewayMAC "$MAC")
|
||||
|
||||
return 0
|
||||
@@ -1354,7 +1605,7 @@ showHostInfo() {
|
||||
[[ "$nameservers" == "127.0.0.1"* ]] && nameservers=""
|
||||
|
||||
echo
|
||||
|
||||
|
||||
if (( ${#nameservers} <= 40 )); then
|
||||
[ -n "$nameservers" ] && line+=" | DNS: $nameservers"
|
||||
echo "$line"
|
||||
@@ -1407,6 +1658,7 @@ prepareNetwork() {
|
||||
|
||||
validateMask
|
||||
validateHost
|
||||
validateHostPorts
|
||||
|
||||
detectAddresses
|
||||
validateAddresses
|
||||
@@ -1437,8 +1689,15 @@ enabled "$DEBUG" && echo "$msg"
|
||||
|
||||
prepareNetwork
|
||||
|
||||
echo "$UPLINK" > "$QEMU_DIR"/qemu.ip
|
||||
echo "$NIC" > "$QEMU_DIR"/qemu.nic
|
||||
if ! echo "$UPLINK" > "$QEMU_DIR"/qemu.ip; then
|
||||
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
|
||||
|
||||
@@ -1467,7 +1726,7 @@ else
|
||||
# Configure tap interface
|
||||
if ! configureNAT; then
|
||||
|
||||
closeBridge
|
||||
closeInterfaces
|
||||
NETWORK="user"
|
||||
|
||||
if ! enabled "$ROOTLESS" || enabled "$DEBUG"; then
|
||||
@@ -1509,10 +1768,8 @@ else
|
||||
|
||||
showGuestInfo
|
||||
|
||||
if [[ "${NETWORK,,}" == "passt" || "${NETWORK,,}" == "slirp" ]]; 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."
|
||||
fi
|
||||
if isUserMode && [ -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."
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
+28
-45
@@ -53,6 +53,32 @@ checkSse42() {
|
||||
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"
|
||||
@@ -130,52 +156,9 @@ configureTcg() {
|
||||
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() {
|
||||
|
||||
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
|
||||
CPU_FLAGS="${CPU_MODEL}${CPU_FEATURES:+,$CPU_FEATURES}${CPU_FLAGS:+,$CPU_FLAGS}"
|
||||
|
||||
return 0
|
||||
}
|
||||
@@ -211,7 +194,7 @@ else
|
||||
configureTcg
|
||||
fi
|
||||
|
||||
extractHostCpuArgument
|
||||
removeCpuArgument
|
||||
composeCpuFlags
|
||||
configureHostCpuName
|
||||
|
||||
|
||||
+2
-2
@@ -27,12 +27,12 @@ do
|
||||
if [ ! -s "$path" ] && [ ! -d "$path" ]; then
|
||||
bytes="0"
|
||||
else
|
||||
bytes=$(du -sb "$path" | cut -f1)
|
||||
bytes=$(du -sb "$path" 2>/dev/null | cut -f1) || bytes="0"
|
||||
fi
|
||||
|
||||
if (( bytes > 4096 )); then
|
||||
if [ -z "$total" ] || [[ "$total" == "0" ]] || [ "$bytes" -gt "$total" ]; then
|
||||
size=$(numfmt --to=iec --suffix=B "$bytes" | sed -r 's/([A-Z])/ \1/')
|
||||
size=$(numfmt --to=iec --suffix=B "$bytes" | sed -r 's/([A-Z])/ \1/') || size="${bytes} bytes"
|
||||
else
|
||||
size="$(echo "$bytes" "$total" | awk '{printf "%.1f", $1 * 100 / $2}')"
|
||||
size="$size%"
|
||||
|
||||
+79
-6
@@ -25,12 +25,29 @@ detectEngine() {
|
||||
|
||||
if [ -f "/run/.containerenv" ]; then
|
||||
ENGINE="${container:-}"
|
||||
|
||||
if [[ "${ENGINE,,}" == *"podman"* ]]; then
|
||||
ROOTLESS="Y"
|
||||
ENGINE="Podman"
|
||||
else
|
||||
[ -z "$ENGINE" ] && ENGINE="Kubernetes"
|
||||
fi
|
||||
elif [ -f "/.dockerenv" ]; then
|
||||
ENGINE="Docker"
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
detectRootless() {
|
||||
|
||||
local uid_map=""
|
||||
|
||||
uid_map=$(awk '{$1=$1; print}' /proc/self/uid_map 2>/dev/null || true)
|
||||
|
||||
if [[ "$uid_map" == "0 0 4294967295" ]]; then
|
||||
ROOTLESS="N"
|
||||
else
|
||||
ROOTLESS="Y"
|
||||
fi
|
||||
|
||||
return 0
|
||||
@@ -53,7 +70,6 @@ checkPrivileged() {
|
||||
max_cap=$(((1 << (last_cap + 1)) - 1))
|
||||
|
||||
if [ "$cap_bnd" -eq "$max_cap" ]; then
|
||||
ROOTLESS="N"
|
||||
PRIVILEGED="Y"
|
||||
fi
|
||||
|
||||
@@ -123,11 +139,67 @@ checkFilesystem() {
|
||||
return 0
|
||||
}
|
||||
|
||||
finiteMemoryLimit() {
|
||||
|
||||
local limit="$1"
|
||||
local sentinel="4611686018427387904"
|
||||
local i=0
|
||||
local left=""
|
||||
local right=""
|
||||
|
||||
[[ "$limit" =~ ^[0-9]+$ ]] || return 1
|
||||
|
||||
(( ${#limit} < ${#sentinel} )) && return 0
|
||||
(( ${#limit} > ${#sentinel} )) && return 1
|
||||
|
||||
for (( i=0; i<${#sentinel}; i++ )); do
|
||||
left="${limit:i:1}"
|
||||
right="${sentinel:i:1}"
|
||||
|
||||
(( left < right )) && return 0
|
||||
(( left > right )) && return 1
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
getMemoryInfo() {
|
||||
|
||||
local host_total=""
|
||||
local host_avail=""
|
||||
local limit=""
|
||||
local current=""
|
||||
local available=""
|
||||
|
||||
host_total=$(free -b | awk '/^Mem:/ {print $2; exit}')
|
||||
host_avail=$(free -b | awk '/^Mem:/ {print $7; exit}')
|
||||
|
||||
RAM_TOTAL="$host_total"
|
||||
RAM_AVAIL="$host_avail"
|
||||
|
||||
if [ -r /sys/fs/cgroup/memory.max ] && [ -r /sys/fs/cgroup/memory.current ]; then
|
||||
limit=$(< /sys/fs/cgroup/memory.max)
|
||||
current=$(< /sys/fs/cgroup/memory.current)
|
||||
elif [ -r /sys/fs/cgroup/memory/memory.limit_in_bytes ] && [ -r /sys/fs/cgroup/memory/memory.usage_in_bytes ]; then
|
||||
limit=$(< /sys/fs/cgroup/memory/memory.limit_in_bytes)
|
||||
current=$(< /sys/fs/cgroup/memory/memory.usage_in_bytes)
|
||||
fi
|
||||
|
||||
if finiteMemoryLimit "$limit" && [[ "$current" =~ ^[0-9]+$ ]]; then
|
||||
(( limit < RAM_TOTAL )) && RAM_TOTAL="$limit"
|
||||
|
||||
available=$(( limit - current ))
|
||||
(( available < 0 )) && available=0
|
||||
(( available < RAM_AVAIL )) && RAM_AVAIL="$available"
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
normalizeRamSize() {
|
||||
|
||||
# Read memory
|
||||
RAM_AVAIL=$(free -b | grep -m 1 Mem: | awk '{print $7}')
|
||||
RAM_TOTAL=$(free -b | grep -m 1 Mem: | awk '{print $2}')
|
||||
# Read host and container memory limits.
|
||||
getMemoryInfo
|
||||
|
||||
RAM_SPARE=500000000
|
||||
RAM_MINIMUM=136314880
|
||||
@@ -232,6 +304,7 @@ PROCESS="${APP,,}"
|
||||
PROCESS="${PROCESS// /-}"
|
||||
|
||||
detectEngine
|
||||
detectRootless
|
||||
|
||||
echo "❯ Starting $APP for $ENGINE v$(</etc/version)..."
|
||||
echo "❯ For support visit $SUPPORT"
|
||||
@@ -273,7 +346,7 @@ 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 "❯ CPU: ${CPU} | RAM: ${AVAIL_MEM/ GB/}/$TOTAL_MEM | DISK: $SPACE_GB (${FS}) | KERNEL: ${SYS}"
|
||||
echo
|
||||
|
||||
checkKvm
|
||||
|
||||
+38
-14
@@ -16,13 +16,15 @@ WEB_PID="/run/nginx.pid"
|
||||
WSD_PID="$QEMU_DIR/websocketd.pid"
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -32,7 +34,7 @@ configureWebPorts() {
|
||||
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
|
||||
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
|
||||
fi
|
||||
|
||||
@@ -44,8 +46,8 @@ configureWebServer() {
|
||||
mkdir -p /etc/nginx/sites-enabled
|
||||
cp /etc/nginx/default.conf /etc/nginx/sites-enabled/web.conf
|
||||
|
||||
configureWebPorts
|
||||
configureIpv6Listen
|
||||
configureWebPorts || return 1
|
||||
configureIpv6Listen || return 1
|
||||
|
||||
return 0
|
||||
}
|
||||
@@ -53,24 +55,46 @@ configureWebServer() {
|
||||
startWebServer() {
|
||||
|
||||
# Start webserver
|
||||
nginx -e stderr
|
||||
nginx -e stderr || return 1
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
startWebsocketServer() {
|
||||
|
||||
local log="/var/log/websocketd.log"
|
||||
rm -f "$log"
|
||||
|
||||
# Start websocket server
|
||||
websocketd --address 127.0.0.1 --port="$WSD_PORT" /run/socket.sh >/var/log/websocketd.log &
|
||||
echo "$!" > "$WSD_PID"
|
||||
websocketd --address 127.0.0.1 --port="$WSD_PORT" /run/socket.sh > "$log" 2>&1 &
|
||||
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..."
|
||||
|
||||
if ! disabled "${WEB:-}"; then
|
||||
configureWebServer
|
||||
startWebServer
|
||||
startWebsocketServer
|
||||
fi
|
||||
disabled "${WEB:-}" && return 0
|
||||
|
||||
configureWebServer || return 1
|
||||
|
||||
startWebServer || return 1
|
||||
startWebsocketServer || return 1
|
||||
|
||||
return 0
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ refresh() {
|
||||
[ ! -f "$path" ] && return 0
|
||||
[ ! -s "$path" ] && return 0
|
||||
|
||||
msg=$(< "$path")
|
||||
msg=$(< "$path") || return 0
|
||||
msg="${msg%$'\n'}"
|
||||
|
||||
[ -z "$msg" ] && return 0
|
||||
|
||||
+141
-14
@@ -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; }
|
||||
|
||||
strip() {
|
||||
|
||||
local value="${1:-}"
|
||||
|
||||
# Remove surrounding whitespace
|
||||
@@ -28,24 +29,38 @@ strip() {
|
||||
}
|
||||
|
||||
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 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
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 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
formatBytes() {
|
||||
|
||||
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. ]}"
|
||||
result="${result//[a-zA-Z ]/}"
|
||||
|
||||
if [[ "${2:-}" == "up" ]]; then
|
||||
if [[ "$result" == *"."* ]]; then
|
||||
result="${result%%.*}"
|
||||
@@ -56,11 +71,13 @@ formatBytes() {
|
||||
result="${result%%.*}"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "$result $unit"
|
||||
return 0
|
||||
}
|
||||
|
||||
isAlive() {
|
||||
|
||||
local pid="$1"
|
||||
[ -z "$pid" ] && return 1
|
||||
|
||||
@@ -72,6 +89,7 @@ isAlive() {
|
||||
}
|
||||
|
||||
waitPid() {
|
||||
|
||||
local i=0
|
||||
local pid="$1"
|
||||
local timeout="${2:-10}"
|
||||
@@ -86,6 +104,7 @@ waitPid() {
|
||||
}
|
||||
|
||||
waitPidFile() {
|
||||
|
||||
local i=0
|
||||
local pid=""
|
||||
local file="$1"
|
||||
@@ -106,6 +125,7 @@ waitPidFile() {
|
||||
}
|
||||
|
||||
pKill() {
|
||||
|
||||
local pid="$1"
|
||||
local timeout="${2:-10}"
|
||||
|
||||
@@ -119,6 +139,7 @@ pKill() {
|
||||
}
|
||||
|
||||
fWait() {
|
||||
|
||||
local i=0
|
||||
local name="$1"
|
||||
local timeout="${2:-10}"
|
||||
@@ -138,6 +159,7 @@ fWait() {
|
||||
}
|
||||
|
||||
fKill() {
|
||||
|
||||
local name="$1"
|
||||
local timeout="${2:-10}"
|
||||
|
||||
@@ -150,6 +172,7 @@ fKill() {
|
||||
}
|
||||
|
||||
sKill() {
|
||||
|
||||
local pid=""
|
||||
local file="$1"
|
||||
|
||||
@@ -165,6 +188,7 @@ sKill() {
|
||||
}
|
||||
|
||||
mKill() {
|
||||
|
||||
local timeout=10
|
||||
local files=("$@")
|
||||
|
||||
@@ -182,14 +206,15 @@ mKill() {
|
||||
}
|
||||
|
||||
setOwner() {
|
||||
|
||||
local file="$1"
|
||||
local dir uid gid
|
||||
|
||||
[ ! -f "$file" ] && return 1
|
||||
|
||||
dir=$(dirname -- "$file")
|
||||
uid=$(stat -c '%u' "$dir")
|
||||
gid=$(stat -c '%g' "$dir")
|
||||
uid=$(stat -c '%u' "$dir") || return 1
|
||||
gid=$(stat -c '%g' "$dir") || return 1
|
||||
|
||||
! chown "$uid:$gid" "$file" && return 1
|
||||
|
||||
@@ -197,6 +222,7 @@ setOwner() {
|
||||
}
|
||||
|
||||
makeDir() {
|
||||
|
||||
local path="$1"
|
||||
local dir uid gid
|
||||
|
||||
@@ -204,25 +230,123 @@ makeDir() {
|
||||
! mkdir -p "$path" && return 1
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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 () {
|
||||
|
||||
local s
|
||||
s=${1//&/\&}
|
||||
s=${s//</\<}
|
||||
s=${s//>/\>}
|
||||
s=${s//'"'/\"}
|
||||
|
||||
printf -- %s "$s"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
html() {
|
||||
|
||||
local title
|
||||
local body
|
||||
local script
|
||||
@@ -247,13 +371,14 @@ html() {
|
||||
HTML="${HTML/\[4\]/$footer}"
|
||||
HTML="${HTML/\[5\]/$FOOTER2}"
|
||||
|
||||
echo "$HTML" > "$PAGE"
|
||||
echo "$body" > "$INFO"
|
||||
echo "$HTML" > "$PAGE" || return 1
|
||||
echo "$body" > "$INFO" || return 1
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
cpu() {
|
||||
|
||||
local ret
|
||||
local cpu=""
|
||||
|
||||
@@ -307,6 +432,7 @@ hasDisk() {
|
||||
}
|
||||
|
||||
getCountry() {
|
||||
|
||||
local url=$1
|
||||
local query=$2
|
||||
local rc json result
|
||||
@@ -345,6 +471,7 @@ setCountry() {
|
||||
}
|
||||
|
||||
addPackage() {
|
||||
|
||||
local pkg=$1
|
||||
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
|
||||
fi
|
||||
|
||||
DEBIAN_FRONTEND=noninteractive apt-get -qq update
|
||||
DEBIAN_FRONTEND=noninteractive apt-get -qq --no-install-recommends -y install "$pkg" > /dev/null
|
||||
DEBIAN_FRONTEND=noninteractive apt-get -qq update || return 1
|
||||
DEBIAN_FRONTEND=noninteractive apt-get -qq --no-install-recommends -y install "$pkg" > /dev/null || return 1
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user