Compare commits

...

41 Commits
v4.40 ... v5.02

Author SHA1 Message Date
Kroese
932c23afba fix: Check SSE4.2 2023-12-16 15:57:30 +01:00
Kroese
2e0107e46f fix: Bash shebang
* fix: Bash shebang
2023-12-16 07:42:57 +01:00
Kroese
d22a3a4c7d feat: Support more CPU models
feat: Support more CPU models
2023-12-16 06:44:58 +01:00
Kroese
f93f870626 feat: CPU features 2023-12-16 06:31:43 +01:00
Kroese
a2e55c5dda fix: Configuration variables 2023-12-16 05:11:20 +01:00
Kroese
32748509ea fix: CPU configuration 2023-12-16 05:08:34 +01:00
Kroese
970a662170 fix: CPU configuration 2023-12-16 05:04:17 +01:00
Kroese
8925323a6e feat: KVM flag 2023-12-16 04:55:40 +01:00
Kroese
10915a601c feat: Display emulated CPU 2023-12-16 04:44:15 +01:00
Kroese
2cc1af19b1 fix: Configure CPU 2023-12-16 04:38:15 +01:00
Kroese
6670ca4fe1 fix: Configuration 2023-12-16 04:36:56 +01:00
Kroese
d3f77c848c feat: Emulate SSE4.2 on ARM64 2023-12-16 04:36:00 +01:00
Kroese
3f2ca67051 feat: Emulate SSE4.2 on ARM64 2023-12-16 04:22:21 +01:00
Kroese
3812101366 feat: Add KVM flag 2023-12-16 04:02:11 +01:00
Kroese
db72acfc4f feat: Install DSM 7.2 on ARM64 2023-12-16 03:12:55 +01:00
Kroese
1b3d760f5f fix: Set CPU model 2023-12-16 03:07:08 +01:00
Kroese
539f5de6d9 fix: Add qemu-user package on arm64 2023-12-16 02:45:16 +01:00
Kroese
fe2d072056 fix: Check for SSE4.2 support 2023-12-16 02:05:54 +01:00
Kroese
469ee67942 fix: Error codes 2023-12-16 01:42:02 +01:00
Kroese
95991d8f5d fix: Error codes 2023-12-16 01:36:33 +01:00
Kroese
7e12585429 fix: Error codes 2023-12-16 01:30:34 +01:00
Kroese
c335078aac feat: Display emulated CPU 2023-12-16 01:00:42 +01:00
Kroese
f1fbbb5623 feat: Configure CPU model 2023-12-16 00:35:08 +01:00
Kroese
b6502e0a38 fix: Simplify healthcheck
fix: Simplify healthcheck
2023-12-15 09:17:53 +01:00
Kroese
2fab3e5897 fix: Simplify healthcheck 2023-12-15 09:01:19 +01:00
Kroese
a4ea89d6e7 fix: Extended error message 2023-12-15 08:49:37 +01:00
Kroese
c451f253fa build: Release token (#454) 2023-12-14 03:45:32 +01:00
Kroese
03121b6c6d build: Renovate (#452) 2023-12-11 00:32:18 +01:00
Kroese
007d20c315 fix: Message variables
* fix: Message variables
2023-12-10 16:54:02 +01:00
Kroese
26d6fa9fcc feat: Improve shutdown
* feat: Improve shutdown
2023-12-10 15:58:37 +01:00
Kroese
b9f3e52ba4 feat: Improve shutdown (#448)
* feat: Improve shutdown
2023-12-10 09:22:35 +01:00
Kroese
03d2665725 fix: Local variables (#447)
* fix: Local variables

* fix: Keep location
2023-12-10 00:26:13 +01:00
Kroese
ba7fd2fe4a fix: Error checking (#446) 2023-12-09 21:45:31 +01:00
Kroese
a8bcae16a4 fix: Remove curly braces
* fix: Remove curly braces
2023-12-09 21:19:08 +01:00
Kroese
2f19d31a81 feat: Show conversion progress (#444)
* feat: Show conversion progress
2023-12-09 15:15:25 +01:00
Kroese
54692e3a75 fix: Move progress (#443) 2023-12-09 06:19:07 +01:00
Kroese
029235a34d docs: Readme (#442) 2023-12-09 05:52:21 +01:00
Kroese
180573d69f fix: Diskspace warning (#441) 2023-12-09 05:40:10 +01:00
Kroese
8fa900335a feat: Add qcow2 disk format (#440)
* feat: Add qcow2 disk format
2023-12-09 02:55:39 +01:00
Kroese
a527080ccd feat: Print curl error (#438)
* feat: Print curl error
2023-12-07 23:36:57 +01:00
Kroese
ce6d60c611 Parse JSON with JQ (#437)
* feat: Parse JSON with JQ
2023-12-07 23:18:47 +01:00
18 changed files with 534 additions and 389 deletions

View File

@@ -1,6 +1,4 @@
{ {
"extends": [ "$schema": "https://docs.renovatebot.com/renovate-schema.json",
"config:base", "extends": ["config:recommended", ":disableDependencyDashboard"]
":disableDependencyDashboard"
]
} }

View File

@@ -79,16 +79,14 @@ jobs:
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
annotations: ${{ steps.meta.outputs.annotations }} annotations: ${{ steps.meta.outputs.annotations }}
build-args: | build-args: |
VCS_REF=${GITHUB_SHA::8}
VERSION_ARG=${{ steps.meta.outputs.version }} VERSION_ARG=${{ steps.meta.outputs.version }}
- -
name: Create a release name: Create a release
uses: action-pack/github-release@v2 uses: action-pack/github-release@v2
env:
GITHUB_TOKEN: ${{ secrets.REPO_ACCESS_TOKEN }}
with: with:
tag: "v${{ steps.meta.outputs.version }}" tag: "v${{ steps.meta.outputs.version }}"
title: "v${{ steps.meta.outputs.version }}" title: "v${{ steps.meta.outputs.version }}"
token: ${{ secrets.REPO_ACCESS_TOKEN }}
- -
name: Increment version variable name: Increment version variable
uses: action-pack/bump@v2 uses: action-pack/bump@v2

View File

@@ -9,11 +9,13 @@ FROM qemux/qemu-host as builder
FROM debian:trixie-slim FROM debian:trixie-slim
ARG TARGETPLATFORM
ARG DEBCONF_NOWARNINGS="yes" ARG DEBCONF_NOWARNINGS="yes"
ARG DEBIAN_FRONTEND noninteractive ARG DEBIAN_FRONTEND noninteractive
RUN apt-get update && apt-get -y upgrade && \ RUN apt-get update && apt-get -y upgrade \
apt-get --no-install-recommends -y install \ && if [ "$TARGETPLATFORM" != "linux/amd64" ]; then extra="qemu-user"; fi \
&& apt-get --no-install-recommends -y install \
jq \ jq \
tini \ tini \
curl \ curl \
@@ -28,9 +30,11 @@ RUN apt-get update && apt-get -y upgrade && \
iproute2 \ iproute2 \
dnsmasq \ dnsmasq \
net-tools \ net-tools \
qemu-utils \
ca-certificates \ ca-certificates \
netcat-openbsd \ netcat-openbsd \
qemu-system-x86 \ qemu-system-x86 \
"$extra" \
&& apt-get clean \ && apt-get clean \
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

View File

@@ -20,4 +20,4 @@ services:
volumes: volumes:
- /opt/dsm:/storage - /opt/dsm:/storage
restart: on-failure restart: on-failure
stop_grace_period: 1m stop_grace_period: 2m

View File

@@ -41,7 +41,7 @@ services:
volumes: volumes:
- /opt/dsm:/storage - /opt/dsm:/storage
restart: on-failure restart: on-failure
stop_grace_period: 1m stop_grace_period: 2m
``` ```
Via `docker run` Via `docker run`
@@ -87,16 +87,16 @@ docker run -it --rm -p 5000:5000 --device=/dev/kvm --cap-add NET_ADMIN --stop-ti
- /mnt/data/example:/storage3 - /mnt/data/example:/storage3
``` ```
* ### How do I change the space reserved by the virtual disk? * ### How do I create a growable disk?
By default, the entire disk space is reserved in advance. To create a growable disk that only reserves the space that is actually used, add the following environment variable: By default, the entire capacity of the disk is reserved in advance. To create a growable disk that only allocates space that is actually used, add the following environment variable:
```yaml ```yaml
environment: environment:
ALLOCATE: "N" DISK_FMT: "qcow2"
``` ```
Keep in mind that this will not affect any of your existing disks, it only applies to newly created disks. This can also be used to convert any existing disks to the ```qcow2``` format.
* ### How do I increase the amount of CPU or RAM? * ### How do I increase the amount of CPU or RAM?

View File

@@ -1,61 +1,17 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -u set -Eeuo pipefail
[ ! -f "/run/qemu.pid" ] && echo "QEMU not running yet.." && exit 0 [ ! -f "/run/qemu.pid" ] && echo "QEMU not running yet.." && exit 0
[ -f "/run/qemu.count" ] && echo "QEMU is shutting down.." && exit 1 [ -f "/run/qemu.count" ] && echo "QEMU is shutting down.." && exit 1
file="/run/dsm.url" file="/run/dsm.url"
[ ! -f "$file" ] && echo "DSM has not enabled networking yet.." && exit 1
if [ ! -f "$file" ]; then location=$(cat "$file")
# Retrieve IP from guest VM for Docker healthcheck
RESPONSE=$(curl -s -m 30 -S http://127.0.0.1:2210/read?command=10 2>&1)
if [[ ! "${RESPONSE}" =~ "\"success\"" ]] ; then
echo "Failed to connect to guest: $RESPONSE" && exit 1
fi
# Retrieve the HTTP port number
if [[ ! "${RESPONSE}" =~ "\"http_port\"" ]] ; then
echo "Failed to parse response from guest: $RESPONSE" && exit 1
fi
rest=${RESPONSE#*http_port}
rest=${rest#*:}
rest=${rest%%,*}
PORT=${rest%%\"*}
[ -z "${PORT}" ] && echo "Guest has not set a portnumber yet.." && exit 1
# Retrieve the IP address
if [[ ! "${RESPONSE}" =~ "eth0" ]] ; then
echo "Failed to parse response from guest: $RESPONSE" && exit 1
fi
rest=${RESPONSE#*eth0}
rest=${rest#*ip}
rest=${rest#*:}
rest=${rest#*\"}
IP=${rest%%\"*}
[ -z "${IP}" ] && echo "Guest has not received an IP yet.." && exit 1
echo "${IP}:${PORT}" > $file
if ! curl -m 20 -ILfSs "http://$location/" > /dev/null; then
echo "Failed to reach page at http://$location" && exit 1
fi fi
LOCATION=$(cat "$file") echo "Healthcheck OK"
if ! curl -m 20 -ILfSs "http://${LOCATION}/" > /dev/null; then
rm -f $file
echo "Failed to reach http://${LOCATION}"
exit 1
fi
if [[ "$LOCATION" == "20.20"* ]]; then
echo "Healthcheck OK"
else
echo "Healthcheck OK ( ${LOCATION%:*} )"
fi
exit 0 exit 0

View File

@@ -1,40 +1,20 @@
#!/bin/bash #!/usr/bin/env bash
set -Eeuo pipefail set -Eeuo pipefail
KVM_ERR=""
KVM_OPTS=""
if [ -e /dev/kvm ] && sh -c 'echo -n > /dev/kvm' &> /dev/null; then
if ! grep -q -e vmx -e svm /proc/cpuinfo; then
KVM_ERR="(vmx/svm disabled)"
fi
else
[ -e /dev/kvm ] && KVM_ERR="(no write access)" || KVM_ERR="(device file missing)"
fi
if [ -n "${KVM_ERR}" ]; then
if [ "$ARCH" == "amd64" ]; then
error "KVM acceleration not detected ${KVM_ERR}, see the FAQ about this."
[[ "${DEBUG}" != [Yy1]* ]] && exit 88
fi
else
KVM_OPTS=",accel=kvm -enable-kvm -cpu host"
fi
DEF_OPTS="-nographic -nodefaults -boot strict=on -display none" DEF_OPTS="-nographic -nodefaults -boot strict=on -display none"
RAM_OPTS=$(echo "-m ${RAM_SIZE}" | sed 's/MB/M/g;s/GB/G/g;s/TB/T/g') RAM_OPTS=$(echo "-m $RAM_SIZE" | sed 's/MB/M/g;s/GB/G/g;s/TB/T/g')
CPU_OPTS="-smp ${CPU_CORES},sockets=1,dies=1,cores=${CPU_CORES},threads=1" CPU_OPTS="-cpu $CPU_MODEL -smp $CPU_CORES,sockets=1,dies=1,cores=$CPU_CORES,threads=1"
MAC_OPTS="-machine type=q35,usb=off,dump-guest-core=off,hpet=off${KVM_OPTS}" MAC_OPTS="-machine type=q35,usb=off,dump-guest-core=off,hpet=off${KVM_OPTS}"
EXTRA_OPTS="-device virtio-balloon-pci,id=balloon0,bus=pcie.0,addr=0x4" EXTRA_OPTS="-device virtio-balloon-pci,id=balloon0,bus=pcie.0,addr=0x4"
EXTRA_OPTS="$EXTRA_OPTS -object rng-random,id=objrng0,filename=/dev/urandom" EXTRA_OPTS="$EXTRA_OPTS -object rng-random,id=objrng0,filename=/dev/urandom"
EXTRA_OPTS="$EXTRA_OPTS -device virtio-rng-pci,rng=objrng0,id=rng0,bus=pcie.0,addr=0x1c" EXTRA_OPTS="$EXTRA_OPTS -device virtio-rng-pci,rng=objrng0,id=rng0,bus=pcie.0,addr=0x1c"
if [[ "${GPU}" == [Yy1]* ]] && [[ "$ARCH" == "amd64" ]]; then if [[ "$GPU" == [Yy1]* ]] && [[ "$ARCH" == "amd64" ]]; then
DEF_OPTS="-nodefaults -boot strict=on -display egl-headless,rendernode=/dev/dri/renderD128" DEF_OPTS="-nodefaults -boot strict=on -display egl-headless,rendernode=/dev/dri/renderD128"
DEF_OPTS="${DEF_OPTS} -device virtio-vga,id=video0,max_outputs=1,bus=pcie.0,addr=0x1" DEF_OPTS="$DEF_OPTS -device virtio-vga,id=video0,max_outputs=1,bus=pcie.0,addr=0x1"
fi fi
ARGS="${DEF_OPTS} ${CPU_OPTS} ${RAM_OPTS} ${MAC_OPTS} ${MON_OPTS} ${SERIAL_OPTS} ${NET_OPTS} ${DISK_OPTS} ${EXTRA_OPTS} ${ARGUMENTS}" ARGS="$DEF_OPTS $CPU_OPTS $RAM_OPTS $MAC_OPTS $MON_OPTS $SERIAL_OPTS $NET_OPTS $DISK_OPTS $EXTRA_OPTS $ARGUMENTS"
ARGS=$(echo "$ARGS" | sed 's/\t/ /g' | tr -s ' ') ARGS=$(echo "$ARGS" | sed 's/\t/ /g' | tr -s ' ')
return 0 return 0

61
src/cpu.sh Normal file
View File

@@ -0,0 +1,61 @@
#!/usr/bin/env bash
set -Eeuo pipefail
# Docker environment variables
: ${HOST_CPU:=''}
: ${CPU_MODEL:='host'}
: ${CPU_FEATURES:='+ssse3,+sse4.1,+sse4.2'}
KVM_ERR=""
KVM_OPTS=""
if [[ "$ARCH" == "amd64" && "$KVM" != [Nn]* ]]; then
if [ -e /dev/kvm ] && sh -c 'echo -n > /dev/kvm' &> /dev/null; then
if ! grep -q -e vmx -e svm /proc/cpuinfo; then
KVM_ERR="(vmx/svm disabled)"
fi
else
[ -e /dev/kvm ] && KVM_ERR="(no write access)" || KVM_ERR="(device file missing)"
fi
if [ -n "$KVM_ERR" ]; then
error "KVM acceleration not detected $KVM_ERR, this will cause a major loss of performance."
error "See the FAQ on how to enable it, or skip this error by setting KVM=N (not recommended)."
[[ "$DEBUG" != [Yy1]* ]] && exit 88
[[ "$CPU_MODEL" == "host"* ]] && CPU_MODEL="max,$CPU_FEATURES"
else
KVM_OPTS=",accel=kvm -enable-kvm"
fi
if [ -n "$KVM_OPTS" ]; then
if ! grep -qE '^flags.* (sse4_2)' /proc/cpuinfo; then
error "Your host CPU does not has the SSE4.2 instruction set that Virtual DSM requires to boot."
error "Disable KVM by setting KVM=N to emulate a compatible CPU, at the cost of performance."
[[ "$DEBUG" != [Yy1]* ]] && exit 89
fi
fi
else
[[ "$CPU_MODEL" == "host"* ]] && CPU_MODEL="max,$CPU_FEATURES"
fi
if [ -z "$HOST_CPU" ]; then
HOST_CPU=$(lscpu | grep '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 [ -n "$HOST_CPU" ]; then
HOST_CPU="${HOST_CPU%%,*},,"
else
HOST_CPU="QEMU, Virtual CPU,"
if [ "$ARCH" == "amd64" ]; then
HOST_CPU="$HOST_CPU X86_64"
else
HOST_CPU="$HOST_CPU $ARCH"
fi
fi
return 0

View File

@@ -4,6 +4,7 @@ set -Eeuo pipefail
# Docker environment variables # Docker environment variables
: ${DISK_IO:='native'} # I/O Mode, can be set to 'native', 'threads' or 'io_turing' : ${DISK_IO:='native'} # I/O Mode, can be set to 'native', 'threads' or 'io_turing'
: ${DISK_FMT:='raw'} # Disk file format, 'raw' by default for best performance
: ${DISK_CACHE:='none'} # Caching mode, can be set to 'writeback' for better performance : ${DISK_CACHE:='none'} # Caching mode, can be set to 'writeback' for better performance
: ${DISK_DISCARD:='on'} # Controls whether unmap (TRIM) commands are passed to the host. : ${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 : ${DISK_ROTATION:='1'} # Rotation rate, set to 1 for SSD storage and increase for HDD
@@ -16,163 +17,300 @@ SYSTEM="$STORAGE/$BASE.system.img"
DISK_OPTS="\ DISK_OPTS="\
-device virtio-scsi-pci,id=hw-synoboot,bus=pcie.0,addr=0xa \ -device virtio-scsi-pci,id=hw-synoboot,bus=pcie.0,addr=0xa \
-drive file=${BOOT},if=none,id=drive-synoboot,format=raw,cache=${DISK_CACHE},aio=${DISK_IO},discard=${DISK_DISCARD},detect-zeroes=on \ -drive file=$BOOT,if=none,id=drive-synoboot,format=raw,cache=$DISK_CACHE,aio=$DISK_IO,discard=$DISK_DISCARD,detect-zeroes=on \
-device scsi-hd,bus=hw-synoboot.0,channel=0,scsi-id=0,lun=0,drive=drive-synoboot,id=synoboot0,rotation_rate=${DISK_ROTATION},bootindex=1 \ -device scsi-hd,bus=hw-synoboot.0,channel=0,scsi-id=0,lun=0,drive=drive-synoboot,id=synoboot0,rotation_rate=$DISK_ROTATION,bootindex=1 \
-device virtio-scsi-pci,id=hw-synosys,bus=pcie.0,addr=0xb \ -device virtio-scsi-pci,id=hw-synosys,bus=pcie.0,addr=0xb \
-drive file=${SYSTEM},if=none,id=drive-synosys,format=raw,cache=${DISK_CACHE},aio=${DISK_IO},discard=${DISK_DISCARD},detect-zeroes=on \ -drive file=$SYSTEM,if=none,id=drive-synosys,format=raw,cache=$DISK_CACHE,aio=$DISK_IO,discard=$DISK_DISCARD,detect-zeroes=on \
-device scsi-hd,bus=hw-synosys.0,channel=0,scsi-id=0,lun=0,drive=drive-synosys,id=synosys0,rotation_rate=${DISK_ROTATION},bootindex=2" -device scsi-hd,bus=hw-synosys.0,channel=0,scsi-id=0,lun=0,drive=drive-synosys,id=synosys0,rotation_rate=$DISK_ROTATION,bootindex=2"
addDisk () { fmt2ext() {
local DISK_FMT=$1
local FS case "${DISK_FMT,,}" in
local GB qcow2)
local DIR echo "qcow2"
local REQ ;;
local SPACE raw)
local CUR_SIZE echo "img"
local DATA_SIZE ;;
local DISK_ID=$1 *)
local DISK_FILE=$2 error "Unrecognized disk format: $DISK_FMT" && exit 78
local DISK_DESC=$3 ;;
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
}
resizeDisk() {
local DISK_FILE=$1
local CUR_SIZE=$2
local DATA_SIZE=$3
local DISK_SPACE=$4 local DISK_SPACE=$4
local DISK_INDEX=$5 local DISK_DESC=$5
local DISK_ADDRESS=$6 local DISK_FMT=$6
local GB REQ FAIL SPACE SPACE_GB
DIR=$(dirname "${DISK_FILE}") GB=$(( (CUR_SIZE + 1073741823)/1073741824 ))
[ ! -d "${DIR}" ] && return 0 info "Resizing $DISK_DESC from ${GB}G to $DISK_SPACE .."
FAIL="Could not resize $DISK_FMT file of $DISK_DESC ($DISK_FILE) from ${GB}G to $DISK_SPACE .."
FS=$(stat -f -c %T "$DIR")
if [[ "$FS" == "overlay"* ]]; then REQ=$((DATA_SIZE-CUR_SIZE))
info "Warning: the filesystem of ${DIR} is OverlayFS, this usually means it was binded to an invalid path!" (( REQ < 1 )) && error "Shrinking disks is not supported!" && exit 71
fi
[ -z "$DISK_SPACE" ] && DISK_SPACE="16G" case "${DISK_FMT,,}" in
DISK_SPACE=$(echo "${DISK_SPACE}" | sed 's/MB/M/g;s/GB/G/g;s/TB/T/g') raw)
DATA_SIZE=$(numfmt --from=iec "${DISK_SPACE}") if [[ "$ALLOCATE" == [Nn]* ]]; then
if (( DATA_SIZE < 6442450944 )); then
error "Please increase ${DISK_DESC^^}_SIZE to at least 6 GB." && exit 83
fi
if [ -f "${DISK_FILE}" ]; then
CUR_SIZE=$(stat -c%s "${DISK_FILE}")
if [ "$DATA_SIZE" -gt "$CUR_SIZE" ]; then
GB=$(( (CUR_SIZE + 1073741823)/1073741824 ))
info "Resizing ${DISK_DESC} from ${GB}G to ${DISK_SPACE} .."
if [[ "${ALLOCATE}" == [Nn]* ]]; then
# Resize file by changing its length # Resize file by changing its length
if ! truncate -s "${DISK_SPACE}" "${DISK_FILE}"; then if ! truncate -s "$DISK_SPACE" "$DISK_FILE"; then
error "Could not resize ${DISK_DESC} file (${DISK_FILE}) to ${DISK_SPACE} .." && exit 85 error "$FAIL" && exit 75
fi fi
else else
REQ=$((DATA_SIZE-CUR_SIZE))
# Check free diskspace # Check free diskspace
SPACE=$(df --output=avail -B 1 "${DIR}" | tail -n 1) SPACE=$(df --output=avail -B 1 "$DIR" | tail -n 1)
SPACE_GB=$(( (SPACE + 1073741823)/1073741824 )) SPACE_GB=$(( (SPACE + 1073741823)/1073741824 ))
if (( REQ > SPACE )); then if (( REQ > SPACE )); then
error "Not enough free space to resize ${DISK_DESC} to ${DISK_SPACE} in ${DIR}, it has only ${SPACE_GB} GB available.." error "Not enough free space to resize $DISK_DESC to $DISK_SPACE in $DIR, it has only $SPACE_GB GB available.."
error "Specify a smaller ${DISK_DESC^^}_SIZE or disable preallocation with ALLOCATE=N." && exit 84 error "Please specify a smaller ${DISK_DESC^^}_SIZE or disable preallocation by setting DISK_FMT to \"qcow2\"." && exit 74
fi fi
# Resize file by allocating more space # Resize file by allocating more space
if ! fallocate -l "${DISK_SPACE}" "${DISK_FILE}"; then if ! fallocate -l "$DISK_SPACE" "$DISK_FILE"; then
if ! truncate -s "${DISK_SPACE}" "${DISK_FILE}"; then if ! truncate -s "$DISK_SPACE" "$DISK_FILE"; then
error "Could not resize ${DISK_DESC} file (${DISK_FILE}) to ${DISK_SPACE}" && exit 85 error "$FAIL" && exit 75
fi fi
fi fi
fi fi
fi ;;
fi qcow2)
if ! qemu-img resize -f "$DISK_FMT" "$DISK_FILE" "$DISK_SPACE" ; then
if [ ! -f "${DISK_FILE}" ]; then error "$FAIL" && exit 72
if [[ "${ALLOCATE}" == [Nn]* ]]; then
# Create an empty file
if ! truncate -s "${DISK_SPACE}" "${DISK_FILE}"; then
rm -f "${DISK_FILE}"
error "Could not create a ${DISK_SPACE} file for ${DISK_DESC} (${DISK_FILE})" && exit 87
fi fi
;;
esac
}
else convertDisk() {
local CONV_FLAGS="-p"
local SOURCE_FILE=$1
local SOURCE_FMT=$2
local DST_FILE=$3
local DST_FMT=$4
# Check free diskspace case "$DST_FMT" in
SPACE=$(df --output=avail -B 1 "${DIR}" | tail -n 1) qcow2)
SPACE_GB=$(( (SPACE + 1073741823)/1073741824 )) CONV_FLAGS="$CONV_FLAGS -c"
;;
esac
if (( DATA_SIZE > SPACE )); then # shellcheck disable=SC2086
error "Not enough free space to create ${DISK_DESC} of ${DISK_SPACE} in ${DIR}, it has only ${SPACE_GB} GB available.." qemu-img convert $CONV_FLAGS -f "$SOURCE_FMT" -O "$DST_FMT" -- "$SOURCE_FILE" "$DST_FILE"
error "Specify a smaller ${DISK_DESC^^}_SIZE or disable preallocation with ALLOCATE=N." && exit 86 }
fi
# Create an empty file createDisk() {
if ! fallocate -l "${DISK_SPACE}" "${DISK_FILE}"; then local DISK_FILE=$1
if ! truncate -s "${DISK_SPACE}" "${DISK_FILE}"; then local DISK_SPACE=$2
rm -f "${DISK_FILE}" local DISK_DESC=$3
error "Could not create a ${DISK_SPACE} file for ${DISK_DESC} (${DISK_FILE})" && exit 87 local DISK_FMT=$4
local GB FAIL SPACE SPACE_GB
FAIL="Could not create a $DISK_SPACE $DISK_FMT file for $DISK_DESC ($DISK_FILE)"
case "${DISK_FMT,,}" in
raw)
if [[ "$ALLOCATE" == [Nn]* ]]; then
# Create an empty file
if ! truncate -s "$DISK_SPACE" "$DISK_FILE"; then
rm -f "$DISK_FILE"
error "$FAIL" && exit 77
fi fi
else
# Check free diskspace
SPACE=$(df --output=avail -B 1 "$DIR" | tail -n 1)
SPACE_GB=$(( (SPACE + 1073741823)/1073741824 ))
if (( DATA_SIZE > SPACE )); then
error "Not enough free space to create a $DISK_DESC of $DISK_SPACE in $DIR, it has only $SPACE_GB GB available.."
error "Please specify a smaller ${DISK_DESC^^}_SIZE or disable preallocation by setting DISK_FMT to \"qcow2\"." && exit 76
fi
# Create an empty file
if ! fallocate -l "$DISK_SPACE" "$DISK_FILE"; then
if ! truncate -s "$DISK_SPACE" "$DISK_FILE"; then
rm -f "$DISK_FILE"
error "$FAIL" && exit 77
fi
fi
fi
;;
qcow2)
if ! qemu-img create -f "$DISK_FMT" -- "$DISK_FILE" "$DISK_SPACE" ; then
rm -f "$DISK_FILE"
error "$FAIL" && exit 70
fi
;;
esac
}
addDisk () {
local DISK_ID=$1
local DISK_BASE=$2
local DISK_EXT=$3
local DISK_DESC=$4
local DISK_SPACE=$5
local DISK_INDEX=$6
local DISK_ADDRESS=$7
local DISK_FMT=$8
local FS DIR CUR_SIZE DATA_SIZE DISK_FILE
DISK_FILE="$DISK_BASE.$DISK_EXT"
DIR=$(dirname "$DISK_FILE")
[ ! -d "$DIR" ] && return 0
FS=$(stat -f -c %T "$DIR")
if [[ "$FS" == "overlay"* ]]; then
info "Warning: the filesystem of $DIR is OverlayFS, this usually means it was binded to an invalid path!"
fi
[ -z "$DISK_SPACE" ] && DISK_SPACE="16G"
DISK_SPACE=$(echo "$DISK_SPACE" | sed 's/MB/M/g;s/GB/G/g;s/TB/T/g')
DATA_SIZE=$(numfmt --from=iec "$DISK_SPACE")
if (( DATA_SIZE < 6442450944 )); then
error "Please increase ${DISK_DESC^^}_SIZE to at least 6 GB." && exit 73
fi
if ! [ -f "$DISK_FILE" ] ; then
local PREV_EXT PREV_FMT PREV_FILE TMP_FILE
if [[ "${DISK_FMT,,}" != "raw" ]]; then
PREV_FMT="raw"
else
PREV_FMT="qcow2"
fi
PREV_EXT="$(fmt2ext "$PREV_FMT")"
PREV_FILE="$DISK_BASE.$PREV_EXT"
if [ -f "$PREV_FILE" ] ; then
info "Detected that ${DISK_DESC^^}_FMT changed from \"$PREV_FMT\" to \"$DISK_FMT\"."
info "Starting conversion of $DISK_DESC to this new format, please wait until completed..."
TMP_FILE="$DISK_BASE.tmp"
rm -f "$TMP_FILE"
if ! convertDisk "$PREV_FILE" "$PREV_FMT" "$TMP_FILE" "$DISK_FMT" ; then
rm -f "$TMP_FILE"
error "Failed to convert $DISK_DESC to $DISK_FMT format." && exit 79
fi fi
mv "$TMP_FILE" "$DISK_FILE"
rm -f "$PREV_FILE"
info "Conversion of $DISK_DESC completed succesfully!"
fi fi
fi fi
DISK_OPTS="${DISK_OPTS} \ if [ -f "$DISK_FILE" ]; then
-device virtio-scsi-pci,id=hw-${DISK_ID},bus=pcie.0,addr=${DISK_ADDRESS} \
-drive file=${DISK_FILE},if=none,id=drive-${DISK_ID},format=raw,cache=${DISK_CACHE},aio=${DISK_IO},discard=${DISK_DISCARD},detect-zeroes=on \ CUR_SIZE=$(getSize "$DISK_FILE")
-device scsi-hd,bus=hw-${DISK_ID}.0,channel=0,scsi-id=0,lun=0,drive=drive-${DISK_ID},id=${DISK_ID},rotation_rate=${DISK_ROTATION},bootindex=${DISK_INDEX}"
if [ "$DATA_SIZE" -gt "$CUR_SIZE" ]; then
resizeDisk "$DISK_FILE" "$CUR_SIZE" "$DATA_SIZE" "$DISK_SPACE" "$DISK_DESC" "$DISK_FMT" || exit $?
fi
else
createDisk "$DISK_FILE" "$DISK_SPACE" "$DISK_DESC" "$DISK_FMT" || exit $?
fi
DISK_OPTS="$DISK_OPTS \
-device virtio-scsi-pci,id=hw-$DISK_ID,bus=pcie.0,addr=$DISK_ADDRESS \
-drive file=$DISK_FILE,if=none,id=drive-$DISK_ID,format=$DISK_FMT,cache=$DISK_CACHE,aio=$DISK_IO,discard=$DISK_DISCARD,detect-zeroes=on \
-device scsi-hd,bus=hw-$DISK_ID.0,channel=0,scsi-id=0,lun=0,drive=drive-$DISK_ID,id=$DISK_ID,rotation_rate=$DISK_ROTATION,bootindex=$DISK_INDEX"
return 0 return 0
} }
DISK1_FILE="${STORAGE}/data.img" DISK_EXT="$(fmt2ext "$DISK_FMT")" || exit $?
if [[ ! -f "${DISK1_FILE}" ]] && [[ -f "${STORAGE}/data${DISK_SIZE}.img" ]]; then DISK1_FILE="$STORAGE/data"
if [[ ! -f "$DISK1_FILE.img" ]] && [[ -f "$STORAGE/data${DISK_SIZE}.img" ]]; then
# Fallback for legacy installs # Fallback for legacy installs
mv "${STORAGE}/data${DISK_SIZE}.img" "${DISK1_FILE}" mv "$STORAGE/data${DISK_SIZE}.img" "$DISK1_FILE.img"
fi fi
DISK2_FILE="/storage2/data2.img" DISK2_FILE="/storage2/data2"
if [ ! -f "$DISK2_FILE.img" ]; then
if [ ! -f "${DISK2_FILE}" ]; then
# Fallback for legacy installs # Fallback for legacy installs
FALLBACK="/storage2/data.img" FALLBACK="/storage2/data.img"
if [[ -f "${DISK1_FILE}" ]] && [[ -f "${FALLBACK}" ]]; then if [[ -f "$DISK1_FILE.img" ]] && [[ -f "$FALLBACK" ]]; then
SIZE1=$(stat -c%s "${FALLBACK}") SIZE1=$(stat -c%s "$FALLBACK")
SIZE2=$(stat -c%s "${DISK1_FILE}") SIZE2=$(stat -c%s "$DISK1_FILE.img")
if [[ SIZE1 -ne SIZE2 ]]; then if [[ SIZE1 -ne SIZE2 ]]; then
mv "${FALLBACK}" "${DISK2_FILE}" mv "$FALLBACK" "$DISK2_FILE.img"
fi fi
fi fi
fi fi
DISK3_FILE="/storage3/data3.img" DISK3_FILE="/storage3/data3"
if [ ! -f "$DISK3_FILE.img" ]; then
if [ ! -f "${DISK3_FILE}" ]; then
# Fallback for legacy installs # Fallback for legacy installs
FALLBACK="/storage3/data.img" FALLBACK="/storage3/data.img"
if [[ -f "${DISK1_FILE}" ]] && [[ -f "${FALLBACK}" ]]; then if [[ -f "$DISK1_FILE.img" ]] && [[ -f "$FALLBACK" ]]; then
SIZE1=$(stat -c%s "${FALLBACK}") SIZE1=$(stat -c%s "$FALLBACK")
SIZE2=$(stat -c%s "${DISK1_FILE}") SIZE2=$(stat -c%s "$DISK1_FILE.img")
if [[ SIZE1 -ne SIZE2 ]]; then if [[ SIZE1 -ne SIZE2 ]]; then
mv "${FALLBACK}" "${DISK3_FILE}" mv "$FALLBACK" "$DISK3_FILE.img"
fi fi
fi fi
fi fi
DISK4_FILE="/storage4/data4.img" DISK4_FILE="/storage4/data4"
DISK5_FILE="/storage5/data5.img" DISK5_FILE="/storage5/data5"
DISK6_FILE="/storage6/data6.img" DISK6_FILE="/storage6/data6"
: ${DISK2_SIZE:=''} : ${DISK2_SIZE:=''}
: ${DISK3_SIZE:=''} : ${DISK3_SIZE:=''}
@@ -180,12 +318,12 @@ DISK6_FILE="/storage6/data6.img"
: ${DISK5_SIZE:=''} : ${DISK5_SIZE:=''}
: ${DISK6_SIZE:=''} : ${DISK6_SIZE:=''}
addDisk "userdata" "${DISK1_FILE}" "disk" "${DISK_SIZE}" "3" "0xc" addDisk "userdata" "$DISK1_FILE" "$DISK_EXT" "disk" "$DISK_SIZE" "3" "0xc" "$DISK_FMT" || exit $?
addDisk "userdata2" "${DISK2_FILE}" "disk2" "${DISK2_SIZE}" "4" "0xd" addDisk "userdata2" "$DISK2_FILE" "$DISK_EXT" "disk2" "$DISK2_SIZE" "4" "0xd" "$DISK_FMT" || exit $?
addDisk "userdata3" "${DISK3_FILE}" "disk3" "${DISK3_SIZE}" "5" "0xe" addDisk "userdata3" "$DISK3_FILE" "$DISK_EXT" "disk3" "$DISK3_SIZE" "5" "0xe" "$DISK_FMT" || exit $?
addDisk "userdata4" "${DISK4_FILE}" "disk4" "${DISK4_SIZE}" "9" "0x7" addDisk "userdata4" "$DISK4_FILE" "$DISK_EXT" "disk4" "$DISK4_SIZE" "9" "0x7" "$DISK_FMT" || exit $?
addDisk "userdata5" "${DISK5_FILE}" "disk5" "${DISK5_SIZE}" "10" "0x8" addDisk "userdata5" "$DISK5_FILE" "$DISK_EXT" "disk5" "$DISK5_SIZE" "10" "0x8" "$DISK_FMT" || exit $?
addDisk "userdata6" "${DISK6_FILE}" "disk6" "${DISK6_SIZE}" "11" "0x9" addDisk "userdata6" "$DISK6_FILE" "$DISK_EXT" "disk6" "$DISK6_SIZE" "11" "0x9" "$DISK_FMT" || exit $?
addDevice () { addDevice () {
@@ -194,13 +332,13 @@ addDevice () {
local DISK_INDEX=$3 local DISK_INDEX=$3
local DISK_ADDRESS=$4 local DISK_ADDRESS=$4
[ -z "${DISK_DEV}" ] && return 0 [ -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 [ ! -b "$DISK_DEV" ] && error "Device $DISK_DEV cannot be found! Please add it to the 'devices' section of your compose file." && exit 55
DISK_OPTS="${DISK_OPTS} \ DISK_OPTS="$DISK_OPTS \
-device virtio-scsi-pci,id=hw-${DISK_ID},bus=pcie.0,addr=${DISK_ADDRESS} \ -device virtio-scsi-pci,id=hw-$DISK_ID,bus=pcie.0,addr=$DISK_ADDRESS \
-drive file=${DISK_DEV},if=none,id=drive-${DISK_ID},format=raw,cache=${DISK_CACHE},aio=${DISK_IO},discard=${DISK_DISCARD},detect-zeroes=on \ -drive file=$DISK_DEV,if=none,id=drive-$DISK_ID,format=raw,cache=$DISK_CACHE,aio=$DISK_IO,discard=$DISK_DISCARD,detect-zeroes=on \
-device scsi-hd,bus=hw-${DISK_ID}.0,channel=0,scsi-id=0,lun=0,drive=drive-${DISK_ID},id=${DISK_ID},rotation_rate=${DISK_ROTATION},bootindex=${DISK_INDEX}" -device scsi-hd,bus=hw-$DISK_ID.0,channel=0,scsi-id=0,lun=0,drive=drive-$DISK_ID,id=$DISK_ID,rotation_rate=$DISK_ROTATION,bootindex=$DISK_INDEX"
return 0 return 0
} }
@@ -212,11 +350,11 @@ addDevice () {
: ${DEVICE5:=''} : ${DEVICE5:=''}
: ${DEVICE6:=''} : ${DEVICE6:=''}
addDevice "userdata7" "${DEVICE}" "6" "0xf" addDevice "userdata7" "$DEVICE" "6" "0xf" || exit $?
addDevice "userdata8" "${DEVICE2}" "7" "0x5" addDevice "userdata8" "$DEVICE2" "7" "0x5" || exit $?
addDevice "userdata9" "${DEVICE3}" "8" "0x6" addDevice "userdata9" "$DEVICE3" "8" "0x6" || exit $?
addDevice "userdata4" "${DEVICE4}" "9" "0x7" addDevice "userdata4" "$DEVICE4" "9" "0x7" || exit $?
addDevice "userdata5" "${DEVICE5}" "10" "0x8" addDevice "userdata5" "$DEVICE5" "10" "0x8" || exit $?
addDevice "userdata6" "${DEVICE6}" "11" "0x9" addDevice "userdata6" "$DEVICE6" "11" "0x9" || exit $?
return 0 return 0

View File

@@ -11,23 +11,24 @@ cd /run
. disk.sh # Initialize disks . disk.sh # Initialize disks
. network.sh # Initialize network . network.sh # Initialize network
. gpu.sh # Initialize graphics . gpu.sh # Initialize graphics
. cpu.sh # Initialize processor
. serial.sh # Initialize serialport . serial.sh # Initialize serialport
. power.sh # Configure shutdown . power.sh # Configure shutdown
. config.sh # Configure arguments . config.sh # Configure arguments
trap - ERR trap - ERR
if [[ "${CONSOLE}" == [Yy]* ]]; then if [[ "$CONSOLE" == [Yy]* ]]; then
exec qemu-system-x86_64 -pidfile "${QEMU_PID}" ${ARGS:+ $ARGS} exec qemu-system-x86_64 -pidfile "$QEMU_PID" ${ARGS:+ $ARGS}
exit $? exit $?
fi fi
set -m set -m
( (
[[ "${DEBUG}" == [Yy1]* ]] && info "$VERS" && set -x [[ "$DEBUG" == [Yy1]* ]] && info "$VERS" && set -x
qemu-system-x86_64 ${ARGS:+ $ARGS} & echo $! > "${QEMU_PID}" qemu-system-x86_64 ${ARGS:+ $ARGS} & echo $! > "$QEMU_PID"
{ set +x; } 2>/dev/null { set +x; } 2>/dev/null
) )
set +m set +m
tail --pid "$(cat "${QEMU_PID}")" --follow /dev/null & wait $! tail --pid "$(cat "$QEMU_PID")" --follow /dev/null & wait $!

View File

@@ -1,7 +1,7 @@
#!/bin/bash #!/usr/bin/env bash
set -Eeuo pipefail set -Eeuo pipefail
if [[ "${GPU}" != [Yy1]* ]] || [[ "$ARCH" != "amd64" ]]; then if [[ "$GPU" != [Yy1]* ]] || [[ "$ARCH" != "amd64" ]]; then
return 0 return 0
fi fi

View File

@@ -5,7 +5,7 @@ set -Eeuo pipefail
: ${DEV:='Y'} # Controls whether device nodes are created. : ${DEV:='Y'} # Controls whether device nodes are created.
if [ -f "$STORAGE"/dsm.ver ]; then if [ -f "$STORAGE"/dsm.ver ]; then
BASE=$(cat "${STORAGE}/dsm.ver") BASE=$(cat "$STORAGE/dsm.ver")
else else
# Fallback for old installs # Fallback for old installs
BASE="DSM_VirtualDSM_42962" BASE="DSM_VirtualDSM_42962"
@@ -29,27 +29,15 @@ DL_GLOBAL="https://global.synologydownload.com/download/DSM"
if [ -z "$DL" ]; then if [ -z "$DL" ]; then
[ -z "$COUNTRY" ] && setCountry [ -z "$COUNTRY" ] && setCountry
[ -z "$COUNTRY" ] && info "Warning: could not detect country to select mirror!"
[[ "${COUNTRY^^}" == "CN" ]] && DL="$DL_CHINA" || DL="$DL_GLOBAL" [[ "${COUNTRY^^}" == "CN" ]] && DL="$DL_CHINA" || DL="$DL_GLOBAL"
fi fi
if [ -z "$URL" ]; then [ -z "$URL" ] && URL="$DL/release/7.2.1/69057-1/DSM_VirtualDSM_69057.pat"
if [ "$ARCH" == "amd64" ]; then
URL="$DL/release/7.2.1/69057-1/DSM_VirtualDSM_69057.pat"
else
URL="$DL/release/7.0.1/42218/DSM_VirtualDSM_42218.pat"
fi
fi
# Check if output is to interactive TTY
if [ -t 1 ]; then
PROGRESS="--progress=bar:noscroll"
else
PROGRESS="--progress=dot:giga"
fi
BASE=$(basename "$URL" .pat) BASE=$(basename "$URL" .pat)
if [[ "$URL" != "file://${STORAGE}/${BASE}.pat" ]]; then if [[ "$URL" != "file://$STORAGE/$BASE.pat" ]]; then
rm -f "$STORAGE"/"$BASE".pat rm -f "$STORAGE"/"$BASE".pat
fi fi
@@ -57,7 +45,7 @@ rm -f "$STORAGE"/"$BASE".agent
rm -f "$STORAGE"/"$BASE".boot.img rm -f "$STORAGE"/"$BASE".boot.img
rm -f "$STORAGE"/"$BASE".system.img rm -f "$STORAGE"/"$BASE".system.img
[[ "${DEBUG}" == [Yy1]* ]] && set -x [[ "$DEBUG" == [Yy1]* ]] && set -x
# Check filesystem # Check filesystem
MIN_ROOT=471859200 MIN_ROOT=471859200
@@ -65,7 +53,7 @@ MIN_SPACE=6442450944
FS=$(stat -f -c %T "$STORAGE") FS=$(stat -f -c %T "$STORAGE")
if [[ "$FS" == "overlay"* ]]; then if [[ "$FS" == "overlay"* ]]; then
info "Warning: the filesystem of ${STORAGE} is OverlayFS, this usually means it was binded to an invalid path!" info "Warning: the filesystem of $STORAGE is OverlayFS, this usually means it was binded to an invalid path!"
fi fi
if [[ "$FS" != "fat"* && "$FS" != "vfat"* && "$FS" != "exfat"* && \ if [[ "$FS" != "fat"* && "$FS" != "vfat"* && "$FS" != "exfat"* && \
@@ -76,7 +64,7 @@ else
SPACE=$(df --output=avail -B 1 /tmp | tail -n 1) SPACE=$(df --output=avail -B 1 /tmp | tail -n 1)
if (( MIN_SPACE > SPACE )); then if (( MIN_SPACE > SPACE )); then
TMP="$STORAGE/tmp" TMP="$STORAGE/tmp"
info "Warning: the ${FS} filesystem of ${STORAGE} does not support UNIX permissions.." info "Warning: the $FS filesystem of $STORAGE does not support UNIX permissions.."
fi fi
fi fi
@@ -88,19 +76,26 @@ SPACE=$(df --output=avail -B 1 / | tail -n 1)
SPACE=$(df --output=avail -B 1 "$TMP" | tail -n 1) SPACE=$(df --output=avail -B 1 "$TMP" | tail -n 1)
SPACE_GB=$(( (SPACE + 1073741823)/1073741824 )) SPACE_GB=$(( (SPACE + 1073741823)/1073741824 ))
(( MIN_SPACE > SPACE )) && error "Not enough free space for installation in ${STORAGE}, have ${SPACE_GB} GB available but need at least 6 GB." && exit 95 (( MIN_SPACE > SPACE )) && error "Not enough free space for installation in $STORAGE, have $SPACE_GB GB available but need at least 6 GB." && exit 95
if [[ "$TMP" != "$STORAGE/tmp" ]]; then if [[ "$TMP" != "$STORAGE/tmp" ]]; then
SPACE=$(df --output=avail -B 1 "$STORAGE" | tail -n 1) SPACE=$(df --output=avail -B 1 "$STORAGE" | tail -n 1)
SPACE_GB=$(( (SPACE + 1073741823)/1073741824 )) SPACE_GB=$(( (SPACE + 1073741823)/1073741824 ))
(( MIN_SPACE > SPACE )) && error "Not enough free space for installation in ${STORAGE}, have ${SPACE_GB} GB available but need at least 6 GB." && exit 94 (( MIN_SPACE > SPACE )) && error "Not enough free space for installation in $STORAGE, have $SPACE_GB GB available but need at least 6 GB." && exit 94
fi
# Check if output is to interactive TTY
if [ -t 1 ]; then
PROGRESS="--progress=bar:noscroll"
else
PROGRESS="--progress=dot:giga"
fi fi
# Download the required files from the Synology website # Download the required files from the Synology website
RDC="$STORAGE/dsm.rd" RDC="$STORAGE/dsm.rd"
if [ ! -f "${RDC}" ]; then if [ ! -f "$RDC" ]; then
info "Install: Downloading installer..." info "Install: Downloading installer..."
@@ -109,7 +104,7 @@ if [ ! -f "${RDC}" ]; then
VERIFY="b4215a4b213ff5154db0488f92c87864" VERIFY="b4215a4b213ff5154db0488f92c87864"
LOC="$DL/release/7.0.1/42218/DSM_VirtualDSM_42218.pat" LOC="$DL/release/7.0.1/42218/DSM_VirtualDSM_42218.pat"
{ curl -r "$POS" -sfk -o "$RD" "$LOC"; rc=$?; } || : { curl -r "$POS" -sfk -S -o "$RD" "$LOC"; rc=$?; } || :
(( rc != 0 )) && error "Failed to download $LOC, reason: $rc" && exit 60 (( rc != 0 )) && error "Failed to download $LOC, reason: $rc" && exit 60
SUM=$(md5sum "$RD" | cut -f 1 -d " ") SUM=$(md5sum "$RD" | cut -f 1 -d " ")
@@ -123,7 +118,7 @@ if [ ! -f "${RDC}" ]; then
{ wget "$LOC" -O "$PAT" -q --no-check-certificate --show-progress "$PROGRESS"; rc=$?; } || : { wget "$LOC" -O "$PAT" -q --no-check-certificate --show-progress "$PROGRESS"; rc=$?; } || :
(( rc != 0 )) && error "Failed to download $LOC, reason: $rc" && exit 60 (( rc != 0 )) && error "Failed to download $LOC, reason: $rc" && exit 60
tar --extract --file="$PAT" --directory="$(dirname "${RD}")"/. "$(basename "${RD}")" tar --extract --file="$PAT" --directory="$(dirname "$RD")"/. "$(basename "$RD")"
rm "$PAT" rm "$PAT"
fi fi
@@ -132,20 +127,21 @@ if [ ! -f "${RDC}" ]; then
fi fi
if [ -f "${RDC}" ]; then if [ -f "$RDC" ]; then
{ xz -dc <"$RDC" >"$TMP/rd" 2>/dev/null; rc=$?; } || : { xz -dc <"$RDC" >"$TMP/rd" 2>/dev/null; rc=$?; } || :
(( rc != 1 )) && error "Failed to unxz $RDC, reason $rc" && exit 91 (( rc != 1 )) && error "Failed to unxz $RDC, reason $rc" && exit 91
if [[ "${DEV}" == [Nn]* ]]; then if [[ "$DEV" == [Nn]* ]]; then
# Exclude dev/ from cpio extract # Exclude dev/ from cpio extract
{ (cd "$TMP" && cpio -it < "$TMP/rd" | grep -Ev 'dev/' | while read -r entry; do cpio -idm "$entry" < "$TMP/rd" 2>/dev/null; done); rc=$?; } || : { (cd "$TMP" && cpio -it < "$TMP/rd" | grep -Ev 'dev/' | while read -r entry; do cpio -idm "$entry" < "$TMP/rd" 2>/dev/null; done); rc=$?; } || :
(( rc != 0 )) && error "Failed to extract $RDC, reason $rc" && exit 92
else else
{ (cd "$TMP" && cpio -idm <"$TMP/rd" 2>/dev/null); rc=$?; } || : { (cd "$TMP" && cpio -idm <"$TMP/rd" 2>/dev/null); rc=$?; } || :
(( rc != 0 )) && error "Failed to extract $RDC, reason $rc"
(( rc != 0 )) && error "If the container runs unprivileged, please set DEV=N to exclude device nodes." && exit 92
fi fi
(( rc != 0 )) && error "Failed to extract $RDC, reason $rc" && exit 92
mkdir -p /run/extract mkdir -p /run/extract
for file in $TMP/usr/lib/libcurl.so.4 \ for file in $TMP/usr/lib/libcurl.so.4 \
$TMP/usr/lib/libmbedcrypto.so.5 \ $TMP/usr/lib/libmbedcrypto.so.5 \
@@ -203,8 +199,6 @@ if { tar tf "$PAT"; } >/dev/null 2>&1; then
else else
[ "$ARCH" != "amd64" ] && addPackage "qemu-user" "QEMU"
info "Install: Extracting downloaded image..." info "Install: Extracting downloaded image..."
export LD_LIBRARY_PATH="/run/extract" export LD_LIBRARY_PATH="/run/extract"
@@ -240,26 +234,26 @@ SYSTEM_SIZE=4954537983
# Check free diskspace # Check free diskspace
SPACE=$(df --output=avail -B 1 "$TMP" | tail -n 1) SPACE=$(df --output=avail -B 1 "$TMP" | tail -n 1)
SPACE_GB=$(( (SPACE + 1073741823)/1073741824 )) SPACE_GB=$(( (SPACE + 1073741823)/1073741824 ))
(( SYSTEM_SIZE > SPACE )) && error "Not enough free space to create a 4 GB system disk, have only ${SPACE_GB} GB available." && exit 87 (( SYSTEM_SIZE > SPACE )) && error "Not enough free space to create a 4 GB system disk, have only $SPACE_GB GB available." && exit 97
if ! fallocate -l "${SYSTEM_SIZE}" "${SYSTEM}"; then if ! fallocate -l "$SYSTEM_SIZE" "$SYSTEM"; then
if ! truncate -s "${SYSTEM_SIZE}" "${SYSTEM}"; then if ! truncate -s "$SYSTEM_SIZE" "$SYSTEM"; then
rm -f "${SYSTEM}" && error "Could not allocate a file for the system disk." && exit 88 rm -f "$SYSTEM" && error "Could not allocate a file for the system disk." && exit 98
fi fi
fi fi
# Check if file exists # Check if file exists
[ ! -f "${SYSTEM}" ] && error "System disk does not exist ($SYSTEM)" && exit 89 [ ! -f "$SYSTEM" ] && error "System disk does not exist ($SYSTEM)" && exit 99
# Check the filesize # Check the filesize
SIZE=$(stat -c%s "${SYSTEM}") SIZE=$(stat -c%s "$SYSTEM")
[[ SIZE -ne SYSTEM_SIZE ]] && rm -f "${SYSTEM}" && error "System disk has the wrong size: ${SIZE}" && exit 90 [[ SIZE -ne SYSTEM_SIZE ]] && rm -f "$SYSTEM" && error "System disk has the wrong size: $SIZE" && exit 90
PART="$TMP/partition.fdisk" PART="$TMP/partition.fdisk"
{ echo "label: dos" { echo "label: dos"
echo "label-id: 0x6f9ee2e9" echo "label-id: 0x6f9ee2e9"
echo "device: ${SYSTEM}" echo "device: $SYSTEM"
echo "unit: sectors" echo "unit: sectors"
echo "sector-size: 512" echo "sector-size: 512"
echo "" echo ""
@@ -276,7 +270,7 @@ rm -rf "$MOUNT" && mkdir -p "$MOUNT"
mv "$HDA.tgz" "$HDA.txz" mv "$HDA.tgz" "$HDA.txz"
if [[ "${DEV}" == [Nn]* ]]; then if [[ "$DEV" == [Nn]* ]]; then
# Exclude dev/ from tar extract # Exclude dev/ from tar extract
tar xpfJ "$HDA.txz" --absolute-names --exclude="dev" -C "$MOUNT/" tar xpfJ "$HDA.txz" --absolute-names --exclude="dev" -C "$MOUNT/"
else else
@@ -301,7 +295,7 @@ rm -rf "$MOUNT"
echo "$BASE" > "$STORAGE"/dsm.ver echo "$BASE" > "$STORAGE"/dsm.ver
if [[ "$URL" == "file://${STORAGE}/${BASE}.pat" ]]; then if [[ "$URL" == "file://$STORAGE/$BASE.pat" ]]; then
rm -f "$PAT" rm -f "$PAT"
else else
mv -f "$PAT" "$STORAGE"/"$BASE".pat mv -f "$PAT" "$STORAGE"/"$BASE".pat
@@ -313,6 +307,6 @@ mv -f "$SYSTEM" "$STORAGE"/"$BASE".system.img
rm -rf "$TMP" rm -rf "$TMP"
{ set +x; } 2>/dev/null { set +x; } 2>/dev/null
[[ "${DEBUG}" == [Yy1]* ]] && echo [[ "$DEBUG" == [Yy1]* ]] && echo
return 0 return 0

View File

@@ -23,37 +23,38 @@ configureDHCP() {
# Create a macvtap network for the VM guest # Create a macvtap network for the VM guest
{ ip link add link "${VM_NET_DEV}" name "${VM_NET_TAP}" address "${VM_NET_MAC}" type macvtap mode bridge ; rc=$?; } || : { ip link add link "$VM_NET_DEV" name "$VM_NET_TAP" address "$VM_NET_MAC" type macvtap mode bridge ; rc=$?; } || :
if (( rc != 0 )); then if (( rc != 0 )); then
error "Cannot create macvtap interface. Please make sure the network type is 'macvlan' and not 'ipvlan'," error "Cannot create macvtap interface. Please make sure the network type is 'macvlan' and not 'ipvlan',"
error "and that the NET_ADMIN capability has been added to the container config: --cap-add NET_ADMIN" && exit 16 error "and that the NET_ADMIN capability has been added to the container config: --cap-add NET_ADMIN" && exit 16
fi fi
while ! ip link set "${VM_NET_TAP}" up; do while ! ip link set "$VM_NET_TAP" up; do
info "Waiting for address to become available..." info "Waiting for address to become available..."
sleep 2 sleep 2
done done
TAP_NR=$(</sys/class/net/"${VM_NET_TAP}"/ifindex) local TAP_NR TAP_PATH MAJOR MINOR
TAP_NR=$(</sys/class/net/"$VM_NET_TAP"/ifindex)
TAP_PATH="/dev/tap${TAP_NR}" TAP_PATH="/dev/tap${TAP_NR}"
# Create dev file (there is no udev in container: need to be done manually) # Create dev file (there is no udev in container: need to be done manually)
IFS=: read -r MAJOR MINOR < <(cat /sys/devices/virtual/net/"${VM_NET_TAP}"/tap*/dev) 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}" && exit 18 (( MAJOR < 1)) && error "Cannot find: sys/devices/virtual/net/$VM_NET_TAP" && exit 18
[[ ! -e "${TAP_PATH}" ]] && [[ -e "/dev0/${TAP_PATH##*/}" ]] && ln -s "/dev0/${TAP_PATH##*/}" "${TAP_PATH}" [[ ! -e "$TAP_PATH" ]] && [[ -e "/dev0/${TAP_PATH##*/}" ]] && ln -s "/dev0/${TAP_PATH##*/}" "$TAP_PATH"
if [[ ! -e "${TAP_PATH}" ]]; then if [[ ! -e "$TAP_PATH" ]]; then
{ mknod "${TAP_PATH}" c "$MAJOR" "$MINOR" ; rc=$?; } || : { mknod "$TAP_PATH" c "$MAJOR" "$MINOR" ; rc=$?; } || :
(( rc != 0 )) && error "Cannot mknod: ${TAP_PATH} ($rc)" && exit 20 (( rc != 0 )) && error "Cannot mknod: $TAP_PATH ($rc)" && exit 20
fi fi
{ exec 30>>"$TAP_PATH"; rc=$?; } 2>/dev/null || : { exec 30>>"$TAP_PATH"; rc=$?; } 2>/dev/null || :
if (( rc != 0 )); then if (( rc != 0 )); then
error "Cannot create TAP interface ($rc). Please add the following docker settings to your " error "Cannot create TAP interface ($rc). Please add the following docker settings to your "
error "container: --device-cgroup-rule='c ${MAJOR}:* rwm' --device=/dev/vhost-net" && exit 21 error "container: --device-cgroup-rule='c $MAJOR:* rwm' --device=/dev/vhost-net" && exit 21
fi fi
{ exec 40>>/dev/vhost-net; rc=$?; } 2>/dev/null || : { exec 40>>/dev/vhost-net; rc=$?; } 2>/dev/null || :
@@ -74,17 +75,17 @@ configureDNS () {
DNSMASQ_OPTS="$DNSMASQ_OPTS --dhcp-range=$VM_NET_IP,$VM_NET_IP --dhcp-host=$VM_NET_MAC,,$VM_NET_IP,$VM_NET_HOST,infinite --dhcp-option=option:netmask,255.255.255.0" DNSMASQ_OPTS="$DNSMASQ_OPTS --dhcp-range=$VM_NET_IP,$VM_NET_IP --dhcp-host=$VM_NET_MAC,,$VM_NET_IP,$VM_NET_HOST,infinite --dhcp-option=option:netmask,255.255.255.0"
# Create lease file for faster resolve # Create lease file for faster resolve
echo "0 $VM_NET_MAC $VM_NET_IP $VM_NET_HOST 01:${VM_NET_MAC}" > /var/lib/misc/dnsmasq.leases echo "0 $VM_NET_MAC $VM_NET_IP $VM_NET_HOST 01:$VM_NET_MAC" > /var/lib/misc/dnsmasq.leases
chmod 644 /var/lib/misc/dnsmasq.leases chmod 644 /var/lib/misc/dnsmasq.leases
# Set DNS server and gateway # Set DNS server and gateway
DNSMASQ_OPTS="$DNSMASQ_OPTS --dhcp-option=option:dns-server,${VM_NET_IP%.*}.1 --dhcp-option=option:router,${VM_NET_IP%.*}.1" DNSMASQ_OPTS="$DNSMASQ_OPTS --dhcp-option=option:dns-server,${VM_NET_IP%.*}.1 --dhcp-option=option:router,${VM_NET_IP%.*}.1"
DNSMASQ_OPTS=$(echo "$DNSMASQ_OPTS" | sed 's/\t/ /g' | tr -s ' ' | sed 's/^ *//') DNSMASQ_OPTS=$(echo "$DNSMASQ_OPTS" | sed 's/\t/ /g' | tr -s ' ' | sed 's/^ *//')
[[ "${DEBUG}" == [Yy1]* ]] && set -x [[ "$DEBUG" == [Yy1]* ]] && set -x
$DNSMASQ ${DNSMASQ_OPTS:+ $DNSMASQ_OPTS} $DNSMASQ ${DNSMASQ_OPTS:+ $DNSMASQ_OPTS}
{ set +x; } 2>/dev/null { set +x; } 2>/dev/null
[[ "${DEBUG}" == [Yy1]* ]] && echo [[ "$DEBUG" == [Yy1]* ]] && echo
return 0 return 0
} }
@@ -94,7 +95,7 @@ configureNAT () {
# Create a bridge with a static IP for the VM guest # Create a bridge with a static IP for the VM guest
VM_NET_IP='20.20.20.21' VM_NET_IP='20.20.20.21'
[[ "${DEBUG}" == [Yy1]* ]] && set -x [[ "$DEBUG" == [Yy1]* ]] && set -x
{ ip link add dev dockerbridge type bridge ; rc=$?; } || : { ip link add dev dockerbridge type bridge ; rc=$?; } || :
@@ -111,19 +112,19 @@ configureNAT () {
done done
# QEMU Works with taps, set tap to the bridge created # QEMU Works with taps, set tap to the bridge created
ip tuntap add dev "${VM_NET_TAP}" mode tap ip tuntap add dev "$VM_NET_TAP" mode tap
while ! ip link set "${VM_NET_TAP}" up promisc on; do while ! ip link set "$VM_NET_TAP" up promisc on; do
info "Waiting for tap to become available..." info "Waiting for tap to become available..."
sleep 2 sleep 2
done done
ip link set dev "${VM_NET_TAP}" master dockerbridge ip link set dev "$VM_NET_TAP" master dockerbridge
# Add internet connection to the VM # Add internet connection to the VM
iptables -t nat -A POSTROUTING -o "${VM_NET_DEV}" -j MASQUERADE iptables -t nat -A POSTROUTING -o "$VM_NET_DEV" -j MASQUERADE
iptables -t nat -A PREROUTING -i "${VM_NET_DEV}" -d "${IP}" -p tcp -j DNAT --to $VM_NET_IP iptables -t nat -A PREROUTING -i "$VM_NET_DEV" -d "$IP" -p tcp -j DNAT --to "$VM_NET_IP"
iptables -t nat -A PREROUTING -i "${VM_NET_DEV}" -d "${IP}" -p udp -j DNAT --to $VM_NET_IP iptables -t nat -A PREROUTING -i "$VM_NET_DEV" -d "$IP" -p udp -j DNAT --to "$VM_NET_IP"
if (( KERNEL > 4 )); then if (( KERNEL > 4 )); then
# Hack for guest VMs complaining about "bad udp checksums in 5 packets" # Hack for guest VMs complaining about "bad udp checksums in 5 packets"
@@ -131,7 +132,7 @@ configureNAT () {
fi fi
{ set +x; } 2>/dev/null { set +x; } 2>/dev/null
[[ "${DEBUG}" == [Yy1]* ]] && echo [[ "$DEBUG" == [Yy1]* ]] && echo
# Check port forwarding flag # Check port forwarding flag
if [[ $(< /proc/sys/net/ipv4/ip_forward) -eq 0 ]]; then if [[ $(< /proc/sys/net/ipv4/ip_forward) -eq 0 ]]; then
@@ -141,7 +142,7 @@ configureNAT () {
fi fi
fi fi
NET_OPTS="-netdev tap,ifname=${VM_NET_TAP},script=no,downscript=no,id=hostnet0" NET_OPTS="-netdev tap,ifname=$VM_NET_TAP,script=no,downscript=no,id=hostnet0"
{ exec 40>>/dev/vhost-net; rc=$?; } 2>/dev/null || : { exec 40>>/dev/vhost-net; rc=$?; } 2>/dev/null || :
(( rc == 0 )) && NET_OPTS="$NET_OPTS,vhost=on,vhostfd=40" (( rc == 0 )) && NET_OPTS="$NET_OPTS,vhost=on,vhostfd=40"
@@ -153,15 +154,19 @@ configureNAT () {
closeNetwork () { closeNetwork () {
if [[ "${DHCP}" == [Yy1]* ]]; then if [[ "$DHCP" == [Yy1]* ]]; then
ip link set "${VM_NET_TAP}" down || true { pkill -f server.sh || true; } 2>/dev/null
ip link delete "${VM_NET_TAP}" || true
ip link set "$VM_NET_TAP" down || true
ip link delete "$VM_NET_TAP" || true
else else
ip link set "${VM_NET_TAP}" down promisc off || true { pkill -f dnsmasq || true; } 2>/dev/null
ip link delete "${VM_NET_TAP}" || true
ip link set "$VM_NET_TAP" down promisc off || true
ip link delete "$VM_NET_TAP" || true
ip link set dockerbridge down || true ip link set dockerbridge down || true
ip link delete dockerbridge || true ip link delete dockerbridge || true
@@ -182,7 +187,7 @@ if [ ! -c /dev/net/tun ]; then
chmod 666 /dev/net/tun chmod 666 /dev/net/tun
fi fi
[ ! -c /dev/net/tun ] && error "TUN network interface not available..." && exit 85 [ ! -c /dev/net/tun ] && error "TUN network interface not available..." && exit 25
# Create the necessary file structure for /dev/vhost-net # Create the necessary file structure for /dev/vhost-net
if [ ! -c /dev/vhost-net ]; then if [ ! -c /dev/vhost-net ]; then
@@ -195,19 +200,19 @@ update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy > /dev/null
VM_NET_MAC="${VM_NET_MAC//-/:}" VM_NET_MAC="${VM_NET_MAC//-/:}"
GATEWAY=$(ip r | grep default | awk '{print $3}') GATEWAY=$(ip r | grep default | awk '{print $3}')
IP=$(ip address show dev "${VM_NET_DEV}" | grep inet | awk '/inet / { print $2 }' | cut -f1 -d/) IP=$(ip address show dev "$VM_NET_DEV" | grep inet | awk '/inet / { print $2 }' | cut -f1 -d/)
if [[ "${DEBUG}" == [Yy1]* ]]; then if [[ "$DEBUG" == [Yy1]* ]]; then
info "Container IP is ${IP} with gateway ${GATEWAY}" && echo info "Container IP is $IP with gateway $GATEWAY" && echo
fi fi
if [[ "${DHCP}" == [Yy1]* ]]; then if [[ "$DHCP" == [Yy1]* ]]; then
if [[ "$GATEWAY" == "172."* ]]; then if [[ "$GATEWAY" == "172."* ]]; then
if [[ "${DEBUG}" == [Yy1]* ]]; then if [[ "$DEBUG" == [Yy1]* ]]; then
info "Warning: Are you sure the container is on a macvlan network?" info "Warning: Are you sure the container is on a macvlan network?"
else else
error "You can only enable DHCP while the container is on a macvlan network!" && exit 86 error "You can only enable DHCP while the container is on a macvlan network!" && exit 26
fi fi
fi fi
@@ -224,6 +229,6 @@ else
fi fi
NET_OPTS="${NET_OPTS} -device virtio-net-pci,romfile=,netdev=hostnet0,mac=${VM_NET_MAC},id=net0" NET_OPTS="$NET_OPTS -device virtio-net-pci,romfile=,netdev=hostnet0,mac=$VM_NET_MAC,id=net0"
return 0 return 0

View File

@@ -8,8 +8,8 @@ QEMU_TIMEOUT=50
QEMU_PID=/run/qemu.pid QEMU_PID=/run/qemu.pid
QEMU_COUNT=/run/qemu.count QEMU_COUNT=/run/qemu.count
rm -f "${QEMU_PID}" rm -f "$QEMU_PID"
rm -f "${QEMU_COUNT}" rm -f "$QEMU_COUNT"
_trap(){ _trap(){
func="$1" ; shift func="$1" ; shift
@@ -21,53 +21,64 @@ _trap(){
_graceful_shutdown() { _graceful_shutdown() {
set +e set +e
local cnt response
[ ! -f "${QEMU_PID}" ] && exit 130 [ ! -f "$QEMU_PID" ] && exit 130
[ -f "${QEMU_COUNT}" ] && return [ -f "$QEMU_COUNT" ] && return
echo 0 > "${QEMU_COUNT}" echo 0 > "$QEMU_COUNT"
echo && info "Received $1 signal, shutting down..." echo && info "Received $1 signal, sending shutdown command..."
# Don't send the powerdown signal because vDSM ignores ACPI signals # Don't send the powerdown signal because vDSM ignores ACPI signals
# echo 'system_powerdown' | nc -q 1 -w 1 localhost "${QEMU_PORT}" > /dev/null # echo 'system_powerdown' | nc -q 1 -w 1 localhost "${QEMU_PORT}" > /dev/null
# Send shutdown command to guest agent via serial port # Send shutdown command to guest agent via serial port
RESPONSE=$(curl -s -m 30 -S http://127.0.0.1:2210/read?command=6 2>&1) url="http://127.0.0.1:2210/read?command=6&timeout=50"
response=$(curl -sk -m 52 -S "$url" 2>&1)
if [[ ! "${RESPONSE}" =~ "\"success\"" ]]; then if [[ "$response" =~ "\"success\"" ]]; then
echo && error "Failed to send shutdown command ( ${RESPONSE} )." echo && info "Virtual DSM is now ready to shutdown..."
kill -15 "$(cat "${QEMU_PID}")" else
response="${response#*message\"\: \"}"
echo && error "Failed to send shutdown command: ${response%%\"*}"
kill -15 "$(cat "$QEMU_PID")"
pkill -f qemu-system-x86_64 || true pkill -f qemu-system-x86_64 || true
fi fi
while [ "$(cat ${QEMU_COUNT})" -lt "${QEMU_TIMEOUT}" ]; do while [ "$(cat $QEMU_COUNT)" -lt "$QEMU_TIMEOUT" ]; do
# Increase the counter # Increase the counter
echo $(($(cat ${QEMU_COUNT})+1)) > ${QEMU_COUNT} echo $(($(cat $QEMU_COUNT)+1)) > "$QEMU_COUNT"
# Try to connect to qemu # Try to connect to qemu
if echo 'info version'| nc -q 1 -w 1 localhost "${QEMU_PORT}" >/dev/null 2>&1 ; then if echo 'info version'| nc -q 1 -w 1 localhost "$QEMU_PORT" >/dev/null 2>&1 ; then
sleep 1 sleep 1
CNT="$(cat ${QEMU_COUNT})/${QEMU_TIMEOUT}" cnt="$(cat $QEMU_COUNT)/$QEMU_TIMEOUT"
[[ "${DEBUG}" == [Yy1]* ]] && info "Shutting down, waiting... (${CNT})" [[ "$DEBUG" == [Yy1]* ]] && info "Shutting down, waiting... ($cnt)"
fi fi
done done
echo && echo " Quitting..." echo && echo " Quitting..."
echo 'quit' | nc -q 1 -w 1 localhost "${QEMU_PORT}" >/dev/null 2>&1 || true echo 'quit' | nc -q 1 -w 1 localhost "$QEMU_PORT" >/dev/null 2>&1 || true
{ pkill -f print.sh || true; } 2>/dev/null
{ pkill -f host.bin || true; } 2>/dev/null
closeNetwork closeNetwork
sleep 1
return return
} }
_trap _graceful_shutdown SIGTERM SIGHUP SIGINT SIGABRT SIGQUIT _trap _graceful_shutdown SIGTERM SIGHUP SIGINT SIGABRT SIGQUIT
MON_OPTS="-monitor telnet:localhost:${QEMU_PORT},server,nowait,nodelay" MON_OPTS="-monitor telnet:localhost:$QEMU_PORT,server,nowait,nodelay"

View File

@@ -1,66 +1,80 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -Eeuo pipefail set -Eeuo pipefail
info () { echo -e >&2 "\E[1;34m\E[1;36m $1\E[0m" ; } info () { printf "%b%s%b" "\E[1;34m \E[1;36m" "$1" "\E[0m\n" >&2; }
error () { echo -e >&2 "\E[1;31m ERROR: $1\E[0m" ; } error () { printf "%b%s%b" "\E[1;31m " "ERROR: $1" "\E[0m\n" >&2; }
file="/run/dsm.url" file="/run/dsm.url"
shutdown="/run/qemu.count"
url="http://127.0.0.1:2210/read?command=10"
resp_err="Guest returned an invalid response:"
jq_err="Failed to parse response from guest: jq error"
while [ ! -f "$file" ] while [ ! -f "$file" ]
do do
# Check if not shutting down
[ -f "$shutdown" ] && exit 1
sleep 3 sleep 3
[ -f "$file" ] && continue
[ -f "$shutdown" ] && exit 1
# Healthcheck may have intervened
[ -f "$file" ] && break
# Retrieve IP from guest VM # Retrieve IP from guest VM
{ json=$(curl -m 20 -sk "$url"); rc=$?; } || :
set +e [ -f "$shutdown" ] && exit 1
RESPONSE=$(curl -s -m 30 -S http://127.0.0.1:2210/read?command=10 2>&1) (( rc != 0 )) && error "Failed to connect to guest: curl error $rc" && continue
set -e
if [[ ! "${RESPONSE}" =~ "\"success\"" ]] ; then { result=$(echo "$json" | jq -r '.status'); rc=$?; } || :
error "Failed to connect to guest: $RESPONSE" && continue (( 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
fi fi
# Retrieve the HTTP port number { port=$(echo "$json" | jq -r '.data.data.dsm_setting.data.http_port'); rc=$?; } || :
if [[ ! "${RESPONSE}" =~ "\"http_port\"" ]] ; then (( rc != 0 )) && error "$jq_err $rc ( $json )" && continue
error "Failed to parse response from guest: $RESPONSE" && continue [[ "$port" == "null" ]] && error "$resp_err $json" && continue
fi [ -z "$port" ] && continue
rest=${RESPONSE#*http_port} { ip=$(echo "$json" | jq -r '.data.data.ip.data[] | select((.name=="eth0") and has("ip")).ip'); rc=$?; } || :
rest=${rest#*:} (( rc != 0 )) && error "$jq_err $rc ( $json )" && continue
rest=${rest%%,*} [[ "$ip" == "null" ]] && error "$resp_err $json" && continue
PORT=${rest%%\"*} [ -z "$ip" ] && continue
[ -z "${PORT}" ] && continue echo "$ip:$port" > $file
# Retrieve the IP address
if [[ ! "${RESPONSE}" =~ "eth0" ]] ; then
error "Failed to parse response from guest: $RESPONSE" && continue
fi
rest=${RESPONSE#*eth0}
rest=${rest#*ip}
rest=${rest#*:}
rest=${rest#*\"}
IP=${rest%%\"*}
[ -z "${IP}" ] && continue
echo "${IP}:${PORT}" > $file
done done
LOCATION=$(cat "$file") [ -f "$shutdown" ] && exit 1
location=$(cat "$file")
if [[ "$location" != "20.20"* ]]; then
msg="http://$location"
if [[ "$LOCATION" == "20.20"* ]]; then
MSG="port ${LOCATION##*:}"
else else
MSG="http://${LOCATION}"
ip=$(ip address show dev eth0 | grep inet | awk '/inet / { print $2 }' | cut -f1 -d/)
port="${location##*:}"
if [[ "$ip" == "172."* ]]; then
msg="port $port"
else
msg="http://$ip:$port"
fi
fi fi
echo "" >&2 echo "" >&2
info "--------------------------------------------------------" info "-----------------------------------------------------------"
info " You can now login to DSM at ${MSG}" info " You can now login to DSM at $msg"
info "--------------------------------------------------------" info "-----------------------------------------------------------"
echo "" >&2 echo "" >&2

View File

@@ -1,19 +1,21 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -Eeuo pipefail set -Eeuo pipefail
info () { echo -e "\E[1;34m \E[1;36m$1\E[0m" ; } info () { printf "%b%s%b" "\E[1;34m \E[1;36m" "$1" "\E[0m\n"; }
error () { echo -e >&2 "\E[1;31m ERROR: $1\E[0m" ; } error () { printf "%b%s%b" "\E[1;31m " "ERROR: $1" "\E[0m\n" >&2; }
trap 'error "Status $? while: ${BASH_COMMAND} (line $LINENO/$BASH_LINENO)"' ERR
trap 'error "Status $? while: $BASH_COMMAND (line $LINENO/$BASH_LINENO)"' ERR
[ ! -f "/run/entry.sh" ] && error "Script must run inside Docker container!" && exit 11 [ ! -f "/run/entry.sh" ] && error "Script must run inside Docker container!" && exit 11
[ "$(id -u)" -ne "0" ] && error "Script must be executed with root privileges." && exit 12 [ "$(id -u)" -ne "0" ] && error "Script must be executed with root privileges." && exit 12
# Docker environment variables # Docker environment variables
: ${GPU:='N'} # Enable GPU passthrough : ${GPU:='N'} # Disable GPU passthrough
: ${DEBUG:='N'} # Enable debugging mode : ${KVM:='Y'} # Enable KVM acceleration
: ${DEBUG:='N'} # Disable debugging mode
: ${COUNTRY:=''} # Country code for mirror : ${COUNTRY:=''} # Country code for mirror
: ${CONSOLE:='N'} # Start in console mode : ${CONSOLE:='N'} # Disable console mode
: ${ALLOCATE:='Y'} # Preallocate diskspace : ${ALLOCATE:='Y'} # Preallocate diskspace
: ${ARGUMENTS:=''} # Extra QEMU parameters : ${ARGUMENTS:=''} # Extra QEMU parameters
: ${CPU_CORES:='1'} # Amount of CPU cores : ${CPU_CORES:='1'} # Amount of CPU cores
@@ -30,7 +32,7 @@ VERS=$(qemu-system-x86_64 --version | head -n 1 | cut -d '(' -f 1)
# Check folder # Check folder
STORAGE="/storage" STORAGE="/storage"
[ ! -d "$STORAGE" ] && error "Storage folder (${STORAGE}) not found!" && exit 13 [ ! -d "$STORAGE" ] && error "Storage folder ($STORAGE) not found!" && exit 13
# Cleanup files # Cleanup files
@@ -47,11 +49,9 @@ rm -rf "$STORAGE/tmp"
getCountry () { getCountry () {
local rc
local json
local result
local url=$1 local url=$1
local query=$2 local query=$2
local rc json result
{ json=$(curl -m 5 -H "Accept: application/json" -sfk "$url"); rc=$?; } || : { json=$(curl -m 5 -H "Accept: application/json" -sfk "$url"); rc=$?; } || :
(( rc != 0 )) && return 0 (( rc != 0 )) && return 0

View File

@@ -1,39 +1,24 @@
#!/bin/bash #!/usr/bin/env bash
set -Eeuo pipefail set -Eeuo pipefail
# Docker environment variables # Docker environment variables
: ${HOST_CPU:=''}
: ${HOST_MAC:=''} : ${HOST_MAC:=''}
: ${HOST_DEBUG:=''} : ${HOST_DEBUG:=''}
: ${HOST_SERIAL:=''} : ${HOST_SERIAL:=''}
: ${HOST_MODEL:=''} : ${HOST_MODEL:=''}
: ${GUEST_SERIAL:=''} : ${GUEST_SERIAL:=''}
if [ -z "$HOST_CPU" ]; then
HOST_CPU=$(lscpu | grep '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 [ -n "$HOST_CPU" ]; then
HOST_CPU="$HOST_CPU,,"
else
if [ "$ARCH" == "amd64" ]; then
HOST_CPU="QEMU, Virtual CPU, X86_64"
else
HOST_CPU="QEMU, Virtual CPU, $ARCH"
fi
fi
HOST_ARGS=() HOST_ARGS=()
HOST_ARGS+=("-cpu=${CPU_CORES}") HOST_ARGS+=("-cpu=$CPU_CORES")
HOST_ARGS+=("-cpu_arch=${HOST_CPU}") HOST_ARGS+=("-cpu_arch=$HOST_CPU")
[ -n "$HOST_MAC" ] && HOST_ARGS+=("-mac=${HOST_MAC}") [ -n "$HOST_MAC" ] && HOST_ARGS+=("-mac=$HOST_MAC")
[ -n "$HOST_MODEL" ] && HOST_ARGS+=("-model=${HOST_MODEL}") [ -n "$HOST_MODEL" ] && HOST_ARGS+=("-model=$HOST_MODEL")
[ -n "$HOST_SERIAL" ] && HOST_ARGS+=("-hostsn=${HOST_SERIAL}") [ -n "$HOST_SERIAL" ] && HOST_ARGS+=("-hostsn=$HOST_SERIAL")
[ -n "$GUEST_SERIAL" ] && HOST_ARGS+=("-guestsn=${GUEST_SERIAL}") [ -n "$GUEST_SERIAL" ] && HOST_ARGS+=("-guestsn=$GUEST_SERIAL")
if [[ "${HOST_DEBUG}" == [Yy1]* ]]; then if [[ "$HOST_DEBUG" == [Yy1]* ]]; then
set -x set -x
./host.bin "${HOST_ARGS[@]}" & ./host.bin "${HOST_ARGS[@]}" &
{ set +x; } 2>/dev/null { set +x; } 2>/dev/null

View File

@@ -14,9 +14,9 @@ trap 'stop' EXIT SIGINT SIGTERM SIGHUP
html() html()
{ {
local h="<!DOCTYPE html><HTML><HEAD><TITLE>VirtualDSM</TITLE>" local h="<!DOCTYPE html><HTML><HEAD><TITLE>VirtualDSM</TITLE>"
h="${h} <STYLE>body { color: white; background-color: #125bdb; font-family: Verdana," h="$h<STYLE>body { color: white; background-color: #125bdb; font-family: Verdana,"
h="${h} Arial,sans-serif; } a, a:hover, a:active, a:visited { color: white; }</STYLE></HEAD>" h="$h Arial,sans-serif; } a, a:hover, a:active, a:visited { color: white; }</STYLE></HEAD>"
h="${h}<BODY><BR><BR><H1><CENTER>$1</CENTER></H1></BODY></HTML>" h="$h<BODY><BR><BR><H1><CENTER>$1</CENTER></H1></BODY></HTML>"
echo "$h" echo "$h"
} }
@@ -33,8 +33,8 @@ if [[ "$2" != "/"* ]]; then
HTML=$(html "$BODY") HTML=$(html "$BODY")
printf '%b' "HTTP/1.1 200 OK\nContent-Length: ${#HTML}\nConnection: close\n\n$HTML" > "$TMP_FILE" printf '%b' "HTTP/1.1 200 OK\nContent-Length: ${#HTML}\nConnection: close\n\n$HTML" > "$TMP_FILE"
socat TCP4-LISTEN:80,reuseaddr,fork,crlf SYSTEM:"cat ${TMP_FILE}" 2> /dev/null & socat TCP4-LISTEN:80,reuseaddr,fork,crlf SYSTEM:"cat $TMP_FILE" 2> /dev/null &
socat TCP4-LISTEN:"${1:-5000}",reuseaddr,fork,crlf SYSTEM:"cat ${TMP_FILE}" 2> /dev/null & wait $! socat TCP4-LISTEN:"${1:-5000}",reuseaddr,fork,crlf SYSTEM:"cat $TMP_FILE" 2> /dev/null & wait $!
exit exit
@@ -46,15 +46,15 @@ if [[ "$2" != "/run/ip.sh" ]]; then
else else
BODY="The location of DSM is <a href='http://\${LOCATION}'>http://\${LOCATION}</a><script>" BODY="The location of DSM is <a href='http://\$LOCATION'>http://\$LOCATION</a><script>"
BODY="${BODY}setTimeout(function(){ window.location.assign('http://\${LOCATION}'); }, 3000);</script>" BODY="$BODY setTimeout(function(){ window.location.assign('http://\$LOCATION'); }, 3000);</script>"
WAIT="Please wait while discovering IP...<script>setTimeout(() => { document.location.reload(); }, 4999);</script>" WAIT="Please wait while discovering IP...<script>setTimeout(() => { document.location.reload(); }, 4999);</script>"
HTML=$(html "xxx") HTML=$(html "xxx")
{ echo "#!/bin/bash" { echo "#!/bin/bash"
echo "[ -f \"/run/dsm.url\" ] && LOCATION=\$(cat \"/run/dsm.url\")" echo "[ -f \"/run/dsm.url\" ] && LOCATION=\$(cat \"/run/dsm.url\")"
echo "HTML=\"$HTML\"; [ -z \"\${LOCATION}\" ] && BODY=\"$WAIT\" || BODY=\"$BODY\"; HTML=\${HTML/xxx/\$BODY}" echo "HTML=\"$HTML\"; [ -z \"\$LOCATION\" ] && BODY=\"$WAIT\" || BODY=\"$BODY\"; HTML=\${HTML/xxx/\$BODY}"
echo "printf '%b' \"HTTP/1.1 200 OK\\nContent-Length: \${#HTML}\\nConnection: close\\n\\n\$HTML\"" echo "printf '%b' \"HTTP/1.1 200 OK\\nContent-Length: \${#HTML}\\nConnection: close\\n\\n\$HTML\""
} > "$TMP_FILE" } > "$TMP_FILE"