Initial commit

Initial commit
This commit is contained in:
Kroese 2023-04-12 00:47:47 +02:00 committed by GitHub
commit 50a89476c3
17 changed files with 47 additions and 737 deletions

View file

@ -1,27 +1,11 @@
FROM golang:1.20 AS builder
COPY serial/ /src/serial/
WORKDIR /src/serial
RUN go get -d -v golang.org/x/net/html
RUN go get -d -v github.com/gorilla/mux
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o /src/serial/main .
FROM debian:bookworm-20230320-slim FROM debian:bookworm-20230320-slim
RUN apt-get update && apt-get -y upgrade && \ RUN apt-get update && apt-get -y upgrade && \
apt-get --no-install-recommends -y install \ apt-get --no-install-recommends -y install \
jq \
curl \
cpio \
wget \ wget \
unzip \
procps \
dnsmasq \ dnsmasq \
iptables \ iptables \
iproute2 \ iproute2 \
xz-utils \
btrfs-progs \
bridge-utils \ bridge-utils \
netcat-openbsd \ netcat-openbsd \
ca-certificates \ ca-certificates \
@ -31,43 +15,20 @@ RUN apt-get update && apt-get -y upgrade && \
COPY run.sh /run/ COPY run.sh /run/
COPY disk.sh /run/ COPY disk.sh /run/
COPY power.sh /run/ COPY power.sh /run/
COPY serial.sh /run/
COPY server.sh /run/
COPY install.sh /run/ COPY install.sh /run/
COPY network.sh /run/ COPY network.sh /run/
COPY agent/agent.sh /agent/
COPY agent/service.sh /agent/
COPY --from=builder /src/serial/main /run/serial.bin
RUN ["chmod", "+x", "/run/run.sh"] RUN ["chmod", "+x", "/run/run.sh"]
RUN ["chmod", "+x", "/run/disk.sh"]
RUN ["chmod", "+x", "/run/power.sh"]
RUN ["chmod", "+x", "/run/serial.sh"]
RUN ["chmod", "+x", "/run/server.sh"]
RUN ["chmod", "+x", "/run/install.sh"] RUN ["chmod", "+x", "/run/install.sh"]
RUN ["chmod", "+x", "/run/network.sh"]
RUN ["chmod", "+x", "/run/serial.bin"]
COPY disks/template.img.xz /data/
VOLUME /storage VOLUME /storage
EXPOSE 22 EXPOSE 22
EXPOSE 80
EXPOSE 139
EXPOSE 443
EXPOSE 445
EXPOSE 5000
EXPOSE 5001
ENV CPU_CORES 1 ENV CPU_CORES 1
ENV DISK_SIZE 16G ENV DISK_SIZE 16G
ENV RAM_SIZE 512M ENV RAM_SIZE 512M
#ENV URL https://global.synologydownload.com/download/DSM/beta/7.2/64216/DSM_VirtualDSM_64216.pat ENV BOOT https://ftp.halifax.rwth-aachen.de/osdn/clonezilla/78259/clonezilla-live-3.0.3-22-amd64.iso
#ENV URL https://global.synologydownload.com/download/DSM/release/7.0.1/42218/DSM_VirtualDSM_42218.pat
ENV URL https://global.synologydownload.com/download/DSM/release/7.1.1/42962-1/DSM_VirtualDSM_42962.pat
ENTRYPOINT ["/run/run.sh"] ENTRYPOINT ["/run/run.sh"]

View file

