mirror of
https://github.com/vdsm/virtual-dsm.git
synced 2026-08-29 12:17:07 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47af1d07f4 | ||
|
|
4660512f12 | ||
|
|
6d37a0426b | ||
|
|
604cad0207 | ||
|
|
3ad7e78787 | ||
|
|
5f908488ef | ||
|
|
dcdc796e59 | ||
|
|
087d876eaf | ||
|
|
25a7d55cdd | ||
|
|
9683b195f3 | ||
|
|
f810576651 | ||
|
|
5fd954bea4 | ||
|
|
768b8fa51a | ||
|
|
b909555ef3 | ||
|
|
a77026eafb | ||
|
|
e8ef357ed2 | ||
|
|
a16c27ecce | ||
|
|
1917eae0ae | ||
|
|
d5bb90f012 | ||
|
|
d854ae811c | ||
|
|
03993aa773 | ||
|
|
b39ff267f2 | ||
|
|
f7870d1693 | ||
|
|
c648338a18 | ||
|
|
9fe52c874e | ||
|
|
c5eae88dfc | ||
|
|
1d0ac30ae1 | ||
|
|
c355cbe5dc | ||
|
|
6f69eb5c68 | ||
|
|
07595976b2 | ||
|
|
ec8440db03 | ||
|
|
06ddd19d1a | ||
|
|
04cd929c9c | ||
|
|
c64046411b | ||
|
|
cb8f440564 | ||
|
|
8b6624cf2f | ||
|
|
c70b93c2b6 | ||
|
|
4c700e87fe | ||
|
|
030255004f | ||
|
|
e64f9f2c39 | ||
|
|
537fc97d53 | ||
|
|
438e992653 | ||
|
|
615fad2a0d | ||
|
|
aac543e5c7 | ||
|
|
fec4f876e7 | ||
|
|
dced815c06 | ||
|
|
51d6f3aeca | ||
|
|
4544620d48 | ||
|
|
de8468161f | ||
|
|
18d6be8210 | ||
|
|
e5b8cf3bf8 | ||
|
|
ec12039f43 | ||
|
|
3c7c3ca1b1 | ||
|
|
2b27f32cd4 | ||
|
|
55d1d50284 | ||
|
|
784b73b5b5 | ||
|
|
85f00bb9cc | ||
|
|
efe8732e47 | ||
|
|
f6871dd61b | ||
|
|
a713b728ff | ||
|
|
0d74c6ac80 | ||
|
|
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 | ||
|
|
c9a2a6966b | ||
|
|
498248f43e | ||
|
|
8963228b1c | ||
|
|
827d6d9e10 | ||
|
|
1c577abef4 | ||
|
|
20fd1a975c | ||
|
|
770774680c | ||
|
|
0c75aa81fe | ||
|
|
36ec57ac15 | ||
|
|
9c488c52b9 | ||
|
|
2ba756260f | ||
|
|
b306012743 | ||
|
|
61b3ee7e01 | ||
|
|
245036f844 | ||
|
|
68cadfe035 | ||
|
|
96102832b1 | ||
|
|
211a56bf73 | ||
|
|
506fb7b114 | ||
|
|
bcc3393b7f | ||
|
|
45c0f91d6e | ||
|
|
a1910546a6 | ||
|
|
637ed0e3a6 | ||
|
|
954a6076ad | ||
|
|
415aaa3038 | ||
|
|
c87e45719b | ||
|
|
102945ab48 | ||
|
|
1c3d490303 | ||
|
|
73fa70f52f | ||
|
|
7b095702fc | ||
|
|
f82b2769d3 | ||
|
|
cafe619028 | ||
|
|
e94a0c4a52 | ||
|
|
d6a353df0e | ||
|
|
050812e996 | ||
|
|
75280e9aa9 | ||
|
|
8dcc8ccee1 | ||
|
|
ba866e9e75 | ||
|
|
06e7a8edb9 | ||
|
|
495c67734a | ||
|
|
1557735331 |
@@ -1,10 +1,21 @@
|
|||||||
version: 2
|
version: 2
|
||||||
|
|
||||||
updates:
|
updates:
|
||||||
- package-ecosystem: docker
|
- package-ecosystem: docker
|
||||||
directory: /
|
directory: /
|
||||||
schedule:
|
schedule:
|
||||||
interval: weekly
|
interval: weekly
|
||||||
|
cooldown:
|
||||||
|
default-days: 7
|
||||||
|
|
||||||
- package-ecosystem: github-actions
|
- package-ecosystem: github-actions
|
||||||
directory: /
|
directory: /
|
||||||
schedule:
|
schedule:
|
||||||
interval: weekly
|
interval: weekly
|
||||||
|
cooldown:
|
||||||
|
default-days: 7
|
||||||
|
ignore:
|
||||||
|
- dependency-name: "*"
|
||||||
|
update-types:
|
||||||
|
- version-update:semver-minor
|
||||||
|
- version-update:semver-patch
|
||||||
|
|||||||
+27
-20
@@ -1,5 +1,7 @@
|
|||||||
on: [workflow_call]
|
on: [workflow_call]
|
||||||
|
|
||||||
name: "Check"
|
name: "Check"
|
||||||
|
|
||||||
permissions: {}
|
permissions: {}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -7,24 +9,29 @@ jobs:
|
|||||||
name: shellcheck
|
name: shellcheck
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
-
|
|
||||||
name: Checkout
|
|
||||||
uses: actions/checkout@v7
|
|
||||||
- parallel:
|
|
||||||
-
|
-
|
||||||
name: Run ShellCheck
|
name: Checkout
|
||||||
uses: ludeeus/action-shellcheck@master
|
uses: actions/checkout@v7
|
||||||
env:
|
- parallel:
|
||||||
SHELLCHECK_OPTS: -x --source-path=src -e SC2001 -e SC2034 -e SC2064 -e SC2317 -e SC2153 -e SC2028
|
-
|
||||||
-
|
name: Run ShellCheck
|
||||||
name: Lint Dockerfile
|
uses: ludeeus/action-shellcheck@master
|
||||||
uses: hadolint/hadolint-action@v3.3.0
|
env:
|
||||||
with:
|
SHELLCHECK_OPTS: >-
|
||||||
dockerfile: Dockerfile
|
-x
|
||||||
ignore: DL3008,DL3003,DL3006,DL3013
|
--source-path=src
|
||||||
failure-threshold: warning
|
-e SC1091
|
||||||
-
|
-e SC2001
|
||||||
name: Validate JSON and YML files
|
-e SC2034
|
||||||
uses: GrantBirki/json-yaml-validate@v5.0.0
|
-e SC2317
|
||||||
with:
|
-e SC2153
|
||||||
yaml_exclude_regex: ".*\\kubernetes\\.yml$"
|
-
|
||||||
|
name: Lint Dockerfile
|
||||||
|
uses: hadolint/hadolint-action@v3.4.0
|
||||||
|
with:
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
ignore: DL3008,DL3067
|
||||||
|
failure-threshold: warning
|
||||||
|
-
|
||||||
|
name: Validate JSON and YML files
|
||||||
|
uses: GrantBirki/json-yaml-validate@v5
|
||||||
|
|||||||
@@ -11,61 +11,4 @@ permissions:
|
|||||||
jobs:
|
jobs:
|
||||||
review:
|
review:
|
||||||
name: review
|
name: review
|
||||||
runs-on: ubuntu-latest
|
uses: action-pack/.github/.github/workflows/review.yml@master
|
||||||
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 }}
|
|
||||||
|
|||||||
+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
|
||||||
|
|||||||
+27
-49
@@ -1,72 +1,50 @@
|
|||||||
# syntax=docker/dockerfile:1
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
FROM qemux/qemu-host:2.05 AS builder
|
FROM qemux/qemu-host:2.06 AS host
|
||||||
FROM debian:trixie-slim
|
FROM scratch
|
||||||
|
|
||||||
|
COPY --from=qemux/qemu:7.48 / /
|
||||||
|
|
||||||
ARG TARGETARCH
|
|
||||||
ARG TARGETPLATFORM
|
|
||||||
ARG VERSION_ARG="0.0"
|
ARG VERSION_ARG="0.0"
|
||||||
ARG VERSION_PASST="2026_06_11"
|
ARG VERSION_CSTRUCT="4.7"
|
||||||
|
|
||||||
ARG DEBCONF_NOWARNINGS="yes"
|
ARG DEBCONF_NOWARNINGS="yes"
|
||||||
ARG DEBIAN_FRONTEND="noninteractive"
|
ARG DEBIAN_FRONTEND="noninteractive"
|
||||||
ARG DEBCONF_NONINTERACTIVE_SEEN="true"
|
ARG DEBCONF_NONINTERACTIVE_SEEN="true"
|
||||||
|
|
||||||
RUN set -eu && \
|
RUN <<EOF
|
||||||
apt-get update && \
|
set -eu
|
||||||
apt-get --no-install-recommends -y install \
|
|
||||||
jq \
|
apt-get update
|
||||||
tini \
|
apt-get --no-install-recommends -y install \
|
||||||
curl \
|
fakeroot \
|
||||||
wget \
|
python3-msgpack \
|
||||||
fdisk \
|
python3-pysodium
|
||||||
unzip \
|
|
||||||
nginx \
|
apt-get clean
|
||||||
procps \
|
|
||||||
ethtool \
|
# Install Python dependencies
|
||||||
python3 \
|
pip3 install --no-cache-dir --break-system-packages --root-user-action=ignore "dissect.cstruct==$VERSION_CSTRUCT"
|
||||||
python3-pip \
|
|
||||||
python3-msgpack \
|
# Set version file
|
||||||
python3-pysodium \
|
echo "$VERSION_ARG" > /etc/version
|
||||||
xz-utils \
|
|
||||||
iptables \
|
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||||
iproute2 \
|
EOF
|
||||||
dnsmasq \
|
|
||||||
fakeroot \
|
|
||||||
apt-utils \
|
|
||||||
net-tools \
|
|
||||||
e2fsprogs \
|
|
||||||
qemu-utils \
|
|
||||||
websocketd \
|
|
||||||
iputils-ping \
|
|
||||||
inotify-tools \
|
|
||||||
ca-certificates \
|
|
||||||
netcat-openbsd \
|
|
||||||
qemu-system-x86 && \
|
|
||||||
wget "https://github.com/qemus/passt/releases/download/v${VERSION_PASST}/passt_${VERSION_PASST}_${TARGETARCH}.deb" -O /tmp/passt.deb -q --timeout=10 && \
|
|
||||||
dpkg -i /tmp/passt.deb && \
|
|
||||||
apt-get clean && \
|
|
||||||
pip3 install --no-cache-dir --break-system-packages --root-user-action=ignore dissect.cstruct && \
|
|
||||||
mkdir -p /etc/qemu && \
|
|
||||||
echo "allow br0" > /etc/qemu/bridge.conf && \
|
|
||||||
unlink /etc/nginx/sites-enabled/default && \
|
|
||||||
sed -i 's/^worker_processes.*/worker_processes 1;/' /etc/nginx/nginx.conf && \
|
|
||||||
echo "$VERSION_ARG" > /etc/version && \
|
|
||||||
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
|
||||||
|
|
||||||
COPY --chmod=755 ./src /run/
|
COPY --chmod=755 ./src /run/
|
||||||
COPY --chmod=755 ./web /var/www/
|
COPY --chmod=755 ./web /var/www/
|
||||||
COPY --chmod=755 --from=builder /qemu-host.bin /run/host.bin
|
COPY --chmod=755 --from=host /qemu-host.bin /run/host.bin
|
||||||
COPY --chmod=744 ./web/conf/nginx.conf /etc/nginx/default.conf
|
COPY --chmod=744 ./web/conf/nginx.conf /etc/nginx/default.conf
|
||||||
ADD --chmod=775 https://raw.githubusercontent.com/sud0woodo/patology/refs/heads/main/patology.py /run/extract.py
|
ADD --chmod=775 https://raw.githubusercontent.com/sud0woodo/patology/refs/heads/main/patology.py /run/extract.py
|
||||||
|
|
||||||
VOLUME /storage
|
VOLUME /storage
|
||||||
EXPOSE 22 139 445 5000
|
EXPOSE 445 5000
|
||||||
|
|
||||||
ENV RAM_SIZE="2G"
|
ENV RAM_SIZE="2G"
|
||||||
ENV CPU_CORES="2"
|
ENV CPU_CORES="2"
|
||||||
ENV DISK_SIZE="256G"
|
ENV DISK_SIZE="256G"
|
||||||
|
|
||||||
HEALTHCHECK --interval=60s --start-period=45s --retries=2 CMD /run/check.sh
|
HEALTHCHECK --interval=60s --start-period=45s --retries=2 CMD ["/run/check.sh"]
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/bin/tini", "-s", "/run/entry.sh"]
|
ENTRYPOINT ["/usr/bin/tini", "-s", "/run/entry.sh"]
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
# 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 `ide`, `scsi`, 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. |
|
||||||
|
| `DISK_OPTIONS` | | Additional options appended to QEMU disk devices. |
|
||||||
|
| `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` | `qemu` | TAP or macvtap interface name. |
|
||||||
|
| `BRIDGE` | `docker` | Bridge name used for NAT networking. |
|
||||||
|
| `HOST_PORTS` | | Ports excluded from guest forwarding. |
|
||||||
|
| `USER_PORTS` | | Additional ports to forward to DSM when using user-mode networking. |
|
||||||
|
| `DNSMASQ_OPTS` | | Additional options passed to dnsmasq. |
|
||||||
|
| `DNSMASQ_DEBUG` | `N` | Enables dnsmasq debug output. |
|
||||||
|
| `DNSMASQ_DISABLE` | `N` | Disables the internal dnsmasq resolver. |
|
||||||
|
| `PASST_OPTS` | | Additional options passed to passt. |
|
||||||
|
| `PASST_DEBUG` | `N` | Enables passt debug output. |
|
||||||
|
|
||||||
|
## 🖥️ Display
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `DISPLAY` | `none` | Display backend, such as `vnc`, `disabled`, or `none`. |
|
||||||
|
| `VGA` | `none` | QEMU video adapter model. |
|
||||||
|
| `GPU` | `N` | Enables experimental GPU acceleration. |
|
||||||
|
| `RENDERNODE` | `/dev/dri/renderD128` | Render node used for GPU acceleration. |
|
||||||
|
|
||||||
|
## 🎈 Memory Ballooning
|
||||||
|
|
||||||
|
Also see [Dynamic memory allocation](https://github.com/qemus/qemu/blob/master/docs/ballooning.md) for usage instructions and important caveats.
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `BALLOONING` | `N` | Enables dynamic memory ballooning. |
|
||||||
|
| `BALLOONING_MIN_MEM` | `33%` | Minimum amount of memory retained by the VM. |
|
||||||
|
| `BALLOONING_RAM_THRESHOLD` | `80.0` | Host RAM usage percentage at which ballooning begins adjusting memory. |
|
||||||
|
| `BALLOONING_RAM_THRESHOLD_HARD` | `90.0` | Host RAM usage percentage at which ballooning becomes more aggressive. |
|
||||||
|
| `BALLOONING_PSI_PRESSURE` | `10.0` | PSI memory pressure level at which ballooning becomes more aggressive. |
|
||||||
|
| `BALLOONING_PSI_PRESSURE_MAX` | `50.0` | PSI memory pressure level at which ballooning reaches its strongest response. |
|
||||||
|
| `BALLOONING_HYSTERESIS` | `128M` | Minimum memory change before the balloon target is updated. |
|
||||||
|
| `BALLOONING_KP` | `0.5` | Proportional gain used by the ballooning controller. |
|
||||||
|
| `BALLOONING_KI` | `0.05` | Integral gain used by the ballooning controller. |
|
||||||
|
| `BALLOONING_INTERVAL` | `5` | Polling interval in seconds. |
|
||||||
|
| `BALLOONING_DEBUG` | `N` | Enables debug output for the ballooning monitor. |
|
||||||
|
|
||||||
|
## 🔌 Shutdown
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `SHUTDOWN` | `Y` | Enables graceful shutdown. |
|
||||||
|
| `TIMEOUT` | `105` | 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. |
|
||||||
|
| `MONITOR` | | QEMU monitor configuration. |
|
||||||
|
| `QMP` | | QEMU Machine Protocol configuration. |
|
||||||
|
| `ARGUMENTS` | | Additional raw arguments appended to the QEMU command line. |
|
||||||
@@ -18,6 +18,8 @@ metadata:
|
|||||||
name: dsm
|
name: dsm
|
||||||
spec:
|
spec:
|
||||||
replicas: 1
|
replicas: 1
|
||||||
|
strategy:
|
||||||
|
type: Recreate
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app: dsm
|
app: dsm
|
||||||
|
|||||||
@@ -16,9 +16,14 @@ Virtual DSM in a Docker container.
|
|||||||
|
|
||||||
## Features ✨
|
## Features ✨
|
||||||
|
|
||||||
- Multiple disks
|
- Runs Virtual DSM inside a Docker container
|
||||||
- KVM acceleration
|
- Automatic download of the installation files
|
||||||
- Automatic download
|
- Web-based access to the DSM desktop
|
||||||
|
- Near-native performance with KVM acceleration
|
||||||
|
- Customizable CPU, memory, and storage allocation
|
||||||
|
- Supports multiple disks and physical disk passthrough
|
||||||
|
- Dynamic memory allocation with memory ballooning
|
||||||
|
- Supports NAT, user-mode, macvlan, and macvtap networking
|
||||||
|
|
||||||
## Usage 🐳
|
## Usage 🐳
|
||||||
|
|
||||||
@@ -62,12 +67,13 @@ kubectl apply -f https://raw.githubusercontent.com/vdsm/virtual-dsm/refs/heads/m
|
|||||||
|
|
||||||
## Requirements ⚙️
|
## Requirements ⚙️
|
||||||
|
|
||||||
- A Linux host with KVM support, or Docker Desktop / Podman on Windows 11 with nested virtualization enabled.
|
- Docker or Podman on a Linux host with KVM support.
|
||||||
- At least 2 GB of RAM available.
|
- Docker Desktop or Podman (Desktop) on Windows 11 with nested virtualization enabled.
|
||||||
- At least 32 GB of free disk space.
|
- At least 1 GB of available RAM.
|
||||||
|
- At least 16 GB of free disk space.
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> Docker Desktop on macOS and Windows 10 do not currently provide the required KVM support for this image.
|
> Docker Desktop on Linux, macOS, and Windows 10 does not currently provide KVM access to containers and is therefore not supported.
|
||||||
|
|
||||||
## FAQ 💬
|
## FAQ 💬
|
||||||
|
|
||||||
@@ -79,7 +85,7 @@ kubectl apply -f https://raw.githubusercontent.com/vdsm/virtual-dsm/refs/heads/m
|
|||||||
|
|
||||||
- Wait until DSM finishes its installation
|
- Wait until DSM finishes its installation
|
||||||
|
|
||||||
- Choose an username and password, and you will be taken to the desktop.
|
- Choose a username and password, and you will be taken to the desktop.
|
||||||
|
|
||||||
Enjoy your brand new NAS, and don't forget to star this repo!
|
Enjoy your brand new NAS, and don't forget to star this repo!
|
||||||
|
|
||||||
@@ -143,34 +149,6 @@ kubectl apply -f https://raw.githubusercontent.com/vdsm/virtual-dsm/refs/heads/m
|
|||||||
CPU_CORES: "4"
|
CPU_CORES: "4"
|
||||||
```
|
```
|
||||||
|
|
||||||
### How do I verify if my system supports KVM?
|
|
||||||
|
|
||||||
First check if your software is compatible using this chart:
|
|
||||||
|
|
||||||
| **Product** | **Linux** | **Win11** | **Win10** | **macOS** |
|
|
||||||
|---|---|---|---|---|
|
|
||||||
| Docker CLI | ✅ | ✅ | ❌ | ❌ |
|
|
||||||
| Docker Desktop | ❌ | ✅ | ❌ | ❌ |
|
|
||||||
| Podman CLI | ✅ | ✅ | ❌ | ❌ |
|
|
||||||
| Podman Desktop | ✅ | ✅ | ❌ | ❌ |
|
|
||||||
|
|
||||||
After that you can run the following commands in Linux to check your system:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo apt install cpu-checker
|
|
||||||
sudo kvm-ok
|
|
||||||
```
|
|
||||||
|
|
||||||
If you receive an error from `kvm-ok` indicating that KVM cannot be used, please check whether:
|
|
||||||
|
|
||||||
- the virtualization extensions (`Intel VT-x` or `AMD SVM`) are enabled in your BIOS.
|
|
||||||
|
|
||||||
- you enabled "nested virtualization" if you are running the container inside a virtual machine.
|
|
||||||
|
|
||||||
- you are not using a cloud provider, as most of them do not allow nested virtualization for their VPSs.
|
|
||||||
|
|
||||||
If you did not receive any error from `kvm-ok` but the container still complains about a missing KVM device, it could help to add `privileged: true` to your compose file (or `sudo` to your `docker` command) to rule out any permission issue.
|
|
||||||
|
|
||||||
### How do I assign an individual IP address to the container?
|
### How do I assign an individual IP address to the container?
|
||||||
|
|
||||||
By default, the container uses bridge networking, which shares the IP address with the host.
|
By default, the container uses bridge networking, which shares the IP address with the host.
|
||||||
@@ -223,9 +201,9 @@ kubectl apply -f https://raw.githubusercontent.com/vdsm/virtual-dsm/refs/heads/m
|
|||||||
- 'c *:* rwm'
|
- 'c *:* rwm'
|
||||||
```
|
```
|
||||||
|
|
||||||
### How do I pass through the GPU?
|
### How do I enable GPU acceleration?
|
||||||
|
|
||||||
To pass through your Intel GPU, add the following lines to your compose file:
|
To enable hardware-accelerated graphics, add the following lines to your compose file:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
environment:
|
environment:
|
||||||
@@ -234,8 +212,13 @@ kubectl apply -f https://raw.githubusercontent.com/vdsm/virtual-dsm/refs/heads/m
|
|||||||
- /dev/dri
|
- /dev/dri
|
||||||
```
|
```
|
||||||
|
|
||||||
> [!NOTE]
|
This can be used to enable the facial recognition function in Synology Photos, but does not provide hardware transcoding for video.
|
||||||
> This can be used to enable the facial recognition function in Synology Photos, but does not provide hardware transcoding for video.
|
|
||||||
|
### How do I enable dynamic memory allocation?
|
||||||
|
|
||||||
|
By default, the DSM is allocated the full amount of RAM configured via `RAM_SIZE` for its entire lifetime.
|
||||||
|
|
||||||
|
However, you can enable [memory ballooning](https://github.com/qemus/qemu/blob/master/docs/ballooning.md) if you want the container to dynamically reclaim unused guest RAM based on host memory pressure.
|
||||||
|
|
||||||
### How do I install a specific version of vDSM?
|
### How do I install a specific version of vDSM?
|
||||||
|
|
||||||
@@ -257,6 +240,41 @@ kubectl apply -f https://raw.githubusercontent.com/vdsm/virtual-dsm/refs/heads/m
|
|||||||
|
|
||||||
Replace the example path `./DSM_VirtualDSM_42218.pat` with the filename of your desired `.pat` file. The value of `URL` will be ignored in this case.
|
Replace the example path `./DSM_VirtualDSM_42218.pat` with the filename of your desired `.pat` file. The value of `URL` will be ignored in this case.
|
||||||
|
|
||||||
|
### Are these all available options?
|
||||||
|
|
||||||
|
No. For a complete overview of all supported settings, see the [environment variables](docs/environment.md) page.
|
||||||
|
|
||||||
|
### How do I verify that KVM is available?
|
||||||
|
|
||||||
|
First, make sure your platform and container runtime meet the [requirements](#requirements-️) listed above.
|
||||||
|
|
||||||
|
On a Linux host, install `cpu-checker` and run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt install cpu-checker
|
||||||
|
sudo kvm-ok
|
||||||
|
```
|
||||||
|
|
||||||
|
A working configuration should report:
|
||||||
|
|
||||||
|
```text
|
||||||
|
KVM acceleration can be used
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also verify that the KVM device exists:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ls -l /dev/kvm
|
||||||
|
```
|
||||||
|
|
||||||
|
If KVM is unavailable, check whether:
|
||||||
|
|
||||||
|
- Hardware virtualization (`Intel VT-x` or `AMD-V`) is enabled in your BIOS or UEFI.
|
||||||
|
- Nested virtualization is enabled when the host itself is a virtual machine.
|
||||||
|
- Your VPS or cloud provider supports nested virtualization.
|
||||||
|
|
||||||
|
If `kvm-ok` succeeds but the container still reports that KVM is unavailable, you can temporarily add `privileged: true` to your Compose file to rule out a permission or device-access issue.
|
||||||
|
|
||||||
### What are the differences compared to the standard DSM?
|
### What are the differences compared to the standard DSM?
|
||||||
|
|
||||||
There are only two minor differences: the Virtual Machine Manager package is not available, and Surveillance Station will not include any free licenses.
|
There are only two minor differences: the Virtual Machine Manager package is not available, and Surveillance Station will not include any free licenses.
|
||||||
|
|||||||
+16
-5
@@ -1,24 +1,35 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -Eeuo pipefail
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
cd /run
|
||||||
|
. utils.sh # Load functions
|
||||||
|
|
||||||
: "${DHCP:="N"}"
|
: "${DHCP:="N"}"
|
||||||
: "${NETWORK:="Y"}"
|
: "${NETWORK:="Y"}"
|
||||||
|
|
||||||
[ -f "/run/shm/qemu.end" ] && echo "QEMU is shutting down..." && exit 1
|
[ -f "/run/shm/qemu.end" ] && echo "QEMU is shutting down..." && exit 1
|
||||||
|
|
||||||
|
# Treat the startup window as healthy so container health checks do not restart
|
||||||
|
# the service before QEMU has had time to publish its PID.
|
||||||
[ ! -s "/run/shm/qemu.pid" ] && echo "QEMU is not running yet..." && exit 0
|
[ ! -s "/run/shm/qemu.pid" ] && echo "QEMU is not running yet..." && exit 0
|
||||||
[[ "$NETWORK" == [Nn]* ]] && echo "Networking is disabled." && exit 0
|
|
||||||
|
disabled "$NETWORK" && echo "Networking is disabled." && exit 0
|
||||||
|
|
||||||
file="/run/shm/dsm.url"
|
file="/run/shm/dsm.url"
|
||||||
address="/run/shm/qemu.ip"
|
address="/run/shm/qemu.host"
|
||||||
gateway="/run/shm/qemu.gw"
|
gateway="/run/shm/qemu.gw"
|
||||||
|
|
||||||
[ ! -s "$file" ] && echo "DSM has not enabled networking yet..." && exit 1
|
# dsm.url is written only after the guest agent reports both the DSM
|
||||||
|
# address and its configured HTTP port.
|
||||||
|
[ ! -s "$file" ] && echo "DSM has not enabled networking yet..." && exit 0
|
||||||
|
|
||||||
location=$(<"$file")
|
location=$(<"$file")
|
||||||
|
|
||||||
if ! curl -m 20 -ILfSs "http://$location/" > /dev/null; then
|
if ! curl -m 20 -LfSs -o /dev/null "http://$location/"; then
|
||||||
|
|
||||||
if [[ "$DHCP" == [Yy1]* ]]; then
|
# In DHCP mode the firewall must allow the container address; with port
|
||||||
|
# forwarding it must allow the internal gateway used to reach the guest.
|
||||||
|
if enabled "$DHCP"; then
|
||||||
ip=$(<"$address")
|
ip=$(<"$address")
|
||||||
echo "Failed to reach DSM at http://$location"
|
echo "Failed to reach DSM at http://$location"
|
||||||
else
|
else
|
||||||
|
|||||||
+108
-10
@@ -1,16 +1,114 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -Eeuo pipefail
|
set -Eeuo pipefail
|
||||||
|
|
||||||
DEF_OPTS="-nodefaults -boot strict=on"
|
: "${RNG:=""}"
|
||||||
RAM_OPTS=$(echo "-m ${RAM_SIZE^^}" | sed 's/MB/M/g;s/GB/G/g;s/TB/T/g')
|
: "${QMP:=""}"
|
||||||
MON_OPTS="-name $PROCESS,process=$PROCESS,debug-threads=on -pidfile $QEMU_PID"
|
: "${UUID:=""}"
|
||||||
CPU_OPTS="-cpu $CPU_FLAGS -smp $CPU_CORES,sockets=1,dies=1,cores=$CPU_CORES,threads=1"
|
: "${MONITOR:=""}"
|
||||||
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"
|
|
||||||
|
|
||||||
ARGS="$DEF_OPTS $CPU_OPTS $RAM_OPTS $MAC_OPTS $DISPLAY_OPTS $MON_OPTS $SERIAL_OPTS $NET_OPTS $DISK_OPTS $DEV_OPTS $ARGUMENTS"
|
msg="Configuring QEMU..."
|
||||||
ARGS=$(echo "$ARGS" | sed 's/\t/ /g' | tr -s ' ')
|
enabled "$DEBUG" && echo "$msg"
|
||||||
|
|
||||||
|
# Sanitize variables
|
||||||
|
QMP=$(strip "$QMP")
|
||||||
|
UUID=$(strip "$UUID")
|
||||||
|
MONITOR=$(strip "$MONITOR")
|
||||||
|
|
||||||
|
configureProcessor() {
|
||||||
|
|
||||||
|
# Expose one thread per core in a single socket; DSM licensing and topology
|
||||||
|
# reporting are more predictable with this fixed layout.
|
||||||
|
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=""
|
||||||
|
[ -n "$QMP" ] && MON_OPTS+=" -qmp $QMP"
|
||||||
|
[ -n "$MONITOR" ] && MON_OPTS+=" -monitor $MONITOR"
|
||||||
|
MON_OPTS="${MON_OPTS# }"
|
||||||
|
|
||||||
|
local name="${APP// /-}"
|
||||||
|
ID_OPTS="-name $name,process=$PROCESS,debug-threads=on"
|
||||||
|
PID_OPTS="-pidfile $QEMU_PID"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
configureMachine() {
|
||||||
|
|
||||||
|
local smm="off"
|
||||||
|
enabled "${SMM:-}" && smm="on"
|
||||||
|
|
||||||
|
# Disable firmware and chipset features that Virtual DSM does not use and
|
||||||
|
# that can introduce extra devices or timing differences.
|
||||||
|
MAC_OPTS="-machine type=$MACHINE,smm=$smm,usb=off"
|
||||||
|
MAC_OPTS+=",vmport=off,dump-guest-core=off,hpet=off${KVM_OPTS}"
|
||||||
|
|
||||||
|
[ -n "$UUID" ] && ID_OPTS+=" -uuid $UUID"
|
||||||
|
[ -n "${SM_BIOS:-}" ] && ID_OPTS+=" $SM_BIOS"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
configureDevices() {
|
||||||
|
|
||||||
|
local bus
|
||||||
|
bus=$(getPciBus)
|
||||||
|
|
||||||
|
DEV_OPTS=""
|
||||||
|
|
||||||
|
if ! disabled "$RNG"; then
|
||||||
|
DEV_OPTS+=" -object rng-random,id=objrng0,filename=/dev/urandom"
|
||||||
|
DEV_OPTS+=" -device virtio-rng-pci,rng=objrng0,id=rng0,bus=$bus,addr=0x1c"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Keep the existing balloon device by default. When dynamic ballooning is
|
||||||
|
# enabled, expose guest statistics and a dedicated QMP control socket.
|
||||||
|
if ! enabled "${BALLOONING:-}"; then
|
||||||
|
DEV_OPTS+=" -device virtio-balloon-pci,id=balloon0,bus=$bus,addr=0x4"
|
||||||
|
else
|
||||||
|
MON_OPTS+=" -qmp unix:${BALLOONING_SOCKET},server=on,wait=off"
|
||||||
|
DEV_OPTS+=" -device virtio-balloon-pci,free-page-reporting=on,guest-stats-polling-interval=1,id=balloon0,bus=$bus,addr=0x4"
|
||||||
|
fi
|
||||||
|
|
||||||
|
DEV_OPTS="${DEV_OPTS# }"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
buildArguments() {
|
||||||
|
|
||||||
|
local default="-nodefaults"
|
||||||
|
local boot="-boot strict=on"
|
||||||
|
|
||||||
|
ARGS="$default $MAC_OPTS $CPU_OPTS $RAM_OPTS $ID_OPTS $PID_OPTS $DISPLAY_OPTS $MON_OPTS $SERIAL_OPTS $NET_OPTS $DISK_OPTS $boot $BOOT_OPTS $DEV_OPTS $ARGUMENTS"
|
||||||
|
|
||||||
|
# Collapse whitespace after optional argument groups are assembled so empty
|
||||||
|
# features cannot leave malformed spacing in the final QEMU command.
|
||||||
|
ARGS=$(echo "$ARGS" | sed 's/\t/ /g' | tr -s ' ')
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
finalizeMemory
|
||||||
|
|
||||||
|
configureMemory
|
||||||
|
configureMonitor
|
||||||
|
configureMachine
|
||||||
|
configureProcessor
|
||||||
|
configureDevices
|
||||||
|
|
||||||
|
buildArguments
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
-713
@@ -1,713 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -Eeuo pipefail
|
|
||||||
|
|
||||||
# Docker environment variables
|
|
||||||
|
|
||||||
: "${DISK_IO:="native"}" # I/O Mode, can be set to 'native', 'threads' or 'io_uring'
|
|
||||||
: "${DISK_FMT:="raw"}" # Disk file format, 'raw' by default for best performance
|
|
||||||
: "${DISK_TYPE:=""}" # Device type to be used, "sata", "nvme", "blk" or "scsi"
|
|
||||||
: "${DISK_FLAGS:=""}" # Specifies the options for use with the qcow2 disk format
|
|
||||||
: "${DISK_CACHE:="none"}" # Caching mode, can be set to 'writeback' for better performance
|
|
||||||
: "${DISK_DISCARD:="on"}" # Controls whether unmap (TRIM) commands are passed to the host.
|
|
||||||
: "${DISK_ROTATION:="1"}" # Rotation rate, set to 1 for SSD storage and increase for HDD
|
|
||||||
|
|
||||||
# Sanitize all variables
|
|
||||||
DISK_IO=$(strip "$DISK_IO")
|
|
||||||
DISK_FMT=$(strip "$DISK_FMT")
|
|
||||||
DISK_TYPE=$(strip "$DISK_TYPE")
|
|
||||||
DISK_FLAGS=$(strip "$DISK_FLAGS")
|
|
||||||
DISK_CACHE=$(strip "$DISK_CACHE")
|
|
||||||
DISK_DISCARD=$(strip "$DISK_DISCARD")
|
|
||||||
DISK_ROTATION=$(strip "$DISK_ROTATION")
|
|
||||||
|
|
||||||
BOOT="$STORAGE/$BASE.boot.img"
|
|
||||||
SYSTEM="$STORAGE/$BASE.system.img"
|
|
||||||
|
|
||||||
[ ! -s "$BOOT" ] && error "Virtual DSM boot-image does not exist ($BOOT)" && exit 81
|
|
||||||
[ ! -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\" !"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! setOwner "$SYSTEM"; then
|
|
||||||
error "Failed to set the owner for \"$SYSTEM\" !"
|
|
||||||
fi
|
|
||||||
|
|
||||||
fmt2ext() {
|
|
||||||
local DISK_FMT="$1"
|
|
||||||
|
|
||||||
case "${DISK_FMT,,}" in
|
|
||||||
qcow2)
|
|
||||||
echo "qcow2"
|
|
||||||
;;
|
|
||||||
raw)
|
|
||||||
echo "img"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
error "Unrecognized disk format: $DISK_FMT" && exit 78
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
ext2fmt() {
|
|
||||||
local DISK_EXT="$1"
|
|
||||||
|
|
||||||
case "${DISK_EXT,,}" in
|
|
||||||
qcow2)
|
|
||||||
echo "qcow2"
|
|
||||||
;;
|
|
||||||
img)
|
|
||||||
echo "raw"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
error "Unrecognized file extension: .$DISK_EXT" && exit 78
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
getSize() {
|
|
||||||
local DISK_FILE="$1"
|
|
||||||
local DISK_EXT DISK_FMT
|
|
||||||
|
|
||||||
DISK_EXT=$(echo "${DISK_FILE//*./}" | sed 's/^.*\.//')
|
|
||||||
DISK_FMT=$(ext2fmt "$DISK_EXT")
|
|
||||||
|
|
||||||
case "${DISK_FMT,,}" in
|
|
||||||
raw)
|
|
||||||
stat -c%s "$DISK_FILE"
|
|
||||||
;;
|
|
||||||
qcow2)
|
|
||||||
qemu-img info "$DISK_FILE" -f "$DISK_FMT" | grep '^virtual size: ' | sed 's/.*(\(.*\) bytes)/\1/'
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
error "Unrecognized disk format: $DISK_FMT" && exit 78
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
isCow() {
|
|
||||||
local FS="$1"
|
|
||||||
|
|
||||||
if [[ "${FS,,}" == "btrfs" ]]; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
supportsDirect() {
|
|
||||||
local FS="$1"
|
|
||||||
|
|
||||||
if [[ "${FS,,}" == "ecryptfs" || "${FS,,}" == "tmpfs" ]]; then
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
createDisk() {
|
|
||||||
|
|
||||||
local DISK_FILE="$1"
|
|
||||||
local DISK_SPACE="$2"
|
|
||||||
local DISK_DESC="$3"
|
|
||||||
local DISK_FMT="$4"
|
|
||||||
local FS="$5"
|
|
||||||
local DATA_SIZE DIR SPACE GB FA
|
|
||||||
|
|
||||||
rm -f "$DISK_FILE"
|
|
||||||
|
|
||||||
DATA_SIZE=$(numfmt --from=iec "$DISK_SPACE")
|
|
||||||
|
|
||||||
if ! disabled "$ALLOCATE"; then
|
|
||||||
|
|
||||||
# Check free diskspace
|
|
||||||
DIR=$(dirname "$DISK_FILE")
|
|
||||||
SPACE=$(df --output=avail -B 1 "$DIR" | tail -n 1)
|
|
||||||
|
|
||||||
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
|
|
||||||
fi
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
html "Creating a $DISK_DESC image..."
|
|
||||||
info "Creating a ${DISK_SPACE/G/ GB} $DISK_STYLE $DISK_DESC image in $DISK_FMT format..."
|
|
||||||
|
|
||||||
local FAIL="Could not create a $DISK_STYLE $DISK_FMT $DISK_DESC image of ${DISK_SPACE/G/ GB} ($DISK_FILE)"
|
|
||||||
|
|
||||||
case "${DISK_FMT,,}" in
|
|
||||||
raw)
|
|
||||||
|
|
||||||
if isCow "$FS"; then
|
|
||||||
if ! touch "$DISK_FILE"; then
|
|
||||||
error "$FAIL" && exit 77
|
|
||||||
fi
|
|
||||||
{ chattr +C "$DISK_FILE"; } || :
|
|
||||||
fi
|
|
||||||
|
|
||||||
if disabled "$ALLOCATE"; then
|
|
||||||
|
|
||||||
# Create an empty file
|
|
||||||
if ! truncate -s "$DATA_SIZE" "$DISK_FILE"; then
|
|
||||||
rm -f "$DISK_FILE"
|
|
||||||
error "$FAIL" && exit 77
|
|
||||||
fi
|
|
||||||
|
|
||||||
else
|
|
||||||
|
|
||||||
# Create an empty file
|
|
||||||
if ! fallocate -l "$DATA_SIZE" "$DISK_FILE" &>/dev/null; then
|
|
||||||
if ! fallocate -l -x "$DATA_SIZE" "$DISK_FILE"; then
|
|
||||||
if ! truncate -s "$DATA_SIZE" "$DISK_FILE"; then
|
|
||||||
rm -f "$DISK_FILE"
|
|
||||||
error "$FAIL" && exit 77
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
qcow2)
|
|
||||||
|
|
||||||
local DISK_PARAM="$DISK_ALLOC"
|
|
||||||
isCow "$FS" && DISK_PARAM+=",nocow=on"
|
|
||||||
[ -n "$DISK_FLAGS" ] && DISK_PARAM+=",$DISK_FLAGS"
|
|
||||||
|
|
||||||
if ! qemu-img create -f "$DISK_FMT" -o "$DISK_PARAM" -- "$DISK_FILE" "$DATA_SIZE" ; then
|
|
||||||
rm -f "$DISK_FILE"
|
|
||||||
error "$FAIL" && 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)"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
CUR_SIZE=$(getSize "$DISK_FILE")
|
|
||||||
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
|
|
||||||
|
|
||||||
if ! disabled "$ALLOCATE"; then
|
|
||||||
|
|
||||||
# Check free diskspace
|
|
||||||
DIR=$(dirname "$DISK_FILE")
|
|
||||||
SPACE=$(df --output=avail -B 1 "$DIR" | tail -n 1)
|
|
||||||
|
|
||||||
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
|
|
||||||
fi
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
GB=$(formatBytes "$CUR_SIZE")
|
|
||||||
MSG="Resizing $DISK_DESC from $GB to ${DISK_SPACE/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)"
|
|
||||||
|
|
||||||
case "${DISK_FMT,,}" in
|
|
||||||
raw)
|
|
||||||
|
|
||||||
if disabled "$ALLOCATE"; then
|
|
||||||
|
|
||||||
# Resize file by changing its length
|
|
||||||
if ! truncate -s "$DATA_SIZE" "$DISK_FILE"; then
|
|
||||||
error "$FAIL" && exit 75
|
|
||||||
fi
|
|
||||||
|
|
||||||
else
|
|
||||||
|
|
||||||
# Resize file by allocating more space
|
|
||||||
if ! fallocate -l "$DATA_SIZE" "$DISK_FILE" &>/dev/null; then
|
|
||||||
if ! fallocate -l -x "$DATA_SIZE" "$DISK_FILE"; then
|
|
||||||
if ! truncate -s "$DATA_SIZE" "$DISK_FILE"; then
|
|
||||||
error "$FAIL" && exit 75
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
qcow2)
|
|
||||||
|
|
||||||
if ! qemu-img resize -f "$DISK_FMT" "--$DISK_ALLOC" "$DISK_FILE" "$DATA_SIZE" ; then
|
|
||||||
error "$FAIL" && exit 72
|
|
||||||
fi
|
|
||||||
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
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"
|
|
||||||
|
|
||||||
[ -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 TMP_FILE="$DISK_BASE.tmp"
|
|
||||||
rm -f "$TMP_FILE"
|
|
||||||
|
|
||||||
local DIR FA
|
|
||||||
DIR=$(dirname "$TMP_FILE")
|
|
||||||
|
|
||||||
if ! disabled "$ALLOCATE"; then
|
|
||||||
|
|
||||||
local CUR_SIZE SPACE GB
|
|
||||||
|
|
||||||
# Check free diskspace
|
|
||||||
CUR_SIZE=$(getSize "$SOURCE_FILE")
|
|
||||||
SPACE=$(df --output=avail -B 1 "$DIR" | tail -n 1)
|
|
||||||
|
|
||||||
if (( CUR_SIZE > SPACE )); then
|
|
||||||
GB=$(formatBytes "$SPACE")
|
|
||||||
error "Not enough free space to convert $DISK_DESC to $DST_FMT in $DIR, it has only $GB available..."
|
|
||||||
error "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"
|
|
||||||
html "$msg..."
|
|
||||||
info "$msg, please wait until completed..."
|
|
||||||
|
|
||||||
local CONV_FLAGS="-p"
|
|
||||||
local DISK_PARAM="$DISK_ALLOC"
|
|
||||||
isCow "$FS" && DISK_PARAM+=",nocow=on"
|
|
||||||
|
|
||||||
if [[ "$DST_FMT" != "raw" ]]; then
|
|
||||||
if disabled "$ALLOCATE"; then
|
|
||||||
CONV_FLAGS+=" -c"
|
|
||||||
fi
|
|
||||||
[ -n "$DISK_FLAGS" ] && DISK_PARAM+=",$DISK_FLAGS"
|
|
||||||
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
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "$DST_FMT" == "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"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
rm -f "$SOURCE_FILE"
|
|
||||||
mv "$TMP_FILE" "$DST_FILE"
|
|
||||||
|
|
||||||
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)"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
msg="Conversion of $DISK_DESC"
|
|
||||||
html "$msg completed..."
|
|
||||||
info "$msg to $DST_FMT completed successfully!"
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
checkFS () {
|
|
||||||
|
|
||||||
local FS="$1"
|
|
||||||
local DISK_FILE="$2"
|
|
||||||
local DISK_DESC="$3"
|
|
||||||
local DIR FA
|
|
||||||
|
|
||||||
DIR=$(dirname "$DISK_FILE")
|
|
||||||
[ ! -d "$DIR" ] && return 0
|
|
||||||
|
|
||||||
if [[ "${FS,,}" == "overlay"* && "${ENGINE,,}" == "docker" ]]; then
|
|
||||||
warn "the filesystem of $DIR is OverlayFS, this usually means it was binded to an invalid path!"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "${FS,,}" == "fuse"* ]]; then
|
|
||||||
warn "the filesystem of $DIR is FUSE, this extra layer will negatively affect performance!"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! supportsDirect "$FS"; then
|
|
||||||
warn "the filesystem of $DIR is $FS, which does not support O_DIRECT mode, adjusting settings..."
|
|
||||||
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!"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
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 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"
|
|
||||||
|
|
||||||
case "${DISK_TYPE,,}" in
|
|
||||||
"none" ) ;;
|
|
||||||
"auto" )
|
|
||||||
echo "$result"
|
|
||||||
;;
|
|
||||||
"usb" )
|
|
||||||
result+=",if=none \
|
|
||||||
-device usb-storage,drive=${DISK_ID}${index}${DISK_SERIAL}${DISK_SECTORS}"
|
|
||||||
echo "$result"
|
|
||||||
;;
|
|
||||||
"nvme" )
|
|
||||||
result+=",if=none \
|
|
||||||
-device nvme,drive=${DISK_ID}${index},serial=deadbeaf${DISK_INDEX}${DISK_SERIAL}${DISK_SECTORS}"
|
|
||||||
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}"
|
|
||||||
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}"
|
|
||||||
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}"
|
|
||||||
echo "$result"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
finishDisks () {
|
|
||||||
|
|
||||||
case "${DISK_TYPE,,}" in
|
|
||||||
"blk" | "scsi" | "virtio-blk" | "virtio-scsi" )
|
|
||||||
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 GB DATA_SIZE FS PREV_FMT PREV_EXT CUR_SIZE LEFT FREE USED
|
|
||||||
|
|
||||||
DISK_EXT=$(fmt2ext "$DISK_FMT")
|
|
||||||
local DISK_FILE="$DISK_BASE.$DISK_EXT"
|
|
||||||
|
|
||||||
DIR=$(dirname "$DISK_FILE")
|
|
||||||
[ ! -d "$DIR" ] && return 0
|
|
||||||
|
|
||||||
if [[ "${DISK_SPACE,,}" == "max" || "${DISK_SPACE,,}" == "half" ]]; then
|
|
||||||
|
|
||||||
local SPARE=1073741824
|
|
||||||
FREE=$(df --output=avail -B 1 "$DIR" | tail -n 1)
|
|
||||||
|
|
||||||
if [[ "${DISK_SPACE,,}" == "max" ]]; then
|
|
||||||
FREE=$(( FREE - SPARE ))
|
|
||||||
else
|
|
||||||
FREE=$(( FREE / 2 ))
|
|
||||||
fi
|
|
||||||
|
|
||||||
(( FREE < SPARE )) && FREE="$SPARE"
|
|
||||||
GB=$(( FREE / 1073741825 ))
|
|
||||||
DISK_SPACE="${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')
|
|
||||||
|
|
||||||
if ! numfmt --from=iec "$SPACE" &>/dev/null; then
|
|
||||||
error "Invalid value for ${DISK_DESC^^}_SIZE: $DISK_SPACE" && exit 73
|
|
||||||
fi
|
|
||||||
|
|
||||||
DATA_SIZE=$(numfmt --from=iec "$SPACE")
|
|
||||||
|
|
||||||
if (( DATA_SIZE < 6442450944 )); then
|
|
||||||
error "Please increase the ${DISK_DESC^^}_SIZE variable to at least 6 GB." && exit 73
|
|
||||||
fi
|
|
||||||
|
|
||||||
FS=$(stat -f -c %T "$DIR")
|
|
||||||
checkFS "$FS" "$DISK_FILE" "$DISK_DESC" || exit $?
|
|
||||||
|
|
||||||
if ! supportsDirect "$FS"; then
|
|
||||||
DISK_IO="threads"
|
|
||||||
DISK_CACHE="writeback"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ! -s "$DISK_FILE" ] ; then
|
|
||||||
|
|
||||||
if [[ "${DISK_FMT,,}" != "raw" ]]; then
|
|
||||||
PREV_FMT="raw"
|
|
||||||
else
|
|
||||||
PREV_FMT="qcow2"
|
|
||||||
fi
|
|
||||||
|
|
||||||
PREV_EXT=$(fmt2ext "$PREV_FMT")
|
|
||||||
|
|
||||||
if [ -s "$DISK_BASE.$PREV_EXT" ] ; then
|
|
||||||
convertDisk "$DISK_BASE.$PREV_EXT" "$PREV_FMT" "$DISK_FILE" "$DISK_FMT" "$DISK_BASE" "$DISK_DESC" "$FS" || exit $?
|
|
||||||
fi
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -s "$DISK_FILE" ]; then
|
|
||||||
|
|
||||||
CUR_SIZE=$(getSize "$DISK_FILE")
|
|
||||||
|
|
||||||
if (( DATA_SIZE > CUR_SIZE )); then
|
|
||||||
|
|
||||||
resizeDisk "$DISK_FILE" "$SPACE" "$DISK_DESC" "$DISK_FMT" "$FS" || exit $?
|
|
||||||
|
|
||||||
else
|
|
||||||
|
|
||||||
if (( DATA_SIZE < CUR_SIZE )); 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..."
|
|
||||||
fi
|
|
||||||
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
else
|
|
||||||
|
|
||||||
createDisk "$DISK_FILE" "$SPACE" "$DISK_DESC" "$DISK_FMT" "$FS" || exit $?
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -f "$DISK_FILE" ] && disabled "$ALLOCATE"; then
|
|
||||||
|
|
||||||
CUR_SIZE=$(getSize "$DISK_FILE")
|
|
||||||
USED=$(du -sB 1 "$DISK_FILE" | cut -f1)
|
|
||||||
FREE=$(df --output=avail -B 1 "$DIR" | tail -n 1)
|
|
||||||
LEFT=$(( CUR_SIZE - USED - FREE ))
|
|
||||||
|
|
||||||
if (( LEFT > 0 )); then
|
|
||||||
|
|
||||||
GB=$(formatBytes "$FREE")
|
|
||||||
LEFT=$(formatBytes "$LEFT")
|
|
||||||
CUR_SIZE=$(formatBytes "$CUR_SIZE")
|
|
||||||
msg="The virtual size of the ${DISK_DESC,,} is $CUR_SIZE"
|
|
||||||
|
|
||||||
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."
|
|
||||||
|
|
||||||
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" "" "")
|
|
||||||
|
|
||||||
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 sectors=""
|
|
||||||
local dev_type=""
|
|
||||||
dev_type=$(lsblk -no TYPE "$DISK_DEV" 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 logical physical
|
|
||||||
result=$(fdisk -l "$DISK_DEV" | grep -m 1 -o "(logical/physical): .*" | cut -c 21-)
|
|
||||||
logical="${result%% *}"
|
|
||||||
physical=$(echo "$result" | grep -m 1 -o "/ .*" | cut -c 3-)
|
|
||||||
physical="${physical%% *}"
|
|
||||||
|
|
||||||
if [ -n "$physical" ]; then
|
|
||||||
if [[ "$physical" != "512" ]]; then
|
|
||||||
sectors=",logical_block_size=$logical,physical_block_size=$physical"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
warn "Failed to determine the sector size for $DISK_DEV"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
DISK_OPTS+=$(createDevice "$DISK_DEV" "$DISK_TYPE" "$DISK_INDEX" "$DISK_ADDRESS" "raw" "$DISK_IO" "$DISK_CACHE" "" "$sectors")
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
[ -z "${DISK_OPTS:-}" ] && DISK_OPTS=""
|
|
||||||
[ -z "${DISK_TYPE:-}" ] && DISK_TYPE="scsi"
|
|
||||||
[ -z "${DISK_NAME:-}" ] && DISK_NAME="data"
|
|
||||||
[ -z "${DISK_DISABLE:-}" ] && DISK_DISABLE=""
|
|
||||||
|
|
||||||
if ! enabled "$DISK_DISABLE"; then
|
|
||||||
msg="Initializing disks..."
|
|
||||||
html "$msg"
|
|
||||||
enabled "$DEBUG" && echo "$msg"
|
|
||||||
fi
|
|
||||||
|
|
||||||
case "${DISK_TYPE,,}" in
|
|
||||||
"ide" | "sata" | "nvme" | "usb" | "scsi" | "blk" | "auto" | "none" ) ;;
|
|
||||||
* ) error "Invalid DISK_TYPE specified, value \"$DISK_TYPE\" is not recognized!" && exit 80 ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
if [ -z "$ALLOCATE" ]; then
|
|
||||||
ALLOCATE="N"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if disabled "$ALLOCATE"; then
|
|
||||||
DISK_STYLE="growable"
|
|
||||||
DISK_ALLOC="preallocation=off"
|
|
||||||
else
|
|
||||||
DISK_STYLE="preallocated"
|
|
||||||
DISK_ALLOC="preallocation=falloc"
|
|
||||||
fi
|
|
||||||
|
|
||||||
DISK_OPTS+=$(createDevice "$BOOT" "$DISK_TYPE" "1" "0xa" "raw" "$DISK_IO" "$DISK_CACHE" "" "")
|
|
||||||
DISK_OPTS+=$(createDevice "$SYSTEM" "$DISK_TYPE" "2" "0xb" "raw" "$DISK_IO" "$DISK_CACHE" "" "")
|
|
||||||
|
|
||||||
if enabled "$DISK_DISABLE"; then
|
|
||||||
finishDisks
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
DISK1_FILE="$STORAGE/${DISK_NAME}"
|
|
||||||
DISK2_FILE="/storage2/${DISK_NAME}2"
|
|
||||||
DISK3_FILE="/storage3/${DISK_NAME}3"
|
|
||||||
DISK4_FILE="/storage4/${DISK_NAME}4"
|
|
||||||
|
|
||||||
: "${DISK2_SIZE:=""}"
|
|
||||||
: "${DISK3_SIZE:=""}"
|
|
||||||
: "${DISK4_SIZE:=""}"
|
|
||||||
|
|
||||||
: "${DEVICE:=""}" # Docker variables to passthrough a block device, like /dev/vdc1.
|
|
||||||
: "${DEVICE2:=""}"
|
|
||||||
: "${DEVICE3:=""}"
|
|
||||||
: "${DEVICE4:=""}"
|
|
||||||
|
|
||||||
[ -z "$DEVICE" ] && [ -b "/disk" ] && DEVICE="/disk"
|
|
||||||
[ -z "$DEVICE" ] && [ -b "/disk1" ] && DEVICE="/disk1"
|
|
||||||
[ -z "$DEVICE2" ] && [ -b "/disk2" ] && DEVICE2="/disk2"
|
|
||||||
[ -z "$DEVICE3" ] && [ -b "/disk3" ] && DEVICE3="/disk3"
|
|
||||||
[ -z "$DEVICE4" ] && [ -b "/disk4" ] && DEVICE4="/disk4"
|
|
||||||
|
|
||||||
[ -z "$DEVICE" ] && [ -b "/dev/disk1" ] && DEVICE="/dev/disk1"
|
|
||||||
[ -z "$DEVICE2" ] && [ -b "/dev/disk2" ] && DEVICE2="/dev/disk2"
|
|
||||||
[ -z "$DEVICE3" ] && [ -b "/dev/disk3" ] && DEVICE3="/dev/disk3"
|
|
||||||
[ -z "$DEVICE4" ] && [ -b "/dev/disk4" ] && DEVICE4="/dev/disk4"
|
|
||||||
|
|
||||||
if [ -n "$DEVICE" ]; then
|
|
||||||
addDevice "$DEVICE" "$DISK_TYPE" "3" "0xc" || exit $?
|
|
||||||
else
|
|
||||||
addDisk "$DISK1_FILE" "$DISK_TYPE" "disk" "$DISK_SIZE" "3" "0xc" "$DISK_FMT" "$DISK_IO" "$DISK_CACHE" || exit $?
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "$DEVICE2" ]; then
|
|
||||||
addDevice "$DEVICE2" "$DISK_TYPE" "4" "0xd" || exit $?
|
|
||||||
else
|
|
||||||
addDisk "$DISK2_FILE" "$DISK_TYPE" "disk2" "$DISK2_SIZE" "4" "0xd" "$DISK_FMT" "$DISK_IO" "$DISK_CACHE" || exit $?
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "$DEVICE3" ]; then
|
|
||||||
addDevice "$DEVICE3" "$DISK_TYPE" "5" "0xe" || exit $?
|
|
||||||
else
|
|
||||||
addDisk "$DISK3_FILE" "$DISK_TYPE" "disk3" "$DISK3_SIZE" "5" "0xe" "$DISK_FMT" "$DISK_IO" "$DISK_CACHE" || exit $?
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "$DEVICE4" ]; then
|
|
||||||
addDevice "$DEVICE4" "$DISK_TYPE" "6" "0xf" || exit $?
|
|
||||||
else
|
|
||||||
addDisk "$DISK4_FILE" "$DISK_TYPE" "disk4" "$DISK4_SIZE" "6" "0xf" "$DISK_FMT" "$DISK_IO" "$DISK_CACHE" || exit $?
|
|
||||||
fi
|
|
||||||
|
|
||||||
finishDisks
|
|
||||||
|
|
||||||
return 0
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -Eeuo pipefail
|
|
||||||
|
|
||||||
# Docker environment variables
|
|
||||||
|
|
||||||
: "${GPU:="N"}" # GPU passthrough
|
|
||||||
: "${VGA:="virtio"}" # VGA adaptor
|
|
||||||
: "${DISPLAY:="none"}" # Display type
|
|
||||||
: "${RENDERNODE:="/dev/dri/renderD128"}" # Render node
|
|
||||||
|
|
||||||
# Sanitize variables
|
|
||||||
VGA=$(strip "$VGA")
|
|
||||||
DISPLAY=$(strip "$DISPLAY")
|
|
||||||
RENDERNODE=$(strip "$RENDERNODE")
|
|
||||||
|
|
||||||
CPU_VENDOR=$(lscpu | awk '/Vendor ID/{print $3}')
|
|
||||||
|
|
||||||
if ! enabled "$GPU" || [[ "$CPU_VENDOR" != "GenuineIntel" || "$ARCH" != "amd64" ]]; then
|
|
||||||
|
|
||||||
[[ "${DISPLAY,,}" == "none" ]] && VGA="none"
|
|
||||||
DISPLAY_OPTS="-display $DISPLAY -vga $VGA"
|
|
||||||
return 0
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
msg="Configuring display drivers..."
|
|
||||||
html "$msg"
|
|
||||||
enabled "$DEBUG" && echo "$msg"
|
|
||||||
|
|
||||||
DISPLAY_OPTS="-display egl-headless,rendernode=$RENDERNODE"
|
|
||||||
DISPLAY_OPTS+=" -vga $VGA"
|
|
||||||
|
|
||||||
[ ! -d /dev/dri ] && mkdir -m 755 /dev/dri
|
|
||||||
|
|
||||||
# Extract the card number from the render node
|
|
||||||
CARD_NUMBER=$(echo "$RENDERNODE" | grep -oP '(?<=renderD)\d+')
|
|
||||||
CARD_DEVICE="/dev/dri/card$((CARD_NUMBER - 128))"
|
|
||||||
|
|
||||||
if [ ! -c "$CARD_DEVICE" ]; then
|
|
||||||
if mknod "$CARD_DEVICE" c 226 $((CARD_NUMBER - 128)); then
|
|
||||||
chmod 666 "$CARD_DEVICE"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ! -c "$RENDERNODE" ]; then
|
|
||||||
if mknod "$RENDERNODE" c 226 "$CARD_NUMBER"; then
|
|
||||||
chmod 666 "$RENDERNODE"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
addPackage "xserver-xorg-video-intel" "Intel GPU drivers"
|
|
||||||
addPackage "qemu-system-modules-opengl" "OpenGL module"
|
|
||||||
|
|
||||||
return 0
|
|
||||||
+24
-6
@@ -5,20 +5,35 @@ set -Eeuo pipefail
|
|||||||
: "${APP:="Virtual DSM"}"
|
: "${APP:="Virtual DSM"}"
|
||||||
: "${SUPPORT:="https://github.com/vdsm/virtual-dsm"}"
|
: "${SUPPORT:="https://github.com/vdsm/virtual-dsm"}"
|
||||||
|
|
||||||
|
: "${USB:="N"}"
|
||||||
|
: "${AUDIO:="N"}"
|
||||||
|
: "${VGA:="none"}"
|
||||||
|
: "${DISPLAY:="none"}"
|
||||||
|
: "${WEB_PORT:="5000"}"
|
||||||
|
: "${DISK_OFFSET:="2"}"
|
||||||
|
: "${DISK_SIZE:="256G"}"
|
||||||
|
: "${RAM_MINIMUM:="1G"}"
|
||||||
|
: "${DISK_MINIMUM:="6G"}"
|
||||||
|
: "${BOOT_MODE:="legacy"}"
|
||||||
|
|
||||||
cd /run
|
cd /run
|
||||||
|
|
||||||
. start.sh # Startup hook
|
. start.sh # Startup hook
|
||||||
. utils.sh # Load functions
|
. utils.sh # Load functions
|
||||||
. reset.sh # Initialize system
|
. init.sh # Initialize system
|
||||||
|
. memory.sh # Check memory
|
||||||
. server.sh # Start webserver
|
. server.sh # Start webserver
|
||||||
|
. download.sh # Load functions
|
||||||
. install.sh # Run installation
|
. install.sh # Run installation
|
||||||
. disk.sh # Initialize disks
|
. disk.sh # Initialize disks
|
||||||
. display.sh # Initialize graphics
|
. display.sh # Initialize graphics
|
||||||
|
. prepare.sh # Prepare for launch
|
||||||
. network.sh # Initialize network
|
. network.sh # Initialize network
|
||||||
|
. boot.sh # Configure boot
|
||||||
. proc.sh # Initialize processor
|
. proc.sh # Initialize processor
|
||||||
. serial.sh # Initialize serialport
|
. serial.sh # Initialize serialport
|
||||||
. power.sh # Configure shutdown
|
. power.sh # Configure shutdown
|
||||||
. memory.sh # Check available memory
|
. balloon.sh # Initialize ballooning
|
||||||
. config.sh # Configure arguments
|
. config.sh # Configure arguments
|
||||||
. finish.sh # Finish initialization
|
. finish.sh # Finish initialization
|
||||||
|
|
||||||
@@ -26,20 +41,23 @@ trap - ERR
|
|||||||
|
|
||||||
cmd=(qemu-system-x86_64)
|
cmd=(qemu-system-x86_64)
|
||||||
version=$("${cmd[@]}" --version | awk 'NR==1 { print $4 }')
|
version=$("${cmd[@]}" --version | awk 'NR==1 { print $4 }')
|
||||||
info "Booting $APP using QEMU v$version..."
|
info "Booting $APP using QEMU v$version..." && echo
|
||||||
|
|
||||||
if ! enabled "$SHUTDOWN"; then
|
if ! enabled "$SHUTDOWN"; then
|
||||||
exec "${cmd[@]}" ${ARGS:+ $ARGS}
|
exec "${cmd[@]}" ${ARGS:+ $ARGS}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ ! -t 1 ] || [ ! -c /dev/tty ]; then
|
if ! interactive; then
|
||||||
"${cmd[@]}" ${ARGS:+ $ARGS} &
|
"${cmd[@]}" ${ARGS:+ $ARGS} &
|
||||||
else
|
else
|
||||||
"${cmd[@]}" ${ARGS:+ $ARGS} </dev/tty >/dev/tty &
|
startConsole
|
||||||
|
startQemu "${cmd[@]}" ${ARGS:+ $ARGS}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
pid=$!
|
||||||
rc=0
|
rc=0
|
||||||
wait $! || rc=$?
|
|
||||||
|
wait "$pid" || rc=$?
|
||||||
[ -f "$QEMU_END" ] && exit "$rc"
|
[ -f "$QEMU_END" ] && exit "$rc"
|
||||||
|
|
||||||
sleep 1 & wait $!
|
sleep 1 & wait $!
|
||||||
|
|||||||
+12
-2
@@ -1,8 +1,18 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -Eeuo pipefail
|
set -Eeuo pipefail
|
||||||
|
|
||||||
if enabled "$DEBUG"; then
|
fKill "progress.sh"
|
||||||
printf "QEMU arguments:\n\n%s\n\n" "${ARGS// -/$'\n-'}"
|
|
||||||
|
if [ -s "$QEMU_DIR/qemu.host" ] && [[ "$(<"$QEMU_DIR/qemu.host")" == "172.17."* ]]; then
|
||||||
|
warn "your container IP starts with 172.17.* which will cause conflicts when you install the Container Manager package inside DSM!"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if enabled "$DEBUG"; then
|
||||||
|
echo
|
||||||
|
printf "QEMU arguments:\n\n %s\n\n" "${ARGS// -/$'\n -'}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Must always remain the very last command
|
||||||
|
enableTrap
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
+617
-275
@@ -1,320 +1,662 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -Eeuo pipefail
|
set -Eeuo pipefail
|
||||||
|
|
||||||
: "${URL:=""}" # URL of the PAT file to be downloaded.
|
: "${TZ:=""}" # System timezone
|
||||||
|
: "${COUNTRY:=""}" # Country code for mirror
|
||||||
|
: "${URL:=""}" # URL of the PAT file to be downloaded.
|
||||||
|
|
||||||
if [ -f "$STORAGE/dsm.ver" ]; then
|
TZ=$(strip "$TZ")
|
||||||
BASE=$(<"$STORAGE/dsm.ver")
|
COUNTRY=$(strip "$COUNTRY")
|
||||||
BASE="${BASE//[![:print:]]/}"
|
|
||||||
[ -z "$BASE" ] && BASE="DSM_VirtualDSM_69057"
|
|
||||||
else
|
|
||||||
# Fallback for old installs
|
|
||||||
BASE="DSM_VirtualDSM_42962"
|
|
||||||
fi
|
|
||||||
|
|
||||||
FN="boot.pat"
|
checkDsmFilesystem() {
|
||||||
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
|
local fs="$1"
|
||||||
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
|
|
||||||
fi
|
|
||||||
|
|
||||||
FILE=$(find / -maxdepth 1 -type f -iname "$FN" -print -quit)
|
if [[ "${fs,,}" == "overlay"* && "${ENGINE,,}" == "docker" ]]; then
|
||||||
[ ! -s "$FILE" ] && FILE=$(find "$STORAGE" -maxdepth 1 -type f -iname "$FN" -print -quit)
|
warn "the filesystem of $STORAGE is OverlayFS, this usually means it was binded to an invalid path!"
|
||||||
[ -s "$FILE" ] && BASE="DSM_VirtualDSM" && URL="file://$FILE"
|
|
||||||
|
|
||||||
URL=$(strip "$URL")
|
|
||||||
|
|
||||||
if [ -n "$URL" ] && [ ! -s "$FILE" ] && [ ! -d "$DIR" ]; then
|
|
||||||
BASE=$(basename "$URL" .pat)
|
|
||||||
if [ ! -s "$STORAGE/$BASE.system.img" ]; then
|
|
||||||
BASE=$(basename "${URL%%\?*}" .pat)
|
|
||||||
printf -v BASE '%b' "${BASE//%/\\x}"
|
|
||||||
BASE="${BASE//[!A-Za-z0-9._-]/_}"
|
|
||||||
fi
|
|
||||||
if [[ "${URL,,}" != "http"* && "${URL,,}" != "file:"* ]] ; then
|
|
||||||
[ ! -s "$STORAGE/$BASE.pat" ] && error "Invalid URL: $URL" && exit 65
|
|
||||||
URL="file://$STORAGE/$BASE.pat"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -s "$STORAGE/$BASE.boot.img" && -s "$STORAGE/$BASE.system.img" ]]; then
|
|
||||||
return 0 # Previous installation found
|
|
||||||
fi
|
|
||||||
|
|
||||||
html "Please wait while Virtual DSM is being installed..."
|
|
||||||
|
|
||||||
DL=""
|
|
||||||
DL_CHINA="https://cndl.synology.cn/download/DSM"
|
|
||||||
DL_GLOBAL="https://global.synologydownload.com/download/DSM"
|
|
||||||
|
|
||||||
[[ "${URL,,}" == *"cndl.synology"* ]] && DL="$DL_CHINA"
|
|
||||||
[[ "${URL,,}" == *"global.synology"* ]] && DL="$DL_GLOBAL"
|
|
||||||
|
|
||||||
if [ -z "$DL" ]; then
|
|
||||||
[ -z "$COUNTRY" ] && setCountry
|
|
||||||
[ -z "$COUNTRY" ] && info "Warning: could not detect country to select mirror!"
|
|
||||||
[[ "${COUNTRY^^}" == "CN" ]] && DL="$DL_CHINA" || DL="$DL_GLOBAL"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$URL" ]; then
|
|
||||||
URL="$DL/release/7.2.2/72806/DSM_VirtualDSM_72806.pat"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ! -s "$FILE" ]; then
|
|
||||||
BASE=$(basename "${URL%%\?*}" .pat)
|
|
||||||
printf -v BASE '%b' "${BASE//%/\\x}"
|
|
||||||
BASE="${BASE//[!A-Za-z0-9._-]/_}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "$URL" != "file://$STORAGE/$BASE.pat" ]]; then
|
|
||||||
rm -f "$STORAGE/$BASE.pat"
|
|
||||||
fi
|
|
||||||
|
|
||||||
rm -f "$STORAGE/$BASE.agent"
|
|
||||||
rm -f "$STORAGE/$BASE.boot.img"
|
|
||||||
rm -f "$STORAGE/$BASE.system.img"
|
|
||||||
|
|
||||||
# Check filesystem
|
|
||||||
FS=$(stat -f -c %T "$STORAGE")
|
|
||||||
|
|
||||||
if [[ "${FS,,}" == "overlay"* && "${ENGINE,,}" == "docker" ]]; then
|
|
||||||
warn "the filesystem of $STORAGE is OverlayFS, this usually means it was binded to an invalid path!"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "${FS,,}" == "fuse"* ]]; then
|
|
||||||
warn "the filesystem of $STORAGE is FUSE, this extra layer will negatively affect performance!"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "${FS,,}" == "ecryptfs" || "${FS,,}" == "tmpfs" ]]; then
|
|
||||||
warn "the filesystem of $STORAGE is $FS, which does not support O_DIRECT mode, adjusting settings..."
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "${FS,,}" == "fat"* || "${FS,,}" == "vfat"* || "${FS,,}" == "msdos"* ]]; then
|
|
||||||
error "Unable to install on $FS filesystems, please use a different filesystem for /storage." && exit 61
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "${FS,,}" != "exfat"* && "${FS,,}" != "ntfs"* && "${FS,,}" != "unknown"* ]]; then
|
|
||||||
TMP="$STORAGE/tmp"
|
|
||||||
rm -rf "$TMP"
|
|
||||||
if ! makeDir "$TMP"; then
|
|
||||||
error "Failed to create directory \"$TMP\" !" && exit 93
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
TMP="/tmp/dsm"
|
|
||||||
TMP_SPACE=2147483648
|
|
||||||
SPACE=$(df --output=avail -B 1 /tmp | tail -n 1)
|
|
||||||
SPACE_MB=$(formatBytes "$SPACE")
|
|
||||||
if (( TMP_SPACE > SPACE )); then
|
|
||||||
error "Not enough free space inside the container, have $SPACE_MB available but need at least 2 GB." && exit 93
|
|
||||||
fi
|
|
||||||
rm -rf "$TMP" && mkdir -p "$TMP"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check free diskspace
|
|
||||||
ROOT_SPACE=536870912
|
|
||||||
SPACE=$(df --output=avail -B 1 / | tail -n 1)
|
|
||||||
SPACE_MB=$(formatBytes "$SPACE" "down")
|
|
||||||
(( ROOT_SPACE > SPACE )) && error "Not enough free space inside the container, have $SPACE_MB available but need at least 500 MB." && exit 96
|
|
||||||
|
|
||||||
MIN_SPACE=15032385536
|
|
||||||
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}"
|
|
||||||
info "Install: Copying installation image..."
|
|
||||||
else
|
|
||||||
MSG="Downloading DSM"
|
|
||||||
ERR="Failed to download $URL"
|
|
||||||
info "Install: Downloading $BASE.pat..."
|
|
||||||
fi
|
|
||||||
|
|
||||||
html "$MSG..."
|
|
||||||
|
|
||||||
PAT="/$BASE.pat"
|
|
||||||
rm -f "$PAT"
|
|
||||||
|
|
||||||
if [[ "$URL" == "file://"* ]]; then
|
|
||||||
|
|
||||||
if [ ! -f "${URL:7}" ]; then
|
|
||||||
error "File '${URL:7}' does not exist!" && exit 65
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cp "${URL:7}" "$PAT"
|
if [[ "${fs,,}" == "fuse"* ]]; then
|
||||||
|
warn "the filesystem of $STORAGE is FUSE, this extra layer will negatively affect performance!"
|
||||||
|
fi
|
||||||
|
|
||||||
else
|
if [[ "${fs,,}" == "ecryptfs" || "${fs,,}" == "tmpfs" ]]; then
|
||||||
|
warn "the filesystem of $STORAGE is $fs, which does not support O_DIRECT mode, adjusting settings..."
|
||||||
|
fi
|
||||||
|
|
||||||
SIZE=0
|
if [[ "${fs,,}" == "fat"* || "${fs,,}" == "vfat"* || "${fs,,}" == "msdos"* ]]; then
|
||||||
[[ "${URL,,}" == *"_72806.pat" ]] && SIZE=361010261
|
error "Unable to install on $fs filesystems, please use a different filesystem for /storage."
|
||||||
[[ "${URL,,}" == *"_69057.pat" ]] && SIZE=363837333
|
return 61
|
||||||
[[ "${URL,,}" == *"_42218.pat" ]] && SIZE=379637760
|
fi
|
||||||
|
|
||||||
/run/progress.sh "$PAT" "$SIZE" "$MSG ([P])..." &
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
{ wget "$URL" -O "$PAT" -q --no-check-certificate --timeout=10 --no-http-keep-alive --show-progress "$PROGRESS"; rc=$?; } || :
|
checkDsmSpace() {
|
||||||
|
|
||||||
|
local rootSpace=536870912
|
||||||
|
local minSpace=15032385536
|
||||||
|
local space available
|
||||||
|
|
||||||
|
space=$(df --output=avail -B 1 / | tail -n 1) || return $?
|
||||||
|
available=$(formatBytes "$space" "down") || return $?
|
||||||
|
|
||||||
|
if (( rootSpace > space )); then
|
||||||
|
error "Not enough free space inside the container, have $available available but need at least 500 MB."
|
||||||
|
return 96
|
||||||
|
fi
|
||||||
|
|
||||||
|
space=$(df --output=avail -B 1 "$STORAGE" | tail -n 1) || return $?
|
||||||
|
available=$(formatBytes "$space") || return $?
|
||||||
|
|
||||||
|
if (( minSpace > space )); then
|
||||||
|
error "Not enough free space for installation in $STORAGE, have $available available but need at least 14 GB."
|
||||||
|
return 94
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadFile() {
|
||||||
|
|
||||||
|
local url="$1"
|
||||||
|
local pat="$2"
|
||||||
|
local msg="$3"
|
||||||
|
local connections="${4:-1}"
|
||||||
|
|
||||||
|
downloadToFile \
|
||||||
|
"$url" \
|
||||||
|
"$pat" \
|
||||||
|
"$msg" \
|
||||||
|
"0" \
|
||||||
|
"$connections" \
|
||||||
|
"Y"
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadPat() {
|
||||||
|
|
||||||
|
local pat="$1"
|
||||||
|
local msg err
|
||||||
|
|
||||||
|
if [[ "$URL" == "file://"* ]]; then
|
||||||
|
msg="Copying DSM"
|
||||||
|
err="Failed to copy ${URL:7}"
|
||||||
|
info "Install: Copying installation image..."
|
||||||
|
html "$msg..." || return $?
|
||||||
|
|
||||||
|
rm -f "$pat" || return $?
|
||||||
|
|
||||||
|
if [ ! -f "${URL:7}" ]; then
|
||||||
|
error "File '${URL:7}' does not exist!"
|
||||||
|
return 65
|
||||||
|
fi
|
||||||
|
|
||||||
|
cp "${URL:7}" "$pat" || return $?
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
|
msg="Downloading DSM"
|
||||||
|
err="Failed to download $URL"
|
||||||
|
info "Install: Downloading $BASE.pat..."
|
||||||
|
|
||||||
|
downloadRetry \
|
||||||
|
"$pat" \
|
||||||
|
"$CONNECTIONS" \
|
||||||
|
"5" \
|
||||||
|
"$BASE.pat" \
|
||||||
|
"0" \
|
||||||
|
"$URL" \
|
||||||
|
"$pat" \
|
||||||
|
"$msg" || return 69
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -s "$pat" ]; then
|
||||||
|
error "$err"
|
||||||
|
return 69
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
extractPat() {
|
||||||
|
|
||||||
|
local pat="$1"
|
||||||
|
local tmp="$2"
|
||||||
|
local size="$3"
|
||||||
|
local msg="Extracting installation image..."
|
||||||
|
local rc
|
||||||
|
|
||||||
|
info "Install: $msg" && html "$msg" || return $?
|
||||||
|
/run/progress.sh "$tmp" "$size" "$msg ([P])..." &
|
||||||
|
|
||||||
|
# Newer PAT files are normal tar archives; older encrypted/proprietary forms
|
||||||
|
# require the bundled extractor as a compatibility fallback.
|
||||||
|
if { tar tf "$pat"; } >/dev/null 2>&1; then
|
||||||
|
|
||||||
|
tar xpf "$pat" -C "$tmp/." || {
|
||||||
|
rc=$?
|
||||||
|
fKill "progress.sh"
|
||||||
|
return "$rc"
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
|
{ (cd "$tmp" && python3 /run/extract.py -i "$pat" -d 2>/run/extract.log); rc=$?; } || :
|
||||||
|
|
||||||
|
if (( rc != 0 )); then
|
||||||
|
fKill "progress.sh"
|
||||||
|
cat /run/extract.log
|
||||||
|
error "Failed to extract PAT file, reason $rc"
|
||||||
|
return 63
|
||||||
|
fi
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
fKill "progress.sh"
|
fKill "progress.sh"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
(( rc == 3 )) && error "$ERR , cannot write file (disk full?)" && exit 69
|
createSystemImage() {
|
||||||
(( 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
|
|
||||||
|
|
||||||
fi
|
local tmp="$1"
|
||||||
|
local fs="$2"
|
||||||
|
local msg="Preparing system partition..."
|
||||||
|
|
||||||
[ ! -s "$PAT" ] && error "$ERR" && exit 69
|
info "Install: $msg" && html "$msg" || return $?
|
||||||
|
|
||||||
SIZE=$(stat -c%s "$PAT")
|
# The PAT boot archive becomes the persistent QEMU boot disk after its
|
||||||
|
# companion system partition has been assembled.
|
||||||
|
DSM_BOOT=$(find "$tmp" -name "*.bin.zip" -print -quit) || return $?
|
||||||
|
|
||||||
if ((SIZE<250000000)); then
|
if [ -z "$DSM_BOOT" ]; then
|
||||||
error "The specified PAT file is probably an update pack as it's too small." && exit 62
|
error "The PAT file contains no boot image."
|
||||||
fi
|
return 67
|
||||||
|
|
||||||
MSG="Extracting installation image..."
|
|
||||||
info "Install: $MSG" && html "$MSG"
|
|
||||||
|
|
||||||
if { tar tf "$PAT"; } >/dev/null 2>&1; then
|
|
||||||
|
|
||||||
tar xpf "$PAT" -C "$TMP/."
|
|
||||||
|
|
||||||
else
|
|
||||||
|
|
||||||
{ (cd "$TMP" && python3 /run/extract.py -i "$PAT" -d 2>/run/extract.log); rc=$?; } || :
|
|
||||||
|
|
||||||
if (( rc != 0 )); then
|
|
||||||
cat /run/extract.log
|
|
||||||
error "Failed to extract PAT file, reason $rc" && exit 63
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
fi
|
if [ ! -s "$DSM_BOOT" ]; then
|
||||||
|
error "The PAT boot image archive is empty."
|
||||||
MSG="Preparing system partition..."
|
return 67
|
||||||
info "Install: $MSG" && html "$MSG"
|
|
||||||
|
|
||||||
BOOT=$(find "$TMP" -name "*.bin.zip")
|
|
||||||
[ ! -s "$BOOT" ] && error "The PAT file contains no boot image." && exit 67
|
|
||||||
|
|
||||||
BOOT=$(echo "$BOOT" | head -c -5)
|
|
||||||
unzip -q -o "$BOOT".zip -d "$TMP"
|
|
||||||
|
|
||||||
SYSTEM="$STORAGE/$BASE.system.img"
|
|
||||||
rm -f "$SYSTEM"
|
|
||||||
|
|
||||||
# Check free diskspace
|
|
||||||
SYSTEM_SIZE=10738466816
|
|
||||||
SPACE=$(df --output=avail -B 1 "$STORAGE" | tail -n 1)
|
|
||||||
SPACE_MB=$(formatBytes "$SPACE")
|
|
||||||
|
|
||||||
if (( SYSTEM_SIZE > SPACE )); then
|
|
||||||
error "Not enough free space in $STORAGE to create a 10 GB system disk, have only $SPACE_MB available." && exit 97
|
|
||||||
fi
|
|
||||||
|
|
||||||
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\" !"
|
|
||||||
|
|
||||||
if [[ "${FS,,}" == "btrfs" ]]; then
|
|
||||||
{ chattr +C "$SYSTEM"; } || :
|
|
||||||
FA=$(lsattr "$SYSTEM")
|
|
||||||
if [[ "$FA" != *"C"* ]]; then
|
|
||||||
error "Failed to disable COW for system image $SYSTEM on ${FS^^} filesystem."
|
|
||||||
fi
|
fi
|
||||||
fi
|
|
||||||
|
|
||||||
if ! fallocate -l "$SYSTEM_SIZE" "$SYSTEM" &>/dev/null; then
|
local bootTmp="$tmp/boot" bootBase
|
||||||
if ! fallocate -l -x "$SYSTEM_SIZE" "$SYSTEM"; then
|
bootBase="$(basename "${DSM_BOOT%.zip}")"
|
||||||
if ! truncate -s "$SYSTEM_SIZE" "$SYSTEM"; then
|
|
||||||
rm -f "$SYSTEM"
|
rm -rf "$bootTmp" || return $?
|
||||||
error "Could not allocate file $SYSTEM for the system disk." && exit 98
|
|
||||||
|
if ! makeDir "$bootTmp"; then
|
||||||
|
error "Failed to create directory \"$bootTmp\" !"
|
||||||
|
return 93
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${fs,,}" == "btrfs" ]]; then
|
||||||
|
{ chattr +C "$bootTmp"; } || :
|
||||||
|
fi
|
||||||
|
|
||||||
|
7z x -y -o"$bootTmp" "$DSM_BOOT" >/dev/null || return $?
|
||||||
|
DSM_BOOT="$bootTmp/$bootBase"
|
||||||
|
|
||||||
|
if [ ! -s "$DSM_BOOT" ]; then
|
||||||
|
error "The PAT boot image could not be extracted."
|
||||||
|
return 67
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${fs,,}" == "btrfs" ]]; then
|
||||||
|
local attrs
|
||||||
|
attrs=$(lsattr "$DSM_BOOT") || return $?
|
||||||
|
if [[ "$attrs" != *"C"* ]]; then
|
||||||
|
error "Failed to disable COW for boot image $DSM_BOOT on ${fs^^} filesystem."
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
|
||||||
|
|
||||||
PART="$TMP/partition.fdisk"
|
SYSTEM="$STORAGE/$BASE.system.img"
|
||||||
|
rm -f "$SYSTEM" || return $?
|
||||||
|
|
||||||
{ echo "label: dos"
|
# Check free diskspace
|
||||||
echo "label-id: 0x6f9ee2e9"
|
local systemSize=10738466816
|
||||||
echo "device: $SYSTEM"
|
local space available
|
||||||
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"
|
space=$(df --output=avail -B 1 "$STORAGE" | tail -n 1) || return $?
|
||||||
|
available=$(formatBytes "$space") || return $?
|
||||||
|
|
||||||
MOUNT="$TMP/system"
|
if (( systemSize > space )); then
|
||||||
rm -rf "$MOUNT"
|
error "Not enough free space in $STORAGE to create a 10 GB system disk, have only $available available."
|
||||||
|
return 97
|
||||||
|
fi
|
||||||
|
|
||||||
if ! makeDir "$MOUNT"; then
|
if ! touch "$SYSTEM"; then
|
||||||
error "Failed to create directory \"$MOUNT\" !" && exit 93
|
error "Could not create file $SYSTEM for the system disk."
|
||||||
fi
|
return 98
|
||||||
|
fi
|
||||||
|
|
||||||
MSG="Extracting system partition..."
|
setOwner "$SYSTEM" || warn "failed to set the owner for \"$SYSTEM\" !"
|
||||||
info "Install: $MSG" && html "$MSG"
|
|
||||||
|
|
||||||
HDA="$TMP/hda1"
|
if [[ "${fs,,}" == "btrfs" ]]; then
|
||||||
IDB="$TMP/indexdb"
|
{ chattr +C "$SYSTEM"; } || :
|
||||||
PKG="$TMP/packages"
|
local attrs
|
||||||
HDP="$TMP/synohdpack_img"
|
attrs=$(lsattr "$SYSTEM") || return $?
|
||||||
|
if [[ "$attrs" != *"C"* ]]; then
|
||||||
|
error "Failed to disable COW for system image $SYSTEM on ${fs^^} filesystem."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
[ ! -s "$HDA.tgz" ] && error "The PAT file contains no OS image." && exit 64
|
if ! fallocate -l "$systemSize" "$SYSTEM" &>/dev/null; then
|
||||||
mv "$HDA.tgz" "$HDA.txz"
|
if ! fallocate -l -x "$systemSize" "$SYSTEM"; then
|
||||||
|
if ! truncate -s "$systemSize" "$SYSTEM"; then
|
||||||
|
rm -f "$SYSTEM"
|
||||||
|
error "Could not allocate file $SYSTEM for the system disk."
|
||||||
|
return 98
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
[ -d "$PKG" ] && mv "$PKG/" "$MOUNT/.SynoUpgradePackages/"
|
# Recreate Synology's expected DOS partition layout inside the fixed 10 GiB
|
||||||
rm -f "$MOUNT/.SynoUpgradePackages/ActiveInsight-"*
|
# system image before populating the ext4 root partition.
|
||||||
|
local part="$tmp/partition.fdisk"
|
||||||
|
|
||||||
if [ -s "$IDB.txz" ]; then
|
if ! {
|
||||||
INDEX_DB="$MOUNT/usr/syno/synoman/indexdb"
|
echo "label: dos"
|
||||||
mkdir -p "$INDEX_DB"
|
echo "label-id: 0x6f9ee2e9"
|
||||||
fi
|
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"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
LABEL="1.44.1-42218"
|
sfdisk -q "$SYSTEM" < "$part" || return $?
|
||||||
OFFSET="1048576" # 2048 * 512
|
|
||||||
NUMBLOCKS="2097152" # (16777216 * 512) / 4096
|
|
||||||
MSG="Installing system partition..."
|
|
||||||
|
|
||||||
fakeroot -- bash -c "set -Eeu;\
|
local mount="$tmp/system"
|
||||||
[ -s $HDP.txz ] && tar xpfJ $HDP.txz --absolute-names -C $MOUNT/;\
|
rm -rf "$mount" || return $?
|
||||||
[ -s $IDB.txz ] && tar xpfJ $IDB.txz --absolute-names -C $INDEX_DB/;\
|
|
||||||
tar xpfJ $HDA.txz --absolute-names --skip-old-files -C $MOUNT/;\
|
|
||||||
printf '%b%s%b' '\E[1;34m❯ \E[1;36m' 'Install: $MSG' '\E[0m\n';\
|
|
||||||
mke2fs -q -t ext4 -b 4096 -d $MOUNT/ -L $LABEL -F -E offset=$OFFSET $SYSTEM $NUMBLOCKS"
|
|
||||||
|
|
||||||
rm -rf "$MOUNT"
|
if ! makeDir "$mount"; then
|
||||||
echo "$BASE" > "$STORAGE/dsm.ver"
|
error "Failed to create directory \"$mount\" !"
|
||||||
! setOwner "$STORAGE/dsm.ver" && error "Failed to set the owner for \"$STORAGE/dsm.ver\" !"
|
return 93
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ "$URL" == "file://$STORAGE/$BASE.pat" ]]; then
|
return 0
|
||||||
rm -f "$PAT"
|
}
|
||||||
else
|
|
||||||
mv -f "$PAT" "$STORAGE/$BASE.pat"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -f "$STORAGE/$BASE.pat" ]; then
|
installSystemPartition() {
|
||||||
! setOwner "$STORAGE/$BASE.pat" && error "Failed to set the owner for \"$STORAGE/$BASE.pat\" !"
|
|
||||||
fi
|
|
||||||
|
|
||||||
mv -f "$BOOT" "$STORAGE/$BASE.boot.img"
|
local tmp="$1"
|
||||||
! setOwner "$STORAGE/$BASE.boot.img" && error "Failed to set the owner for \"$STORAGE/$BASE.boot.img\" !"
|
local mount="$tmp/system"
|
||||||
|
local msg="Extracting system partition..."
|
||||||
|
|
||||||
rm -rf "$TMP"
|
info "Install: $msg" && html "$msg" || return $?
|
||||||
|
|
||||||
|
local hda="$tmp/hda1"
|
||||||
|
local idb="$tmp/indexdb"
|
||||||
|
local pkg="$tmp/packages"
|
||||||
|
local hdp="$tmp/synohdpack_img"
|
||||||
|
|
||||||
|
if [ ! -s "$hda.tgz" ]; then
|
||||||
|
error "The PAT file contains no OS image."
|
||||||
|
return 64
|
||||||
|
fi
|
||||||
|
|
||||||
|
mv "$hda.tgz" "$hda.txz" || return $?
|
||||||
|
|
||||||
|
if [ -d "$pkg" ]; then
|
||||||
|
mv "$pkg/" "$mount/.SynoUpgradePackages/" || return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -f "$mount/.SynoUpgradePackages/ActiveInsight-"* || return $?
|
||||||
|
|
||||||
|
local indexDb="$mount/usr/syno/synoman/indexdb"
|
||||||
|
|
||||||
|
if [ -s "$idb.txz" ]; then
|
||||||
|
mkdir -p "$indexDb" || return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
local label="1.44.1-42218"
|
||||||
|
local offset="1048576" # 2048 * 512
|
||||||
|
local numBlocks="2097152" # (16777216 * 512) / 4096
|
||||||
|
local rc
|
||||||
|
msg="Installing system partition..."
|
||||||
|
|
||||||
|
# Build the ext4 filesystem directly from the extracted tree under fakeroot,
|
||||||
|
# preserving archive ownership without mounting a loop device.
|
||||||
|
if fakeroot -- bash -c "set -Eeu;\
|
||||||
|
[ -s $hdp.txz ] && tar xpfJ $hdp.txz --absolute-names -C $mount/;\
|
||||||
|
[ -s $idb.txz ] && tar xpfJ $idb.txz --absolute-names -C $indexDb/;\
|
||||||
|
tar xpfJ $hda.txz --absolute-names --skip-old-files -C $mount/;\
|
||||||
|
printf '%b%s%b' '\E[1;34m❯ \E[1;36m' 'Install: $msg' '\E[0m\n';\
|
||||||
|
mke2fs -q -t ext4 -b 4096 -d $mount/ -L $label -F -E offset=$offset $SYSTEM $numBlocks"; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
rc=$?
|
||||||
|
return "$rc"
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -rf "$mount" || return $?
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
checkSse42() {
|
||||||
|
|
||||||
|
if disabled "$KVM" || [[ "${PLATFORM,,}" != "x64" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! hasFlag "sse4_2"; then
|
||||||
|
error "Your CPU does not have the SSE4.2 instruction set that Virtual DSM requires!"
|
||||||
|
enabled "$DEBUG" || return 88
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
sanitizePatBase() {
|
||||||
|
|
||||||
|
local source="$1"
|
||||||
|
local base
|
||||||
|
|
||||||
|
base=$(basename "${source%%\?*}" .pat) || return 1
|
||||||
|
printf -v base '%b' "${base//%/\\x}" || return 1
|
||||||
|
base="${base//[!A-Za-z0-9._-]/_}"
|
||||||
|
|
||||||
|
printf '%s' "$base"
|
||||||
|
}
|
||||||
|
|
||||||
|
reservePorts() {
|
||||||
|
|
||||||
|
local port ports=""
|
||||||
|
local hostPorts="${HOST_PORTS:-}"
|
||||||
|
|
||||||
|
# Older configurations may reserve DSM's web ports for the container.
|
||||||
|
# They now need to be forwarded to the guest instead.
|
||||||
|
for port in ${hostPorts//,/ }; do
|
||||||
|
|
||||||
|
case "${port,,}" in
|
||||||
|
5000|5000/tcp|5001|5001/tcp)
|
||||||
|
continue ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
ports+="${ports:+,}$port"
|
||||||
|
|
||||||
|
done
|
||||||
|
|
||||||
|
HOST_PORTS="$ports"
|
||||||
|
|
||||||
|
# NAT can fall back to user-mode networking after this point,
|
||||||
|
# so always add the DSM web interface ports for non-DHCP networking.
|
||||||
|
USER_PORTS="${USER_PORTS:+$USER_PORTS,}5000/tcp,5001/tcp,80/tcp,443/tcp,445/tcp"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
prepareMac() {
|
||||||
|
|
||||||
|
local file="$STORAGE/$PROCESS.mac"
|
||||||
|
local legacy="$STORAGE/dsm.mac"
|
||||||
|
local container mac=""
|
||||||
|
|
||||||
|
# An explicitly configured MAC remains network.sh's responsibility.
|
||||||
|
[ -n "${MAC:-}" ] && return 0
|
||||||
|
[ -s "$file" ] && return 0
|
||||||
|
|
||||||
|
# Preserve the address generated by older Virtual DSM releases.
|
||||||
|
if [ -s "$legacy" ]; then
|
||||||
|
mac=$(readFile "$legacy") || return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$mac" ]; then
|
||||||
|
container=$(containerID) || return $?
|
||||||
|
mac=$(echo "$container" | md5sum |
|
||||||
|
sed 's/^\(..\)\(..\)\(..\)\(..\)\(..\).*$/02:11:32:\3:\4:\5/') || return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! writeState "mac" "${mac^^}"; then
|
||||||
|
error "Failed to write MAC address to \"$file\" !"
|
||||||
|
return 28
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
getCountry() {
|
||||||
|
|
||||||
|
local url=$1
|
||||||
|
local query=$2
|
||||||
|
local json result
|
||||||
|
|
||||||
|
{ 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=$?; } || :
|
||||||
|
(( rc != 0 )) && return 0
|
||||||
|
|
||||||
|
[[ ${#result} -ne 2 ]] && return 0
|
||||||
|
[[ "${result^^}" == "XX" ]] && return 0
|
||||||
|
|
||||||
|
COUNTRY="${result^^}"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
setCountry() {
|
||||||
|
|
||||||
|
[[ "${TZ,,}" == "asia/harbin" ]] && COUNTRY="CN"
|
||||||
|
[[ "${TZ,,}" == "asia/beijing" ]] && COUNTRY="CN"
|
||||||
|
[[ "${TZ,,}" == "asia/urumqi" ]] && COUNTRY="CN"
|
||||||
|
[[ "${TZ,,}" == "asia/kashgar" ]] && COUNTRY="CN"
|
||||||
|
[[ "${TZ,,}" == "asia/shanghai" ]] && COUNTRY="CN"
|
||||||
|
[[ "${TZ,,}" == "asia/chongqing" ]] && COUNTRY="CN"
|
||||||
|
|
||||||
|
# Country detection is best-effort and tries independent services in order;
|
||||||
|
# failure leaves mirror selection at its global default.
|
||||||
|
[ -z "$COUNTRY" ] && getCountry "https://api.ipapi.is" ".location.country_code"
|
||||||
|
[ -z "$COUNTRY" ] && getCountry "https://ifconfig.co/json" ".country_iso"
|
||||||
|
[ -z "$COUNTRY" ] && getCountry "https://api.ip2location.io" ".country_code"
|
||||||
|
[ -z "$COUNTRY" ] && getCountry "https://ipinfo.io/json" ".country"
|
||||||
|
[ -z "$COUNTRY" ] && getCountry "https://api.ipquery.io/?format=json" ".location.country_code"
|
||||||
|
[ -z "$COUNTRY" ] && getCountry "https://api.myip.com" ".cc"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
finishDsmInstall() {
|
||||||
|
|
||||||
|
local pat="$1"
|
||||||
|
local tmp="$2"
|
||||||
|
|
||||||
|
echo "$BASE" > "$STORAGE/dsm.ver" || return 1
|
||||||
|
setOwner "$STORAGE/dsm.ver" || warn "failed to set the owner for \"$STORAGE/dsm.ver\" !"
|
||||||
|
|
||||||
|
# Do not keep a second copy when the source PAT already lives in storage;
|
||||||
|
# downloaded or externally mounted sources are cached for later reuse.
|
||||||
|
if [[ "$URL" == "file://$STORAGE/$BASE.pat" ]]; then
|
||||||
|
rm -f "$pat" || return $?
|
||||||
|
else
|
||||||
|
mv -f "$pat" "$STORAGE/$BASE.pat" || return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f "$STORAGE/$BASE.pat" ]; then
|
||||||
|
setOwner "$STORAGE/$BASE.pat" || warn "failed to set the owner for \"$STORAGE/$BASE.pat\" !"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mv -f "$DSM_BOOT" "$STORAGE/$BASE.boot.img" || return $?
|
||||||
|
setOwner "$STORAGE/$BASE.boot.img" || warn "failed to set the owner for \"$STORAGE/$BASE.boot.img\" !"
|
||||||
|
|
||||||
|
rm -rf "$tmp" || return $?
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
installDSM() {
|
||||||
|
|
||||||
|
rm -rf /tmp/dsm || return $?
|
||||||
|
rm -f "$QEMU_DIR/dsm.url" || return $?
|
||||||
|
|
||||||
|
checkSse42 || return $?
|
||||||
|
reservePorts || return $?
|
||||||
|
prepareMac || return $?
|
||||||
|
|
||||||
|
local patName="boot.pat"
|
||||||
|
local patDir patFile
|
||||||
|
|
||||||
|
# Persist the exact PAT base name so future starts reopen the matching boot,
|
||||||
|
# system, and cached installation files.
|
||||||
|
if [ -f "$STORAGE/dsm.ver" ]; then
|
||||||
|
BASE=$(<"$STORAGE/dsm.ver") || return $?
|
||||||
|
BASE="${BASE//[![:print:]]/}"
|
||||||
|
[ -z "$BASE" ] && BASE="DSM_VirtualDSM_69057"
|
||||||
|
else
|
||||||
|
# Fallback for old installs
|
||||||
|
BASE="DSM_VirtualDSM_42962"
|
||||||
|
fi
|
||||||
|
|
||||||
|
patDir=$(find / -maxdepth 1 -type d -iname "$patName" -print -quit) || return $?
|
||||||
|
|
||||||
|
if [ ! -d "$patDir" ]; then
|
||||||
|
patDir=$(find "$STORAGE" -maxdepth 1 -type d -iname "$patName" -print -quit) || return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# A boot.pat directory bind represents already extracted boot and system
|
||||||
|
# images and therefore takes precedence over PAT file or URL discovery.
|
||||||
|
if [ -d "$patDir" ]; then
|
||||||
|
BASE="DSM_VirtualDSM"
|
||||||
|
URL="file://$patDir"
|
||||||
|
|
||||||
|
if [[ ! -s "$STORAGE/$BASE.boot.img" || ! -s "$STORAGE/$BASE.system.img" ]]; then
|
||||||
|
error "The bind $patDir maps to a file that does not exist!"
|
||||||
|
return 65
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
patFile=$(find / -maxdepth 1 -type f -iname "$patName" -print -quit) || return $?
|
||||||
|
|
||||||
|
if [ ! -s "$patFile" ]; then
|
||||||
|
patFile=$(find "$STORAGE" -maxdepth 1 -type f -iname "$patName" -print -quit) || return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -s "$patFile" ]; then
|
||||||
|
BASE="DSM_VirtualDSM"
|
||||||
|
URL="file://$patFile"
|
||||||
|
fi
|
||||||
|
|
||||||
|
URL=$(strip "$URL") || return $?
|
||||||
|
|
||||||
|
# Derive a filesystem-safe identity from the URL only when no local boot.pat
|
||||||
|
# source was supplied; preserve an existing system image identity if present.
|
||||||
|
if [ -n "$URL" ] && [ ! -s "$patFile" ] && [ ! -d "$patDir" ]; then
|
||||||
|
BASE=$(basename "$URL" .pat) || return $?
|
||||||
|
|
||||||
|
if [ ! -s "$STORAGE/$BASE.system.img" ]; then
|
||||||
|
BASE=$(sanitizePatBase "$URL") || return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${URL,,}" != "http"* && "${URL,,}" != "file:"* ]]; then
|
||||||
|
if [ ! -s "$STORAGE/$BASE.pat" ]; then
|
||||||
|
error "Invalid URL: $URL"
|
||||||
|
return 65
|
||||||
|
fi
|
||||||
|
|
||||||
|
URL="file://$STORAGE/$BASE.pat"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# A complete matching image pair is the installation marker; the cached PAT
|
||||||
|
# itself is optional after installation.
|
||||||
|
if [[ -s "$STORAGE/$BASE.boot.img" && -s "$STORAGE/$BASE.system.img" ]]; then
|
||||||
|
return 0 # Previous installation found
|
||||||
|
fi
|
||||||
|
|
||||||
|
html "Please wait while Virtual DSM is being installed..." || return $?
|
||||||
|
|
||||||
|
local mirror=""
|
||||||
|
local chinaMirror="https://cndl.synology.cn/download/DSM"
|
||||||
|
local globalMirror="https://global.synologydownload.com/download/DSM"
|
||||||
|
|
||||||
|
[[ "${URL,,}" == *"cndl.synology"* ]] && mirror="$chinaMirror"
|
||||||
|
[[ "${URL,,}" == *"global.synology"* ]] && mirror="$globalMirror"
|
||||||
|
|
||||||
|
# Honor an explicitly selected Synology mirror first, otherwise choose the
|
||||||
|
# China or global endpoint from the detected country.
|
||||||
|
if [ -z "$mirror" ]; then
|
||||||
|
if [ -z "$COUNTRY" ]; then
|
||||||
|
setCountry || return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
[ -z "$COUNTRY" ] && info "Warning: could not detect country to select mirror!"
|
||||||
|
[[ "${COUNTRY^^}" == "CN" ]] && mirror="$chinaMirror" || mirror="$globalMirror"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$URL" ]; then
|
||||||
|
URL="$mirror/release/7.2.2/72806/DSM_VirtualDSM_72806.pat"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -s "$patFile" ]; then
|
||||||
|
BASE=$(sanitizePatBase "$URL") || return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$URL" != "file://$STORAGE/$BASE.pat" ]]; then
|
||||||
|
rm -f "$STORAGE/$BASE.pat" || return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -f "$STORAGE/$BASE.agent" || return $?
|
||||||
|
rm -f "$STORAGE/$BASE.boot.img" || return $?
|
||||||
|
rm -f "$STORAGE/$BASE.system.img" || return $?
|
||||||
|
|
||||||
|
# Check filesystem
|
||||||
|
local fs
|
||||||
|
fs=$(stat -f -c %T "$STORAGE") || return $?
|
||||||
|
checkDsmFilesystem "$fs" || return $?
|
||||||
|
|
||||||
|
local tmp
|
||||||
|
|
||||||
|
# Extract beside storage on Unix filesystems to avoid container-space limits;
|
||||||
|
# use /tmp for filesystems that cannot safely host the installer workspace.
|
||||||
|
if [[ "${fs,,}" != "exfat"* && "${fs,,}" != "ntfs"* && "${fs,,}" != "unknown"* ]]; then
|
||||||
|
tmp="$STORAGE/tmp"
|
||||||
|
rm -rf "$tmp" || return $?
|
||||||
|
|
||||||
|
if ! makeDir "$tmp"; then
|
||||||
|
error "Failed to create directory \"$tmp\" !"
|
||||||
|
return 93
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
tmp="/tmp/dsm"
|
||||||
|
local tmpSpace=2147483648
|
||||||
|
local space available
|
||||||
|
|
||||||
|
space=$(df --output=avail -B 1 /tmp | tail -n 1) || return $?
|
||||||
|
available=$(formatBytes "$space") || return $?
|
||||||
|
|
||||||
|
if (( tmpSpace > space )); then
|
||||||
|
error "Not enough free space inside the container, have $available available but need at least 2 GB."
|
||||||
|
return 93
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -rf "$tmp" || return $?
|
||||||
|
mkdir -p "$tmp" || return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check free diskspace
|
||||||
|
checkDsmSpace || return $?
|
||||||
|
|
||||||
|
local pat="/$BASE.pat"
|
||||||
|
downloadPat "$pat" || return $?
|
||||||
|
|
||||||
|
local size
|
||||||
|
size=$(stat -c%s "$pat") || return $?
|
||||||
|
|
||||||
|
# Full Virtual DSM PAT files are substantially larger than update packs;
|
||||||
|
# reject undersized inputs before attempting destructive image preparation.
|
||||||
|
if (( size < 250000000 )); then
|
||||||
|
error "The specified PAT file is probably an update pack as it's too small."
|
||||||
|
return 62
|
||||||
|
fi
|
||||||
|
|
||||||
|
extractPat "$pat" "$tmp" "$size" || return $?
|
||||||
|
createSystemImage "$tmp" "$fs" || return $?
|
||||||
|
installSystemPartition "$tmp" || return $?
|
||||||
|
finishDsmInstall "$pat" "$tmp" || return $?
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
installDSM || exit $?
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
@@ -1,78 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
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")
|
|
||||||
|
|
||||||
if ! disabled "$RAM_CHECK" && [[ "${RAM_SIZE,,}" != "max" && "${RAM_SIZE,,}" != "half" ]]; then
|
|
||||||
|
|
||||||
wanted=$(numfmt --from=iec "$RAM_SIZE")
|
|
||||||
|
|
||||||
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,"
|
|
||||||
if [[ "${FS,,}" == "zfs" ]]; then
|
|
||||||
info "$msg but since ZFS is active this will be ignored."
|
|
||||||
else
|
|
||||||
RAM_SIZE="max"
|
|
||||||
warn "$msg it will automatically be adjusted to a lower amount."
|
|
||||||
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,"
|
|
||||||
if [[ "${FS,,}" == "zfs" ]]; then
|
|
||||||
info "$msg but since ZFS is active this will be ignored."
|
|
||||||
else
|
|
||||||
warn "$msg please consider a lower amount."
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "${RAM_SIZE,,}" == "half" ]]; then
|
|
||||||
|
|
||||||
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."
|
|
||||||
else
|
|
||||||
RAM_SIZE="max"
|
|
||||||
fi
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "${RAM_SIZE,,}" == "max" ]]; then
|
|
||||||
|
|
||||||
if (( RAM_AVAIL < (RAM_SPARE * 2) )); then
|
|
||||||
|
|
||||||
wanted=$(( RAM_AVAIL / 2 ))
|
|
||||||
|
|
||||||
else
|
|
||||||
|
|
||||||
wanted=$(( RAM_AVAIL - (RAM_SPARE * 3) ))
|
|
||||||
|
|
||||||
if (( wanted < (RAM_SPARE * 6) )); then
|
|
||||||
wanted=$(( RAM_AVAIL - RAM_SPARE ))
|
|
||||||
fi
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
wanted=$(( wanted / 1048577 ))
|
|
||||||
RAM_SIZE="${wanted}M"
|
|
||||||
|
|
||||||
info "Allocated $wanted MB of RAM for the virtual machine."
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
wanted=$(numfmt --from=iec "$RAM_SIZE")
|
|
||||||
|
|
||||||
if [ "$wanted" -lt "$RAM_MINIMUM" ]; then
|
|
||||||
wanted=$(( wanted / 1048577 ))
|
|
||||||
error "Not enough memory available, there is only $wanted MB left!"
|
|
||||||
exit 16
|
|
||||||
fi
|
|
||||||
|
|
||||||
return 0
|
|
||||||
-906
@@ -1,906 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -Eeuo pipefail
|
|
||||||
|
|
||||||
# Docker environment variables
|
|
||||||
|
|
||||||
: "${MAC:=""}"
|
|
||||||
: "${MTU:=""}"
|
|
||||||
: "${DHCP:="N"}"
|
|
||||||
: "${NETWORK:="Y"}"
|
|
||||||
: "${HOST_PORTS:=""}"
|
|
||||||
: "${USER_PORTS:=""}"
|
|
||||||
: "${ADAPTER:="virtio-net-pci"}"
|
|
||||||
|
|
||||||
: "${VM_NET_IP:=""}"
|
|
||||||
: "${VM_NET_DEV:=""}"
|
|
||||||
: "${VM_NET_TAP:="dsm"}"
|
|
||||||
: "${VM_NET_MAC:="$MAC"}"
|
|
||||||
: "${VM_NET_BRIDGE:="docker"}"
|
|
||||||
: "${VM_NET_HOST:="VirtualDSM"}"
|
|
||||||
: "${VM_NET_MASK:="255.255.255.0"}"
|
|
||||||
|
|
||||||
: "${PASST:="/run/passt"}"
|
|
||||||
: "${PASST_MTU:=""}"
|
|
||||||
: "${PASST_OPTS:=""}"
|
|
||||||
: "${PASST_DEBUG:=""}"
|
|
||||||
: "${PASST_PID:="/var/run/passt.pid"}"
|
|
||||||
: "${PASST_SOCKET:="/tmp/passt.socket"}"
|
|
||||||
|
|
||||||
: "${DNSMASQ_OPTS:=""}"
|
|
||||||
: "${DNSMASQ_DEBUG:=""}"
|
|
||||||
: "${DNSMASQ:="/usr/sbin/dnsmasq"}"
|
|
||||||
: "${DNSMASQ_PID:="/var/run/dnsmasq.pid"}"
|
|
||||||
: "${DNSMASQ_CONF_DIR:="/etc/dnsmasq.d"}"
|
|
||||||
|
|
||||||
# Sanitize variables
|
|
||||||
MAC=$(strip "$MAC")
|
|
||||||
MTU=$(strip "$MTU")
|
|
||||||
ADAPTER=$(strip "$ADAPTER")
|
|
||||||
NETWORK=$(strip "$NETWORK")
|
|
||||||
PASST_MTU=$(strip "$PASST_MTU")
|
|
||||||
HOST_PORTS=$(strip "$HOST_PORTS")
|
|
||||||
USER_PORTS=$(strip "$USER_PORTS")
|
|
||||||
|
|
||||||
ADD_ERR="Please add the following setting to your container:"
|
|
||||||
|
|
||||||
# ######################################
|
|
||||||
# Functions
|
|
||||||
# ######################################
|
|
||||||
|
|
||||||
configureDHCP() {
|
|
||||||
|
|
||||||
enabled "$DEBUG" && echo "Configuring MACVTAP networking..."
|
|
||||||
|
|
||||||
# Create the necessary file structure for /dev/vhost-net
|
|
||||||
if [ ! -c /dev/vhost-net ]; then
|
|
||||||
if mknod /dev/vhost-net c 10 238; then
|
|
||||||
chmod 660 /dev/vhost-net
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Create a macvtap network for the VM guest
|
|
||||||
{ msg=$(ip link add link "$VM_NET_DEV" name "$VM_NET_TAP" address "$VM_NET_MAC" type macvtap mode bridge 2>&1); rc=$?; } || :
|
|
||||||
|
|
||||||
case "$msg" in
|
|
||||||
"RTNETLINK answers: File exists"* )
|
|
||||||
while ! ip link add link "$VM_NET_DEV" name "$VM_NET_TAP" address "$VM_NET_MAC" type macvtap mode bridge; do
|
|
||||||
info "Waiting for macvtap interface to become available.."
|
|
||||||
sleep 5
|
|
||||||
done ;;
|
|
||||||
"RTNETLINK answers: Invalid argument"* )
|
|
||||||
error "Cannot create macvtap interface. Please make sure that the network type of the container is 'macvlan' and not 'ipvlan'."
|
|
||||||
return 1 ;;
|
|
||||||
"RTNETLINK answers: Operation not permitted"* )
|
|
||||||
error "No permission to create macvtap interface. Please make sure that your host kernel supports it and that the NET_ADMIN capability is set."
|
|
||||||
return 1 ;;
|
|
||||||
*)
|
|
||||||
[ -n "$msg" ] && echo "$msg" >&2
|
|
||||||
if (( rc != 0 )); then
|
|
||||||
error "Cannot create macvtap interface."
|
|
||||||
return 1
|
|
||||||
fi ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
if [[ "$MTU" != "0" && "$MTU" != "1500" ]]; then
|
|
||||||
if ! ip link set dev "$VM_NET_TAP" mtu "$MTU"; then
|
|
||||||
warn "Failed to set MTU size to $MTU."
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
while ! ip link set "$VM_NET_TAP" up; do
|
|
||||||
info "Waiting for MAC address $VM_NET_MAC to become available..."
|
|
||||||
info "If you cloned this machine, please delete the 'dsm.mac' file to generate a different MAC address."
|
|
||||||
sleep 2
|
|
||||||
done
|
|
||||||
|
|
||||||
local TAP_NR TAP_PATH MAJOR MINOR
|
|
||||||
TAP_NR=$(</sys/class/net/"$VM_NET_TAP"/ifindex)
|
|
||||||
TAP_PATH="/dev/tap${TAP_NR}"
|
|
||||||
|
|
||||||
# Create dev file (there is no udev in container: need to be done manually)
|
|
||||||
IFS=: read -r MAJOR MINOR < <(cat /sys/devices/virtual/net/"$VM_NET_TAP"/tap*/dev)
|
|
||||||
(( MAJOR < 1)) && error "Cannot find: sys/devices/virtual/net/$VM_NET_TAP" && return 1
|
|
||||||
|
|
||||||
[[ ! -e "$TAP_PATH" && -e "/dev0/${TAP_PATH##*/}" ]] && ln -s "/dev0/${TAP_PATH##*/}" "$TAP_PATH"
|
|
||||||
|
|
||||||
if [[ ! -e "$TAP_PATH" ]]; then
|
|
||||||
{ mknod "$TAP_PATH" c "$MAJOR" "$MINOR" ; rc=$?; } || :
|
|
||||||
(( rc != 0 )) && error "Cannot mknod: $TAP_PATH ($rc)" && return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
{ exec 30>>"$TAP_PATH"; rc=$?; } 2>/dev/null || :
|
|
||||||
|
|
||||||
if (( rc != 0 )); then
|
|
||||||
error "Cannot create TAP interface ($rc). $ADD_ERR --device-cgroup-rule='c *:* rwm'" && return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
{ exec 40>>/dev/vhost-net; rc=$?; } 2>/dev/null || :
|
|
||||||
|
|
||||||
if (( rc != 0 )); then
|
|
||||||
error "VHOST can not be found ($rc). $ADD_ERR --device=/dev/vhost-net" && return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
NET_OPTS="-netdev tap,id=hostnet0,vhost=on,vhostfd=40,fd=30"
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
configureDNS() {
|
|
||||||
|
|
||||||
local fa="$1"
|
|
||||||
local ip="$2"
|
|
||||||
local mac="$3"
|
|
||||||
local host="$4"
|
|
||||||
local mask="$5"
|
|
||||||
local gateway="$6"
|
|
||||||
local arguments="$DNSMASQ_OPTS"
|
|
||||||
|
|
||||||
echo "$gateway" > /run/shm/qemu.gw
|
|
||||||
|
|
||||||
enabled "${DNSMASQ_DISABLE:-}" && return 0
|
|
||||||
enabled "$DEBUG" && echo "Starting dnsmasq daemon..."
|
|
||||||
|
|
||||||
[ -s "$DNSMASQ_PID" ] && pKill "$(<"$DNSMASQ_PID")"
|
|
||||||
rm -f "$DNSMASQ_PID"
|
|
||||||
|
|
||||||
case "${NETWORK,,}" in
|
|
||||||
"tap" | "tun" | "tuntap" | "y" )
|
|
||||||
|
|
||||||
# Create lease file for faster resolve
|
|
||||||
echo "0 $mac $ip $host 01:$mac" > /var/lib/misc/dnsmasq.leases
|
|
||||||
chmod 644 /var/lib/misc/dnsmasq.leases
|
|
||||||
|
|
||||||
# dnsmasq configuration:
|
|
||||||
arguments+=" --dhcp-authoritative"
|
|
||||||
|
|
||||||
# Set DHCP range and host
|
|
||||||
arguments+=" --dhcp-range=$ip,$ip"
|
|
||||||
arguments+=" --dhcp-host=$mac,,$ip,$host,infinite"
|
|
||||||
|
|
||||||
# Set DNS server and gateway
|
|
||||||
arguments+=" --dhcp-option=option:netmask,$mask"
|
|
||||||
arguments+=" --dhcp-option=option:router,$gateway"
|
|
||||||
arguments+=" --dhcp-option=option:dns-server,$gateway"
|
|
||||||
|
|
||||||
esac
|
|
||||||
|
|
||||||
# Set interfaces
|
|
||||||
arguments+=" --interface=$fa"
|
|
||||||
arguments+=" --bind-interfaces"
|
|
||||||
|
|
||||||
# Workaround NET_RAW capability
|
|
||||||
arguments+=" --no-ping"
|
|
||||||
|
|
||||||
# Add DNS entry for container
|
|
||||||
arguments+=" --address=/host.lan/$gateway"
|
|
||||||
|
|
||||||
# Set local dns resolver to dnsmasq when needed
|
|
||||||
[ -f /etc/resolv.dnsmasq ] && arguments+=" --resolv-file=/etc/resolv.dnsmasq"
|
|
||||||
|
|
||||||
# Enable logging to file
|
|
||||||
local log="/var/log/dnsmasq.log"
|
|
||||||
rm -f "$log"
|
|
||||||
arguments+=" --log-facility=$log"
|
|
||||||
|
|
||||||
arguments=$(echo "$arguments" | sed 's/\t/ /g' | tr -s ' ' | sed 's/^ *//')
|
|
||||||
enabled "$DEBUG" && printf "Dnsmasq arguments:\n\n%s\n\n" "${arguments// -/$'\n-'}"
|
|
||||||
|
|
||||||
{ $DNSMASQ ${arguments:+ $arguments}; rc=$?; } || :
|
|
||||||
|
|
||||||
if (( rc != 0 )); then
|
|
||||||
|
|
||||||
local msg="Failed to start Dnsmasq, reason: $rc"
|
|
||||||
|
|
||||||
if [[ "${NETWORK,,}" == "slirp" || "${NETWORK,,}" == "passt" ]] || ! enabled "$ROOTLESS" || enabled "$DEBUG"; then
|
|
||||||
[ -f "$log" ] && [ -s "$log" ] && cat "$log"
|
|
||||||
error "$msg"
|
|
||||||
fi
|
|
||||||
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if enabled "$DNSMASQ_DEBUG"; then
|
|
||||||
tail -fn +0 "$log" --pid=$$ &
|
|
||||||
fi
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
getHostPorts() {
|
|
||||||
|
|
||||||
local list="${HOST_PORTS// /},"
|
|
||||||
|
|
||||||
# Remove duplicates
|
|
||||||
list=$(echo "${list//,,/,}," | awk 'BEGIN{RS=ORS=","} !seen[$0]++' | sed 's/,*$//g')
|
|
||||||
|
|
||||||
echo "$list"
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
getUserPorts() {
|
|
||||||
|
|
||||||
local ssh="22"
|
|
||||||
local dsm="5000,5001"
|
|
||||||
|
|
||||||
local list="$ssh,$dsm,"
|
|
||||||
list+="${USER_PORTS// /},"
|
|
||||||
|
|
||||||
local exclude
|
|
||||||
exclude=$(getHostPorts)
|
|
||||||
|
|
||||||
local ports=""
|
|
||||||
local userport=""
|
|
||||||
local hostport=""
|
|
||||||
|
|
||||||
for userport in ${list//,/ }; do
|
|
||||||
|
|
||||||
local num="${userport///tcp}"
|
|
||||||
num="${num///udp}"
|
|
||||||
|
|
||||||
for hostport in ${exclude//,/ }; do
|
|
||||||
|
|
||||||
local port="${hostport///tcp}"
|
|
||||||
port="${port///udp}"
|
|
||||||
|
|
||||||
if [[ "$num" == "$port" ]]; then
|
|
||||||
num=""
|
|
||||||
if [[ "$port" != "$WEB_PORT" ]]; then
|
|
||||||
warn "Could not assign port $port to \"USER_PORTS\" because it is already in \"HOST_PORTS\"!"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
done
|
|
||||||
|
|
||||||
[ -n "$num" ] && ports+="$userport,"
|
|
||||||
|
|
||||||
done
|
|
||||||
|
|
||||||
# Remove duplicates
|
|
||||||
echo "${ports//,,/,}," | awk 'BEGIN{RS=ORS=","} !seen[$0]++' | sed 's/,*$//g'
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
getSlirp() {
|
|
||||||
|
|
||||||
local ip="$1"
|
|
||||||
local args=""
|
|
||||||
local list=""
|
|
||||||
|
|
||||||
list=$(getUserPorts)
|
|
||||||
|
|
||||||
for port in ${list//,/ }; do
|
|
||||||
|
|
||||||
local proto="tcp"
|
|
||||||
local num="${port%/tcp}"
|
|
||||||
[ -z "$num" ] && continue
|
|
||||||
|
|
||||||
if [[ "$port" == *"/udp" ]]; then
|
|
||||||
proto="udp"
|
|
||||||
num="${port%/udp}"
|
|
||||||
elif [[ "$port" != *"/tcp" ]]; then
|
|
||||||
args+="hostfwd=$proto::$num-$ip:$num,"
|
|
||||||
proto="udp"
|
|
||||||
num="${port%/udp}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
args+="hostfwd=$proto::$num-$ip:$num,"
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "$args" | sed 's/,*$//g'
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
configureSlirp() {
|
|
||||||
|
|
||||||
NETWORK="slirp"
|
|
||||||
enabled "$DEBUG" && echo "Configuring slirp networking..."
|
|
||||||
|
|
||||||
local ip="$IP"
|
|
||||||
[ -n "$VM_NET_IP" ] && ip="$VM_NET_IP"
|
|
||||||
local base="${ip%.*}."
|
|
||||||
[ "${ip/$base/}" -lt "4" ] && ip="${ip%.*}.4"
|
|
||||||
local gateway="${ip%.*}.1"
|
|
||||||
|
|
||||||
local ipv6=""
|
|
||||||
[ -n "$IP6" ] && ipv6="ipv6=on,"
|
|
||||||
|
|
||||||
NET_OPTS="-netdev user,id=hostnet0,ipv4=on,host=$gateway,net=${gateway%.*}.0/24,dhcpstart=$ip,${ipv6}hostname=$VM_NET_HOST"
|
|
||||||
|
|
||||||
local forward=""
|
|
||||||
forward=$(getSlirp "$ip")
|
|
||||||
[ -n "$forward" ] && NET_OPTS+=",$forward"
|
|
||||||
|
|
||||||
if enabled "${DNSMASQ_DISABLE:-}"; then
|
|
||||||
echo "$gateway" > /run/shm/qemu.gw
|
|
||||||
else
|
|
||||||
[ ! -f /etc/resolv.dnsmasq ] && cp /etc/resolv.conf /etc/resolv.dnsmasq
|
|
||||||
configureDNS "lo" "$ip" "$VM_NET_MAC" "$VM_NET_HOST" "$VM_NET_MASK" "$gateway" || return 1
|
|
||||||
printf '%s\n' \
|
|
||||||
'nameserver 127.0.0.1' \
|
|
||||||
'search .' \
|
|
||||||
'options ndots:0' > /etc/resolv.conf
|
|
||||||
fi
|
|
||||||
|
|
||||||
VM_NET_IP="$ip"
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
configurePasst() {
|
|
||||||
|
|
||||||
NETWORK="passt"
|
|
||||||
enabled "$DEBUG" && echo "Configuring user-mode networking..."
|
|
||||||
|
|
||||||
local log="/tmp/passt.log"
|
|
||||||
rm -f "$log"
|
|
||||||
|
|
||||||
local ip="$IP"
|
|
||||||
[ -n "$VM_NET_IP" ] && ip="$VM_NET_IP"
|
|
||||||
|
|
||||||
local gateway=""
|
|
||||||
if [[ "$ip" != *".1" ]]; then
|
|
||||||
gateway="${ip%.*}.1"
|
|
||||||
else
|
|
||||||
gateway="${ip%.*}.2"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# passt configuration:
|
|
||||||
[ -z "$IP6" ] && PASST_OPTS+=" -4"
|
|
||||||
|
|
||||||
PASST_OPTS+=" -a $ip"
|
|
||||||
PASST_OPTS+=" -g $gateway"
|
|
||||||
PASST_OPTS+=" -n $VM_NET_MASK"
|
|
||||||
[ -n "$PASST_MTU" ] && PASST_OPTS+=" -m $PASST_MTU"
|
|
||||||
|
|
||||||
local forward=""
|
|
||||||
forward=$(getUserPorts)
|
|
||||||
forward="${forward///tcp}"
|
|
||||||
forward="${forward///udp}"
|
|
||||||
|
|
||||||
if [ -n "$forward" ]; then
|
|
||||||
forward="%${VM_NET_DEV}/$forward"
|
|
||||||
PASST_OPTS+=" -t $forward"
|
|
||||||
PASST_OPTS+=" -u $forward"
|
|
||||||
fi
|
|
||||||
|
|
||||||
PASST_OPTS+=" -H $VM_NET_HOST"
|
|
||||||
PASST_OPTS+=" -M $GATEWAY_MAC"
|
|
||||||
PASST_OPTS+=" -P $PASST_PID"
|
|
||||||
PASST_OPTS+=" -s $PASST_SOCKET"
|
|
||||||
PASST_OPTS+=" -l $log"
|
|
||||||
PASST_OPTS+=" -q"
|
|
||||||
|
|
||||||
if ! enabled "${DNSMASQ_DISABLE:-}"; then
|
|
||||||
[ ! -f /etc/resolv.dnsmasq ] && cp /etc/resolv.conf /etc/resolv.dnsmasq
|
|
||||||
printf '%s\n' \
|
|
||||||
'nameserver 127.0.0.1' \
|
|
||||||
'search .' \
|
|
||||||
'options ndots:0' > /etc/resolv.conf
|
|
||||||
fi
|
|
||||||
|
|
||||||
PASST_OPTS=$(echo "$PASST_OPTS" | sed 's/\t/ /g' | tr -s ' ' | sed 's/^ *//')
|
|
||||||
|
|
||||||
if enabled "$DEBUG" || enabled "$PASST_DEBUG"; then
|
|
||||||
printf "Passt arguments:\n\n%s\n\n" "${PASST_OPTS// -/$'\n-'}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
[ ! -f "$PASST" ] && cp /usr/bin/passt* /run
|
|
||||||
|
|
||||||
if ! "$PASST" ${PASST_OPTS:+$PASST_OPTS} >/dev/null 2>&1; then
|
|
||||||
|
|
||||||
rm -f "$log"
|
|
||||||
PASST_OPTS="${PASST_OPTS/ -q/}"
|
|
||||||
{ "$PASST" ${PASST_OPTS:+$PASST_OPTS}; rc=$?; } || :
|
|
||||||
|
|
||||||
if (( rc != 0 )); then
|
|
||||||
[ -f "$log" ] && [ -s "$log" ] && cat "$log"
|
|
||||||
warn "failed to start passt ($rc), falling back to slirp networking!"
|
|
||||||
configureSlirp && return 0 || return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
if enabled "$PASST_DEBUG"; then
|
|
||||||
tail -fn +0 "$log" --pid=$$ &
|
|
||||||
else
|
|
||||||
if enabled "$DEBUG"; then
|
|
||||||
[ -f "$log" ] && [ -s "$log" ] && cat "$log" && echo ""
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
NET_OPTS="-netdev stream,id=hostnet0,server=off,addr.type=unix,addr.path=$PASST_SOCKET"
|
|
||||||
|
|
||||||
configureDNS "lo" "$ip" "$VM_NET_MAC" "$VM_NET_HOST" "$VM_NET_MASK" "$gateway" || return 1
|
|
||||||
|
|
||||||
VM_NET_IP="$ip"
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
clearTables() {
|
|
||||||
local table="" line rules
|
|
||||||
|
|
||||||
# Choose between iptables or nftables
|
|
||||||
if command -v iptables-nft >/dev/null 2>&1 && iptables-nft -V >/dev/null 2>&1; then
|
|
||||||
update-alternatives --set iptables /usr/sbin/iptables-nft > /dev/null
|
|
||||||
update-alternatives --set ip6tables /usr/sbin/ip6tables-nft > /dev/null
|
|
||||||
else
|
|
||||||
update-alternatives --set iptables /usr/sbin/iptables-legacy > /dev/null
|
|
||||||
update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy > /dev/null
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Store the current iptables ruleset
|
|
||||||
! rules=$(iptables-save 2> /dev/null) && return 0
|
|
||||||
[ -z "$rules" ] && return 0
|
|
||||||
|
|
||||||
# Delete every rule tagged with our unique identifier, leaving all other rules intact.
|
|
||||||
while IFS= read -r line; do
|
|
||||||
case "$line" in
|
|
||||||
\*nat) table="nat" ;;
|
|
||||||
\*filter) table="filter" ;;
|
|
||||||
\*mangle) table="mangle" ;;
|
|
||||||
\*raw) table="raw" ;;
|
|
||||||
esac
|
|
||||||
if [[ "$line" == -A* ]]; then
|
|
||||||
local re="--comment[[:space:]]+\"?remove\"?([[:space:]]|\$)"
|
|
||||||
if [[ "$line" =~ $re ]]; then
|
|
||||||
read -ra args <<< "${line/-A /-D }"
|
|
||||||
iptables -t "$table" "${args[@]}" &> /dev/null || :
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done <<< "$rules"
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
configureNAT() {
|
|
||||||
|
|
||||||
local tuntap="TUN device is missing. $ADD_ERR --device /dev/net/tun"
|
|
||||||
local tables="the 'ip_tables' kernel module is not loaded. Try this command: sudo modprobe ip_tables iptable_nat"
|
|
||||||
|
|
||||||
enabled "$DEBUG" && echo "Configuring NAT networking..."
|
|
||||||
|
|
||||||
# Create the necessary file structure for /dev/net/tun
|
|
||||||
if [ ! -c /dev/net/tun ]; then
|
|
||||||
[ ! -d /dev/net ] && mkdir -m 755 /dev/net
|
|
||||||
if mknod /dev/net/tun c 10 200; then
|
|
||||||
chmod 666 /dev/net/tun
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ! -c /dev/net/tun ]; then
|
|
||||||
enabled "$ROOTLESS" && ! enabled "$DEBUG" && return 1
|
|
||||||
warn "$tuntap" && return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check port forwarding flag
|
|
||||||
if [[ $(< /proc/sys/net/ipv4/ip_forward) -eq 0 ]]; then
|
|
||||||
{ sysctl -w net.ipv4.ip_forward=1 > /dev/null 2>&1; rc=$?; } || :
|
|
||||||
if (( rc != 0 )) || [[ $(< /proc/sys/net/ipv4/ip_forward) -eq 0 ]]; then
|
|
||||||
enabled "$ROOTLESS" && ! enabled "$DEBUG" && return 1
|
|
||||||
warn "IP forwarding is disabled. $ADD_ERR --sysctl net.ipv4.ip_forward=1"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
local ip base gateway exclude
|
|
||||||
base=$(cut -d. -f3,4 <<< "$IP")
|
|
||||||
|
|
||||||
if [[ "$IP" != "172.30."* ]]; then
|
|
||||||
ip="172.30.$base"
|
|
||||||
else
|
|
||||||
ip="172.31.$base"
|
|
||||||
fi
|
|
||||||
|
|
||||||
[ -n "$VM_NET_IP" ] && ip="$VM_NET_IP"
|
|
||||||
|
|
||||||
if [[ "$ip" != *".1" ]]; then
|
|
||||||
gateway="${ip%.*}.1"
|
|
||||||
else
|
|
||||||
gateway="${ip%.*}.2"
|
|
||||||
fi
|
|
||||||
|
|
||||||
local subnet="${ip%.*}.0/24"
|
|
||||||
local broadcast="${ip%.*}.255"
|
|
||||||
|
|
||||||
# Create a bridge with a static IP for the VM guest
|
|
||||||
{ ip link add dev "$VM_NET_BRIDGE" type bridge ; rc=$?; } || :
|
|
||||||
|
|
||||||
if (( rc != 0 )); then
|
|
||||||
enabled "$ROOTLESS" && ! enabled "$DEBUG" && return 1
|
|
||||||
warn "failed to create bridge. $ADD_ERR --cap-add NET_ADMIN" && return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! ip address add "$gateway/24" broadcast "$broadcast" dev "$VM_NET_BRIDGE"; then
|
|
||||||
warn "failed to add IP address pool!" && return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
while ! ip link set "$VM_NET_BRIDGE" up; do
|
|
||||||
info "Waiting for IP address to become available..."
|
|
||||||
sleep 2
|
|
||||||
done
|
|
||||||
|
|
||||||
# Set tap to the bridge created
|
|
||||||
if ! ip tuntap add dev "$VM_NET_TAP" mode tap; then
|
|
||||||
enabled "$ROOTLESS" && ! enabled "$DEBUG" && return 1
|
|
||||||
warn "$tuntap" && return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "$MTU" != "0" && "$MTU" != "1500" ]]; then
|
|
||||||
if ! ip link set dev "$VM_NET_TAP" mtu "$MTU"; then
|
|
||||||
warn "failed to set MTU size to $MTU."
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! ip link set dev "$VM_NET_TAP" address "$GATEWAY_MAC"; then
|
|
||||||
warn "failed to set gateway MAC address."
|
|
||||||
fi
|
|
||||||
|
|
||||||
while ! ip link set "$VM_NET_TAP" up promisc on; do
|
|
||||||
info "Waiting for TAP to become available..."
|
|
||||||
sleep 2
|
|
||||||
done
|
|
||||||
|
|
||||||
if ! ip link set dev "$VM_NET_TAP" master "$VM_NET_BRIDGE"; then
|
|
||||||
warn "failed to set master bridge!" && return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Flush existing tables
|
|
||||||
clearTables
|
|
||||||
|
|
||||||
exclude=$(getHostPorts)
|
|
||||||
|
|
||||||
if [ -n "$exclude" ]; then
|
|
||||||
if [[ "$exclude" != *","* ]]; then
|
|
||||||
exclude=" ! --dport $exclude"
|
|
||||||
else
|
|
||||||
exclude=" -m multiport ! --dports $exclude"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# NAT traffic from bridge subnet to Docker uplink
|
|
||||||
if ! iptables -t nat -A POSTROUTING -o "$VM_NET_DEV" -s "$subnet" ! -d "$subnet" -m comment --comment "remove" -j MASQUERADE > /dev/null 2>&1; then
|
|
||||||
enabled "$ROOTLESS" && ! enabled "$DEBUG" && return 1
|
|
||||||
if ! iptables -t nat -A POSTROUTING -o "$VM_NET_DEV" -s "$subnet" ! -d "$subnet" -m comment --comment "remove" -j MASQUERADE; then
|
|
||||||
warn "$tables" && return 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# shellcheck disable=SC2086
|
|
||||||
if ! iptables -t nat -A PREROUTING -i "$VM_NET_DEV" -d "$IP" -p tcp${exclude} -m comment --comment "remove" -j DNAT --to "$ip"; then
|
|
||||||
warn "failed to configure IP tables!" && return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! iptables -t nat -A PREROUTING -i "$VM_NET_DEV" -d "$IP" -p udp -m comment --comment "remove" -j DNAT --to "$ip"; then
|
|
||||||
warn "failed to configure IP tables!" && return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if (( KERNEL > 4 )); then
|
|
||||||
# Hack for guest VMs complaining about "bad udp checksums in 5 packets"
|
|
||||||
iptables -A POSTROUTING -t mangle -p udp --dport bootpc -m comment --comment "remove" -j CHECKSUM --checksum-fill > /dev/null 2>&1 || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Allow forwarding from bridge -> dev
|
|
||||||
if ! iptables -A FORWARD -i "$VM_NET_BRIDGE" -o "$VM_NET_DEV" -m comment --comment "remove" -j ACCEPT; then
|
|
||||||
warn "failed to configure IP tables!" && return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Allow return traffic
|
|
||||||
if ! iptables -A FORWARD -i "$VM_NET_DEV" -o "$VM_NET_BRIDGE" -m conntrack --ctstate RELATED,ESTABLISHED -m comment --comment "remove" -j ACCEPT; then
|
|
||||||
warn "failed to configure IP tables!" && return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
NET_OPTS="-netdev tap,id=hostnet0,ifname=$VM_NET_TAP"
|
|
||||||
|
|
||||||
if [ -c /dev/vhost-net ]; then
|
|
||||||
{ exec 40>>/dev/vhost-net; rc=$?; } 2>/dev/null || :
|
|
||||||
(( rc == 0 )) && NET_OPTS+=",vhost=on,vhostfd=40"
|
|
||||||
fi
|
|
||||||
|
|
||||||
NET_OPTS+=",script=no,downscript=no"
|
|
||||||
|
|
||||||
configureDNS "$VM_NET_BRIDGE" "$ip" "$VM_NET_MAC" "$VM_NET_HOST" "$VM_NET_MASK" "$gateway" || return 1
|
|
||||||
|
|
||||||
VM_NET_IP="$ip"
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
closeBridge() {
|
|
||||||
|
|
||||||
local pids=( "$PASST_PID" "$DNSMASQ_PID" )
|
|
||||||
mKill "${pids[@]}"
|
|
||||||
|
|
||||||
ip link set "$VM_NET_TAP" down promisc off &> /dev/null || :
|
|
||||||
ip link delete "$VM_NET_TAP" &> /dev/null || :
|
|
||||||
|
|
||||||
ip link set "$VM_NET_BRIDGE" down &> /dev/null || :
|
|
||||||
ip link delete "$VM_NET_BRIDGE" &> /dev/null || :
|
|
||||||
|
|
||||||
clearTables
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
closeWeb() {
|
|
||||||
|
|
||||||
local pids=( "$WEB_PID" "$WSD_PID" )
|
|
||||||
mKill "${pids[@]}"
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
closeNetwork() {
|
|
||||||
|
|
||||||
if ! disabled "${WEB:-}" && enabled "$DHCP"; then
|
|
||||||
closeWeb
|
|
||||||
fi
|
|
||||||
|
|
||||||
disabled "$NETWORK" && return 0
|
|
||||||
|
|
||||||
exec 30<&- || true
|
|
||||||
exec 40<&- || true
|
|
||||||
|
|
||||||
closeBridge
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanUp() {
|
|
||||||
|
|
||||||
closeBridge
|
|
||||||
|
|
||||||
# Clean up old files
|
|
||||||
rm -f "$PASST_PID" "$PASST_SOCKET"
|
|
||||||
rm -f "$DNSMASQ_PID" /etc/resolv.dnsmasq
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
checkOS() {
|
|
||||||
|
|
||||||
local os=""
|
|
||||||
local kernel=""
|
|
||||||
local iface="macvlan"
|
|
||||||
|
|
||||||
kernel=$(uname -a)
|
|
||||||
|
|
||||||
[[ "${kernel,,}" == *"darwin"* ]] && os="$ENGINE Desktop for macOS"
|
|
||||||
[[ "${kernel,,}" == *"microsoft"* ]] && os="$ENGINE Desktop for Windows"
|
|
||||||
|
|
||||||
if enabled "$DHCP"; then
|
|
||||||
iface="macvtap"
|
|
||||||
[[ "${kernel,,}" == *"synology"* ]] && os="Synology Container Manager"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "$os" ]; then
|
|
||||||
warn "you are using $os which does not support $iface, please revert to bridge networking!"
|
|
||||||
fi
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
getInfo() {
|
|
||||||
|
|
||||||
if [ -z "$VM_NET_DEV" ]; then
|
|
||||||
# Give Kubernetes priority over the default interface
|
|
||||||
[ -d "/sys/class/net/net0" ] && VM_NET_DEV="net0"
|
|
||||||
[ -d "/sys/class/net/net1" ] && VM_NET_DEV="net1"
|
|
||||||
[ -d "/sys/class/net/net2" ] && VM_NET_DEV="net2"
|
|
||||||
[ -d "/sys/class/net/net3" ] && VM_NET_DEV="net3"
|
|
||||||
# Automatically detect the default network interface
|
|
||||||
[ -z "$VM_NET_DEV" ] && VM_NET_DEV=$(awk '$2 == 00000000 { print $1; exit }' /proc/net/route)
|
|
||||||
[ -z "$VM_NET_DEV" ] && VM_NET_DEV="eth0"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ! -d "/sys/class/net/$VM_NET_DEV" ]; then
|
|
||||||
error "Network interface '$VM_NET_DEV' does not exist inside the container!"
|
|
||||||
error "$ADD_ERR -e \"VM_NET_DEV=NAME\" to specify another interface name." && exit 26
|
|
||||||
fi
|
|
||||||
|
|
||||||
GATEWAY=$(ip route list dev "$VM_NET_DEV" | awk ' /^default/ {print $3}' | head -n 1)
|
|
||||||
{ IP=$(ip address show dev "$VM_NET_DEV" | grep inet | awk '/inet / { print $2 }' | cut -f1 -d/ | head -n 1); } 2>/dev/null || :
|
|
||||||
[ -z "$IP" ] && ! enabled "$DHCP" && error "Could not determine container IPv4 address!" && exit 26
|
|
||||||
|
|
||||||
IP6=""
|
|
||||||
# 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
|
|
||||||
{ IP6=$(ip -6 addr show dev "$VM_NET_DEV" scope global up); rc=$?; } 2>/dev/null || :
|
|
||||||
(( rc != 0 )) && IP6=""
|
|
||||||
[ -n "$IP6" ] && IP6=$(echo "$IP6" | sed -e's/^.*inet6 \([^ ]*\)\/.*$/\1/;t;d' | head -n 1)
|
|
||||||
fi
|
|
||||||
|
|
||||||
local result nic bus
|
|
||||||
result=$(ethtool -i "$VM_NET_DEV")
|
|
||||||
nic=$(grep -m 1 -i 'driver:' <<< "$result" | awk '{print $2}')
|
|
||||||
bus=$(grep -m 1 -i 'bus-info:' <<< "$result" | awk '{print $2}')
|
|
||||||
|
|
||||||
if [[ "${bus,,}" != "" && "${bus,,}" != "n/a" && "${bus,,}" != "tap" ]]; then
|
|
||||||
enabled "$DEBUG" && info "Detected BUS: $bus"
|
|
||||||
error "This container does not support host mode networking!"
|
|
||||||
exit 29
|
|
||||||
fi
|
|
||||||
|
|
||||||
if enabled "$DHCP"; then
|
|
||||||
|
|
||||||
checkOS
|
|
||||||
|
|
||||||
if [[ "${nic,,}" == "ipvlan" ]]; then
|
|
||||||
error "This container does not support IPVLAN networking when DHCP=Y."
|
|
||||||
exit 29
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "${nic,,}" != "macvlan" ]]; then
|
|
||||||
enabled "$DEBUG" && info "Detected NIC: $nic"
|
|
||||||
error "The container needs to be in a MACVLAN network when DHCP=Y."
|
|
||||||
exit 29
|
|
||||||
fi
|
|
||||||
|
|
||||||
if uname -a | grep -Eqi 'unraid|truenas'; then
|
|
||||||
|
|
||||||
# Check if host exposes the bridge-nf sysctl
|
|
||||||
# (only visible if br_netfilter is loaded and /proc/sys is accessible)
|
|
||||||
|
|
||||||
BNF="/proc/sys/net/bridge/bridge-nf-call-iptables"
|
|
||||||
|
|
||||||
if [[ -r "$BNF" ]] && [[ "$(cat "$BNF")" != "0" ]]; then
|
|
||||||
warn "external LAN clients may not be able to reach this container, because net.bridge.bridge-nf-call-iptables=1."
|
|
||||||
warn "you can fix this issue by running 'sysctl -w net.bridge.bridge-nf-call-iptables=0' on the host system."
|
|
||||||
fi
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
else
|
|
||||||
|
|
||||||
if [[ "$IP" != "172."* && "$IP" != "10.8"* && "$IP" != "10.9"* ]]; then
|
|
||||||
checkOS
|
|
||||||
fi
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
local mtu=""
|
|
||||||
|
|
||||||
if [ -f "/sys/class/net/$VM_NET_DEV/mtu" ]; then
|
|
||||||
mtu=$(< "/sys/class/net/$VM_NET_DEV/mtu")
|
|
||||||
fi
|
|
||||||
|
|
||||||
[ -z "$MTU" ] && MTU="$mtu"
|
|
||||||
[ -z "$MTU" ] && MTU="0"
|
|
||||||
|
|
||||||
if [[ "${ADAPTER,,}" != "virtio-net-pci" ]]; then
|
|
||||||
if [[ "$MTU" != "0" ]] && [ "$MTU" -lt "1500" ]; then
|
|
||||||
warn "MTU size is $MTU, but cannot be set for $ADAPTER adapters!" && MTU="0"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$VM_NET_MAC" ]; then
|
|
||||||
local file="$STORAGE/dsm.mac"
|
|
||||||
[ -s "$file" ] && VM_NET_MAC=$(<"$file")
|
|
||||||
VM_NET_MAC="${VM_NET_MAC//[![:print:]]/}"
|
|
||||||
if [ -z "$VM_NET_MAC" ]; then
|
|
||||||
# Generate MAC address based on Docker container ID in hostname
|
|
||||||
VM_NET_MAC=$(echo "$HOST" | md5sum | sed 's/^\(..\)\(..\)\(..\)\(..\)\(..\).*$/02:11:32:\3:\4:\5/')
|
|
||||||
echo "${VM_NET_MAC^^}" > "$file"
|
|
||||||
! setOwner "$file" && error "Failed to set the owner for \"$file\" !"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
VM_NET_MAC="${VM_NET_MAC^^}"
|
|
||||||
VM_NET_MAC="${VM_NET_MAC//-/:}"
|
|
||||||
|
|
||||||
if [[ ${#VM_NET_MAC} == 12 ]]; then
|
|
||||||
m="$VM_NET_MAC"
|
|
||||||
VM_NET_MAC="${m:0:2}:${m:2:2}:${m:4:2}:${m:6:2}:${m:8:2}:${m:10:2}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ ${#VM_NET_MAC} != 17 ]]; then
|
|
||||||
error "Invalid MAC address: '$VM_NET_MAC', should be 12 or 17 digits long!" && exit 28
|
|
||||||
fi
|
|
||||||
|
|
||||||
GATEWAY_MAC=$(echo "$VM_NET_MAC" | md5sum | sed 's/^\(..\)\(..\)\(..\)\(..\)\(..\).*$/02:\1:\2:\3:\4:\5/')
|
|
||||||
|
|
||||||
if enabled "$DEBUG"; then
|
|
||||||
line="Host: $HOST IP: $IP Gateway: $GATEWAY Interface: $VM_NET_DEV MAC: $VM_NET_MAC MTU: $mtu"
|
|
||||||
[[ "$MTU" != "0" && "$MTU" != "$mtu" ]] && line+=" ($MTU)"
|
|
||||||
info "$line"
|
|
||||||
if [ -f /etc/resolv.conf ]; then
|
|
||||||
nameservers=$(grep '^nameserver ' /etc/resolv.conf | sed 's/^nameserver //' | paste -sd ',' | sed 's/,/, /g')
|
|
||||||
[ -n "$nameservers" ] && info "Nameservers: $nameservers"
|
|
||||||
fi
|
|
||||||
echo
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "$IP" > "$QEMU_DIR"/qemu.ip
|
|
||||||
echo "$nic" > "$QEMU_DIR"//qemu.nic
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
# ######################################
|
|
||||||
# Configure Network
|
|
||||||
# ######################################
|
|
||||||
|
|
||||||
if disabled "$NETWORK"; then
|
|
||||||
NET_OPTS=""
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
msg="Initializing network..."
|
|
||||||
html "$msg"
|
|
||||||
enabled "$DEBUG" && echo "$msg"
|
|
||||||
|
|
||||||
getInfo
|
|
||||||
cleanUp
|
|
||||||
|
|
||||||
if [[ "$IP" == "172.17."* ]]; then
|
|
||||||
warn "your container IP starts with 172.17.* which will cause conflicts when you install the Container Manager package inside DSM!"
|
|
||||||
fi
|
|
||||||
|
|
||||||
MSG="Booting DSM instance..."
|
|
||||||
html "$MSG"
|
|
||||||
|
|
||||||
if enabled "$DHCP"; then
|
|
||||||
|
|
||||||
# Configure for macvtap interface
|
|
||||||
configureDHCP || exit 20
|
|
||||||
|
|
||||||
else
|
|
||||||
|
|
||||||
if ! disabled "${WEB:-}"; then
|
|
||||||
sleep 1.2
|
|
||||||
closeWeb
|
|
||||||
fi
|
|
||||||
|
|
||||||
case "${NETWORK,,}" in
|
|
||||||
"passt" | "slirp" | "user"* ) ;;
|
|
||||||
"tap" | "tun" | "tuntap" | "y" | "" )
|
|
||||||
|
|
||||||
# Configure tap interface
|
|
||||||
if ! configureNAT; then
|
|
||||||
|
|
||||||
closeBridge
|
|
||||||
NETWORK="user"
|
|
||||||
|
|
||||||
if ! enabled "$ROOTLESS" || enabled "$DEBUG"; then
|
|
||||||
msg="falling back to user-mode networking!"
|
|
||||||
msg="failed to setup NAT networking, $msg"
|
|
||||||
warn "$msg"
|
|
||||||
fi
|
|
||||||
|
|
||||||
fi ;;
|
|
||||||
|
|
||||||
esac
|
|
||||||
|
|
||||||
case "${NETWORK,,}" in
|
|
||||||
"tap" | "tun" | "tuntap" | "y" | "" ) ;;
|
|
||||||
"passt" | "user"* )
|
|
||||||
|
|
||||||
# Configure for user-mode networking (passt)
|
|
||||||
if ! configurePasst; then
|
|
||||||
error "Failed to configure user-mode networking!"
|
|
||||||
exit 24
|
|
||||||
fi ;;
|
|
||||||
|
|
||||||
"slirp" )
|
|
||||||
|
|
||||||
# Configure for user-mode networking (slirp)
|
|
||||||
if ! configureSlirp; then
|
|
||||||
error "Failed to configure user-mode networking!"
|
|
||||||
exit 24
|
|
||||||
fi ;;
|
|
||||||
|
|
||||||
*)
|
|
||||||
error "Unrecognized NETWORK value: \"$NETWORK\"" && exit 24 ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
case "${NETWORK,,}" in
|
|
||||||
"passt" | "slirp" )
|
|
||||||
|
|
||||||
if [ -z "$USER_PORTS" ]; then
|
|
||||||
info "Notice: because user-mode networking is active, when you need to forward custom ports to DSM, add them to the \"USER_PORTS\" variable."
|
|
||||||
fi ;;
|
|
||||||
|
|
||||||
esac
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
NET_OPTS+=" -device $ADAPTER,id=net0,netdev=hostnet0,romfile=,mac=$VM_NET_MAC"
|
|
||||||
[[ "$MTU" != "0" && "$MTU" != "1500" ]] && NET_OPTS+=",host_mtu=$MTU"
|
|
||||||
|
|
||||||
return 0
|
|
||||||
+126
-96
@@ -2,116 +2,68 @@
|
|||||||
set -Eeuo pipefail
|
set -Eeuo pipefail
|
||||||
|
|
||||||
: "${SHUTDOWN:="Y"}" # Graceful ACPI shutdown
|
: "${SHUTDOWN:="Y"}" # Graceful ACPI shutdown
|
||||||
: "${TIMEOUT:="115"}" # QEMU termination timeout
|
: "${TIMEOUT:="105"}" # QEMU termination timeout
|
||||||
: "${API_TIMEOUT:="90"}" # External API call timeout
|
: "${API_TIMEOUT:="90"}" # External API call timeout
|
||||||
|
|
||||||
# Configure QEMU for graceful shutdown
|
# Configure QEMU for graceful shutdown
|
||||||
|
|
||||||
API_CMD=6
|
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"
|
QEMU_END="$QEMU_DIR/qemu.end"
|
||||||
|
CONSOLE_PID="$QEMU_DIR/console.pid"
|
||||||
_trap() {
|
CONSOLE_SOCKET="$QEMU_DIR/console.sock"
|
||||||
local func="$1"; shift
|
QEMU_START_PID="$QEMU_DIR/qemu.start.pid"
|
||||||
local sig
|
|
||||||
TRAP_PID=$BASHPID
|
|
||||||
|
|
||||||
for sig; do
|
|
||||||
trap "$func $sig" "$sig"
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
app() {
|
|
||||||
echo "$APP" && return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
finish() {
|
finish() {
|
||||||
|
|
||||||
local i=0
|
local reason=$1 failed=0
|
||||||
local pid=""
|
|
||||||
local reason=$1
|
|
||||||
local pids=( "${HOST_PID:-}" "${WSD_PID:-}" \
|
|
||||||
"${WEB_PID:-}" "${PASST_PID:-}" "${DNSMASQ_PID:-}" )
|
|
||||||
|
|
||||||
touch "$QEMU_END"
|
# A nonzero exit is unexpected only when QEMU_END is missing.
|
||||||
|
if [ ! -f "$QEMU_END" ] && (( reason != 0 )); then
|
||||||
if [ -s "$QEMU_PID" ]; then
|
failed=1
|
||||||
if read -r pid <"$QEMU_PID"; then
|
|
||||||
if [ -n "$pid" ] && isAlive "$pid"; then
|
|
||||||
local display="$reason"
|
|
||||||
case "$reason" in
|
|
||||||
129 ) display="SIGHUP" ;;
|
|
||||||
130 ) display="SIGINT" ;;
|
|
||||||
131 ) display="SIGQUIT" ;;
|
|
||||||
134 ) display="SIGABRT" ;;
|
|
||||||
143 ) display="SIGTERM" ;;
|
|
||||||
esac
|
|
||||||
error "Forcefully terminating $(app), reason: $display..."
|
|
||||||
{ disown "$pid" || :; kill -9 -- "$pid" || :; } 2>/dev/null
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
mKill "${pids[@]}"
|
touch "$QEMU_END" || :
|
||||||
|
|
||||||
|
forceKillQemu "$reason"
|
||||||
|
cleanupHelpers "$HOST_PID"
|
||||||
|
|
||||||
fKill "print.sh"
|
fKill "print.sh"
|
||||||
|
rm -f -- "$HOST_API_SOCKET" "$HOST_AGENT_SOCKET"
|
||||||
|
|
||||||
closeNetwork
|
if ! waitQemuExit 10; then
|
||||||
|
|
||||||
if ! waitPidFile "$QEMU_PID" 10; then
|
|
||||||
warn "Timed out while waiting for $(app) to exit!"
|
warn "Timed out while waiting for $(app) to exit!"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
(( reason != 1 )) && echo && echo "❯ Shutdown completed!"
|
stopConsole
|
||||||
|
echo
|
||||||
|
|
||||||
|
if (( failed == 0 )); then
|
||||||
|
echo "❯ Shutdown completed!"
|
||||||
|
else
|
||||||
|
error "QEMU exited unexpectedly!"
|
||||||
|
fi
|
||||||
|
|
||||||
exit "$reason"
|
exit "$reason"
|
||||||
}
|
}
|
||||||
|
|
||||||
graceful_shutdown() {
|
sendGuestShutdown() {
|
||||||
|
|
||||||
local sig="$1"
|
local pid="$1"
|
||||||
local pid=""
|
local response
|
||||||
local code=0
|
|
||||||
local start url response elapsed
|
|
||||||
|
|
||||||
[[ $BASHPID != "$TRAP_PID" ]] && return
|
|
||||||
|
|
||||||
case "$sig" in
|
|
||||||
SIGHUP) code=129 ;;
|
|
||||||
SIGINT) code=130 ;;
|
|
||||||
SIGQUIT) code=131 ;;
|
|
||||||
SIGABRT) code=134 ;;
|
|
||||||
SIGTERM) code=143 ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
if [ -f "$QEMU_END" ]; then
|
|
||||||
echo && info "Received $1 signal while already shutting down..."
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
set +e
|
|
||||||
start=$SECONDS
|
|
||||||
touch "$QEMU_END"
|
|
||||||
echo && info "Received $1 signal, sending shutdown command..."
|
|
||||||
|
|
||||||
if [ ! -s "$QEMU_PID" ] || ! read -r pid <"$QEMU_PID"; then
|
|
||||||
warn "QEMU PID file ($QEMU_PID) does not exist?"
|
|
||||||
finish "$code"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$pid" ] || ! isAlive "$pid"; then
|
|
||||||
warn "QEMU process with PID $pid does not exist?"
|
|
||||||
finish "$code"
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
# Virtual DSM ignores ACPI powerdown, so graceful shutdown must go through
|
||||||
|
# the qemu-host guest API exposed on the Unix socket.
|
||||||
# Don't send the powerdown signal because vDSM ignores ACPI signals
|
# Don't send the powerdown signal because vDSM ignores ACPI signals
|
||||||
# nc -q 1 -w 1 -U "$QEMU_DIR/monitor.sock" &> /dev/null <<<'system_powerdown' || :
|
# nc -q 1 -w 1 -U "$QEMU_DIR/monitor.sock" &> /dev/null <<<'system_powerdown' || :
|
||||||
|
|
||||||
# Send shutdown command to guest agent via serial port
|
# Send shutdown command to guest agent via serial port
|
||||||
API_TIMEOUT=$(strip "$API_TIMEOUT")
|
API_TIMEOUT=$(strip "$API_TIMEOUT")
|
||||||
url="http://$API_HOST/read?command=$API_CMD&timeout=$API_TIMEOUT"
|
local url="http://localhost/read?command=$API_CMD&timeout=$API_TIMEOUT"
|
||||||
response=$(curl -sk -m "$(( API_TIMEOUT+2 ))" -S "$url" 2>&1)
|
response=$(curl --unix-socket "$HOST_API_SOCKET" -sk -m "$(( API_TIMEOUT+2 ))" -S "$url" 2>&1)
|
||||||
|
|
||||||
if [[ "$response" =~ "\"success\"" ]]; then
|
if [[ "$response" =~ "\"success\"" ]]; then
|
||||||
|
|
||||||
@@ -121,20 +73,25 @@ graceful_shutdown() {
|
|||||||
|
|
||||||
response="${response#*message\"\: \"}"
|
response="${response#*message\"\: \"}"
|
||||||
[ -z "$response" ] && response="second signal"
|
[ -z "$response" ] && response="second signal"
|
||||||
|
|
||||||
echo && error "Forcefully terminating because of: ${response%%\"*}"
|
echo && error "Forcefully terminating because of: ${response%%\"*}"
|
||||||
kill -15 -- "$pid" 2>/dev/null || :
|
kill -15 -- "$pid" 2>/dev/null || :
|
||||||
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local name
|
return 0
|
||||||
name="$(app)"
|
}
|
||||||
|
|
||||||
|
normalizeTimeout() {
|
||||||
|
|
||||||
|
# Divide the remaining timeout into guest wait, SIGTERM grace, and final
|
||||||
|
# cleanup instead of allowing the API call to consume the entire budget.
|
||||||
local term_grace=3 # seconds before loop ends to send SIGTERM
|
local term_grace=3 # seconds before loop ends to send SIGTERM
|
||||||
local cleanup_grace=3 # seconds reserved after the loop for cleanup
|
local cleanup_grace=3 # seconds reserved after the loop for cleanup
|
||||||
|
|
||||||
TIMEOUT=$(strip "$TIMEOUT")
|
TIMEOUT=$(strip "$TIMEOUT")
|
||||||
if [[ ! "$TIMEOUT" =~ ^[0-9]+$ ]]; then
|
if [[ ! "$TIMEOUT" =~ ^[0-9]+$ ]]; then
|
||||||
TIMEOUT=115
|
TIMEOUT=105
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if (( TIMEOUT >= 30 )); then
|
if (( TIMEOUT >= 30 )); then
|
||||||
@@ -145,27 +102,36 @@ graceful_shutdown() {
|
|||||||
cleanup_grace=4
|
cleanup_grace=4
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local cnt=0 sigterm_at=0 min wait_until elapsed timeout_left
|
local elapsed=$((SECONDS - start))
|
||||||
|
local timeout_left=$((TIMEOUT - elapsed))
|
||||||
|
|
||||||
elapsed=$((SECONDS - start))
|
local min=$((term_grace + cleanup_grace + 1))
|
||||||
timeout_left=$((TIMEOUT - elapsed))
|
|
||||||
|
|
||||||
min=$((term_grace + cleanup_grace + 1))
|
|
||||||
(( timeout_left < min )) && timeout_left=$min
|
(( timeout_left < min )) && timeout_left=$min
|
||||||
|
|
||||||
wait_until=$((timeout_left - cleanup_grace))
|
wait_until=$((timeout_left - cleanup_grace))
|
||||||
sigterm_at=$((wait_until - term_grace))
|
sigterm_at=$((wait_until - term_grace))
|
||||||
|
|
||||||
while (( cnt <= wait_until )); do
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
waitForShutdown() {
|
||||||
|
|
||||||
|
local cnt=0
|
||||||
|
local pid="$1"
|
||||||
|
local name="$APP"
|
||||||
|
|
||||||
|
while (( cnt <= wait_until && SHUTDOWN_SKIP == 0 )); do
|
||||||
|
|
||||||
sleep 1 &
|
sleep 1 &
|
||||||
local slp=$!
|
local slp=$!
|
||||||
|
|
||||||
# Stop waiting if the process has exited
|
# Stop waiting if the process has exited
|
||||||
! isAlive "$pid" && break
|
isAlive "$pid" || break
|
||||||
|
|
||||||
|
# The process state is authoritative, but disappearance of both pidfiles
|
||||||
|
# also ends the wait when a wrapper exits before process reaping completes.
|
||||||
# Workaround for stale/zombie QEMU pid file
|
# Workaround for stale/zombie QEMU pid file
|
||||||
[ ! -s "$QEMU_PID" ] && break
|
[ ! -s "$QEMU_START_PID" ] && [ ! -s "$QEMU_PID" ] && break
|
||||||
|
|
||||||
if (( cnt == sigterm_at )); then
|
if (( cnt == sigterm_at )); then
|
||||||
info "${name^} is still running, sending SIGTERM... ($cnt/$wait_until)"
|
info "${name^} is still running, sending SIGTERM... ($cnt/$wait_until)"
|
||||||
@@ -174,17 +140,81 @@ graceful_shutdown() {
|
|||||||
info "Waiting for $name to shut down... ($cnt/$wait_until)"
|
info "Waiting for $name to shut down... ($cnt/$wait_until)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
wait "$slp"
|
wait "$slp" || :
|
||||||
(( cnt++ ))
|
(( cnt++ ))
|
||||||
|
|
||||||
done
|
done
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
gracefulShutdown() {
|
||||||
|
|
||||||
|
local sig="$1"
|
||||||
|
local pid code
|
||||||
|
|
||||||
|
[[ $BASHPID != "$TRAP_PID" ]] && return
|
||||||
|
|
||||||
|
code=$(signalCode "$sig")
|
||||||
|
|
||||||
|
if (( SHUTDOWN_SIGNAL != 0 )); then
|
||||||
|
|
||||||
|
# A second Ctrl-C is the explicit user request to skip the remaining
|
||||||
|
# graceful-shutdown wait and proceed to forced cleanup.
|
||||||
|
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
|
||||||
|
|
||||||
|
start=$SECONDS
|
||||||
|
SHUTDOWN_SIGNAL=$code
|
||||||
|
|
||||||
|
# Shutdown handlers must continue through missing processes and failed cleanup
|
||||||
|
# commands instead of being aborted by errexit.
|
||||||
|
set +e
|
||||||
|
touch "$QEMU_END"
|
||||||
|
|
||||||
|
echo && info "Received $sig signal, sending shutdown command..."
|
||||||
|
|
||||||
|
if ! readQemuPid pid; then
|
||||||
|
if ! interactive || ! waitQemuPid pid; then
|
||||||
|
warn "QEMU PID file does not exist?"
|
||||||
|
finish "$code"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$pid" ] || ! isAlive "$pid"; then
|
||||||
|
warn "QEMU process with PID $pid does not exist?"
|
||||||
|
finish "$code"
|
||||||
|
fi
|
||||||
|
|
||||||
|
sendGuestShutdown "$pid"
|
||||||
|
normalizeTimeout
|
||||||
|
waitForShutdown "$pid"
|
||||||
|
|
||||||
finish "$code"
|
finish "$code"
|
||||||
}
|
}
|
||||||
|
|
||||||
! enabled "$SHUTDOWN" && return 0
|
enableTrap() {
|
||||||
|
|
||||||
|
enabled "$SHUTDOWN" || return 0
|
||||||
|
|
||||||
|
# Keep Ctrl-C available to interactive users without installing an unnecessary
|
||||||
|
# SIGINT handler for background/container execution.
|
||||||
|
if interactive; then
|
||||||
|
_trap gracefulShutdown SIGINT
|
||||||
|
fi
|
||||||
|
|
||||||
|
_trap gracefulShutdown SIGTERM SIGHUP SIGABRT SIGQUIT
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
[ -n "${QEMU_TIMEOUT:-}" ] && TIMEOUT="$QEMU_TIMEOUT"
|
[ -n "${QEMU_TIMEOUT:-}" ] && TIMEOUT="$QEMU_TIMEOUT"
|
||||||
|
|
||||||
_trap graceful_shutdown SIGTERM SIGHUP SIGABRT SIGQUIT
|
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
addSystemDisks() {
|
||||||
|
|
||||||
|
local boot="$STORAGE/$BASE.boot.img"
|
||||||
|
local system="$STORAGE/$BASE.system.img"
|
||||||
|
|
||||||
|
[ ! -s "$boot" ] && error "Virtual DSM boot-image does not exist ($boot)" && return 81
|
||||||
|
[ ! -s "$system" ] && error "Virtual DSM system-image does not exist ($system)" && return 82
|
||||||
|
|
||||||
|
setOwner "$boot" || warn "failed to set the owner for \"$boot\" !"
|
||||||
|
setOwner "$system" || warn "failed to set the owner for \"$system\" !"
|
||||||
|
|
||||||
|
DISK_OPTS+=$(createDevice "$boot" "$DISK_TYPE" "1" "0xa" "raw" "$DISK_IO" "$DISK_CACHE" "" "" "synoboot")
|
||||||
|
DISK_OPTS+=$(createDevice "$system" "$DISK_TYPE" "2" "0xb" "raw" "$DISK_IO" "$DISK_CACHE" "" "" "synosys")
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
closeWebserver() {
|
||||||
|
|
||||||
|
disabled "${NETWORK:-Y}" && return 0
|
||||||
|
|
||||||
|
MSG="Booting DSM instance..."
|
||||||
|
html "$MSG" || return $?
|
||||||
|
|
||||||
|
if enabled "${DHCP:-N}" || disabled "${WEB:-}"; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
writeAtomic "$WSD_COMMAND" "portal" || return $?
|
||||||
|
|
||||||
|
sleep 1.2
|
||||||
|
stopAllServers
|
||||||
|
|
||||||
|
WEB="N"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
addSystemDisks || return $?
|
||||||
|
closeWebserver || return $?
|
||||||
|
|
||||||
|
return 0
|
||||||
+175
-53
@@ -1,106 +1,228 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
# shellcheck disable=SC2329
|
||||||
set -Eeuo pipefail
|
set -Eeuo pipefail
|
||||||
|
|
||||||
: "${DHCP:="N"}"
|
: "${DHCP:="N"}"
|
||||||
: "${NETWORK:="Y"}"
|
: "${NETWORK:="Y"}"
|
||||||
|
|
||||||
[[ "$NETWORK" == [Nn]* ]] && exit 0
|
cd /run
|
||||||
|
. utils.sh # Load functions
|
||||||
|
|
||||||
info () { printf "%b%s%b" "\E[1;34m❯ \E[1;36m" "$1" "\E[0m\n" >&2; }
|
info () { printf "%b%s%b" "\E[1;34m❯ \E[1;36m" "$1" "\E[0m\n" >&2; }
|
||||||
|
warn () { printf "%b%s%b" "\E[1;33m❯ " "WARNING: $1" "\E[0m\n" >&2; }
|
||||||
error () { printf "%b%s%b" "\E[1;31m❯ " "ERROR: $1" "\E[0m\n" >&2; }
|
error () { printf "%b%s%b" "\E[1;31m❯ " "ERROR: $1" "\E[0m\n" >&2; }
|
||||||
|
|
||||||
|
disabled "$NETWORK" && exit 0
|
||||||
|
|
||||||
file="/run/shm/dsm.url"
|
file="/run/shm/dsm.url"
|
||||||
info="/run/shm/msg.html"
|
msgs="/run/shm/msg.html"
|
||||||
driver="/run/shm/qemu.nic"
|
driver="/run/shm/qemu.nic"
|
||||||
page="/run/shm/index.html"
|
page="/run/shm/index.html"
|
||||||
address="/run/shm/qemu.ip"
|
address="/run/shm/qemu.host"
|
||||||
shutdown="/run/shm/qemu.end"
|
shutdown="/run/shm/qemu.end"
|
||||||
|
command="/run/shm/status.cmd"
|
||||||
|
socket="/run/shm/qemu-host-api.sock"
|
||||||
template="/var/www/index.html"
|
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:"
|
resp_err="Guest returned an invalid response:"
|
||||||
curl_err="Failed to connect to guest: curl error"
|
curl_err="Failed to connect to guest: curl error"
|
||||||
jq_err="Failed to parse response from guest: jq error"
|
jq_err="Failed to parse response from guest: jq error"
|
||||||
|
|
||||||
while [ ! -s "$file" ]
|
exitIfShuttingDown() {
|
||||||
do
|
|
||||||
|
|
||||||
# Check if not shutting down
|
|
||||||
[ -f "$shutdown" ] && exit 1
|
|
||||||
|
|
||||||
sleep 3
|
|
||||||
|
|
||||||
[ -f "$shutdown" ] && exit 1
|
[ -f "$shutdown" ] && exit 1
|
||||||
[ -s "$file" ] && break
|
|
||||||
|
|
||||||
# Retrieve network info from guest VM
|
return 0
|
||||||
{ json=$(curl -m 20 -sk "$url"); rc=$?; } || :
|
}
|
||||||
|
|
||||||
[ -f "$shutdown" ] && exit 1
|
queryGuest() {
|
||||||
(( rc != 0 )) && error "$curl_err $rc" && continue
|
|
||||||
|
|
||||||
{ result=$(echo "$json" | jq -r '.status'); rc=$?; } || :
|
# Query DSM through the qemu-host sidecar rather than the guest network,
|
||||||
(( rc != 0 )) && error "$jq_err $rc ( $json )" && continue
|
# which may not be configured yet.
|
||||||
[[ "$result" == "null" ]] && error "$resp_err $json" && continue
|
{ json=$(curl --unix-socket "$socket" -m 20 -sk "$url"); local rc=$?; } || :
|
||||||
|
|
||||||
if [[ "$result" != "success" ]] ; then
|
exitIfShuttingDown
|
||||||
{ msg=$(echo "$json" | jq -r '.message'); rc=$?; } || :
|
|
||||||
error "Guest replied $result: $msg" && continue
|
if (( rc != 0 )); then
|
||||||
|
error "$curl_err $rc"
|
||||||
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
{ port=$(echo "$json" | jq -r '.data.data.dsm_setting.data.http_port'); rc=$?; } || :
|
return 0
|
||||||
(( rc != 0 )) && error "$jq_err $rc ( $json )" && continue
|
}
|
||||||
[[ "$port" == "null" ]] && error "$resp_err $json" && continue
|
|
||||||
[ -z "$port" ] && continue
|
|
||||||
|
|
||||||
{ ip=$(echo "$json" | jq -r '.data.data.ip.data[] | select((.name=="eth0") and has("ip")).ip'); rc=$?; } || :
|
readJsonField() {
|
||||||
(( rc != 0 )) && error "$jq_err $rc ( $json )" && continue
|
|
||||||
[[ "$ip" == "null" ]] && error "$resp_err $json" && continue
|
|
||||||
|
|
||||||
[ -z "$ip" ] && continue
|
local query="$1"
|
||||||
echo "$ip:$port" > $file
|
local result
|
||||||
|
|
||||||
done
|
{ result=$(jq -r "$query" <<< "$json"); local rc=$?; } || :
|
||||||
|
|
||||||
[ -f "$shutdown" ] && exit 1
|
if (( rc != 0 )); then
|
||||||
|
error "$jq_err $rc ( $json )"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
location=$(<"$file")
|
if [[ "$result" == "null" ]]; then
|
||||||
|
error "$resp_err $json"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ "$DHCP" == [Yy1]* ]]; then
|
printf '%s\n' "$result"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
readGuestStatus() {
|
||||||
|
|
||||||
|
local result msg
|
||||||
|
|
||||||
|
result=$(readJsonField '.status') || return 1
|
||||||
|
|
||||||
|
if [[ "$result" != "success" ]]; then
|
||||||
|
{ msg=$(jq -r '.message // empty' <<< "$json"); local rc=$?; } || :
|
||||||
|
|
||||||
|
if (( rc != 0 )); then
|
||||||
|
error "$jq_err $rc ( $json )"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
error "Guest replied $result: $msg"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
readGuestPort() {
|
||||||
|
|
||||||
|
port=$(readJsonField '.data.data.dsm_setting.data.http_port') || return 1
|
||||||
|
[ -z "$port" ] && return 1
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
readGuestIp() {
|
||||||
|
|
||||||
|
ip=$(readJsonField '.data.data.ip.data[] | select(.name=="eth0" and has("ip")) | .ip | select(test("^[0-9]+\\."))') || return 1
|
||||||
|
[ -z "$ip" ] && return 1
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
writeDsmLocation() {
|
||||||
|
|
||||||
|
echo "$ip:$port" > "$file"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pollGuestLocation() {
|
||||||
|
|
||||||
|
# Keep polling until the guest reports a usable address, but stop promptly
|
||||||
|
# when container shutdown begins.
|
||||||
|
while [ ! -s "$file" ]; do
|
||||||
|
|
||||||
|
# Check if not shutting down
|
||||||
|
exitIfShuttingDown
|
||||||
|
|
||||||
|
sleep 3
|
||||||
|
|
||||||
|
exitIfShuttingDown
|
||||||
|
[ -s "$file" ] && break
|
||||||
|
|
||||||
|
# Retrieve network info from guest VM
|
||||||
|
queryGuest || continue
|
||||||
|
readGuestStatus || continue
|
||||||
|
readGuestPort || continue
|
||||||
|
readGuestIp || continue
|
||||||
|
writeDsmLocation
|
||||||
|
|
||||||
|
done
|
||||||
|
|
||||||
|
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 html
|
||||||
|
|
||||||
msg="http://$location"
|
msg="http://$location"
|
||||||
title="<title>Virtual DSM</title>"
|
local title="<title>Virtual DSM</title>"
|
||||||
body="The location of DSM is <a href='http://$location'>http://$location</a>"
|
local body="The location of DSM is <a href='http://$location'>http://$location</a>"
|
||||||
script="<script>setTimeout(function(){ window.location.assign('http://$location'); }, 3000);</script>"
|
local script="<script>setTimeout(function(){ window.location.assign('http://$location'); }, 3000);</script>"
|
||||||
|
|
||||||
HTML=$(<"$template")
|
html=$(<"$template")
|
||||||
HTML="${HTML/\[1\]/$title}"
|
html="${html/\[1\]/$title}"
|
||||||
HTML="${HTML/\[2\]/$script}"
|
html="${html/\[2\]/$script}"
|
||||||
HTML="${HTML/\[3\]/$body}"
|
html="${html/\[3\]/$body}"
|
||||||
HTML="${HTML/\[4\]/}"
|
html="${html/\[4\]/}"
|
||||||
HTML="${HTML/\[5\]/}"
|
html="${html/\[5\]/}"
|
||||||
|
|
||||||
echo "$HTML" > "$page"
|
echo "$html" > "$page"
|
||||||
echo "$body" > "$info"
|
echo "$body" > "$msgs"
|
||||||
|
writeAtomic "$command" "portal http://$location"
|
||||||
|
|
||||||
else
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
buildStaticMessage() {
|
||||||
|
|
||||||
|
local nic ip
|
||||||
|
|
||||||
nic=$(<"$driver")
|
nic=$(<"$driver")
|
||||||
ip=$(<"$address")
|
ip=$(<"$address")
|
||||||
port="${location##*:}"
|
local port="${location##*:}"
|
||||||
|
|
||||||
|
# NAT and user-mode networking are reached through a forwarded host port;
|
||||||
|
# macvlan exposes DSM directly on the container-facing LAN address.
|
||||||
if [[ "${nic,,}" != "macvlan" ]]; then
|
if [[ "${nic,,}" != "macvlan" ]]; then
|
||||||
msg="port $port"
|
msg="port $port"
|
||||||
else
|
else
|
||||||
msg="http://$ip:$port"
|
msg="http://$ip:$port"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
printLoginMessage() {
|
||||||
|
|
||||||
|
echo "" >&2
|
||||||
|
info "-----------------------------------------------------------"
|
||||||
|
info " You can now login to DSM at $msg"
|
||||||
|
info "-----------------------------------------------------------"
|
||||||
|
echo "" >&2
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pollGuestLocation
|
||||||
|
exitIfShuttingDown
|
||||||
|
|
||||||
|
location=$(<"$file")
|
||||||
|
|
||||||
|
if enabled "$DHCP"; then
|
||||||
|
checkAddressConflict
|
||||||
|
writeDhcpPage
|
||||||
|
else
|
||||||
|
buildStaticMessage
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "" >&2
|
printLoginMessage
|
||||||
info "-----------------------------------------------------------"
|
|
||||||
info " You can now login to DSM at $msg"
|
|
||||||
info "-----------------------------------------------------------"
|
|
||||||
echo "" >&2
|
|
||||||
|
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
-144
@@ -1,144 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -Eeuo pipefail
|
|
||||||
|
|
||||||
# Docker environment variables
|
|
||||||
|
|
||||||
: "${HOST_CPU:=""}"
|
|
||||||
: "${CPU_FLAGS:=""}"
|
|
||||||
: "${CPU_MODEL:=""}"
|
|
||||||
|
|
||||||
HOST_CPU=$(strip "$HOST_CPU")
|
|
||||||
CPU_FLAGS=$(strip "$CPU_FLAGS")
|
|
||||||
CPU_MODEL=$(strip "$CPU_MODEL")
|
|
||||||
|
|
||||||
CLOCKSOURCE="tsc"
|
|
||||||
[[ "${ARCH,,}" == "arm64" ]] && CLOCKSOURCE="arch_sys_counter"
|
|
||||||
CLOCK="/sys/devices/system/clocksource/clocksource0/current_clocksource"
|
|
||||||
|
|
||||||
if [ ! -f "$CLOCK" ]; then
|
|
||||||
warn "file \"$CLOCK\" cannot be found?"
|
|
||||||
else
|
|
||||||
result=$(<"$CLOCK")
|
|
||||||
result="${result//[![:print:]]/}"
|
|
||||||
case "${result,,}" in
|
|
||||||
"${CLOCKSOURCE,,}" ) ;;
|
|
||||||
"kvm-clock" ) info "Nested KVM virtualization detected.." ;;
|
|
||||||
"hyperv_clocksource_tsc_page" ) info "Nested Hyper-V virtualization detected.." ;;
|
|
||||||
"hpet" ) warn "unsupported clock source detected: '$result'. Please set host clock source to '$CLOCKSOURCE'." ;;
|
|
||||||
*) warn "unexpected clock source detected: '$result'. Please set host clock source to '$CLOCKSOURCE'." ;;
|
|
||||||
esac
|
|
||||||
fi
|
|
||||||
|
|
||||||
flags=$(sed -ne '/^flags/s/^.*: //p' /proc/cpuinfo)
|
|
||||||
|
|
||||||
if ! disabled "$KVM"; then
|
|
||||||
|
|
||||||
CPU_FEATURES="kvm=on,l3-cache=on,+hypervisor"
|
|
||||||
KVM_OPTS=",accel=kvm -enable-kvm -global kvm-pit.lost_tick_policy=discard"
|
|
||||||
|
|
||||||
if ! grep -qw "sse4_2" <<< "$flags"; then
|
|
||||||
error "Your CPU does not have the SSE4 instruction set that Virtual DSM requires!"
|
|
||||||
! enabled "$DEBUG" && exit 88
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$CPU_MODEL" ]; then
|
|
||||||
CPU_MODEL="host"
|
|
||||||
CPU_FEATURES+=",migratable=no"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if grep -qw "svm" <<< "$flags"; then
|
|
||||||
|
|
||||||
# AMD processor
|
|
||||||
|
|
||||||
if grep -qw "tsc_scale" <<< "$flags"; then
|
|
||||||
CPU_FEATURES+=",+invtsc"
|
|
||||||
fi
|
|
||||||
|
|
||||||
else
|
|
||||||
|
|
||||||
# Intel processor
|
|
||||||
|
|
||||||
vmx=$(sed -ne '/^vmx flags/s/^.*: //p' /proc/cpuinfo)
|
|
||||||
|
|
||||||
if grep -qw "tsc_scaling" <<< "$vmx"; then
|
|
||||||
CPU_FEATURES+=",+invtsc"
|
|
||||||
fi
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
else
|
|
||||||
|
|
||||||
KVM_OPTS=""
|
|
||||||
CPU_FEATURES="l3-cache=on,+hypervisor"
|
|
||||||
|
|
||||||
if [[ "$ARCH" == "amd64" ]]; then
|
|
||||||
KVM_OPTS=" -accel tcg,thread=multi"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$CPU_MODEL" ]; then
|
|
||||||
if [[ "$ARCH" == "amd64" ]]; then
|
|
||||||
CPU_MODEL="max"
|
|
||||||
CPU_FEATURES+=",migratable=no"
|
|
||||||
else
|
|
||||||
CPU_MODEL="qemu64"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
CPU_FEATURES+=",+ssse3,+sse4.1,+sse4.2"
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
if [ -z "$HOST_CPU" ]; then
|
|
||||||
[[ "${CPU,,}" != "unknown" ]] && HOST_CPU="$CPU"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "$HOST_CPU" ]; then
|
|
||||||
HOST_CPU="${HOST_CPU%%,*},,"
|
|
||||||
else
|
|
||||||
HOST_CPU="QEMU, Virtual CPU,"
|
|
||||||
if [ "$ARCH" == "amd64" ]; then
|
|
||||||
HOST_CPU+=" X86_64"
|
|
||||||
else
|
|
||||||
HOST_CPU+=" $ARCH"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
return 0
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -Eeuo pipefail
|
|
||||||
|
|
||||||
info="/run/shm/msg.html"
|
|
||||||
|
|
||||||
escape () {
|
|
||||||
local s
|
|
||||||
s=${1//&/\&}
|
|
||||||
s=${s//</\<}
|
|
||||||
s=${s//>/\>}
|
|
||||||
s=${s//'"'/\"}
|
|
||||||
printf -- %s "$s"
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
path="$1"
|
|
||||||
total="$2"
|
|
||||||
body=$(escape "$3")
|
|
||||||
|
|
||||||
if [[ "$body" == *"..." ]]; then
|
|
||||||
body="<p class=\"loading\">${body::-3}</p>"
|
|
||||||
fi
|
|
||||||
|
|
||||||
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%"
|
|
||||||
fi
|
|
||||||
[[ "$size" != "0.0%" ]] && echo "${body//(\[P\])/($size)}"> "$info"
|
|
||||||
fi
|
|
||||||
|
|
||||||
sleep 1 & wait $!
|
|
||||||
|
|
||||||
done
|
|
||||||
-240
@@ -1,240 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -Eeuo pipefail
|
|
||||||
|
|
||||||
trap 'error "Status $? while: $BASH_COMMAND (line $LINENO/$BASH_LINENO)"' ERR
|
|
||||||
enabled "${TRACE:-}" && set -o functrace && trap 'echo "# $BASH_COMMAND" >&2' DEBUG
|
|
||||||
|
|
||||||
[ ! -f "/run/entry.sh" ] && error "Script must be run inside the container!" && exit 11
|
|
||||||
[ "$(id -u)" -ne "0" ] && error "Script must be executed with root privileges." && exit 12
|
|
||||||
|
|
||||||
# Docker environment variables
|
|
||||||
|
|
||||||
: "${TZ:=""}" # System timezone
|
|
||||||
: "${KVM:="Y"}" # KVM acceleration
|
|
||||||
: "${DEBUG:="N"}" # Disable debugging mode
|
|
||||||
: "${COUNTRY:=""}" # Country code for mirror
|
|
||||||
: "${ALLOCATE:=""}" # Preallocate diskspace
|
|
||||||
: "${ARGUMENTS:=""}" # Extra QEMU parameters
|
|
||||||
: "${CPU_CORES:="2"}" # Amount of CPU cores
|
|
||||||
: "${RAM_SIZE:="2G"}" # Maximum RAM amount
|
|
||||||
: "${RAM_CHECK:="Y"}" # Check available RAM
|
|
||||||
: "${DISK_SIZE:="16G"}" # Initial data disk size
|
|
||||||
: "${STORAGE:="/storage"}" # Storage folder location
|
|
||||||
|
|
||||||
# Sanitize variables
|
|
||||||
TZ=$(strip "$TZ")
|
|
||||||
STORAGE=$(strip "$STORAGE")
|
|
||||||
COUNTRY=$(strip "$COUNTRY")
|
|
||||||
DISK_SIZE=$(strip "$DISK_SIZE")
|
|
||||||
|
|
||||||
# Helper variables
|
|
||||||
ROOTLESS="N"
|
|
||||||
PRIVILEGED="N"
|
|
||||||
ENGINE="Docker"
|
|
||||||
PROCESS="${APP,,}"
|
|
||||||
PROCESS="${PROCESS// /-}"
|
|
||||||
|
|
||||||
if [ -f "/run/.containerenv" ]; then
|
|
||||||
ENGINE="${container:-}"
|
|
||||||
if [[ "${ENGINE,,}" == *"podman"* ]]; then
|
|
||||||
ROOTLESS="Y"
|
|
||||||
ENGINE="Podman"
|
|
||||||
else
|
|
||||||
[ -z "$ENGINE" ] && ENGINE="Kubernetes"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "❯ Starting $APP for $ENGINE v$(</etc/version)..."
|
|
||||||
echo "❯ For support visit $SUPPORT"
|
|
||||||
|
|
||||||
# Get the capability bounding set
|
|
||||||
CAP_BND=$(grep '^CapBnd:' /proc/$$/status | awk '{print $2}')
|
|
||||||
CAP_BND=$(printf "%d" "0x${CAP_BND}")
|
|
||||||
|
|
||||||
# Get the last capability number
|
|
||||||
LAST_CAP=$(cat /proc/sys/kernel/cap_last_cap)
|
|
||||||
|
|
||||||
# Calculate the maximum capability value
|
|
||||||
MAX_CAP=$(((1 << (LAST_CAP + 1)) - 1))
|
|
||||||
|
|
||||||
if [ "${CAP_BND}" -eq "${MAX_CAP}" ]; then
|
|
||||||
ROOTLESS="N"
|
|
||||||
PRIVILEGED="Y"
|
|
||||||
fi
|
|
||||||
|
|
||||||
INFO="/run/shm/msg.html"
|
|
||||||
PAGE="/run/shm/index.html"
|
|
||||||
TEMPLATE="/var/www/index.html"
|
|
||||||
FOOTER1="$APP for $ENGINE v$(</etc/version)"
|
|
||||||
FOOTER2="<a href='$SUPPORT'>$SUPPORT</a>"
|
|
||||||
|
|
||||||
SOCKETS=1
|
|
||||||
CPU=$(cpu)
|
|
||||||
SYS=$(uname -r)
|
|
||||||
HOST=$(hostname -s)
|
|
||||||
KERNEL=$(echo "$SYS" | cut -b 1)
|
|
||||||
MINOR=$(echo "$SYS" | cut -d '.' -f2)
|
|
||||||
ARCH=$(dpkg --print-architecture)
|
|
||||||
CORES=$(grep -c '^processor' /proc/cpuinfo)
|
|
||||||
|
|
||||||
if grep -qi "socket(s)" <<< "$(lscpu)"; then
|
|
||||||
SOCKETS=$(lscpu | grep -m 1 -i 'socket(s)' | awk '{print $2}')
|
|
||||||
[ -z "${SOCKETS##*[!0-9]*}" ] && SOCKETS=1
|
|
||||||
[ "$SOCKETS" -lt "1" ] && SOCKETS=1
|
|
||||||
fi
|
|
||||||
|
|
||||||
CPU_CORES=$(strip "$CPU_CORES")
|
|
||||||
[ -z "$CPU_CORES" ] && CPU_CORES=2
|
|
||||||
[[ "${CPU_CORES,,}" == "max" ]] && CPU_CORES="$CORES"
|
|
||||||
[[ "${CPU_CORES,,}" == "half" ]] && CPU_CORES=$(( CORES / 2 ))
|
|
||||||
[ -z "${CPU_CORES##*[!0-9]*}" ] && error "Invalid amount of CPU_CORES: $CPU_CORES" && exit 15
|
|
||||||
|
|
||||||
[ "$CPU_CORES" -lt "1" ] && CPU_CORES=1
|
|
||||||
if [ "$CPU_CORES" -gt "$CORES" ]; then
|
|
||||||
warn "The amount for CPU_CORES (${CPU_CORES}) exceeds the amount of logical cores available (${CORES}) and will be limited."
|
|
||||||
CPU_CORES="$CORES"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check system
|
|
||||||
|
|
||||||
QEMU_DIR="/run/shm"
|
|
||||||
|
|
||||||
if [ ! -d "/dev/shm" ]; then
|
|
||||||
error "Directory /dev/shm not found!" && exit 14
|
|
||||||
else
|
|
||||||
[ ! -d "$QEMU_DIR" ] && ln -s /dev/shm "$QEMU_DIR"
|
|
||||||
fi
|
|
||||||
|
|
||||||
QEMU_PID="$QEMU_DIR/qemu.pid"
|
|
||||||
|
|
||||||
# Check folder
|
|
||||||
|
|
||||||
if [[ "${STORAGE,,}" != "/storage" ]]; then
|
|
||||||
mkdir -p "$STORAGE"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ! -d "$STORAGE" ]; then
|
|
||||||
error "Storage folder ($STORAGE) not found!" && exit 13
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ! -w "$STORAGE" ]; then
|
|
||||||
msg="Storage folder ($STORAGE) is not writeable!"
|
|
||||||
msg+=" If SELinux is active, you need to add the \":Z\" flag to the bind mount."
|
|
||||||
error "$msg" && exit 13
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check filesystem
|
|
||||||
FS=$(stat -f -c %T "$STORAGE")
|
|
||||||
|
|
||||||
if [[ "${FS,,}" == "ecryptfs" || "${FS,,}" == "tmpfs" ]]; then
|
|
||||||
DISK_IO="threads"
|
|
||||||
DISK_CACHE="writeback"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Read memory
|
|
||||||
RAM_AVAIL=$(free -b | grep -m 1 Mem: | awk '{print $7}')
|
|
||||||
RAM_TOTAL=$(free -b | grep -m 1 Mem: | awk '{print $2}')
|
|
||||||
|
|
||||||
RAM_SPARE=500000000
|
|
||||||
RAM_MINIMUM=136314880
|
|
||||||
|
|
||||||
RAM_SIZE=$(strip "$RAM_SIZE")
|
|
||||||
RAM_SIZE="${RAM_SIZE// /}"
|
|
||||||
[ -z "$RAM_SIZE" ] && RAM_SIZE="2G"
|
|
||||||
|
|
||||||
if [[ "${RAM_SIZE,,}" != "max" && "${RAM_SIZE,,}" != "half" ]]; then
|
|
||||||
|
|
||||||
if [ -z "${RAM_SIZE//[0-9. ]}" ]; then
|
|
||||||
[ "${RAM_SIZE%%.*}" -lt "130" ] && RAM_SIZE="${RAM_SIZE}G" || RAM_SIZE="${RAM_SIZE}M"
|
|
||||||
fi
|
|
||||||
|
|
||||||
RAM_SIZE=$(echo "${RAM_SIZE^^}" | sed 's/MB/M/g;s/GB/G/g;s/TB/T/g')
|
|
||||||
! numfmt --from=iec "$RAM_SIZE" &>/dev/null && error "Invalid RAM_SIZE: $RAM_SIZE" && exit 16
|
|
||||||
wanted=$(numfmt --from=iec "$RAM_SIZE")
|
|
||||||
[ "$wanted" -lt "$RAM_MINIMUM" ] && error "RAM_SIZE is too low: $RAM_SIZE" && exit 16
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Print system info
|
|
||||||
SYS="${SYS/-generic/}"
|
|
||||||
FS="${FS/UNKNOWN //}"
|
|
||||||
FS="${FS/ext2\/ext3/ext4}"
|
|
||||||
FS=$(echo "$FS" | sed 's/[)(]//g')
|
|
||||||
SPACE=$(df --output=avail -B 1 "$STORAGE" | tail -n 1)
|
|
||||||
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
|
|
||||||
|
|
||||||
# Check KVM support
|
|
||||||
|
|
||||||
if [[ "${PLATFORM,,}" == "x64" ]]; then
|
|
||||||
TARGET="amd64"
|
|
||||||
else
|
|
||||||
TARGET="arm64"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if disabled "$KVM"; then
|
|
||||||
warn "KVM acceleration is disabled, this will cause the machine to run about 10 times slower!"
|
|
||||||
else
|
|
||||||
if [[ "${ARCH,,}" != "$TARGET" ]]; then
|
|
||||||
KVM="N"
|
|
||||||
warn "your CPU architecture is ${ARCH^^} and cannot provide KVM acceleration for ${PLATFORM^^} instructions, so the machine will run about 10 times slower."
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! disabled "$KVM"; then
|
|
||||||
|
|
||||||
KVM_ERR=""
|
|
||||||
|
|
||||||
if [ ! -e /dev/kvm ]; then
|
|
||||||
KVM_ERR="(/dev/kvm is missing)"
|
|
||||||
else
|
|
||||||
if ! sh -c 'echo -n > /dev/kvm' &> /dev/null; then
|
|
||||||
KVM_ERR="(/dev/kvm is unwriteable)"
|
|
||||||
else
|
|
||||||
if [[ "${PLATFORM,,}" == "x64" ]]; then
|
|
||||||
flags=$(sed -ne '/^flags/s/^.*: //p' /proc/cpuinfo)
|
|
||||||
if ! grep -qw "vmx\|svm" <<< "$flags"; then
|
|
||||||
KVM_ERR="(not enabled in BIOS)"
|
|
||||||
fi
|
|
||||||
if ! grep -qw "sse4_2" <<< "$flags"; then
|
|
||||||
error "Your CPU does not have the SSE4 instruction set that Virtual DSM requires!"
|
|
||||||
! enabled "$DEBUG" && exit 88
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "$KVM_ERR" ]; then
|
|
||||||
KVM="N"
|
|
||||||
if [[ "$OSTYPE" =~ ^darwin ]]; then
|
|
||||||
warn "you are using macOS which has no KVM support, so the machine will run about 10 times slower."
|
|
||||||
else
|
|
||||||
kernel=$(uname -a)
|
|
||||||
case "${kernel,,}" in
|
|
||||||
*"microsoft"* )
|
|
||||||
error "Please bind '/dev/kvm' as a volume in the optional container settings when using Docker Desktop." ;;
|
|
||||||
*"synology"* )
|
|
||||||
error "Please make sure that Synology VMM (Virtual Machine Manager) is installed and that '/dev/kvm' is binded to this container." ;;
|
|
||||||
*)
|
|
||||||
error "KVM acceleration is not available $KVM_ERR, this will cause the machine to run about 10 times slower."
|
|
||||||
error "See the FAQ for possible causes, or disable acceleration by adding the \"KVM=N\" variable (not recommended)." ;;
|
|
||||||
esac
|
|
||||||
! enabled "$DEBUG" && exit 88
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Cleanup files
|
|
||||||
rm -f "$QEMU_DIR"/dsm.url
|
|
||||||
rm -f "$QEMU_DIR"/qemu.* "$QEMU_DIR"/*.pid "$QEMU_DIR"/*.sock
|
|
||||||
|
|
||||||
# Cleanup dirs
|
|
||||||
rm -rf /tmp/dsm
|
|
||||||
rm -rf "$STORAGE/tmp"
|
|
||||||
|
|
||||||
return 0
|
|
||||||
+139
-39
@@ -4,6 +4,7 @@ set -Eeuo pipefail
|
|||||||
# Docker environment variables
|
# Docker environment variables
|
||||||
|
|
||||||
: "${HOST_MAC:=""}"
|
: "${HOST_MAC:=""}"
|
||||||
|
: "${HOST_CPU:=""}"
|
||||||
: "${HOST_DEBUG:=""}"
|
: "${HOST_DEBUG:=""}"
|
||||||
: "${HOST_MODEL:=""}"
|
: "${HOST_MODEL:=""}"
|
||||||
: "${HOST_SERIAL:=""}"
|
: "${HOST_SERIAL:=""}"
|
||||||
@@ -11,68 +12,167 @@ set -Eeuo pipefail
|
|||||||
|
|
||||||
# Sanitize variables
|
# Sanitize variables
|
||||||
HOST_MAC=$(strip "$HOST_MAC")
|
HOST_MAC=$(strip "$HOST_MAC")
|
||||||
|
HOST_CPU=$(strip "$HOST_CPU")
|
||||||
HOST_MODEL=$(strip "$HOST_MODEL")
|
HOST_MODEL=$(strip "$HOST_MODEL")
|
||||||
HOST_SERIAL=$(strip "$HOST_SERIAL")
|
HOST_SERIAL=$(strip "$HOST_SERIAL")
|
||||||
GUEST_SERIAL=$(strip "$GUEST_SERIAL")
|
GUEST_SERIAL=$(strip "$GUEST_SERIAL")
|
||||||
|
|
||||||
if [ -n "$HOST_MAC" ]; then
|
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() {
|
||||||
|
|
||||||
|
if [ -z "$HOST_MAC" ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
HOST_MAC="${HOST_MAC//-/:}"
|
HOST_MAC="${HOST_MAC//-/:}"
|
||||||
|
|
||||||
if [[ ${#HOST_MAC} == 12 ]]; then
|
if [[ ${#HOST_MAC} == 12 ]]; then
|
||||||
m="$HOST_MAC"
|
local m="$HOST_MAC"
|
||||||
HOST_MAC="${m:0:2}:${m:2:2}:${m:4:2}:${m:6:2}:${m:8:2}:${m:10:2}"
|
HOST_MAC="${m:0:2}:${m:2:2}:${m:4:2}:${m:6:2}:${m:8:2}:${m:10:2}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ${#HOST_MAC} != 17 ]]; then
|
if [[ ${#HOST_MAC} != 17 ]]; then
|
||||||
error "Invalid HOST_MAC address: '$HOST_MAC', should be 12 or 17 digits long!" && exit 28
|
error "Invalid HOST_MAC address: '$HOST_MAC', should be 12 or 17 digits long!"
|
||||||
|
exit 28
|
||||||
fi
|
fi
|
||||||
|
|
||||||
fi
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
HOST_PID="$QEMU_DIR/host.pid"
|
configureHostCpuName() {
|
||||||
|
|
||||||
HOST_ARGS=()
|
if [ -z "$HOST_CPU" ]; then
|
||||||
HOST_ARGS+=("-cpu=$CPU_CORES")
|
[[ "${CPU,,}" != "unknown" ]] && HOST_CPU="$CPU"
|
||||||
HOST_ARGS+=("-cpu_arch=$HOST_CPU")
|
fi
|
||||||
|
|
||||||
[ -n "$HOST_MAC" ] && HOST_ARGS+=("-mac=$HOST_MAC")
|
if [ -n "$HOST_CPU" ]; then
|
||||||
[ -n "$HOST_MODEL" ] && HOST_ARGS+=("-model=$HOST_MODEL")
|
# qemu-host expects a comma-separated CPU description with empty family
|
||||||
[ -n "$HOST_SERIAL" ] && HOST_ARGS+=("-hostsn=$HOST_SERIAL")
|
# and suffix fields, not QEMU's -cpu syntax.
|
||||||
[ -n "$GUEST_SERIAL" ] && HOST_ARGS+=("-guestsn=$GUEST_SERIAL")
|
HOST_CPU="${HOST_CPU%%,*},,"
|
||||||
|
else
|
||||||
|
HOST_CPU="QEMU, Virtual CPU,"
|
||||||
|
if [ "$ARCH" == "amd64" ]; then
|
||||||
|
HOST_CPU+=" X86_64"
|
||||||
|
else
|
||||||
|
HOST_CPU+=" $ARCH"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
if enabled "$HOST_DEBUG"; then
|
return 0
|
||||||
set -x
|
}
|
||||||
./host.bin "${HOST_ARGS[@]}" &
|
|
||||||
{ set +x; } 2>/dev/null
|
|
||||||
echo "$!" > "$HOST_PID"
|
|
||||||
echo
|
|
||||||
else
|
|
||||||
./host.bin "${HOST_ARGS[@]}" >/dev/null &
|
|
||||||
echo "$!" > "$HOST_PID"
|
|
||||||
fi
|
|
||||||
|
|
||||||
cnt=0
|
buildHostArguments() {
|
||||||
sleep 0.2
|
|
||||||
|
|
||||||
while ! nc -z -w2 127.0.0.1 "$COM_PORT" > /dev/null 2>&1; do
|
# qemu-host is a sidecar that bridges DSM's proprietary serial agent to
|
||||||
sleep 0.1
|
# Unix sockets used by shutdown and post-boot discovery helpers.
|
||||||
cnt=$((cnt + 1))
|
HOST_ARGS=()
|
||||||
(( cnt > 50 )) && error "Failed to connect to qemu-host.." && exit 58
|
HOST_ARGS+=("-cpu=$CPU_CORES")
|
||||||
done
|
HOST_ARGS+=("-cpu_arch=$HOST_CPU")
|
||||||
|
HOST_ARGS+=("-api=$HOST_API_SOCKET")
|
||||||
|
HOST_ARGS+=("-addr=$HOST_AGENT_SOCKET")
|
||||||
|
|
||||||
cnt=0
|
[ -n "$HOST_MAC" ] && HOST_ARGS+=("-mac=$HOST_MAC")
|
||||||
|
[ -n "$HOST_MODEL" ] && HOST_ARGS+=("-model=$HOST_MODEL")
|
||||||
|
[ -n "$HOST_SERIAL" ] && HOST_ARGS+=("-hostsn=$HOST_SERIAL")
|
||||||
|
[ -n "$GUEST_SERIAL" ] && HOST_ARGS+=("-guestsn=$GUEST_SERIAL")
|
||||||
|
|
||||||
while ! nc -z -w2 127.0.0.1 "$CHR_PORT" > /dev/null 2>&1; do
|
return 0
|
||||||
sleep 0.1
|
}
|
||||||
cnt=$((cnt + 1))
|
|
||||||
(( cnt > 50 )) && error "Failed to connect to qemu-host.." && exit 59
|
|
||||||
done
|
|
||||||
|
|
||||||
# Configure serial ports
|
startHostBinary() {
|
||||||
SERIAL_OPTS="-serial mon:stdio \
|
|
||||||
-device virtio-serial-pci,id=virtio-serial0,bus=pcie.0,addr=0x3 \
|
local pid
|
||||||
-chardev socket,id=charchannel0,host=127.0.0.1,port=$CHR_PORT,reconnect=10 \
|
|
||||||
|
# Remove stale sockets and pid state before starting the sidecar; a Unix
|
||||||
|
# socket path cannot be rebound while an old filesystem entry remains.
|
||||||
|
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
|
||||||
|
pid=$!
|
||||||
|
echo
|
||||||
|
else
|
||||||
|
./host.bin "${HOST_ARGS[@]}" >/dev/null &
|
||||||
|
pid=$!
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s\n' "$pid" > "$HOST_PID"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
waitForSocket() {
|
||||||
|
|
||||||
|
local socket="$1"
|
||||||
|
local exit_code="$2"
|
||||||
|
local timeout=5 pid
|
||||||
|
local deadline=$((SECONDS + timeout))
|
||||||
|
|
||||||
|
# Do not start QEMU until both sidecar sockets are ready; otherwise the
|
||||||
|
# VirtIO serial channel or API client may race initial creation.
|
||||||
|
while [ ! -S "$socket" ]; do
|
||||||
|
|
||||||
|
if ! readPidFile pid "$HOST_PID" || ! isAlive "$pid"; then
|
||||||
|
error "qemu-host exited unexpectedly!"
|
||||||
|
exit "$exit_code"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if (( SECONDS >= deadline )); then
|
||||||
|
error "Failed to create qemu-host socket: $socket"
|
||||||
|
exit "$exit_code"
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep 0.1
|
||||||
|
done
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
configureSerialPorts() {
|
||||||
|
|
||||||
|
local bus
|
||||||
|
bus=$(getPciBus)
|
||||||
|
|
||||||
|
# Managed interactive mode separates the console and QEMU monitor into
|
||||||
|
# reconnecting sockets; other runs keep the simple combined stdio monitor.
|
||||||
|
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=$bus,addr=0x3 \
|
||||||
|
-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"
|
-device virtserialport,bus=virtio-serial0.0,nr=1,chardev=charchannel0,id=channel0,name=vchannel"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
validateHostMac
|
||||||
|
configureHostCpuName
|
||||||
|
|
||||||
|
buildHostArguments
|
||||||
|
startHostBinary
|
||||||
|
|
||||||
|
waitForSocket "$HOST_API_SOCKET" 58
|
||||||
|
waitForSocket "$HOST_AGENT_SOCKET" 59
|
||||||
|
|
||||||
|
configureSerialPorts
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
#!/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_PID="$QEMU_DIR/websocketd.pid"
|
|
||||||
|
|
||||||
cp -r /var/www/* "$QEMU_DIR"
|
|
||||||
rm -f "$WSD_PID" "$WEB_PID"
|
|
||||||
|
|
||||||
html "Starting $APP for $ENGINE..."
|
|
||||||
|
|
||||||
if ! disabled "${WEB:-}"; then
|
|
||||||
|
|
||||||
mkdir -p /etc/nginx/sites-enabled
|
|
||||||
cp /etc/nginx/default.conf /etc/nginx/sites-enabled/web.conf
|
|
||||||
|
|
||||||
sed -i "s/listen 5000 default_server;/listen $WEB_PORT default_server;/g" /etc/nginx/sites-enabled/web.conf
|
|
||||||
sed -i "s/proxy_pass http:\/\/127.0.0.1:8004\/;/proxy_pass http:\/\/127.0.0.1:$WSD_PORT\/;/g" /etc/nginx/sites-enabled/web.conf
|
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Start webserver
|
|
||||||
nginx -e stderr
|
|
||||||
|
|
||||||
# Start websocket server
|
|
||||||
websocketd --address 127.0.0.1 --port="$WSD_PORT" /run/socket.sh >/var/log/websocketd.log &
|
|
||||||
echo "$!" > "$WSD_PID"
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
return 0
|
|
||||||
+52
-12
@@ -2,14 +2,20 @@
|
|||||||
set -Eeuo pipefail
|
set -Eeuo pipefail
|
||||||
|
|
||||||
lastmsg=""
|
lastmsg=""
|
||||||
path="/run/shm/msg.html"
|
lastcmd=""
|
||||||
|
status="/run/shm/msg.html"
|
||||||
|
command="/run/shm/status.cmd"
|
||||||
|
dir=$(dirname -- "$status")
|
||||||
|
status_name=$(basename -- "$status")
|
||||||
|
command_name=$(basename -- "$command")
|
||||||
|
|
||||||
refresh() {
|
# websocketd clients use s: for status updates and c: for control commands.
|
||||||
|
refreshStatus() {
|
||||||
|
|
||||||
[ ! -f "$path" ] && return 0
|
[ ! -f "$status" ] && return 0
|
||||||
[ ! -s "$path" ] && return 0
|
[ ! -s "$status" ] && return 0
|
||||||
|
|
||||||
msg=$(< "$path")
|
msg=$(< "$status") || return 0
|
||||||
msg="${msg%$'\n'}"
|
msg="${msg%$'\n'}"
|
||||||
|
|
||||||
[ -z "$msg" ] && return 0
|
[ -z "$msg" ] && return 0
|
||||||
@@ -17,15 +23,49 @@ refresh() {
|
|||||||
|
|
||||||
lastmsg="$msg"
|
lastmsg="$msg"
|
||||||
echo "s: $msg"
|
echo "s: $msg"
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
refresh
|
refreshCommand() {
|
||||||
|
|
||||||
|
[ ! -f "$command" ] && return 0
|
||||||
|
[ ! -s "$command" ] && return 0
|
||||||
|
|
||||||
|
cmd=$(< "$command") || return 0
|
||||||
|
cmd="${cmd%$'\n'}"
|
||||||
|
|
||||||
|
[ -z "$cmd" ] && return 0
|
||||||
|
[[ "$cmd" == "$lastcmd" ]] && return 0
|
||||||
|
|
||||||
|
lastcmd="$cmd"
|
||||||
|
echo "c: $cmd"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshStatus
|
||||||
|
refreshCommand
|
||||||
|
|
||||||
|
# Watch the directory because status and command updates use atomic rename.
|
||||||
|
inotifywait \
|
||||||
|
-m -q \
|
||||||
|
-e close_write,moved_to \
|
||||||
|
--format '%e %f' \
|
||||||
|
"$dir" |
|
||||||
|
while read -r event file; do
|
||||||
|
|
||||||
|
case "$file" in
|
||||||
|
"$status_name" )
|
||||||
|
case "${event,,}" in
|
||||||
|
"close_write"* | "moved_to"* )
|
||||||
|
refreshStatus ;;
|
||||||
|
esac ;;
|
||||||
|
"$command_name" )
|
||||||
|
case "${event,,}" in
|
||||||
|
"close_write"* | "moved_to"* )
|
||||||
|
refreshCommand ;;
|
||||||
|
esac ;;
|
||||||
|
esac
|
||||||
|
|
||||||
inotifywait -m "$path" |
|
|
||||||
while read -r fp event fn; do
|
|
||||||
case "${event,,}" in
|
|
||||||
"modify"* ) refresh ;;
|
|
||||||
"delete_self" ) echo "c: vnc" ;;
|
|
||||||
esac
|
|
||||||
done
|
done
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -Eeuo pipefail
|
|
||||||
|
|
||||||
# You can override this hook to execute a script before startup!
|
|
||||||
|
|
||||||
return 0
|
|
||||||
-370
@@ -1,370 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -Eeuo pipefail
|
|
||||||
|
|
||||||
# Helper functions
|
|
||||||
|
|
||||||
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; }
|
|
||||||
|
|
||||||
strip() {
|
|
||||||
local value="${1:-}"
|
|
||||||
|
|
||||||
# Remove surrounding whitespace
|
|
||||||
value="${value#"${value%%[![:space:]]*}"}"
|
|
||||||
value="${value%"${value##*[![:space:]]}"}"
|
|
||||||
|
|
||||||
# Remove leading/trailing single/double quotes
|
|
||||||
value="${value%\"}"
|
|
||||||
value="${value#\"}"
|
|
||||||
value="${value%\'}"
|
|
||||||
value="${value#\'}"
|
|
||||||
|
|
||||||
# Remove surrounding whitespace again
|
|
||||||
value="${value#"${value%%[![:space:]]*}"}"
|
|
||||||
value="${value%"${value##*[![:space:]]}"}"
|
|
||||||
|
|
||||||
printf '%s' "$value"
|
|
||||||
}
|
|
||||||
|
|
||||||
enabled() {
|
|
||||||
case "$(strip "${1:-}")" in
|
|
||||||
Y|y|YES|Yes|yes|TRUE|True|true|1|ON|On|on) return 0 ;;
|
|
||||||
*) return 1 ;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
disabled() {
|
|
||||||
case "$(strip "${1:-}")" in
|
|
||||||
N|n|NO|No|no|FALSE|False|false|0|OFF|Off|off) 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;')
|
|
||||||
local unit="${result//[0-9. ]}"
|
|
||||||
result="${result//[a-zA-Z ]/}"
|
|
||||||
if [[ "${2:-}" == "up" ]]; then
|
|
||||||
if [[ "$result" == *"."* ]]; then
|
|
||||||
result="${result%%.*}"
|
|
||||||
result=$((result+1))
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
if [[ "${2:-}" == "down" ]]; then
|
|
||||||
result="${result%%.*}"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
echo "$result $unit"
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
isAlive() {
|
|
||||||
local pid="$1"
|
|
||||||
[ -z "$pid" ] && return 1
|
|
||||||
|
|
||||||
if kill -0 "$pid" 2>/dev/null; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
waitPid() {
|
|
||||||
local i=0
|
|
||||||
local pid="$1"
|
|
||||||
local timeout="${2:-10}"
|
|
||||||
|
|
||||||
while [ -n "$pid" ] && isAlive "$pid"; do
|
|
||||||
sleep 0.2
|
|
||||||
i=$((i + 1))
|
|
||||||
(( i >= timeout * 5 )) && return 1
|
|
||||||
done
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
waitPidFile() {
|
|
||||||
local i=0
|
|
||||||
local pid=""
|
|
||||||
local file="$1"
|
|
||||||
local timeout="${2:-10}"
|
|
||||||
|
|
||||||
[ ! -s "$file" ] && return 0
|
|
||||||
! read -r pid <"$file" && return 0
|
|
||||||
[ -z "$pid" ] && return 0
|
|
||||||
|
|
||||||
while [ -s "$file" ] && isAlive "$pid"; do
|
|
||||||
sleep 0.2
|
|
||||||
i=$((i + 1))
|
|
||||||
(( i >= timeout * 5 )) && return 1
|
|
||||||
done
|
|
||||||
|
|
||||||
rm -f -- "$file"
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
pKill() {
|
|
||||||
local pid="$1"
|
|
||||||
local timeout="${2:-10}"
|
|
||||||
|
|
||||||
{ kill -15 -- "$pid" || :; } 2>/dev/null
|
|
||||||
|
|
||||||
if ! waitPid "$pid" "$timeout"; then
|
|
||||||
warn "Timed out while waiting for PID $pid"
|
|
||||||
fi
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
fWait() {
|
|
||||||
local i=0
|
|
||||||
local name="$1"
|
|
||||||
local timeout="${2:-10}"
|
|
||||||
|
|
||||||
[ -z "$name" ] && return 0
|
|
||||||
|
|
||||||
while pgrep -f -l "$name" >/dev/null; do
|
|
||||||
sleep 0.2
|
|
||||||
i=$((i + 1))
|
|
||||||
if (( i >= timeout * 5 )); then
|
|
||||||
warn "Timed out while waiting for process: $name"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
fKill() {
|
|
||||||
local name="$1"
|
|
||||||
local timeout="${2:-10}"
|
|
||||||
|
|
||||||
[ -z "$name" ] && return 0
|
|
||||||
|
|
||||||
{ pkill -f "$name" || :; } 2>/dev/null
|
|
||||||
fWait "$name" "$timeout"
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
sKill() {
|
|
||||||
local pid=""
|
|
||||||
local file="$1"
|
|
||||||
|
|
||||||
[ ! -s "$file" ] && return 0
|
|
||||||
! read -r pid <"$file" && return 0
|
|
||||||
[ -z "$pid" ] && return 0
|
|
||||||
|
|
||||||
if isAlive "$pid"; then
|
|
||||||
{ kill -15 -- "$pid" || :; } 2>/dev/null
|
|
||||||
fi
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
mKill() {
|
|
||||||
local timeout=10
|
|
||||||
local files=("$@")
|
|
||||||
|
|
||||||
for file in "${files[@]}"; do
|
|
||||||
sKill "$file"
|
|
||||||
done
|
|
||||||
|
|
||||||
for file in "${files[@]}"; do
|
|
||||||
if ! waitPidFile "$file" "$timeout"; then
|
|
||||||
warn "Timed out while waiting for PID file: $file"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
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")
|
|
||||||
|
|
||||||
! chown "$uid:$gid" "$file" && return 1
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
makeDir() {
|
|
||||||
local path="$1"
|
|
||||||
local dir uid gid
|
|
||||||
|
|
||||||
[ -d "$path" ] && return 0
|
|
||||||
! mkdir -p "$path" && return 1
|
|
||||||
|
|
||||||
dir=$(dirname -- "$path")
|
|
||||||
uid=$(stat -c '%u' "$dir")
|
|
||||||
gid=$(stat -c '%g' "$dir")
|
|
||||||
|
|
||||||
! chown "$uid:$gid" "$path" && return 1
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
escape () {
|
|
||||||
local s
|
|
||||||
s=${1//&/\&}
|
|
||||||
s=${s//</\<}
|
|
||||||
s=${s//>/\>}
|
|
||||||
s=${s//'"'/\"}
|
|
||||||
printf -- %s "$s"
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
html() {
|
|
||||||
local title
|
|
||||||
local body
|
|
||||||
local script
|
|
||||||
local footer
|
|
||||||
|
|
||||||
title=$(escape "$APP")
|
|
||||||
title="<title>$title</title>"
|
|
||||||
footer=$(escape "$FOOTER1")
|
|
||||||
|
|
||||||
body=$(escape "$1")
|
|
||||||
if [[ "$body" == *"..." ]]; then
|
|
||||||
body="<p class=\"loading\">${body/.../}</p>"
|
|
||||||
fi
|
|
||||||
|
|
||||||
[ -n "${2:-}" ] && script="$2" || script=""
|
|
||||||
|
|
||||||
local HTML
|
|
||||||
HTML=$(<"$TEMPLATE")
|
|
||||||
HTML="${HTML/\[1\]/$title}"
|
|
||||||
HTML="${HTML/\[2\]/$script}"
|
|
||||||
HTML="${HTML/\[3\]/$body}"
|
|
||||||
HTML="${HTML/\[4\]/$footer}"
|
|
||||||
HTML="${HTML/\[5\]/$FOOTER2}"
|
|
||||||
|
|
||||||
echo "$HTML" > "$PAGE"
|
|
||||||
echo "$body" > "$INFO"
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
cpu() {
|
|
||||||
local ret
|
|
||||||
local cpu=""
|
|
||||||
|
|
||||||
ret=$(lscpu)
|
|
||||||
|
|
||||||
if grep -qi "model name" <<< "$ret"; then
|
|
||||||
cpu=$(echo "$ret" | grep -m 1 -i 'model name' | cut -f 2 -d ":" | awk '{$1=$1}1' | sed 's# @.*##g' | sed s/"(R)"//g | sed 's/[^[:alnum:] ]\+/ /g' | sed 's/ */ /g')
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "${cpu// /}" ] && grep -qi "model:" <<< "$ret"; then
|
|
||||||
cpu=$(echo "$ret" | grep -m 1 -i 'model:' | cut -f 2 -d ":" | awk '{$1=$1}1' | sed 's# @.*##g' | sed s/"(R)"//g | sed 's/[^[:alnum:] ]\+/ /g' | sed 's/ */ /g')
|
|
||||||
fi
|
|
||||||
|
|
||||||
cpu="${cpu// CPU/}"
|
|
||||||
cpu="${cpu// [0-9][0-9][0-9] Core}"
|
|
||||||
cpu="${cpu// [0-9][0-9] Core}"
|
|
||||||
cpu="${cpu// [0-9] Core}"
|
|
||||||
cpu="${cpu//[0-9][0-9]th Gen }"
|
|
||||||
cpu="${cpu//[0-9]th Gen }"
|
|
||||||
cpu="${cpu// Processor/}"
|
|
||||||
cpu="${cpu// Quad core/}"
|
|
||||||
cpu="${cpu// Dual core/}"
|
|
||||||
cpu="${cpu// Octa core/}"
|
|
||||||
cpu="${cpu// Hexa core/}"
|
|
||||||
cpu="${cpu// Core TM/ Core}"
|
|
||||||
cpu="${cpu// with Radeon Graphics/}"
|
|
||||||
cpu="${cpu// with Radeon Vega Graphics/}"
|
|
||||||
cpu="${cpu// with Radeon Vega Mobile Gfx/}"
|
|
||||||
cpu="${cpu// w Radeon [0-9][0-9][0-9]M Graphics/}"
|
|
||||||
|
|
||||||
[ -z "${cpu// /}" ] && cpu="Unknown"
|
|
||||||
|
|
||||||
echo "$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
|
|
||||||
|
|
||||||
{ json=$(curl -m 5 -H "Accept: application/json" -sfk "$url"); rc=$?; } || :
|
|
||||||
(( rc != 0 )) && return 0
|
|
||||||
|
|
||||||
{ result=$(echo "$json" | jq -r "$query" 2> /dev/null); rc=$?; } || :
|
|
||||||
(( rc != 0 )) && return 0
|
|
||||||
|
|
||||||
[[ ${#result} -ne 2 ]] && return 0
|
|
||||||
[[ "${result^^}" == "XX" ]] && return 0
|
|
||||||
|
|
||||||
COUNTRY="${result^^}"
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
setCountry() {
|
|
||||||
|
|
||||||
[[ "${TZ,,}" == "asia/harbin" ]] && COUNTRY="CN"
|
|
||||||
[[ "${TZ,,}" == "asia/beijing" ]] && COUNTRY="CN"
|
|
||||||
[[ "${TZ,,}" == "asia/urumqi" ]] && COUNTRY="CN"
|
|
||||||
[[ "${TZ,,}" == "asia/kashgar" ]] && COUNTRY="CN"
|
|
||||||
[[ "${TZ,,}" == "asia/shanghai" ]] && COUNTRY="CN"
|
|
||||||
[[ "${TZ,,}" == "asia/chongqing" ]] && COUNTRY="CN"
|
|
||||||
|
|
||||||
[ -z "$COUNTRY" ] && getCountry "https://api.ipapi.is" ".location.country_code"
|
|
||||||
[ -z "$COUNTRY" ] && getCountry "https://ifconfig.co/json" ".country_iso"
|
|
||||||
[ -z "$COUNTRY" ] && getCountry "https://api.ip2location.io" ".country_code"
|
|
||||||
[ -z "$COUNTRY" ] && getCountry "https://ipinfo.io/json" ".country"
|
|
||||||
[ -z "$COUNTRY" ] && getCountry "https://api.ipquery.io/?format=json" ".location.country_code"
|
|
||||||
[ -z "$COUNTRY" ] && getCountry "https://api.myip.com" ".cc"
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
addPackage() {
|
|
||||||
local pkg=$1
|
|
||||||
local desc=$2
|
|
||||||
|
|
||||||
if apt-mark showinstall | grep -qx "$pkg"; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
MSG="Installing $desc..."
|
|
||||||
info "$MSG" && html "$MSG"
|
|
||||||
|
|
||||||
[ -z "$COUNTRY" ] && setCountry
|
|
||||||
|
|
||||||
if [[ "${COUNTRY^^}" == "CN" ]]; then
|
|
||||||
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
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0
|
|
||||||
+13
-3
@@ -1,4 +1,5 @@
|
|||||||
server {
|
server {
|
||||||
|
|
||||||
listen 5000 default_server;
|
listen 5000 default_server;
|
||||||
|
|
||||||
autoindex on;
|
autoindex on;
|
||||||
@@ -9,6 +10,9 @@ server {
|
|||||||
error_log /dev/null;
|
error_log /dev/null;
|
||||||
access_log /dev/null;
|
access_log /dev/null;
|
||||||
|
|
||||||
|
auth_basic off;
|
||||||
|
auth_basic_user_file /etc/nginx/.htpasswd;
|
||||||
|
|
||||||
include /etc/nginx/mime.types;
|
include /etc/nginx/mime.types;
|
||||||
|
|
||||||
gzip on;
|
gzip on;
|
||||||
@@ -19,7 +23,13 @@ server {
|
|||||||
gzip_disable "msie6";
|
gzip_disable "msie6";
|
||||||
gzip_types text/css text/javascript text/xml text/plain text/x-component application/javascript application/json application/xml application/rss+xml font/truetype font/opentype application/vnd.ms-fontobject image/svg+xml;
|
gzip_types text/css text/javascript text/xml text/plain text/x-component application/javascript application/json application/xml application/rss+xml font/truetype font/opentype application/vnd.ms-fontobject image/svg+xml;
|
||||||
|
|
||||||
add_header Cache-Control "no-cache";
|
set $cache_control "no-cache";
|
||||||
|
|
||||||
|
if ($uri = /msg.html) {
|
||||||
|
set $cache_control "no-store";
|
||||||
|
}
|
||||||
|
|
||||||
|
add_header Cache-Control $cache_control always;
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
|
|
||||||
@@ -39,6 +49,6 @@ server {
|
|||||||
proxy_read_timeout 3600s;
|
proxy_read_timeout 3600s;
|
||||||
proxy_send_timeout 3600s;
|
proxy_send_timeout 3600s;
|
||||||
|
|
||||||
proxy_pass http://127.0.0.1:8004/;
|
proxy_pass http://unix:/run/shm/status-ws.sock:/;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,167 +0,0 @@
|
|||||||
body {
|
|
||||||
color: white;
|
|
||||||
background-color: #125bdb;
|
|
||||||
font-smoothing: antialiased;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
font-family: Verdana, Geneva, sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
#info {
|
|
||||||
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.25);
|
|
||||||
}
|
|
||||||
|
|
||||||
#content {
|
|
||||||
text-align: center;
|
|
||||||
padding: 20px;
|
|
||||||
margin-top: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer {
|
|
||||||
width: 98%;
|
|
||||||
position: fixed;
|
|
||||||
bottom: 0px;
|
|
||||||
height: 40px;
|
|
||||||
text-align: center;
|
|
||||||
color: #0c8aeb;
|
|
||||||
text-shadow: 0 0 1px #0c8aeb;
|
|
||||||
}
|
|
||||||
|
|
||||||
#empty {
|
|
||||||
height: 40px;
|
|
||||||
/* Same height as footer */
|
|
||||||
}
|
|
||||||
|
|
||||||
a,
|
|
||||||
a:hover,
|
|
||||||
a:active,
|
|
||||||
a:visited {
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer a:link,
|
|
||||||
footer a:visited,
|
|
||||||
footer a:active {
|
|
||||||
color: #0c8aeb;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer a:hover {
|
|
||||||
color: #73e6ff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading:after {
|
|
||||||
content: " .";
|
|
||||||
animation: dots 1s steps(5, end) infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes dots {
|
|
||||||
|
|
||||||
0%,
|
|
||||||
20% {
|
|
||||||
color: rgba(0, 0, 0, 0);
|
|
||||||
text-shadow: 0.25em 0 0 rgba(0, 0, 0, 0), 0.5em 0 0 rgba(0, 0, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
40% {
|
|
||||||
color: white;
|
|
||||||
text-shadow: 0.25em 0 0 rgba(0, 0, 0, 0), 0.5em 0 0 rgba(0, 0, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
60% {
|
|
||||||
text-shadow: 0.25em 0 0 white, 0.5em 0 0 rgba(0, 0, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
80%,
|
|
||||||
100% {
|
|
||||||
text-shadow: 0.25em 0 0 white, 0.5em 0 0 white;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.spinner_LWk7 {
|
|
||||||
animation: spinner_GWy6 1.2s linear infinite, spinner_BNNO 1.2s linear infinite
|
|
||||||
}
|
|
||||||
|
|
||||||
.spinner_yOMU {
|
|
||||||
animation: spinner_GWy6 1.2s linear infinite, spinner_pVqn 1.2s linear infinite;
|
|
||||||
animation-delay: .15s
|
|
||||||
}
|
|
||||||
|
|
||||||
.spinner_KS4S {
|
|
||||||
animation: spinner_GWy6 1.2s linear infinite, spinner_6uKB 1.2s linear infinite;
|
|
||||||
animation-delay: .3s
|
|
||||||
}
|
|
||||||
|
|
||||||
.spinner_zVee {
|
|
||||||
animation: spinner_GWy6 1.2s linear infinite, spinner_Qw4x 1.2s linear infinite;
|
|
||||||
animation-delay: .45s
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spinner_GWy6 {
|
|
||||||
|
|
||||||
0%,
|
|
||||||
50% {
|
|
||||||
width: 9px;
|
|
||||||
height: 9px
|
|
||||||
}
|
|
||||||
|
|
||||||
10% {
|
|
||||||
width: 11px;
|
|
||||||
height: 11px
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spinner_BNNO {
|
|
||||||
|
|
||||||
0%,
|
|
||||||
50% {
|
|
||||||
x: 1.5px;
|
|
||||||
y: 1.5px
|
|
||||||
}
|
|
||||||
|
|
||||||
10% {
|
|
||||||
x: .5px;
|
|
||||||
y: .5px
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spinner_pVqn {
|
|
||||||
|
|
||||||
0%,
|
|
||||||
50% {
|
|
||||||
x: 13.5px;
|
|
||||||
y: 1.5px
|
|
||||||
}
|
|
||||||
|
|
||||||
10% {
|
|
||||||
x: 12.5px;
|
|
||||||
y: .5px
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spinner_6uKB {
|
|
||||||
|
|
||||||
0%,
|
|
||||||
50% {
|
|
||||||
x: 13.5px;
|
|
||||||
y: 13.5px
|
|
||||||
}
|
|
||||||
|
|
||||||
10% {
|
|
||||||
x: 12.5px;
|
|
||||||
y: 12.5px
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spinner_Qw4x {
|
|
||||||
|
|
||||||
0%,
|
|
||||||
50% {
|
|
||||||
x: 1.5px;
|
|
||||||
y: 13.5px
|
|
||||||
}
|
|
||||||
|
|
||||||
10% {
|
|
||||||
x: .5px;
|
|
||||||
y: 12.5px
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
<svg id="Capa_1" enable-background="new 0 0 511.962 511.962" height="512" viewBox="0 0 511.962 511.962" width="512" xmlns="http://www.w3.org/2000/svg"><g><path d="m489.965 120.063c0-5.77-3.31-11.028-8.512-13.524l-218.984-105.063c-4.102-1.967-8.875-1.967-12.977 0l-218.985 105.063c-5.202 2.496-8.511 7.755-8.511 13.524l-.003 271.834c0 5.77 3.31 11.028 8.512 13.524l218.989 105.064c2.051.983 4.27 1.476 6.488 1.476 2.219 0 4.438-.492 6.488-1.476l218.989-105.064c5.202-2.496 8.512-7.755 8.512-13.524z" fill="#4e6ba6"/><path d="m489.965 120.063c0-5.77-3.31-11.028-8.512-13.524l-218.984-105.063c-2.051-.984-4.269-1.476-6.488-1.476v511.962c2.219 0 4.438-.492 6.488-1.476l218.989-105.064c5.202-2.496 8.512-7.755 8.512-13.524z" fill="#28487a"/><path d="m425.812 160.441c0-2.27-.519-4.457-1.457-6.432l-336.701-.095c-.967 1.999-1.504 4.22-1.504 6.526l-.002 191.081c0 5.769 3.31 11.028 8.512 13.524l154.833 74.285c2.051.983 4.27 1.476 6.488 1.476 2.219 0 4.438-.492 6.488-1.476l154.834-74.285c5.202-2.496 8.512-7.755 8.512-13.524z" fill="#8dc2eb"/><path d="m424.354 154.009h-168.373v286.798c2.219 0 4.438-.492 6.488-1.476l154.834-74.285c5.202-2.496 8.512-7.755 8.512-13.524l-.003-191.081c0-2.27-.52-4.458-1.458-6.432z" fill="#5e9ff6"/><path d="m417.3 146.916-154.831-74.284c-4.102-1.967-8.875-1.967-12.977 0l-154.831 74.284c-3.122 1.498-5.555 3.996-7.007 6.998l168.328 80.812 168.374-80.717c-1.448-3.044-3.9-5.579-7.056-7.093z" fill="#ecf9fd"/><path d="m417.3 146.916-154.831-74.284c-2.051-.983-4.27-1.476-6.488-1.476v163.569l168.374-80.717c-1.447-3.043-3.899-5.578-7.055-7.092z" fill="#d9f3fc"/></g></svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.6 KiB |
@@ -1,34 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
[1]
|
|
||||||
<meta http-equiv="Cache-Control" content="no-cache" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<link rel="stylesheet" type="text/css" href="css/style.css" />
|
|
||||||
<link rel="icon" href="img/favicon.svg" type="image/x-icon">
|
|
||||||
[2]
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div id="page">
|
|
||||||
<div id="content">
|
|
||||||
<svg id="spinner" width="64" height="64" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<rect class="spinner_LWk7" fill="#0c8aeb" x="1.5" y="1.5" rx="1" width="9" height="9"/>
|
|
||||||
<rect class="spinner_yOMU" fill="#0c8aeb" x="13.5" y="1.5" rx="1" width="9" height="9"/>
|
|
||||||
<rect class="spinner_KS4S" fill="#0c8aeb" x="13.5" y="13.5" rx="1" width="9" height="9"/>
|
|
||||||
<rect class="spinner_zVee" fill="#0c8aeb" x="1.5" y="13.5" rx="1" width="9" height="9"/>
|
|
||||||
</svg>
|
|
||||||
<h1 id="info">[3]</h1>
|
|
||||||
</div>
|
|
||||||
<div id="empty">
|
|
||||||
</div>
|
|
||||||
<footer id="footer">
|
|
||||||
[4]<br />
|
|
||||||
[5]
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
<script type="text/javascript" src="js/script.js"></script>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
+516
-94
@@ -1,15 +1,90 @@
|
|||||||
|
var timer;
|
||||||
var request;
|
var request;
|
||||||
|
var failureTimer;
|
||||||
|
var navigationTimer;
|
||||||
var booting = false;
|
var booting = false;
|
||||||
|
var portal = false;
|
||||||
|
var portalUrl = "";
|
||||||
var interval = 1000;
|
var interval = 1000;
|
||||||
|
var lastStatus = "";
|
||||||
|
|
||||||
|
function abortRequest() {
|
||||||
|
|
||||||
|
if (!request) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
request.onreadystatechange = null;
|
||||||
|
request.abort();
|
||||||
|
request = null;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearFailure() {
|
||||||
|
|
||||||
|
if (!failureTimer) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearTimeout(failureTimer);
|
||||||
|
failureTimer = null;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function connectionLost() {
|
||||||
|
|
||||||
|
if (booting || (portal && portalUrl.length == 0) ||
|
||||||
|
document.hidden || failureTimer) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
failureTimer = setTimeout(function() {
|
||||||
|
|
||||||
|
if (booting || (portal && portalUrl.length == 0) ||
|
||||||
|
document.hidden) {
|
||||||
|
failureTimer = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setStopped();
|
||||||
|
}, interval * 3);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearNavigation() {
|
||||||
|
|
||||||
|
if (!navigationTimer) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearTimeout(navigationTimer);
|
||||||
|
navigationTimer = null;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function visibilityChanged() {
|
||||||
|
|
||||||
|
clearFailure();
|
||||||
|
clearNavigation();
|
||||||
|
|
||||||
|
if (document.hidden) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
getInfo();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
function getInfo() {
|
function getInfo() {
|
||||||
|
|
||||||
var url = "msg.html";
|
var url = "msg.html";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (request) {
|
abortRequest();
|
||||||
request.abort();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (window.XMLHttpRequest) {
|
if (window.XMLHttpRequest) {
|
||||||
request = new XMLHttpRequest();
|
request = new XMLHttpRequest();
|
||||||
@@ -34,67 +109,126 @@ function getURL() {
|
|||||||
return protocol + "//" + window.location.host + path;
|
return protocol + "//" + window.location.host + path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function redirect(url) {
|
||||||
|
|
||||||
|
if (document.hidden || navigationTimer) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
navigationTimer = setTimeout(function() {
|
||||||
|
navigationTimer = null;
|
||||||
|
window.location.assign(url);
|
||||||
|
}, 3000);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function beginPortal(url) {
|
||||||
|
|
||||||
|
portal = true;
|
||||||
|
portalUrl = url || "";
|
||||||
|
booting = false;
|
||||||
|
|
||||||
|
clearFailure();
|
||||||
|
setInfo("Connecting to web portal", true);
|
||||||
|
schedule();
|
||||||
|
|
||||||
|
if (portalUrl.length > 0) {
|
||||||
|
redirect(portalUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function processCommand(msg) {
|
||||||
|
|
||||||
|
if (msg == "portal") {
|
||||||
|
return beginPortal("");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.indexOf("portal ") == 0) {
|
||||||
|
return beginPortal(msg.substring(7));
|
||||||
|
}
|
||||||
|
|
||||||
|
console.warn("Unknown command: " + msg);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
function processMsg(msg) {
|
function processMsg(msg) {
|
||||||
|
|
||||||
|
rememberStatus(msg);
|
||||||
|
setInfo(msg);
|
||||||
|
|
||||||
if (msg.toLowerCase().indexOf("href=") !== -1) {
|
if (msg.toLowerCase().indexOf("href=") !== -1) {
|
||||||
var div = document.createElement("div");
|
var div = document.createElement("div");
|
||||||
div.innerHTML = msg;
|
div.innerHTML = msg;
|
||||||
var url = div.querySelector("a").href;
|
return beginPortal(div.querySelector("a").href);
|
||||||
setTimeout(() => {
|
|
||||||
window.location.assign(url);
|
|
||||||
}, 3000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setInfo(msg);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function processInfo() {
|
function processInfo() {
|
||||||
try {
|
|
||||||
|
|
||||||
if (request.readyState != 4) {
|
if (request.readyState != 4) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var msg = request.responseText;
|
var response = request;
|
||||||
if (msg == null || msg.length == 0) {
|
request = null;
|
||||||
|
|
||||||
if (booting) {
|
var status = response.status;
|
||||||
schedule();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
window.location.reload();
|
if (status == 502 || status == 503 || status == 504) {
|
||||||
return false;
|
connectionLost();
|
||||||
}
|
schedule();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
var notFound = (request.status == 404);
|
var msg = response.responseText;
|
||||||
|
if (msg == null || msg.length == 0) {
|
||||||
if (request.status == 200) {
|
connectionLost();
|
||||||
if (msg.toLowerCase().indexOf("<html>") !== -1) {
|
schedule();
|
||||||
notFound = true;
|
|
||||||
} else {
|
|
||||||
processMsg(msg);
|
|
||||||
if (msg.toLowerCase().indexOf("href=") == -1) {
|
|
||||||
schedule();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (notFound) {
|
|
||||||
setInfo("Connecting to web portal", true);
|
|
||||||
reload();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
setError("Error: Received statuscode " + request.status);
|
|
||||||
return false;
|
|
||||||
|
|
||||||
} catch (e) {
|
|
||||||
setError("Error: " + e.message);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var notFound = (status == 404);
|
||||||
|
|
||||||
|
if (status == 200) {
|
||||||
|
if (msg.toLowerCase().indexOf("<html>") !== -1) {
|
||||||
|
notFound = true;
|
||||||
|
} else {
|
||||||
|
clearFailure();
|
||||||
|
processMsg(msg);
|
||||||
|
|
||||||
|
if (portalUrl.length > 0) {
|
||||||
|
setInfo("Connecting to web portal", true);
|
||||||
|
redirect(portalUrl);
|
||||||
|
} else {
|
||||||
|
schedule();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notFound) {
|
||||||
|
clearFailure();
|
||||||
|
booting = false;
|
||||||
|
setInfo("Connecting to web portal", true);
|
||||||
|
|
||||||
|
if (portalUrl.length > 0) {
|
||||||
|
redirect(portalUrl);
|
||||||
|
} else {
|
||||||
|
portal = true;
|
||||||
|
reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
setError("Error: Received statuscode " + status);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractContent(s) {
|
function extractContent(s) {
|
||||||
@@ -103,52 +237,277 @@ function extractContent(s) {
|
|||||||
return span.textContent || span.innerText;
|
return span.textContent || span.innerText;
|
||||||
};
|
};
|
||||||
|
|
||||||
function setInfo(msg, loading, error) {
|
function escapeContent(s) {
|
||||||
try {
|
var span = document.createElement('span');
|
||||||
|
span.textContent = s;
|
||||||
|
return span.innerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
if (msg == null || msg.length == 0) {
|
function rememberStatus(msg) {
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (msg.includes("Booting ")) {
|
var text = extractContent(msg).trim();
|
||||||
booting = true;
|
if (text.length == 0) {
|
||||||
}
|
|
||||||
|
|
||||||
var el = document.getElementById("info");
|
|
||||||
|
|
||||||
if (el.innerText == msg || el.innerHTML == msg) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
var spin = document.getElementById("spinner");
|
|
||||||
|
|
||||||
error = !!error;
|
|
||||||
if (!error) {
|
|
||||||
spin.style.visibility = 'visible';
|
|
||||||
} else {
|
|
||||||
spin.style.visibility = 'hidden';
|
|
||||||
}
|
|
||||||
|
|
||||||
var p = "<p class=\"loading\">";
|
|
||||||
loading = !!loading;
|
|
||||||
if (loading) {
|
|
||||||
msg = p + msg + "</p>";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (msg.includes(p)) {
|
|
||||||
if (el.innerHTML.includes(p)) {
|
|
||||||
el.getElementsByClassName('loading')[0].textContent = extractContent(msg);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
el.innerHTML = msg;
|
|
||||||
return true;
|
|
||||||
|
|
||||||
} catch (e) {
|
|
||||||
console.log("Error: " + e.message);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (text.includes("Booting ")) {
|
||||||
|
booting = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastStatus = text;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseSize(value, unit) {
|
||||||
|
|
||||||
|
var powers = {
|
||||||
|
"B": 0,
|
||||||
|
"KB": 1,
|
||||||
|
"KIB": 1,
|
||||||
|
"MB": 2,
|
||||||
|
"MIB": 2,
|
||||||
|
"GB": 3,
|
||||||
|
"GIB": 3,
|
||||||
|
"TB": 4,
|
||||||
|
"TIB": 4,
|
||||||
|
"PB": 5,
|
||||||
|
"PIB": 5,
|
||||||
|
"EB": 6,
|
||||||
|
"EIB": 6
|
||||||
|
};
|
||||||
|
|
||||||
|
unit = unit.toUpperCase();
|
||||||
|
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(powers, unit)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bytes = Number(value) * Math.pow(1024, powers[unit]);
|
||||||
|
|
||||||
|
if (!Number.isFinite(bytes) || bytes < 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
function estimateProgress(bytes) {
|
||||||
|
|
||||||
|
var boundary = 512 * 1024 * 1024;
|
||||||
|
var previousBoundary = 0;
|
||||||
|
|
||||||
|
while (bytes > boundary) {
|
||||||
|
previousBoundary = boundary;
|
||||||
|
boundary *= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (previousBoundary === 0) {
|
||||||
|
return Math.min(bytes / boundary * 100, 99.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
var rangeProgress =
|
||||||
|
(bytes - previousBoundary) /
|
||||||
|
(boundary - previousBoundary);
|
||||||
|
|
||||||
|
return Math.min(30 + Math.pow(rangeProgress, 2) * 70, 99.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseProgress(msg) {
|
||||||
|
|
||||||
|
var container = document.createElement("div");
|
||||||
|
container.innerHTML = msg;
|
||||||
|
|
||||||
|
var walker = document.createTreeWalker(
|
||||||
|
container,
|
||||||
|
NodeFilter.SHOW_TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
var node;
|
||||||
|
var lastNode = null;
|
||||||
|
|
||||||
|
while ((node = walker.nextNode())) {
|
||||||
|
if (node.nodeValue.trim() !== "") {
|
||||||
|
lastNode = node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!lastNode) {
|
||||||
|
return {
|
||||||
|
message: msg,
|
||||||
|
progress: null,
|
||||||
|
size: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var percentMatch = lastNode.nodeValue.match(
|
||||||
|
/\s+\((\d+(?:\.\d+)?)%\)\s*$/
|
||||||
|
);
|
||||||
|
|
||||||
|
if (percentMatch) {
|
||||||
|
var progress = Number(percentMatch[1]);
|
||||||
|
|
||||||
|
if (Number.isFinite(progress) && progress >= 0 && progress <= 100) {
|
||||||
|
lastNode.nodeValue = lastNode.nodeValue.slice(
|
||||||
|
0,
|
||||||
|
percentMatch.index
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
message: container.innerHTML,
|
||||||
|
progress: progress,
|
||||||
|
size: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var sizeMatch = lastNode.nodeValue.match(
|
||||||
|
/\s+\((\d+(?:\.\d+)?)\s+([KMGTPE]?i?B)\)\s*$/i
|
||||||
|
);
|
||||||
|
|
||||||
|
if (sizeMatch) {
|
||||||
|
var bytes = parseSize(sizeMatch[1], sizeMatch[2]);
|
||||||
|
|
||||||
|
if (bytes != null) {
|
||||||
|
var size = sizeMatch[1] + " " + sizeMatch[2];
|
||||||
|
|
||||||
|
return {
|
||||||
|
message: msg,
|
||||||
|
progress: estimateProgress(bytes),
|
||||||
|
size: size
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
message: msg,
|
||||||
|
progress: null,
|
||||||
|
size: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function resizeProgress() {
|
||||||
|
|
||||||
|
var info = document.getElementById("info");
|
||||||
|
var progress = document.getElementById("progress");
|
||||||
|
|
||||||
|
if (!info || !progress || progress.hidden) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var style = window.getComputedStyle(info);
|
||||||
|
var measurement = document.createElement("span");
|
||||||
|
|
||||||
|
measurement.textContent = info.innerText;
|
||||||
|
measurement.style.position = "fixed";
|
||||||
|
measurement.style.left = "-9999px";
|
||||||
|
measurement.style.top = "-9999px";
|
||||||
|
measurement.style.visibility = "hidden";
|
||||||
|
measurement.style.whiteSpace = "nowrap";
|
||||||
|
measurement.style.fontFamily = style.fontFamily;
|
||||||
|
measurement.style.fontSize = style.fontSize;
|
||||||
|
measurement.style.fontWeight = style.fontWeight;
|
||||||
|
measurement.style.fontStyle = style.fontStyle;
|
||||||
|
measurement.style.letterSpacing = style.letterSpacing;
|
||||||
|
measurement.style.textTransform = style.textTransform;
|
||||||
|
|
||||||
|
document.body.appendChild(measurement);
|
||||||
|
|
||||||
|
var textWidth = measurement.getBoundingClientRect().width;
|
||||||
|
var loading = info.getElementsByClassName("loading").length > 0;
|
||||||
|
var dotsWidth = 0;
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
dotsWidth = parseFloat(style.fontSize) * 0.75;
|
||||||
|
}
|
||||||
|
|
||||||
|
measurement.remove();
|
||||||
|
|
||||||
|
var maximumWidth = window.innerWidth * 0.8;
|
||||||
|
var width = Math.min(textWidth + dotsWidth + 100, maximumWidth);
|
||||||
|
|
||||||
|
progress.style.width = width + "px";
|
||||||
|
progress.style.transform = loading
|
||||||
|
? "translateX(" + (dotsWidth / 2) + "px)"
|
||||||
|
: "";
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setProgress(value, size) {
|
||||||
|
|
||||||
|
var progress = document.getElementById("progress");
|
||||||
|
var fill = document.getElementById("progress-fill");
|
||||||
|
|
||||||
|
if (value == null) {
|
||||||
|
progress.hidden = true;
|
||||||
|
progress.removeAttribute("title");
|
||||||
|
progress.removeAttribute("aria-valuenow");
|
||||||
|
progress.removeAttribute("aria-valuetext");
|
||||||
|
fill.style.width = "0%";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
progress.hidden = false;
|
||||||
|
progress.title = size != null ? size : value + "%";
|
||||||
|
progress.setAttribute("aria-valuenow", value);
|
||||||
|
|
||||||
|
if (size != null) {
|
||||||
|
progress.setAttribute("aria-valuetext", size);
|
||||||
|
} else {
|
||||||
|
progress.removeAttribute("aria-valuetext");
|
||||||
|
}
|
||||||
|
|
||||||
|
fill.style.width = value + "%";
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setInfo(msg, loading, error) {
|
||||||
|
|
||||||
|
if (msg == null || msg.length == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var parsed = parseProgress(msg);
|
||||||
|
msg = parsed.message;
|
||||||
|
setProgress(parsed.progress, parsed.size);
|
||||||
|
|
||||||
|
var el = document.getElementById("info");
|
||||||
|
|
||||||
|
if (el.innerText == msg || el.innerHTML == msg) {
|
||||||
|
var progressEl = document.getElementById("progress");
|
||||||
|
if (progressEl && !progressEl.hidden && !progressEl.style.width) {
|
||||||
|
resizeProgress();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var spin = document.getElementById("spinner");
|
||||||
|
|
||||||
|
error = !!error;
|
||||||
|
if (!error) {
|
||||||
|
spin.style.visibility = 'visible';
|
||||||
|
} else {
|
||||||
|
spin.style.visibility = 'hidden';
|
||||||
|
}
|
||||||
|
|
||||||
|
var p = "<p class=\"loading\">";
|
||||||
|
loading = !!loading;
|
||||||
|
if (loading) {
|
||||||
|
msg = p + msg + "</p>";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.includes(p)) {
|
||||||
|
if (el.innerHTML.includes(p)) {
|
||||||
|
el.getElementsByClassName('loading')[0].textContent = extractContent(msg);
|
||||||
|
resizeProgress();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
el.innerHTML = msg;
|
||||||
|
resizeProgress();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setError(text) {
|
function setError(text) {
|
||||||
@@ -156,14 +515,38 @@ function setError(text) {
|
|||||||
return setInfo(text, false, true);
|
return setInfo(text, false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setStopped() {
|
||||||
|
|
||||||
|
var msg = "<span style=\"display:block\">The container has stopped.</span>";
|
||||||
|
msg += "<span style=\"display:block; margin-top:1em; font-size:0.85em; font-weight:normal\">Check the container log for more details.</span>";
|
||||||
|
|
||||||
|
if (lastStatus.length > 0) {
|
||||||
|
msg += "<span style=\"display:block; margin-top:1em; font-size:0.75em; font-weight:normal; color:rgba(255,255,255,0.65)\">[ Last status: " + escapeContent(lastStatus) + " ]</span>";
|
||||||
|
}
|
||||||
|
|
||||||
|
return setError(msg);
|
||||||
|
}
|
||||||
|
|
||||||
function schedule() {
|
function schedule() {
|
||||||
setTimeout(getInfo, interval);
|
|
||||||
|
clearTimeout(timer);
|
||||||
|
timer = setTimeout(getInfo, interval);
|
||||||
}
|
}
|
||||||
|
|
||||||
function reload() {
|
function reload() {
|
||||||
setTimeout(() => {
|
|
||||||
window.location.reload();
|
if (document.hidden) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearNavigation();
|
||||||
|
|
||||||
|
navigationTimer = setTimeout(function() {
|
||||||
|
navigationTimer = null;
|
||||||
|
window.location.replace(window.location.href);
|
||||||
}, 3000);
|
}, 3000);
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function connect() {
|
function connect() {
|
||||||
@@ -171,19 +554,49 @@ function connect() {
|
|||||||
var wsUrl = getURL() + "/status";
|
var wsUrl = getURL() + "/status";
|
||||||
var ws = new WebSocket(wsUrl);
|
var ws = new WebSocket(wsUrl);
|
||||||
|
|
||||||
|
ws.onopen = function(e) {
|
||||||
|
|
||||||
|
clearFailure();
|
||||||
|
|
||||||
|
if (portalUrl.length > 0) {
|
||||||
|
redirect(portalUrl);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
ws.onmessage = function(e) {
|
ws.onmessage = function(e) {
|
||||||
|
|
||||||
|
clearFailure();
|
||||||
|
|
||||||
var pos = e.data.indexOf(":");
|
var pos = e.data.indexOf(":");
|
||||||
var cmd = e.data.substring(0, pos);
|
var cmd = e.data.substring(0, pos);
|
||||||
var msg = e.data.substring(pos + 2);
|
var msg = e.data.substring(pos + 2);
|
||||||
|
|
||||||
switch (cmd) {
|
switch (cmd) {
|
||||||
case "s":
|
case "s":
|
||||||
|
|
||||||
|
if (abortRequest()) {
|
||||||
|
schedule();
|
||||||
|
}
|
||||||
|
|
||||||
processMsg(msg);
|
processMsg(msg);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case "c":
|
||||||
|
|
||||||
|
abortRequest();
|
||||||
|
processCommand(msg);
|
||||||
|
break;
|
||||||
|
|
||||||
case "e":
|
case "e":
|
||||||
|
|
||||||
|
if (abortRequest()) {
|
||||||
|
schedule();
|
||||||
|
}
|
||||||
|
|
||||||
|
rememberStatus(msg);
|
||||||
setError(msg);
|
setError(msg);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
console.warn("Unknown event: " + cmd);
|
console.warn("Unknown event: " + cmd);
|
||||||
break;
|
break;
|
||||||
@@ -191,18 +604,27 @@ function connect() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
ws.onclose = function(e) {
|
ws.onclose = function(e) {
|
||||||
|
|
||||||
|
connectionLost();
|
||||||
|
|
||||||
|
if (portal && portalUrl.length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
connect();
|
connect();
|
||||||
}, interval);
|
}, interval);
|
||||||
};
|
};
|
||||||
|
|
||||||
ws.onerror = function(e) {
|
ws.onerror = function(e) {
|
||||||
|
connectionLost();
|
||||||
ws.close();
|
ws.close();
|
||||||
if (!booting) {
|
|
||||||
window.location.reload();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.addEventListener("resize", resizeProgress);
|
||||||
|
document.addEventListener("visibilitychange", visibilityChanged);
|
||||||
|
rememberStatus(document.getElementById("info").innerHTML);
|
||||||
|
|
||||||
schedule();
|
schedule();
|
||||||
connect();
|
connect();
|
||||||
|
|||||||
Reference in New Issue
Block a user