Compare commits

...
60 Commits
Author SHA1 Message Date
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
KroeseandGitHub 6fe19a8af4 feat: Improve download progress reporting (#1316) 2026-07-21 06:00:58 +02:00
KroeseandGitHub 072b5c3070 build: Update workflow (#1315) 2026-07-21 01:31:29 +02:00
KroeseandGitHub 89dbaeb2a4 feat: Improve download progress reporting (#1314) 2026-07-21 00:13:53 +02:00
KroeseandGitHub 02f76b44cd fix: Resolve shellcheck error (#1313) 2026-07-20 17:00:32 +02:00
KroeseandGitHub 99d6b8f830 feat: Preserve graceful shutdown for interactive console (#1312) 2026-07-20 16:31:21 +02:00
KroeseandGitHub e6b68b2f08 fix: Avoid shellcheck warning (#1311) 2026-07-20 14:32:03 +02:00
KroeseandGitHub 7a8f0041e7 build: Update workflow (#1310) 2026-07-20 12:31:13 +02:00
KroeseandGitHub 64809bb4d4 feat: Support progress tracking for segmented downloads (#1309) 2026-07-20 05:48:16 +02:00
KroeseandGitHub f08b2cbd99 fix: Decrease default timeout (#1307) 2026-07-19 09:17:51 +02:00
KroeseandGitHub 18d9ecd434 fix: Decrease default timeout (#1308) 2026-07-19 08:57:09 +02:00
KroeseandGitHub 6944293a32 fix: Add Recreate strategy to Kubernetes deployment (#1306) 2026-07-19 03:15:10 +02:00
KroeseandGitHub d807a365ff feat: Added optional lossy VNC compression (#1305) 2026-07-19 03:09:35 +02:00
KroeseandGitHub 066af83d07 fix: Decrease default timeout (#1304) 2026-07-19 01:03:21 +02:00
KroeseandGitHub 81536818df feat: Add escapeHTML function (#1303) 2026-07-18 22:00:45 +02:00
KroeseandGitHub 949c288565 fix: Set correct MTU option for dnsmasq (#1302) 2026-07-18 20:40:52 +02:00
KroeseandGitHub 5d5bbbcf4d feat: Improve NAT networking (#1300) 2026-07-18 15:34:46 +02:00
KroeseandGitHub 82a62c0240 build: Update workflow (#1301) 2026-07-18 15:32:07 +02:00
KroeseandGitHub 5399e1d463 feat: Use Unix sockets for qemu-host communication (#1299) 2026-07-18 11:31:20 +02:00
KroeseandGitHub c7e7b60f01 fix: Improve IP tables cleanup verification (#1298) 2026-07-18 10:54:20 +02:00
KroeseandGitHub f1defa6890 docs: Environment variables (#1297) 2026-07-18 00:28:24 +02:00
KroeseandGitHub a7fb161974 feat: Improve download progress readability (#1296) 2026-07-17 22:59:07 +02:00
KroeseandGitHub dcdf5e5293 fix: Improve interactive terminal detection (#1295) 2026-07-17 22:45:07 +02:00
KroeseandGitHub 1eb0db66ba feat: Improve download progress in container logs (#1294) 2026-07-17 22:29:10 +02:00
KroeseandGitHub 3eec8e03ed feat: Improve AMD detection (#1293) 2026-07-17 20:54:41 +02:00
KroeseandGitHub 5483981ed4 feat: Refactor config code (#1292) 2026-07-17 20:37:54 +02:00
KroeseandGitHub 62330a5bf4 fix: Cleanup stale pipe files (#1291) 2026-07-17 20:01:53 +02:00
KroeseandGitHub 2b0b59dfba feat: Preserve graceful shutdown for interactive console (#1290) 2026-07-17 19:56:05 +02:00
KroeseandGitHub b680ff9dd8 feat: Preserve graceful shutdown for interactive console (#1289) 2026-07-17 19:16:30 +02:00
KroeseandGitHub 5c889b272d fix: Store pid for serial debugging (#1288) 2026-07-17 14:44:49 +02:00
KroeseandGitHub 585ebb3f53 build: Update Passt to v2026_07_16 (#1287) 2026-07-17 13:49:26 +02:00
KroeseandGitHub 401307b981 build: Update QEMU host to v2.06 (#1286) 2026-07-17 13:24:34 +02:00
KroeseandGitHub 46820fe6eb build: Set dependabot cooldown period (#1284) 2026-07-17 13:20:19 +02:00
KroeseandGitHub 9452d48694 feat: Make machine type configurable (#1283) 2026-07-17 11:33:05 +02:00
KroeseandGitHub 5c3fed1387 fix: Clear service logs on startup (#1281) 2026-07-17 06:26:25 +02:00
KroeseandGitHub 797411ae7f fix: Show QEMU errors on unexpected exit (#1280) 2026-07-17 05:54:04 +02:00
KroeseandGitHub e81c509208 feat: Check RENDERNODE permissions (#1279) 2026-07-17 02:11:38 +02:00
KroeseandGitHub ace8614a50 feat: Refactor disk code (#1278) 2026-07-17 01:52:47 +02:00
KroeseandGitHub 8263b3a737 feat: Improve iptable backend selection (#1277) 2026-07-17 01:51:39 +02:00
KroeseandGitHub 61c6142988 feat: Remove unused functions (#1276) 2026-07-17 00:10:59 +02:00
KroeseandGitHub 5d26338bb3 feat: Improve detection of valid data disk (#1275) 2026-07-16 21:41:29 +02:00
KroeseandGitHub 084d475cde build: Add diffutils package (#1274) 2026-07-16 20:55:47 +02:00
KroeseandGitHub c36b0dcb00 docs: Environment variables (#1273) 2026-07-16 20:50:17 +02:00
KroeseandGitHub 6e9993742b feat: Limit wget progress output (#1272) 2026-07-15 23:01:39 +02:00
KroeseandGitHub 6946af0212 build: Update workflow (#1271) 2026-07-15 21:55:07 +02:00
KroeseandGitHub 0a9361d62f fix: Localize available memory formatting (#1270) 2026-07-15 21:13:51 +02:00
KroeseandGitHub 7cbfcd44a6 fix: Remove unnecessary guards (#1269) 2026-07-15 21:00:50 +02:00
25 changed files with 2352 additions and 948 deletions
+4
View File
@@ -4,7 +4,11 @@ updates:
directory: /
schedule:
interval: weekly
cooldown:
default-days: 7
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
cooldown:
default-days: 7
+27 -20
View File
@@ -1,5 +1,7 @@
on: [workflow_call]
name: "Check"
permissions: {}
jobs:
@@ -7,24 +9,29 @@ jobs:
name: shellcheck
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v7
- parallel:
-
name: Run ShellCheck
uses: ludeeus/action-shellcheck@master
env:
SHELLCHECK_OPTS: -x --source-path=src -e SC2001 -e SC2034 -e SC2064 -e SC2317 -e SC2153 -e SC2028
-
name: Lint Dockerfile
uses: hadolint/hadolint-action@v3.3.0
with:
dockerfile: Dockerfile
ignore: DL3008,DL3003,DL3006,DL3013
failure-threshold: warning
-
name: Validate JSON and YML files
uses: GrantBirki/json-yaml-validate@v5.0.0
with:
yaml_exclude_regex: ".*\\kubernetes\\.yml$"
name: Checkout
uses: actions/checkout@v7
- parallel:
-
name: Run ShellCheck
uses: ludeeus/action-shellcheck@master
env:
SHELLCHECK_OPTS: >-
-x
--source-path=src
-e SC1091
-e SC2001
-e SC2034
-e SC2317
-e SC2153
-
name: Lint Dockerfile
uses: hadolint/hadolint-action@v3.3.0
with:
dockerfile: Dockerfile
ignore: DL3008
failure-threshold: warning
-
name: Validate JSON and YML files
uses: GrantBirki/json-yaml-validate@v5
+1 -58
View File
@@ -11,61 +11,4 @@ permissions:
jobs:
review:
name: review
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v7
- parallel:
-
name: Spelling
uses: reviewdog/action-misspell@v1
with:
locale: "US"
level: warning
pattern: |
*.md
*.sh
reporter: github-pr-review
github_token: ${{ secrets.GITHUB_TOKEN }}
-
name: Hadolint
uses: reviewdog/action-hadolint@v1
with:
level: warning
fail_level: error
reporter: github-pr-review
hadolint_ignore: DL3008 DL3003 DL3006 DL3013
github_token: ${{ secrets.GITHUB_TOKEN }}
-
name: YamlLint
uses: reviewdog/action-yamllint@v1
with:
level: warning
reporter: github-pr-review
github_token: ${{ secrets.GITHUB_TOKEN }}
-
name: ActionLint
uses: reviewdog/action-actionlint@v1
with:
level: warning
reporter: github-pr-review
github_token: ${{ secrets.GITHUB_TOKEN }}
-
name: Shellformat
uses: reviewdog/action-shfmt@v1
if: false
with:
level: warning
fail_on_error: "true"
shfmt_flags: "-i 2 -ci -bn"
github_token: ${{ secrets.GITHUB_TOKEN }}
-
name: Shellcheck
uses: reviewdog/action-shellcheck@v1
with:
level: warning
fail_level: error
reporter: github-pr-review
shellcheck_flags: -x -e SC1091 -e SC2001 -e SC2034 -e SC2064 -e SC2317 -e SC2153 -e SC2028
github_token: ${{ secrets.GITHUB_TOKEN }}
uses: action-pack/.github/.github/workflows/review.yml@master
+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 -3
View File
@@ -1,12 +1,14 @@
# syntax=docker/dockerfile:1
FROM qemux/qemu-host:2.05 AS builder
FROM qemux/qemu-host:2.06 AS builder
FROM debian:trixie-slim
ARG TARGETARCH
ARG TARGETPLATFORM
ARG VERSION_ARG="0.0"
ARG VERSION_PASST="2026_06_11"
ARG VERSION_CSTRUCT="4.7"
ARG VERSION_PASST="2026_07_16"
ARG DEBCONF_NOWARNINGS="yes"
ARG DEBIAN_FRONTEND="noninteractive"
@@ -39,6 +41,7 @@ RUN <<EOF
apt-utils \
net-tools \
e2fsprogs \
diffutils \
qemu-utils \
websocketd \
iputils-ping \
@@ -54,7 +57,7 @@ RUN <<EOF
apt-get clean
# Install Python dependencies
pip3 install --no-cache-dir --break-system-packages --root-user-action=ignore dissect.cstruct
pip3 install --no-cache-dir --break-system-packages --root-user-action=ignore "dissect.cstruct==$VERSION_CSTRUCT"
# Configure QEMU
mkdir -p /etc/qemu
+14 -8
View File
@@ -8,7 +8,8 @@ An empty default means the variable is unset and its value is determined automat
| 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. |
| `HOST_MAC` | | MAC address reported to DSM. |
| `HOST_MODEL` | | Synology host model reported to DSM. |
| `HOST_SERIAL` | | Synology host serial number reported to DSM. |
@@ -45,15 +46,15 @@ An empty default means the variable is unset and its value is determined automat
| 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. |
| `NETWORK` | | Network mode, such as `nat`, `user`, or `N` to disable networking. |
| `DHCP` | `N` | Enables macvtap networking so DSM receives a DHCP address. |
| `HOST` | | Hostname assigned to the machine on the network. |
| `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. |
| `MASK` | `255.255.255.0` | IPv4 netmask for guest network. |
| `TAP` | `dsm` | TAP or macvtap interface name. |
| `BRIDGE` | `docker` | Bridge name used for NAT networking. |
| `HOST_PORTS` | | Ports excluded from guest forwarding. |
@@ -69,10 +70,18 @@ An empty default means the variable is unset and its value is determined automat
| Variable | Default | Description |
|---|---|---|
| `DISPLAY` | `none` | Display backend, such as `vnc`, `disabled`, or `none`. |
| `LOSSY` | `N` | Enables lossy VNC compression to reduce bandwidth usage. |
| `VGA` | `none` | QEMU video adapter model. |
| `GPU` | `N` | Enables Intel iGPU acceleration. |
| `RENDERNODE` | `/dev/dri/renderD128` | Render node used for GPU acceleration. |
## ⚙️ System
| Variable | Default | Description |
|---|---|---|
| `MACHINE` | `q35` | QEMU machine type. |
| `ARGUMENTS` | | Additional raw arguments appended to the QEMU command line. |
## 🔌 Shutdown
| Variable | Default | Description |
@@ -87,7 +96,4 @@ An empty default means the variable is unset and its value is determined automat
|---|---|---|
| `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. |
+2
View File
@@ -18,6 +18,8 @@ metadata:
name: dsm
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: dsm
+1 -1
View File
@@ -15,7 +15,7 @@ file="/run/shm/dsm.url"
address="/run/shm/qemu.ip"
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")
+56 -9
View File
@@ -2,15 +2,62 @@
set -Eeuo pipefail
DEF_OPTS="-nodefaults -boot strict=on"
RAM_OPTS=$(echo "-m ${RAM_SIZE^^}" | sed 's/MB/M/g;s/GB/G/g;s/TB/T/g')
MON_OPTS="-name $PROCESS,process=$PROCESS,debug-threads=on -pidfile $QEMU_PID"
CPU_OPTS="-cpu $CPU_FLAGS -smp $CPU_CORES,sockets=1,dies=1,cores=$CPU_CORES,threads=1"
MAC_OPTS="-machine type=q35,smm=off,usb=off,vmport=off,dump-guest-core=off,hpet=off${KVM_OPTS}"
DEV_OPTS="-device virtio-balloon-pci,id=balloon0,bus=pcie.0,addr=0x4"
DEV_OPTS+=" -object rng-random,id=objrng0,filename=/dev/urandom"
DEV_OPTS+=" -device virtio-rng-pci,rng=objrng0,id=rng0,bus=pcie.0,addr=0x1c"
DEV_OPTS=""
ARGS="$DEF_OPTS $CPU_OPTS $RAM_OPTS $MAC_OPTS $DISPLAY_OPTS $MON_OPTS $SERIAL_OPTS $NET_OPTS $DISK_OPTS $DEV_OPTS $ARGUMENTS"
ARGS=$(echo "$ARGS" | sed 's/\t/ /g' | tr -s ' ')
configureProcessor() {
CPU_OPTS="-cpu $CPU_FLAGS"
CPU_OPTS+=" -smp $CPU_CORES,sockets=1,dies=1,cores=$CPU_CORES,threads=1"
return 0
}
configureMemory() {
RAM_OPTS=$(echo "-m ${RAM_SIZE^^}" | sed 's/MB/M/g;s/GB/G/g;s/TB/T/g')
return 0
}
configureMonitor() {
MON_OPTS="-name $PROCESS,process=$PROCESS,debug-threads=on"
MON_OPTS+=" -pidfile $QEMU_PID"
return 0
}
configureMachine() {
MAC_OPTS="-machine type=$MACHINE,smm=off,usb=off"
MAC_OPTS+=",vmport=off,dump-guest-core=off,hpet=off${KVM_OPTS}"
return 0
}
configureVirtioDevices() {
DEV_OPTS="-device virtio-balloon-pci,id=balloon0,bus=pcie.0,addr=0x4"
DEV_OPTS+=" -object rng-random,id=objrng0,filename=/dev/urandom"
DEV_OPTS+=" -device virtio-rng-pci,rng=objrng0,id=rng0,bus=pcie.0,addr=0x1c"
return 0
}
buildArguments() {
ARGS="$DEF_OPTS $CPU_OPTS $RAM_OPTS $MAC_OPTS $DISPLAY_OPTS $MON_OPTS $SERIAL_OPTS $NET_OPTS $DISK_OPTS $DEV_OPTS $ARGUMENTS"
ARGS=$(echo "$ARGS" | sed 's/\t/ /g' | tr -s ' ')
return 0
}
configureMemory
configureMonitor
configureMachine
configureProcessor
configureVirtioDevices
buildArguments
return 0
+339 -302
View File
@@ -35,58 +35,58 @@ if ! setOwner "$SYSTEM"; then
fi
fmt2ext() {
local DISK_FMT="$1"
local diskFmt="$1"
case "${DISK_FMT,,}" in
case "${diskFmt,,}" in
qcow2) echo "qcow2" ;;
raw) echo "img" ;;
*) error "Unrecognized disk format: $DISK_FMT" && exit 78 ;;
*) error "Unrecognized disk format: $diskFmt" && exit 78 ;;
esac
}
ext2fmt() {
local DISK_EXT="$1"
local diskExt="$1"
case "${DISK_EXT,,}" in
case "${diskExt,,}" in
qcow2) echo "qcow2" ;;
img) echo "raw" ;;
*) error "Unrecognized file extension: .$DISK_EXT" && exit 78 ;;
*) error "Unrecognized file extension: .$diskExt" && exit 78 ;;
esac
}
getSize() {
local DISK_FILE="$1"
local DISK_EXT DISK_FMT size
local diskFile="$1"
local diskExt diskFmt size
DISK_EXT=$(echo "${DISK_FILE//*./}" | sed 's/^.*\.//')
DISK_FMT=$(ext2fmt "$DISK_EXT")
diskExt=$(echo "${diskFile//*./}" | sed 's/^.*\.//')
diskFmt=$(ext2fmt "$diskExt")
case "${DISK_FMT,,}" in
case "${diskFmt,,}" in
raw)
stat -c%s "$DISK_FILE"
stat -c%s "$diskFile"
;;
qcow2)
size=$(qemu-img info --output=json -f "$DISK_FMT" "$DISK_FILE" | jq -r '."virtual-size" // empty')
size=$(qemu-img info --output=json -f "$diskFmt" "$diskFile" | jq -r '."virtual-size" // empty')
if [[ ! "$size" =~ ^[0-9]+$ ]]; then
error "Failed to determine virtual size of $DISK_FILE"
error "Failed to determine virtual size of $diskFile"
exit 78
fi
echo "$size"
;;
*)
error "Unrecognized disk format: $DISK_FMT"
error "Unrecognized disk format: $diskFmt"
exit 78
;;
esac
}
isCow() {
local FS="$1"
local fs="$1"
if [[ "${FS,,}" == "btrfs" ]]; then
if [[ "${fs,,}" == "btrfs" ]]; then
return 0
fi
@@ -94,92 +94,104 @@ isCow() {
}
supportsDirect() {
local FS="$1"
local fs="$1"
if [[ "${FS,,}" == "ecryptfs" || "${FS,,}" == "tmpfs" ]]; then
if [[ "${fs,,}" == "ecryptfs" || "${fs,,}" == "tmpfs" ]]; then
return 1
fi
return 0
}
validDiskType() {
case "${1,,}" in
"ide" | "sata" | "nvme" | "usb" | "scsi" | "blk" | \
"virtio-blk" | "virtio-scsi" | "auto" | "none" )
return 0
;;
esac
return 1
}
allocateRaw() {
local DISK_FILE="$1"
local DATA_SIZE="$2"
local diskFile="$1"
local dataSize="$2"
if disabled "$ALLOCATE"; then
truncate -s "$DATA_SIZE" "$DISK_FILE"
truncate -s "$dataSize" "$diskFile"
return $?
fi
fallocate -l "$DATA_SIZE" "$DISK_FILE" &>/dev/null && return 0
fallocate -l -x "$DATA_SIZE" "$DISK_FILE" && return 0
truncate -s "$DATA_SIZE" "$DISK_FILE" || return 1
fallocate -l "$dataSize" "$diskFile" &>/dev/null && return 0
fallocate -l -x "$dataSize" "$diskFile" && return 0
truncate -s "$dataSize" "$diskFile" || return 1
return 0
}
getDiskOptions() {
local FS="$1"
local DISK_FMT="$2"
local DISK_PARAM="$DISK_ALLOC"
local fs="$1"
local diskFmt="$2"
local diskParam="$DISK_ALLOC"
isCow "$FS" && DISK_PARAM+=",nocow=on"
isCow "$fs" && diskParam+=",nocow=on"
if [[ "${DISK_FMT,,}" != "raw" ]]; then
[ -n "$DISK_FLAGS" ] && DISK_PARAM+=",$DISK_FLAGS"
if [[ "${diskFmt,,}" != "raw" ]]; then
[ -n "$DISK_FLAGS" ] && diskParam+=",$DISK_FLAGS"
fi
echo "$DISK_PARAM"
echo "$diskParam"
return 0
}
normalizeSize() {
local DISK_SPACE="$1"
local DISK_DESC="$2"
local DIR="$3"
local SPACE FREE GB DATA_SIZE
local diskSpace="$1"
local diskDesc="$2"
local dir="$3"
if [[ "${DISK_SPACE,,}" == "max" || "${DISK_SPACE,,}" == "half" ]]; then
local free dataSize
local spare=1073741824
local SPARE=1073741824
FREE=$(df --output=avail -B 1 "$DIR" | tail -n 1)
if [[ "${diskSpace,,}" == "max" || "${diskSpace,,}" == "half" ]]; then
if [[ "${DISK_SPACE,,}" == "max" ]]; then
FREE=$(( FREE - SPARE ))
free=$(df --output=avail -B 1 "$dir" | tail -n 1)
if [[ "${diskSpace,,}" == "max" ]]; then
free=$(( free - spare ))
else
FREE=$(( FREE / 2 ))
free=$(( free / 2 ))
fi
(( FREE < SPARE )) && FREE="$SPARE"
GB=$(( FREE / 1073741825 ))
DISK_SPACE="${GB}G"
(( free < spare )) && free="$spare"
local gb=$(( free / 1073741825 ))
diskSpace="${gb}G"
fi
SPACE="${DISK_SPACE// /}"
[ -z "$SPACE" ] && SPACE="256G"
[ -z "${SPACE//[0-9. ]}" ] && SPACE="${SPACE}G"
SPACE=$(echo "${SPACE^^}" | sed 's/MB/M/g;s/GB/G/g;s/TB/T/g')
local space="${diskSpace// /}"
[ -z "$space" ] && space="256G"
[ -z "${space//[0-9. ]}" ] && space="${space}G"
space=$(echo "${space^^}" | sed 's/MB/M/g;s/GB/G/g;s/TB/T/g')
if ! numfmt --from=iec "$SPACE" &>/dev/null; then
error "Invalid value for ${DISK_DESC^^}_SIZE: $DISK_SPACE" && exit 73
if ! numfmt --from=iec "$space" &>/dev/null; then
error "Invalid value for ${diskDesc^^}_SIZE: $diskSpace" && exit 73
fi
DATA_SIZE=$(numfmt --from=iec "$SPACE")
dataSize=$(numfmt --from=iec "$space")
if (( DATA_SIZE < 6442450944 )); then
error "Please increase the ${DISK_DESC^^}_SIZE variable to at least 6 GB." && exit 73
if (( dataSize < 6442450944 )); then
error "Please increase the ${diskDesc^^}_SIZE variable to at least 6 GB." && exit 73
fi
echo "$SPACE"
echo "$space"
return 0
}
baseDir() {
local path="${1%/}"
@@ -195,74 +207,95 @@ baseDir() {
echo "/$path"
return 0
}
freeSpace() {
local path="$1"
local base
base=$(baseDir "$path")
if ! available=$(df --output=avail -B 1 "$path" | tail -n 1); then
error "Failed to check free space in $base."
exit 76
fi
if [[ ! "$available" =~ ^[0-9]+$ ]]; then
error "Failed to check free space in $base."
exit 76
fi
return 0
}
createDisk() {
local DISK_FILE="$1"
local DISK_SPACE="$2"
local DISK_DESC="$3"
local DISK_FMT="$4"
local FS="$5"
local DATA_SIZE DIR BASE_DIR SPACE GB FA
local diskFile="$1"
local diskSpace="$2"
local diskDesc="$3"
local diskFmt="$4"
local fs="$5"
rm -f "$DISK_FILE"
local gb dir base
local attributes available
DATA_SIZE=$(numfmt --from=iec "$DISK_SPACE")
rm -f "$diskFile"
local dataSize
dataSize=$(numfmt --from=iec "$diskSpace")
if ! disabled "$ALLOCATE"; then
# Check free diskspace
DIR=$(dirname "$DISK_FILE")
BASE_DIR=$(baseDir "$DIR")
dir=$(dirname "$diskFile")
base=$(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
freeSpace "$dir"
if (( DATA_SIZE > SPACE )); then
GB=$(formatBytes "$SPACE")
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
if (( dataSize > available )); then
gb=$(formatBytes "$available")
error "Not enough free space to create a $diskDesc of ${diskSpace/G/ GB} in $base, it has only $gb available..."
error "Please specify a smaller ${diskDesc^^}_SIZE or disable preallocation by setting ALLOCATE=N." && exit 76
fi
fi
html "Creating a $DISK_DESC image..."
info "Creating a ${DISK_SPACE/G/ GB} $DISK_STYLE $DISK_DESC image in $DISK_FMT format..."
html "Creating a $diskDesc image..."
info "Creating a ${diskSpace/G/ GB} $DISK_STYLE $diskDesc image in $diskFmt format..."
local FAIL="Could not create a $DISK_STYLE $DISK_FMT $DISK_DESC image of ${DISK_SPACE/G/ GB} ($DISK_FILE)"
local failure="Could not create a $DISK_STYLE $diskFmt $diskDesc image of ${diskSpace/G/ GB} ($diskFile)"
case "${DISK_FMT,,}" in
case "${diskFmt,,}" in
raw)
if isCow "$FS"; then
if ! touch "$DISK_FILE"; then
error "$FAIL" && exit 77
if isCow "$fs"; then
if ! touch "$diskFile"; then
error "$failure" && exit 77
fi
{ chattr +C "$DISK_FILE"; } || :
{ chattr +C "$diskFile"; } || :
fi
if ! allocateRaw "$DISK_FILE" "$DATA_SIZE"; then
rm -f "$DISK_FILE"
error "$FAIL" && exit 77
if ! allocateRaw "$diskFile" "$dataSize"; then
rm -f "$diskFile"
error "$failure" && exit 77
fi
;;
qcow2)
local DISK_PARAM
DISK_PARAM=$(getDiskOptions "$FS" "$DISK_FMT")
local diskParam
diskParam=$(getDiskOptions "$fs" "$diskFmt")
if ! qemu-img create -f "$DISK_FMT" -o "$DISK_PARAM" -- "$DISK_FILE" "$DATA_SIZE" ; then
rm -f "$DISK_FILE"
error "$FAIL" && exit 70
if ! qemu-img create -f "$diskFmt" -o "$diskParam" -- "$diskFile" "$dataSize" ; then
rm -f "$diskFile"
error "$failure" && exit 70
fi
;;
esac
if isCow "$FS"; then
FA=$(lsattr "$DISK_FILE")
if [[ "$FA" != *"C"* ]]; then
error "Failed to disable COW for $DISK_DESC image $DISK_FILE on ${FS^^} filesystem (returned $FA)"
if isCow "$fs"; then
attributes=$(lsattr "$diskFile")
if [[ "$attributes" != *"C"* ]]; then
error "Failed to disable COW for $diskDesc image $diskFile on ${fs^^} filesystem (returned $attributes)"
fi
fi
@@ -271,54 +304,53 @@ createDisk() {
resizeDisk() {
local DISK_FILE="$1"
local DISK_SPACE="$2"
local DISK_DESC="$3"
local DISK_FMT="$4"
local FS="$5"
local CUR_SIZE DATA_SIZE DIR BASE_DIR SPACE GB
local diskFile="$1"
local diskSpace="$2"
local diskDesc="$3"
local diskFmt="$4"
local fs="$5"
CUR_SIZE=$(getSize "$DISK_FILE") || exit 71
DATA_SIZE=$(numfmt --from=iec "$DISK_SPACE")
local REQ=$(( DATA_SIZE - CUR_SIZE ))
(( REQ < 1 )) && error "Shrinking disks is not supported yet, please increase ${DISK_DESC^^}_SIZE." && exit 71
local gb dir base dataSize
local available currentSize
currentSize=$(getSize "$diskFile") || exit 71
dataSize=$(numfmt --from=iec "$diskSpace")
local required=$(( dataSize - currentSize ))
(( required < 1 )) && error "Shrinking disks is not supported yet, please increase ${diskDesc^^}_SIZE." && exit 71
if ! disabled "$ALLOCATE"; then
# Check free diskspace
DIR=$(dirname "$DISK_FILE")
BASE_DIR=$(baseDir "$DIR")
dir=$(dirname "$diskFile")
base=$(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
freeSpace "$dir"
if (( REQ > SPACE )); then
GB=$(formatBytes "$SPACE")
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
if (( required > available )); then
gb=$(formatBytes "$available")
error "Not enough free space to resize $diskDesc to ${diskSpace/G/ GB} in $base, it has only $gb available.."
error "Please specify a smaller ${diskDesc^^}_SIZE or disable preallocation by setting ALLOCATE=N." && exit 74
fi
fi
GB=$(formatBytes "$CUR_SIZE")
MSG="Resizing $DISK_DESC from $GB to ${DISK_SPACE/G/ GB}..."
gb=$(formatBytes "$currentSize")
MSG="Resizing $diskDesc from $gb to ${diskSpace/G/ GB}..."
info "$MSG" && html "$MSG"
local FAIL="Could not resize the $DISK_STYLE $DISK_FMT $DISK_DESC image from ${GB} to ${DISK_SPACE/G/ GB} ($DISK_FILE)"
local failure="Could not resize the $DISK_STYLE $diskFmt $diskDesc image from ${gb} to ${diskSpace/G/ GB} ($diskFile)"
case "${DISK_FMT,,}" in
case "${diskFmt,,}" in
raw)
if ! allocateRaw "$DISK_FILE" "$DATA_SIZE"; then
error "$FAIL" && exit 75
if ! allocateRaw "$diskFile" "$dataSize"; then
error "$failure" && exit 75
fi
;;
qcow2)
if ! qemu-img resize -f "$DISK_FMT" "--$DISK_ALLOC" "$DISK_FILE" "$DATA_SIZE" ; then
error "$FAIL" && exit 72
if ! qemu-img resize -f "$diskFmt" "--$DISK_ALLOC" "$diskFile" "$dataSize" ; then
error "$failure" && exit 72
fi
;;
@@ -329,133 +361,131 @@ resizeDisk() {
convertDisk() {
local SOURCE_FILE="$1"
local SOURCE_FMT="$2"
local DST_FILE="$3"
local DST_FMT="$4"
local DISK_BASE="$5"
local DISK_DESC="$6"
local FS="$7"
local sourceFile="$1"
local sourceFmt="$2"
local destinationFile="$3"
local destinationFmt="$4"
local diskBase="$5"
local diskDesc="$6"
local fs="$7"
local tmpFile="$diskBase.tmp"
[ -f "$DST_FILE" ] && error "Conversion failed, destination file $DST_FILE already exists?" && exit 79
[ ! -f "$SOURCE_FILE" ] && error "Conversion failed, source file $SOURCE_FILE does not exist?" && exit 79
local gb dir base attributes
local available currentSize
local TMP_FILE="$DISK_BASE.tmp"
rm -f "$TMP_FILE"
[ -f "$destinationFile" ] && error "Conversion failed, destination file $destinationFile already exists?" && exit 79
[ ! -f "$sourceFile" ] && error "Conversion failed, source file $sourceFile does not exist?" && exit 79
local DIR BASE_DIR FA
DIR=$(dirname "$TMP_FILE")
BASE_DIR=$(baseDir "$DIR")
rm -f "$tmpFile"
dir=$(dirname "$tmpFile")
base=$(baseDir "$dir")
if ! disabled "$ALLOCATE"; then
local CUR_SIZE SPACE GB
# Check free diskspace
CUR_SIZE=$(getSize "$SOURCE_FILE") || exit 79
currentSize=$(getSize "$sourceFile") || exit 79
if ! SPACE=$(df --output=avail -B 1 "$DIR" | tail -n 1); then
error "Failed to check free space in $BASE_DIR."
exit 76
fi
freeSpace "$dir"
if (( CUR_SIZE > SPACE )); then
GB=$(formatBytes "$SPACE")
error "Not enough free space to convert $DISK_DESC to $DST_FMT in $BASE_DIR, it has only $GB available..."
if (( currentSize > available )); then
gb=$(formatBytes "$available")
error "Not enough free space to convert $diskDesc to $destinationFmt in $base, it has only $gb available..."
error "Please free up some disk space or disable preallocation by setting ALLOCATE=N." && exit 76
fi
fi
local msg="Converting $DISK_DESC to $DST_FMT"
local msg="Converting $diskDesc to $destinationFmt"
html "$msg..."
info "$msg, please wait until completed..."
local CONV_FLAGS="-p"
local DISK_PARAM
DISK_PARAM=$(getDiskOptions "$FS" "$DST_FMT")
local convertFlags="-p"
local diskParam
diskParam=$(getDiskOptions "$fs" "$destinationFmt")
if [[ "$DST_FMT" != "raw" ]]; then
if [[ "$destinationFmt" != "raw" ]]; then
if disabled "$ALLOCATE"; then
CONV_FLAGS+=" -c"
convertFlags+=" -c"
fi
fi
# 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 $BASE_DIR, is there enough space available?" && exit 79
if ! qemu-img convert -f "$sourceFmt" $convertFlags -o "$diskParam" -O "$destinationFmt" -- "$sourceFile" "$tmpFile"; then
rm -f "$tmpFile"
error "Failed to convert $DISK_STYLE $diskDesc image to $destinationFmt format in $base, is there enough space available?" && exit 79
fi
if [[ "$DST_FMT" == "raw" ]]; then
if [[ "$destinationFmt" == "raw" ]]; then
if ! disabled "$ALLOCATE"; then
# Work around qemu-img bug
if ! CUR_SIZE=$(stat -c%s "$TMP_FILE"); then
error "Failed to determine converted image size: $TMP_FILE"
if ! currentSize=$(stat -c%s "$tmpFile"); then
error "Failed to determine converted image size: $tmpFile"
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"
if ! fallocate -l "$currentSize" "$tmpFile" &>/dev/null; then
if ! fallocate -l -x "$currentSize" "$tmpFile"; then
error "Failed to allocate $currentSize bytes for $diskDesc image $tmpFile"
fi
fi
fi
fi
if ! mv "$TMP_FILE" "$DST_FILE"; then
error "Failed to move converted $DISK_DESC image to $DST_FILE."
if ! mv "$tmpFile" "$destinationFile"; then
error "Failed to move converted $diskDesc image to $destinationFile."
exit 79
fi
if ! rm -f "$SOURCE_FILE"; then
error "Failed to remove old $DISK_DESC image $SOURCE_FILE."
if ! rm -f "$sourceFile"; then
error "Failed to remove old $diskDesc image $sourceFile."
exit 79
fi
if isCow "$FS"; then
FA=$(lsattr "$DST_FILE")
if [[ "$FA" != *"C"* ]]; then
error "Failed to disable COW for $DISK_DESC image $DST_FILE on ${FS^^} filesystem (returned $FA)"
if isCow "$fs"; then
attributes=$(lsattr "$destinationFile")
if [[ "$attributes" != *"C"* ]]; then
error "Failed to disable COW for $diskDesc image $destinationFile on ${fs^^} filesystem (returned $attributes)"
fi
fi
msg="Conversion of $DISK_DESC"
html "$msg completed..."
info "$msg to $DST_FMT completed successfully!"
msg="Conversion of $diskDesc"
info "$msg to $destinationFmt completed successfully!"
return 0
}
checkFS () {
local FS="$1"
local DISK_FILE="$2"
local DISK_DESC="$3"
local DIR BASE_DIR FA
local fs="$1"
local diskFile="$2"
local diskDesc="$3"
DIR=$(dirname "$DISK_FILE")
BASE_DIR=$(baseDir "$DIR")
[ ! -d "$DIR" ] && return 0
local dir base
local attributes
if [[ "${FS,,}" == "overlay"* && "${ENGINE,,}" == "docker" ]]; then
warn "the filesystem of $BASE_DIR is OverlayFS, this usually means it was binded to an invalid path!"
dir=$(dirname "$diskFile")
base=$(baseDir "$dir")
[ ! -d "$dir" ] && return 0
if [[ "${fs,,}" == "overlay"* && "${ENGINE,,}" == "docker" ]]; then
warn "the filesystem of $base is OverlayFS, this usually means it was binded to an invalid path!"
fi
if [[ "${FS,,}" == "fuse"* ]]; then
warn "the filesystem of $BASE_DIR is FUSE, this extra layer will negatively affect performance!"
if [[ "${fs,,}" == "fuse"* ]]; then
warn "the filesystem of $base is FUSE, this extra layer will negatively affect performance!"
fi
if ! supportsDirect "$FS"; then
warn "the filesystem of $BASE_DIR is $FS, which does not support O_DIRECT mode, adjusting settings..."
if ! supportsDirect "$fs"; then
warn "the filesystem of $base is $fs, which does not support O_DIRECT mode, adjusting settings..."
fi
if isCow "$FS"; then
if [ -f "$DISK_FILE" ]; then
FA=$(lsattr "$DISK_FILE")
if [[ "$FA" != *"C"* ]]; then
warn "COW (copy on write) is not disabled for $DISK_DESC image file $DISK_FILE, this is recommended on ${FS^^} filesystems!"
if isCow "$fs"; then
if [ -f "$diskFile" ]; then
attributes=$(lsattr "$diskFile")
if [[ "$attributes" != *"C"* ]]; then
warn "COW (copy on write) is not disabled for $diskDesc image file $diskFile, this is recommended on ${fs^^} filesystems!"
fi
fi
fi
@@ -465,51 +495,54 @@ checkFS () {
createDevice () {
local DISK_FILE="$1"
local DISK_TYPE="$2"
local DISK_INDEX="$3"
local DISK_ADDRESS="$4"
local DISK_FMT="$5"
local DISK_IO="$6"
local DISK_CACHE="$7"
local DISK_SERIAL="$8"
local DISK_SECTORS="$9"
local DISK_ID="data$DISK_INDEX"
local diskFile="$1"
local diskType="$2"
local diskIndex="$3"
local diskAddress="$4"
local diskFmt="$5"
local diskIo="$6"
local diskCache="$7"
local diskSerial="$8"
local diskSectors="$9"
local bus="${PCI_BUS:-pcie.0}"
local index=""
[ -n "$DISK_INDEX" ] && index=",bootindex=$DISK_INDEX"
local result=" -drive file=$DISK_FILE,id=$DISK_ID,format=$DISK_FMT,cache=$DISK_CACHE,aio=$DISK_IO,discard=$DISK_DISCARD,detect-zeroes=on"
[[ -z "${PCI_BUS:-}" && ( "${MACHINE,,}" == pc || "${MACHINE,,}" == pc-i440fx* ) ]] && bus="pci.0"
case "${DISK_TYPE,,}" in
local bootIndex=""
local diskId="data$diskIndex"
[ -n "$diskIndex" ] && bootIndex=",bootindex=$diskIndex"
local result=" -drive file=$diskFile,id=$diskId,format=$diskFmt,cache=$diskCache,aio=$diskIo,discard=$DISK_DISCARD,detect-zeroes=on"
case "${diskType,,}" in
"none" ) ;;
"auto" )
echo "$result"
;;
"usb" )
result+=",if=none \
-device usb-storage,drive=${DISK_ID}${index}${DISK_SERIAL}${DISK_SECTORS}"
-device usb-storage,drive=${diskId}${bootIndex}${diskSerial}${diskSectors}"
echo "$result"
;;
"nvme" )
result+=",if=none \
-device nvme,drive=${DISK_ID}${index},serial=deadbeaf${DISK_INDEX}${DISK_SERIAL}${DISK_SECTORS}"
-device nvme,drive=${diskId}${bootIndex},serial=deadbeaf${diskIndex}${diskSerial}${diskSectors}"
echo "$result"
;;
"ide" | "sata" )
result+=",if=none \
-device ich9-ahci,id=ahci${DISK_INDEX},addr=$DISK_ADDRESS \
-device ide-hd,drive=${DISK_ID},bus=ahci$DISK_INDEX.0,rotation_rate=$DISK_ROTATION${index}${DISK_SERIAL}${DISK_SECTORS}"
-device ich9-ahci,id=ahci${diskIndex},addr=$diskAddress \
-device ide-hd,drive=${diskId},bus=ahci$diskIndex.0,rotation_rate=$DISK_ROTATION${bootIndex}${diskSerial}${diskSectors}"
echo "$result"
;;
"blk" | "virtio-blk" )
result+=",if=none \
-device virtio-blk-pci,drive=${DISK_ID},bus=pcie.0,addr=$DISK_ADDRESS,iothread=io2${index}${DISK_SERIAL}${DISK_SECTORS}"
-device virtio-blk-pci,drive=${diskId},bus=$bus,addr=$diskAddress,iothread=io2${bootIndex}${diskSerial}${diskSectors}"
echo "$result"
;;
"scsi" | "virtio-scsi" )
result+=",if=none \
-device virtio-scsi-pci,id=${DISK_ID}b,bus=pcie.0,addr=$DISK_ADDRESS,iothread=io2 \
-device scsi-hd,drive=${DISK_ID},bus=${DISK_ID}b.0,channel=0,scsi-id=0,lun=0,rotation_rate=$DISK_ROTATION${index}${DISK_SERIAL}${DISK_SECTORS}"
-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}"
echo "$result"
;;
esac
@@ -524,73 +557,78 @@ finishDisks () {
[[ "$DISK_OPTS" != *" -object iothread,id=io2"* ]] && DISK_OPTS+=" -object iothread,id=io2" ;;
esac
if ! enabled "$DISK_DISABLE"; then
html "Initialized disks successfully..."
fi
return 0
}
addDisk () {
local DISK_BASE="$1"
local DISK_TYPE="$2"
local DISK_DESC="$3"
local DISK_SPACE="$4"
local DISK_INDEX="$5"
local DISK_ADDRESS="$6"
local DISK_FMT="$7"
local DISK_IO="$8"
local DISK_CACHE="$9"
local DISK_EXT DIR SPACE DATA_SIZE FS PREV_FMT PREV_EXT CUR_SIZE LEFT FREE USED
local diskBase="$1"
local diskType="$2"
local diskDesc="$3"
local diskSpace="$4"
local diskIndex="$5"
local diskAddress="$6"
local diskFmt="$7"
local diskIo="$8"
local diskCache="$9"
DISK_EXT=$(fmt2ext "$DISK_FMT")
local DISK_FILE="$DISK_BASE.$DISK_EXT"
local fs dir used space
local diskExt dataSize
local available currentSize
local previousExt
DIR=$(dirname "$DISK_FILE")
[ ! -d "$DIR" ] && return 0
diskExt=$(fmt2ext "$diskFmt")
local diskFile="$diskBase.$diskExt"
SPACE=$(normalizeSize "$DISK_SPACE" "$DISK_DESC" "$DIR")
DATA_SIZE=$(numfmt --from=iec "$SPACE")
dir=$(dirname "$diskFile")
[ ! -d "$dir" ] && return 0
FS=$(stat -f -c %T "$DIR")
checkFS "$FS" "$DISK_FILE" "$DISK_DESC" || exit $?
space=$(normalizeSize "$diskSpace" "$diskDesc" "$dir")
dataSize=$(numfmt --from=iec "$space")
if ! supportsDirect "$FS"; then
DISK_IO="threads"
DISK_CACHE="writeback"
if ! fs=$(stat -f -c %T "$dir"); then
error "Failed to determine filesystem type of \"$dir\" !"
return 1
fi
if [ ! -s "$DISK_FILE" ] ; then
checkFS "$fs" "$diskFile" "$diskDesc" || exit $?
if [[ "${DISK_FMT,,}" != "raw" ]]; then
PREV_FMT="raw"
if ! supportsDirect "$fs"; then
diskIo="threads"
diskCache="writeback"
fi
if [ ! -f "$diskFile" ] || [ ! -s "$diskFile" ]; then
if [[ "${diskFmt,,}" != "raw" ]]; then
local previousFmt="raw"
else
PREV_FMT="qcow2"
local previousFmt="qcow2"
fi
PREV_EXT=$(fmt2ext "$PREV_FMT")
previousExt=$(fmt2ext "$previousFmt")
if [ -s "$DISK_BASE.$PREV_EXT" ] ; then
convertDisk "$DISK_BASE.$PREV_EXT" "$PREV_FMT" "$DISK_FILE" "$DISK_FMT" "$DISK_BASE" "$DISK_DESC" "$FS" || exit $?
if [ -f "$diskBase.$previousExt" ] &&
[ -s "$diskBase.$previousExt" ]; then
convertDisk "$diskBase.$previousExt" "$previousFmt" "$diskFile" "$diskFmt" "$diskBase" "$diskDesc" "$fs" || exit $?
fi
fi
if [ -s "$DISK_FILE" ]; then
if [ -f "$diskFile" ] && [ -s "$diskFile" ]; then
CUR_SIZE=$(getSize "$DISK_FILE") || exit 71
currentSize=$(getSize "$diskFile") || exit 71
if (( DATA_SIZE > CUR_SIZE )); then
if (( dataSize > currentSize )); then
resizeDisk "$DISK_FILE" "$SPACE" "$DISK_DESC" "$DISK_FMT" "$FS" || exit $?
resizeDisk "$diskFile" "$space" "$diskDesc" "$diskFmt" "$fs" || exit $?
else
if (( DATA_SIZE < CUR_SIZE )); then
if (( dataSize < currentSize )); then
if [[ "${DISK_SPACE,,}" != "max" && "${DISK_SPACE,,}" != "half" ]]; then
info "You decreased the ${DISK_DESC^^}_SIZE variable to ${DISK_SPACE/G/ GB} but shrinking disks is not supported, will be ignored..."
if [[ "${diskSpace,,}" != "max" && "${diskSpace,,}" != "half" ]]; then
info "You decreased the ${diskDesc^^}_SIZE variable to ${diskSpace/G/ GB} but shrinking disks is not supported, will be ignored..."
fi
fi
@@ -598,72 +636,72 @@ addDisk () {
else
createDisk "$DISK_FILE" "$SPACE" "$DISK_DESC" "$DISK_FMT" "$FS" || exit $?
createDisk "$diskFile" "$space" "$diskDesc" "$diskFmt" "$fs" || exit $?
fi
if [ -f "$DISK_FILE" ] && disabled "$ALLOCATE"; then
if [ -f "$diskFile" ] && disabled "$ALLOCATE"; then
CUR_SIZE=$(getSize "$DISK_FILE") || exit 73
USED=$(du -sB 1 "$DISK_FILE" | cut -f1)
FREE=$(df --output=avail -B 1 "$DIR" | tail -n 1)
LEFT=$(( CUR_SIZE - USED - FREE ))
(( LEFT < 0 )) && LEFT=0
currentSize=$(getSize "$diskFile") || exit 73
used=$(du -sB 1 "$diskFile" | cut -f1)
available=$(df --output=avail -B 1 "$dir" | tail -n 1)
local missing=$(( currentSize - used - available ))
(( missing < 0 )) && missing=0
if (( LEFT > 0 )); then
if (( missing > 0 )); then
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"
local gb base
if [ -n "$USED" ] && [[ "$USED" != "0" ]]; then
USED=$(formatBytes "$USED")
msg+=" (of which $USED is used)"
gb=$(formatBytes "$available")
base=$(baseDir "$dir")
missing=$(formatBytes "$missing")
currentSize=$(formatBytes "$currentSize")
local msg="The virtual size of the ${diskDesc,,} is $currentSize"
if [ -n "$used" ] && [[ "$used" != "0" ]]; then
used=$(formatBytes "$used")
msg+=" (of which $used is used)"
fi
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."
info "$msg, but there is only $gb of free space remaining in $base now."
info "Please consider making at least $missing more space available in $base for future expansions."
fi
fi
if [ -f "$DISK_FILE" ]; then
if ! setOwner "$DISK_FILE"; then
warn "failed to set the owner for \"$DISK_FILE\" !"
fi
fi
DISK_OPTS+=$(createDevice "$DISK_FILE" "$DISK_TYPE" "$DISK_INDEX" "$DISK_ADDRESS" "$DISK_FMT" "$DISK_IO" "$DISK_CACHE" "" "")
if [ -f "$diskFile" ]; then
if ! setOwner "$diskFile"; then
warn "failed to set the owner for \"$diskFile\" !"
fi
fi
DISK_OPTS+=$(createDevice "$diskFile" "$diskType" "$diskIndex" "$diskAddress" "$diskFmt" "$diskIo" "$diskCache" "" "")
return 0
}
addDevice () {
local DISK_DEV="$1"
local DISK_TYPE="$2"
local DISK_INDEX="$3"
local DISK_ADDRESS="$4"
[ -z "$DISK_DEV" ] && return 0
[ ! -b "$DISK_DEV" ] && error "Device $DISK_DEV cannot be found! Please add it to the 'devices' section of your compose file." && exit 55
local diskDev="$1"
local diskType="$2"
local diskIndex="$3"
local diskAddress="$4"
local sectors=""
local dev_type=""
dev_type=$(lsblk -no TYPE "$DISK_DEV" 2>/dev/null | head -n1)
local devType
devType=$(lsblk -no TYPE "$diskDev" 2>/dev/null | head -n1)
# Only detect and apply sector sizes for partitions, not whole disks
# Whole disk passthrough with explicit sector sizes causes DSM not to recognize the disk
if [[ "$dev_type" == "part" ]]; then
local result=""
local logical=""
local physical=""
[ -z "$diskDev" ] && return 0
[ ! -b "$diskDev" ] && error "Device $diskDev cannot be found! Please add it to the 'devices' section of your compose file." && exit 55
result=$(fdisk -l "$DISK_DEV" 2>/dev/null | grep -m 1 -o "(logical/physical): .*" | cut -c 21- || true)
# Only detect and apply sector sizes for partitions, not whole disks.
# Whole disk passthrough with explicit sector sizes causes DSM not to recognize the disk.
if [[ "$devType" == "part" ]]; then
local result
local logical="" physical=""
result=$(fdisk -l "$diskDev" 2>/dev/null | grep -m 1 -o "(logical/physical): .*" | cut -c 21- || true)
if [ -n "$result" ]; then
logical="${result%% *}"
@@ -672,13 +710,14 @@ addDevice () {
fi
if [ -z "$logical" ] || [ -z "$physical" ]; then
warn "Failed to determine the sector size for $DISK_DEV"
warn "Failed to determine the sector size for $diskDev"
elif [[ "$physical" != "512" ]]; then
sectors=",logical_block_size=$logical,physical_block_size=$physical"
fi
fi
DISK_OPTS+=$(createDevice "$DISK_DEV" "$DISK_TYPE" "$DISK_INDEX" "$DISK_ADDRESS" "raw" "$DISK_IO" "$DISK_CACHE" "" "$sectors")
DISK_OPTS+=$(createDevice "$diskDev" "$diskType" "$diskIndex" "$diskAddress" "raw" "$DISK_IO" "$DISK_CACHE" "" "$sectors")
return 0
}
@@ -690,7 +729,6 @@ addDevice () {
if ! enabled "$DISK_DISABLE"; then
msg="Initializing disks..."
html "$msg"
enabled "$DEBUG" && echo "$msg"
fi
@@ -723,10 +761,10 @@ case "$DISK_FMT" in
* ) error "Invalid DISK_FMT specified, value \"$DISK_FMT\" is not recognized!" && exit 78 ;;
esac
case "${DISK_TYPE,,}" in
"ide" | "sata" | "nvme" | "usb" | "scsi" | "blk" | "virtio-blk" | "virtio-scsi" | "auto" | "none" ) ;;
* ) error "Invalid DISK_TYPE specified, value \"$DISK_TYPE\" is not recognized!" && exit 80 ;;
esac
if ! validDiskType "$DISK_TYPE"; then
error "Invalid DISK_TYPE specified, value \"$DISK_TYPE\" is not recognized!"
exit 80
fi
if [[ "$DISK_FLAGS" =~ [[:space:]] ]]; then
error "Invalid DISK_FLAGS value '$DISK_FLAGS', spaces are not allowed."
@@ -749,8 +787,7 @@ DISK_OPTS+=$(createDevice "$BOOT" "$DISK_TYPE" "1" "0xa" "raw" "$DISK_IO" "$DISK
DISK_OPTS+=$(createDevice "$SYSTEM" "$DISK_TYPE" "2" "0xb" "raw" "$DISK_IO" "$DISK_CACHE" "" "")
if enabled "$DISK_DISABLE"; then
finishDisks
return 0
finishDisks && return 0
fi
DISK1_FILE="$STORAGE/${DISK_NAME}"
+14 -3
View File
@@ -6,19 +6,26 @@ set -Eeuo pipefail
: "${GPU:="N"}" # GPU passthrough
: "${VGA:="virtio"}" # VGA adaptor
: "${DISPLAY:="none"}" # Display type
: "${LOSSY:="N"}" # Lossy VNC compression
: "${RENDERNODE:="/dev/dri/renderD128"}" # Render node
# Sanitize variables
VGA=$(strip "$VGA")
LOSSY=$(strip "$LOSSY")
DISPLAY=$(strip "$DISPLAY")
RENDERNODE=$(strip "$RENDERNODE")
CPU_VENDOR=$(lscpu | awk '/Vendor ID/{print $3}')
if ! enabled "$GPU" || [[ "$CPU_VENDOR" != "GenuineIntel" || "$ARCH" != "amd64" ]]; then
if ! enabled "$GPU" || isAmdCpu || [[ "$ARCH" != "amd64" ]]; then
[[ "${DISPLAY,,}" == "none" ]] && VGA="none"
DISPLAY_OPTS="-display $DISPLAY -vga $VGA"
if enabled "$LOSSY" && [[ "${DISPLAY,,}" == vnc=* ]]; then
DISPLAY+=",lossy=on"
fi
DISPLAY_OPTS="-display ${DISPLAY} -vga ${VGA}"
return 0
fi
@@ -27,7 +34,7 @@ msg="Configuring display drivers..."
html "$msg"
enabled "$DEBUG" && echo "$msg"
DISPLAY_OPTS="-display egl-headless,rendernode=$RENDERNODE"
DISPLAY_OPTS="-display egl-headless,rendernode=${RENDERNODE}"
DISPLAY_OPTS+=" -vga $VGA"
[ ! -d /dev/dri ] && mkdir -m 755 /dev/dri
@@ -48,6 +55,10 @@ if [ ! -c "$RENDERNODE" ]; then
fi
fi
if [ ! -c "$RENDERNODE" ] || [ ! -r "$RENDERNODE" ] || [ ! -w "$RENDERNODE" ]; then
warn "render device '${RENDERNODE}' is unavailable or inaccessible."
fi
addPackage "xserver-xorg-video-intel" "Intel GPU drivers"
addPackage "qemu-system-modules-opengl" "OpenGL module"
+6 -3
View File
@@ -32,14 +32,17 @@ if ! enabled "$SHUTDOWN"; then
exec "${cmd[@]}" ${ARGS:+ $ARGS}
fi
if [ ! -t 1 ] || [ ! -c /dev/tty ]; then
if ! interactive; then
"${cmd[@]}" ${ARGS:+ $ARGS} &
else
"${cmd[@]}" ${ARGS:+ $ARGS} </dev/tty >/dev/tty &
startConsole
startQemu "${cmd[@]}" ${ARGS:+ $ARGS}
fi
pid=$!
rc=0
wait $! || rc=$?
wait "$pid" || rc=$?
[ -f "$QEMU_END" ] && exit "$rc"
sleep 1 & wait $!
+11 -9
View File
@@ -126,13 +126,6 @@ SPACE=$(df --output=avail -B 1 "$STORAGE" | tail -n 1)
SPACE_GB=$(formatBytes "$SPACE")
(( MIN_SPACE > SPACE )) && error "Not enough free space for installation in $STORAGE, have $SPACE_GB available but need at least 14 GB." && exit 94
# Check if output is to interactive TTY
if [ -t 1 ]; then
PROGRESS="--progress=bar:noscroll"
else
PROGRESS="--progress=dot:giga"
fi
if [[ "$URL" == "file://"* ]]; then
MSG="Copying DSM"
ERR="Failed to copy ${URL:7}"
@@ -160,17 +153,26 @@ else
SIZE=0
REASON=""
PROGRESS=()
OUTPUT=""
LOG=$(mktemp)
[[ "${URL,,}" == *"_72806.pat" ]] && SIZE=361010261
[[ "${URL,,}" == *"_69057.pat" ]] && SIZE=363837333
[[ "${URL,,}" == *"_42218.pat" ]] && SIZE=379637760
/run/progress.sh "$PAT" "$SIZE" "$MSG ([P])..." &
# Use Wget's progress bar in a terminal and progress.sh in container logs.
if [ -t 1 ]; then
PROGRESS=( --show-progress --progress=bar:noscroll )
else
OUTPUT="log"
fi
/run/progress.sh "$PAT" "$SIZE" "$MSG ([P])..." "$OUTPUT" 52428800 &
{
LC_ALL=C wget "$URL" -O "$PAT" --no-verbose --no-check-certificate \
--timeout=30 --no-http-keep-alive --show-progress "$PROGRESS" \
--timeout=30 --no-http-keep-alive "${PROGRESS[@]}" \
--output-file="$LOG"
rc=$?
} || :
+4 -4
View File
@@ -10,16 +10,17 @@ app() {
}
checkConfiguredMemory() {
local wanted msg
if disabled "$RAM_CHECK" || [[ "${RAM_SIZE,,}" == "max" || "${RAM_SIZE,,}" == "half" ]]; then
return 0
fi
local wanted msg avail_mem
wanted=$(numfmt --from=iec "$RAM_SIZE")
avail_mem=$(formatBytes "$RAM_AVAIL")
if (( (wanted + RAM_SPARE) > RAM_AVAIL )); then
msg="Your configured RAM_SIZE of ${RAM_SIZE/G/ GB} is too high for the $AVAIL_MEM of free memory available,"
msg="Your configured RAM_SIZE of ${RAM_SIZE/G/ GB} is too high for the $avail_mem of free memory available,"
if [[ "${FS,,}" == "zfs" ]]; then
info "$msg but since ZFS is active this will be ignored."
else
@@ -28,7 +29,7 @@ checkConfiguredMemory() {
fi
else
if (( (wanted + (RAM_SPARE * 3)) > RAM_AVAIL )); then
msg="your configured RAM_SIZE of ${RAM_SIZE/G/ GB} is very close to the $AVAIL_MEM of free memory available,"
msg="your configured RAM_SIZE of ${RAM_SIZE/G/ GB} is very close to the $avail_mem of free memory available,"
if [[ "${FS,,}" == "zfs" ]]; then
info "$msg but since ZFS is active this will be ignored."
else
@@ -102,7 +103,6 @@ checkMinimumMemory() {
}
getMemoryInfo
AVAIL_MEM=$(formatBytes "$RAM_AVAIL")
checkConfiguredMemory
configureHalfMemory
+852 -351
View File
File diff suppressed because it is too large Load Diff
+171 -34
View File
@@ -2,25 +2,31 @@
set -Eeuo pipefail
: "${SHUTDOWN:="Y"}" # Graceful ACPI shutdown
: "${TIMEOUT:="115"}" # QEMU termination timeout
: "${TIMEOUT:="105"}" # QEMU termination timeout
: "${API_TIMEOUT:="90"}" # External API call timeout
# Configure QEMU for graceful shutdown
API_CMD=6
API_HOST="127.0.0.1:$COM_PORT"
# Configure QEMU for graceful shutdown
SHUTDOWN_SKIP=0
SHUTDOWN_SIGNAL=0
QEMU_END="$QEMU_DIR/qemu.end"
CONSOLE_PID="$QEMU_DIR/console.pid"
CONSOLE_SOCKET="$QEMU_DIR/console.sock"
QEMU_START_PID="$QEMU_DIR/qemu.start.pid"
_trap() {
local func="$1"; shift
local sig
TRAP_PID=$BASHPID
for sig; do
# Capture the local callback and signal while registering the trap.
# shellcheck disable=SC2064
trap "$func $sig" "$sig"
done
@@ -62,19 +68,55 @@ displayReason() {
readQemuPid() {
local -n _pid="$1"
local file
if [ ! -s "$QEMU_PID" ] || ! read -r _pid <"$QEMU_PID"; then
return 1
fi
for file in "$QEMU_START_PID" "$QEMU_PID"; do
if [ -s "$file" ] && read -r _pid < "$file"; then
return 0
fi
done
return 1
}
qemuPidFile() {
local -n _file="$1"
_file="$QEMU_PID"
[ -s "$QEMU_START_PID" ] && _file="$QEMU_START_PID"
return 0
}
waitQemuExit() {
local timeout="${1:-10}"
local file
qemuPidFile file
waitPidFile "$file" "$timeout"
}
waitQemuPid() {
local -n _pid="$1"
local cnt=0 value
while ! readQemuPid value; do
sleep 0.02
cnt=$((cnt + 1))
(( cnt >= 50 )) && return 1
done
_pid="$value"
return 0
}
forceKillQemu() {
local reason="$1"
local pid=""
local display
local pid display
! readQemuPid pid && return 0
! isAlive "$pid" && return 0
@@ -88,29 +130,115 @@ forceKillQemu() {
cleanupHelpers() {
local pids=( "${HOST_PID:-}" "${WSD_PID:-}" \
local pids=( "${HOST_PID:-}" "${WSD_PID:-}" "${CONSOLE_PID:-}" \
"${WEB_PID:-}" "${PASST_PID:-}" "${DNSMASQ_PID:-}" )
mKill "${pids[@]}"
fKill "print.sh"
rm -f -- "$HOST_API_SOCKET" "$HOST_AGENT_SOCKET"
closeNetwork
return 0
}
startConsole() {
local output="${1:-/dev/tty}"
local cnt=0
rm -f -- "$CONSOLE_SOCKET" "$CONSOLE_PID"
if ! stty -icanon -echo isig -ixon min 1 time 0 </dev/tty; then
error "Failed to configure serial console terminal!"
return 1
fi
(
trap '' INT QUIT
exec nc -lU "$CONSOLE_SOCKET" </dev/tty >"$output"
) &
local pid="$!"
echo "$pid" > "$CONSOLE_PID"
while [ ! -S "$CONSOLE_SOCKET" ]; do
if ! isAlive "$pid"; then
rm -f -- "$CONSOLE_PID"
error "Serial console relay exited unexpectedly!"
return 1
fi
sleep 0.02
cnt=$((cnt + 1))
if (( cnt > 100 )); then
error "Failed to start serial console relay!"
return 1
fi
done
return 0
}
stopConsole() {
mKill "$CONSOLE_PID"
return 0
}
startQemu() {
rm -f -- "$QEMU_START_PID"
(
trap '' INT QUIT
# shellcheck disable=SC2016
exec setsid -f -w sh -c '
file=$1
shift
"$@" &
pid=$!
printf "%s\n" "$pid" > "$file" || exit 1
rc=0
wait "$pid" 2>/dev/null || rc=$?
exit "$rc"
' sh "$QEMU_START_PID" "$@"
) </dev/null &
return 0
}
finish() {
local reason=$1
local reason=$1 failed=0
if [ ! -f "$QEMU_END" ] && (( reason != 0 )); then
failed=1
fi
touch "$QEMU_END"
forceKillQemu "$reason"
cleanupHelpers
if ! waitPidFile "$QEMU_PID" 10; then
if ! waitQemuExit 10; then
warn "Timed out while waiting for $(app) to exit!"
fi
(( reason != 1 )) && echo && echo " Shutdown completed!"
echo
if (( failed == 0 )); then
echo " Shutdown completed!"
else
error "QEMU exited unexpectedly!"
fi
exit "$reason"
}
@@ -119,15 +247,14 @@ sendGuestShutdown() {
local pid="$1"
local response
local url
# 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' || :
# Send shutdown command to guest agent via serial port
API_TIMEOUT=$(strip "$API_TIMEOUT")
url="http://$API_HOST/read?command=$API_CMD&timeout=$API_TIMEOUT"
response=$(curl -sk -m "$(( API_TIMEOUT+2 ))" -S "$url" 2>&1)
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)
if [[ "$response" =~ "\"success\"" ]]; then
@@ -150,13 +277,10 @@ normalizeTimeout() {
local term_grace=3 # seconds before loop ends to send SIGTERM
local cleanup_grace=3 # seconds reserved after the loop for cleanup
local elapsed
local timeout_left
local min
TIMEOUT=$(strip "$TIMEOUT")
if [[ ! "$TIMEOUT" =~ ^[0-9]+$ ]]; then
TIMEOUT=115
TIMEOUT=105
fi
if (( TIMEOUT >= 30 )); then
@@ -167,10 +291,10 @@ normalizeTimeout() {
cleanup_grace=4
fi
elapsed=$((SECONDS - start))
timeout_left=$((TIMEOUT - elapsed))
local elapsed=$((SECONDS - start))
local timeout_left=$((TIMEOUT - elapsed))
min=$((term_grace + cleanup_grace + 1))
local min=$((term_grace + cleanup_grace + 1))
(( timeout_left < min )) && timeout_left=$min
wait_until=$((timeout_left - cleanup_grace))
@@ -184,18 +308,17 @@ waitForShutdown() {
local cnt=0
local pid="$1"
local name="$APP"
local slp
while (( cnt <= wait_until )); do
while (( cnt <= wait_until && SHUTDOWN_SKIP == 0 )); do
sleep 1 &
slp=$!
local slp=$!
# Stop waiting if the process has exited
! isAlive "$pid" && break
# Workaround for stale/zombie QEMU pid file
[ ! -s "$QEMU_PID" ] && break
[ ! -s "$QEMU_START_PID" ] && [ ! -s "$QEMU_PID" ] && break
if (( cnt == sigterm_at )); then
info "${name^} is still running, sending SIGTERM... ($cnt/$wait_until)"
@@ -204,7 +327,7 @@ waitForShutdown() {
info "Waiting for $name to shut down... ($cnt/$wait_until)"
fi
wait "$slp"
wait "$slp" || :
(( cnt++ ))
done
@@ -215,29 +338,39 @@ waitForShutdown() {
graceful_shutdown() {
local sig="$1"
local pid=""
local code=0
local pid code
[[ $BASHPID != "$TRAP_PID" ]] && return
code=$(signalCode "$sig")
if [ -f "$QEMU_END" ]; then
echo && info "Received $1 signal while already shutting down..."
if (( code == 130 && SHUTDOWN_SIGNAL == code )); then
SHUTDOWN_SKIP=1
echo && info "Received SIGINT again, forcing shutdown..."
return
fi
echo && info "Received $sig signal while already shutting down..."
return
fi
set +e
start=$SECONDS
SHUTDOWN_SIGNAL=$code
touch "$QEMU_END"
echo && info "Received $1 signal, sending shutdown command..."
echo && info "Received $sig signal, sending shutdown command..."
if ! readQemuPid pid; then
warn "QEMU PID file ($QEMU_PID) does not exist?"
finish "$code"
if ! interactive || ! waitQemuPid pid; then
warn "QEMU PID file does not exist?"
finish "$code"
fi
fi
if ! isAlive "$pid"; then
if [ -z "$pid" ] || ! isAlive "$pid"; then
warn "QEMU process with PID $pid does not exist?"
finish "$code"
fi
@@ -252,6 +385,10 @@ graceful_shutdown() {
! enabled "$SHUTDOWN" && return 0
[ -n "${QEMU_TIMEOUT:-}" ] && TIMEOUT="$QEMU_TIMEOUT"
if interactive; then
_trap graceful_shutdown SIGINT
fi
_trap graceful_shutdown SIGTERM SIGHUP SIGABRT SIGQUIT
return 0
+29 -14
View File
@@ -9,6 +9,7 @@ cd /run
. utils.sh # Load functions
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; }
disabled "$NETWORK" && exit 0
@@ -19,8 +20,9 @@ driver="/run/shm/qemu.nic"
page="/run/shm/index.html"
address="/run/shm/qemu.ip"
shutdown="/run/shm/qemu.end"
socket="/run/shm/qemu-host-api.sock"
template="/var/www/index.html"
url="http://127.0.0.1:2210/read?command=10"
url="http://localhost/read?command=10"
resp_err="Guest returned an invalid response:"
curl_err="Failed to connect to guest: curl error"
@@ -35,9 +37,7 @@ exitIfShuttingDown() {
queryGuest() {
local rc
{ json=$(curl -m 20 -sk "$url"); rc=$?; } || :
{ json=$(curl --unix-socket "$socket" -m 20 -sk "$url"); local rc=$?; } || :
exitIfShuttingDown
@@ -53,9 +53,8 @@ readJsonField() {
local query="$1"
local result
local rc
{ result=$(jq -r "$query" <<< "$json"); rc=$?; } || :
{ result=$(jq -r "$query" <<< "$json"); local rc=$?; } || :
if (( rc != 0 )); then
error "$jq_err $rc ( $json )"
@@ -73,12 +72,12 @@ readJsonField() {
readGuestStatus() {
local result msg rc
local result msg
result=$(readJsonField '.status') || return 1
if [[ "$result" != "success" ]]; then
{ msg=$(jq -r '.message // empty' <<< "$json"); rc=$?; } || :
{ msg=$(jq -r '.message // empty' <<< "$json"); local rc=$?; } || :
if (( rc != 0 )); then
error "$jq_err $rc ( $json )"
@@ -139,14 +138,29 @@ pollGuestLocation() {
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() {
local title body script html
local html
msg="http://$location"
title="<title>Virtual DSM</title>"
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 title="<title>Virtual DSM</title>"
local body="The location of DSM is <a href='http://$location'>http://$location</a>"
local script="<script>setTimeout(function(){ window.location.assign('http://$location'); }, 3000);</script>"
html=$(<"$template")
html="${html/\[1\]/$title}"
@@ -163,11 +177,11 @@ writeDhcpPage() {
buildStaticMessage() {
local nic ip port
local nic ip
nic=$(<"$driver")
ip=$(<"$address")
port="${location##*:}"
local port="${location##*:}"
if [[ "${nic,,}" != "macvlan" ]]; then
msg="port $port"
@@ -195,6 +209,7 @@ exitIfShuttingDown
location=$(<"$file")
if enabled "$DHCP"; then
checkAddressConflict
writeDhcpPage
else
buildStaticMessage
+3 -5
View File
@@ -45,7 +45,7 @@ checkClocksource() {
checkSse42() {
if ! grep -qw "sse4_2" <<< "$flags"; then
if ! hasFlag "sse4_2"; then
error "Your CPU does not have the SSE4 instruction set that Virtual DSM requires!"
! enabled "$DEBUG" && exit 88
fi
@@ -94,10 +94,10 @@ configureKvmCpuModel() {
appendKvmInvtscFeature() {
if grep -qw "svm" <<< "$flags"; then
if hasFlag "svm"; then
# AMD processor
if grep -qw "tsc_scale" <<< "$flags"; then
if hasFlag "tsc_scale"; then
CPU_FEATURES+=",+invtsc"
fi
@@ -186,8 +186,6 @@ configureHostCpuName() {
selectClocksource
checkClocksource
flags=$(sed -ne '/^flags/s/^.*: //p' /proc/cpuinfo)
if ! disabled "$KVM"; then
configureKvm
else
+280 -23
View File
@@ -2,44 +2,301 @@
set -Eeuo pipefail
info="/run/shm/msg.html"
info_tmp="${info}.${BASHPID}.tmp"
escape () {
local s
s=${1//&/\&amp;}
s=${s//</\&lt;}
s=${s//>/\&gt;}
s=${s//'"'/\&quot;}
printf -- %s "$s"
escape() {
local s
s=${1//&/\&amp;}
s=${s//</\&lt;}
s=${s//>/\&gt;}
s=${s//'"'/\&quot;}
s=${s//"'"/\&#39;}
printf '%s' "$s"
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() {
local path="$1"
local mode="$2"
local bytes="0"
if [[ "$mode" == "counter" ]]; then
if [ -r "$path" ]; then
read -r bytes < "$path" || bytes="0"
fi
[[ "$bytes" =~ ^[0-9]+$ ]] || bytes="0"
printf '%s\n' "$bytes"
return 0
fi
if [ ! -s "$path" ] && [ ! -d "$path" ]; then
printf '0\n'
return 0
fi
if [[ "$mode" == "allocated" ]]; then
bytes=$(du -sB1 -- "$path" 2>/dev/null | cut -f1) || bytes="0"
else
bytes=$(du -sb -- "$path" 2>/dev/null | cut -f1) || bytes="0"
fi
printf '%s\n' "$bytes"
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() {
local bytes="$1"
local size
size=$(numfmt --to=iec --suffix=B "$bytes" |
sed -r 's/([A-Z])/ \1/') ||
size="${bytes} bytes"
printf '%s' "$size"
return 0
}
printPercentProgress() {
local percent="$1"
while (( next_percent <= percent && next_percent <= 100 )); do
if [[ "$printed" == "Y" ]]; then
printf ' → %s%%' "$next_percent"
else
printf '%s%%' "$next_percent"
fi
printed="Y"
next_percent=$((next_percent + 10))
done
return 0
}
printCurrentSize() {
local bytes="$1"
local size
size=$(formatSize "$bytes")
if [[ "$printed" == "Y" ]]; then
printf ' → %s' "$size"
else
printf '%s' "$size"
fi
printed="Y"
return 0
}
printSizeProgress() {
local bytes="$1"
local size
while (( bytes >= next_bytes )); do
size=$(formatSize "$next_bytes")
if [[ "$printed" == "Y" ]]; then
printf ' → %s' "$size"
else
printf '%s' "$size"
fi
printed="Y"
next_bytes=$((next_bytes + step_bytes))
done
return 0
}
stopProgress() {
if [ -z "$status_file" ]; then
exit 0
fi
stopping="Y"
return 0
}
finishProgress() {
rm -f -- "$info_tmp"
if [[ "$output" == "log" && "$printed" == "Y" ]]; then
printf '\n'
fi
return 0
}
path="$1"
total="$2"
body=$(escape "$3")
output="${4:-}"
step_bytes="${5:-536870912}"
mode="${6:-apparent}"
status_file="${7:-}"
if [[ -n "$total" && ! "$total" =~ ^(0|[1-9][0-9]*)$ ]]; then
printf 'Invalid total size: %s\n' "$total" >&2
exit 2
fi
if [[ ! "$step_bytes" =~ ^[1-9][0-9]*$ ]]; then
printf 'Invalid progress interval: %s\n' "$step_bytes" >&2
exit 2
fi
case "$mode" in
apparent | allocated | counter ) ;;
* )
printf 'Invalid progress mode: %s\n' "$mode" >&2
exit 2
;;
esac
case "$output" in
"" | log ) ;;
* )
printf 'Invalid progress output: %s\n' "$output" >&2
exit 2
;;
esac
printed="N"
next_percent=10
next_bytes="$step_bytes"
log_mode="percent"
stopping="N"
if [ -z "$total" ] || [[ "$total" == "0" ]]; then
log_mode="size"
fi
trap finishProgress EXIT
trap 'exit 0' HUP INT QUIT
trap stopProgress TERM
if [[ "$body" == *"..." ]]; then
body="<p class=\"loading\">${body::-3}</p>"
fi
while true
do
while true; do
if [ ! -s "$path" ] && [ ! -d "$path" ]; then
bytes="0"
else
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="${bytes} bytes"
else
size="$(echo "$bytes" "$total" | awk '{printf "%.1f", $1 * 100 / $2}')"
size="$size%"
final_pass="${stopping:-}"
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
[[ "$size" != "0.0%" ]] && echo "${body//(\[P\])/($size)}"> "$info"
fi
sleep 1 & wait $!
# 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
write_html="Y"
if [ -z "$effective_total" ] ||
[[ "$effective_total" == "0" ]] ||
(( bytes > effective_total )); then
size=$(formatSize "$bytes")
if [[ "$output" == "log" ]]; then
if [[ "$log_mode" == "percent" ]]; then
printCurrentSize "$bytes"
next_bytes=$(((bytes / step_bytes + 1) * step_bytes))
log_mode="size"
else
printSizeProgress "$bytes"
fi
fi
else
# Truncate to one decimal so progress is never reported early.
progress=$((bytes * 1000 / effective_total))
(( progress > 1000 )) && progress=1000
percent=$((progress / 10))
printf -v size '%d.%d%%' \
"$((progress / 10))" \
"$((progress % 10))"
if [[ "$output" == "log" ]]; then
if [[ "$log_mode" == "size" ]]; then
printSizeProgress "$bytes"
else
printPercentProgress "$percent"
fi
fi
# Do not update the web viewer until at least 0.1% is reached.
(( progress == 0 )) && write_html="N"
fi
if [[ "$write_html" == "Y" ]]; then
writeInfo "${body//(\[P\])/($size)}"
fi
fi
[[ "$final_pass" == "Y" ]] && break
sleep 1 &
wait $! || :
done
+11 -18
View File
@@ -13,6 +13,7 @@ enabled "${TRACE:-}" && set -o functrace && trap 'echo "# $BASH_COMMAND" >&2' DE
: "${KVM:="Y"}" # KVM acceleration
: "${DEBUG:="N"}" # Disable debugging mode
: "${COUNTRY:=""}" # Country code for mirror
: "${MACHINE:="q35"}" # Machine type selection
: "${ALLOCATE:=""}" # Preallocate diskspace
: "${ARGUMENTS:=""}" # Extra QEMU parameters
: "${CPU_CORES:="2"}" # Amount of CPU cores
@@ -40,7 +41,7 @@ detectEngine() {
detectRootless() {
local uid_map=""
local uid_map
uid_map=$(awk '{$1=$1; print}' /proc/self/uid_map 2>/dev/null || true)
@@ -57,8 +58,6 @@ checkPrivileged() {
local cap_bnd
local last_cap
local max_cap
# Get the capability bounding set
cap_bnd=$(grep '^CapBnd:' /proc/$$/status | awk '{print $2}')
cap_bnd=$(printf "%d" "0x${cap_bnd}")
@@ -67,7 +66,7 @@ checkPrivileged() {
last_cap=$(cat /proc/sys/kernel/cap_last_cap)
# 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
PRIVILEGED="Y"
@@ -143,9 +142,7 @@ finiteMemoryLimit() {
local limit="$1"
local sentinel="4611686018427387904"
local i=0
local left=""
local right=""
local i
[[ "$limit" =~ ^[0-9]+$ ]] || return 1
@@ -153,8 +150,8 @@ finiteMemoryLimit() {
(( ${#limit} > ${#sentinel} )) && return 1
for (( i=0; i<${#sentinel}; i++ )); do
left="${limit:i:1}"
right="${sentinel:i:1}"
local left="${limit:i:1}"
local right="${sentinel:i:1}"
(( left < right )) && return 0
(( left > right )) && return 1
@@ -165,11 +162,8 @@ finiteMemoryLimit() {
getMemoryInfo() {
local host_total=""
local host_avail=""
local limit=""
local current=""
local available=""
local limit="" current=""
local host_total host_avail
host_total=$(free -b | awk '/^Mem:/ {print $2; exit}')
host_avail=$(free -b | awk '/^Mem:/ {print $7; exit}')
@@ -188,7 +182,7 @@ getMemoryInfo() {
if finiteMemoryLimit "$limit" && [[ "$current" =~ ^[0-9]+$ ]]; then
(( limit < RAM_TOTAL )) && RAM_TOTAL="$limit"
available=$(( limit - current ))
local available=$(( limit - current ))
(( available < 0 )) && available=0
(( available < RAM_AVAIL )) && RAM_AVAIL="$available"
fi
@@ -320,9 +314,8 @@ FOOTER2="<a href='$SUPPORT'>$SUPPORT</a>"
SOCKETS=1
CPU=$(cpu)
SYS=$(uname -r)
KERNEL=$(echo "$SYS" | cut -b 1)
MINOR=$(echo "$SYS" | cut -d '.' -f2)
ARCH=$(dpkg --print-architecture)
IFS=. read -r KERNEL MINOR _ <<< "$SYS"
CORES=$(grep -c '^processor' /proc/cpuinfo)
if grep -qi "socket(s)" <<< "$(lscpu)"; then
@@ -353,7 +346,7 @@ checkKvm
# Cleanup files
rm -f "$QEMU_DIR"/dsm.url
rm -f "$QEMU_DIR"/qemu.* "$QEMU_DIR"/*.pid "$QEMU_DIR"/*.sock
rm -f "$QEMU_DIR"/{qemu.*,*.{pid,sock,pipe}}
# Cleanup dirs
rm -rf /tmp/dsm
+50 -16
View File
@@ -15,6 +15,10 @@ HOST_MODEL=$(strip "$HOST_MODEL")
HOST_SERIAL=$(strip "$HOST_SERIAL")
GUEST_SERIAL=$(strip "$GUEST_SERIAL")
HOST_PID="$QEMU_DIR/host.pid"
HOST_API_SOCKET="$QEMU_DIR/qemu-host-api.sock"
HOST_AGENT_SOCKET="$QEMU_DIR/qemu-host-agent.sock"
validateHostMac() {
local m
@@ -41,6 +45,8 @@ buildHostArguments() {
HOST_ARGS=()
HOST_ARGS+=("-cpu=$CPU_CORES")
HOST_ARGS+=("-cpu_arch=$HOST_CPU")
HOST_ARGS+=("-api=$HOST_API_SOCKET")
HOST_ARGS+=("-addr=$HOST_AGENT_SOCKET")
[ -n "$HOST_MAC" ] && HOST_ARGS+=("-mac=$HOST_MAC")
[ -n "$HOST_MODEL" ] && HOST_ARGS+=("-model=$HOST_MODEL")
@@ -52,30 +58,47 @@ buildHostArguments() {
startHostBinary() {
local pid
rm -f -- "$HOST_PID" "$HOST_API_SOCKET" "$HOST_AGENT_SOCKET" || return 1
if enabled "$HOST_DEBUG"; then
set -x
./host.bin "${HOST_ARGS[@]}" &
{ set +x; } 2>/dev/null
echo "$!" > "$HOST_PID"
pid=$!
echo
else
./host.bin "${HOST_ARGS[@]}" >/dev/null &
echo "$!" > "$HOST_PID"
pid=$!
fi
echo "$pid" > "$HOST_PID"
return 0
}
waitForPort() {
waitForSocket() {
local port="$1"
local socket="$1"
local exit_code="$2"
local cnt=0
local pid cnt=0
while [ ! -S "$socket" ]; do
if ! read -r pid < "$HOST_PID" || ! isAlive "$pid"; then
error "qemu-host exited unexpectedly!"
exit "$exit_code"
fi
while ! nc -z -w2 127.0.0.1 "$port" > /dev/null 2>&1; do
sleep 0.1
cnt=$((cnt + 1))
(( cnt > 50 )) && error "Failed to connect to qemu-host.." && exit "$exit_code"
if (( cnt > 50 )); then
error "Failed to create qemu-host socket: $socket"
exit "$exit_code"
fi
done
return 0
@@ -83,10 +106,25 @@ waitForPort() {
configureSerialPorts() {
# Configure serial ports
SERIAL_OPTS="-serial mon:stdio \
if enabled "${SHUTDOWN:-Y}" && interactive; then
CONSOLE_SOCKET="$QEMU_DIR/console.sock"
MONITOR_SOCKET="$QEMU_DIR/monitor.sock"
SERIAL_OPTS="-chardev socket,id=console0,path=$CONSOLE_SOCKET,reconnect-ms=1000 \
-serial chardev:console0 \
-chardev socket,id=monitor0,path=$MONITOR_SOCKET,server=on,wait=off \
-mon chardev=monitor0,mode=readline"
else
SERIAL_OPTS="-serial mon:stdio"
fi
SERIAL_OPTS+=" \
-device virtio-serial-pci,id=virtio-serial0,bus=pcie.0,addr=0x3 \
-chardev socket,id=charchannel0,host=127.0.0.1,port=$CHR_PORT,reconnect=10 \
-chardev socket,id=charchannel0,path=$HOST_AGENT_SOCKET,reconnect-ms=1000 \
-device virtserialport,bus=virtio-serial0.0,nr=1,chardev=charchannel0,id=channel0,name=vchannel"
return 0
@@ -94,15 +132,11 @@ configureSerialPorts() {
validateHostMac
HOST_PID="$QEMU_DIR/host.pid"
buildHostArguments
startHostBinary
sleep 0.2
waitForPort "$COM_PORT" 58
waitForPort "$CHR_PORT" 59
waitForSocket "$HOST_API_SOCKET" 58
waitForSocket "$HOST_AGENT_SOCKET" 59
configureSerialPorts
+77 -25
View File
@@ -1,41 +1,49 @@
#!/usr/bin/env bash
set -Eeuo pipefail
: "${COM_PORT:="2210"}" # Comm port
: "${WEB_PORT:="5000"}" # Webserver port
: "${CHR_PORT:="12345"}" # Character port
: "${WSD_PORT:="8004"}" # Websockets port
# Sanitize port variables
COM_PORT=$(strip "$COM_PORT")
WEB_PORT=$(strip "$WEB_PORT")
CHR_PORT=$(strip "$CHR_PORT")
WSD_PORT=$(strip "$WSD_PORT")
WEB_PID="/run/nginx.pid"
WSD_LOG="/var/log/websocketd.log"
WSD_PID="$QEMU_DIR/websocketd.pid"
prepareWebFiles() {
cp -r /var/www/* "$QEMU_DIR" || return 1
rm -f "$WSD_PID" "$WEB_PID" || return 1
rm -f -- "$WSD_PID" "$WEB_PID" "$WSD_LOG" || 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
if ! sed -i \
-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
}
configureIpv6Listen() {
# shellcheck disable=SC2143
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
return 0
@@ -52,6 +60,22 @@ configureWebServer() {
return 0
}
stopWebServer() {
local pid
if [ -s "$WEB_PID" ] && read -r pid < "$WEB_PID" && [ -n "$pid" ]; then
pKill "$pid" 2
if isAlive "$pid"; then
kill -9 -- "$pid" 2>/dev/null || :
fi
fi
rm -f -- "$WEB_PID"
return 0
}
startWebServer() {
# Start webserver
@@ -60,41 +84,69 @@ startWebServer() {
return 0
}
stopWebsocketServer() {
local pid
if [ -s "$WSD_PID" ] && read -r pid < "$WSD_PID" && [ -n "$pid" ]; then
pKill "$pid" 2
if isAlive "$pid"; then
kill -9 -- "$pid" 2>/dev/null || :
fi
fi
rm -f -- "$WSD_PID"
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 > "$log" 2>&1 &
websocketd \
--address 127.0.0.1 \
--port="$WSD_PORT" \
/run/socket.sh \
>"$WSD_LOG" 2>&1 &
local pid=$!
if ! echo "$pid" > "$WSD_PID"; then
kill "$pid" 2>/dev/null || :
rm -f -- "$WSD_PID"
return 1
fi
sleep 0.1
local i
for (( i = 1; i <= 5; i++ )); do
if ! isAlive "$pid"; then
rm -f "$WSD_PID"
[ -s "$log" ] && cat "$log" >&2
error "Failed to start websocket server!"
return 1
fi
if ! isAlive "$pid"; then
rm -f -- "$WSD_PID"
[ -s "$WSD_LOG" ] && cat "$WSD_LOG" >&2
error "Failed to start websocket server!"
return 1
fi
sleep 0.1
done
return 0
}
prepareWebFiles || return 1
prepareWebFiles
html "Starting $APP for $ENGINE..."
disabled "${WEB:-}" && return 0
configureWebServer || return 1
configureWebServer
startWebServer || return 1
startWebsocketServer || return 1
if startWebServer && startWebsocketServer; then
return 0
fi
return 0
stopWebsocketServer || :
stopWebServer || :
return 1
+18 -5
View File
@@ -3,6 +3,8 @@ set -Eeuo pipefail
lastmsg=""
path="/run/shm/msg.html"
dir=$(dirname -- "$path")
name=$(basename -- "$path")
refresh() {
@@ -17,15 +19,26 @@ refresh() {
lastmsg="$msg"
echo "s: $msg"
return 0
}
refresh
inotifywait -m "$path" |
while read -r fp event fn; do
inotifywait \
-m -q \
-e close_write,moved_to,delete \
--format '%e %f' \
"$dir" |
while read -r event file; do
[[ "$file" == "$name" ]] || continue
case "${event,,}" in
"modify"* ) refresh ;;
"delete_self" ) echo "c: vnc" ;;
esac
"delete"* )
echo "c: vnc" ;;
"close_write"* | "moved_to"* )
refresh ;;
esac
done
+67 -28
View File
@@ -7,6 +7,34 @@ 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; }
warn () { printf "%b%s%b" "\E[1;31m " "Warning: ${1:-}" "\E[0m\n" >&2; }
hasFlag() {
# Match a whitespace-delimited token in /proc/cpuinfo
grep -m1 '^flags[[:space:]]*:' /proc/cpuinfo | grep -Fqw -- "$1"
}
hasFeature() {
# Match a whitespace-delimited token in /proc/cpuinfo
grep -m1 '^Features[[:space:]]*:' /proc/cpuinfo | grep -Fqw -- "$1"
}
isAmdCpu() {
local vendor
vendor=$(awk -F ': *' '/^vendor_id/{print $2; exit}' /proc/cpuinfo)
[[ "$vendor" == "AuthenticAMD" ]]
}
interactive() {
[ -t 0 ] && : 2>/dev/null </dev/tty >/dev/tty
}
strip() {
local value="${1:-}"
@@ -105,8 +133,7 @@ waitPid() {
waitPidFile() {
local i=0
local pid=""
local i=0 pid
local file="$1"
local timeout="${2:-10}"
@@ -173,7 +200,7 @@ fKill() {
sKill() {
local pid=""
local pid
local file="$1"
[ ! -s "$file" ] && return 0
@@ -272,6 +299,25 @@ writeFile() {
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() {
local path="$1"
@@ -334,8 +380,7 @@ restoreState() {
escape () {
local s
s=${1//&/\&amp;}
local s=${1//&/\&amp;}
s=${s//</\&lt;}
s=${s//>/\&gt;}
s=${s//'"'/\&quot;}
@@ -345,11 +390,23 @@ escape () {
return 0
}
escapeXML() {
printf '%s' "$1" | sed \
-e 's/&/\&amp;/g' \
-e 's/</\&lt;/g' \
-e 's/>/\&gt;/g' \
-e 's/"/\&quot;/g' \
-e "s/'/\&apos;/g"
return 0
}
html() {
local title
local body
local script
local script="${2:-}"
local footer
title=$(escape "$APP")
@@ -361,8 +418,6 @@ html() {
body="<p class=\"loading\">${body/.../}</p>"
fi
[ -n "${2:-}" ] && script="$2" || script=""
local HTML
HTML=$(<"$TEMPLATE")
HTML="${HTML/\[1\]/$title}"
@@ -371,8 +426,8 @@ html() {
HTML="${HTML/\[4\]/$footer}"
HTML="${HTML/\[5\]/$FOOTER2}"
echo "$HTML" > "$PAGE" || return 1
echo "$body" > "$INFO" || return 1
writeAtomic "$PAGE" "$HTML" || return 1
writeAtomic "$INFO" "$body" || return 1
return 0
}
@@ -415,29 +470,13 @@ cpu() {
return 0
}
hasDisk() {
enabled "${DISK_DISABLE:-}" && return 1
[ -b "/disk" ] && return 0
[ -b "/disk1" ] && return 0
[ -b "/dev/disk1" ] && return 0
[ -b "${DEVICE:-}" ] && return 0
[ -z "${DISK_NAME:-}" ] && DISK_NAME="data"
[ -s "$STORAGE/$DISK_NAME.img" ] && return 0
[ -s "$STORAGE/$DISK_NAME.qcow2" ] && return 0
return 1
}
getCountry() {
local url=$1
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
{ result=$(echo "$json" | jq -r "$query" 2> /dev/null); rc=$?; } || :
+41 -8
View File
@@ -1,15 +1,27 @@
var timer;
var request;
var booting = false;
var interval = 1000;
function abortRequest() {
if (!request) {
return false;
}
request.onreadystatechange = null;
request.abort();
request = null;
return true;
}
function getInfo() {
var url = "msg.html";
try {
if (request) {
request.abort();
}
abortRequest();
if (window.XMLHttpRequest) {
request = new XMLHttpRequest();
@@ -56,7 +68,10 @@ function processInfo() {
return true;
}
var msg = request.responseText;
var response = request;
request = null;
var msg = response.responseText;
if (msg == null || msg.length == 0) {
if (booting) {
@@ -68,9 +83,9 @@ function processInfo() {
return false;
}
var notFound = (request.status == 404);
var notFound = (response.status == 404);
if (request.status == 200) {
if (response.status == 200) {
if (msg.toLowerCase().indexOf("<html>") !== -1) {
notFound = true;
} else {
@@ -88,7 +103,7 @@ function processInfo() {
return true;
}
setError("Error: Received statuscode " + request.status);
setError("Error: Received statuscode " + response.status);
return false;
} catch (e) {
@@ -157,7 +172,9 @@ function setError(text) {
}
function schedule() {
setTimeout(getInfo, interval);
clearTimeout(timer);
timer = setTimeout(getInfo, interval);
}
function reload() {
@@ -179,11 +196,27 @@ function connect() {
switch (cmd) {
case "s":
var aborted = abortRequest();
processMsg(msg);
if (aborted &&
msg.toLowerCase().indexOf("href=") == -1) {
schedule();
}
break;
case "e":
if (abortRequest()) {
schedule();
}
setError(msg);
break;
default:
console.warn("Unknown event: " + cmd);
break;