@ -1,65 +0,0 @@
#!/usr/bin/env bash
set -u
declare nmi
function checkNMI {
nmi=$(cat /proc/interrupts | grep NMI)
nmi=$(echo "$nmi" | sed 's/[^0-9]*//g')
nmi=$(echo "$nmi" | sed 's/^0*//')
if [ "$nmi" != "" ]; then
echo "Received shutdown request through NMI.." > /dev/ttyS0
/usr/syno/sbin/synoshutdown -s > /dev/null
exit 0
fi
}
chmod 666 /dev/ttyS0
checkNMI
first_run=false
for filename in /usr/local/packages/*.spk; do
if [ -f "$filename" ]; then
first_run=true
fi
done
if [ "$first_run" = true ]; then
for filename in /usr/local/packages/*.spk; do
if [ -f "$filename" ]; then
/usr/syno/bin/synopkg install "$filename" > /dev/null
BASE=$(basename "$filename" .spk)
BASE="${BASE%%-*}"
/usr/syno/bin/synopkg start "$BASE" > /dev/null
rm "$filename"
fi
done
else
sleep 5
fi
echo "-------------------------------------------" > /dev/ttyS0
echo " You can now login to DSM at port 5000 " > /dev/ttyS0
echo "-------------------------------------------" > /dev/ttyS0
while true; do
checkNMI
sleep 1
done

View file

@ -1,49 +0,0 @@
#!/bin/bash
PIDFILE="/var/run/agent.pid"
LOGFILE="/var/log/agent.log"
SCRIPT="/usr/local/bin/agent.sh"
status() {
if [ -f "$PIDFILE" ]; then
echo 'Service running' >&2
return 1
fi
}
start() {
if [ -f "$PIDFILE" ] && kill -0 "$(cat "$PIDFILE")"; then
echo 'Service already running' >&2
return 1
fi
printf 'Starting agent service...' >&2
"$SCRIPT" &> "$LOGFILE" & echo $! > "$PIDFILE"
}
stop() {
if [ ! -f "$PIDFILE" ] || ! kill -0 "$(cat "$PIDFILE")"; then
echo 'Service not running' >&2
return 1
fi
echo 'Stopping agent service' >&2
kill -15 "$(cat "$PIDFILE")" && rm -f "$PIDFILE"
echo 'Service stopped' >&2
}
case "$1" in
start)
start
;;
stop)
stop
;;
status)
status
;;
restart)
stop
start
;;
*)
echo "Usage: $0 {start|stop|restart}"
esac

View file

@ -1,6 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -e set -e
docker build --tag dsm . docker build --tag qemu .
docker images dsm:latest --format "{{.Repository}}:{{.Tag}} -> {{.Size}}" docker images qemu:latest --format "{{.Repository}}:{{.Tag}} -> {{.Size}}"
docker run --rm -it --name dsm --device="/dev/kvm" --cap-add NET_ADMIN -p 80:5000 -p 443:5001 -p 5000:5000 -p 5001:5001 docker.io/library/dsm docker run --rm -it --name qemu --device="/dev/kvm" --cap-add NET_ADMIN -p 80:80 docker.io/library/qemu

51
disk.sh
View file

@ -2,50 +2,21 @@
set -eu set -eu
IMG="/storage" IMG="/storage"
BASE=$(basename "$URL" .pat) FILE="$IMG/boot.img"
[ ! -f "$FILE" ] && echo "ERROR: Boot image does not exist ($FILE)" && exit 81
FILE="$IMG/$BASE.boot.img"
[ ! -f "$FILE" ] && echo "ERROR: Virtual DSM boot-image does not exist ($FILE)" && exit 81
FILE="$IMG/$BASE.system.img"
[ ! -f "$FILE" ] && echo "ERROR: Virtual DSM system-image does not exist ($FILE)" && exit 82
DISK_SIZE=$(echo "${DISK_SIZE}" | sed 's/MB/M/g;s/GB/G/g;s/TB/T/g') DISK_SIZE=$(echo "${DISK_SIZE}" | sed 's/MB/M/g;s/GB/G/g;s/TB/T/g')
NEW_SIZE=$(numfmt --from=iec "${DISK_SIZE}") NEW_SIZE=$(numfmt --from=iec "${DISK_SIZE}")
FILE="$IMG/data$DISK_SIZE.img" FILE="$IMG/data${DISK_SIZE}.img"
[ ! -f "$FILE" ] && truncate -s "${NEW_SIZE}" "${FILE}"
if [ ! -f "$FILE" ]; then [ ! -f "$FILE" ] && echo "ERROR: Data image does not exist ($FILE)" && exit 83
truncate -s "${NEW_SIZE}" "${FILE}"
mkfs.btrfs -q -L data -d single -m single "${FILE}" > /dev/null
#qemu-img convert -f raw -O qcow2 -o extended_l2=on,cluster_size=128k,compression_type=zstd,preallocation=metadata "$TMP" "$FILE"
fi
[ ! -f "$FILE" ] && echo "ERROR: Virtual DSM data-image does not exist ($FILE)" && exit 83
#OLD_SIZE=$(stat -c%s "${FILE}")
#
#if [ "$NEW_SIZE" -ne "$OLD_SIZE" ]; then
# echo "Resizing data disk from $OLD_SIZE to $NEW_SIZE bytes"
#
# if [ "$NEW_SIZE" -gt "$OLD_SIZE" ]; then
# truncate -s "${NEW_SIZE}" "${FILE}"
# btrfs filesystem resize "${NEW_SIZE}" "${FILE}"
# fi
#
# if [ "$NEW_SIZE" -lt "$OLD_SIZE" ]; then
# btrfs filesystem resize "${NEW_SIZE}" "${FILE}"
# truncate -s "${NEW_SIZE}" "${FILE}"
# fi
#fi
KVM_DISK_OPTS="\ KVM_DISK_OPTS="\
-device virtio-scsi-pci,id=hw-synoboot,bus=pcie.0,addr=0xa \ -device virtio-scsi-pci,id=hw-boot,bus=pcie.0,addr=0xa \
-drive file=${IMG}/${BASE}.boot.img,if=none,id=drive-synoboot,format=raw,cache=none,aio=native,discard=on,detect-zeroes=on \ -drive file=${IMG}/boot.img,if=none,id=drive-boot,format=raw,cache=none,aio=native,discard=on,detect-zeroes=on \
-device scsi-hd,bus=hw-synoboot.0,channel=0,scsi-id=0,lun=0,drive=drive-synoboot,id=synoboot0,rotation_rate=1,bootindex=1 \ -device scsi-hd,bus=hw-boot.0,channel=0,scsi-id=0,lun=0,drive=drive-boot,id=boot0,rotation_rate=1,bootindex=1 \
-device virtio-scsi-pci,id=hw-synosys,bus=pcie.0,addr=0xb \ -device virtio-scsi-pci,id=hw-userdata,bus=pcie.0,addr=0xb \
-drive file=${IMG}/${BASE}.system.img,if=none,id=drive-synosys,format=raw,cache=none,aio=native,discard=on,detect-zeroes=on \
-device scsi-hd,bus=hw-synosys.0,channel=0,scsi-id=0,lun=0,drive=drive-synosys,id=synosys0,rotation_rate=1,bootindex=2 \
-device virtio-scsi-pci,id=hw-userdata,bus=pcie.0,addr=0xc \
-drive file=${IMG}/data${DISK_SIZE}.img,if=none,id=drive-userdata,format=raw,cache=none,aio=native,discard=on,detect-zeroes=on \ -drive file=${IMG}/data${DISK_SIZE}.img,if=none,id=drive-userdata,format=raw,cache=none,aio=native,discard=on,detect-zeroes=on \
-device scsi-hd,bus=hw-userdata.0,channel=0,scsi-id=0,lun=0,drive=drive-userdata,id=userdata0,rotation_rate=1,bootindex=3" -device scsi-hd,bus=hw-userdata.0,channel=0,scsi-id=0,lun=0,drive=drive-userdata,id=userdata0,rotation_rate=1,bootindex=2"

View file

@ -1,18 +1,18 @@
version: "3" version: "3"
services: services:
vm: vm:
container_name: dsm container_name: qemu
image: kroese/virtual-dsm:latest image: kroese/docker-qemu:latest
environment: environment:
CPU_CORES: "1" CPU_CORES: "1"
RAM_SIZE: "512M" RAM_SIZE: "512M"
DISK_SIZE: "16G" DISK_SIZE: "16G"
BOOT: "https://ftp.halifax.rwth-aachen.de/osdn/clonezilla/78259/clonezilla-live-3.0.3-22-amd64.iso"
devices: devices:
- /dev/kvm - /dev/kvm
cap_add: cap_add:
- NET_ADMIN - NET_ADMIN
ports: ports:
- 5000:5000 - 22:22
- 5001:5001
restart: on-failure restart: on-failure
stop_grace_period: 60s stop_grace_period: 60s

View file

@ -2,43 +2,14 @@
set -eu set -eu
IMG="/storage" IMG="/storage"
BASE=$(basename "$URL" .pat)
[ ! -d "$IMG" ] && echo "Storage folder (${IMG}) not found!" && exit 69 [ ! -d "$IMG" ] && echo "Storage folder (${IMG}) not found!" && exit 69
[ ! -f "/run/server.sh" ] && echo "Script must run inside Docker container!" && exit 60 [ -f "$IMG/boot.img" ] && exit 0
[ ! -f "$IMG/$BASE.boot.img" ] && rm -f "$IMG"/"$BASE".system.img
[ -f "$IMG/$BASE.system.img" ] && exit 0
# Display wait message on port 5000
/run/server.sh 5000 > /dev/null &
TMP="$IMG/tmp" TMP="$IMG/tmp"
echo "Install: Downloading extractor..." echo "Install: Downloading $BOOT..."
rm -rf $TMP && mkdir -p $TMP
FILE="$TMP/rd.gz"
curl -r 64493568-69886247 -s -k -o "$FILE" https://global.synologydownload.com/download/DSM/release/7.0.1/42218/DSM_VirtualDSM_42218.pat
set +e
xz -dc <$TMP/rd.gz >$TMP/rd 2>/dev/null
(cd $TMP && cpio -idm <$TMP/rd 2>/dev/null)
set -e
mkdir -p /run/extract
for file in $TMP/usr/lib/libcurl.so.4 $TMP/usr/lib/libmbedcrypto.so.5 $TMP/usr/lib/libmbedtls.so.13 $TMP/usr/lib/libmbedx509.so.1 $TMP/usr/lib/libmsgpackc.so.2 $TMP/usr/lib/libsodium.so $TMP/usr/lib/libsynocodesign-ng-virtual-junior-wins.so.7 $TMP/usr/syno/bin/scemd; do
cp "$file" /run/extract/
done
mv /run/extract/scemd /run/extract/syno_extract_system_patch
chmod +x /run/extract/syno_extract_system_patch
echo "Install: Downloading $URL..."
FILE="$TMP/dsm.pat"
FILE="$TMP/boot.img"
rm -rf $TMP && mkdir -p $TMP rm -rf $TMP && mkdir -p $TMP
# Check if running with interactive TTY or redirected to docker log # Check if running with interactive TTY or redirected to docker log
@ -50,105 +21,7 @@ fi
[ ! -f "$FILE" ] && echo "Download failed" && exit 61 [ ! -f "$FILE" ] && echo "Download failed" && exit 61
SIZE=$(stat -c%s "$FILE") mv -f "$BOOT" "$IMG"/boot.img
if ((SIZE<250000000)); then
echo "Invalid PAT file: File is an update pack which contains no OS image." && exit 62
fi
echo "Install: Extracting downloaded system image..."
if { tar tf "$FILE"; } >/dev/null 2>&1; then
tar xpf $FILE -C $TMP/.
else
export LD_LIBRARY_PATH="/run/extract"
if ! /run/extract/syno_extract_system_patch $FILE $TMP/. ; then
echo "Invalid PAT file: File is an update pack which contains no OS image." && exit 63
fi
export LD_LIBRARY_PATH=""
fi
HDA="$TMP/hda1"
IDB="$TMP/indexdb"
PKG="$TMP/packages"
HDP="$TMP/synohdpack_img"
[ ! -f "$HDA.tgz" ] && echo "Invalid PAT file: File contains no OS image." && exit 64
[ ! -f "$HDP.txz" ] && echo "Invalid PAT file: HD pack not found." && exit 65
[ ! -f "$IDB.txz" ] && echo "Invalid PAT file: IndexDB file not found." && exit 66
[ ! -d "$PKG" ] && echo "Invalid PAT file: File contains no packages." && exit 68
BOOT=$(find $TMP -name "*.bin.zip")
[ ! -f "$BOOT" ] && echo "Invalid PAT file: boot file not found." && exit 67
BOOT=$(echo "$BOOT" | head -c -5)
unzip -q -o "$BOOT".zip -d $TMP
echo "Install: Extracting prepared disk image..."
SYSTEM="$TMP/temp.img"
PLATE="/data/template.img"
rm -f $PLATE
unxz $PLATE.xz
mv -f $PLATE $SYSTEM
echo "Install: Extracting system partition..."
PRIVILEGED=false
LABEL="1.44.1-42218"
OFFSET="1048576" # 2048 * 512
NUMBLOCKS="622560" # 2550005760 / 4096
MOUNT="/mnt/tmp"
rm -rf $MOUNT && mkdir -p $MOUNT
mount -t ext4 -o loop,offset=$OFFSET $SYSTEM $MOUNT 2>/dev/null && PRIVILEGED=true
rm -rf ${MOUNT:?}/{,.[!.],..?}*
mv -f $HDA.tgz $HDA.txz
tar xpfJ $HDP.txz --absolute-names -C $MOUNT/
tar xpfJ $HDA.txz --absolute-names -C $MOUNT/
tar xpfJ $IDB.txz --absolute-names -C $MOUNT/usr/syno/synoman/indexdb/
LOC="$MOUNT/usr/local"
mkdir -p $LOC
mv $PKG/ $LOC/
LOC="$MOUNT/usr/local/bin"
mkdir -p $LOC
cp /agent/agent.sh $LOC/agent.sh
chmod +x $LOC/agent.sh
LOC="$MOUNT/usr/local/etc/rc.d"
mkdir -p $LOC
cp /agent/service.sh $LOC/agent.sh
chmod +x $LOC/agent.sh
# Store agent version
echo "2" > "$IMG"/agent.ver
if [ "$PRIVILEGED" = false ]; then
echo "Install: Installing system partition..."
# Workaround for containers that are not privileged to mount loop devices
mke2fs -q -t ext4 -b 4096 -d $MOUNT/ -L $LABEL -F -E offset=$OFFSET $SYSTEM $NUMBLOCKS
else
umount $MOUNT
fi
rm -rf $MOUNT
mv -f "$BOOT" "$IMG"/"$BASE".boot.img
mv -f "$SYSTEM" "$IMG"/"$BASE".system.img
rm -rf $TMP rm -rf $TMP
exit 0

View file

@ -18,7 +18,7 @@ setupLocalDhcp () {
IP="$2" IP="$2"
MAC="$1" MAC="$1"
CIDR="24" CIDR="24"
HOSTNAME="VirtualDSM" HOSTNAME="QEMU"
# dnsmasq configuration: # dnsmasq configuration:
DNSMASQ_OPTS="$DNSMASQ_OPTS --dhcp-range=$IP,$IP --dhcp-host=$MAC,,$IP,$HOSTNAME,infinite --dhcp-option=option:netmask,255.255.255.0" DNSMASQ_OPTS="$DNSMASQ_OPTS --dhcp-range=$IP,$IP --dhcp-host=$MAC,,$IP,$HOSTNAME,infinite --dhcp-option=option:netmask,255.255.255.0"
# Create lease file for faster resolve # Create lease file for faster resolve
@ -61,14 +61,6 @@ if [ ! -c /dev/net/tun ]; then
fi fi
[ ! -c /dev/net/tun ] && echo "Error: TUN network interface not available..." && exit 85 [ ! -c /dev/net/tun ] && echo "Error: TUN network interface not available..." && exit 85
[ ! -d "$IMG" ] && echo "Storage folder (${IMG}) not found!" && exit 86
#If environment variabele not set fall back to file
if [ -z "$VM_NET_MAC" ]; then
FILE="${IMG}/guest.mac"
[ ! -f "$FILE" ] && echo "00:11:32:2C:A7:85" > "$FILE"
VM_NET_MAC=$(cat "${FILE}")
fi
update-alternatives --set iptables /usr/sbin/iptables-legacy > /dev/null update-alternatives --set iptables /usr/sbin/iptables-legacy > /dev/null
update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy > /dev/null update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy > /dev/null

View file

@ -25,29 +25,8 @@ _graceful_shutdown(){
echo "Received $1 signal, shutting down..." echo "Received $1 signal, shutting down..."
echo 0 > "${_QEMU_SHUTDOWN_COUNTER}" echo 0 > "${_QEMU_SHUTDOWN_COUNTER}"
# Don't send the powerdown signal because vDSM ignores ACPI signals # Send the shutdown (system_powerdown) command to the QMP monitor
# echo 'system_powerdown' | nc -q 1 -w 1 localhost "${QEMU_MONPORT}">/dev/null echo 'system_powerdown' | nc -q 1 -w 1 localhost "${QEMU_MONPORT}">/dev/null
# Send shutdown command to guest agent tools instead via serial port
RESPONSE=$(curl -s -m 2 -S http://127.0.0.1:2210/write?command=6 2>&1)
if [[ ! "${RESPONSE}" =~ "\"success\"" ]] ; then
echo "Could not send shutdown command to guest, error: $RESPONSE"
FILE="${IMG}/agent.ver"
[ ! -f "$FILE" ] && echo "1" > "$FILE"
AGENT_VERSION=$(cat "${FILE}")
if ((AGENT_VERSION < 2)); then
echo "Please update the agent to allow gracefull shutdowns..."
pkill -f qemu-system-x86_64
else
# Send a NMI interrupt which will be detected by the kernel
echo 'nmi' | nc -q 1 -w 1 localhost "${QEMU_MONPORT}">/dev/null
fi
fi
while [ "$(cat ${_QEMU_SHUTDOWN_COUNTER})" -lt "${QEMU_POWERDOWN_TIMEOUT}" ]; do while [ "$(cat ${_QEMU_SHUTDOWN_COUNTER})" -lt "${QEMU_POWERDOWN_TIMEOUT}" ]; do

View file

@ -1,24 +1,23 @@
virtual-dsm docker-qemu
============= =============
[![build_img]][build_url] [![build_img]][build_url]
[![gh_last_release_svg]][dsm-docker-hub] [![gh_last_release_svg]][qemu-docker-hub]
[![Docker Image Size]][dsm-docker-hub] [![Docker Image Size]][qemu-docker-hub]
[![Docker Pulls Count]][dsm-docker-hub] [![Docker Pulls Count]][qemu-docker-hub]
[build_url]: https://github.com/kroese/virtual-dsm/actions [build_url]: https://github.com/kroese/docker-qemu/actions
[dsm-docker-hub]: https://hub.docker.com/r/kroese/virtual-dsm [qemu-docker-hub]: https://hub.docker.com/r/kroese/docker-qemu
[build_img]: https://github.com/kroese/virtual-dsm/actions/workflows/build.yml/badge.svg [build_img]: https://github.com/kroese/docker-qemu/actions/workflows/build.yml/badge.svg
[Docker Image Size]: https://img.shields.io/docker/image-size/kroese/virtual-dsm/latest [Docker Image Size]: https://img.shields.io/docker/image-size/kroese/docker-qemu/latest
[Docker Pulls Count]: https://img.shields.io/docker/pulls/kroese/virtual-dsm.svg?style=flat [Docker Pulls Count]: https://img.shields.io/docker/pulls/kroese/docker-qemu.svg?style=flat
[gh_last_release_svg]: https://img.shields.io/docker/v/kroese/virtual-dsm?arch=amd64&sort=date [gh_last_release_svg]: https://img.shields.io/docker/v/kroese/docker-qemu?arch=amd64&sort=date
A docker container of Virtual DSM v7.2 A docker container of QEMU
## Features ## Features
- Upgrades supported
- KVM acceleration - KVM acceleration
- Graceful shutdown - Graceful shutdown
@ -31,16 +30,16 @@ version: "3"
services: services:
vm: vm:
container_name: dsm container_name: dsm
image: kroese/virtual-dsm:latest image: kroese/docker-qemu:latest
environment: environment:
DISK_SIZE: "16G" DISK_SIZE: "16G"
BOOT: "https://ftp.halifax.rwth-aachen.de/osdn/clonezilla/78259/clonezilla-live-3.0.3-22-amd64.iso"
devices: devices:
- /dev/kvm - /dev/kvm
cap_add: cap_add:
- NET_ADMIN - NET_ADMIN
ports: ports:
- 5000:5000 - 22:22
- 5001:5001
restart: on-failure restart: on-failure
stop_grace_period: 60s stop_grace_period: 60s
``` ```
@ -48,7 +47,7 @@ services:
Via `docker run` Via `docker run`
```bash ```bash
docker run -p 5000:5000 --device=/dev/kvm --cap-add NET_ADMIN --stop-timeout 60 kroese/virtual-dsm:latest docker run -p 22:22 --device=/dev/kvm --cap-add NET_ADMIN --stop-timeout 60 kroese/docker-qemu:latest
``` ```
## FAQ ## FAQ
@ -118,15 +117,3 @@ docker run -p 5000:5000 --device=/dev/kvm --cap-add NET_ADMIN --stop-timeout 60
``` ```
You can also switch back and forth between versions this way without loosing your file data. You can also switch back and forth between versions this way without loosing your file data.
* ### What are the differences compared to standard DSM? ###
There are only three minor differences: the Virtual Machine Manager package is not available, Surveillance Station does not include any free licenses, and logging in to your Synology account is not supported.
## Acknowledgments
Based on an [article](https://jxcn.org/2022/04/vdsm-first-try/) by JXCN.
## Disclaimer
Only run this container on original Synology hardware, any other use is not permitted and might not be legal.

9
run.sh
View file

@ -2,7 +2,7 @@
set -eu set -eu
if /run/install.sh; then if /run/install.sh; then
echo "Starting Virtual DSM..." echo "Starting QEMU..."
else else
echo "Installation failed (code $?)" && exit 81 echo "Installation failed (code $?)" && exit 81
fi fi
@ -15,9 +15,10 @@ source /run/network.sh
[ -z "${KVM_NET_OPTS}" ] && echo "Error: Failed to setup network..." && exit 84 [ -z "${KVM_NET_OPTS}" ] && echo "Error: Failed to setup network..." && exit 84
source /run/serial.sh KVM_SERIAL_OPTS="\
-serial mon:stdio \
[ -z "${KVM_SERIAL_OPTS}" ] && echo "Error: Failed to setup serial..." && exit 85 -device virtio-serial-pci,id=virtio-serial0,bus=pcie.0,addr=0x3 \
-chardev pty,id=charserial0"
source /run/power.sh source /run/power.sh

View file

@ -1,55 +0,0 @@
#!/bin/bash
# Docker environment variabeles
: ${HOST_SERIAL:=''}
: ${GUEST_SERIAL:=''}
permanent="DSM"
serialstart="2000"
[ ! -d "$IMG" ] && echo "Storage folder (${IMG}) not found!" && exit 69
#If environment variabele not set fall back to file
if [ -z "$HOST_SERIAL" ]; then
FILE="${IMG}/host.serial"
if [ ! -f "$FILE" ]; then
SERIAL="$(echo "$serialstart" | tr ' ' '\n' | sort -R | tail -1)$permanent"$(printf "%06d" $((RANDOM % 30000 + 1)))
echo $SERIAL > "$FILE"
fi
HOST_SERIAL=$(cat "${FILE}")
fi
#If environment variabele not set fall back to file
if [ -z "$GUEST_SERIAL" ]; then
FILE="${IMG}/guest.serial"
if [ ! -f "$FILE" ]; then
SERIAL="$(echo "$serialstart" | tr ' ' '\n' | sort -R | tail -1)$permanent"$(printf "%06d" $((RANDOM % 30000 + 1)))
echo $SERIAL > "$FILE"
fi
GUEST_SERIAL=$(cat "${FILE}")
fi
CPU=$(lscpu | sed -nr '/Model name/ s/.*:\s*(.*) @ .*/\1/p' | sed ':a;s/ / /;ta' | sed s/"(R)"//g | sed s/"-"//g | sed 's/[^[:alnum:] ]\+//g')
if [ -n "$CPU" ]; then
CPU="$CPU,,"
else
CPU="QEMU, Virtual CPU, X86_64"
fi
./run/serial.bin -cpu="${CPU_CORES}" \
-cpu_arch="${CPU}" \
-buildnumber=42962 \
-vmmts=1679863686 \
-hostsn="${HOST_SERIAL}" \
-guestsn="${GUEST_SERIAL}" \
-vmmversion="2.6.1-12139" \
-guestuuid="ba13a19a-c0c1-4fef-9346-915ed3b98341" > /dev/null 2>&1 &
KVM_SERIAL_OPTS="\
-serial mon:stdio \
-device virtio-serial-pci,id=virtio-serial0,bus=pcie.0,addr=0x3 \
-chardev pty,id=charserial0 \
-device isa-serial,chardev=charserial0,id=serial0 \
-chardev socket,id=charchannel0,host=127.0.0.1,port=12345,reconnect=10 \
-device virtserialport,bus=virtio-serial0.0,nr=1,chardev=charchannel0,id=channel0,name=vchannel"

