Compare commits

..
23 Commits
Author SHA1 Message Date
KroeseandGitHub 3c7c3ca1b1 fix: Prevent race when reading PID files (#1342) 2026-07-28 14:22:08 +02:00
KroeseandGitHub 2b27f32cd4 fix: Disk options were applied to the controller (#1341) 2026-07-28 03:30:55 +02:00
KroeseandGitHub 55d1d50284 fix: Retry transient gateway errors in web status (#1340) 2026-07-27 23:49:22 +02:00
KroeseandGitHub 784b73b5b5 feat: Add DISK_OPTIONS support to disk devices (#1339) 2026-07-27 23:48:21 +02:00
KroeseandGitHub 85f00bb9cc docs: Added new DISK_OPTIONS variable (#1338) 2026-07-27 12:04:25 +02:00
KroeseandGitHub efe8732e47 fix: Avoid shadowing PID output variables (#1337) 2026-07-27 12:03:24 +02:00
KroeseandGitHub f6871dd61b fix: Clear invalid PID values in shared reader (#1336) 2026-07-26 17:52:14 +02:00
KroeseandGitHub a713b728ff fix: Use shared PID reader for helper processes (#1335) 2026-07-26 17:41:46 +02:00
KroeseandGitHub 0d74c6ac80 build: Update dependabot config (#1334) 2026-07-26 00:58:36 +02:00
KroeseandGitHub 63e4529eff fix: Add cleanup for failed web server startup (#1333) 2026-07-25 13:47:20 +02:00
KroeseandGitHub afd848cebb fix: Make healthcheck succeed during download (#1332) 2026-07-25 12:16:41 +02:00
KroeseandGitHub 695b075130 feat: Provide host access through system.lan (#1331) 2026-07-25 03:38:36 +02:00
KroeseandGitHub 7eff53e722 feat: Improve disk error handling (#1328) 2026-07-24 13:51:59 +02:00
KroeseandGitHub bcaf0e5980 fix: Improve network error handling (#1327) 2026-07-24 13:27:09 +02:00
KroeseandGitHub faa820c2cc feat: Inline local variable declarations (#1326) 2026-07-24 13:11:40 +02:00
KroeseandGitHub 81e477fcc4 feat: Warn when DSM and container share an IP address (#1325) 2026-07-24 12:45:54 +02:00
KroeseandGitHub abd3a1c3df fix: Network mode shown for DHCP mode (#1324) 2026-07-24 12:03:57 +02:00
KroeseandGitHub 3b59bcd284 fix: Ensure progress reaches 100% (#1322) 2026-07-23 23:03:54 +02:00
KroeseandGitHub f1b56f394a build: Create a detailed .gitignore file (#1321) 2026-07-23 15:42:59 +02:00
KroeseandGitHub 1a983ebcd1 feat: Improve error handling for webserver config (#1320) 2026-07-23 04:17:26 +02:00
KroeseandGitHub 41c76198fa feat: Use atomic writes for progress updates (#1319) 2026-07-23 04:10:09 +02:00
KroeseandGitHub 222b5649b0 fix: Prevent stale polling responses for web status (#1318) 2026-07-23 04:00:12 +02:00
KroeseandGitHub e14fd4b711 docs: Environment variables (#1317) 2026-07-21 13:14:32 +02:00
15 changed files with 825 additions and 224 deletions
+7
View File
@@ -1,4 +1,5 @@
version: 2 version: 2
updates: updates:
- package-ecosystem: docker - package-ecosystem: docker
directory: / directory: /
@@ -6,9 +7,15 @@ updates:
interval: weekly interval: weekly
cooldown: cooldown:
default-days: 7 default-days: 7
- package-ecosystem: github-actions - package-ecosystem: github-actions
directory: / directory: /
schedule: schedule:
interval: weekly interval: weekly
cooldown: cooldown:
default-days: 7 default-days: 7
ignore:
- dependency-name: "*"
update-types:
- version-update:semver-minor
- version-update:semver-patch
+268 -1
View File
@@ -1 +1,268 @@
build.sh ##############################
# Operating System Files
##############################
.DS_Store
.AppleDouble
.LSOverride
Thumbs.db
ehthumbs.db
Desktop.ini
Icon?
$RECYCLE.BIN/
.Spotlight-V100/
.Trashes/
.fseventsd
##############################
# IDEs
##############################
.vscode/
.idea/
*.iml
*.ipr
*.iws
##############################
# VS Code
##############################
.history/
*.code-workspace
##############################
# Vim
##############################
*.swp
*.swo
Session.vim
##############################
# Sublime
##############################
*.sublime-workspace
*.sublime-project
##############################
# Temporary Files
##############################
*.tmp
*.temp
*.bak
*.old
*.orig
*.rej
*.save
##############################
# Logs
##############################
*.log
logs/
log/
*.out
*.err
*.trace
##############################
# Runtime
##############################
*.pid
*.seed
*.pid.lock
##############################
# Secrets
##############################
.env
.env.*
!.env.example
*.pem
*.key
*.crt
*.cer
*.p12
*.pfx
*.kdbx
*.secret
*.token
##############################
# SSH
##############################
.ssh/
##############################
# Docker
##############################
docker-compose.override.yml
docker-compose.local.yml
##############################
# VM Storage
##############################
storage/
windows/
downloads/
##############################
# Disk Images
##############################
*.qcow2
*.qcow
*.vhd
*.vhdx
*.vdi
*.raw
*.img
*.iso
*.bin
##############################
# QEMU
##############################
*.nvram
*.fd
*.efi
*.sock
*.monitor
*.serial
##############################
# Samba
##############################
shared/
share/
##############################
# Backups
##############################
backup/
backups/
*.backup
##############################
# Cache
##############################
.cache/
.cache-loader/
.tmp/
temp/
tmp/
##############################
# Python
##############################
__pycache__/
*.py[cod]
.pytest_cache/
.mypy_cache/
.venv/
venv/
##############################
# Node
##############################
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
##############################
# Build
##############################
dist/
build/
out/
release/
##############################
# Coverage
##############################
coverage/
.coverage
coverage.xml
##############################
# Archives
##############################
*.zip
*.tar
*.tar.gz
*.tgz
*.7z
*.rar
##############################
# Generated Config
##############################
config.local.*
settings.local.*
local.env
##############################
# Test Files
##############################
test-output/
playwright-report/
##############################
# macOS
##############################
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
##############################
# Linux
##############################
*~
.nfs*
##############################
# Windows
##############################
*.stackdump
##############################
# Misc
##############################
*.cache
*.lock
*.lock.json
*.bak.*
##############################
# Keep Examples
##############################
!.gitkeep
!.env.example
+6 -5
View File
@@ -8,8 +8,8 @@ An empty default means the variable is unset and its value is determined automat
| Variable | Default | Description | | Variable | Default | Description |
|---|---|---| |---|---|---|
| `URL` | | URL or local path to the DSM `.pat` installation file. Downloads the default Virtual DSM image automatically when unset. | | `URL` | | URL or local path to a custom `.pat` installation file. |
| `COUNTRY` | | Country code used to select the Synology download mirror. Detected automatically when unset. | | `COUNTRY` | | Country code used to select the Synology download mirror. |
| `HOST_MAC` | | MAC address reported to DSM. | | `HOST_MAC` | | MAC address reported to DSM. |
| `HOST_MODEL` | | Synology host model reported to DSM. | | `HOST_MODEL` | | Synology host model reported to DSM. |
| `HOST_SERIAL` | | Synology host serial number reported to DSM. | | `HOST_SERIAL` | | Synology host serial number reported to DSM. |
@@ -39,6 +39,7 @@ An empty default means the variable is unset and its value is determined automat
| `DISK_DISCARD` | `unmap` | Discard/TRIM mode for the primary disk. | | `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_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. | | `DISK_FLAGS` | | Additional options used when creating `qcow2` disks. |
| `DISK_OPTIONS` | | Additional options appended to QEMU disk devices. |
| `ALLOCATE` | `N` | Preallocates space for the data disks. | | `ALLOCATE` | `N` | Preallocates space for the data disks. |
| `STORAGE` | `/storage` | Storage directory used for disks, settings, and downloads. | | `STORAGE` | `/storage` | Storage directory used for disks, settings, and downloads. |
@@ -47,14 +48,14 @@ An empty default means the variable is unset and its value is determined automat
| Variable | Default | Description | | Variable | Default | Description |
|---|---|---| |---|---|---|
| `NETWORK` | | Network mode, such as `nat`, `user`, or `N` to disable networking. | | `NETWORK` | | Network mode, such as `nat`, `user`, or `N` to disable networking. |
| `DHCP` | `N` | Enables macvtap networking so DSM receives an address from the external LAN through DHCP. | | `DHCP` | `N` | Enables macvtap networking so DSM receives a DHCP address. |
| `HOST` | `VirtualDSM` | Hostname assigned to DSM. | | `HOST` | | Hostname assigned to the machine on the network. |
| `IP` | | Overrides the automatically selected guest IPv4 address. | | `IP` | | Overrides the automatically selected guest IPv4 address. |
| `MAC` | | Guest network adapter MAC address. | | `MAC` | | Guest network adapter MAC address. |
| `ADAPTER` | `virtio-net-pci` | QEMU network adapter model. | | `ADAPTER` | `virtio-net-pci` | QEMU network adapter model. |
| `DEV` | `eth0` | Container network interface used as the uplink. | | `DEV` | `eth0` | Container network interface used as the uplink. |
| `MTU` | | MTU assigned to the guest network interface. | | `MTU` | | MTU assigned to the guest network interface. |
| `MASK` | `255.255.255.0` | IPv4 netmask. | | `MASK` | `255.255.255.0` | IPv4 netmask for guest network. |
| `TAP` | `dsm` | TAP or macvtap interface name. | | `TAP` | `dsm` | TAP or macvtap interface name. |
| `BRIDGE` | `docker` | Bridge name used for NAT networking. | | `BRIDGE` | `docker` | Bridge name used for NAT networking. |
| `HOST_PORTS` | | Ports excluded from guest forwarding. | | `HOST_PORTS` | | Ports excluded from guest forwarding. |
+1 -1
View File
@@ -15,7 +15,7 @@ file="/run/shm/dsm.url"
address="/run/shm/qemu.ip" address="/run/shm/qemu.ip"
gateway="/run/shm/qemu.gw" gateway="/run/shm/qemu.gw"
[ ! -s "$file" ] && echo "DSM has not enabled networking yet..." && exit 1 [ ! -s "$file" ] && echo "DSM has not enabled networking yet..." && exit 0
location=$(<"$file") location=$(<"$file")
+36 -22
View File
@@ -7,6 +7,7 @@ set -Eeuo pipefail
: "${DISK_FMT:="raw"}" # Disk file format, 'raw' by default for best performance : "${DISK_FMT:="raw"}" # Disk file format, 'raw' by default for best performance
: "${DISK_TYPE:=""}" # Device type to be used, "sata", "nvme", "blk" or "scsi" : "${DISK_TYPE:=""}" # Device type to be used, "sata", "nvme", "blk" or "scsi"
: "${DISK_FLAGS:=""}" # Specifies the options for use with the qcow2 disk format : "${DISK_FLAGS:=""}" # Specifies the options for use with the qcow2 disk format
: "${DISK_OPTIONS:=""}" # Specifies additional options for the QEMU disk device
: "${DISK_CACHE:="none"}" # Caching mode, can be set to 'writeback' for better performance : "${DISK_CACHE:="none"}" # Caching mode, can be set to 'writeback' for better performance
: "${DISK_DISCARD:="unmap"}" # Controls whether unmap (TRIM) commands are passed to the host. : "${DISK_DISCARD:="unmap"}" # Controls whether unmap (TRIM) commands are passed to the host.
: "${DISK_ROTATION:="1"}" # Rotation rate, set to 1 for SSD storage and increase for HDD : "${DISK_ROTATION:="1"}" # Rotation rate, set to 1 for SSD storage and increase for HDD
@@ -16,6 +17,7 @@ DISK_IO=$(strip "$DISK_IO")
DISK_FMT=$(strip "$DISK_FMT") DISK_FMT=$(strip "$DISK_FMT")
DISK_TYPE=$(strip "$DISK_TYPE") DISK_TYPE=$(strip "$DISK_TYPE")
DISK_FLAGS=$(strip "$DISK_FLAGS") DISK_FLAGS=$(strip "$DISK_FLAGS")
DISK_OPTIONS=$(strip "$DISK_OPTIONS")
DISK_CACHE=$(strip "$DISK_CACHE") DISK_CACHE=$(strip "$DISK_CACHE")
DISK_DISCARD=$(strip "$DISK_DISCARD") DISK_DISCARD=$(strip "$DISK_DISCARD")
DISK_ROTATION=$(strip "$DISK_ROTATION") DISK_ROTATION=$(strip "$DISK_ROTATION")
@@ -154,8 +156,8 @@ normalizeSize() {
local diskDesc="$2" local diskDesc="$2"
local dir="$3" local dir="$3"
local gb free space local free dataSize
local dataSize spare=1073741824 local spare=1073741824
if [[ "${diskSpace,,}" == "max" || "${diskSpace,,}" == "half" ]]; then if [[ "${diskSpace,,}" == "max" || "${diskSpace,,}" == "half" ]]; then
@@ -168,12 +170,12 @@ normalizeSize() {
fi fi
(( free < spare )) && free="$spare" (( free < spare )) && free="$spare"
gb=$(( free / 1073741825 )) local gb=$(( free / 1073741825 ))
diskSpace="${gb}G" diskSpace="${gb}G"
fi fi
space="${diskSpace// /}" local space="${diskSpace// /}"
[ -z "$space" ] && space="256G" [ -z "$space" ] && space="256G"
[ -z "${space//[0-9. ]}" ] && space="${space}G" [ -z "${space//[0-9. ]}" ] && space="${space}G"
space=$(echo "${space^^}" | sed 's/MB/M/g;s/GB/G/g;s/TB/T/g') space=$(echo "${space^^}" | sed 's/MB/M/g;s/GB/G/g;s/TB/T/g')
@@ -508,6 +510,9 @@ createDevice () {
[[ -z "${PCI_BUS:-}" && ( "${MACHINE,,}" == pc || "${MACHINE,,}" == pc-i440fx* ) ]] && bus="pci.0" [[ -z "${PCI_BUS:-}" && ( "${MACHINE,,}" == pc || "${MACHINE,,}" == pc-i440fx* ) ]] && bus="pci.0"
local options=""
[ -n "$DISK_OPTIONS" ] && options=",${DISK_OPTIONS#,}"
local bootIndex="" local bootIndex=""
local diskId="data$diskIndex" local diskId="data$diskIndex"
[ -n "$diskIndex" ] && bootIndex=",bootindex=$diskIndex" [ -n "$diskIndex" ] && bootIndex=",bootindex=$diskIndex"
@@ -520,29 +525,29 @@ createDevice () {
;; ;;
"usb" ) "usb" )
result+=",if=none \ result+=",if=none \
-device usb-storage,drive=${diskId}${bootIndex}${diskSerial}${diskSectors}" -device usb-storage,drive=${diskId}${bootIndex}${diskSerial}${diskSectors}${options}"
echo "$result" echo "$result"
;; ;;
"nvme" ) "nvme" )
result+=",if=none \ result+=",if=none \
-device nvme,drive=${diskId}${bootIndex},serial=deadbeaf${diskIndex}${diskSerial}${diskSectors}" -device nvme,drive=${diskId}${bootIndex},serial=deadbeaf${diskIndex}${diskSerial}${diskSectors}${options}"
echo "$result" echo "$result"
;; ;;
"ide" | "sata" ) "ide" | "sata" )
result+=",if=none \ result+=",if=none \
-device ich9-ahci,id=ahci${diskIndex},addr=$diskAddress \ -device ich9-ahci,id=ahci${diskIndex},addr=$diskAddress \
-device ide-hd,drive=${diskId},bus=ahci$diskIndex.0,rotation_rate=$DISK_ROTATION${bootIndex}${diskSerial}${diskSectors}" -device ide-hd,drive=${diskId},bus=ahci$diskIndex.0,rotation_rate=$DISK_ROTATION${bootIndex}${diskSerial}${diskSectors}${options}"
echo "$result" echo "$result"
;; ;;
"blk" | "virtio-blk" ) "blk" | "virtio-blk" )
result+=",if=none \ result+=",if=none \
-device virtio-blk-pci,drive=${diskId},bus=$bus,addr=$diskAddress,iothread=io2${bootIndex}${diskSerial}${diskSectors}" -device virtio-blk-pci,drive=${diskId},bus=$bus,addr=$diskAddress,iothread=io2${bootIndex}${diskSerial}${diskSectors}${options}"
echo "$result" echo "$result"
;; ;;
"scsi" | "virtio-scsi" ) "scsi" | "virtio-scsi" )
result+=",if=none \ result+=",if=none \
-device virtio-scsi-pci,id=${diskId}b,bus=$bus,addr=$diskAddress,iothread=io2,hotplug=off \ -device virtio-scsi-pci,id=${diskId}b,bus=$bus,addr=$diskAddress,iothread=io2,hotplug=off \
-device scsi-hd,drive=${diskId},bus=${diskId}b.0,channel=0,scsi-id=0,lun=0,rotation_rate=$DISK_ROTATION${bootIndex}${diskSerial}${diskSectors}" -device scsi-hd,drive=${diskId},bus=${diskId}b.0,channel=0,scsi-id=0,lun=0,rotation_rate=$DISK_ROTATION${bootIndex}${diskSerial}${diskSectors}${options}"
echo "$result" echo "$result"
;; ;;
esac esac
@@ -573,13 +578,12 @@ addDisk () {
local diskCache="$9" local diskCache="$9"
local fs dir used space local fs dir used space
local diskExt diskFile local diskExt dataSize
local dataSize missing
local available currentSize local available currentSize
local previousExt previousFmt local previousExt
diskExt=$(fmt2ext "$diskFmt") diskExt=$(fmt2ext "$diskFmt")
diskFile="$diskBase.$diskExt" local diskFile="$diskBase.$diskExt"
dir=$(dirname "$diskFile") dir=$(dirname "$diskFile")
[ ! -d "$dir" ] && return 0 [ ! -d "$dir" ] && return 0
@@ -587,7 +591,11 @@ addDisk () {
space=$(normalizeSize "$diskSpace" "$diskDesc" "$dir") space=$(normalizeSize "$diskSpace" "$diskDesc" "$dir")
dataSize=$(numfmt --from=iec "$space") dataSize=$(numfmt --from=iec "$space")
fs=$(stat -f -c %T "$dir") if ! fs=$(stat -f -c %T "$dir"); then
error "Failed to determine filesystem type of \"$dir\" !"
return 1
fi
checkFS "$fs" "$diskFile" "$diskDesc" || exit $? checkFS "$fs" "$diskFile" "$diskDesc" || exit $?
if ! supportsDirect "$fs"; then if ! supportsDirect "$fs"; then
@@ -595,23 +603,24 @@ addDisk () {
diskCache="writeback" diskCache="writeback"
fi fi
if [ ! -s "$diskFile" ] ; then if [ ! -f "$diskFile" ] || [ ! -s "$diskFile" ]; then
if [[ "${diskFmt,,}" != "raw" ]]; then if [[ "${diskFmt,,}" != "raw" ]]; then
previousFmt="raw" local previousFmt="raw"
else else
previousFmt="qcow2" local previousFmt="qcow2"
fi fi
previousExt=$(fmt2ext "$previousFmt") previousExt=$(fmt2ext "$previousFmt")
if [ -s "$diskBase.$previousExt" ] ; then if [ -f "$diskBase.$previousExt" ] &&
[ -s "$diskBase.$previousExt" ]; then
convertDisk "$diskBase.$previousExt" "$previousFmt" "$diskFile" "$diskFmt" "$diskBase" "$diskDesc" "$fs" || exit $? convertDisk "$diskBase.$previousExt" "$previousFmt" "$diskFile" "$diskFmt" "$diskBase" "$diskDesc" "$fs" || exit $?
fi fi
fi fi
if [ -s "$diskFile" ]; then if [ -f "$diskFile" ] && [ -s "$diskFile" ]; then
currentSize=$(getSize "$diskFile") || exit 71 currentSize=$(getSize "$diskFile") || exit 71
@@ -641,18 +650,18 @@ addDisk () {
currentSize=$(getSize "$diskFile") || exit 73 currentSize=$(getSize "$diskFile") || exit 73
used=$(du -sB 1 "$diskFile" | cut -f1) used=$(du -sB 1 "$diskFile" | cut -f1)
available=$(df --output=avail -B 1 "$dir" | tail -n 1) available=$(df --output=avail -B 1 "$dir" | tail -n 1)
missing=$(( currentSize - used - available )) local missing=$(( currentSize - used - available ))
(( missing < 0 )) && missing=0 (( missing < 0 )) && missing=0
if (( missing > 0 )); then if (( missing > 0 )); then
local gb base msg local gb base
gb=$(formatBytes "$available") gb=$(formatBytes "$available")
base=$(baseDir "$dir") base=$(baseDir "$dir")
missing=$(formatBytes "$missing") missing=$(formatBytes "$missing")
currentSize=$(formatBytes "$currentSize") currentSize=$(formatBytes "$currentSize")
msg="The virtual size of the ${diskDesc,,} is $currentSize" local msg="The virtual size of the ${diskDesc,,} is $currentSize"
if [ -n "$used" ] && [[ "$used" != "0" ]]; then if [ -n "$used" ] && [[ "$used" != "0" ]]; then
used=$(formatBytes "$used") used=$(formatBytes "$used")
@@ -767,6 +776,11 @@ if [[ "$DISK_FLAGS" =~ [[:space:]] ]]; then
exit 78 exit 78
fi fi
if [[ "$DISK_OPTIONS" =~ [[:space:]] ]]; then
error "Invalid DISK_OPTIONS value '$DISK_OPTIONS', spaces are not allowed."
exit 78
fi
if [ -z "$ALLOCATE" ]; then if [ -z "$ALLOCATE" ]; then
ALLOCATE="N" ALLOCATE="N"
fi fi
+196 -91
View File
@@ -84,8 +84,7 @@ getMTU() {
minMTU() { minMTU() {
local mtu="" local mtu min=""
local min=""
for mtu in "$@"; do for mtu in "$@"; do
[[ -z "$mtu" || "$mtu" == "0" ]] && continue [[ -z "$mtu" || "$mtu" == "0" ]] && continue
@@ -124,7 +123,7 @@ gatewayMAC() {
maskToCIDR() { maskToCIDR() {
local mask="$1" local mask="$1"
local prefix="" local prefix
if ! command -v ipcalc > /dev/null 2>&1; then if ! command -v ipcalc > /dev/null 2>&1; then
error "Required command 'ipcalc' is not installed!" error "Required command 'ipcalc' is not installed!"
@@ -154,7 +153,7 @@ maskToCIDR() {
networkCIDR() { networkCIDR() {
local ip="$1" local ip="$1"
local network="" local network
network=$(ipcalc -n -b "$ip/$MASK" 2>/dev/null | awk ' network=$(ipcalc -n -b "$ip/$MASK" 2>/dev/null | awk '
/^Network:/ { /^Network:/ {
@@ -172,6 +171,37 @@ networkCIDR() {
return 0 return 0
} }
upstreamIP() {
local subnet="$1"
local guest="$2"
local gateway="$3"
local broadcast candidate last
broadcast=$(ipcalc -n -b "$subnet" 2>/dev/null | awk '
/^Broadcast:/ {
print $2
exit
}
')
if [[ ! "$broadcast" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
return 1
fi
last="${broadcast##*.}"
for (( last--; last>=2; last-- )); do
candidate="${broadcast%.*}.$last"
[[ "$candidate" == "$guest" || "$candidate" == "$gateway" ]] && continue
echo "$candidate"
return 0
done
return 1
}
detectInterface() { detectInterface() {
if [ -n "$DEV" ]; then if [ -n "$DEV" ]; then
@@ -207,16 +237,23 @@ formatAddress() {
return 0 return 0
} }
defaultGateway() {
ip -4 route list default dev "$1" 2>/dev/null |
awk '$1 == "default" { for (i = 1; i < NF; i++) if ($i == "via") { print $(i + 1); exit } }' || :
return 0
}
detectAddresses() { detectAddresses() {
GATEWAY=$(ip route list dev "$DEV" | awk ' /^default/ {print $3}' | head -n 1) GATEWAY=$(defaultGateway "$DEV")
{ UPLINK=$(ip address show dev "$DEV" | grep inet | awk '/inet / { print $2 }' | cut -f1 -d/ | head -n 1); } 2>/dev/null || : { UPLINK=$(ip address show dev "$DEV" | grep inet | awk '/inet / { print $2 }' | cut -f1 -d/ | head -n 1); } 2>/dev/null || :
IP6="" IP6=""
if [ -f /proc/net/if_inet6 ] && [[ "$(cat /proc/sys/net/ipv6/conf/all/disable_ipv6 2>/dev/null)" != "1" ]]; then if [ -f /proc/net/if_inet6 ] && [[ "$(cat /proc/sys/net/ipv6/conf/all/disable_ipv6 2>/dev/null)" != "1" ]]; then
local rc=0 { IP6=$(ip -6 addr show dev "$DEV" scope global up); local rc=$?; } 2>/dev/null || :
{ IP6=$(ip -6 addr show dev "$DEV" scope global up); rc=$?; } 2>/dev/null || :
(( rc != 0 )) && IP6="" (( rc != 0 )) && IP6=""
[ -n "$IP6" ] && IP6=$(echo "$IP6" | sed -e's/^.*inet6 \([^ ]*\)\/.*$/\1/;t;d' | head -n 1) [ -n "$IP6" ] && IP6=$(echo "$IP6" | sed -e's/^.*inet6 \([^ ]*\)\/.*$/\1/;t;d' | head -n 1)
fi fi
@@ -226,7 +263,7 @@ detectAddresses() {
detectAdapter() { detectAdapter() {
local result="" local result
NIC="" NIC=""
BUS="" BUS=""
@@ -252,7 +289,7 @@ detectAdapter() {
containerID() { containerID() {
local id="" local id
id=$(hostname -s 2>/dev/null || true) id=$(hostname -s 2>/dev/null || true)
@@ -260,7 +297,7 @@ containerID() {
id=$(< /etc/machine-id) id=$(< /etc/machine-id)
fi fi
if [ -z "$id" ] && [ -s /proc/sys/kernel/random/boot_id ]; then if [ -z "$id" ] && [ -r /proc/sys/kernel/random/boot_id ]; then
id=$(< /proc/sys/kernel/random/boot_id) id=$(< /proc/sys/kernel/random/boot_id)
fi fi
@@ -286,7 +323,7 @@ disableIPv6() {
subnetInUse() { subnetInUse() {
local subnet="$1" local subnet="$1"
local broader="" narrower="" routes="" local broader narrower routes
if ! broader=$(ip -4 route show table all match "$subnet" 2>/dev/null); then if ! broader=$(ip -4 route show table all match "$subnet" 2>/dev/null); then
error "Failed to inspect existing routes for subnet $subnet." error "Failed to inspect existing routes for subnet $subnet."
@@ -324,15 +361,14 @@ guestIP() {
natGuestIP() { natGuestIP() {
local ip="$1" local ip="$1"
local start="" guest="" subnet="" local guest subnet second third
local second="" third="" rc=""
third=$(cut -d. -f3 <<< "$ip") third=$(cut -d. -f3 <<< "$ip")
if [[ "$ip" == "172.30."* ]]; then if [[ "$ip" == "172.30."* ]]; then
start="31" local start="31"
else else
start="30" local start="30"
fi fi
for (( second=start; second<=254; second++ )); do for (( second=start; second<=254; second++ )); do
@@ -342,7 +378,7 @@ natGuestIP() {
if subnetInUse "$subnet"; then if subnetInUse "$subnet"; then
continue continue
else else
rc=$? local rc=$?
(( rc == 1 )) || return 1 (( rc == 1 )) || return 1
fi fi
@@ -357,7 +393,7 @@ natGuestIP() {
if subnetInUse "$subnet"; then if subnetInUse "$subnet"; then
continue continue
else else
rc=$? local rc=$?
(( rc == 1 )) || return 1 (( rc == 1 )) || return 1
fi fi
@@ -411,7 +447,9 @@ configureDNS() {
local host="$4" local host="$4"
local mask="$5" local mask="$5"
local gateway="$6" local gateway="$6"
local arguments="$DNSMASQ_OPTS" rc local upstream="${7:-}"
local arguments="$DNSMASQ_OPTS"
local pid
if ! echo "$gateway" > /run/shm/qemu.gw; then if ! echo "$gateway" > /run/shm/qemu.gw; then
error "Failed to write gateway file." error "Failed to write gateway file."
@@ -421,7 +459,10 @@ configureDNS() {
enabled "${DNSMASQ_DISABLE:-}" && return 0 enabled "${DNSMASQ_DISABLE:-}" && return 0
enabled "$DEBUG" && echo "Starting dnsmasq daemon..." enabled "$DEBUG" && echo "Starting dnsmasq daemon..."
[ -s "$DNSMASQ_PID" ] && pKill "$(<"$DNSMASQ_PID")" if readPidFile pid "$DNSMASQ_PID"; then
pKill "$pid"
fi
rm -f "$DNSMASQ_PID" rm -f "$DNSMASQ_PID"
if isNAT; then if isNAT; then
@@ -459,6 +500,11 @@ configureDNS() {
# Add DNS entry for container # Add DNS entry for container
arguments+=" --address=/host.lan/$gateway" arguments+=" --address=/host.lan/$gateway"
# Add DNS entry for the upstream gateway.
if isNAT && [ -n "$upstream" ]; then
arguments+=" --address=/system.lan/$upstream"
fi
# Avoid returning IPv6 records when the active network mode is IPv4-only. # Avoid returning IPv6 records when the active network mode is IPv4-only.
if isNAT || [ -z "$IP6" ]; then if isNAT || [ -z "$IP6" ]; then
arguments+=" --filter-AAAA" arguments+=" --filter-AAAA"
@@ -478,7 +524,7 @@ configureDNS() {
arguments=$(echo "$arguments" | sed 's/\t/ /g' | tr -s ' ' | sed 's/^ *//') arguments=$(echo "$arguments" | sed 's/\t/ /g' | tr -s ' ' | sed 's/^ *//')
enabled "$DEBUG" && printf "Dnsmasq arguments:\n\n%s\n\n" "${arguments// -/$'\n-'}" enabled "$DEBUG" && printf "Dnsmasq arguments:\n\n%s\n\n" "${arguments// -/$'\n-'}"
{ $DNSMASQ ${arguments:+ $arguments}; rc=$?; } || : { $DNSMASQ ${arguments:+ $arguments}; local rc=$?; } || :
if (( rc != 0 )); then if (( rc != 0 )); then
@@ -501,9 +547,9 @@ configureDNS() {
getHostPorts() { getHostPorts() {
local port="" ports=""
local num="" mode="${1:-tcp}"
local list="${HOST_PORTS// /}," local list="${HOST_PORTS// /},"
local port ports=""
local mode="${1:-tcp}"
for port in ${list//,/ }; do for port in ${list//,/ }; do
@@ -512,14 +558,14 @@ getHostPorts() {
case "$mode" in case "$mode" in
"tcp" ) "tcp" )
[[ "$port" == *"/udp" ]] && continue [[ "$port" == *"/udp" ]] && continue
num="${port%/tcp}" local num="${port%/tcp}"
;; ;;
"all" ) "all" )
if [[ "$port" == *"/udp" ]]; then if [[ "$port" == *"/udp" ]]; then
num="${port%/udp}" local num="${port%/udp}"
[ -n "$num" ] && ports+="$num/udp," [ -n "$num" ] && ports+="$num/udp,"
else else
num="${port%/tcp}" local num="${port%/tcp}"
[ -n "$num" ] && ports+="$num/tcp," [ -n "$num" ] && ports+="$num/tcp,"
fi fi
continue continue
@@ -545,16 +591,15 @@ getUserPorts() {
local defaults="22/tcp,5000/tcp,5001/tcp" local defaults="22/tcp,5000/tcp,5001/tcp"
local list="$defaults,${USER_PORTS// /}," local list="$defaults,${USER_PORTS// /},"
local num="" ports="" proto="" local ports=""
local userport="" hostport="" local userport hostport exclude
local exclude=""
exclude=$(getHostPorts "all") exclude=$(getHostPorts "all")
for userport in ${list//,/ }; do for userport in ${list//,/ }; do
proto="tcp" local proto="tcp"
num="$userport" local num="$userport"
if [[ "$userport" == *"/udp" ]]; then if [[ "$userport" == *"/udp" ]]; then
proto="udp" proto="udp"
@@ -593,7 +638,7 @@ getUserPorts() {
getSlirp() { getSlirp() {
local ip="$1" local ip="$1"
local args="" list="" local args="" list
list=$(getUserPorts) list=$(getUserPorts)
@@ -617,9 +662,9 @@ getSlirp() {
getPasst() { getPasst() {
local args="" list="" port="" local list port
local num="" tcp="" udp=""
local bind="$UPLINK" local bind="$UPLINK"
local tcp="" udp="" args=""
list=$(getUserPorts) list=$(getUserPorts)
@@ -629,12 +674,12 @@ getPasst() {
if [[ "$port" == *"/udp" ]]; then if [[ "$port" == *"/udp" ]]; then
num="${port%/udp}" local num="${port%/udp}"
[ -n "$num" ] && udp+="$num," [ -n "$num" ] && udp+="$num,"
elif [[ "$port" == *"/tcp" ]]; then elif [[ "$port" == *"/tcp" ]]; then
num="${port%/tcp}" local num="${port%/tcp}"
[ -n "$num" ] && tcp+="$num," [ -n "$num" ] && tcp+="$num,"
else else
@@ -665,8 +710,7 @@ getPasst() {
configureVTAP() { configureVTAP() {
local msg="" local msg dev
local rc
enabled "$DEBUG" && echo "Configuring MACVTAP networking..." enabled "$DEBUG" && echo "Configuring MACVTAP networking..."
@@ -678,7 +722,7 @@ configureVTAP() {
fi fi
# Create a macvtap network for the VM guest # Create a macvtap network for the VM guest
{ msg=$(ip link add link "$DEV" name "$TAP" address "$MAC" type macvtap mode bridge 2>&1); rc=$?; } || : { msg=$(ip link add link "$DEV" name "$TAP" address "$MAC" type macvtap mode bridge 2>&1); local rc=$?; } || :
case "$msg" in case "$msg" in
"RTNETLINK answers: File exists"* ) "RTNETLINK answers: File exists"* )
@@ -711,15 +755,35 @@ configureVTAP() {
sleep 2 sleep 2
done done
local TAP_NR TAP_PATH MAJOR MINOR local TAP_NR MAJOR MINOR
TAP_NR=$(</sys/class/net/"$TAP"/ifindex)
TAP_PATH="/dev/tap${TAP_NR}" if ! dev=$(cat /sys/devices/virtual/net/"$TAP"/tap*/dev); then
error "Failed to determine device numbers for MACVTAP interface \"$TAP\" !"
return 1
fi
IFS=: read -r MAJOR MINOR <<< "$dev"
if [[ ! "$MAJOR" =~ ^[0-9]+$ || ! "$MINOR" =~ ^[0-9]+$ ]]; then
error "Failed to parse device numbers for MACVTAP interface \"$TAP\" !"
return 1
fi
if (( MAJOR < 1 )); then
error "Cannot find: sys/devices/virtual/net/$TAP"
return 1
fi
if ! TAP_NR=$(<"/sys/class/net/$TAP/ifindex"); then
error "Failed to determine interface index of MACVTAP interface \"$TAP\" !"
return 1
fi
# Create dev file (there is no udev in container: need to be done manually) # Create dev file (there is no udev in container: need to be done manually)
IFS=: read -r MAJOR MINOR < <(cat /sys/devices/virtual/net/"$TAP"/tap*/dev) local TAP_PATH="/dev/tap${TAP_NR}"
(( MAJOR < 1)) && error "Cannot find: sys/devices/virtual/net/$TAP" && return 1
[[ ! -e "$TAP_PATH" && -e "/dev0/${TAP_PATH##*/}" ]] && ln -s "/dev0/${TAP_PATH##*/}" "$TAP_PATH" [[ ! -e "$TAP_PATH" && -e "/dev0/${TAP_PATH##*/}" ]] &&
ln -s "/dev0/${TAP_PATH##*/}" "$TAP_PATH"
if [[ ! -e "$TAP_PATH" ]]; then if [[ ! -e "$TAP_PATH" ]]; then
{ mknod "$TAP_PATH" c "$MAJOR" "$MINOR"; rc=$?; } || : { mknod "$TAP_PATH" c "$MAJOR" "$MINOR"; rc=$?; } || :
@@ -753,7 +817,7 @@ configureSlirp() {
ip=$(guestIP "$ip" 4) ip=$(guestIP "$ip" 4)
local gateway="${ip%.*}.1" local gateway="${ip%.*}.1"
local subnet="" local subnet
subnet=$(networkCIDR "$ip") || return 1 subnet=$(networkCIDR "$ip") || return 1
local ipv6="ipv6=off," local ipv6="ipv6=off,"
@@ -761,7 +825,7 @@ configureSlirp() {
NET_OPTS="-netdev user,id=hostnet0,ipv4=on,host=$gateway,net=$subnet,dhcpstart=$ip,${ipv6}hostname=$HOST" NET_OPTS="-netdev user,id=hostnet0,ipv4=on,host=$gateway,net=$subnet,dhcpstart=$ip,${ipv6}hostname=$HOST"
local forward="" local forward
forward=$(getSlirp "$ip") forward=$(getSlirp "$ip")
[ -n "$forward" ] && NET_OPTS+=",$forward" [ -n "$forward" ] && NET_OPTS+=",$forward"
@@ -818,7 +882,7 @@ configurePasst() {
# Pass an explicit MTU to passt. # Pass an explicit MTU to passt.
PASST_OPTS+=" -m $passt_mtu" PASST_OPTS+=" -m $passt_mtu"
local forward="" local forward
forward=$(getPasst) forward=$(getPasst)
[ -n "$forward" ] && PASST_OPTS+="$forward" [ -n "$forward" ] && PASST_OPTS+="$forward"
@@ -854,11 +918,10 @@ configurePasst() {
if ! "$PASST" ${PASST_OPTS:+$PASST_OPTS} >/dev/null 2>&1; then if ! "$PASST" ${PASST_OPTS:+$PASST_OPTS} >/dev/null 2>&1; then
local rc=0
rm -f "$log" rm -f "$log"
PASST_OPTS="${PASST_OPTS/ -q/}" PASST_OPTS="${PASST_OPTS/ -q/}"
{ "$PASST" ${PASST_OPTS:+$PASST_OPTS}; rc=$?; } || : { "$PASST" ${PASST_OPTS:+$PASST_OPTS}; local rc=$?; } || :
if (( rc != 0 )); then if (( rc != 0 )); then
[ -f "$log" ] && [ -s "$log" ] && cat "$log" [ -f "$log" ] && [ -s "$log" ] && cat "$log"
@@ -889,10 +952,10 @@ configurePasst() {
createBridge() { createBridge() {
local gateway="$1" local gateway="$1"
local rc msg="" local msg
# Create a bridge with a static IP for the VM guest # Create a bridge with a static IP for the VM guest
{ msg=$(ip link add dev "$BRIDGE" type bridge 2>&1); rc=$?; } || : { msg=$(ip link add dev "$BRIDGE" type bridge 2>&1); local rc=$?; } || :
if (( rc != 0 )); then if (( rc != 0 )); then
enabled "$ROOTLESS" && ! enabled "$DEBUG" && return 1 enabled "$ROOTLESS" && ! enabled "$DEBUG" && return 1
@@ -930,10 +993,10 @@ createBridge() {
createTap() { createTap() {
local tuntap="$1" local tuntap="$1"
local rc msg="" local msg
# Set tap to the bridge created # Set tap to the bridge created
{ msg=$(ip tuntap add dev "$TAP" mode tap 2>&1); rc=$?; } || : { msg=$(ip tuntap add dev "$TAP" mode tap 2>&1); local rc=$?; } || :
if (( rc != 0 )); then if (( rc != 0 )); then
enabled "$ROOTLESS" && ! enabled "$DEBUG" && return 1 enabled "$ROOTLESS" && ! enabled "$DEBUG" && return 1
@@ -976,7 +1039,7 @@ hasTable() {
getTablesBackend() { getTablesBackend() {
local version="" local version
version=$(iptables --version 2>/dev/null || true) version=$(iptables --version 2>/dev/null || true)
case "$version" in case "$version" in
@@ -989,7 +1052,7 @@ getTablesBackend() {
setTables() { setTables() {
local mode="$1" local mode="$1"
local path="" local path
path=$(command -v "iptables-$mode" 2>/dev/null || true) path=$(command -v "iptables-$mode" 2>/dev/null || true)
[ -z "$path" ] && return 1 [ -z "$path" ] && return 1
@@ -1003,7 +1066,7 @@ showRules() {
local chain="$2" local chain="$2"
local label="$3" local label="$3"
local rule_tag="$4" local rule_tag="$4"
local rules="" local rules
local own_rule="--comment[[:space:]]+\"?$rule_tag\"?([[:space:]]|\$)" local own_rule="--comment[[:space:]]+\"?$rule_tag\"?([[:space:]]|\$)"
enabled "$DEBUG" || return 0 enabled "$DEBUG" || return 0
@@ -1022,7 +1085,7 @@ showRules() {
checkExistingTables() { checkExistingTables() {
local msg="" rules="" conflicts="" local rules conflicts
local rule_tag="QEMU_DNAT" local rule_tag="QEMU_DNAT"
local own_rule="--comment[[:space:]]+\"?$rule_tag\"?([[:space:]]|\$)" local own_rule="--comment[[:space:]]+\"?$rule_tag\"?([[:space:]]|\$)"
@@ -1040,7 +1103,7 @@ checkExistingTables() {
<<< "$rules" || true) <<< "$rules" || true)
if [ -n "$conflicts" ]; then if [ -n "$conflicts" ]; then
msg="your existing NAT rules may take precedence over VM port forwarding" local msg="your existing NAT rules may take precedence over VM port forwarding"
if enabled "$DEBUG"; then if enabled "$DEBUG"; then
warn "${msg}." warn "${msg}."
@@ -1060,7 +1123,7 @@ checkExistingTables() {
<<< "$rules" || true) <<< "$rules" || true)
if [ -n "$conflicts" ]; then if [ -n "$conflicts" ]; then
msg="your existing firewall rules may block traffic forwarded to or from the VM" local msg="your existing firewall rules may block traffic forwarded to or from the VM"
if enabled "$DEBUG"; then if enabled "$DEBUG"; then
warn "${msg}." warn "${msg}."
@@ -1088,13 +1151,13 @@ runTableRule() {
local silent="$1" local silent="$1"
local result="$2" local result="$2"
local rc msg="" local msg
shift 2 shift 2
printf -v "$result" '%s' "" printf -v "$result" '%s' ""
{ msg=$("$@" 2>&1); rc=$?; } || : { msg=$("$@" 2>&1); local rc=$?; } || :
(( rc == 0 )) && return 0 (( rc == 0 )) && return 0
printf -v "$result" '%s' "$msg" printf -v "$result" '%s' "$msg"
@@ -1154,8 +1217,8 @@ applyTables() {
local ip="$1" local ip="$1"
local subnet="$2" local subnet="$2"
local silent="${3:-N}" local silent="${3:-N}"
local exclude="" port="" local exclude port
local table_error="" local table_error
local dnat_chain="QEMU_DNAT" local dnat_chain="QEMU_DNAT"
local rule_tag="$dnat_chain" local rule_tag="$dnat_chain"
@@ -1283,8 +1346,8 @@ applyTables() {
clearTables() { clearTables() {
local table="" line="" chain="" local line
local rules="" remaining="" message="" local rules remaining message
local dnat_chain="QEMU_DNAT" local dnat_chain="QEMU_DNAT"
local rule_tag="$dnat_chain" local rule_tag="$dnat_chain"
local own_rule="--comment[[:space:]]+\"?$rule_tag\"?([[:space:]]|\$)" local own_rule="--comment[[:space:]]+\"?$rule_tag\"?([[:space:]]|\$)"
@@ -1309,15 +1372,15 @@ clearTables() {
while IFS= read -r line; do while IFS= read -r line; do
case "$line" in case "$line" in
\*nat ) table="nat" ;; \*nat ) local table="nat" ;;
\*filter ) table="filter" ;; \*filter ) local table="filter" ;;
\*mangle ) table="mangle" ;; \*mangle ) local table="mangle" ;;
\*raw ) table="raw" ;; \*raw ) local table="raw" ;;
esac esac
if [[ "$line" == -A* ]] && [[ "$line" =~ $own_rule ]]; then if [[ "$line" == -A* ]] && [[ "$line" =~ $own_rule ]]; then
chain="${line#-A }" local chain="${line#-A }"
chain="${chain%% *}" chain="${chain%% *}"
# Rules inside this chain are removed together by the flush below. # Rules inside this chain are removed together by the flush below.
@@ -1383,7 +1446,7 @@ clearTables() {
hasTaggedRules() { hasTaggedRules() {
local save="$1" local save="$1"
local rules="" local rules
local dnat_chain="QEMU_DNAT" local dnat_chain="QEMU_DNAT"
local rule_tag="$dnat_chain" local rule_tag="$dnat_chain"
local own_rule="--comment[[:space:]]+\"?$rule_tag\"?([[:space:]]|\$)" local own_rule="--comment[[:space:]]+\"?$rule_tag\"?([[:space:]]|\$)"
@@ -1405,9 +1468,8 @@ configureTables() {
local ip="$1" local ip="$1"
local subnet="$2" local subnet="$2"
local preferred="" local preferred
local alternate="" local alternate_save
local alternate_save=""
local preferred_clean="N" local preferred_clean="N"
local alternate_dirty="N" local alternate_dirty="N"
@@ -1418,8 +1480,8 @@ configureTables() {
} }
case "$preferred" in case "$preferred" in
"nft" ) alternate="legacy" ;; "nft" ) local alternate="legacy" ;;
"legacy" ) alternate="nft" ;; "legacy" ) local alternate="nft" ;;
* ) * )
enabled "$ROOTLESS" && ! enabled "$DEBUG" && return 1 enabled "$ROOTLESS" && ! enabled "$DEBUG" && return 1
warn "unsupported IP tables backend: $preferred" warn "unsupported IP tables backend: $preferred"
@@ -1605,10 +1667,46 @@ configureTables() {
return 1 return 1
} }
addUpstream() {
local upstream="$1"
local table_error
local rule_tag="QEMU_DNAT"
[ -n "$upstream" ] || return 1
[ -n "$GATEWAY" ] || return 1
if ! ip address add "$upstream/32" dev "$BRIDGE"; then
if ! enabled "$ROOTLESS" || enabled "$DEBUG"; then
warn "failed to add the system.lan address; access through that name will be unavailable."
fi
return 1
fi
if ! runTableRule "Y" table_error \
iptables -t nat -A PREROUTING \
-i "$BRIDGE" \
-d "$upstream" \
-m comment --comment "$rule_tag" \
-j DNAT --to-destination "$GATEWAY"; then
ip address del "$upstream/32" dev "$BRIDGE" > /dev/null 2>&1 || :
if ! enabled "$ROOTLESS" || enabled "$DEBUG"; then
[ -n "$table_error" ] && echo "$table_error" >&2
warn "failed to configure system.lan forwarding; access through that name will be unavailable."
fi
return 1
fi
return 0
}
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 ip subnet msg="" forwarding="" local ip subnet upstream="" forwarding=""
enabled "$DEBUG" && echo "Configuring NAT networking..." enabled "$DEBUG" && echo "Configuring NAT networking..."
@@ -1616,7 +1714,8 @@ configureNAT() {
if [ ! -c /dev/net/tun ]; then if [ ! -c /dev/net/tun ]; then
[ ! -d /dev/net ] && mkdir -m 755 /dev/net > /dev/null 2>&1 || : [ ! -d /dev/net ] && mkdir -m 755 /dev/net > /dev/null 2>&1 || :
{ msg=$(mknod /dev/net/tun c 10 200 2>&1); rc=$?; } || : local msg
{ msg=$(mknod /dev/net/tun c 10 200 2>&1); local rc=$?; } || :
if (( rc == 0 )); then if (( rc == 0 )); then
chmod 666 /dev/net/tun chmod 666 /dev/net/tun
@@ -1635,7 +1734,7 @@ configureNAT() {
forwarding=$(< /proc/sys/net/ipv4/ip_forward) forwarding=$(< /proc/sys/net/ipv4/ip_forward)
if [[ "$forwarding" != "1" ]]; then 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; local rc=$?; } || :
forwarding="" forwarding=""
[ -r /proc/sys/net/ipv4/ip_forward ] && [ -r /proc/sys/net/ipv4/ip_forward ] &&
@@ -1657,11 +1756,15 @@ configureNAT() {
local gateway="${ip%.*}.1" local gateway="${ip%.*}.1"
subnet=$(networkCIDR "$ip") || return 1 subnet=$(networkCIDR "$ip") || return 1
if [ -n "$GATEWAY" ]; then
upstream=$(upstreamIP "$subnet" "$ip" "$gateway") || upstream=""
fi
if subnetInUse "$subnet"; then if subnetInUse "$subnet"; then
error "VM subnet $subnet conflicts with an existing route inside the container." error "VM subnet $subnet conflicts with an existing route inside the container."
return 1 return 1
else else
rc=$? local rc=$?
(( rc == 1 )) || return 1 (( rc == 1 )) || return 1
fi fi
@@ -1675,16 +1778,20 @@ configureNAT() {
configureTables "$ip" "$subnet" || return 1 configureTables "$ip" "$subnet" || return 1
if [ -n "$upstream" ] && ! addUpstream "$upstream"; then
upstream=""
fi
NET_OPTS="-netdev tap,id=hostnet0,ifname=$TAP" NET_OPTS="-netdev tap,id=hostnet0,ifname=$TAP"
if [ -c /dev/vhost-net ]; then if [ -c /dev/vhost-net ]; then
{ exec 40>>/dev/vhost-net; rc=$?; } 2>/dev/null || : { exec 40>>/dev/vhost-net; local rc=$?; } 2>/dev/null || :
(( rc == 0 )) && NET_OPTS+=",vhost=on,vhostfd=40" (( rc == 0 )) && NET_OPTS+=",vhost=on,vhostfd=40"
fi fi
NET_OPTS+=",script=no,downscript=no" NET_OPTS+=",script=no,downscript=no"
configureDNS "$BRIDGE" "$ip" "$MAC" "$HOST" "$MASK" "$gateway" || return 1 configureDNS "$BRIDGE" "$ip" "$MAC" "$HOST" "$MASK" "$gateway" "$upstream" || return 1
IP="$ip" IP="$ip"
return 0 return 0
@@ -1740,7 +1847,7 @@ closeNetwork() {
checkOS() { checkOS() {
local iface="macvlan" local iface="macvlan"
local os="" kernel="" local os="" kernel
kernel=$(uname -a) kernel=$(uname -a)
@@ -1891,14 +1998,12 @@ configureMTU() {
configureMAC() { configureMAC() {
local container="" local container
local file=""
container=$(containerID) container=$(containerID)
if [ -z "$MAC" ]; then if [ -z "$MAC" ]; then
file="$STORAGE/dsm.mac" local file="$STORAGE/dsm.mac"
if [ -s "$file" ]; then if [ -s "$file" ]; then
if ! MAC=$(readFile "$file"); then if ! MAC=$(readFile "$file"); then
@@ -1941,7 +2046,7 @@ configureMAC() {
showHostInfo() { showHostInfo() {
local mtu="" host="" uplink="" prefix="" local mtu host uplink prefix
prefix=$(ip -4 -o address show dev "$DEV" scope global 2>/dev/null | prefix=$(ip -4 -o address show dev "$DEV" scope global 2>/dev/null |
awk -v ip="$UPLINK" ' awk -v ip="$UPLINK" '
@@ -2028,10 +2133,10 @@ showGuestInfo() {
local mode="${NETWORK,,}" local mode="${NETWORK,,}"
if isNAT; then if enabled "$DHCP"; then
mode="NAT"
elif enabled "$DHCP"; then
mode="DHCP" mode="DHCP"
elif isNAT; then
mode="NAT"
elif isUserMode; then elif isUserMode; then
mode="User ($mode)" mode="User ($mode)"
elif [ -z "$mode" ]; then elif [ -z "$mode" ]; then
+11 -21
View File
@@ -67,16 +67,8 @@ displayReason() {
readQemuPid() { readQemuPid() {
local -n _pid="$1" readPidFile "$1" "$QEMU_START_PID" && return 0
local file readPidFile "$1" "$QEMU_PID"
for file in "$QEMU_START_PID" "$QEMU_PID"; do
if [ -s "$file" ] && read -r _pid < "$file"; then
return 0
fi
done
return 1
} }
qemuPidFile() { qemuPidFile() {
@@ -92,7 +84,7 @@ qemuPidFile() {
waitQemuExit() { waitQemuExit() {
local timeout="${1:-10}" local timeout="${1:-10}"
local file="" local file
qemuPidFile file qemuPidFile file
waitPidFile "$file" "$timeout" waitPidFile "$file" "$timeout"
@@ -100,23 +92,21 @@ waitQemuExit() {
waitQemuPid() { waitQemuPid() {
local -n _pid="$1" local cnt=0
local cnt=0 value=""
while ! readQemuPid value; do while ! readQemuPid "$1"; do
sleep 0.02 sleep 0.02
cnt=$((cnt + 1)) cnt=$((cnt + 1))
(( cnt >= 50 )) && return 1 (( cnt >= 50 )) && return 1
done done
_pid="$value"
return 0 return 0
} }
forceKillQemu() { forceKillQemu() {
local reason="$1" local reason="$1"
local pid="" display local pid display
! readQemuPid pid && return 0 ! readQemuPid pid && return 0
! isAlive "$pid" && return 0 ! isAlive "$pid" && return 0
@@ -145,7 +135,7 @@ cleanupHelpers() {
startConsole() { startConsole() {
local output="${1:-/dev/tty}" local output="${1:-/dev/tty}"
local cnt=0 pid="" local cnt=0
rm -f -- "$CONSOLE_SOCKET" "$CONSOLE_PID" rm -f -- "$CONSOLE_SOCKET" "$CONSOLE_PID"
@@ -159,7 +149,7 @@ startConsole() {
exec nc -lU "$CONSOLE_SOCKET" </dev/tty >"$output" exec nc -lU "$CONSOLE_SOCKET" </dev/tty >"$output"
) & ) &
pid=$! local pid="$!"
echo "$pid" > "$CONSOLE_PID" echo "$pid" > "$CONSOLE_PID"
while [ ! -S "$CONSOLE_SOCKET" ]; do while [ ! -S "$CONSOLE_SOCKET" ]; do
@@ -246,14 +236,14 @@ finish() {
sendGuestShutdown() { sendGuestShutdown() {
local pid="$1" local pid="$1"
local response url local response
# Don't send the powerdown signal because vDSM ignores ACPI signals # Don't send the powerdown signal because vDSM ignores ACPI signals
# nc -q 1 -w 1 -U "$QEMU_DIR/monitor.sock" &> /dev/null <<<'system_powerdown' || : # nc -q 1 -w 1 -U "$QEMU_DIR/monitor.sock" &> /dev/null <<<'system_powerdown' || :
# Send shutdown command to guest agent via serial port # Send shutdown command to guest agent via serial port
API_TIMEOUT=$(strip "$API_TIMEOUT") API_TIMEOUT=$(strip "$API_TIMEOUT")
url="http://localhost/read?command=$API_CMD&timeout=$API_TIMEOUT" local url="http://localhost/read?command=$API_CMD&timeout=$API_TIMEOUT"
response=$(curl --unix-socket "$HOST_API_SOCKET" -sk -m "$(( API_TIMEOUT+2 ))" -S "$url" 2>&1) response=$(curl --unix-socket "$HOST_API_SOCKET" -sk -m "$(( API_TIMEOUT+2 ))" -S "$url" 2>&1)
if [[ "$response" =~ "\"success\"" ]]; then if [[ "$response" =~ "\"success\"" ]]; then
@@ -338,7 +328,7 @@ waitForShutdown() {
graceful_shutdown() { graceful_shutdown() {
local sig="$1" local sig="$1"
local pid="" code=0 local pid code
[[ $BASHPID != "$TRAP_PID" ]] && return [[ $BASHPID != "$TRAP_PID" ]] && return
+27 -13
View File
@@ -9,6 +9,7 @@ cd /run
. utils.sh # Load functions . utils.sh # Load functions
info () { printf "%b%s%b" "\E[1;34m \E[1;36m" "$1" "\E[0m\n" >&2; } info () { printf "%b%s%b" "\E[1;34m \E[1;36m" "$1" "\E[0m\n" >&2; }
warn () { printf "%b%s%b" "\E[1;33m " "WARNING: $1" "\E[0m\n" >&2; }
error () { printf "%b%s%b" "\E[1;31m " "ERROR: $1" "\E[0m\n" >&2; } error () { printf "%b%s%b" "\E[1;31m " "ERROR: $1" "\E[0m\n" >&2; }
disabled "$NETWORK" && exit 0 disabled "$NETWORK" && exit 0
@@ -36,9 +37,7 @@ exitIfShuttingDown() {
queryGuest() { queryGuest() {
local rc { json=$(curl --unix-socket "$socket" -m 20 -sk "$url"); local rc=$?; } || :
{ json=$(curl --unix-socket "$socket" -m 20 -sk "$url"); rc=$?; } || :
exitIfShuttingDown exitIfShuttingDown
@@ -54,9 +53,8 @@ readJsonField() {
local query="$1" local query="$1"
local result local result
local rc
{ result=$(jq -r "$query" <<< "$json"); rc=$?; } || : { result=$(jq -r "$query" <<< "$json"); local rc=$?; } || :
if (( rc != 0 )); then if (( rc != 0 )); then
error "$jq_err $rc ( $json )" error "$jq_err $rc ( $json )"
@@ -74,12 +72,12 @@ readJsonField() {
readGuestStatus() { readGuestStatus() {
local result msg rc local result msg
result=$(readJsonField '.status') || return 1 result=$(readJsonField '.status') || return 1
if [[ "$result" != "success" ]]; then if [[ "$result" != "success" ]]; then
{ msg=$(jq -r '.message // empty' <<< "$json"); rc=$?; } || : { msg=$(jq -r '.message // empty' <<< "$json"); local rc=$?; } || :
if (( rc != 0 )); then if (( rc != 0 )); then
error "$jq_err $rc ( $json )" error "$jq_err $rc ( $json )"
@@ -140,14 +138,29 @@ pollGuestLocation() {
return 0 return 0
} }
checkAddressConflict() {
local guest_ip="${location%:*}"
local container_ip=""
[ -s "$address" ] && container_ip=$(<"$address")
[ -z "$container_ip" ] && return 0
[[ "$guest_ip" != "$container_ip" ]] && return 0
warn "DSM is using the same IP as the container, this will cause connectivity issues."
warn "change the container's macvlan IP or assign DSM a different address in your router."
return 0
}
writeDhcpPage() { writeDhcpPage() {
local title body script html local html
msg="http://$location" msg="http://$location"
title="<title>Virtual DSM</title>" local title="<title>Virtual DSM</title>"
body="The location of DSM is <a href='http://$location'>http://$location</a>" local body="The location of DSM is <a href='http://$location'>http://$location</a>"
script="<script>setTimeout(function(){ window.location.assign('http://$location'); }, 3000);</script>" local script="<script>setTimeout(function(){ window.location.assign('http://$location'); }, 3000);</script>"
html=$(<"$template") html=$(<"$template")
html="${html/\[1\]/$title}" html="${html/\[1\]/$title}"
@@ -164,11 +177,11 @@ writeDhcpPage() {
buildStaticMessage() { buildStaticMessage() {
local nic ip port local nic ip
nic=$(<"$driver") nic=$(<"$driver")
ip=$(<"$address") ip=$(<"$address")
port="${location##*:}" local port="${location##*:}"
if [[ "${nic,,}" != "macvlan" ]]; then if [[ "${nic,,}" != "macvlan" ]]; then
msg="port $port" msg="port $port"
@@ -196,6 +209,7 @@ exitIfShuttingDown
location=$(<"$file") location=$(<"$file")
if enabled "$DHCP"; then if enabled "$DHCP"; then
checkAddressConflict
writeDhcpPage writeDhcpPage
else else
buildStaticMessage buildStaticMessage
+82 -7
View File
@@ -2,6 +2,7 @@
set -Eeuo pipefail set -Eeuo pipefail
info="/run/shm/msg.html" info="/run/shm/msg.html"
info_tmp="${info}.${BASHPID}.tmp"
escape() { escape() {
@@ -17,6 +18,23 @@ escape() {
return 0 return 0
} }
writeInfo() {
local content="$1"
if ! printf '%s\n' "$content" > "$info_tmp"; then
rm -f -- "$info_tmp"
return 1
fi
if ! mv -f -- "$info_tmp" "$info"; then
rm -f -- "$info_tmp"
return 1
fi
return 0
}
getBytes() { getBytes() {
local path="$1" local path="$1"
@@ -48,6 +66,24 @@ getBytes() {
return 0 return 0
} }
getStatus() {
local file="$1"
local bytes total extra=""
[ -r "$file" ] || return 1
read -r bytes total extra < "$file" || return 1
if [[ ! "$bytes" =~ ^[0-9]+$ ||
! "$total" =~ ^[0-9]+$ ||
-n "$extra" ]]; then
return 1
fi
printf '%s %s\n' "$bytes" "$total"
return 0
}
formatSize() { formatSize() {
local bytes="$1" local bytes="$1"
@@ -117,7 +153,19 @@ printSizeProgress() {
return 0 return 0
} }
finishLogProgress() { stopProgress() {
if [ -z "$status_file" ]; then
exit 0
fi
stopping="Y"
return 0
}
finishProgress() {
rm -f -- "$info_tmp"
if [[ "$output" == "log" && "$printed" == "Y" ]]; then if [[ "$output" == "log" && "$printed" == "Y" ]]; then
printf '\n' printf '\n'
@@ -132,6 +180,7 @@ body=$(escape "$3")
output="${4:-}" output="${4:-}"
step_bytes="${5:-536870912}" step_bytes="${5:-536870912}"
mode="${6:-apparent}" mode="${6:-apparent}"
status_file="${7:-}"
if [[ -n "$total" && ! "$total" =~ ^(0|[1-9][0-9]*)$ ]]; then if [[ -n "$total" && ! "$total" =~ ^(0|[1-9][0-9]*)$ ]]; then
printf 'Invalid total size: %s\n' "$total" >&2 printf 'Invalid total size: %s\n' "$total" >&2
@@ -163,13 +212,15 @@ printed="N"
next_percent=10 next_percent=10
next_bytes="$step_bytes" next_bytes="$step_bytes"
log_mode="percent" log_mode="percent"
stopping="N"
if [ -z "$total" ] || [[ "$total" == "0" ]]; then if [ -z "$total" ] || [[ "$total" == "0" ]]; then
log_mode="size" log_mode="size"
fi fi
trap finishLogProgress EXIT trap finishProgress EXIT
trap 'exit 0' HUP INT QUIT TERM trap 'exit 0' HUP INT QUIT
trap stopProgress TERM
if [[ "$body" == *"..." ]]; then if [[ "$body" == *"..." ]]; then
body="<p class=\"loading\">${body::-3}</p>" body="<p class=\"loading\">${body::-3}</p>"
@@ -177,13 +228,34 @@ fi
while true; do while true; do
final_pass="${stopping:-}"
bytes=$(getBytes "$path" "$mode") bytes=$(getBytes "$path" "$mode")
effective_total="$total"
if [ -n "$status_file" ] && status=$(getStatus "$status_file"); then
read -r status_bytes status_total <<< "$status"
bytes="$status_bytes"
if (( status_total > 0 )); then
effective_total="$status_total"
fi
fi
# A real total may become available shortly after aria2 starts.
if [[ "$log_mode" == "size" &&
"$printed" == "N" &&
-n "$effective_total" &&
"$effective_total" != "0" ]]; then
log_mode="percent"
fi
if (( bytes > 4096 )); then if (( bytes > 4096 )); then
write_html="Y" write_html="Y"
if [ -z "$total" ] || [[ "$total" == "0" ]] || (( bytes > total )); then if [ -z "$effective_total" ] ||
[[ "$effective_total" == "0" ]] ||
(( bytes > effective_total )); then
size=$(formatSize "$bytes") size=$(formatSize "$bytes")
if [[ "$output" == "log" ]]; then if [[ "$output" == "log" ]]; then
@@ -197,7 +269,7 @@ while true; do
fi fi
else else
# Truncate to one decimal so progress is never reported early. # Truncate to one decimal so progress is never reported early.
progress=$((bytes * 1000 / total)) progress=$((bytes * 1000 / effective_total))
(( progress > 1000 )) && progress=1000 (( progress > 1000 )) && progress=1000
percent=$((progress / 10)) percent=$((progress / 10))
@@ -219,9 +291,12 @@ while true; do
fi fi
if [[ "$write_html" == "Y" ]]; then if [[ "$write_html" == "Y" ]]; then
printf '%s\n' "${body//(\[P\])/($size)}" > "$info" writeInfo "${body//(\[P\])/($size)}"
fi fi
fi fi
sleep 1 & wait $! [[ "$final_pass" == "Y" ]] && break
sleep 1 &
wait $! || :
done done
+8 -15
View File
@@ -41,7 +41,7 @@ detectEngine() {
detectRootless() { detectRootless() {
local uid_map="" local uid_map
uid_map=$(awk '{$1=$1; print}' /proc/self/uid_map 2>/dev/null || true) uid_map=$(awk '{$1=$1; print}' /proc/self/uid_map 2>/dev/null || true)
@@ -58,8 +58,6 @@ checkPrivileged() {
local cap_bnd local cap_bnd
local last_cap local last_cap
local max_cap
# Get the capability bounding set # Get the capability bounding set
cap_bnd=$(grep '^CapBnd:' /proc/$$/status | awk '{print $2}') cap_bnd=$(grep '^CapBnd:' /proc/$$/status | awk '{print $2}')
cap_bnd=$(printf "%d" "0x${cap_bnd}") cap_bnd=$(printf "%d" "0x${cap_bnd}")
@@ -68,7 +66,7 @@ checkPrivileged() {
last_cap=$(cat /proc/sys/kernel/cap_last_cap) last_cap=$(cat /proc/sys/kernel/cap_last_cap)
# Calculate the maximum capability value # Calculate the maximum capability value
max_cap=$(((1 << (last_cap + 1)) - 1)) local max_cap=$(((1 << (last_cap + 1)) - 1))
if [ "$cap_bnd" -eq "$max_cap" ]; then if [ "$cap_bnd" -eq "$max_cap" ]; then
PRIVILEGED="Y" PRIVILEGED="Y"
@@ -144,9 +142,7 @@ finiteMemoryLimit() {
local limit="$1" local limit="$1"
local sentinel="4611686018427387904" local sentinel="4611686018427387904"
local i=0 local i
local left=""
local right=""
[[ "$limit" =~ ^[0-9]+$ ]] || return 1 [[ "$limit" =~ ^[0-9]+$ ]] || return 1
@@ -154,8 +150,8 @@ finiteMemoryLimit() {
(( ${#limit} > ${#sentinel} )) && return 1 (( ${#limit} > ${#sentinel} )) && return 1
for (( i=0; i<${#sentinel}; i++ )); do for (( i=0; i<${#sentinel}; i++ )); do
left="${limit:i:1}" local left="${limit:i:1}"
right="${sentinel:i:1}" local right="${sentinel:i:1}"
(( left < right )) && return 0 (( left < right )) && return 0
(( left > right )) && return 1 (( left > right )) && return 1
@@ -166,11 +162,8 @@ finiteMemoryLimit() {
getMemoryInfo() { getMemoryInfo() {
local host_total="" local limit="" current=""
local host_avail="" local host_total host_avail
local limit=""
local current=""
local available=""
host_total=$(free -b | awk '/^Mem:/ {print $2; exit}') host_total=$(free -b | awk '/^Mem:/ {print $2; exit}')
host_avail=$(free -b | awk '/^Mem:/ {print $7; exit}') host_avail=$(free -b | awk '/^Mem:/ {print $7; exit}')
@@ -189,7 +182,7 @@ getMemoryInfo() {
if finiteMemoryLimit "$limit" && [[ "$current" =~ ^[0-9]+$ ]]; then if finiteMemoryLimit "$limit" && [[ "$current" =~ ^[0-9]+$ ]]; then
(( limit < RAM_TOTAL )) && RAM_TOTAL="$limit" (( limit < RAM_TOTAL )) && RAM_TOTAL="$limit"
available=$(( limit - current )) local available=$(( limit - current ))
(( available < 0 )) && available=0 (( available < 0 )) && available=0
(( available < RAM_AVAIL )) && RAM_AVAIL="$available" (( available < RAM_AVAIL )) && RAM_AVAIL="$available"
fi fi
+2 -2
View File
@@ -73,7 +73,7 @@ startHostBinary() {
pid=$! pid=$!
fi fi
echo "$pid" > "$HOST_PID" printf '%s\n' "$pid" > "$HOST_PID"
return 0 return 0
} }
@@ -86,7 +86,7 @@ waitForSocket() {
while [ ! -S "$socket" ]; do while [ ! -S "$socket" ]; do
if ! read -r pid < "$HOST_PID" || ! isAlive "$pid"; then if ! readPidFile pid "$HOST_PID" || ! isAlive "$pid"; then
error "qemu-host exited unexpectedly!" error "qemu-host exited unexpectedly!"
exit "$exit_code" exit "$exit_code"
fi fi
+68 -15
View File
@@ -15,24 +15,35 @@ WSD_PID="$QEMU_DIR/websocketd.pid"
prepareWebFiles() { prepareWebFiles() {
cp -r /var/www/* "$QEMU_DIR" || return 1 cp -r /var/www/* "$QEMU_DIR" || return 1
rm -f "$WSD_PID" "$WEB_PID" "$WSD_LOG" || return 1 rm -f -- "$WSD_PID" "$WEB_PID" "$WSD_LOG" || 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 if ! sed -i \
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 -e "s|listen 5000 default_server;|listen $WEB_PORT default_server;|g" \
-e "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; then
error "Failed to configure webserver ports!"
return 1
fi
return 0 return 0
} }
configureIpv6Listen() { configureIpv6Listen() {
# shellcheck disable=SC2143
if [ -f /proc/net/if_inet6 ] && [[ "$(cat /proc/sys/net/ipv6/conf/all/disable_ipv6 2>/dev/null)" != "1" ]]; 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
if ! sed -i \
"s/listen $WEB_PORT default_server;/listen [::]:$WEB_PORT default_server ipv6only=off;/g" \
/etc/nginx/sites-enabled/web.conf; then
error "Failed to configure IPv6 webserver listener!"
return 1
fi
fi fi
return 0 return 0
@@ -49,6 +60,22 @@ configureWebServer() {
return 0 return 0
} }
stopWebServer() {
local pid
if readPidFile pid "$WEB_PID"; then
pKill "$pid" 2
if isAlive "$pid"; then
kill -9 -- "$pid" 2>/dev/null || :
fi
fi
rm -f -- "$WEB_PID"
return 0
}
startWebServer() { startWebServer() {
# Start webserver # Start webserver
@@ -57,6 +84,22 @@ startWebServer() {
return 0 return 0
} }
stopWebsocketServer() {
local pid
if readPidFile pid "$WSD_PID"; then
pKill "$pid" 2
if isAlive "$pid"; then
kill -9 -- "$pid" 2>/dev/null || :
fi
fi
rm -f -- "$WSD_PID"
return 0
}
startWebsocketServer() { startWebsocketServer() {
# Start websocket server # Start websocket server
@@ -70,17 +113,23 @@ startWebsocketServer() {
if ! echo "$pid" > "$WSD_PID"; then if ! echo "$pid" > "$WSD_PID"; then
kill "$pid" 2>/dev/null || : kill "$pid" 2>/dev/null || :
rm -f -- "$WSD_PID"
return 1 return 1
fi fi
sleep 0.1 local i
for (( i = 1; i <= 5; i++ )); do
if ! isAlive "$pid"; then if ! isAlive "$pid"; then
rm -f "$WSD_PID" rm -f -- "$WSD_PID"
[ -s "$WSD_LOG" ] && cat "$WSD_LOG" >&2 [ -s "$WSD_LOG" ] && cat "$WSD_LOG" >&2
error "Failed to start websocket server!" error "Failed to start websocket server!"
return 1 return 1
fi fi
sleep 0.1
done
return 0 return 0
} }
@@ -93,7 +142,11 @@ disabled "${WEB:-}" && return 0
configureWebServer configureWebServer
startWebServer if startWebServer && startWebsocketServer; then
startWebsocketServer return 0
fi
return 0 stopWebsocketServer || :
stopWebServer || :
return 1
+18 -5
View File
@@ -3,6 +3,8 @@ set -Eeuo pipefail
lastmsg="" lastmsg=""
path="/run/shm/msg.html" path="/run/shm/msg.html"
dir=$(dirname -- "$path")
name=$(basename -- "$path")
refresh() { refresh() {
@@ -17,15 +19,26 @@ refresh() {
lastmsg="$msg" lastmsg="$msg"
echo "s: $msg" echo "s: $msg"
return 0 return 0
} }
refresh refresh
inotifywait -m "$path" | inotifywait \
while read -r fp event fn; do -m -q \
-e close_write,moved_to,delete \
--format '%e %f' \
"$dir" |
while read -r event file; do
[[ "$file" == "$name" ]] || continue
case "${event,,}" in case "${event,,}" in
"modify"* ) refresh ;; "delete"* )
"delete_self" ) echo "c: vnc" ;; echo "c: vnc" ;;
esac "close_write"* | "moved_to"* )
refresh ;;
esac
done done
+47 -18
View File
@@ -7,6 +7,24 @@ info () { printf "%b%s%b" "\E[1;34m \E[1;36m" "${1:-}" "\E[0m\n"; }
error () { printf "%b%s%b" "\E[1;31m " "ERROR: ${1:-}" "\E[0m\n" >&2; } error () { printf "%b%s%b" "\E[1;31m " "ERROR: ${1:-}" "\E[0m\n" >&2; }
warn () { printf "%b%s%b" "\E[1;31m " "Warning: ${1:-}" "\E[0m\n" >&2; } warn () { printf "%b%s%b" "\E[1;31m " "Warning: ${1:-}" "\E[0m\n" >&2; }
readPidFile() {
local -n _pid="$1"
_pid=""
if ! _pid=$(cat -- "$2" 2>/dev/null); then
_pid=""
return 1
fi
if [[ ! "$_pid" =~ ^[1-9][0-9]*$ ]]; then
_pid=""
return 1
fi
return 0
}
hasFlag() { hasFlag() {
# Match a whitespace-delimited token in /proc/cpuinfo # Match a whitespace-delimited token in /proc/cpuinfo
@@ -133,14 +151,11 @@ waitPid() {
waitPidFile() { waitPidFile() {
local i=0 local i=0 pid
local pid=""
local file="$1" local file="$1"
local timeout="${2:-10}" local timeout="${2:-10}"
[ ! -s "$file" ] && return 0 ! readPidFile pid "$file" && return 0
! read -r pid <"$file" && return 0
[ -z "$pid" ] && return 0
while [ -s "$file" ] && isAlive "$pid"; do while [ -s "$file" ] && isAlive "$pid"; do
sleep 0.2 sleep 0.2
@@ -201,12 +216,10 @@ fKill() {
sKill() { sKill() {
local pid="" local pid
local file="$1" local file="$1"
[ ! -s "$file" ] && return 0 ! readPidFile pid "$file" && return 0
! read -r pid <"$file" && return 0
[ -z "$pid" ] && return 0
if isAlive "$pid"; then if isAlive "$pid"; then
{ kill -15 -- "$pid" || :; } 2>/dev/null { kill -15 -- "$pid" || :; } 2>/dev/null
@@ -300,6 +313,25 @@ writeFile() {
return 0 return 0
} }
writeAtomic() {
local path="$1"
local content="$2"
local tmp="${path}.${BASHPID}.tmp"
if ! printf '%s\n' "$content" > "$tmp"; then
rm -f -- "$tmp"
return 1
fi
if ! mv -f -- "$tmp" "$path"; then
rm -f -- "$tmp"
return 1
fi
return 0
}
readFile() { readFile() {
local path="$1" local path="$1"
@@ -362,8 +394,7 @@ restoreState() {
escape () { escape () {
local s local s=${1//&/\&amp;}
s=${1//&/\&amp;}
s=${s//</\&lt;} s=${s//</\&lt;}
s=${s//>/\&gt;} s=${s//>/\&gt;}
s=${s//'"'/\&quot;} s=${s//'"'/\&quot;}
@@ -389,7 +420,7 @@ html() {
local title local title
local body local body
local script local script="${2:-}"
local footer local footer
title=$(escape "$APP") title=$(escape "$APP")
@@ -401,8 +432,6 @@ html() {
body="<p class=\"loading\">${body/.../}</p>" body="<p class=\"loading\">${body/.../}</p>"
fi fi
[ -n "${2:-}" ] && script="$2" || script=""
local HTML local HTML
HTML=$(<"$TEMPLATE") HTML=$(<"$TEMPLATE")
HTML="${HTML/\[1\]/$title}" HTML="${HTML/\[1\]/$title}"
@@ -411,8 +440,8 @@ html() {
HTML="${HTML/\[4\]/$footer}" HTML="${HTML/\[4\]/$footer}"
HTML="${HTML/\[5\]/$FOOTER2}" HTML="${HTML/\[5\]/$FOOTER2}"
echo "$HTML" > "$PAGE" || return 1 writeAtomic "$PAGE" "$HTML" || return 1
echo "$body" > "$INFO" || return 1 writeAtomic "$INFO" "$body" || return 1
return 0 return 0
} }
@@ -459,9 +488,9 @@ getCountry() {
local url=$1 local url=$1
local query=$2 local query=$2
local rc json result local json result
{ json=$(curl -m 5 -H "Accept: application/json" -sfk "$url"); rc=$?; } || : { json=$(curl -m 5 -H "Accept: application/json" -sfk "$url"); local rc=$?; } || :
(( rc != 0 )) && return 0 (( rc != 0 )) && return 0
{ result=$(echo "$json" | jq -r "$query" 2> /dev/null); rc=$?; } || : { result=$(echo "$json" | jq -r "$query" 2> /dev/null); rc=$?; } || :
+48 -8
View File
@@ -1,15 +1,27 @@
var timer;
var request; var request;
var booting = false; var booting = false;
var interval = 1000; var interval = 1000;
function abortRequest() {
if (!request) {
return false;
}
request.onreadystatechange = null;
request.abort();
request = null;
return true;
}
function getInfo() { function getInfo() {
var url = "msg.html"; var url = "msg.html";
try { try {
if (request) { abortRequest();
request.abort();
}
if (window.XMLHttpRequest) { if (window.XMLHttpRequest) {
request = new XMLHttpRequest(); request = new XMLHttpRequest();
@@ -56,7 +68,17 @@ function processInfo() {
return true; return true;
} }
var msg = request.responseText; var response = request;
request = null;
var status = response.status;
if (status == 502 || status == 503 || status == 504) {
schedule();
return true;
}
var msg = response.responseText;
if (msg == null || msg.length == 0) { if (msg == null || msg.length == 0) {
if (booting) { if (booting) {
@@ -68,9 +90,9 @@ function processInfo() {
return false; return false;
} }
var notFound = (request.status == 404); var notFound = (status == 404);
if (request.status == 200) { if (status == 200) {
if (msg.toLowerCase().indexOf("<html>") !== -1) { if (msg.toLowerCase().indexOf("<html>") !== -1) {
notFound = true; notFound = true;
} else { } else {
@@ -88,7 +110,7 @@ function processInfo() {
return true; return true;
} }
setError("Error: Received statuscode " + request.status); setError("Error: Received statuscode " + status);
return false; return false;
} catch (e) { } catch (e) {
@@ -157,7 +179,9 @@ function setError(text) {
} }
function schedule() { function schedule() {
setTimeout(getInfo, interval);
clearTimeout(timer);
timer = setTimeout(getInfo, interval);
} }
function reload() { function reload() {
@@ -179,11 +203,27 @@ function connect() {
switch (cmd) { switch (cmd) {
case "s": case "s":
var aborted = abortRequest();
processMsg(msg); processMsg(msg);
if (aborted &&
msg.toLowerCase().indexOf("href=") == -1) {
schedule();
}
break; break;
case "e": case "e":
if (abortRequest()) {
schedule();
}
setError(msg); setError(msg);
break; break;
default: default:
console.warn("Unknown event: " + cmd); console.warn("Unknown event: " + cmd);
break; break;