mirror of
https://github.com/vdsm/virtual-dsm.git
synced 2026-08-29 04:07:02 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
63e4529eff | ||
|
|
afd848cebb | ||
|
|
695b075130 | ||
|
|
7eff53e722 | ||
|
|
bcaf0e5980 | ||
|
|
faa820c2cc | ||
|
|
81e477fcc4 | ||
|
|
abd3a1c3df | ||
|
|
3b59bcd284 | ||
|
|
f1b56f394a | ||
|
|
1a983ebcd1 | ||
|
|
41c76198fa | ||
|
|
222b5649b0 | ||
|
|
e14fd4b711 | ||
|
|
6fe19a8af4 | ||
|
|
072b5c3070 | ||
|
|
89dbaeb2a4 | ||
|
|
02f76b44cd | ||
|
|
99d6b8f830 | ||
|
|
e6b68b2f08 | ||
|
|
7a8f0041e7 | ||
|
|
64809bb4d4 | ||
|
|
f08b2cbd99 | ||
|
|
18d9ecd434 | ||
|
|
6944293a32 | ||
|
|
d807a365ff | ||
|
|
066af83d07 | ||
|
|
81536818df | ||
|
|
949c288565 | ||
|
|
5d5bbbcf4d | ||
|
|
82a62c0240 | ||
|
|
5399e1d463 | ||
|
|
c7e7b60f01 | ||
|
|
f1defa6890 | ||
|
|
a7fb161974 | ||
|
|
dcdf5e5293 | ||
|
|
1eb0db66ba | ||
|
|
3eec8e03ed | ||
|
|
5483981ed4 | ||
|
|
62330a5bf4 | ||
|
|
2b0b59dfba | ||
|
|
b680ff9dd8 | ||
|
|
5c889b272d | ||
|
|
585ebb3f53 | ||
|
|
401307b981 | ||
|
|
46820fe6eb | ||
|
|
9452d48694 | ||
|
|
5c3fed1387 | ||
|
|
797411ae7f | ||
|
|
e81c509208 | ||
|
|
ace8614a50 | ||
|
|
8263b3a737 | ||
|
|
61c6142988 | ||
|
|
5d26338bb3 | ||
|
|
084d475cde | ||
|
|
c36b0dcb00 | ||
|
|
6e9993742b | ||
|
|
6946af0212 | ||
|
|
0a9361d62f | ||
|
|
7cbfcd44a6 | ||
|
|
6bcacf5e59 | ||
|
|
3bea87f087 | ||
|
|
f01e1564f7 | ||
|
|
cf8cdbf0c1 | ||
|
|
0813494ee0 | ||
|
|
0d71e30f50 | ||
|
|
c203deab9a | ||
|
|
9cf31df266 | ||
|
|
0cd08f2ee1 | ||
|
|
a32d215e90 | ||
|
|
c1f9a690f9 | ||
|
|
5de504deb3 | ||
|
|
250eb18238 | ||
|
|
ca80da59bd | ||
|
|
9cd86dfe82 | ||
|
|
5bb93651f7 | ||
|
|
0c268308a4 | ||
|
|
7b741d3e4b | ||
|
|
f1d29212ae | ||
|
|
fdee9f89a3 | ||
|
|
210a360797 | ||
|
|
991bb2627a | ||
|
|
764a186836 | ||
|
|
726786a98a | ||
|
|
2caf97d407 | ||
|
|
0e67286599 | ||
|
|
2b9fc80275 | ||
|
|
0d88774bd3 | ||
|
|
fa23052ed4 | ||
|
|
cc3267f258 | ||
|
|
fd002564a6 | ||
|
|
2703a97cfa | ||
|
|
29e2d034bf | ||
|
|
cbe3644335 |
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
+7
-4
@@ -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
|
||||
@@ -83,6 +86,6 @@ ENV RAM_SIZE="2G"
|
||||
ENV CPU_CORES="2"
|
||||
ENV DISK_SIZE="256G"
|
||||
|
||||
HEALTHCHECK --interval=60s --start-period=45s --retries=2 CMD /run/check.sh
|
||||
HEALTHCHECK --interval=60s --start-period=45s --retries=2 CMD ["/run/check.sh"]
|
||||
|
||||
ENTRYPOINT ["/usr/bin/tini", "-s", "/run/entry.sh"]
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
# Environment Variables
|
||||
|
||||
This page lists all the environment variables that can be used to configure the container.
|
||||
|
||||
An empty default means the variable is unset and its value is determined automatically when applicable.
|
||||
|
||||
## 💽 Virtual DSM
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `URL` | | URL or local path to 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. |
|
||||
| `GUEST_SERIAL` | | Synology guest serial number reported to DSM. |
|
||||
|
||||
## 🧠 CPU and Memory
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `CPU_CORES` | `2` | Number of virtual CPU cores, such as `4`, `half`, or `max`. |
|
||||
| `CPU_MODEL` | `host` | QEMU CPU model. |
|
||||
| `CPU_FLAGS` | | Additional QEMU CPU flags. |
|
||||
| `HOST_CPU` | | CPU name reported to DSM. Selected automatically when unset. |
|
||||
| `KVM` | `Y` | Enables KVM hardware acceleration. |
|
||||
| `RAM_SIZE` | `2G` | Amount of RAM assigned to DSM, such as `2G`, `4G`, `half`, or `max`. |
|
||||
| `RAM_CHECK` | `Y` | Checks whether enough host memory is available before starting DSM. |
|
||||
|
||||
## 💾 Storage
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `DISK_SIZE` | `256G` | Size of the main data disk. |
|
||||
| `DISK_FMT` | `raw` | Disk image format: `raw` or `qcow2`. |
|
||||
| `DISK_TYPE` | `scsi` | Disk device type, such as `sata`, `scsi`, `nvme`, or `blk`. |
|
||||
| `DISK_CACHE` | `none` | Disk cache mode, such as `none` or `writeback`. |
|
||||
| `DISK_IO` | `native` | Disk I/O mode, such as `native`, `threads`, or `io_uring`. |
|
||||
| `DISK_DISCARD` | `unmap` | Discard/TRIM mode for the primary disk. |
|
||||
| `DISK_ROTATION` | `1` | Rotation rate reported to the guest. Use `1` to identify the disk as an SSD. |
|
||||
| `DISK_FLAGS` | | Additional options used when creating `qcow2` disks. |
|
||||
| `ALLOCATE` | `N` | Preallocates space for the data disks. |
|
||||
| `STORAGE` | `/storage` | Storage directory used for disks, settings, and downloads. |
|
||||
|
||||
## 🌐 Networking
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `NETWORK` | | Network mode, such as `nat`, `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 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. |
|
||||
| `USER_PORTS` | | Additional ports to forward to DSM when using user-mode networking. |
|
||||
| `DNSMASQ_OPTS` | | Additional options passed to dnsmasq. |
|
||||
| `DNSMASQ_DEBUG` | `N` | Enables dnsmasq debug output. |
|
||||
| `DNSMASQ_DISABLE` | `N` | Disables the internal dnsmasq resolver. |
|
||||
| `PASST_OPTS` | | Additional options passed to passt. |
|
||||
| `PASST_DEBUG` | `N` | Enables passt debug output. |
|
||||
|
||||
## 🖥️ Display
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `DISPLAY` | `none` | Display backend, such as `vnc`, `disabled`, or `none`. |
|
||||
| `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 |
|
||||
|---|---|---|
|
||||
| `SHUTDOWN` | `Y` | Enables graceful shutdown. |
|
||||
| `TIMEOUT` | `115` | Maximum time, in seconds, to wait before forcing DSM to stop. |
|
||||
| `API_TIMEOUT` | `90` | Maximum time, in seconds, to wait for the shutdown API call. |
|
||||
|
||||
## 🐞 Debugging
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `DEBUG` | `N` | Enables verbose debug output. |
|
||||
| `TRACE` | `N` | Enables shell command tracing. |
|
||||
| `HOST_DEBUG` | `N` | Enables debug output for the DSM host helper. |
|
||||
@@ -18,6 +18,8 @@ metadata:
|
||||
name: dsm
|
||||
spec:
|
||||
replicas: 1
|
||||
strategy:
|
||||
type: Recreate
|
||||
selector:
|
||||
matchLabels:
|
||||
app: dsm
|
||||
|
||||
@@ -22,7 +22,7 @@ Virtual DSM in a Docker container.
|
||||
- Near-native performance with KVM acceleration
|
||||
- Customizable CPU, memory, and storage allocation
|
||||
- Supports multiple disks and physical disk passthrough
|
||||
- Supports bridge, user-mode, macvlan, and macvtap networking
|
||||
- Supports NAT, user-mode, macvlan, and macvtap networking
|
||||
|
||||
## Usage 🐳
|
||||
|
||||
@@ -66,12 +66,13 @@ kubectl apply -f https://raw.githubusercontent.com/vdsm/virtual-dsm/refs/heads/m
|
||||
|
||||
## Requirements ⚙️
|
||||
|
||||
- A Linux host with KVM support, or Docker Desktop / Podman on Windows 11 with nested virtualization enabled.
|
||||
- At least 2 GB of RAM available.
|
||||
- At least 32 GB of free disk space.
|
||||
- Docker or Podman on a Linux host with KVM support.
|
||||
- Docker Desktop or Podman (Desktop) on Windows 11 with nested virtualization enabled.
|
||||
- At least 2 GB of available RAM.
|
||||
- At least 32 GB of free disk space.
|
||||
|
||||
> [!NOTE]
|
||||
> Docker Desktop on macOS and Windows 10 do not currently provide the required KVM support for this image.
|
||||
> Docker Desktop on Linux, macOS, and Windows 10 does not currently provide KVM access to containers and is therefore not supported.
|
||||
|
||||
## FAQ 💬
|
||||
|
||||
@@ -147,34 +148,6 @@ kubectl apply -f https://raw.githubusercontent.com/vdsm/virtual-dsm/refs/heads/m
|
||||
CPU_CORES: "4"
|
||||
```
|
||||
|
||||
### How do I verify if my system supports KVM?
|
||||
|
||||
First check if your software is compatible using this chart:
|
||||
|
||||
| **Product** | **Linux** | **Win11** | **Win10** | **macOS** |
|
||||
|---|---|---|---|---|
|
||||
| Docker CLI | ✅ | ✅ | ❌ | ❌ |
|
||||
| Docker Desktop | ❌ | ✅ | ❌ | ❌ |
|
||||
| Podman CLI | ✅ | ✅ | ❌ | ❌ |
|
||||
| Podman Desktop | ✅ | ✅ | ❌ | ❌ |
|
||||
|
||||
After that you can run the following commands in Linux to check your system:
|
||||
|
||||
```bash
|
||||
sudo apt install cpu-checker
|
||||
sudo kvm-ok
|
||||
```
|
||||
|
||||
If you receive an error from `kvm-ok` indicating that KVM cannot be used, please check whether:
|
||||
|
||||
- the virtualization extensions (`Intel VT-x` or `AMD SVM`) are enabled in your BIOS.
|
||||
|
||||
- you enabled "nested virtualization" if you are running the container inside a virtual machine.
|
||||
|
||||
- you are not using a cloud provider, as most of them do not allow nested virtualization for their VPSs.
|
||||
|
||||
If you did not receive any error from `kvm-ok` but the container still complains about a missing KVM device, it could help to add `privileged: true` to your compose file (or `sudo` to your `docker` command) to rule out any permission issue.
|
||||
|
||||
### How do I assign an individual IP address to the container?
|
||||
|
||||
By default, the container uses bridge networking, which shares the IP address with the host.
|
||||
@@ -261,6 +234,41 @@ kubectl apply -f https://raw.githubusercontent.com/vdsm/virtual-dsm/refs/heads/m
|
||||
|
||||
Replace the example path `./DSM_VirtualDSM_42218.pat` with the filename of your desired `.pat` file. The value of `URL` will be ignored in this case.
|
||||
|
||||
### Are these all available options?
|
||||
|
||||
No. For a complete overview of all supported settings, see the [environment variables](docs/environment.md) page.
|
||||
|
||||
### How do I verify that KVM is available?
|
||||
|
||||
First, make sure your platform and container runtime meet the [requirements](#requirements-️) listed above.
|
||||
|
||||
On a Linux host, install `cpu-checker` and run:
|
||||
|
||||
```bash
|
||||
sudo apt install cpu-checker
|
||||
sudo kvm-ok
|
||||
```
|
||||
|
||||
A working configuration should report:
|
||||
|
||||
```text
|
||||
KVM acceleration can be used
|
||||
```
|
||||
|
||||
You can also verify that the KVM device exists:
|
||||
|
||||
```bash
|
||||
ls -l /dev/kvm
|
||||
```
|
||||
|
||||
If KVM is unavailable, check whether:
|
||||
|
||||
- Hardware virtualization (`Intel VT-x` or `AMD-V`) is enabled in your BIOS or UEFI.
|
||||
- Nested virtualization is enabled when the host itself is a virtual machine.
|
||||
- Your VPS or cloud provider supports nested virtualization.
|
||||
|
||||
If `kvm-ok` succeeds but the container still reports that KVM is unavailable, you can temporarily add `privileged: true` to your Compose file to rule out a permission or device-access issue.
|
||||
|
||||
### What are the differences compared to the standard DSM?
|
||||
|
||||
There are only two minor differences: the Virtual Machine Manager package is not available, and Surveillance Station will not include any free licenses.
|
||||
|
||||
+1
-1
@@ -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
@@ -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
|
||||
|
||||
+364
-291
@@ -27,66 +27,66 @@ SYSTEM="$STORAGE/$BASE.system.img"
|
||||
[ ! -s "$SYSTEM" ] && error "Virtual DSM system-image does not exist ($SYSTEM)" && exit 82
|
||||
|
||||
if ! setOwner "$BOOT"; then
|
||||
error "Failed to set the owner for \"$BOOT\" !"
|
||||
warn "failed to set the owner for \"$BOOT\" !"
|
||||
fi
|
||||
|
||||
if ! setOwner "$SYSTEM"; then
|
||||
error "Failed to set the owner for \"$SYSTEM\" !"
|
||||
warn "failed to set the owner for \"$SYSTEM\" !"
|
||||
fi
|
||||
|
||||
fmt2ext() {
|
||||
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,154 +94,208 @@ 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"
|
||||
return 0
|
||||
}
|
||||
|
||||
baseDir() {
|
||||
|
||||
local path="${1%/}"
|
||||
|
||||
[[ -z "$path" || "$path" == "/" ]] && {
|
||||
echo "/"
|
||||
return 0
|
||||
}
|
||||
|
||||
path="${path#/}"
|
||||
path="${path%%/*}"
|
||||
|
||||
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
|
||||
|
||||
echo "$SPACE"
|
||||
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 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")
|
||||
SPACE=$(df --output=avail -B 1 "$DIR" | tail -n 1)
|
||||
dir=$(dirname "$diskFile")
|
||||
base=$(baseDir "$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 $DIR, it has only $GB available..."
|
||||
error "Please specify a smaller ${DISK_DESC^^}_SIZE or disable preallocation by setting ALLOCATE=N." && exit 76
|
||||
freeSpace "$dir"
|
||||
|
||||
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
|
||||
|
||||
@@ -250,49 +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 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")
|
||||
SPACE=$(df --output=avail -B 1 "$DIR" | tail -n 1)
|
||||
dir=$(dirname "$diskFile")
|
||||
base=$(baseDir "$dir")
|
||||
|
||||
if (( REQ > SPACE )); then
|
||||
GB=$(formatBytes "$SPACE")
|
||||
error "Not enough free space to resize $DISK_DESC to ${DISK_SPACE/G/ GB} in $DIR, it has only $GB available.."
|
||||
error "Please specify a smaller ${DISK_DESC^^}_SIZE or disable preallocation by setting ALLOCATE=N." && exit 74
|
||||
freeSpace "$dir"
|
||||
|
||||
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
|
||||
|
||||
;;
|
||||
@@ -303,124 +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 FA
|
||||
DIR=$(dirname "$TMP_FILE")
|
||||
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
|
||||
SPACE=$(df --output=avail -B 1 "$DIR" | tail -n 1)
|
||||
currentSize=$(getSize "$sourceFile") || exit 79
|
||||
|
||||
if (( CUR_SIZE > SPACE )); then
|
||||
GB=$(formatBytes "$SPACE")
|
||||
error "Not enough free space to convert $DISK_DESC to $DST_FMT in $DIR, it has only $GB available..."
|
||||
freeSpace "$dir"
|
||||
|
||||
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 $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
|
||||
CUR_SIZE=$(stat -c%s "$TMP_FILE")
|
||||
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 ! currentSize=$(stat -c%s "$tmpFile"); then
|
||||
error "Failed to determine converted image size: $tmpFile"
|
||||
exit 79
|
||||
fi
|
||||
|
||||
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 ! rm -f "$SOURCE_FILE"; then
|
||||
rm -f "$TMP_FILE"
|
||||
error "Failed to remove old $DISK_DESC image $SOURCE_FILE."
|
||||
if ! mv "$tmpFile" "$destinationFile"; then
|
||||
error "Failed to move converted $diskDesc image to $destinationFile."
|
||||
exit 79
|
||||
fi
|
||||
|
||||
if ! mv "$TMP_FILE" "$DST_FILE"; then
|
||||
rm -f "$TMP_FILE"
|
||||
error "Failed to move converted $DISK_DESC image to $DST_FILE."
|
||||
if ! rm -f "$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 FA
|
||||
local fs="$1"
|
||||
local diskFile="$2"
|
||||
local diskDesc="$3"
|
||||
|
||||
DIR=$(dirname "$DISK_FILE")
|
||||
[ ! -d "$DIR" ] && return 0
|
||||
local dir base
|
||||
local attributes
|
||||
|
||||
if [[ "${FS,,}" == "overlay"* && "${ENGINE,,}" == "docker" ]]; then
|
||||
warn "the filesystem of $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 $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 $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
|
||||
@@ -430,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
|
||||
@@ -486,76 +554,81 @@ finishDisks () {
|
||||
|
||||
case "${DISK_TYPE,,}" in
|
||||
"blk" | "scsi" | "virtio-blk" | "virtio-scsi" )
|
||||
DISK_OPTS+=" -object iothread,id=io2" ;;
|
||||
[[ "$DISK_OPTS" != *" -object iothread,id=io2"* ]] && DISK_OPTS+=" -object iothread,id=io2" ;;
|
||||
esac
|
||||
|
||||
if ! enabled "$DISK_DISABLE"; then
|
||||
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
|
||||
@@ -563,71 +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
|
||||
GB=$(formatBytes "$FREE")
|
||||
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 $DIR now."
|
||||
info "Please consider making at least $LEFT more space available in $DIR for future expansions."
|
||||
info "$msg, but there is only $gb of free space remaining in $base 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
|
||||
error "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%% *}"
|
||||
@@ -636,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
|
||||
}
|
||||
@@ -654,7 +729,6 @@ addDevice () {
|
||||
|
||||
if ! enabled "$DISK_DISABLE"; then
|
||||
msg="Initializing disks..."
|
||||
html "$msg"
|
||||
enabled "$DEBUG" && echo "$msg"
|
||||
fi
|
||||
|
||||
@@ -687,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."
|
||||
@@ -713,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
@@ -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"
|
||||
|
||||
|
||||
+7
-4
@@ -26,20 +26,23 @@ trap - ERR
|
||||
|
||||
cmd=(qemu-system-x86_64)
|
||||
version=$("${cmd[@]}" --version | awk 'NR==1 { print $4 }')
|
||||
info "Booting $APP using QEMU v$version..."
|
||||
info "Booting $APP using QEMU v$version..." && echo
|
||||
|
||||
if ! enabled "$SHUTDOWN"; then
|
||||
exec "${cmd[@]}" ${ARGS:+ $ARGS}
|
||||
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 $!
|
||||
|
||||
+55
-28
@@ -17,7 +17,7 @@ DIR=$(find / -maxdepth 1 -type d -iname "$FN" -print -quit)
|
||||
[ ! -d "$DIR" ] && DIR=$(find "$STORAGE" -maxdepth 1 -type d -iname "$FN" -print -quit)
|
||||
|
||||
if [ -d "$DIR" ]; then
|
||||
BASE="DSM_VirtualDSM" && URL="file://$DIR"
|
||||
BASE="DSM_VirtualDSM" && URL="file://$DIR"
|
||||
if [[ ! -s "$STORAGE/$BASE.boot.img" || ! -s "$STORAGE/$BASE.system.img" ]]; then
|
||||
error "The bind $DIR maps to a file that does not exist!" && exit 65
|
||||
fi
|
||||
@@ -25,7 +25,7 @@ fi
|
||||
|
||||
FILE=$(find / -maxdepth 1 -type f -iname "$FN" -print -quit)
|
||||
[ ! -s "$FILE" ] && FILE=$(find "$STORAGE" -maxdepth 1 -type f -iname "$FN" -print -quit)
|
||||
[ -s "$FILE" ] && BASE="DSM_VirtualDSM" && URL="file://$FILE"
|
||||
[ -s "$FILE" ] && BASE="DSM_VirtualDSM" && URL="file://$FILE"
|
||||
|
||||
URL=$(strip "$URL")
|
||||
|
||||
@@ -36,7 +36,7 @@ if [ -n "$URL" ] && [ ! -s "$FILE" ] && [ ! -d "$DIR" ]; then
|
||||
printf -v BASE '%b' "${BASE//%/\\x}"
|
||||
BASE="${BASE//[!A-Za-z0-9._-]/_}"
|
||||
fi
|
||||
if [[ "${URL,,}" != "http"* && "${URL,,}" != "file:"* ]] ; then
|
||||
if [[ "${URL,,}" != "http"* && "${URL,,}" != "file:"* ]]; then
|
||||
[ ! -s "$STORAGE/$BASE.pat" ] && error "Invalid URL: $URL" && exit 65
|
||||
URL="file://$STORAGE/$BASE.pat"
|
||||
fi
|
||||
@@ -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}"
|
||||
@@ -159,20 +152,53 @@ if [[ "$URL" == "file://"* ]]; then
|
||||
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
|
||||
|
||||
{ wget "$URL" -O "$PAT" -q --no-check-certificate --timeout=10 --no-http-keep-alive --show-progress "$PROGRESS"; rc=$?; } || :
|
||||
/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 "${PROGRESS[@]}" \
|
||||
--output-file="$LOG"
|
||||
rc=$?
|
||||
} || :
|
||||
|
||||
fKill "progress.sh"
|
||||
|
||||
(( rc == 3 )) && error "$ERR , cannot write file (disk full?)" && exit 69
|
||||
(( rc == 4 )) && error "$ERR , network failure!" && exit 69
|
||||
(( rc == 8 )) && error "$ERR , server issued an error response!" && exit 69
|
||||
(( rc != 0 )) && error "$ERR , reason: $rc" && exit 69
|
||||
if (( rc != 0 )); then
|
||||
REASON=$(sed -n \
|
||||
-e 's/^wget: //p' \
|
||||
-e 's/^[0-9-]\{10\} [0-9:]\{8\} ERROR //p' \
|
||||
"$LOG" | tail -n 1)
|
||||
fi
|
||||
|
||||
rm -f "$LOG"
|
||||
|
||||
if (( rc == 3 )); then
|
||||
error "$ERR because the file could not be written (disk full?)."
|
||||
exit 69
|
||||
elif (( rc != 0 )); then
|
||||
if [ -n "$REASON" ]; then
|
||||
error "$ERR: ${REASON%.}."
|
||||
else
|
||||
error "$ERR with exit status $rc."
|
||||
fi
|
||||
exit 69
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
@@ -228,7 +254,7 @@ if ! touch "$SYSTEM"; then
|
||||
error "Could not create file $SYSTEM for the system disk." && exit 98
|
||||
fi
|
||||
|
||||
! setOwner "$SYSTEM" && error "Failed to set the owner for \"$SYSTEM\" !"
|
||||
! setOwner "$SYSTEM" && warn "failed to set the owner for \"$SYSTEM\" !"
|
||||
|
||||
if [[ "${FS,,}" == "btrfs" ]]; then
|
||||
{ chattr +C "$SYSTEM"; } || :
|
||||
@@ -249,14 +275,15 @@ fi
|
||||
|
||||
PART="$TMP/partition.fdisk"
|
||||
|
||||
{ echo "label: dos"
|
||||
echo "label-id: 0x6f9ee2e9"
|
||||
echo "device: $SYSTEM"
|
||||
echo "unit: sectors"
|
||||
echo "sector-size: 512"
|
||||
echo ""
|
||||
echo "${SYSTEM}1 : start= 2048, size= 16777216, type=83"
|
||||
echo "${SYSTEM}2 : start= 16779264, size= 4194304, type=82"
|
||||
{
|
||||
echo "label: dos"
|
||||
echo "label-id: 0x6f9ee2e9"
|
||||
echo "device: $SYSTEM"
|
||||
echo "unit: sectors"
|
||||
echo "sector-size: 512"
|
||||
echo ""
|
||||
echo "${SYSTEM}1 : start= 2048, size= 16777216, type=83"
|
||||
echo "${SYSTEM}2 : start= 16779264, size= 4194304, type=82"
|
||||
} > "$PART"
|
||||
|
||||
sfdisk -q "$SYSTEM" < "$PART"
|
||||
@@ -302,7 +329,7 @@ fakeroot -- bash -c "set -Eeu;\
|
||||
|
||||
rm -rf "$MOUNT"
|
||||
echo "$BASE" > "$STORAGE/dsm.ver"
|
||||
! setOwner "$STORAGE/dsm.ver" && error "Failed to set the owner for \"$STORAGE/dsm.ver\" !"
|
||||
! setOwner "$STORAGE/dsm.ver" && warn "failed to set the owner for \"$STORAGE/dsm.ver\" !"
|
||||
|
||||
if [[ "$URL" == "file://$STORAGE/$BASE.pat" ]]; then
|
||||
rm -f "$PAT"
|
||||
@@ -311,11 +338,11 @@ else
|
||||
fi
|
||||
|
||||
if [ -f "$STORAGE/$BASE.pat" ]; then
|
||||
! setOwner "$STORAGE/$BASE.pat" && error "Failed to set the owner for \"$STORAGE/$BASE.pat\" !"
|
||||
! setOwner "$STORAGE/$BASE.pat" && warn "failed to set the owner for \"$STORAGE/$BASE.pat\" !"
|
||||
fi
|
||||
|
||||
mv -f "$BOOT" "$STORAGE/$BASE.boot.img"
|
||||
! setOwner "$STORAGE/$BASE.boot.img" && error "Failed to set the owner for \"$STORAGE/$BASE.boot.img\" !"
|
||||
! setOwner "$STORAGE/$BASE.boot.img" && warn "failed to set the owner for \"$STORAGE/$BASE.boot.img\" !"
|
||||
|
||||
rm -rf "$TMP"
|
||||
|
||||
|
||||
+12
-11
@@ -4,21 +4,23 @@ set -Eeuo pipefail
|
||||
msg="Checking memory..."
|
||||
enabled "$DEBUG" && echo "$msg"
|
||||
|
||||
RAM_AVAIL=$(free -b | grep -m 1 Mem: | awk '{print $7}')
|
||||
AVAIL_MEM=$(formatBytes "$RAM_AVAIL")
|
||||
app() {
|
||||
echo "Virtual DSM"
|
||||
return 0
|
||||
}
|
||||
|
||||
checkConfiguredMemory() {
|
||||
|
||||
local wanted msg
|
||||
|
||||
if disabled "$RAM_CHECK" || [[ "${RAM_SIZE,,}" == "max" || "${RAM_SIZE,,}" == "half" ]]; then
|
||||
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
|
||||
@@ -27,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
|
||||
@@ -40,7 +42,6 @@ checkConfiguredMemory() {
|
||||
}
|
||||
|
||||
configureHalfMemory() {
|
||||
|
||||
local wanted
|
||||
|
||||
if [[ "${RAM_SIZE,,}" != "half" ]]; then
|
||||
@@ -50,7 +51,7 @@ configureHalfMemory() {
|
||||
if (( (RAM_AVAIL / 2) > RAM_SPARE )); then
|
||||
wanted=$(( (RAM_AVAIL / 2) / 1048577 ))
|
||||
RAM_SIZE="${wanted}M"
|
||||
info "Allocated $wanted MB of RAM for the virtual machine."
|
||||
info "Allocated $wanted MB of RAM for $(app)."
|
||||
else
|
||||
RAM_SIZE="max"
|
||||
fi
|
||||
@@ -59,7 +60,6 @@ configureHalfMemory() {
|
||||
}
|
||||
|
||||
configureMaxMemory() {
|
||||
|
||||
local wanted
|
||||
|
||||
if [[ "${RAM_SIZE,,}" != "max" ]]; then
|
||||
@@ -83,13 +83,12 @@ configureMaxMemory() {
|
||||
wanted=$(( wanted / 1048577 ))
|
||||
RAM_SIZE="${wanted}M"
|
||||
|
||||
info "Allocated $wanted MB of RAM for the virtual machine."
|
||||
info "Allocated $wanted MB of RAM for $(app)."
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
checkMinimumMemory() {
|
||||
|
||||
local wanted
|
||||
|
||||
wanted=$(numfmt --from=iec "$RAM_SIZE")
|
||||
@@ -103,6 +102,8 @@ checkMinimumMemory() {
|
||||
return 0
|
||||
}
|
||||
|
||||
getMemoryInfo
|
||||
|
||||
checkConfiguredMemory
|
||||
configureHalfMemory
|
||||
configureMaxMemory
|
||||
|
||||
+1050
-292
File diff suppressed because it is too large
Load Diff
+171
-34
@@ -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
@@ -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
|
||||
|
||||
+31
-50
@@ -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
|
||||
@@ -53,6 +53,32 @@ checkSse42() {
|
||||
return 0
|
||||
}
|
||||
|
||||
trimSpaces() {
|
||||
|
||||
local value="$1"
|
||||
|
||||
value="${value#"${value%%[![:space:]]*}"}"
|
||||
value="${value%"${value##*[![:space:]]}"}"
|
||||
|
||||
echo "$value"
|
||||
return 0
|
||||
}
|
||||
|
||||
removeCpuArgument() {
|
||||
|
||||
local args=" ${ARGUMENTS:-} "
|
||||
|
||||
while [[ "$args" =~ [[:space:]]-cpu([[:space:]][^[:space:]]+|=[^[:space:]]+)? ]]; do
|
||||
local cpu="${BASH_REMATCH[0]}"
|
||||
args="${args/$cpu/ }"
|
||||
warn "Ignoring '${cpu#" "}' from ARGUMENTS, use CPU_MODEL and CPU_FLAGS instead."
|
||||
done
|
||||
|
||||
ARGUMENTS=$(trimSpaces "$args")
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
configureKvmCpuModel() {
|
||||
|
||||
CPU_FEATURES="kvm=on,l3-cache=on,+hypervisor"
|
||||
@@ -68,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
|
||||
|
||||
@@ -130,52 +156,9 @@ configureTcg() {
|
||||
return 0
|
||||
}
|
||||
|
||||
extractHostCpuArgument() {
|
||||
|
||||
local args prefix suffix param
|
||||
|
||||
if [[ "$ARGUMENTS" == *"-cpu host,"* ]]; then
|
||||
|
||||
args="${ARGUMENTS} "
|
||||
prefix="${args/-cpu host,*/}"
|
||||
suffix="${args/*-cpu host,/}"
|
||||
param="${suffix%% *}"
|
||||
suffix="${suffix#* }"
|
||||
args="${prefix}${suffix}"
|
||||
ARGUMENTS="${args::-1}"
|
||||
|
||||
if [ -z "$CPU_FLAGS" ]; then
|
||||
CPU_FLAGS="$param"
|
||||
else
|
||||
CPU_FLAGS+=",$param"
|
||||
fi
|
||||
|
||||
else
|
||||
|
||||
if [[ "$ARGUMENTS" == *"-cpu host"* ]]; then
|
||||
ARGUMENTS="${ARGUMENTS//-cpu host/}"
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
composeCpuFlags() {
|
||||
|
||||
if [ -z "$CPU_FLAGS" ]; then
|
||||
if [ -z "$CPU_FEATURES" ]; then
|
||||
CPU_FLAGS="$CPU_MODEL"
|
||||
else
|
||||
CPU_FLAGS="$CPU_MODEL,$CPU_FEATURES"
|
||||
fi
|
||||
else
|
||||
if [ -z "$CPU_FEATURES" ]; then
|
||||
CPU_FLAGS="$CPU_MODEL,$CPU_FLAGS"
|
||||
else
|
||||
CPU_FLAGS="$CPU_MODEL,$CPU_FEATURES,$CPU_FLAGS"
|
||||
fi
|
||||
fi
|
||||
CPU_FLAGS="${CPU_MODEL}${CPU_FEATURES:+,$CPU_FEATURES}${CPU_FLAGS:+,$CPU_FLAGS}"
|
||||
|
||||
return 0
|
||||
}
|
||||
@@ -203,15 +186,13 @@ configureHostCpuName() {
|
||||
selectClocksource
|
||||
checkClocksource
|
||||
|
||||
flags=$(sed -ne '/^flags/s/^.*: //p' /proc/cpuinfo)
|
||||
|
||||
if ! disabled "$KVM"; then
|
||||
configureKvm
|
||||
else
|
||||
configureTcg
|
||||
fi
|
||||
|
||||
extractHostCpuArgument
|
||||
removeCpuArgument
|
||||
composeCpuFlags
|
||||
configureHostCpuName
|
||||
|
||||
|
||||
+280
-23
@@ -2,44 +2,301 @@
|
||||
set -Eeuo pipefail
|
||||
|
||||
info="/run/shm/msg.html"
|
||||
info_tmp="${info}.${BASHPID}.tmp"
|
||||
|
||||
escape () {
|
||||
local s
|
||||
s=${1//&/\&}
|
||||
s=${s//</\<}
|
||||
s=${s//>/\>}
|
||||
s=${s//'"'/\"}
|
||||
printf -- %s "$s"
|
||||
escape() {
|
||||
|
||||
local s
|
||||
|
||||
s=${1//&/\&}
|
||||
s=${s//</\<}
|
||||
s=${s//>/\>}
|
||||
s=${s//'"'/\"}
|
||||
s=${s//"'"/\'}
|
||||
|
||||
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" | cut -f1)
|
||||
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/')
|
||||
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
|
||||
|
||||
+78
-12
@@ -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
|
||||
@@ -25,12 +26,29 @@ detectEngine() {
|
||||
|
||||
if [ -f "/run/.containerenv" ]; then
|
||||
ENGINE="${container:-}"
|
||||
|
||||
if [[ "${ENGINE,,}" == *"podman"* ]]; then
|
||||
ROOTLESS="Y"
|
||||
ENGINE="Podman"
|
||||
else
|
||||
[ -z "$ENGINE" ] && ENGINE="Kubernetes"
|
||||
fi
|
||||
elif [ -f "/.dockerenv" ]; then
|
||||
ENGINE="Docker"
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
detectRootless() {
|
||||
|
||||
local uid_map
|
||||
|
||||
uid_map=$(awk '{$1=$1; print}' /proc/self/uid_map 2>/dev/null || true)
|
||||
|
||||
if [[ "$uid_map" == "0 0 4294967295" ]]; then
|
||||
ROOTLESS="N"
|
||||
else
|
||||
ROOTLESS="Y"
|
||||
fi
|
||||
|
||||
return 0
|
||||
@@ -40,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}")
|
||||
@@ -50,10 +66,9 @@ 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
|
||||
ROOTLESS="N"
|
||||
PRIVILEGED="Y"
|
||||
fi
|
||||
|
||||
@@ -123,11 +138,62 @@ checkFilesystem() {
|
||||
return 0
|
||||
}
|
||||
|
||||
finiteMemoryLimit() {
|
||||
|
||||
local limit="$1"
|
||||
local sentinel="4611686018427387904"
|
||||
local i
|
||||
|
||||
[[ "$limit" =~ ^[0-9]+$ ]] || return 1
|
||||
|
||||
(( ${#limit} < ${#sentinel} )) && return 0
|
||||
(( ${#limit} > ${#sentinel} )) && return 1
|
||||
|
||||
for (( i=0; i<${#sentinel}; i++ )); do
|
||||
local left="${limit:i:1}"
|
||||
local right="${sentinel:i:1}"
|
||||
|
||||
(( left < right )) && return 0
|
||||
(( left > right )) && return 1
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
getMemoryInfo() {
|
||||
|
||||
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}')
|
||||
|
||||
RAM_TOTAL="$host_total"
|
||||
RAM_AVAIL="$host_avail"
|
||||
|
||||
if [ -r /sys/fs/cgroup/memory.max ] && [ -r /sys/fs/cgroup/memory.current ]; then
|
||||
limit=$(< /sys/fs/cgroup/memory.max)
|
||||
current=$(< /sys/fs/cgroup/memory.current)
|
||||
elif [ -r /sys/fs/cgroup/memory/memory.limit_in_bytes ] && [ -r /sys/fs/cgroup/memory/memory.usage_in_bytes ]; then
|
||||
limit=$(< /sys/fs/cgroup/memory/memory.limit_in_bytes)
|
||||
current=$(< /sys/fs/cgroup/memory/memory.usage_in_bytes)
|
||||
fi
|
||||
|
||||
if finiteMemoryLimit "$limit" && [[ "$current" =~ ^[0-9]+$ ]]; then
|
||||
(( limit < RAM_TOTAL )) && RAM_TOTAL="$limit"
|
||||
|
||||
local available=$(( limit - current ))
|
||||
(( available < 0 )) && available=0
|
||||
(( available < RAM_AVAIL )) && RAM_AVAIL="$available"
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
normalizeRamSize() {
|
||||
|
||||
# Read memory
|
||||
RAM_AVAIL=$(free -b | grep -m 1 Mem: | awk '{print $7}')
|
||||
RAM_TOTAL=$(free -b | grep -m 1 Mem: | awk '{print $2}')
|
||||
# Read host and container memory limits.
|
||||
getMemoryInfo
|
||||
|
||||
RAM_SPARE=500000000
|
||||
RAM_MINIMUM=136314880
|
||||
@@ -232,6 +298,7 @@ PROCESS="${APP,,}"
|
||||
PROCESS="${PROCESS// /-}"
|
||||
|
||||
detectEngine
|
||||
detectRootless
|
||||
|
||||
echo "❯ Starting $APP for $ENGINE v$(</etc/version)..."
|
||||
echo "❯ For support visit $SUPPORT"
|
||||
@@ -247,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
|
||||
@@ -273,14 +339,14 @@ SPACE_GB=$(formatBytes "$SPACE" "down")
|
||||
AVAIL_MEM=$(formatBytes "$RAM_AVAIL" "down")
|
||||
TOTAL_MEM=$(formatBytes "$RAM_TOTAL" "up")
|
||||
|
||||
echo "❯ CPU: ${CPU} | RAM: ${AVAIL_MEM/ GB/}/$TOTAL_MEM | DISK: $SPACE_GB (${FS}) | KERNEL: ${SYS}..."
|
||||
echo "❯ CPU: ${CPU} | RAM: ${AVAIL_MEM/ GB/}/$TOTAL_MEM | DISK: $SPACE_GB (${FS}) | KERNEL: ${SYS}"
|
||||
echo
|
||||
|
||||
checkKvm
|
||||
|
||||
# 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
@@ -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
|
||||
|
||||
|
||||
+97
-21
@@ -1,39 +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"
|
||||
rm -f "$WSD_PID" "$WEB_PID"
|
||||
|
||||
cp -r /var/www/* "$QEMU_DIR" || 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" ]] && [ -n "$(ifconfig -a | grep inet6)" ]; then
|
||||
sed -i "s/listen $WEB_PORT default_server;/listen [::]:$WEB_PORT default_server ipv6only=off;/g" /etc/nginx/sites-enabled/web.conf
|
||||
if [ -f /proc/net/if_inet6 ] && [[ "$(cat /proc/sys/net/ipv6/conf/all/disable_ipv6 2>/dev/null)" != "1" ]]; then
|
||||
|
||||
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
|
||||
@@ -44,33 +54,99 @@ configureWebServer() {
|
||||
mkdir -p /etc/nginx/sites-enabled
|
||||
cp /etc/nginx/default.conf /etc/nginx/sites-enabled/web.conf
|
||||
|
||||
configureWebPorts
|
||||
configureIpv6Listen
|
||||
configureWebPorts || return 1
|
||||
configureIpv6Listen || return 1
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
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
|
||||
nginx -e stderr
|
||||
nginx -e stderr || return 1
|
||||
|
||||
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() {
|
||||
|
||||
# Start websocket server
|
||||
websocketd --address 127.0.0.1 --port="$WSD_PORT" /run/socket.sh >/var/log/websocketd.log &
|
||||
echo "$!" > "$WSD_PID"
|
||||
websocketd \
|
||||
--address 127.0.0.1 \
|
||||
--port="$WSD_PORT" \
|
||||
/run/socket.sh \
|
||||
>"$WSD_LOG" 2>&1 &
|
||||
|
||||
local pid=$!
|
||||
|
||||
if ! echo "$pid" > "$WSD_PID"; then
|
||||
kill "$pid" 2>/dev/null || :
|
||||
rm -f -- "$WSD_PID"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local i
|
||||
for (( i = 1; i <= 5; i++ )); do
|
||||
|
||||
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
|
||||
|
||||
html "Starting $APP for $ENGINE..."
|
||||
|
||||
if ! disabled "${WEB:-}"; then
|
||||
configureWebServer
|
||||
startWebServer
|
||||
startWebsocketServer
|
||||
disabled "${WEB:-}" && return 0
|
||||
|
||||
configureWebServer
|
||||
|
||||
if startWebServer && startWebsocketServer; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 0
|
||||
stopWebsocketServer || :
|
||||
stopWebServer || :
|
||||
|
||||
return 1
|
||||
|
||||
+19
-6
@@ -3,13 +3,15 @@ set -Eeuo pipefail
|
||||
|
||||
lastmsg=""
|
||||
path="/run/shm/msg.html"
|
||||
dir=$(dirname -- "$path")
|
||||
name=$(basename -- "$path")
|
||||
|
||||
refresh() {
|
||||
|
||||
[ ! -f "$path" ] && return 0
|
||||
[ ! -s "$path" ] && return 0
|
||||
|
||||
msg=$(< "$path")
|
||||
msg=$(< "$path") || return 0
|
||||
msg="${msg%$'\n'}"
|
||||
|
||||
[ -z "$msg" ] && return 0
|
||||
@@ -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
|
||||
|
||||
+206
-40
@@ -7,7 +7,36 @@ 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:-}"
|
||||
|
||||
# Remove surrounding whitespace
|
||||
@@ -28,24 +57,38 @@ strip() {
|
||||
}
|
||||
|
||||
enabled() {
|
||||
case "$(strip "${1:-}")" in
|
||||
Y|y|YES|Yes|yes|TRUE|True|true|1|ON|On|on) return 0 ;;
|
||||
|
||||
local value
|
||||
value=$(strip "${1:-}")
|
||||
|
||||
case "${value,,}" in
|
||||
y|yes|true|1|on|enable|enabled) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
disabled() {
|
||||
case "$(strip "${1:-}")" in
|
||||
N|n|NO|No|no|FALSE|False|false|0|OFF|Off|off) return 0 ;;
|
||||
|
||||
local value
|
||||
value=$(strip "${1:-}")
|
||||
|
||||
case "${value,,}" in
|
||||
n|no|none|false|0|off|disable|disabled) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
formatBytes() {
|
||||
|
||||
local result
|
||||
result=$(numfmt --to=iec --suffix=B "$1" | sed -r 's/([A-Z])/ \1/' | sed 's/ B/ bytes/g;')
|
||||
|
||||
if ! result=$(numfmt --to=iec --suffix=B "$1" | sed -r 's/([A-Z])/ \1/' | sed 's/ B/ bytes/g;'); then
|
||||
return 1
|
||||
fi
|
||||
|
||||
local unit="${result//[0-9. ]}"
|
||||
result="${result//[a-zA-Z ]/}"
|
||||
|
||||
if [[ "${2:-}" == "up" ]]; then
|
||||
if [[ "$result" == *"."* ]]; then
|
||||
result="${result%%.*}"
|
||||
@@ -56,11 +99,13 @@ formatBytes() {
|
||||
result="${result%%.*}"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "$result $unit"
|
||||
return 0
|
||||
}
|
||||
|
||||
isAlive() {
|
||||
|
||||
local pid="$1"
|
||||
[ -z "$pid" ] && return 1
|
||||
|
||||
@@ -72,6 +117,7 @@ isAlive() {
|
||||
}
|
||||
|
||||
waitPid() {
|
||||
|
||||
local i=0
|
||||
local pid="$1"
|
||||
local timeout="${2:-10}"
|
||||
@@ -86,8 +132,8 @@ waitPid() {
|
||||
}
|
||||
|
||||
waitPidFile() {
|
||||
local i=0
|
||||
local pid=""
|
||||
|
||||
local i=0 pid
|
||||
local file="$1"
|
||||
local timeout="${2:-10}"
|
||||
|
||||
@@ -106,6 +152,7 @@ waitPidFile() {
|
||||
}
|
||||
|
||||
pKill() {
|
||||
|
||||
local pid="$1"
|
||||
local timeout="${2:-10}"
|
||||
|
||||
@@ -119,6 +166,7 @@ pKill() {
|
||||
}
|
||||
|
||||
fWait() {
|
||||
|
||||
local i=0
|
||||
local name="$1"
|
||||
local timeout="${2:-10}"
|
||||
@@ -138,6 +186,7 @@ fWait() {
|
||||
}
|
||||
|
||||
fKill() {
|
||||
|
||||
local name="$1"
|
||||
local timeout="${2:-10}"
|
||||
|
||||
@@ -150,7 +199,8 @@ fKill() {
|
||||
}
|
||||
|
||||
sKill() {
|
||||
local pid=""
|
||||
|
||||
local pid
|
||||
local file="$1"
|
||||
|
||||
[ ! -s "$file" ] && return 0
|
||||
@@ -165,6 +215,7 @@ sKill() {
|
||||
}
|
||||
|
||||
mKill() {
|
||||
|
||||
local timeout=10
|
||||
local files=("$@")
|
||||
|
||||
@@ -182,14 +233,15 @@ mKill() {
|
||||
}
|
||||
|
||||
setOwner() {
|
||||
|
||||
local file="$1"
|
||||
local dir uid gid
|
||||
|
||||
[ ! -f "$file" ] && return 1
|
||||
|
||||
dir=$(dirname -- "$file")
|
||||
uid=$(stat -c '%u' "$dir")
|
||||
gid=$(stat -c '%g' "$dir")
|
||||
uid=$(stat -c '%u' "$dir") || return 1
|
||||
gid=$(stat -c '%g' "$dir") || return 1
|
||||
|
||||
! chown "$uid:$gid" "$file" && return 1
|
||||
|
||||
@@ -197,6 +249,7 @@ setOwner() {
|
||||
}
|
||||
|
||||
makeDir() {
|
||||
|
||||
local path="$1"
|
||||
local dir uid gid
|
||||
|
||||
@@ -204,28 +257,156 @@ makeDir() {
|
||||
! mkdir -p "$path" && return 1
|
||||
|
||||
dir=$(dirname -- "$path")
|
||||
uid=$(stat -c '%u' "$dir")
|
||||
gid=$(stat -c '%g' "$dir")
|
||||
|
||||
! chown "$uid:$gid" "$path" && return 1
|
||||
if ! uid=$(stat -c '%u' "$dir") || ! gid=$(stat -c '%g' "$dir"); then
|
||||
warn "failed to determine the owner for \"$path\"."
|
||||
return 0
|
||||
fi
|
||||
|
||||
if ! chown "$uid:$gid" "$path"; then
|
||||
warn "failed to set the owner for \"$path\"."
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
stateFile() {
|
||||
|
||||
local name="$1"
|
||||
local prefix="${2:-$PROCESS}"
|
||||
|
||||
[[ "$name" == */* ]] && printf '%s\n' "$name" && return 0
|
||||
|
||||
printf '%s/%s.%s\n' "$STORAGE" "$prefix" "$name"
|
||||
return 0
|
||||
}
|
||||
|
||||
writeFile() {
|
||||
|
||||
local txt="$1"
|
||||
local path="$2"
|
||||
|
||||
if ! printf '%s\n' "$txt" > "$path"; then
|
||||
error "Failed to write file \"$path\" !"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! setOwner "$path"; then
|
||||
warn "failed to set the owner for \"$path\"."
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
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"
|
||||
local value
|
||||
|
||||
[ -s "$path" ] || return 0
|
||||
|
||||
value=$(<"$path") || return 1
|
||||
value="${value//[![:print:]]/}"
|
||||
|
||||
printf '%s\n' "$value"
|
||||
return 0
|
||||
}
|
||||
|
||||
writeState() {
|
||||
|
||||
local name="$1"
|
||||
local value="$2"
|
||||
local prefix="${3:-$PROCESS}"
|
||||
local path
|
||||
|
||||
[ -z "$value" ] && return 0
|
||||
|
||||
path=$(stateFile "$name" "$prefix") || return 1
|
||||
writeFile "$value" "$path"
|
||||
|
||||
return $?
|
||||
}
|
||||
|
||||
readState() {
|
||||
|
||||
local name="$1"
|
||||
local prefix="${2:-$PROCESS}"
|
||||
local path
|
||||
|
||||
path=$(stateFile "$name" "$prefix") || return 1
|
||||
readFile "$path"
|
||||
|
||||
return $?
|
||||
}
|
||||
|
||||
restoreState() {
|
||||
|
||||
local var="$1"
|
||||
local name="$2"
|
||||
local force="${3:-N}"
|
||||
local prefix="${4:-$PROCESS}"
|
||||
local value
|
||||
|
||||
if ! enabled "$force"; then
|
||||
[ -z "${!var:-}" ] || return 0
|
||||
fi
|
||||
|
||||
value=$(readState "$name" "$prefix") || return 1
|
||||
[ -n "$value" ] || return 0
|
||||
|
||||
printf -v "$var" '%s' "$value" || return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
escape () {
|
||||
local s
|
||||
s=${1//&/\&}
|
||||
|
||||
local s=${1//&/\&}
|
||||
s=${s//</\<}
|
||||
s=${s//>/\>}
|
||||
s=${s//'"'/\"}
|
||||
|
||||
printf -- %s "$s"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
escapeXML() {
|
||||
|
||||
printf '%s' "$1" | sed \
|
||||
-e 's/&/\&/g' \
|
||||
-e 's/</\</g' \
|
||||
-e 's/>/\>/g' \
|
||||
-e 's/"/\"/g' \
|
||||
-e "s/'/\'/g"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
html() {
|
||||
|
||||
local title
|
||||
local body
|
||||
local script
|
||||
local script="${2:-}"
|
||||
local footer
|
||||
|
||||
title=$(escape "$APP")
|
||||
@@ -237,8 +418,6 @@ html() {
|
||||
body="<p class=\"loading\">${body/.../}</p>"
|
||||
fi
|
||||
|
||||
[ -n "${2:-}" ] && script="$2" || script=""
|
||||
|
||||
local HTML
|
||||
HTML=$(<"$TEMPLATE")
|
||||
HTML="${HTML/\[1\]/$title}"
|
||||
@@ -247,13 +426,14 @@ html() {
|
||||
HTML="${HTML/\[4\]/$footer}"
|
||||
HTML="${HTML/\[5\]/$FOOTER2}"
|
||||
|
||||
echo "$HTML" > "$PAGE"
|
||||
echo "$body" > "$INFO"
|
||||
writeAtomic "$PAGE" "$HTML" || return 1
|
||||
writeAtomic "$INFO" "$body" || return 1
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
cpu() {
|
||||
|
||||
local ret
|
||||
local cpu=""
|
||||
|
||||
@@ -290,28 +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=$?; } || :
|
||||
@@ -345,6 +510,7 @@ setCountry() {
|
||||
}
|
||||
|
||||
addPackage() {
|
||||
|
||||
local pkg=$1
|
||||
local desc=$2
|
||||
|
||||
@@ -361,8 +527,8 @@ addPackage() {
|
||||
sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list.d/debian.sources
|
||||
fi
|
||||
|
||||
DEBIAN_FRONTEND=noninteractive apt-get -qq update
|
||||
DEBIAN_FRONTEND=noninteractive apt-get -qq --no-install-recommends -y install "$pkg" > /dev/null
|
||||
DEBIAN_FRONTEND=noninteractive apt-get -qq update || return 1
|
||||
DEBIAN_FRONTEND=noninteractive apt-get -qq --no-install-recommends -y install "$pkg" > /dev/null || return 1
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
+41
-8
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user