mirror of
https://github.com/vdsm/virtual-dsm.git
synced 2026-08-29 04:07:02 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
+57
-41
@@ -12,47 +12,63 @@ ARG DEBCONF_NOWARNINGS="yes"
|
||||
ARG DEBIAN_FRONTEND="noninteractive"
|
||||
ARG DEBCONF_NONINTERACTIVE_SEEN="true"
|
||||
|
||||
RUN set -eu && \
|
||||
apt-get update && \
|
||||
apt-get --no-install-recommends -y install \
|
||||
jq \
|
||||
tini \
|
||||
curl \
|
||||
wget \
|
||||
fdisk \
|
||||
unzip \
|
||||
nginx \
|
||||
procps \
|
||||
ethtool \
|
||||
python3 \
|
||||
python3-pip \
|
||||
python3-msgpack \
|
||||
python3-pysodium \
|
||||
xz-utils \
|
||||
iptables \
|
||||
iproute2 \
|
||||
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/*
|
||||
RUN <<EOF
|
||||
set -eu
|
||||
|
||||
apt-get update
|
||||
apt-get --no-install-recommends -y install \
|
||||
jq \
|
||||
tini \
|
||||
curl \
|
||||
wget \
|
||||
fdisk \
|
||||
unzip \
|
||||
nginx \
|
||||
procps \
|
||||
ipcalc \
|
||||
ethtool \
|
||||
python3 \
|
||||
python3-pip \
|
||||
python3-msgpack \
|
||||
python3-pysodium \
|
||||
xz-utils \
|
||||
iptables \
|
||||
iproute2 \
|
||||
dnsmasq \
|
||||
fakeroot \
|
||||
apt-utils \
|
||||
net-tools \
|
||||
e2fsprogs \
|
||||
qemu-utils \
|
||||
websocketd \
|
||||
iputils-ping \
|
||||
inotify-tools \
|
||||
ca-certificates \
|
||||
netcat-openbsd \
|
||||
qemu-system-x86
|
||||
|
||||
# Install Passt package
|
||||
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
|
||||
|
||||
# Install Python dependencies
|
||||
pip3 install --no-cache-dir --break-system-packages --root-user-action=ignore dissect.cstruct
|
||||
|
||||
# Configure QEMU
|
||||
mkdir -p /etc/qemu
|
||||
echo "allow br0" > /etc/qemu/bridge.conf
|
||||
|
||||
# Configure nginx
|
||||
unlink /etc/nginx/sites-enabled/default
|
||||
sed -i 's/^worker_processes.*/worker_processes 1;/' /etc/nginx/nginx.conf
|
||||
|
||||
# Set version file
|
||||
echo "$VERSION_ARG" > /etc/version
|
||||
|
||||
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
EOF
|
||||
|
||||
COPY --chmod=755 ./src /run/
|
||||
COPY --chmod=755 ./web /var/www/
|
||||
|
||||
@@ -16,9 +16,13 @@ Virtual DSM in a Docker container.
|
||||
|
||||
## Features ✨
|
||||
|
||||
- Multiple disks
|
||||
- KVM acceleration
|
||||
- Automatic download
|
||||
- Runs Virtual DSM inside a Docker container
|
||||
- Automatic download of the installation files
|
||||
- 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
|
||||
- Supports bridge, user-mode, macvlan, and macvtap networking
|
||||
|
||||
## Usage 🐳
|
||||
|
||||
@@ -79,7 +83,7 @@ kubectl apply -f https://raw.githubusercontent.com/vdsm/virtual-dsm/refs/heads/m
|
||||
|
||||
- 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!
|
||||
|
||||
|
||||
+5
-2
@@ -1,12 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
cd /run
|
||||
. utils.sh # Load functions
|
||||
|
||||
: "${DHCP:="N"}"
|
||||
: "${NETWORK:="Y"}"
|
||||
|
||||
[ -f "/run/shm/qemu.end" ] && echo "QEMU is shutting down..." && exit 1
|
||||
[ ! -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"
|
||||
address="/run/shm/qemu.ip"
|
||||
@@ -18,7 +21,7 @@ location=$(<"$file")
|
||||
|
||||
if ! curl -m 20 -ILfSs "http://$location/" > /dev/null; then
|
||||
|
||||
if [[ "$DHCP" == [Yy1]* ]]; then
|
||||
if enabled "$DHCP"; then
|
||||
ip=$(<"$address")
|
||||
echo "Failed to reach DSM at http://$location"
|
||||
else
|
||||
|
||||
+185
-134
@@ -8,7 +8,7 @@ set -Eeuo pipefail
|
||||
: "${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_DISCARD:="unmap"}" # Controls whether unmap (TRIM) commands are passed to the host.
|
||||
: "${DISK_ROTATION:="1"}" # Rotation rate, set to 1 for SSD storage and increase for HDD
|
||||
|
||||
# Sanitize all variables
|
||||
@@ -38,15 +38,9 @@ fmt2ext() {
|
||||
local DISK_FMT="$1"
|
||||
|
||||
case "${DISK_FMT,,}" in
|
||||
qcow2)
|
||||
echo "qcow2"
|
||||
;;
|
||||
raw)
|
||||
echo "img"
|
||||
;;
|
||||
*)
|
||||
error "Unrecognized disk format: $DISK_FMT" && exit 78
|
||||
;;
|
||||
qcow2) echo "qcow2" ;;
|
||||
raw) echo "img" ;;
|
||||
*) error "Unrecognized disk format: $DISK_FMT" && exit 78 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
@@ -54,21 +48,16 @@ ext2fmt() {
|
||||
local DISK_EXT="$1"
|
||||
|
||||
case "${DISK_EXT,,}" in
|
||||
qcow2)
|
||||
echo "qcow2"
|
||||
;;
|
||||
img)
|
||||
echo "raw"
|
||||
;;
|
||||
*)
|
||||
error "Unrecognized file extension: .$DISK_EXT" && exit 78
|
||||
;;
|
||||
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
|
||||
local DISK_EXT DISK_FMT size
|
||||
|
||||
DISK_EXT=$(echo "${DISK_FILE//*./}" | sed 's/^.*\.//')
|
||||
DISK_FMT=$(ext2fmt "$DISK_EXT")
|
||||
@@ -77,11 +66,19 @@ getSize() {
|
||||
raw)
|
||||
stat -c%s "$DISK_FILE"
|
||||
;;
|
||||
|
||||
qcow2)
|
||||
qemu-img info "$DISK_FILE" -f "$DISK_FMT" | grep '^virtual size: ' | sed 's/.*(\(.*\) bytes)/\1/'
|
||||
size=$(qemu-img info --output=json -f "$DISK_FMT" "$DISK_FILE" | jq -r '."virtual-size" // empty')
|
||||
if [[ ! "$size" =~ ^[0-9]+$ ]]; then
|
||||
error "Failed to determine virtual size of $DISK_FILE"
|
||||
exit 78
|
||||
fi
|
||||
echo "$size"
|
||||
;;
|
||||
|
||||
*)
|
||||
error "Unrecognized disk format: $DISK_FMT" && exit 78
|
||||
error "Unrecognized disk format: $DISK_FMT"
|
||||
exit 78
|
||||
;;
|
||||
esac
|
||||
}
|
||||
@@ -106,6 +103,82 @@ supportsDirect() {
|
||||
return 0
|
||||
}
|
||||
|
||||
allocateRaw() {
|
||||
|
||||
local DISK_FILE="$1"
|
||||
local DATA_SIZE="$2"
|
||||
|
||||
if disabled "$ALLOCATE"; then
|
||||
truncate -s "$DATA_SIZE" "$DISK_FILE"
|
||||
return $?
|
||||
fi
|
||||
|
||||
fallocate -l "$DATA_SIZE" "$DISK_FILE" &>/dev/null && return 0
|
||||
fallocate -l -x "$DATA_SIZE" "$DISK_FILE" && return 0
|
||||
truncate -s "$DATA_SIZE" "$DISK_FILE" || return 1
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
getDiskOptions() {
|
||||
|
||||
local FS="$1"
|
||||
local DISK_FMT="$2"
|
||||
local DISK_PARAM="$DISK_ALLOC"
|
||||
|
||||
isCow "$FS" && DISK_PARAM+=",nocow=on"
|
||||
|
||||
if [[ "${DISK_FMT,,}" != "raw" ]]; then
|
||||
[ -n "$DISK_FLAGS" ] && DISK_PARAM+=",$DISK_FLAGS"
|
||||
fi
|
||||
|
||||
echo "$DISK_PARAM"
|
||||
return 0
|
||||
}
|
||||
|
||||
normalizeSize() {
|
||||
|
||||
local DISK_SPACE="$1"
|
||||
local DISK_DESC="$2"
|
||||
local DIR="$3"
|
||||
local SPACE FREE GB DATA_SIZE
|
||||
|
||||
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
|
||||
|
||||
echo "$SPACE"
|
||||
return 0
|
||||
}
|
||||
|
||||
createDisk() {
|
||||
|
||||
local DISK_FILE="$1"
|
||||
@@ -148,33 +221,15 @@ createDisk() {
|
||||
{ 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
|
||||
|
||||
if ! allocateRaw "$DISK_FILE" "$DATA_SIZE"; then
|
||||
rm -f "$DISK_FILE"
|
||||
error "$FAIL" && exit 77
|
||||
fi
|
||||
;;
|
||||
qcow2)
|
||||
|
||||
local DISK_PARAM="$DISK_ALLOC"
|
||||
isCow "$FS" && DISK_PARAM+=",nocow=on"
|
||||
[ -n "$DISK_FLAGS" ] && DISK_PARAM+=",$DISK_FLAGS"
|
||||
local DISK_PARAM
|
||||
DISK_PARAM=$(getDiskOptions "$FS" "$DISK_FMT")
|
||||
|
||||
if ! qemu-img create -f "$DISK_FMT" -o "$DISK_PARAM" -- "$DISK_FILE" "$DATA_SIZE" ; then
|
||||
rm -f "$DISK_FILE"
|
||||
@@ -202,7 +257,7 @@ resizeDisk() {
|
||||
local FS="$5"
|
||||
local CUR_SIZE DATA_SIZE DIR SPACE GB
|
||||
|
||||
CUR_SIZE=$(getSize "$DISK_FILE")
|
||||
CUR_SIZE=$(getSize "$DISK_FILE") || exit 71
|
||||
DATA_SIZE=$(numfmt --from=iec "$DISK_SPACE")
|
||||
local REQ=$(( DATA_SIZE - CUR_SIZE ))
|
||||
(( REQ < 1 )) && error "Shrinking disks is not supported yet, please increase ${DISK_DESC^^}_SIZE." && exit 71
|
||||
@@ -230,24 +285,8 @@ resizeDisk() {
|
||||
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
|
||||
|
||||
if ! allocateRaw "$DISK_FILE" "$DATA_SIZE"; then
|
||||
error "$FAIL" && exit 75
|
||||
fi
|
||||
;;
|
||||
qcow2)
|
||||
@@ -286,7 +325,7 @@ convertDisk() {
|
||||
local CUR_SIZE SPACE GB
|
||||
|
||||
# Check free diskspace
|
||||
CUR_SIZE=$(getSize "$SOURCE_FILE")
|
||||
CUR_SIZE=$(getSize "$SOURCE_FILE") || exit 79
|
||||
SPACE=$(df --output=avail -B 1 "$DIR" | tail -n 1)
|
||||
|
||||
if (( CUR_SIZE > SPACE )); then
|
||||
@@ -302,14 +341,13 @@ convertDisk() {
|
||||
info "$msg, please wait until completed..."
|
||||
|
||||
local CONV_FLAGS="-p"
|
||||
local DISK_PARAM="$DISK_ALLOC"
|
||||
isCow "$FS" && DISK_PARAM+=",nocow=on"
|
||||
local DISK_PARAM
|
||||
DISK_PARAM=$(getDiskOptions "$FS" "$DST_FMT")
|
||||
|
||||
if [[ "$DST_FMT" != "raw" ]]; then
|
||||
if disabled "$ALLOCATE"; then
|
||||
CONV_FLAGS+=" -c"
|
||||
fi
|
||||
[ -n "$DISK_FLAGS" ] && DISK_PARAM+=",$DISK_FLAGS"
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC2086
|
||||
@@ -330,8 +368,17 @@ convertDisk() {
|
||||
fi
|
||||
fi
|
||||
|
||||
rm -f "$SOURCE_FILE"
|
||||
mv "$TMP_FILE" "$DST_FILE"
|
||||
if ! rm -f "$SOURCE_FILE"; then
|
||||
rm -f "$TMP_FILE"
|
||||
error "Failed to remove old $DISK_DESC image $SOURCE_FILE."
|
||||
exit 79
|
||||
fi
|
||||
|
||||
if ! mv "$TMP_FILE" "$DST_FILE"; then
|
||||
rm -f "$TMP_FILE"
|
||||
error "Failed to move converted $DISK_DESC image to $DST_FILE."
|
||||
exit 79
|
||||
fi
|
||||
|
||||
if isCow "$FS"; then
|
||||
FA=$(lsattr "$DST_FILE")
|
||||
@@ -460,7 +507,7 @@ addDisk () {
|
||||
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
|
||||
local DISK_EXT DIR SPACE DATA_SIZE FS PREV_FMT PREV_EXT CUR_SIZE LEFT FREE USED
|
||||
|
||||
DISK_EXT=$(fmt2ext "$DISK_FMT")
|
||||
local DISK_FILE="$DISK_BASE.$DISK_EXT"
|
||||
@@ -468,38 +515,9 @@ addDisk () {
|
||||
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
|
||||
|
||||
SPACE=$(normalizeSize "$DISK_SPACE" "$DISK_DESC" "$DIR")
|
||||
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 $?
|
||||
|
||||
@@ -526,7 +544,7 @@ addDisk () {
|
||||
|
||||
if [ -s "$DISK_FILE" ]; then
|
||||
|
||||
CUR_SIZE=$(getSize "$DISK_FILE")
|
||||
CUR_SIZE=$(getSize "$DISK_FILE") || exit 71
|
||||
|
||||
if (( DATA_SIZE > CUR_SIZE )); then
|
||||
|
||||
@@ -551,13 +569,15 @@ addDisk () {
|
||||
|
||||
if [ -f "$DISK_FILE" ] && disabled "$ALLOCATE"; then
|
||||
|
||||
CUR_SIZE=$(getSize "$DISK_FILE")
|
||||
CUR_SIZE=$(getSize "$DISK_FILE") || exit 73
|
||||
USED=$(du -sB 1 "$DISK_FILE" | cut -f1)
|
||||
FREE=$(df --output=avail -B 1 "$DIR" | tail -n 1)
|
||||
LEFT=$(( CUR_SIZE - USED - FREE ))
|
||||
(( LEFT < 0 )) && LEFT=0
|
||||
|
||||
if (( LEFT > 0 )); then
|
||||
|
||||
local GB
|
||||
GB=$(formatBytes "$FREE")
|
||||
LEFT=$(formatBytes "$LEFT")
|
||||
CUR_SIZE=$(formatBytes "$CUR_SIZE")
|
||||
@@ -603,18 +623,22 @@ addDevice () {
|
||||
# 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%% *}"
|
||||
local result=""
|
||||
local logical=""
|
||||
local physical=""
|
||||
|
||||
if [ -n "$physical" ]; then
|
||||
if [[ "$physical" != "512" ]]; then
|
||||
sectors=",logical_block_size=$logical,physical_block_size=$physical"
|
||||
fi
|
||||
else
|
||||
result=$(fdisk -l "$DISK_DEV" 2>/dev/null | grep -m 1 -o "(logical/physical): .*" | cut -c 21- || true)
|
||||
|
||||
if [ -n "$result" ]; then
|
||||
logical="${result%% *}"
|
||||
physical=$(echo "$result" | grep -m 1 -o "/ .*" | cut -c 3- || true)
|
||||
physical="${physical%% *}"
|
||||
fi
|
||||
|
||||
if [ -z "$logical" ] || [ -z "$physical" ]; then
|
||||
warn "Failed to determine the sector size for $DISK_DEV"
|
||||
elif [[ "$physical" != "512" ]]; then
|
||||
sectors=",logical_block_size=$logical,physical_block_size=$physical"
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -634,11 +658,45 @@ if ! enabled "$DISK_DISABLE"; then
|
||||
enabled "$DEBUG" && echo "$msg"
|
||||
fi
|
||||
|
||||
if [[ "${DISK_IO,,}" == "native" && "${DISK_CACHE,,}" != "none" && "${DISK_CACHE,,}" != "directsync" ]]; then
|
||||
warn "DISK_IO=native requires direct I/O caching, using DISK_IO=threads with DISK_CACHE=$DISK_CACHE."
|
||||
DISK_IO="threads"
|
||||
fi
|
||||
|
||||
case "${DISK_DISCARD,,}" in
|
||||
"y" | "yes" | "true" | "1" | "on" | "unmap" )
|
||||
DISK_DISCARD="unmap" ;;
|
||||
|
||||
"n" | "no" | "false" | "0" | "off" | "ignore" )
|
||||
DISK_DISCARD="ignore" ;;
|
||||
|
||||
* )
|
||||
warn "Invalid DISK_DISCARD value '$DISK_DISCARD', using 'unmap'."
|
||||
DISK_DISCARD="unmap" ;;
|
||||
esac
|
||||
|
||||
if [[ ! "$DISK_ROTATION" =~ ^[0-9]+$ ]]; then
|
||||
warn "Invalid DISK_ROTATION value '$DISK_ROTATION', using 1."
|
||||
DISK_ROTATION="1"
|
||||
fi
|
||||
|
||||
DISK_FMT="${DISK_FMT,,}"
|
||||
|
||||
case "$DISK_FMT" in
|
||||
"raw" | "qcow2" ) ;;
|
||||
* ) error "Invalid DISK_FMT specified, value \"$DISK_FMT\" is not recognized!" && exit 78 ;;
|
||||
esac
|
||||
|
||||
case "${DISK_TYPE,,}" in
|
||||
"ide" | "sata" | "nvme" | "usb" | "scsi" | "blk" | "auto" | "none" ) ;;
|
||||
"ide" | "sata" | "nvme" | "usb" | "scsi" | "blk" | "virtio-blk" | "virtio-scsi" | "auto" | "none" ) ;;
|
||||
* ) error "Invalid DISK_TYPE specified, value \"$DISK_TYPE\" is not recognized!" && exit 80 ;;
|
||||
esac
|
||||
|
||||
if [[ "$DISK_FLAGS" =~ [[:space:]] ]]; then
|
||||
error "Invalid DISK_FLAGS value '$DISK_FLAGS', spaces are not allowed."
|
||||
exit 78
|
||||
fi
|
||||
|
||||
if [ -z "$ALLOCATE" ]; then
|
||||
ALLOCATE="N"
|
||||
fi
|
||||
@@ -684,29 +742,22 @@ DISK4_FILE="/storage4/${DISK_NAME}4"
|
||||
[ -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
|
||||
DISK_FILES=( "$DISK1_FILE" "$DISK2_FILE" "$DISK3_FILE" "$DISK4_FILE" )
|
||||
DISK_DESCS=( "disk" "disk2" "disk3" "disk4" )
|
||||
DISK_SIZES=( "$DISK_SIZE" "$DISK2_SIZE" "$DISK3_SIZE" "$DISK4_SIZE" )
|
||||
DISK_DEVICES=( "$DEVICE" "$DEVICE2" "$DEVICE3" "$DEVICE4" )
|
||||
DISK_INDEXES=( "3" "4" "5" "6" )
|
||||
DISK_ADDRESSES=( "0xc" "0xd" "0xe" "0xf" )
|
||||
|
||||
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
|
||||
for i in "${!DISK_FILES[@]}"; do
|
||||
|
||||
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 "${DISK_DEVICES[i]}" ]; then
|
||||
addDevice "${DISK_DEVICES[i]}" "$DISK_TYPE" "${DISK_INDEXES[i]}" "${DISK_ADDRESSES[i]}" || exit $?
|
||||
else
|
||||
addDisk "${DISK_FILES[i]}" "$DISK_TYPE" "${DISK_DESCS[i]}" "${DISK_SIZES[i]}" "${DISK_INDEXES[i]}" "${DISK_ADDRESSES[i]}" "$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
|
||||
done
|
||||
|
||||
finishDisks
|
||||
|
||||
|
||||
+7
-5
@@ -205,11 +205,12 @@ fi
|
||||
MSG="Preparing system partition..."
|
||||
info "Install: $MSG" && html "$MSG"
|
||||
|
||||
BOOT=$(find "$TMP" -name "*.bin.zip")
|
||||
[ ! -s "$BOOT" ] && error "The PAT file contains no boot image." && exit 67
|
||||
BOOT=$(find "$TMP" -name "*.bin.zip" -print -quit)
|
||||
[ -z "$BOOT" ] && error "The PAT file contains no boot image." && exit 67
|
||||
[ ! -s "$BOOT" ] && error "The PAT boot image archive is empty." && exit 67
|
||||
|
||||
BOOT=$(echo "$BOOT" | head -c -5)
|
||||
unzip -q -o "$BOOT".zip -d "$TMP"
|
||||
unzip -q -o "$BOOT" -d "$TMP"
|
||||
BOOT="${BOOT%.zip}"
|
||||
|
||||
SYSTEM="$STORAGE/$BASE.system.img"
|
||||
rm -f "$SYSTEM"
|
||||
@@ -281,8 +282,9 @@ mv "$HDA.tgz" "$HDA.txz"
|
||||
[ -d "$PKG" ] && mv "$PKG/" "$MOUNT/.SynoUpgradePackages/"
|
||||
rm -f "$MOUNT/.SynoUpgradePackages/ActiveInsight-"*
|
||||
|
||||
INDEX_DB="$MOUNT/usr/syno/synoman/indexdb"
|
||||
|
||||
if [ -s "$IDB.txz" ]; then
|
||||
INDEX_DB="$MOUNT/usr/syno/synoman/indexdb"
|
||||
mkdir -p "$INDEX_DB"
|
||||
fi
|
||||
|
||||
|
||||
+45
-12
@@ -7,7 +7,13 @@ 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
|
||||
checkConfiguredMemory() {
|
||||
|
||||
local wanted msg
|
||||
|
||||
if disabled "$RAM_CHECK" || [[ "${RAM_SIZE,,}" == "max" || "${RAM_SIZE,,}" == "half" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
wanted=$(numfmt --from=iec "$RAM_SIZE")
|
||||
|
||||
@@ -30,9 +36,16 @@ if ! disabled "$RAM_CHECK" && [[ "${RAM_SIZE,,}" != "max" && "${RAM_SIZE,,}" !=
|
||||
fi
|
||||
fi
|
||||
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
if [[ "${RAM_SIZE,,}" == "half" ]]; then
|
||||
configureHalfMemory() {
|
||||
|
||||
local wanted
|
||||
|
||||
if [[ "${RAM_SIZE,,}" != "half" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if (( (RAM_AVAIL / 2) > RAM_SPARE )); then
|
||||
wanted=$(( (RAM_AVAIL / 2) / 1048577 ))
|
||||
@@ -42,9 +55,16 @@ if [[ "${RAM_SIZE,,}" == "half" ]]; then
|
||||
RAM_SIZE="max"
|
||||
fi
|
||||
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
if [[ "${RAM_SIZE,,}" == "max" ]]; then
|
||||
configureMaxMemory() {
|
||||
|
||||
local wanted
|
||||
|
||||
if [[ "${RAM_SIZE,,}" != "max" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if (( RAM_AVAIL < (RAM_SPARE * 2) )); then
|
||||
|
||||
@@ -65,14 +85,27 @@ if [[ "${RAM_SIZE,,}" == "max" ]]; then
|
||||
|
||||
info "Allocated $wanted MB of RAM for the virtual machine."
|
||||
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
wanted=$(numfmt --from=iec "$RAM_SIZE")
|
||||
checkMinimumMemory() {
|
||||
|
||||
if [ "$wanted" -lt "$RAM_MINIMUM" ]; then
|
||||
wanted=$(( wanted / 1048577 ))
|
||||
error "Not enough memory available, there is only $wanted MB left!"
|
||||
exit 16
|
||||
fi
|
||||
local wanted
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
checkConfiguredMemory
|
||||
configureHalfMemory
|
||||
configureMaxMemory
|
||||
checkMinimumMemory
|
||||
|
||||
return 0
|
||||
|
||||
+1009
-385
File diff suppressed because it is too large
Load Diff
+131
-64
@@ -15,6 +15,7 @@ API_HOST="127.0.0.1:$COM_PORT"
|
||||
QEMU_END="$QEMU_DIR/qemu.end"
|
||||
|
||||
_trap() {
|
||||
|
||||
local func="$1"; shift
|
||||
local sig
|
||||
TRAP_PID=$BASHPID
|
||||
@@ -22,88 +23,103 @@ _trap() {
|
||||
for sig; do
|
||||
trap "$func $sig" "$sig"
|
||||
done
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
app() {
|
||||
echo "$APP" && return 0
|
||||
signalCode() {
|
||||
|
||||
local sig="$1"
|
||||
|
||||
case "$sig" in
|
||||
SIGHUP) echo 129 ;;
|
||||
SIGINT) echo 130 ;;
|
||||
SIGQUIT) echo 131 ;;
|
||||
SIGABRT) echo 134 ;;
|
||||
SIGTERM) echo 143 ;;
|
||||
*) echo 0 ;;
|
||||
esac
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
finish() {
|
||||
displayReason() {
|
||||
|
||||
local i=0
|
||||
local reason="$1"
|
||||
|
||||
case "$reason" in
|
||||
129 ) echo "SIGHUP" ;;
|
||||
130 ) echo "SIGINT" ;;
|
||||
131 ) echo "SIGQUIT" ;;
|
||||
134 ) echo "SIGABRT" ;;
|
||||
143 ) echo "SIGTERM" ;;
|
||||
* ) echo "$reason" ;;
|
||||
esac
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
readQemuPid() {
|
||||
|
||||
local -n _pid="$1"
|
||||
|
||||
if [ ! -s "$QEMU_PID" ] || ! read -r _pid <"$QEMU_PID"; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
forceKillQemu() {
|
||||
|
||||
local reason="$1"
|
||||
local pid=""
|
||||
local reason=$1
|
||||
local display
|
||||
|
||||
! readQemuPid pid && return 0
|
||||
! isAlive "$pid" && return 0
|
||||
|
||||
display=$(displayReason "$reason")
|
||||
error "Forcefully terminating $(app), reason: $display..."
|
||||
{ disown "$pid" || :; kill -9 -- "$pid" || :; } 2>/dev/null
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
cleanupHelpers() {
|
||||
|
||||
local pids=( "${HOST_PID:-}" "${WSD_PID:-}" \
|
||||
"${WEB_PID:-}" "${PASST_PID:-}" "${DNSMASQ_PID:-}" )
|
||||
|
||||
touch "$QEMU_END"
|
||||
|
||||
if [ -s "$QEMU_PID" ]; then
|
||||
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
|
||||
|
||||
mKill "${pids[@]}"
|
||||
fKill "print.sh"
|
||||
|
||||
closeNetwork
|
||||
return 0
|
||||
}
|
||||
|
||||
finish() {
|
||||
|
||||
local reason=$1
|
||||
touch "$QEMU_END"
|
||||
|
||||
forceKillQemu "$reason"
|
||||
cleanupHelpers
|
||||
|
||||
if ! waitPidFile "$QEMU_PID" 10; then
|
||||
warn "Timed out while waiting for $(app) to exit!"
|
||||
fi
|
||||
|
||||
(( reason != 1 )) && echo && echo "❯ Shutdown completed!"
|
||||
|
||||
exit "$reason"
|
||||
}
|
||||
|
||||
graceful_shutdown() {
|
||||
sendGuestShutdown() {
|
||||
|
||||
local sig="$1"
|
||||
local pid=""
|
||||
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
|
||||
local pid="$1"
|
||||
local response
|
||||
local url
|
||||
|
||||
# Don't send the powerdown signal because vDSM ignores ACPI signals
|
||||
# nc -q 1 -w 1 -U "$QEMU_DIR/monitor.sock" &> /dev/null <<<'system_powerdown' || :
|
||||
@@ -121,16 +137,22 @@ graceful_shutdown() {
|
||||
|
||||
response="${response#*message\"\: \"}"
|
||||
[ -z "$response" ] && response="second signal"
|
||||
|
||||
echo && error "Forcefully terminating because of: ${response%%\"*}"
|
||||
kill -15 -- "$pid" 2>/dev/null || :
|
||||
|
||||
fi
|
||||
|
||||
local name
|
||||
name="$(app)"
|
||||
return 0
|
||||
}
|
||||
|
||||
normalizeTimeout() {
|
||||
|
||||
local term_grace=3 # seconds before loop ends to send SIGTERM
|
||||
local cleanup_grace=3 # seconds reserved after the loop for cleanup
|
||||
local elapsed
|
||||
local timeout_left
|
||||
local min
|
||||
|
||||
TIMEOUT=$(strip "$TIMEOUT")
|
||||
if [[ ! "$TIMEOUT" =~ ^[0-9]+$ ]]; then
|
||||
@@ -145,8 +167,6 @@ graceful_shutdown() {
|
||||
cleanup_grace=4
|
||||
fi
|
||||
|
||||
local cnt=0 sigterm_at=0 min wait_until elapsed timeout_left
|
||||
|
||||
elapsed=$((SECONDS - start))
|
||||
timeout_left=$((TIMEOUT - elapsed))
|
||||
|
||||
@@ -156,10 +176,20 @@ graceful_shutdown() {
|
||||
wait_until=$((timeout_left - cleanup_grace))
|
||||
sigterm_at=$((wait_until - term_grace))
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
waitForShutdown() {
|
||||
|
||||
local cnt=0
|
||||
local pid="$1"
|
||||
local name="$APP"
|
||||
local slp
|
||||
|
||||
while (( cnt <= wait_until )); do
|
||||
|
||||
sleep 1 &
|
||||
local slp=$!
|
||||
slp=$!
|
||||
|
||||
# Stop waiting if the process has exited
|
||||
! isAlive "$pid" && break
|
||||
@@ -179,6 +209,43 @@ graceful_shutdown() {
|
||||
|
||||
done
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
graceful_shutdown() {
|
||||
|
||||
local sig="$1"
|
||||
local pid=""
|
||||
local code=0
|
||||
|
||||
[[ $BASHPID != "$TRAP_PID" ]] && return
|
||||
|
||||
code=$(signalCode "$sig")
|
||||
|
||||
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 ! readQemuPid pid; then
|
||||
warn "QEMU PID file ($QEMU_PID) does not exist?"
|
||||
finish "$code"
|
||||
fi
|
||||
|
||||
if ! isAlive "$pid"; then
|
||||
warn "QEMU process with PID $pid does not exist?"
|
||||
finish "$code"
|
||||
fi
|
||||
|
||||
sendGuestShutdown "$pid"
|
||||
normalizeTimeout
|
||||
waitForShutdown "$pid"
|
||||
|
||||
finish "$code"
|
||||
}
|
||||
|
||||
|
||||
+146
-47
@@ -1,16 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
# shellcheck disable=SC2329
|
||||
set -Eeuo pipefail
|
||||
|
||||
: "${DHCP:="N"}"
|
||||
: "${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; }
|
||||
error () { printf "%b%s%b" "\E[1;31m❯ " "ERROR: $1" "\E[0m\n" >&2; }
|
||||
|
||||
disabled "$NETWORK" && exit 0
|
||||
|
||||
file="/run/shm/dsm.url"
|
||||
info="/run/shm/msg.html"
|
||||
msgs="/run/shm/msg.html"
|
||||
driver="/run/shm/qemu.nic"
|
||||
page="/run/shm/index.html"
|
||||
address="/run/shm/qemu.ip"
|
||||
@@ -22,68 +26,144 @@ resp_err="Guest returned an invalid response:"
|
||||
curl_err="Failed to connect to guest: curl error"
|
||||
jq_err="Failed to parse response from guest: jq error"
|
||||
|
||||
while [ ! -s "$file" ]
|
||||
do
|
||||
|
||||
# Check if not shutting down
|
||||
[ -f "$shutdown" ] && exit 1
|
||||
|
||||
sleep 3
|
||||
exitIfShuttingDown() {
|
||||
|
||||
[ -f "$shutdown" ] && exit 1
|
||||
[ -s "$file" ] && break
|
||||
|
||||
# Retrieve network info from guest VM
|
||||
return 0
|
||||
}
|
||||
|
||||
queryGuest() {
|
||||
|
||||
local rc
|
||||
|
||||
{ json=$(curl -m 20 -sk "$url"); rc=$?; } || :
|
||||
|
||||
[ -f "$shutdown" ] && exit 1
|
||||
(( rc != 0 )) && error "$curl_err $rc" && continue
|
||||
exitIfShuttingDown
|
||||
|
||||
{ result=$(echo "$json" | jq -r '.status'); rc=$?; } || :
|
||||
(( rc != 0 )) && error "$jq_err $rc ( $json )" && continue
|
||||
[[ "$result" == "null" ]] && error "$resp_err $json" && continue
|
||||
|
||||
if [[ "$result" != "success" ]] ; then
|
||||
{ msg=$(echo "$json" | jq -r '.message'); rc=$?; } || :
|
||||
error "Guest replied $result: $msg" && continue
|
||||
if (( rc != 0 )); then
|
||||
error "$curl_err $rc"
|
||||
return 1
|
||||
fi
|
||||
|
||||
{ port=$(echo "$json" | jq -r '.data.data.dsm_setting.data.http_port'); rc=$?; } || :
|
||||
(( rc != 0 )) && error "$jq_err $rc ( $json )" && continue
|
||||
[[ "$port" == "null" ]] && error "$resp_err $json" && continue
|
||||
[ -z "$port" ] && continue
|
||||
return 0
|
||||
}
|
||||
|
||||
{ ip=$(echo "$json" | jq -r '.data.data.ip.data[] | select((.name=="eth0") and has("ip")).ip'); rc=$?; } || :
|
||||
(( rc != 0 )) && error "$jq_err $rc ( $json )" && continue
|
||||
[[ "$ip" == "null" ]] && error "$resp_err $json" && continue
|
||||
readJsonField() {
|
||||
|
||||
[ -z "$ip" ] && continue
|
||||
echo "$ip:$port" > $file
|
||||
local query="$1"
|
||||
local result
|
||||
local rc
|
||||
|
||||
done
|
||||
{ result=$(jq -r "$query" <<< "$json"); 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 rc
|
||||
|
||||
result=$(readJsonField '.status') || return 1
|
||||
|
||||
if [[ "$result" != "success" ]]; then
|
||||
{ msg=$(jq -r '.message // empty' <<< "$json"); 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() {
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
writeDhcpPage() {
|
||||
|
||||
local title body script html
|
||||
|
||||
msg="http://$location"
|
||||
title="<title>Virtual DSM</title>"
|
||||
body="The location of DSM is <a href='http://$location'>http://$location</a>"
|
||||
script="<script>setTimeout(function(){ window.location.assign('http://$location'); }, 3000);</script>"
|
||||
|
||||
HTML=$(<"$template")
|
||||
HTML="${HTML/\[1\]/$title}"
|
||||
HTML="${HTML/\[2\]/$script}"
|
||||
HTML="${HTML/\[3\]/$body}"
|
||||
HTML="${HTML/\[4\]/}"
|
||||
HTML="${HTML/\[5\]/}"
|
||||
html=$(<"$template")
|
||||
html="${html/\[1\]/$title}"
|
||||
html="${html/\[2\]/$script}"
|
||||
html="${html/\[3\]/$body}"
|
||||
html="${html/\[4\]/}"
|
||||
html="${html/\[5\]/}"
|
||||
|
||||
echo "$HTML" > "$page"
|
||||
echo "$body" > "$info"
|
||||
echo "$html" > "$page"
|
||||
echo "$body" > "$msgs"
|
||||
|
||||
else
|
||||
return 0
|
||||
}
|
||||
|
||||
buildStaticMessage() {
|
||||
|
||||
local nic ip port
|
||||
|
||||
nic=$(<"$driver")
|
||||
ip=$(<"$address")
|
||||
@@ -95,12 +175,31 @@ else
|
||||
msg="http://$ip:$port"
|
||||
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
|
||||
writeDhcpPage
|
||||
else
|
||||
buildStaticMessage
|
||||
fi
|
||||
|
||||
echo "" >&2
|
||||
info "-----------------------------------------------------------"
|
||||
info " You can now login to DSM at $msg"
|
||||
info "-----------------------------------------------------------"
|
||||
echo "" >&2
|
||||
printLoginMessage
|
||||
|
||||
exit 0
|
||||
|
||||
+143
-69
@@ -11,15 +11,27 @@ 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"
|
||||
selectClocksource() {
|
||||
|
||||
CLOCKSOURCE="tsc"
|
||||
[[ "${ARCH,,}" == "arm64" ]] && CLOCKSOURCE="arch_sys_counter"
|
||||
CLOCK="/sys/devices/system/clocksource/clocksource0/current_clocksource"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
checkClocksource() {
|
||||
|
||||
local result
|
||||
|
||||
if [ ! -f "$CLOCK" ]; then
|
||||
warn "file \"$CLOCK\" cannot be found?"
|
||||
return 0
|
||||
fi
|
||||
|
||||
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.." ;;
|
||||
@@ -27,29 +39,38 @@ else
|
||||
"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)
|
||||
return 0
|
||||
}
|
||||
|
||||
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"
|
||||
checkSse42() {
|
||||
|
||||
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
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
configureKvmCpuModel() {
|
||||
|
||||
CPU_FEATURES="kvm=on,l3-cache=on,+hypervisor"
|
||||
KVM_OPTS=",accel=kvm -enable-kvm -global kvm-pit.lost_tick_policy=discard"
|
||||
|
||||
if [ -z "$CPU_MODEL" ]; then
|
||||
CPU_MODEL="host"
|
||||
CPU_FEATURES+=",migratable=no"
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
appendKvmInvtscFeature() {
|
||||
|
||||
if grep -qw "svm" <<< "$flags"; then
|
||||
|
||||
# AMD processor
|
||||
|
||||
if grep -qw "tsc_scale" <<< "$flags"; then
|
||||
CPU_FEATURES+=",+invtsc"
|
||||
fi
|
||||
@@ -57,7 +78,7 @@ if ! disabled "$KVM"; then
|
||||
else
|
||||
|
||||
# Intel processor
|
||||
|
||||
local vmx
|
||||
vmx=$(sed -ne '/^vmx flags/s/^.*: //p' /proc/cpuinfo)
|
||||
|
||||
if grep -qw "tsc_scaling" <<< "$vmx"; then
|
||||
@@ -66,7 +87,35 @@ if ! disabled "$KVM"; then
|
||||
|
||||
fi
|
||||
|
||||
else
|
||||
return 0
|
||||
}
|
||||
|
||||
configureKvm() {
|
||||
|
||||
configureKvmCpuModel
|
||||
checkSse42
|
||||
appendKvmInvtscFeature
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
configureTcgCpuModel() {
|
||||
|
||||
if [ -n "$CPU_MODEL" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ "$ARCH" == "amd64" ]]; then
|
||||
CPU_MODEL="max"
|
||||
CPU_FEATURES+=",migratable=no"
|
||||
else
|
||||
CPU_MODEL="qemu64"
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
configureTcg() {
|
||||
|
||||
KVM_OPTS=""
|
||||
CPU_FEATURES="l3-cache=on,+hypervisor"
|
||||
@@ -75,70 +124,95 @@ else
|
||||
KVM_OPTS=" -accel tcg,thread=multi"
|
||||
fi
|
||||
|
||||
if [ -z "$CPU_MODEL" ]; then
|
||||
if [[ "$ARCH" == "amd64" ]]; then
|
||||
CPU_MODEL="max"
|
||||
CPU_FEATURES+=",migratable=no"
|
||||
configureTcgCpuModel
|
||||
CPU_FEATURES+=",+ssse3,+sse4.1,+sse4.2"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
extractHostCpuArgument() {
|
||||
|
||||
local args prefix suffix param
|
||||
|
||||
if [[ "$ARGUMENTS" == *"-cpu host,"* ]]; then
|
||||
|
||||
args="${ARGUMENTS} "
|
||||
prefix="${args/-cpu host,*/}"
|
||||
suffix="${args/*-cpu host,/}"
|
||||
param="${suffix%% *}"
|
||||
suffix="${suffix#* }"
|
||||
args="${prefix}${suffix}"
|
||||
ARGUMENTS="${args::-1}"
|
||||
|
||||
if [ -z "$CPU_FLAGS" ]; then
|
||||
CPU_FLAGS="$param"
|
||||
else
|
||||
CPU_MODEL="qemu64"
|
||||
CPU_FLAGS+=",$param"
|
||||
fi
|
||||
|
||||
else
|
||||
|
||||
if [[ "$ARGUMENTS" == *"-cpu host"* ]]; then
|
||||
ARGUMENTS="${ARGUMENTS//-cpu host/}"
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
composeCpuFlags() {
|
||||
|
||||
if [ -z "$CPU_FLAGS" ]; then
|
||||
if [ -z "$CPU_FEATURES" ]; then
|
||||
CPU_FLAGS="$CPU_MODEL"
|
||||
else
|
||||
CPU_FLAGS="$CPU_MODEL,$CPU_FEATURES"
|
||||
fi
|
||||
else
|
||||
if [ -z "$CPU_FEATURES" ]; then
|
||||
CPU_FLAGS="$CPU_MODEL,$CPU_FLAGS"
|
||||
else
|
||||
CPU_FLAGS="$CPU_MODEL,$CPU_FEATURES,$CPU_FLAGS"
|
||||
fi
|
||||
fi
|
||||
|
||||
CPU_FEATURES+=",+ssse3,+sse4.1,+sse4.2"
|
||||
return 0
|
||||
}
|
||||
|
||||
fi
|
||||
configureHostCpuName() {
|
||||
|
||||
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"
|
||||
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
|
||||
}
|
||||
|
||||
selectClocksource
|
||||
checkClocksource
|
||||
|
||||
flags=$(sed -ne '/^flags/s/^.*: //p' /proc/cpuinfo)
|
||||
|
||||
if ! disabled "$KVM"; then
|
||||
configureKvm
|
||||
else
|
||||
|
||||
if [[ "$ARGUMENTS" == *"-cpu host"* ]]; then
|
||||
ARGUMENTS="${ARGUMENTS//-cpu host/}"
|
||||
fi
|
||||
|
||||
configureTcg
|
||||
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
|
||||
extractHostCpuArgument
|
||||
composeCpuFlags
|
||||
configureHostCpuName
|
||||
|
||||
return 0
|
||||
|
||||
+204
-155
@@ -21,6 +21,203 @@ enabled "${TRACE:-}" && set -o functrace && trap 'echo "# $BASH_COMMAND" >&2' DE
|
||||
: "${DISK_SIZE:="16G"}" # Initial data disk size
|
||||
: "${STORAGE:="/storage"}" # Storage folder location
|
||||
|
||||
detectEngine() {
|
||||
|
||||
if [ -f "/run/.containerenv" ]; then
|
||||
ENGINE="${container:-}"
|
||||
if [[ "${ENGINE,,}" == *"podman"* ]]; then
|
||||
ROOTLESS="Y"
|
||||
ENGINE="Podman"
|
||||
else
|
||||
[ -z "$ENGINE" ] && ENGINE="Kubernetes"
|
||||
fi
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
checkPrivileged() {
|
||||
|
||||
local cap_bnd
|
||||
local last_cap
|
||||
local max_cap
|
||||
|
||||
# Get the capability bounding set
|
||||
cap_bnd=$(grep '^CapBnd:' /proc/$$/status | awk '{print $2}')
|
||||
cap_bnd=$(printf "%d" "0x${cap_bnd}")
|
||||
|
||||
# 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
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
normalizeCpuCores() {
|
||||
|
||||
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
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
checkStorage() {
|
||||
|
||||
# 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
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
checkFilesystem() {
|
||||
|
||||
# Check filesystem
|
||||
FS=$(stat -f -c %T "$STORAGE")
|
||||
|
||||
if [[ "${FS,,}" == "ecryptfs" || "${FS,,}" == "tmpfs" ]]; then
|
||||
DISK_IO="threads"
|
||||
DISK_CACHE="writeback"
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
normalizeRamSize() {
|
||||
|
||||
# Read memory
|
||||
RAM_AVAIL=$(free -b | grep -m 1 Mem: | awk '{print $7}')
|
||||
RAM_TOTAL=$(free -b | grep -m 1 Mem: | awk '{print $2}')
|
||||
|
||||
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
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
checkKvm() {
|
||||
|
||||
# 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
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Sanitize variables
|
||||
TZ=$(strip "$TZ")
|
||||
STORAGE=$(strip "$STORAGE")
|
||||
@@ -34,33 +231,12 @@ 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
|
||||
detectEngine
|
||||
|
||||
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
|
||||
checkPrivileged
|
||||
|
||||
INFO="/run/shm/msg.html"
|
||||
PAGE="/run/shm/index.html"
|
||||
@@ -71,7 +247,6 @@ 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)
|
||||
@@ -83,77 +258,10 @@ if grep -qi "socket(s)" <<< "$(lscpu)"; then
|
||||
[ "$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
|
||||
normalizeCpuCores
|
||||
checkStorage
|
||||
checkFilesystem
|
||||
normalizeRamSize
|
||||
|
||||
# Print system info
|
||||
SYS="${SYS/-generic/}"
|
||||
@@ -168,66 +276,7 @@ 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
|
||||
checkKvm
|
||||
|
||||
# Cleanup files
|
||||
rm -f "$QEMU_DIR"/dsm.url
|
||||
|
||||
+66
-35
@@ -15,7 +15,12 @@ HOST_MODEL=$(strip "$HOST_MODEL")
|
||||
HOST_SERIAL=$(strip "$HOST_SERIAL")
|
||||
GUEST_SERIAL=$(strip "$GUEST_SERIAL")
|
||||
|
||||
if [ -n "$HOST_MAC" ]; then
|
||||
validateHostMac() {
|
||||
local m
|
||||
|
||||
if [ -z "$HOST_MAC" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
HOST_MAC="${HOST_MAC//-/:}"
|
||||
|
||||
@@ -28,51 +33,77 @@ if [ -n "$HOST_MAC" ]; then
|
||||
error "Invalid HOST_MAC address: '$HOST_MAC', should be 12 or 17 digits long!" && exit 28
|
||||
fi
|
||||
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
HOST_PID="$QEMU_DIR/host.pid"
|
||||
buildHostArguments() {
|
||||
|
||||
HOST_ARGS=()
|
||||
HOST_ARGS+=("-cpu=$CPU_CORES")
|
||||
HOST_ARGS+=("-cpu_arch=$HOST_CPU")
|
||||
HOST_ARGS=()
|
||||
HOST_ARGS+=("-cpu=$CPU_CORES")
|
||||
HOST_ARGS+=("-cpu_arch=$HOST_CPU")
|
||||
|
||||
[ -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")
|
||||
[ -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")
|
||||
|
||||
if enabled "$HOST_DEBUG"; then
|
||||
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
|
||||
return 0
|
||||
}
|
||||
|
||||
cnt=0
|
||||
sleep 0.2
|
||||
startHostBinary() {
|
||||
|
||||
while ! nc -z -w2 127.0.0.1 "$COM_PORT" > /dev/null 2>&1; do
|
||||
sleep 0.1
|
||||
cnt=$((cnt + 1))
|
||||
(( cnt > 50 )) && error "Failed to connect to qemu-host.." && exit 58
|
||||
done
|
||||
if enabled "$HOST_DEBUG"; then
|
||||
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
|
||||
return 0
|
||||
}
|
||||
|
||||
while ! nc -z -w2 127.0.0.1 "$CHR_PORT" > /dev/null 2>&1; do
|
||||
sleep 0.1
|
||||
cnt=$((cnt + 1))
|
||||
(( cnt > 50 )) && error "Failed to connect to qemu-host.." && exit 59
|
||||
done
|
||||
waitForPort() {
|
||||
|
||||
# Configure serial ports
|
||||
SERIAL_OPTS="-serial mon:stdio \
|
||||
local port="$1"
|
||||
local exit_code="$2"
|
||||
local cnt=0
|
||||
|
||||
while ! nc -z -w2 127.0.0.1 "$port" > /dev/null 2>&1; do
|
||||
sleep 0.1
|
||||
cnt=$((cnt + 1))
|
||||
(( cnt > 50 )) && error "Failed to connect to qemu-host.." && exit "$exit_code"
|
||||
done
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
configureSerialPorts() {
|
||||
|
||||
# Configure serial ports
|
||||
SERIAL_OPTS="-serial mon:stdio \
|
||||
-device virtio-serial-pci,id=virtio-serial0,bus=pcie.0,addr=0x3 \
|
||||
-chardev socket,id=charchannel0,host=127.0.0.1,port=$CHR_PORT,reconnect=10 \
|
||||
-device virtserialport,bus=virtio-serial0.0,nr=1,chardev=charchannel0,id=channel0,name=vchannel"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
validateHostMac
|
||||
|
||||
HOST_PID="$QEMU_DIR/host.pid"
|
||||
|
||||
buildHostArguments
|
||||
startHostBinary
|
||||
|
||||
sleep 0.2
|
||||
|
||||
waitForPort "$COM_PORT" 58
|
||||
waitForPort "$CHR_PORT" 59
|
||||
|
||||
configureSerialPorts
|
||||
|
||||
return 0
|
||||
|
||||
+40
-11
@@ -15,33 +15,62 @@ 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"
|
||||
prepareWebFiles() {
|
||||
cp -r /var/www/* "$QEMU_DIR"
|
||||
rm -f "$WSD_PID" "$WEB_PID"
|
||||
|
||||
html "Starting $APP for $ENGINE..."
|
||||
return 0
|
||||
}
|
||||
|
||||
if ! disabled "${WEB:-}"; then
|
||||
configureWebPorts() {
|
||||
sed -i "s/listen 5000 default_server;/listen $WEB_PORT default_server;/g" /etc/nginx/sites-enabled/web.conf
|
||||
sed -i "s/proxy_pass http:\/\/127.0.0.1:8004\/;/proxy_pass http:\/\/127.0.0.1:$WSD_PORT\/;/g" /etc/nginx/sites-enabled/web.conf
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
configureIpv6Listen() {
|
||||
|
||||
# shellcheck disable=SC2143
|
||||
if [ -f /proc/net/if_inet6 ] && [[ "$(cat /proc/sys/net/ipv6/conf/all/disable_ipv6 2>/dev/null)" != "1" ]] && [ -n "$(ifconfig -a | grep inet6)" ]; then
|
||||
sed -i "s/listen $WEB_PORT default_server;/listen [::]:$WEB_PORT default_server ipv6only=off;/g" /etc/nginx/sites-enabled/web.conf
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
configureWebServer() {
|
||||
|
||||
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
|
||||
configureWebPorts
|
||||
configureIpv6Listen
|
||||
|
||||
# shellcheck disable=SC2143
|
||||
if [ -f /proc/net/if_inet6 ] && [[ "$(cat /proc/sys/net/ipv6/conf/all/disable_ipv6 2>/dev/null)" != "1" ]] && [ -n "$(ifconfig -a | grep inet6)" ]; then
|
||||
return 0
|
||||
}
|
||||
|
||||
sed -i "s/listen $WEB_PORT default_server;/listen [::]:$WEB_PORT default_server ipv6only=off;/g" /etc/nginx/sites-enabled/web.conf
|
||||
|
||||
fi
|
||||
startWebServer() {
|
||||
|
||||
# Start webserver
|
||||
nginx -e stderr
|
||||
}
|
||||
|
||||
startWebsocketServer() {
|
||||
|
||||
# Start websocket server
|
||||
websocketd --address 127.0.0.1 --port="$WSD_PORT" /run/socket.sh >/var/log/websocketd.log &
|
||||
echo "$!" > "$WSD_PID"
|
||||
}
|
||||
|
||||
prepareWebFiles
|
||||
|
||||
html "Starting $APP for $ENGINE..."
|
||||
|
||||
if ! disabled "${WEB:-}"; then
|
||||
configureWebServer
|
||||
startWebServer
|
||||
startWebsocketServer
|
||||
fi
|
||||
|
||||
return 0
|
||||
|
||||
Reference in New Issue
Block a user