View file

@ -1,6 +0,0 @@
// vdsm-serial project doc.go
/*
vdsm-serial document
*/
package main

View file

@ -1,4 +0,0 @@
module vdsm-serial
go 1.20

View file

@ -1 +0,0 @@

View file

@ -1,263 +0,0 @@
package main
import (
"bytes"
"encoding/binary"
"flag"
"fmt"
"log"
"net"
"strconv"
"net/http"
"math/rand"
"github.com/gorilla/mux"
)
type REQ struct {
RandID int64
GuestUUID [16]byte
GuestID int64
IsReq int32
IsResp int32
NeedResponse int32
ReqLength int32
RespLength int32
CommandID int32
SubCommand int32
Reserve int32
}
var HostSN = flag.String("hostsn", "0000000000000", "Host SN, 13 bytes")
var GuestSN = flag.String("guestsn", "0000000000000", "Guest SN, 13 bytes")
var GuestUUID = flag.String("guestuuid", "ba13a19a-c0c1-4fef-9346-915ed3b98341", "Guest UUID")
var GuestCPUs = flag.Int("cpu", 1, "Num of Guest cpu")
var GuestCPU_ARCH = flag.String("cpu_arch", "QEMU, Virtual CPU, X86_64", "CPU arch")
var HostDSMBuildNumber = flag.Int("buildnumber", 42962, "Build Number of Host")
var HostDSMfixNumber = flag.Int("fixNumber", 0, "Fix Number of Host")
var VMMVersion = flag.String("vmmversion", "2.6.1-12139", "VMM version")
var VMMTimestamp = flag.Int("vmmts", 1679863686, "VMM Timestamp")
var Cluster_UUID = "3bdea92b-68f4-4fe9-aa4b-d645c3c63864"
var ApiPort = flag.String("api", ":2210", "API port")
var ListenAddr = flag.String("addr", "0.0.0.0:12345", "Listen address")
var LastConnection net.Conn
func main() {
flag.Parse()
r := mux.NewRouter()
r.HandleFunc("/", home)
r.HandleFunc("/write", write)
go http.ListenAndServe(*ApiPort, r)
listener, err := net.Listen("tcp", *ListenAddr)
if err != nil {
log.Println("Error listening", err.Error())
return
}
log.Println("Start listen on " + *ListenAddr)
for {
conn, err := listener.Accept()
if err != nil {
log.Println("Error on accept", err.Error())
return
}
log.Printf("New connection from %s\n", conn.RemoteAddr().String())
go incoming_conn(conn)
}
}
func incoming_conn(conn net.Conn) {
LastConnection = conn
for {
buf := make([]byte, 4096)
len, err := conn.Read(buf)
if err != nil {
log.Println("Error on read", err.Error())
return
}
if len != 4096 {
log.Printf("Read %d Bytes, not 4096\n", len)
// something wrong, close and wait for reconnect
conn.Close()
return
}
go process_req(buf, conn)
//log.Printf("Read %d Bytes\n%#v\n", len, string(buf[:len]))
}
}
var commandsName = map[int]string{
3: "Guest Power info",
4: "Host DSM version",
5: "Guest SN",
7: "Guest CPU info",
9: "Host DSM version",
8: "VMM version",
10: "Get Guest Info",
11: "Guest UUID",
12: "Cluster UUID",
13: "Host SN",
16: "Update Deadline",
17: "Guest Timestamp",
}
func process_req(buf []byte, conn net.Conn) {
var req REQ
var data string
err := binary.Read(bytes.NewReader(buf), binary.LittleEndian, &req)
if err != nil {
log.Printf("Error on decode %s\n", err)
return
}
if req.IsReq == 1 {
data = string(buf[64 : 64+req.ReqLength])
} else if req.IsResp == 1 {
data = string(buf[64 : 64+req.RespLength])
}
// log.Printf("%#v\n", req)
log.Printf("Command: %s from Guest:%d \n", commandsName[int(req.CommandID)], req.GuestID)
if data != "" {
log.Printf("Info: %s\n", data)
}
// Hard code of command
switch req.CommandID {
case 3:
// Guest start/reboot
case 4:
// Host DSM version
data = fmt.Sprintf(`{"buildnumber":%d,"smallfixnumber":%d}`, *HostDSMBuildNumber, *HostDSMfixNumber)
case 5:
// Guest SN
data = *GuestSN
case 7:
// CPU info
// {"cpuinfo":"QEMU, Virtual CPU, X86_64, 1" "vcpu_num":1}
data = fmt.Sprintf(`{"cpuinfo":"%s","vcpu_num":%d}`,
*GuestCPU_ARCH+", "+strconv.Itoa(*GuestCPUs), *GuestCPUs)
case 8:
data = fmt.Sprintf(`{"id":"Virtualization","name":"Virtual Machine Manager","timestamp":%d,"version":"%s"}`,
*VMMTimestamp, *VMMVersion)
case 9:
// Version Info
case 10:
// Guest Info
case 11:
// Guest UUID
data = *GuestUUID
case 12:
// cluster UUID
data = Cluster_UUID
case 13:
// Host SN
data = *HostSN
case 16:
// Update Dead line time, always 0x7fffffffffffffff
data = "9223372036854775807"
case 17:
// TimeStamp
default:
log.Printf("No handler for this command %d\n", req.CommandID)
return
}
// if it's a req and need response
if req.IsReq == 1 && req.NeedResponse == 1 {
buf = make([]byte, 0, 4096)
writer := bytes.NewBuffer(buf)
req.IsResp = 1
req.IsReq = 0
req.ReqLength = 0
req.RespLength = int32(len([]byte(data)) + 1)
log.Printf("Response data: %s\n", data)
// write to buf
binary.Write(writer, binary.LittleEndian, &req)
writer.Write([]byte(data))
res := writer.Bytes()
// full fill 4096
buf = make([]byte, 4096, 4096)
copy(buf, res)
conn.Write(buf)
}
}
func home(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(`{"status": "error", "data": null, "message": "No command specified"}`))
}
func write(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
var err error
var commandID int
query := r.URL.Query()
commandID, err = strconv.Atoi(query.Get("command"))
if (err != nil || commandID < 1) {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(`{"status": "error", "data": null, "message": "Invalid command ID"}`))
return
}
if (send_command((int32)(commandID), 1) == false) {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(`{"status": "error", "data": null, "message": "Failed to send command"}`))
return
}
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"status": "success", "data": null, "message": null}`))
return
}
func send_command(CommandID int32, SubCommand int32) bool {
var req REQ
req.CommandID = CommandID
req.SubCommand = SubCommand
req.IsReq = 1
req.IsResp = 0
req.ReqLength = 0
req.RespLength = 0
req.NeedResponse = 0
req.GuestID = 10000000
req.RandID = rand.Int63()
var buf = make([]byte, 0, 4096)
var writer = bytes.NewBuffer(buf)
// write to buf
binary.Write(writer, binary.LittleEndian, &req)
res := writer.Bytes()
// full fill 4096
buf = make([]byte, 4096, 4096)
copy(buf, res)
//log.Printf("Writing command %d\n", CommandID)
if (LastConnection == nil) { return false }
LastConnection.Write(buf)
return true
}

View file

@ -1,11 +0,0 @@
#!/usr/bin/env bash
set -eu
HTML="<HTML><BODY><H1><CENTER>Please wait while Virtual DSM is installing...</CENTER></H1></BODY></HTML>"
RESPONSE="HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n$HTML\r\n"
while { echo -en "$RESPONSE"; } | nc -lN "${1:-8080}"; do
echo "================================================"
done