mirror of
https://github.com/vdsm/virtual-dsm.git
synced 2025-11-06 18:13:43 +08:00
Compare commits
64 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e13258bc9 | ||
|
|
2c7cea042f | ||
|
|
fc92b66ff4 | ||
|
|
89ae24a2ac | ||
|
|
bd4a23b287 | ||
|
|
1b8c4d9f08 | ||
|
|
a1187decb5 | ||
|
|
b9d7aa182d | ||
|
|
b908c1118d | ||
|
|
fab776764f | ||
|
|
f3e17e399d | ||
|
|
3706ca873b | ||
|
|
10c565f32b | ||
|
|
d237e5b9e6 | ||
|
|
3c7e1ce12f | ||
|
|
f422f64325 | ||
|
|
df0656b345 | ||
|
|
1fc9c56c8f | ||
|
|
893a013ae9 | ||
|
|
6d3812d1d0 | ||
|
|
6422aec780 | ||
|
|
fcd7b8a825 | ||
|
|
575da1f574 | ||
|
|
3a507f5bf6 | ||
|
|
a3d6e3740c | ||
|
|
53e0330e21 | ||
|
|
f935c1e28a | ||
|
|
6a0c708224 | ||
|
|
a407d2d94d | ||
|
|
53605bd6e8 | ||
|
|
944faaa927 | ||
|
|
fb7cfc09de | ||
|
|
b9ae73e322 | ||
|
|
c28dbb6b9b | ||
|
|
dcc26b67a6 | ||
|
|
2627d320f3 | ||
|
|
fd4bc28835 | ||
|
|
63e4d588a2 | ||
|
|
feb1680909 | ||
|
|
9d21489b8e | ||
|
|
e70ed1900f | ||
|
|
d65b5a089a | ||
|
|
84643647cc | ||
|
|
57f487db6d | ||
|
|
0862cad2ce | ||
|
|
5ae4f59315 | ||
|
|
3fc3005ba5 | ||
|
|
20f48edd00 | ||
|
|
b854aad830 | ||
|
|
6cbe03f656 | ||
|
|
63cac9a75e | ||
|
|
08616f1057 | ||
|
|
e6193b1020 | ||
|
|
f28b9903f3 | ||
|
|
7bf2d119ea | ||
|
|
527bded1b2 | ||
|
|
1208c53ebb | ||
|
|
973efa2d27 | ||
|
|
d09588b915 | ||
|
|
19aa313753 | ||
|
|
9db12cd25f | ||
|
|
69e785e6ee | ||
|
|
159fce6839 | ||
|
|
08e4084458 |
2
.github/workflows/check.yml
vendored
2
.github/workflows/check.yml
vendored
@@ -11,4 +11,4 @@ jobs:
|
||||
- name: Run ShellCheck
|
||||
uses: ludeeus/action-shellcheck@master
|
||||
env:
|
||||
SHELLCHECK_OPTS: -x --source-path=src -e SC2001 -e SC2002 -e SC2223 -e SC2034 -e SC2064 -e SC2317 -e SC2028 -e SC2153 -e SC2004
|
||||
SHELLCHECK_OPTS: -x --source-path=src -e SC2001 -e SC2034 -e SC2064 -e SC2317 -e SC2153 -e SC2028
|
||||
|
||||
28
Dockerfile
28
Dockerfile
@@ -2,7 +2,7 @@ FROM qemux/qemu-host as builder
|
||||
|
||||
# FROM golang as builder
|
||||
# WORKDIR /
|
||||
# RUN git clone https://github.com/qemu-tools/qemu-host.git
|
||||
# RUN git clone https://github.com/qemus/qemu-host.git
|
||||
# WORKDIR /qemu-host/src
|
||||
# RUN go mod download
|
||||
# RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o /qemu-host.bin .
|
||||
@@ -10,11 +10,12 @@ FROM qemux/qemu-host as builder
|
||||
FROM debian:trixie-slim
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
ARG DEBCONF_NOWARNINGS="yes"
|
||||
ARG DEBIAN_FRONTEND noninteractive
|
||||
ARG DEBCONF_NOWARNINGS "yes"
|
||||
ARG DEBIAN_FRONTEND "noninteractive"
|
||||
ARG DEBCONF_NONINTERACTIVE_SEEN "true"
|
||||
|
||||
RUN apt-get update && apt-get -y upgrade \
|
||||
&& if [ "$TARGETPLATFORM" != "linux/amd64" ]; then extra="qemu-user"; fi \
|
||||
RUN if [ "$TARGETPLATFORM" != "linux/amd64" ]; then extra="qemu-user"; fi \
|
||||
&& apt-get update \
|
||||
&& apt-get --no-install-recommends -y install \
|
||||
jq \
|
||||
tini \
|
||||
@@ -23,11 +24,12 @@ RUN apt-get update && apt-get -y upgrade \
|
||||
wget \
|
||||
fdisk \
|
||||
unzip \
|
||||
socat \
|
||||
nginx \
|
||||
procps \
|
||||
xz-utils \
|
||||
iptables \
|
||||
iproute2 \
|
||||
apt-utils \
|
||||
dnsmasq \
|
||||
fakeroot \
|
||||
net-tools \
|
||||
@@ -37,25 +39,25 @@ RUN apt-get update && apt-get -y upgrade \
|
||||
qemu-system-x86 \
|
||||
"$extra" \
|
||||
&& apt-get clean \
|
||||
&& unlink /etc/nginx/sites-enabled/default \
|
||||
&& sed -i 's/^worker_processes.*/worker_processes 1;/' /etc/nginx/nginx.conf \
|
||||
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
COPY ./src /run/
|
||||
COPY ./web /var/www/
|
||||
COPY --from=builder /qemu-host.bin /run/host.bin
|
||||
|
||||
RUN chmod +x /run/*.sh && chmod +x /run/*.bin
|
||||
RUN mv /var/www/nginx.conf /etc/nginx/sites-enabled/web.conf
|
||||
|
||||
VOLUME /storage
|
||||
|
||||
EXPOSE 22
|
||||
EXPOSE 80
|
||||
EXPOSE 139
|
||||
EXPOSE 445
|
||||
EXPOSE 5000
|
||||
EXPOSE 22 139 445 5000
|
||||
|
||||
ENV RAM_SIZE "1G"
|
||||
ENV DISK_SIZE "16G"
|
||||
ENV CPU_CORES "1"
|
||||
|
||||
ARG VERSION_ARG="0.0"
|
||||
ARG VERSION_ARG "0.0"
|
||||
RUN echo "$VERSION_ARG" > /run/version
|
||||
|
||||
HEALTHCHECK --interval=60s --start-period=45s --retries=2 CMD /run/check.sh
|
||||
|
||||
140
agent/agent.sh
140
agent/agent.sh
@@ -1,140 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -u
|
||||
|
||||
VERSION="9"
|
||||
HEADER="VirtualDSM Agent"
|
||||
|
||||
# Functions
|
||||
|
||||
error () { echo -e "\E[1;31m❯ ERROR: $1\E[0m" ; }
|
||||
info () { echo -e "\E[1;34m❯\E[1;36m $1\E[0m" ; }
|
||||
|
||||
finish() {
|
||||
|
||||
echo "❯ $HEADER: Shutting down.."
|
||||
exit
|
||||
|
||||
}
|
||||
|
||||
function checkNMI {
|
||||
|
||||
local nmi
|
||||
nmi=$(cat /proc/interrupts | grep NMI | sed 's/[^1-9]*//g')
|
||||
|
||||
if [ "$nmi" != "" ]; then
|
||||
|
||||
info "Received shutdown request through NMI.."
|
||||
|
||||
/usr/syno/sbin/synoshutdown -s > /dev/null
|
||||
finish
|
||||
|
||||
fi
|
||||
}
|
||||
|
||||
function downloadUpdate {
|
||||
|
||||
TMP="/tmp/agent.sh"
|
||||
rm -f "${TMP}"
|
||||
|
||||
# Auto update the agent
|
||||
|
||||
URL="https://raw.githubusercontent.com/vdsm/virtual-dsm/master/agent/agent.sh"
|
||||
|
||||
remote_size=$(curl -sIk -m 4 "${URL}" | grep -i "content-length:" | tr -d " \t" | cut -d ':' -f 2)
|
||||
remote_size=${remote_size//$'\r'}
|
||||
|
||||
[[ "$remote_size" == "" || "$remote_size" == "0" ]] && return
|
||||
|
||||
remote_size=$(($remote_size+0))
|
||||
((remote_size<100)) && return
|
||||
|
||||
SCRIPT=$(readlink -f "${BASH_SOURCE[0]}")
|
||||
local_size=$(stat -c%s "$SCRIPT")
|
||||
local_size=$(($local_size+0))
|
||||
|
||||
[[ remote_size -eq local_size ]] && return
|
||||
|
||||
if ! curl -sfk -m 10 -o "${TMP}" "${URL}"; then
|
||||
error "$HEADER: curl error ($?)" && return
|
||||
fi
|
||||
|
||||
if [ ! -f "${TMP}" ]; then
|
||||
error "$HEADER: update error, file not found.." && return
|
||||
fi
|
||||
|
||||
line=$(head -1 "${TMP}")
|
||||
|
||||
if [[ "$line" != "#!/usr/bin/env bash" ]]; then
|
||||
error "$HEADER: update error, invalid header: $line" && return
|
||||
fi
|
||||
|
||||
if cmp --silent -- "${TMP}" "${SCRIPT}"; then
|
||||
error "$HEADER: update file is already equal? (${local_size} / ${remote_size})" && return
|
||||
fi
|
||||
|
||||
mv -f "${TMP}" "${SCRIPT}"
|
||||
chmod 755 "${SCRIPT}"
|
||||
|
||||
info "$HEADER: succesfully installed update..."
|
||||
|
||||
}
|
||||
|
||||
function installPackages {
|
||||
|
||||
for filename in /usr/local/packages/*.spk; do
|
||||
if [ -f "$filename" ]; then
|
||||
|
||||
BASE=$(basename "$filename" .spk)
|
||||
BASE="${BASE%%-*}"
|
||||
|
||||
[[ $BASE == "ActiveInsight" ]] && continue
|
||||
|
||||
info "Installing package ${BASE}.."
|
||||
|
||||
/usr/syno/bin/synopkg install "$filename" > /dev/null
|
||||
/usr/syno/bin/synopkg start "$BASE" > /dev/null &
|
||||
|
||||
rm "$filename"
|
||||
|
||||
fi
|
||||
done
|
||||
|
||||
}
|
||||
|
||||
trap finish SIGINT SIGTERM
|
||||
|
||||
ts=$(date +%s%N)
|
||||
|
||||
echo ""
|
||||
echo "❯ Started $HEADER v$VERSION..."
|
||||
|
||||
checkNMI
|
||||
|
||||
# Install packages
|
||||
|
||||
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
|
||||
|
||||
installPackages
|
||||
|
||||
else
|
||||
|
||||
downloadUpdate
|
||||
|
||||
fi
|
||||
|
||||
# Wait for NMI interrupt as a shutdown signal
|
||||
|
||||
while true; do
|
||||
|
||||
checkNMI
|
||||
sleep 2 & wait $!
|
||||
|
||||
done
|
||||
@@ -1,87 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
PIDFILE="/var/run/agent.pid"
|
||||
SCRIPT="/usr/local/bin/agent.sh"
|
||||
|
||||
error () { echo -e "\E[1;31m❯ ERROR: $1\E[0m" ; }
|
||||
info () { echo -e "\E[1;34m❯\E[1;36m $1\E[0m" ; }
|
||||
|
||||
status() {
|
||||
|
||||
if [ -f "$PIDFILE" ] && kill -0 "$(cat "$PIDFILE")"; then
|
||||
echo 'Service running'
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
start() {
|
||||
|
||||
if [ -f "$PIDFILE" ] && kill -0 "$(cat "$PIDFILE")"; then
|
||||
echo 'Service already running'
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo 'Starting agent service...'
|
||||
chmod 666 /dev/ttyS0
|
||||
|
||||
if [ ! -f "$SCRIPT" ]; then
|
||||
|
||||
URL="https://raw.githubusercontent.com/vdsm/virtual-dsm/master/agent/agent.sh"
|
||||
|
||||
if ! curl -sfk -m 10 -o "${SCRIPT}" "${URL}"; then
|
||||
error 'Failed to download agent script.' > /dev/ttyS0
|
||||
rm -f "${SCRIPT}"
|
||||
return 1
|
||||
else
|
||||
info 'Agent script was missing?' > /dev/ttyS0
|
||||
fi
|
||||
|
||||
chmod 755 "${SCRIPT}"
|
||||
|
||||
fi
|
||||
|
||||
echo "-" > /var/lock/subsys/agent.sh
|
||||
"$SCRIPT" &> /dev/ttyS0 & echo $! > "$PIDFILE"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
stop() {
|
||||
|
||||
if [ ! -f "$PIDFILE" ] || ! kill -0 "$(cat "$PIDFILE")"; then
|
||||
echo 'Service not running'
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo 'Stopping agent service...'
|
||||
|
||||
chmod 666 /dev/ttyS0
|
||||
info 'Stopping agent service...' > /dev/ttyS0
|
||||
|
||||
kill -15 "$(cat "$PIDFILE")" && rm -f "$PIDFILE"
|
||||
rm -f /var/lock/subsys/agent.sh
|
||||
|
||||
echo 'Service stopped'
|
||||
return 0
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
start
|
||||
;;
|
||||
stop)
|
||||
stop
|
||||
;;
|
||||
status)
|
||||
status
|
||||
;;
|
||||
restart)
|
||||
stop
|
||||
start
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {start|stop|restart}"
|
||||
exit 1
|
||||
esac
|
||||
@@ -9,8 +9,6 @@ services:
|
||||
CPU_CORES: "1"
|
||||
devices:
|
||||
- /dev/kvm
|
||||
- /dev/net/tun
|
||||
- /dev/vhost-net
|
||||
device_cgroup_rules:
|
||||
- 'c *:* rwm'
|
||||
cap_add:
|
||||
|
||||
74
readme.md
74
readme.md
@@ -1,4 +1,4 @@
|
||||
<h1 align="center">Virtual DSM for Docker<br />
|
||||
<h1 align="center">Virtual DSM<br />
|
||||
<div align="center">
|
||||
<img src="https://github.com/vdsm/virtual-dsm/raw/master/.github/screen.jpg" title="Screenshot" style="max-width:100%;" width="432" />
|
||||
</div>
|
||||
@@ -17,7 +17,7 @@ Virtual DSM in a docker container.
|
||||
|
||||
- Multiple disks
|
||||
- KVM acceleration
|
||||
- GPU passthrough
|
||||
- GPU pass-through
|
||||
- Upgrades supported
|
||||
|
||||
## Usage
|
||||
@@ -29,7 +29,7 @@ version: "3"
|
||||
services:
|
||||
dsm:
|
||||
container_name: dsm
|
||||
image: vdsm/virtual-dsm:latest
|
||||
image: vdsm/virtual-dsm
|
||||
environment:
|
||||
DISK_SIZE: "16G"
|
||||
devices:
|
||||
@@ -47,12 +47,24 @@ services:
|
||||
Via `docker run`
|
||||
|
||||
```bash
|
||||
docker run -it --rm -p 5000:5000 --device=/dev/kvm --cap-add NET_ADMIN --stop-timeout 60 vdsm/virtual-dsm:latest
|
||||
docker run -it --rm -p 5000:5000 --device=/dev/kvm --cap-add NET_ADMIN --stop-timeout 60 vdsm/virtual-dsm
|
||||
```
|
||||
|
||||
## FAQ
|
||||
|
||||
* ### How do I change the size of the virtual disk?
|
||||
* ### How do I use it?
|
||||
|
||||
Very simple! These are the steps:
|
||||
|
||||
- Start the container and get some coffee.
|
||||
|
||||
- Connect to [port 5000](http://localhost:5000) of the container in your web browser.
|
||||
|
||||
- Wait until DSM is ready, choose an username and password, and you will be taken to the desktop.
|
||||
|
||||
- Enjoy your brand new machine, and don't forget to star this repo!
|
||||
|
||||
* ### How do I change the size of the disk?
|
||||
|
||||
To expand the default size of 16 GB, locate the `DISK_SIZE` setting in your compose file and modify it to your preferred capacity:
|
||||
|
||||
@@ -63,9 +75,9 @@ docker run -it --rm -p 5000:5000 --device=/dev/kvm --cap-add NET_ADMIN --stop-ti
|
||||
|
||||
This can also be used to resize the existing disk to a larger capacity without any data loss.
|
||||
|
||||
* ### How do I change the location of the virtual disk?
|
||||
* ### How do I change the storage location?
|
||||
|
||||
To change the location of the virtual disk, include the following bind mount in your compose file:
|
||||
To change the storage location, include the following bind mount in your compose file:
|
||||
|
||||
```yaml
|
||||
volumes:
|
||||
@@ -74,7 +86,20 @@ docker run -it --rm -p 5000:5000 --device=/dev/kvm --cap-add NET_ADMIN --stop-ti
|
||||
|
||||
Replace the example path `/var/dsm` with the desired storage folder.
|
||||
|
||||
* ### How do I add multiple disks?
|
||||
* ### How do I create a growable disk?
|
||||
|
||||
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
|
||||
environment:
|
||||
DISK_FMT: "qcow2"
|
||||
```
|
||||
|
||||
Please note that this may reduce the write performance of the disk.
|
||||
|
||||
* ### How do I add multiple disks?
|
||||
|
||||
To create additional disks, modify your compose file like this:
|
||||
|
||||
@@ -87,23 +112,26 @@ docker run -it --rm -p 5000:5000 --device=/dev/kvm --cap-add NET_ADMIN --stop-ti
|
||||
- /mnt/data/example:/storage3
|
||||
```
|
||||
|
||||
* ### How do I create a growable disk?
|
||||
* ### How do I pass-through a disk?
|
||||
|
||||
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 variables:
|
||||
It is possible to pass-through disk devices directly by adding them to your compose file in this way:
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
ALLOCATE: "N"
|
||||
DISK_FMT: "qcow2"
|
||||
DEVICE2: "/dev/sda"
|
||||
DEVICE3: "/dev/sdb"
|
||||
devices:
|
||||
- /dev/sda
|
||||
- /dev/sdb
|
||||
```
|
||||
|
||||
Please note that this may reduce the write performance of the disk.
|
||||
Please note that the device needs to be totally empty (without any partition table) otherwise DSM does not always format it into a volume.
|
||||
|
||||
Do NOT use this feature with the goal of sharing files from the host, they will all be lost without warning when DSM creates the volume.
|
||||
|
||||
* ### How do I increase the amount of CPU or RAM?
|
||||
|
||||
By default, a single core and 1 GB of RAM are allocated to the container.
|
||||
By default, a single CPU core and 1 GB of RAM are allocated to the container.
|
||||
|
||||
To increase this, add the following environment variables:
|
||||
|
||||
@@ -122,7 +150,7 @@ docker run -it --rm -p 5000:5000 --device=/dev/kvm --cap-add NET_ADMIN --stop-ti
|
||||
sudo kvm-ok
|
||||
```
|
||||
|
||||
If you receive an error from `kvm-ok` indicating that KVM acceleration can't be used, check your BIOS settings.
|
||||
If you receive an error from `kvm-ok` indicating that KVM acceleration can't be used, check the virtualization settings in the BIOS.
|
||||
|
||||
* ### How do I assign an individual IP address to the container?
|
||||
|
||||
@@ -156,11 +184,11 @@ docker run -it --rm -p 5000:5000 --device=/dev/kvm --cap-add NET_ADMIN --stop-ti
|
||||
external: true
|
||||
```
|
||||
|
||||
An added benefit of this approach is that you won't have to perform any port mapping anymore since all ports will be exposed by default.
|
||||
An added benefit of this approach is that you won't have to perform any port mapping anymore, since all ports will be exposed by default.
|
||||
|
||||
Please note that this IP address won't be accessible from the Docker host due to the design of macvlan, which doesn't permit communication between the two. If this is a concern, you need to create a [second macvlan](https://blog.oddbit.com/post/2018-03-12-using-docker-macvlan-networks/#host-access) as a workaround.
|
||||
|
||||
* ### How can the container acquire an IP address from my router?
|
||||
* ### How can DSM acquire an IP address from my router?
|
||||
|
||||
After configuring the container for macvlan (see above), it is possible for DSM to become part of your home network by requesting an IP from your router, just like your other devices.
|
||||
|
||||
@@ -173,11 +201,11 @@ docker run -it --rm -p 5000:5000 --device=/dev/kvm --cap-add NET_ADMIN --stop-ti
|
||||
- 'c *:* rwm'
|
||||
```
|
||||
|
||||
Please note that even if you don't want DHCP, it's still recommended to enable this feature as it prevents NAT issues and increases performance by using a `macvtap` interface. In that case just set a static IP from the DSM control panel after you enabled this mode.
|
||||
Please note that even if you don't want DHCP, it's still recommended to enable this feature, as it prevents NAT issues and increases performance by using a `macvtap` interface. In that case, just set a static IP from the DSM control panel after you enabled this mode.
|
||||
|
||||
* ### How do I passthrough the GPU?
|
||||
* ### How do I pass-through the GPU?
|
||||
|
||||
To passthrough your Intel GPU, add the following lines to your compose file:
|
||||
To pass-through your Intel GPU, add the following lines to your compose file:
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
@@ -190,7 +218,7 @@ docker run -it --rm -p 5000:5000 --device=/dev/kvm --cap-add NET_ADMIN --stop-ti
|
||||
|
||||
* ### How do I install a specific version of vDSM?
|
||||
|
||||
By default, version 7.2.1 will be installed, but if you prefer an older version, you can add its download URL to your compose file as follows:
|
||||
By default, version 7.2 will be installed, but if you prefer an older version, you can add its download URL to your compose file as follows:
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
|
||||
12
src/check.sh
12
src/check.sh
@@ -1,15 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
: ${VM_NET_DEV:='eth0'}
|
||||
[ -f "/run/shm/qemu.end" ] && echo "QEMU is shutting down.." && exit 1
|
||||
[ ! -f "/run/shm/qemu.pid" ] && echo "QEMU is 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
|
||||
file="/run/shm/dsm.url"
|
||||
address="/run/shm/qemu.ip"
|
||||
|
||||
file="/run/dsm.url"
|
||||
[ ! -f "$file" ] && echo "DSM has not enabled networking yet.." && exit 1
|
||||
|
||||
location=$(cat "$file")
|
||||
location=$(<"$file")
|
||||
|
||||
if ! curl -m 20 -ILfSs "http://$location/" > /dev/null; then
|
||||
|
||||
@@ -19,7 +19,7 @@ if ! curl -m 20 -ILfSs "http://$location/" > /dev/null; then
|
||||
echo "Failed to reach DSM at port $port"
|
||||
else
|
||||
echo "Failed to reach DSM at http://$location"
|
||||
ip=$(ip address show dev "$VM_NET_DEV" | grep inet | awk '/inet / { print $2 }' | cut -f1 -d/)
|
||||
ip=$(<"$address")
|
||||
fi
|
||||
|
||||
echo "You might need to whitelist IP $ip in the DSM firewall." && exit 1
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
DEF_OPTS="-nographic -nodefaults -boot strict=on -display none"
|
||||
DEF_OPTS="-nodefaults -boot strict=on"
|
||||
RAM_OPTS=$(echo "-m $RAM_SIZE" | sed 's/MB/M/g;s/GB/G/g;s/TB/T/g')
|
||||
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}"
|
||||
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 -device virtio-rng-pci,rng=objrng0,id=rng0,bus=pcie.0,addr=0x1c"
|
||||
DEV_OPTS="-device virtio-balloon-pci,id=balloon0,bus=pcie.0,addr=0x4"
|
||||
DEV_OPTS="$DEV_OPTS -object rng-random,id=objrng0,filename=/dev/urandom"
|
||||
DEV_OPTS="$DEV_OPTS -device virtio-rng-pci,rng=objrng0,id=rng0,bus=pcie.0,addr=0x1c"
|
||||
|
||||
if [[ "$GPU" == [Yy1]* ]] && [[ "$ARCH" == "amd64" ]]; then
|
||||
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"
|
||||
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 $DISPLAY_OPTS $MON_OPTS $SERIAL_OPTS $NET_OPTS $DISK_OPTS $DEV_OPTS $ARGUMENTS"
|
||||
ARGS=$(echo "$ARGS" | sed 's/\t/ /g' | tr -s ' ')
|
||||
|
||||
return 0
|
||||
|
||||
146
src/disk.sh
146
src/disk.sh
@@ -3,12 +3,12 @@ set -Eeuo pipefail
|
||||
|
||||
# Docker environment variables
|
||||
|
||||
: ${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_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_FLAGS:=''} # Specifies the options for use with the qcow2 disk format
|
||||
: "${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_FLAGS:=""}" # Specifies the options for use with the qcow2 disk format
|
||||
: "${DISK_CACHE:="none"}" # Caching mode, can be set to 'writeback' for better performance
|
||||
: "${DISK_DISCARD:="on"}" # Controls whether unmap (TRIM) commands are passed to the host.
|
||||
: "${DISK_ROTATION:="1"}" # Rotation rate, set to 1 for SSD storage and increase for HDD
|
||||
|
||||
BOOT="$STORAGE/$BASE.boot.img"
|
||||
SYSTEM="$STORAGE/$BASE.system.img"
|
||||
@@ -17,11 +17,11 @@ SYSTEM="$STORAGE/$BASE.system.img"
|
||||
[ ! -f "$SYSTEM" ] && error "Virtual DSM system-image does not exist ($SYSTEM)" && exit 82
|
||||
|
||||
DISK_OPTS="\
|
||||
-object iothread,id=io1 -object iothread,id=io2 \
|
||||
-device virtio-scsi-pci,id=hw-synoboot,iothread=io1,bus=pcie.0,addr=0xa \
|
||||
-object iothread,id=io2 \
|
||||
-device virtio-scsi-pci,id=hw-synoboot,iothread=io2,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 \
|
||||
-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,iothread=io1,bus=pcie.0,addr=0xb \
|
||||
-device virtio-scsi-pci,id=hw-synosys,iothread=io2,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 \
|
||||
-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"
|
||||
|
||||
@@ -61,8 +61,8 @@ getSize() {
|
||||
local DISK_FILE=$1
|
||||
local DISK_EXT DISK_FMT
|
||||
|
||||
DISK_EXT="$(echo "${DISK_FILE//*./}" | sed 's/^.*\.//')"
|
||||
DISK_FMT="$(ext2fmt "$DISK_EXT")"
|
||||
DISK_EXT=$(echo "${DISK_FILE//*./}" | sed 's/^.*\.//')
|
||||
DISK_FMT=$(ext2fmt "$DISK_EXT")
|
||||
|
||||
case "${DISK_FMT,,}" in
|
||||
raw)
|
||||
@@ -83,7 +83,7 @@ isCow() {
|
||||
if [[ "${FS,,}" == "xfs" || "${FS,,}" == "zfs" || "${FS,,}" == "btrfs" || "${FS,,}" == "bcachefs" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
@@ -112,7 +112,9 @@ createDisk() {
|
||||
fi
|
||||
fi
|
||||
|
||||
info "Creating a $DISK_TYPE $DISK_DESC image in $DISK_FMT format with a size of $DISK_SPACE..."
|
||||
MSG="Creating a $DISK_TYPE $DISK_DESC image in $DISK_FMT format with a size of $DISK_SPACE..."
|
||||
info "$MSG" && html "$MSG"
|
||||
|
||||
local FAIL="Could not create a $DISK_TYPE $DISK_FMT $DISK_DESC image of $DISK_SPACE ($DISK_FILE)"
|
||||
|
||||
case "${DISK_FMT,,}" in
|
||||
@@ -126,7 +128,7 @@ createDisk() {
|
||||
fi
|
||||
|
||||
if [[ "$ALLOCATE" == [Nn]* ]]; then
|
||||
|
||||
|
||||
# Create an empty file
|
||||
if ! truncate -s "$DATA_SIZE" "$DISK_FILE"; then
|
||||
rm -f "$DISK_FILE"
|
||||
@@ -195,7 +197,9 @@ resizeDisk() {
|
||||
fi
|
||||
|
||||
local GB=$(( (CUR_SIZE + 1073741823)/1073741824 ))
|
||||
info "Resizing $DISK_DESC from ${GB}G to $DISK_SPACE..."
|
||||
MSG="Resizing $DISK_DESC from ${GB}G to $DISK_SPACE..."
|
||||
info "$MSG" && html "$MSG"
|
||||
|
||||
local FAIL="Could not resize the $DISK_TYPE $DISK_FMT $DISK_DESC image from ${GB}G to $DISK_SPACE ($DISK_FILE)"
|
||||
|
||||
case "${DISK_FMT,,}" in
|
||||
@@ -262,7 +266,8 @@ convertDisk() {
|
||||
fi
|
||||
fi
|
||||
|
||||
info "Converting $DISK_DESC to $DST_FMT, please wait until completed..."
|
||||
MSG="Converting $DISK_DESC to $DST_FMT, please wait until completed..."
|
||||
info "$MSG" && html "$MSG"
|
||||
|
||||
local CONV_FLAGS="-p"
|
||||
local DISK_PARAM="$DISK_ALLOC"
|
||||
@@ -301,7 +306,8 @@ convertDisk() {
|
||||
fi
|
||||
fi
|
||||
|
||||
info "Conversion of $DISK_DESC to $DST_FMT completed succesfully!"
|
||||
MSG="Conversion of $DISK_DESC to $DST_FMT completed succesfully!"
|
||||
info "$MSG" && html "$MSG"
|
||||
|
||||
return 0
|
||||
}
|
||||
@@ -319,6 +325,10 @@ checkFS () {
|
||||
info "Warning: the filesystem of $DIR is OverlayFS, this usually means it was binded to an invalid path!"
|
||||
fi
|
||||
|
||||
if [[ "${FS,,}" == "fuse"* ]]; then
|
||||
info "Warning: the filesystem of $DIR is FUSE, this extra layer will negatively affect performance!"
|
||||
fi
|
||||
|
||||
if isCow "$FS"; then
|
||||
if [ -f "$DISK_FILE" ]; then
|
||||
FA=$(lsattr "$DISK_FILE")
|
||||
@@ -368,7 +378,7 @@ addDisk () {
|
||||
else
|
||||
PREV_FMT="qcow2"
|
||||
fi
|
||||
PREV_EXT="$(fmt2ext "$PREV_FMT")"
|
||||
PREV_EXT=$(fmt2ext "$PREV_FMT")
|
||||
|
||||
if [ -f "$DISK_BASE.$PREV_EXT" ] ; then
|
||||
convertDisk "$DISK_BASE.$PREV_EXT" "$PREV_FMT" "$DISK_FILE" "$DISK_FMT" "$DISK_BASE" "$DISK_DESC" "$FS" || exit $?
|
||||
@@ -397,7 +407,36 @@ addDisk () {
|
||||
return 0
|
||||
}
|
||||
|
||||
DISK_EXT="$(fmt2ext "$DISK_FMT")" || exit $?
|
||||
addDevice () {
|
||||
|
||||
local DISK_ID=$1
|
||||
local DISK_DEV=$2
|
||||
local DISK_DESC=$3
|
||||
local DISK_INDEX=$4
|
||||
local DISK_ADDRESS=$5
|
||||
|
||||
[ -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
|
||||
|
||||
DISK_OPTS="$DISK_OPTS \
|
||||
-device virtio-scsi-pci,id=hw-$DISK_ID,iothread=io2,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 \
|
||||
-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
|
||||
}
|
||||
|
||||
html "Initializing disks..."
|
||||
|
||||
DISK_EXT=$(fmt2ext "$DISK_FMT")
|
||||
|
||||
if [ -z "$ALLOCATE" ]; then
|
||||
if [[ "${DISK_FMT,,}" == "raw" ]]; then
|
||||
ALLOCATE="Y"
|
||||
else
|
||||
ALLOCATE="N"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "$ALLOCATE" == [Nn]* ]]; then
|
||||
DISK_TYPE="growable"
|
||||
@@ -440,52 +479,39 @@ if [ ! -f "$DISK3_FILE.img" ]; then
|
||||
fi
|
||||
|
||||
DISK4_FILE="/storage4/data4"
|
||||
DISK5_FILE="/storage5/data5"
|
||||
DISK6_FILE="/storage6/data6"
|
||||
|
||||
: ${DISK2_SIZE:=''}
|
||||
: ${DISK3_SIZE:=''}
|
||||
: ${DISK4_SIZE:=''}
|
||||
: ${DISK5_SIZE:=''}
|
||||
: ${DISK6_SIZE:=''}
|
||||
: "${DISK2_SIZE:=""}"
|
||||
: "${DISK3_SIZE:=""}"
|
||||
: "${DISK4_SIZE:=""}"
|
||||
|
||||
addDisk "userdata" "$DISK1_FILE" "$DISK_EXT" "disk" "$DISK_SIZE" "3" "0xc" "$DISK_FMT" || exit $?
|
||||
addDisk "userdata2" "$DISK2_FILE" "$DISK_EXT" "disk2" "$DISK2_SIZE" "4" "0xd" "$DISK_FMT" || exit $?
|
||||
addDisk "userdata3" "$DISK3_FILE" "$DISK_EXT" "disk3" "$DISK3_SIZE" "5" "0xe" "$DISK_FMT" || exit $?
|
||||
addDisk "userdata4" "$DISK4_FILE" "$DISK_EXT" "disk4" "$DISK4_SIZE" "9" "0x7" "$DISK_FMT" || exit $?
|
||||
addDisk "userdata5" "$DISK5_FILE" "$DISK_EXT" "disk5" "$DISK5_SIZE" "10" "0x8" "$DISK_FMT" || exit $?
|
||||
addDisk "userdata6" "$DISK6_FILE" "$DISK_EXT" "disk6" "$DISK6_SIZE" "11" "0x9" "$DISK_FMT" || exit $?
|
||||
: "${DEVICE:=""}" # Docker variables to passthrough a block device, like /dev/vdc1.
|
||||
: "${DEVICE2:=""}"
|
||||
: "${DEVICE3:=""}"
|
||||
: "${DEVICE4:=""}"
|
||||
|
||||
addDevice () {
|
||||
if [ -n "$DEVICE" ]; then
|
||||
addDevice "userdata" "$DEVICE" "device" "3" "0xc" || exit $?
|
||||
else
|
||||
addDisk "userdata" "$DISK1_FILE" "$DISK_EXT" "disk" "$DISK_SIZE" "3" "0xc" "$DISK_FMT" || exit $?
|
||||
fi
|
||||
|
||||
local DISK_ID=$1
|
||||
local DISK_DEV=$2
|
||||
local DISK_INDEX=$3
|
||||
local DISK_ADDRESS=$4
|
||||
if [ -n "$DEVICE2" ]; then
|
||||
addDevice "userdata2" "$DEVICE2" "device2" "4" "0xd" || exit $?
|
||||
else
|
||||
addDisk "userdata2" "$DISK2_FILE" "$DISK_EXT" "disk2" "$DISK2_SIZE" "4" "0xd" "$DISK_FMT" || exit $?
|
||||
fi
|
||||
|
||||
[ -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
|
||||
if [ -n "$DEVICE3" ]; then
|
||||
addDevice "userdata3" "$DEVICE3" "device3" "5" "0xe" || exit $?
|
||||
else
|
||||
addDisk "userdata3" "$DISK3_FILE" "$DISK_EXT" "disk3" "$DISK3_SIZE" "5" "0xe" "$DISK_FMT" || exit $?
|
||||
fi
|
||||
|
||||
DISK_OPTS="$DISK_OPTS \
|
||||
-device virtio-scsi-pci,id=hw-$DISK_ID,iothread=io2,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 \
|
||||
-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
|
||||
}
|
||||
|
||||
: ${DEVICE:=''} # Docker variable to passthrough a block device, like /dev/vdc1.
|
||||
: ${DEVICE2:=''}
|
||||
: ${DEVICE3:=''}
|
||||
: ${DEVICE4:=''}
|
||||
: ${DEVICE5:=''}
|
||||
: ${DEVICE6:=''}
|
||||
|
||||
addDevice "userdata7" "$DEVICE" "6" "0xf" || exit $?
|
||||
addDevice "userdata8" "$DEVICE2" "7" "0x5" || exit $?
|
||||
addDevice "userdata9" "$DEVICE3" "8" "0x6" || exit $?
|
||||
addDevice "userdata4" "$DEVICE4" "9" "0x7" || exit $?
|
||||
addDevice "userdata5" "$DEVICE5" "10" "0x8" || exit $?
|
||||
addDevice "userdata6" "$DEVICE6" "11" "0x9" || exit $?
|
||||
if [ -n "$DEVICE4" ]; then
|
||||
addDevice "userdata4" "$DEVICE4" "device4" "6" "0xf" || exit $?
|
||||
else
|
||||
addDisk "userdata4" "$DISK4_FILE" "$DISK_EXT" "disk4" "$DISK4_SIZE" "6" "0xf" "$DISK_FMT" || exit $?
|
||||
fi
|
||||
|
||||
html "Initialized disks successfully..."
|
||||
return 0
|
||||
|
||||
@@ -1,10 +1,22 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
# Docker environment variables
|
||||
|
||||
: "${GPU:="N"}" # GPU passthrough
|
||||
: "${VGA:="virtio"}" # VGA adaptor
|
||||
: "${DISPLAY:="none"}" # Display type
|
||||
|
||||
if [[ "$GPU" != [Yy1]* ]] || [[ "$ARCH" != "amd64" ]]; then
|
||||
|
||||
[[ "${DISPLAY,,}" == "none" ]] && VGA="none"
|
||||
DISPLAY_OPTS="-display $DISPLAY -vga $VGA"
|
||||
return 0
|
||||
|
||||
fi
|
||||
|
||||
DISPLAY_OPTS="-display egl-headless,rendernode=/dev/dri/renderD128 -vga $VGA"
|
||||
|
||||
[ ! -d /dev/dri ] && mkdir -m 755 /dev/dri
|
||||
|
||||
if [ ! -c /dev/dri/card0 ]; then
|
||||
27
src/entry.sh
27
src/entry.sh
@@ -1,17 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
echo "❯ Starting Virtual DSM for Docker v$(</run/version)..."
|
||||
echo "❯ For support visit https://github.com/vdsm/virtual-dsm/"
|
||||
APP="Virtual DSM"
|
||||
SUPPORT="https://github.com/vdsm/virtual-dsm"
|
||||
|
||||
cd /run
|
||||
|
||||
. reset.sh # Initialize system
|
||||
. install.sh # Run installation
|
||||
. disk.sh # Initialize disks
|
||||
. display.sh # Initialize graphics
|
||||
. network.sh # Initialize network
|
||||
. gpu.sh # Initialize graphics
|
||||
. cpu.sh # Initialize processor
|
||||
. proc.sh # Initialize processor
|
||||
. serial.sh # Initialize serialport
|
||||
. power.sh # Configure shutdown
|
||||
. config.sh # Configure arguments
|
||||
@@ -19,16 +19,15 @@ cd /run
|
||||
trap - ERR
|
||||
|
||||
if [[ "$CONSOLE" == [Yy]* ]]; then
|
||||
exec qemu-system-x86_64 -pidfile "$QEMU_PID" ${ARGS:+ $ARGS}
|
||||
exit $?
|
||||
exec qemu-system-x86_64 ${ARGS:+ $ARGS}
|
||||
fi
|
||||
|
||||
set -m
|
||||
(
|
||||
[[ "$DEBUG" == [Yy1]* ]] && info "$VERS" && set -x
|
||||
qemu-system-x86_64 ${ARGS:+ $ARGS} & echo $! > "$QEMU_PID"
|
||||
{ set +x; } 2>/dev/null
|
||||
)
|
||||
set +m
|
||||
[[ "$DEBUG" == [Yy1]* ]] && info "$VERS" && echo "Arguments: $ARGS" && echo
|
||||
{ qemu-system-x86_64 ${ARGS:+ $ARGS} >"$QEMU_OUT" 2>"$QEMU_LOG"; rc=$?; } || :
|
||||
(( rc != 0 )) && error "$(<"$QEMU_LOG")" && exit 15
|
||||
|
||||
tail --pid "$(cat "$QEMU_PID")" --follow /dev/null & wait $!
|
||||
terminal
|
||||
tail -fn +0 "$QEMU_LOG" 2>/dev/null &
|
||||
cat "$QEMU_TERM" 2>/dev/null & wait $! || :
|
||||
|
||||
sleep 1 && finish 0
|
||||
|
||||
121
src/install.sh
121
src/install.sh
@@ -1,23 +1,29 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
: ${URL:=''} # URL of the PAT file to be downloaded.
|
||||
: "${URL:=""}" # URL of the PAT file to be downloaded.
|
||||
|
||||
if [ -f "$STORAGE"/dsm.ver ]; then
|
||||
BASE=$(cat "$STORAGE/dsm.ver")
|
||||
if [ -f "$STORAGE/dsm.ver" ]; then
|
||||
BASE=$(<"$STORAGE/dsm.ver")
|
||||
else
|
||||
# Fallback for old installs
|
||||
BASE="DSM_VirtualDSM_42962"
|
||||
fi
|
||||
|
||||
[ -n "$URL" ] && BASE=$(basename "$URL" .pat)
|
||||
if [ -n "$URL" ]; then
|
||||
BASE=$(basename "$URL" .pat)
|
||||
if [ ! -f "$STORAGE/$BASE.system.img" ]; then
|
||||
BASE=$(basename "${URL%%\?*}" .pat)
|
||||
: "${BASE//+/ }"; printf -v BASE '%b' "${_//%/\\x}"
|
||||
BASE=$(echo "$BASE" | sed -e 's/[^A-Za-z0-9._-]/_/g')
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -f "$STORAGE/$BASE.boot.img" ]] && [[ -f "$STORAGE/$BASE.system.img" ]]; then
|
||||
return 0 # Previous installation found
|
||||
fi
|
||||
|
||||
# Display wait message
|
||||
/run/server.sh 5000 install &
|
||||
html "Please wait while Virtual DSM is being installed..."
|
||||
|
||||
DL=""
|
||||
DL_CHINA="https://cndl.synology.cn/download/DSM"
|
||||
@@ -34,47 +40,55 @@ fi
|
||||
|
||||
[ -z "$URL" ] && URL="$DL/release/7.2.1/69057-1/DSM_VirtualDSM_69057.pat"
|
||||
|
||||
BASE=$(basename "$URL" .pat)
|
||||
BASE=$(basename "${URL%%\?*}" .pat)
|
||||
: "${BASE//+/ }"; printf -v BASE '%b' "${_//%/\\x}"
|
||||
BASE=$(echo "$BASE" | sed -e 's/[^A-Za-z0-9._-]/_/g')
|
||||
|
||||
if [[ "$URL" != "file://$STORAGE/$BASE.pat" ]]; then
|
||||
rm -f "$STORAGE"/"$BASE".pat
|
||||
rm -f "$STORAGE/$BASE.pat"
|
||||
fi
|
||||
|
||||
rm -f "$STORAGE"/"$BASE".agent
|
||||
rm -f "$STORAGE"/"$BASE".boot.img
|
||||
rm -f "$STORAGE"/"$BASE".system.img
|
||||
rm -f "$STORAGE/$BASE.agent"
|
||||
rm -f "$STORAGE/$BASE.boot.img"
|
||||
rm -f "$STORAGE/$BASE.system.img"
|
||||
|
||||
[[ "$DEBUG" == [Yy1]* ]] && set -x
|
||||
|
||||
# Check filesystem
|
||||
MIN_ROOT=471859200
|
||||
MIN_SPACE=6442450944
|
||||
FS=$(stat -f -c %T "$STORAGE")
|
||||
|
||||
if [[ "${FS,,}" == "overlay"* ]]; then
|
||||
info "Warning: the filesystem of $STORAGE is OverlayFS, this usually means it was binded to an invalid path!"
|
||||
fi
|
||||
|
||||
if [[ "${FS,,}" != "fat"* && "${FS,,}" != "vfat"* && "${FS,,}" != "exfat"* && \
|
||||
"${FS,,}" != "ntfs"* && "${FS,,}" != "fuse"* && "${FS,,}" != "msdos"* ]]; then
|
||||
if [[ "${FS,,}" == "fuse"* ]]; then
|
||||
info "Warning: the filesystem of $STORAGE is FUSE, this extra layer will negatively affect performance!"
|
||||
fi
|
||||
|
||||
if [[ "${FS,,}" != "fat"* && "${FS,,}" != "vfat"* && "${FS,,}" != "exfat"* && "${FS,,}" != "ntfs"* && "${FS,,}" != "msdos"* ]]; then
|
||||
TMP="$STORAGE/tmp"
|
||||
else
|
||||
TMP="/tmp/dsm"
|
||||
TMP_SPACE=2147483648
|
||||
SPACE=$(df --output=avail -B 1 /tmp | tail -n 1)
|
||||
if (( MIN_SPACE > SPACE )); then
|
||||
error "The ${FS^^} filesystem of $STORAGE does not support UNIX permissions, and no space left in container!" && exit 93
|
||||
SPACE_MB=$(( (SPACE + 1048575)/1048576 ))
|
||||
if (( TMP_SPACE > SPACE )); then
|
||||
error "Not enough free space inside the container, have $SPACE_MB MB available but need at least 2 GB." && exit 93
|
||||
fi
|
||||
fi
|
||||
|
||||
rm -rf "$TMP" && mkdir -p "$TMP"
|
||||
|
||||
# Check free diskspace
|
||||
ROOT_SPACE=536870912
|
||||
SPACE=$(df --output=avail -B 1 / | tail -n 1)
|
||||
(( MIN_ROOT > SPACE )) && error "Not enough free space in container root, need at least 450 MB available." && exit 96
|
||||
SPACE_MB=$(( (SPACE + 1048575)/1048576 ))
|
||||
(( ROOT_SPACE > SPACE )) && error "Not enough free space inside the container, have $SPACE_MB MB available but need at least 500 MB." && exit 96
|
||||
|
||||
MIN_SPACE=8589934592
|
||||
SPACE=$(df --output=avail -B 1 "$STORAGE" | tail -n 1)
|
||||
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 8 GB." && exit 94
|
||||
|
||||
# Check if output is to interactive TTY
|
||||
if [ -t 1 ]; then
|
||||
@@ -90,7 +104,8 @@ RDC="$STORAGE/dsm.rd"
|
||||
|
||||
if [ ! -f "$RDC" ]; then
|
||||
|
||||
info "Install: Downloading installer..."
|
||||
MSG="Downloading installer..."
|
||||
info "Install: $MSG" && html "$MSG"
|
||||
|
||||
RD="$TMP/rd.gz"
|
||||
POS="65627648-71021835"
|
||||
@@ -133,7 +148,7 @@ if [ -f "$RDC" ]; then
|
||||
(( rc != 0 )) && error "Failed to extract $RDC, reason $rc" && exit 92
|
||||
fi
|
||||
|
||||
mkdir -p /run/extract
|
||||
rm -rf /run/extract && 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 \
|
||||
@@ -159,7 +174,8 @@ fi
|
||||
|
||||
rm -rf "$TMP" && mkdir -p "$TMP"
|
||||
|
||||
info "Install: Downloading $(basename "$URL")..."
|
||||
info "Install: Downloading $BASE.pat..."
|
||||
html "Install: Downloading DSM from Synology..."
|
||||
|
||||
PAT="/$BASE.pat"
|
||||
rm -f "$PAT"
|
||||
@@ -183,7 +199,8 @@ if ((SIZE<250000000)); then
|
||||
error "The specified PAT file is probably an update pack as it's too small." && exit 62
|
||||
fi
|
||||
|
||||
info "Install: Extracting downloaded image..."
|
||||
MSG="Extracting downloaded image..."
|
||||
info "Install: $MSG" && html "$MSG"
|
||||
|
||||
if { tar tf "$PAT"; } >/dev/null 2>&1; then
|
||||
|
||||
@@ -205,7 +222,10 @@ else
|
||||
|
||||
fi
|
||||
|
||||
info "Install: Preparing system partition..."
|
||||
rm -rf /run/extract
|
||||
|
||||
MSG="Preparing system partition..."
|
||||
info "Install: $MSG" && html "$MSG"
|
||||
|
||||
BOOT=$(find "$TMP" -name "*.bin.zip")
|
||||
[ ! -f "$BOOT" ] && error "The PAT file contains no boot image." && exit 67
|
||||
@@ -213,19 +233,22 @@ BOOT=$(find "$TMP" -name "*.bin.zip")
|
||||
BOOT=$(echo "$BOOT" | head -c -5)
|
||||
unzip -q -o "$BOOT".zip -d "$TMP"
|
||||
|
||||
SYSTEM="$TMP/sys.img"
|
||||
SYSTEM_SIZE=4954537983
|
||||
SYSTEM="$STORAGE/$BASE.system.img"
|
||||
rm -f "$SYSTEM"
|
||||
|
||||
# Check free diskspace
|
||||
SPACE=$(df --output=avail -B 1 "$TMP" | tail -n 1)
|
||||
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 97
|
||||
SYSTEM_SIZE=4954537983
|
||||
SPACE=$(df --output=avail -B 1 "$STORAGE" | tail -n 1)
|
||||
SPACE_MB=$(( (SPACE + 1048575)/1048576 ))
|
||||
|
||||
if (( SYSTEM_SIZE > SPACE )); then
|
||||
error "Not enough free space in $STORAGE to create a 5 GB system disk, have only $SPACE_MB MB available." && exit 97
|
||||
fi
|
||||
|
||||
if ! touch "$SYSTEM"; then
|
||||
error "Could not create file $SYSTEM for the system disk." && exit 98
|
||||
fi
|
||||
|
||||
|
||||
if [[ "${FS,,}" == "xfs" || "${FS,,}" == "zfs" || "${FS,,}" == "btrfs" || "${FS,,}" == "bcachefs" ]]; then
|
||||
{ chattr +C "$SYSTEM"; } || :
|
||||
FA=$(lsattr "$SYSTEM")
|
||||
@@ -241,17 +264,6 @@ if ! fallocate -l "$SYSTEM_SIZE" "$SYSTEM"; then
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check if file exists
|
||||
[ ! -f "$SYSTEM" ] && error "System disk does not exist ($SYSTEM)" && exit 99
|
||||
|
||||
# Check the filesize
|
||||
SIZE=$(stat -c%s "$SYSTEM")
|
||||
|
||||
if [[ SIZE -ne SYSTEM_SIZE ]]; then
|
||||
rm -f "$SYSTEM"
|
||||
error "System disk has the wrong size: $SIZE vs $SYSTEM_SIZE" && exit 90
|
||||
fi
|
||||
|
||||
PART="$TMP/partition.fdisk"
|
||||
|
||||
{ echo "label: dos"
|
||||
@@ -269,7 +281,8 @@ sfdisk -q "$SYSTEM" < "$PART"
|
||||
MOUNT="$TMP/system"
|
||||
rm -rf "$MOUNT" && mkdir -p "$MOUNT"
|
||||
|
||||
info "Install: Extracting system partition..."
|
||||
MSG="Extracting system partition..."
|
||||
info "Install: $MSG" && html "$MSG"
|
||||
|
||||
HDA="$TMP/hda1"
|
||||
IDB="$TMP/indexdb"
|
||||
@@ -277,15 +290,8 @@ PKG="$TMP/packages"
|
||||
HDP="$TMP/synohdpack_img"
|
||||
|
||||
[ ! -f "$HDA.tgz" ] && error "The PAT file contains no OS image." && exit 64
|
||||
|
||||
mv "$HDA.tgz" "$HDA.txz"
|
||||
|
||||
if [[ "$ROOT" != [Nn]* ]]; then
|
||||
|
||||
tar xpfJ "$HDA.txz" --absolute-names -C "$MOUNT/"
|
||||
|
||||
fi
|
||||
|
||||
[ -d "$PKG" ] && mv "$PKG/" "$MOUNT/.SynoUpgradePackages/"
|
||||
rm -f "$MOUNT/.SynoUpgradePackages/ActiveInsight-"*
|
||||
|
||||
@@ -300,10 +306,13 @@ fi
|
||||
LABEL="1.44.1-42218"
|
||||
OFFSET="1048576" # 2048 * 512
|
||||
NUMBLOCKS="622560" # (4980480 * 512) / 4096
|
||||
MSG="Installing system partition..."
|
||||
|
||||
if [[ "$ROOT" != [Nn]* ]]; then
|
||||
|
||||
info "Install: Installing system partition..."
|
||||
tar xpfJ "$HDA.txz" --absolute-names --skip-old-files -C "$MOUNT/"
|
||||
|
||||
info "Install: $MSG" && html "$MSG"
|
||||
|
||||
mke2fs -q -t ext4 -b 4096 -d "$MOUNT/" -L "$LABEL" -F -E "offset=$OFFSET" "$SYSTEM" "$NUMBLOCKS"
|
||||
|
||||
@@ -311,27 +320,25 @@ else
|
||||
|
||||
fakeroot -- bash -c "set -Eeu;\
|
||||
tar xpfJ $HDA.txz --absolute-names --skip-old-files -C $MOUNT/;\
|
||||
printf '%b%s%b' '\E[1;34m❯ \E[1;36m' 'Install: Installing system partition...' '\E[0m\n';\
|
||||
printf '%b%s%b' '\E[1;34m❯ \E[1;36m' 'Install: $MSG' '\E[0m\n';\
|
||||
mke2fs -q -t ext4 -b 4096 -d $MOUNT/ -L $LABEL -F -E offset=$OFFSET $SYSTEM $NUMBLOCKS"
|
||||
|
||||
fi
|
||||
|
||||
rm -rf "$MOUNT"
|
||||
|
||||
echo "$BASE" > "$STORAGE"/dsm.ver
|
||||
echo "$BASE" > "$STORAGE/dsm.ver"
|
||||
|
||||
if [[ "$URL" == "file://$STORAGE/$BASE.pat" ]]; then
|
||||
rm -f "$PAT"
|
||||
else
|
||||
mv -f "$PAT" "$STORAGE"/"$BASE".pat
|
||||
mv -f "$PAT" "$STORAGE/$BASE.pat"
|
||||
fi
|
||||
|
||||
mv -f "$BOOT" "$STORAGE"/"$BASE".boot.img
|
||||
mv -f "$SYSTEM" "$STORAGE"/"$BASE".system.img
|
||||
|
||||
mv -f "$BOOT" "$STORAGE/$BASE.boot.img"
|
||||
rm -rf "$TMP"
|
||||
|
||||
{ set +x; } 2>/dev/null
|
||||
[[ "$DEBUG" == [Yy1]* ]] && echo
|
||||
|
||||
html "Installation finished successfully..."
|
||||
return 0
|
||||
|
||||
129
src/network.sh
129
src/network.sh
@@ -3,17 +3,17 @@ set -Eeuo pipefail
|
||||
|
||||
# Docker environment variables
|
||||
|
||||
: ${DHCP:='N'}
|
||||
: ${MAC:='02:11:32:AA:BB:CC'}
|
||||
: "${DHCP:="N"}"
|
||||
: "${MAC:="02:11:32:AA:BB:CC"}"
|
||||
|
||||
: ${VM_NET_TAP:='dsm'}
|
||||
: ${VM_NET_DEV:='eth0'}
|
||||
: ${VM_NET_MAC:="$MAC"}
|
||||
: ${VM_NET_HOST:='VirtualDSM'}
|
||||
: "${VM_NET_DEV:=""}"
|
||||
: "${VM_NET_TAP:="dsm"}"
|
||||
: "${VM_NET_MAC:="$MAC"}"
|
||||
: "${VM_NET_HOST:="VirtualDSM"}"
|
||||
|
||||
: ${DNSMASQ_OPTS:=''}
|
||||
: ${DNSMASQ:='/usr/sbin/dnsmasq'}
|
||||
: ${DNSMASQ_CONF_DIR:='/etc/dnsmasq.d'}
|
||||
: "${DNSMASQ_OPTS:=""}"
|
||||
: "${DNSMASQ:="/usr/sbin/dnsmasq"}"
|
||||
: "${DNSMASQ_CONF_DIR:="/etc/dnsmasq.d"}"
|
||||
|
||||
ADD_ERR="Please add the following setting to your container:"
|
||||
|
||||
@@ -69,7 +69,7 @@ configureDHCP() {
|
||||
return 0
|
||||
}
|
||||
|
||||
configureDNS () {
|
||||
configureDNS() {
|
||||
|
||||
# dnsmasq configuration:
|
||||
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"
|
||||
@@ -90,7 +90,27 @@ configureDNS () {
|
||||
return 0
|
||||
}
|
||||
|
||||
configureNAT () {
|
||||
configureNAT() {
|
||||
|
||||
# Create the necessary file structure for /dev/net/tun
|
||||
if [ ! -c /dev/net/tun ]; then
|
||||
[ ! -d /dev/net ] && mkdir -m 755 /dev/net
|
||||
if mknod /dev/net/tun c 10 200; then
|
||||
chmod 666 /dev/net/tun
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ! -c /dev/net/tun ]; then
|
||||
error "TUN device missing. $ADD_ERR --cap-add NET_ADMIN" && exit 25
|
||||
fi
|
||||
|
||||
# Check port forwarding flag
|
||||
if [[ $(< /proc/sys/net/ipv4/ip_forward) -eq 0 ]]; then
|
||||
{ sysctl -w net.ipv4.ip_forward=1 ; rc=$?; } || :
|
||||
if (( rc != 0 )); then
|
||||
error "IP forwarding is disabled. $ADD_ERR --sysctl net.ipv4.ip_forward=1" && exit 24
|
||||
fi
|
||||
fi
|
||||
|
||||
# Create a bridge with a static IP for the VM guest
|
||||
|
||||
@@ -121,8 +141,11 @@ configureNAT () {
|
||||
ip link set dev "$VM_NET_TAP" master dockerbridge
|
||||
|
||||
# Add internet connection to the VM
|
||||
update-alternatives --set iptables /usr/sbin/iptables-legacy > /dev/null
|
||||
update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy > /dev/null
|
||||
|
||||
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"
|
||||
|
||||
if (( KERNEL > 4 )); then
|
||||
@@ -133,14 +156,6 @@ configureNAT () {
|
||||
{ set +x; } 2>/dev/null
|
||||
[[ "$DEBUG" == [Yy1]* ]] && echo
|
||||
|
||||
# Check port forwarding flag
|
||||
if [[ $(< /proc/sys/net/ipv4/ip_forward) -eq 0 ]]; then
|
||||
{ sysctl -w net.ipv4.ip_forward=1 ; rc=$?; } || :
|
||||
if (( rc != 0 )); then
|
||||
error "IP forwarding is disabled. $ADD_ERR --sysctl net.ipv4.ip_forward=1" && exit 24
|
||||
fi
|
||||
fi
|
||||
|
||||
NET_OPTS="-netdev tap,ifname=$VM_NET_TAP,script=no,downscript=no,id=hostnet0"
|
||||
|
||||
{ exec 40>>/dev/vhost-net; rc=$?; } 2>/dev/null || :
|
||||
@@ -151,21 +166,24 @@ configureNAT () {
|
||||
return 0
|
||||
}
|
||||
|
||||
closeNetwork () {
|
||||
closeNetwork() {
|
||||
|
||||
exec 30<&- || true
|
||||
exec 40<&- || true
|
||||
|
||||
if [[ "$DHCP" == [Yy1]* ]]; then
|
||||
|
||||
{ pkill -f server.sh || true; } 2>/dev/null
|
||||
# Shutdown nginx
|
||||
nginx -s stop 2> /dev/null
|
||||
fWait "nginx"
|
||||
|
||||
ip link set "$VM_NET_TAP" down || true
|
||||
ip link delete "$VM_NET_TAP" || true
|
||||
|
||||
else
|
||||
|
||||
{ pkill -f dnsmasq || true; } 2>/dev/null
|
||||
local pid="/var/run/dnsmasq.pid"
|
||||
[ -f "$pid" ] && pKill "$(<"$pid")"
|
||||
|
||||
ip link set "$VM_NET_TAP" down promisc off || true
|
||||
ip link delete "$VM_NET_TAP" || true
|
||||
@@ -174,42 +192,55 @@ closeNetwork () {
|
||||
ip link delete dockerbridge || true
|
||||
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
getInfo() {
|
||||
|
||||
if [ -z "$VM_NET_DEV" ]; then
|
||||
# Automaticly detect the default network interface
|
||||
VM_NET_DEV=$(awk '$2 == 00000000 { print $1 }' /proc/net/route)
|
||||
[ -z "$VM_NET_DEV" ] && VM_NET_DEV="eth0"
|
||||
fi
|
||||
|
||||
if [ ! -d "/sys/class/net/$VM_NET_DEV" ]; then
|
||||
error "Network interface '$VM_NET_DEV' does not exist inside the container!"
|
||||
error "$ADD_ERR -e \"VM_NET_DEV=NAME\" to specify another interface name." && exit 27
|
||||
fi
|
||||
|
||||
VM_NET_MAC="${VM_NET_MAC//-/:}"
|
||||
if [[ ${#VM_NET_MAC} == 12 ]]; then
|
||||
m="$VM_NET_MAC"
|
||||
VM_NET_MAC="${m:0:2}:${m:2:2}:${m:4:2}:${m:6:2}:${m:8:2}:${m:10:2}"
|
||||
fi
|
||||
|
||||
if [[ ${#VM_NET_MAC} != 17 ]]; then
|
||||
error "Invalid mac address: '$VM_NET_MAC', should be 12 or 17 digits long!" && exit 28
|
||||
fi
|
||||
|
||||
GATEWAY=$(ip r | grep default | awk '{print $3}')
|
||||
IP=$(ip address show dev "$VM_NET_DEV" | grep inet | awk '/inet / { print $2 }' | cut -f1 -d/)
|
||||
echo "$IP" > /run/shm/qemu.ip
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# ######################################
|
||||
# Configure Network
|
||||
# ######################################
|
||||
|
||||
{ pkill -f server.sh || true; } 2>/dev/null
|
||||
|
||||
# Create the necessary file structure for /dev/net/tun
|
||||
if [ ! -c /dev/net/tun ]; then
|
||||
[ ! -d /dev/net ] && mkdir -m 755 /dev/net
|
||||
if mknod /dev/net/tun c 10 200; then
|
||||
chmod 666 /dev/net/tun
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ! -c /dev/net/tun ]; then
|
||||
error "TUN device missing. $ADD_ERR --cap-add NET_ADMIN" && exit 25
|
||||
fi
|
||||
|
||||
# Create the necessary file structure for /dev/vhost-net
|
||||
if [ ! -c /dev/vhost-net ]; then
|
||||
if mknod /dev/vhost-net c 10 238; then
|
||||
chmod 660 /dev/vhost-net
|
||||
fi
|
||||
fi
|
||||
|
||||
update-alternatives --set iptables /usr/sbin/iptables-legacy > /dev/null
|
||||
update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy > /dev/null
|
||||
|
||||
VM_NET_MAC="${VM_NET_MAC//-/:}"
|
||||
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/)
|
||||
getInfo
|
||||
html "Initializing network..."
|
||||
|
||||
if [[ "$DEBUG" == [Yy1]* ]]; then
|
||||
info "Container IP is $IP with gateway $GATEWAY" && echo
|
||||
info "Container IP is $IP with gateway $GATEWAY on interface $VM_NET_DEV" && echo
|
||||
fi
|
||||
|
||||
if [[ "$DHCP" == [Yy1]* ]]; then
|
||||
@@ -223,11 +254,15 @@ if [[ "$DHCP" == [Yy1]* ]]; then
|
||||
# Configuration for DHCP IP
|
||||
configureDHCP
|
||||
|
||||
# Display IP on port 80 and 5000
|
||||
/run/server.sh 5000 /run/ip.sh &
|
||||
MSG="Booting DSM instance..."
|
||||
html "$MSG"
|
||||
|
||||
else
|
||||
|
||||
# Shutdown nginx
|
||||
nginx -s stop 2> /dev/null
|
||||
fWait "nginx"
|
||||
|
||||
# Configuration for static IP
|
||||
configureNAT
|
||||
|
||||
|
||||
177
src/power.sh
177
src/power.sh
@@ -3,38 +3,133 @@ set -Eeuo pipefail
|
||||
|
||||
# Configure QEMU for graceful shutdown
|
||||
|
||||
API_CMD=6
|
||||
API_TIMEOUT=50
|
||||
API_HOST="127.0.0.1:2210"
|
||||
|
||||
QEMU_TERM=""
|
||||
QEMU_PORT=7100
|
||||
QEMU_TIMEOUT=55
|
||||
QEMU_PID="/run/qemu.pid"
|
||||
QEMU_COUNT="/run/qemu.count"
|
||||
QEMU_TIMEOUT=50
|
||||
QEMU_PID="/run/shm/qemu.pid"
|
||||
QEMU_LOG="/run/shm/qemu.log"
|
||||
QEMU_OUT="/run/shm/qemu.out"
|
||||
QEMU_END="/run/shm/qemu.end"
|
||||
|
||||
rm -f "$QEMU_PID"
|
||||
rm -f "$QEMU_COUNT"
|
||||
if [[ "$KVM" == [Nn]* ]]; then
|
||||
API_TIMEOUT=$(( API_TIMEOUT*2 ))
|
||||
QEMU_TIMEOUT=$(( QEMU_TIMEOUT*2 ))
|
||||
fi
|
||||
|
||||
_trap(){
|
||||
func="$1" ; shift
|
||||
for sig ; do
|
||||
trap "$func $sig" "$sig"
|
||||
touch "$QEMU_LOG"
|
||||
|
||||
_trap() {
|
||||
func="$1" ; shift
|
||||
for sig ; do
|
||||
trap "$func $sig" "$sig"
|
||||
done
|
||||
}
|
||||
|
||||
finish() {
|
||||
|
||||
local pid
|
||||
local reason=$1
|
||||
|
||||
if [ -f "$QEMU_PID" ]; then
|
||||
|
||||
pid=$(<"$QEMU_PID")
|
||||
echo && error "Forcefully terminating QEMU process, reason: $reason..."
|
||||
{ kill -15 "$pid" || true; } 2>/dev/null
|
||||
|
||||
while isAlive "$pid"; do
|
||||
sleep 1
|
||||
# Workaround for zombie pid
|
||||
[ ! -f "$QEMU_PID" ] && break
|
||||
done
|
||||
fi
|
||||
|
||||
fKill "print.sh"
|
||||
fKill "host.bin"
|
||||
|
||||
closeNetwork
|
||||
|
||||
sleep 1
|
||||
echo && echo "❯ Shutdown completed!"
|
||||
|
||||
exit "$reason"
|
||||
}
|
||||
|
||||
terminal() {
|
||||
|
||||
local dev=""
|
||||
|
||||
if [ -f "$QEMU_OUT" ]; then
|
||||
|
||||
local msg
|
||||
msg=$(<"$QEMU_OUT")
|
||||
|
||||
if [ -n "$msg" ]; then
|
||||
|
||||
if [[ "${msg,,}" != "char"* || "$msg" != *"serial0)" ]]; then
|
||||
echo "$msg"
|
||||
fi
|
||||
|
||||
dev="${msg#*/dev/p}"
|
||||
dev="/dev/p${dev%% *}"
|
||||
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ! -c "$dev" ]; then
|
||||
dev=$(echo 'info chardev' | nc -q 1 -w 1 localhost "$QEMU_PORT" | tr -d '\000')
|
||||
dev="${dev#*serial0}"
|
||||
dev="${dev#*pty:}"
|
||||
dev="${dev%%$'\n'*}"
|
||||
dev="${dev%%$'\r'*}"
|
||||
fi
|
||||
|
||||
if [ ! -c "$dev" ]; then
|
||||
error "Device '$dev' not found!"
|
||||
finish 34 && return 34
|
||||
fi
|
||||
|
||||
QEMU_TERM="$dev"
|
||||
return 0
|
||||
}
|
||||
|
||||
_graceful_shutdown() {
|
||||
|
||||
local cnt=0
|
||||
local code=$?
|
||||
local pid url response
|
||||
|
||||
set +e
|
||||
local cnt response
|
||||
|
||||
[ ! -f "$QEMU_PID" ] && exit 130
|
||||
[ -f "$QEMU_COUNT" ] && return
|
||||
if [ -f "$QEMU_END" ]; then
|
||||
echo && info "Received $1 signal while already shutting down..."
|
||||
return
|
||||
fi
|
||||
|
||||
echo 0 > "$QEMU_COUNT"
|
||||
touch "$QEMU_END"
|
||||
echo && info "Received $1 signal, sending shutdown command..."
|
||||
|
||||
if [ ! -f "$QEMU_PID" ]; then
|
||||
echo && error "QEMU PID file does not exist?"
|
||||
finish "$code" && return "$code"
|
||||
fi
|
||||
|
||||
pid=$(<"$QEMU_PID")
|
||||
|
||||
if ! isAlive "$pid"; then
|
||||
echo && error "QEMU process does not exist?"
|
||||
finish "$code" && return "$code"
|
||||
fi
|
||||
|
||||
# Don't send the powerdown signal because vDSM ignores ACPI signals
|
||||
# echo 'system_powerdown' | nc -q 1 -w 1 localhost "${QEMU_PORT}" > /dev/null
|
||||
|
||||
# Send shutdown command to guest agent via serial port
|
||||
url="http://127.0.0.1:2210/read?command=6&timeout=50"
|
||||
response=$(curl -sk -m 52 -S "$url" 2>&1)
|
||||
url="http://$API_HOST/read?command=$API_CMD&timeout=$API_TIMEOUT"
|
||||
response=$(curl -sk -m "$(( API_TIMEOUT+2 ))" -S "$url" 2>&1)
|
||||
|
||||
if [[ "$response" =~ "\"success\"" ]]; then
|
||||
|
||||
@@ -43,45 +138,43 @@ _graceful_shutdown() {
|
||||
else
|
||||
|
||||
response="${response#*message\"\: \"}"
|
||||
echo && error "Failed to send shutdown command: ${response%%\"*}"
|
||||
|
||||
kill -15 "$(cat "$QEMU_PID")"
|
||||
pkill -f qemu-system-x86_64 || true
|
||||
[ -z "$response" ] && response="second signal"
|
||||
echo && error "Forcefully terminating because of: ${response%%\"*}"
|
||||
{ kill -15 "$pid" || true; } 2>/dev/null
|
||||
|
||||
fi
|
||||
|
||||
while [ "$(cat $QEMU_COUNT)" -lt "$QEMU_TIMEOUT" ]; do
|
||||
while [ "$cnt" -lt "$QEMU_TIMEOUT" ]; do
|
||||
|
||||
# Try to connect to qemu
|
||||
if ! echo 'info version'| nc -q 1 -w 1 localhost "$QEMU_PORT" >/dev/null 2>&1 ; then
|
||||
break
|
||||
fi
|
||||
! isAlive "$pid" && break
|
||||
|
||||
# Increase the counter
|
||||
cnt=$(($(cat $QEMU_COUNT)+1))
|
||||
echo $cnt > "$QEMU_COUNT"
|
||||
sleep 1
|
||||
cnt=$((cnt+1))
|
||||
|
||||
[[ "$DEBUG" == [Yy1]* ]] && info "Shutting down, waiting... ($cnt/$QEMU_TIMEOUT)"
|
||||
|
||||
# Workaround for zombie pid
|
||||
[ ! -f "$QEMU_PID" ] && break
|
||||
|
||||
done
|
||||
|
||||
if [ "$(cat $QEMU_COUNT)" -ge "$QEMU_TIMEOUT" ]; then
|
||||
echo && error "Shutdown timeout reached, forcefully quitting.."
|
||||
else
|
||||
echo && echo "❯ Quitting..."
|
||||
if [ "$cnt" -ge "$QEMU_TIMEOUT" ]; then
|
||||
echo && error "Shutdown timeout reached, aborting..."
|
||||
fi
|
||||
|
||||
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
|
||||
sleep 1
|
||||
|
||||
return
|
||||
finish "$code" && return "$code"
|
||||
}
|
||||
|
||||
_trap _graceful_shutdown SIGTERM SIGHUP SIGINT SIGABRT SIGQUIT
|
||||
MON_OPTS="\
|
||||
-pidfile $QEMU_PID \
|
||||
-monitor telnet:localhost:$QEMU_PORT,server,nowait,nodelay"
|
||||
|
||||
MON_OPTS="-monitor telnet:localhost:$QEMU_PORT,server,nowait,nodelay"
|
||||
if [[ "$CONSOLE" != [Yy]* ]]; then
|
||||
|
||||
MON_OPTS="$MON_OPTS -daemonize -D $QEMU_LOG"
|
||||
|
||||
_trap _graceful_shutdown SIGTERM SIGHUP SIGINT SIGABRT SIGQUIT
|
||||
|
||||
fi
|
||||
|
||||
return 0
|
||||
|
||||
31
src/print.sh
31
src/print.sh
@@ -1,17 +1,21 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
: ${DHCP:='N'}
|
||||
: ${VM_NET_DEV:='eth0'}
|
||||
: "${DHCP:="N"}"
|
||||
|
||||
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; }
|
||||
|
||||
file="/run/dsm.url"
|
||||
shutdown="/run/qemu.count"
|
||||
file="/run/shm/dsm.url"
|
||||
info="/run/shm/msg.html"
|
||||
page="/run/shm/index.html"
|
||||
address="/run/shm/qemu.ip"
|
||||
shutdown="/run/shm/qemu.end"
|
||||
template="/var/www/index.html"
|
||||
url="http://127.0.0.1:2210/read?command=10"
|
||||
|
||||
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 [ ! -f "$file" ]
|
||||
@@ -29,7 +33,7 @@ do
|
||||
{ json=$(curl -m 20 -sk "$url"); rc=$?; } || :
|
||||
|
||||
[ -f "$shutdown" ] && exit 1
|
||||
(( rc != 0 )) && error "Failed to connect to guest: curl error $rc" && continue
|
||||
(( rc != 0 )) && error "$curl_err $rc" && continue
|
||||
|
||||
{ result=$(echo "$json" | jq -r '.status'); rc=$?; } || :
|
||||
(( rc != 0 )) && error "$jq_err $rc ( $json )" && continue
|
||||
@@ -60,15 +64,28 @@ done
|
||||
|
||||
[ -f "$shutdown" ] && exit 1
|
||||
|
||||
location=$(cat "$file")
|
||||
location=$(<"$file")
|
||||
|
||||
if [[ "$location" != "20.20"* ]]; then
|
||||
|
||||
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\]/}"
|
||||
|
||||
echo "$HTML" > "$page"
|
||||
echo "$body" > "$info"
|
||||
|
||||
else
|
||||
|
||||
ip=$(ip address show dev "$VM_NET_DEV" | grep inet | awk '/inet / { print $2 }' | cut -f1 -d/)
|
||||
ip=$(<"$address")
|
||||
port="${location##*:}"
|
||||
|
||||
if [[ "$ip" == "172."* ]]; then
|
||||
|
||||
@@ -3,9 +3,10 @@ set -Eeuo pipefail
|
||||
|
||||
# Docker environment variables
|
||||
|
||||
: ${HOST_CPU:=''}
|
||||
: ${CPU_MODEL:='host'}
|
||||
: ${CPU_FEATURES:='+ssse3,+sse4.1,+sse4.2'}
|
||||
: "${KVM:="Y"}"
|
||||
: "${HOST_CPU:=""}"
|
||||
: "${CPU_MODEL:="host"}"
|
||||
: "${CPU_FEATURES:="+ssse3,+sse4.1,+sse4.2"}"
|
||||
|
||||
[ "$ARCH" != "amd64" ] && KVM="N"
|
||||
|
||||
@@ -13,12 +14,16 @@ if [[ "$KVM" != [Nn]* ]]; then
|
||||
|
||||
KVM_ERR=""
|
||||
|
||||
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
|
||||
if [ ! -e /dev/kvm ]; then
|
||||
KVM_ERR="(device file missing)"
|
||||
else
|
||||
[ -e /dev/kvm ] && KVM_ERR="(no write access)" || KVM_ERR="(device file missing)"
|
||||
if ! sh -c 'echo -n > /dev/kvm' &> /dev/null; then
|
||||
KVM_ERR="(no write access)"
|
||||
else
|
||||
if ! grep -q -e vmx -e svm /proc/cpuinfo; then
|
||||
KVM_ERR="(vmx/svm disabled)"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -n "$KVM_ERR" ]; then
|
||||
158
src/reset.sh
158
src/reset.sh
@@ -3,28 +3,38 @@ set -Eeuo pipefail
|
||||
|
||||
info () { printf "%b%s%b" "\E[1;34m❯ \E[1;36m" "$1" "\E[0m\n"; }
|
||||
error () { printf "%b%s%b" "\E[1;31m❯ " "ERROR: $1" "\E[0m\n" >&2; }
|
||||
warn () { printf "%b%s%b" "\E[1;31m❯ " "Warning: $1" "\E[0m\n" >&2; }
|
||||
|
||||
trap 'error "Status $? while: $BASH_COMMAND (line $LINENO/$BASH_LINENO)"' ERR
|
||||
|
||||
[ ! -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
|
||||
|
||||
echo "❯ Starting $APP for Docker v$(</run/version)..."
|
||||
echo "❯ For support visit $SUPPORT"
|
||||
echo
|
||||
|
||||
# Docker environment variables
|
||||
|
||||
: ${TZ:=''} # System local timezone
|
||||
: ${GPU:='N'} # Disable GPU passthrough
|
||||
: ${KVM:='Y'} # Enable KVM acceleration
|
||||
: ${DEBUG:='N'} # Disable debugging mode
|
||||
: ${COUNTRY:=''} # Country code for mirror
|
||||
: ${CONSOLE:='N'} # Disable console mode
|
||||
: ${ALLOCATE:='Y'} # Preallocate diskspace
|
||||
: ${ARGUMENTS:=''} # Extra QEMU parameters
|
||||
: ${CPU_CORES:='1'} # Amount of CPU cores
|
||||
: ${RAM_SIZE:='1G'} # Maximum RAM amount
|
||||
: ${DISK_SIZE:='16G'} # Initial data disk size
|
||||
: "${TZ:=""}" # System local timezone
|
||||
: "${DEBUG:="N"}" # Disable debugging mode
|
||||
: "${COUNTRY:=""}" # Country code for mirror
|
||||
: "${CONSOLE:="N"}" # Disable console mode
|
||||
: "${ALLOCATE:=""}" # Preallocate diskspace
|
||||
: "${ARGUMENTS:=""}" # Extra QEMU parameters
|
||||
: "${CPU_CORES:="1"}" # Amount of CPU cores
|
||||
: "${RAM_SIZE:="1G"}" # Maximum RAM amount
|
||||
: "${DISK_SIZE:="16G"}" # Initial data disk size
|
||||
|
||||
# Helper variables
|
||||
|
||||
STORAGE="/storage"
|
||||
INFO="/dev/shm/msg.html"
|
||||
PAGE="/run/shm/index.html"
|
||||
TEMPLATE="/var/www/index.html"
|
||||
FOOTER1="$APP for Docker v$(</run/version)"
|
||||
FOOTER2="<a href='$SUPPORT'>$SUPPORT</a>"
|
||||
|
||||
KERNEL=$(uname -r | cut -b 1)
|
||||
MINOR=$(uname -r | cut -d '.' -f2)
|
||||
ARCH=$(dpkg --print-architecture)
|
||||
@@ -32,25 +42,112 @@ VERS=$(qemu-system-x86_64 --version | head -n 1 | cut -d '(' -f 1)
|
||||
|
||||
# Check folder
|
||||
|
||||
STORAGE="/storage"
|
||||
[ ! -d "$STORAGE" ] && error "Storage folder ($STORAGE) not found!" && exit 13
|
||||
if [ ! -d "$STORAGE" ]; then
|
||||
error "Storage folder ($STORAGE) not found!" && exit 13
|
||||
fi
|
||||
|
||||
if [ ! -d "/run/shm" ]; then
|
||||
if [ -d "/dev/shm" ]; then
|
||||
ln -s /dev/shm /run/shm
|
||||
else
|
||||
error "Folder /dev/shm not found!" && exit 14
|
||||
fi
|
||||
fi
|
||||
|
||||
# Cleanup files
|
||||
|
||||
rm -f /run/dsm.url
|
||||
rm -f /run/qemu.pid
|
||||
rm -f /run/qemu.count
|
||||
rm -f /run/shm/qemu.*
|
||||
rm -f /run/shm/dsm.url
|
||||
|
||||
# Cleanup dirs
|
||||
|
||||
rm -rf /tmp/dsm
|
||||
rm -f /tmp/server.*
|
||||
rm -rf "$STORAGE/tmp"
|
||||
|
||||
# Helper functions
|
||||
|
||||
getCountry () {
|
||||
isAlive() {
|
||||
local pid=$1
|
||||
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
pKill() {
|
||||
local pid=$1
|
||||
|
||||
{ kill -15 "$pid" || true; } 2>/dev/null
|
||||
|
||||
while isAlive "$pid"; do
|
||||
sleep 0.2
|
||||
done
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
fWait() {
|
||||
local name=$1
|
||||
|
||||
while pgrep -f -l "$name" >/dev/null; do
|
||||
sleep 0.2
|
||||
done
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
fKill() {
|
||||
local name=$1
|
||||
|
||||
{ pkill -f "$name" || true; } 2>/dev/null
|
||||
fWait "$name"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
escape () {
|
||||
local s
|
||||
s=${1//&/\&}
|
||||
s=${s//</\<}
|
||||
s=${s//>/\>}
|
||||
s=${s//'"'/\"}
|
||||
printf -- %s "$s"
|
||||
return 0
|
||||
}
|
||||
|
||||
html()
|
||||
{
|
||||
local title
|
||||
local body
|
||||
local script
|
||||
local footer
|
||||
|
||||
title=$(escape "$APP")
|
||||
title="<title>$title</title>"
|
||||
footer=$(escape "$FOOTER1")
|
||||
|
||||
body=$(escape "$1")
|
||||
if [[ "$body" == *"..." ]]; then
|
||||
body="<p class=\"loading\">${body/.../}</p>"
|
||||
fi
|
||||
|
||||
[ -n "${2:-}" ] && script="$2" || script=""
|
||||
|
||||
local HTML
|
||||
HTML=$(<"$TEMPLATE")
|
||||
HTML="${HTML/\[1\]/$title}"
|
||||
HTML="${HTML/\[2\]/$script}"
|
||||
HTML="${HTML/\[3\]/$body}"
|
||||
HTML="${HTML/\[4\]/$footer}"
|
||||
HTML="${HTML/\[5\]/$FOOTER2}"
|
||||
|
||||
echo "$HTML" > "$PAGE"
|
||||
echo "$body" > "$INFO"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
getCountry() {
|
||||
local url=$1
|
||||
local query=$2
|
||||
local rc json result
|
||||
@@ -69,7 +166,7 @@ getCountry () {
|
||||
return 0
|
||||
}
|
||||
|
||||
setCountry () {
|
||||
setCountry() {
|
||||
|
||||
[[ "${TZ,,}" == "asia/harbin" ]] && COUNTRY="CN"
|
||||
[[ "${TZ,,}" == "asia/beijing" ]] && COUNTRY="CN"
|
||||
@@ -80,14 +177,14 @@ setCountry () {
|
||||
|
||||
[ -z "$COUNTRY" ] && getCountry "https://api.ipapi.is" ".location.country_code"
|
||||
[ -z "$COUNTRY" ] && getCountry "https://ifconfig.co/json" ".country_iso"
|
||||
[ -z "$COUNTRY" ] && getCountry "https://api.ip2location.io" ".country_code"
|
||||
[ -z "$COUNTRY" ] && getCountry "https://ipinfo.io/json" ".country"
|
||||
[ -z "$COUNTRY" ] && getCountry "https://api.myip.com" ".cc"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
addPackage () {
|
||||
|
||||
addPackage() {
|
||||
local pkg=$1
|
||||
local desc=$2
|
||||
|
||||
@@ -95,10 +192,8 @@ addPackage () {
|
||||
return 0
|
||||
fi
|
||||
|
||||
info "Installing $desc..."
|
||||
|
||||
export DEBCONF_NOWARNINGS="yes"
|
||||
export DEBIAN_FRONTEND="noninteractive"
|
||||
MSG="Installing $desc..."
|
||||
info "$MSG" && html "$MSG"
|
||||
|
||||
[ -z "$COUNTRY" ] && setCountry
|
||||
|
||||
@@ -106,10 +201,15 @@ addPackage () {
|
||||
sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list.d/debian.sources
|
||||
fi
|
||||
|
||||
apt-get -qq update
|
||||
apt-get -qq --no-install-recommends -y install "$pkg" > /dev/null
|
||||
DEBIAN_FRONTEND=noninteractive apt-get -qq update
|
||||
DEBIAN_FRONTEND=noninteractive apt-get -qq --no-install-recommends -y install "$pkg" > /dev/null
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Start webserver
|
||||
cp -r /var/www/* /run/shm
|
||||
html "Starting $APP for Docker..."
|
||||
nginx -e stderr
|
||||
|
||||
return 0
|
||||
|
||||
@@ -3,11 +3,11 @@ set -Eeuo pipefail
|
||||
|
||||
# Docker environment variables
|
||||
|
||||
: ${HOST_MAC:=''}
|
||||
: ${HOST_DEBUG:=''}
|
||||
: ${HOST_SERIAL:=''}
|
||||
: ${HOST_MODEL:=''}
|
||||
: ${GUEST_SERIAL:=''}
|
||||
: "${HOST_MAC:=""}"
|
||||
: "${HOST_DEBUG:=""}"
|
||||
: "${HOST_SERIAL:=""}"
|
||||
: "${HOST_MODEL:=""}"
|
||||
: "${GUEST_SERIAL:=""}"
|
||||
|
||||
HOST_ARGS=()
|
||||
HOST_ARGS+=("-cpu=$CPU_CORES")
|
||||
@@ -46,11 +46,14 @@ done
|
||||
|
||||
# Configure serial ports
|
||||
|
||||
SERIAL_OPTS="\
|
||||
-serial mon:stdio \
|
||||
if [[ "$CONSOLE" != [Yy]* ]]; then
|
||||
SERIAL_OPTS="-serial pty"
|
||||
else
|
||||
SERIAL_OPTS="-serial mon:stdio"
|
||||
fi
|
||||
|
||||
SERIAL_OPTS="$SERIAL_OPTS \
|
||||
-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"
|
||||
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eu
|
||||
|
||||
TMP_FILE=$(mktemp -q /tmp/server.XXXXXX)
|
||||
|
||||
stop() {
|
||||
trap - SIGINT EXIT
|
||||
{ pkill -f socat || true; } 2>/dev/null
|
||||
[ -f "$TMP_FILE" ] && rm -f "$TMP_FILE"
|
||||
}
|
||||
|
||||
trap 'stop' EXIT SIGINT SIGTERM SIGHUP
|
||||
|
||||
html()
|
||||
{
|
||||
local h="<!DOCTYPE html><HTML><HEAD><TITLE>VirtualDSM</TITLE>"
|
||||
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<BODY><BR><BR><H1><CENTER>$1</CENTER></H1></BODY></HTML>"
|
||||
|
||||
echo "$h"
|
||||
}
|
||||
|
||||
if [[ "$2" != "/"* ]]; then
|
||||
|
||||
BODY="$2"
|
||||
|
||||
if [[ "$BODY" == "install" ]]; then
|
||||
BODY="Please wait while Virtual DSM is being installed..."
|
||||
BODY="$BODY<script>setTimeout(() => { document.location.reload(); }, 9999);</script>"
|
||||
fi
|
||||
|
||||
HTML=$(html "$BODY")
|
||||
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:"${1:-5000}",reuseaddr,fork,crlf SYSTEM:"cat $TMP_FILE" 2> /dev/null & wait $!
|
||||
|
||||
exit
|
||||
|
||||
fi
|
||||
|
||||
if [[ "$2" != "/run/ip.sh" ]]; then
|
||||
|
||||
cp "$2" "$TMP_FILE"
|
||||
|
||||
else
|
||||
|
||||
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>"
|
||||
WAIT="Please wait while discovering IP...<script>setTimeout(() => { document.location.reload(); }, 4999);</script>"
|
||||
|
||||
HTML=$(html "xxx")
|
||||
|
||||
{ echo "#!/bin/bash"
|
||||
echo "[ -f \"/run/dsm.url\" ] && LOCATION=\$(cat \"/run/dsm.url\")"
|
||||
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\""
|
||||
} > "$TMP_FILE"
|
||||
|
||||
fi
|
||||
|
||||
chmod +x "$TMP_FILE"
|
||||
|
||||
socat TCP4-LISTEN:80,reuseaddr,fork,crlf SYSTEM:"$TMP_FILE" 2> /dev/null &
|
||||
socat TCP4-LISTEN:"${1:-5000}",reuseaddr,fork,crlf SYSTEM:"$TMP_FILE" 2> /dev/null & wait $!
|
||||
154
web/css/style.css
Normal file
154
web/css/style.css
Normal file
@@ -0,0 +1,154 @@
|
||||
body {
|
||||
color: white;
|
||||
background-color: #125bdb;
|
||||
font-family: Verdana, Arial, sans-serif;
|
||||
}
|
||||
|
||||
#content {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
footer {
|
||||
width: 98%;
|
||||
position: fixed;
|
||||
bottom: 0px;
|
||||
height: 40px;
|
||||
text-align: center;
|
||||
color: #0c8aeb;
|
||||
}
|
||||
|
||||
#empty {
|
||||
height: 40px;
|
||||
/* Same height as footer */
|
||||
}
|
||||
|
||||
a,
|
||||
a:hover,
|
||||
a:active,
|
||||
a:visited {
|
||||
color: white;
|
||||
}
|
||||
|
||||
footer a:link,
|
||||
footer a:visited,
|
||||
footer a:active { color: #0c8aeb; }
|
||||
footer a:hover { color: #73e6ff; }
|
||||
|
||||
.loading:after {
|
||||
content: " .";
|
||||
animation: dots 1s steps(5, end) infinite;
|
||||
}
|
||||
|
||||
@keyframes dots {
|
||||
|
||||
0%,
|
||||
20% {
|
||||
color: rgba(0, 0, 0, 0);
|
||||
text-shadow: 0.25em 0 0 rgba(0, 0, 0, 0), 0.5em 0 0 rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
40% {
|
||||
color: white;
|
||||
text-shadow: 0.25em 0 0 rgba(0, 0, 0, 0), 0.5em 0 0 rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
60% {
|
||||
text-shadow: 0.25em 0 0 white, 0.5em 0 0 rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
80%,
|
||||
100% {
|
||||
text-shadow: 0.25em 0 0 white, 0.5em 0 0 white;
|
||||
}
|
||||
}
|
||||
|
||||
.spinner_LWk7 {
|
||||
animation: spinner_GWy6 1.2s linear infinite, spinner_BNNO 1.2s linear infinite
|
||||
}
|
||||
|
||||
.spinner_yOMU {
|
||||
animation: spinner_GWy6 1.2s linear infinite, spinner_pVqn 1.2s linear infinite;
|
||||
animation-delay: .15s
|
||||
}
|
||||
|
||||
.spinner_KS4S {
|
||||
animation: spinner_GWy6 1.2s linear infinite, spinner_6uKB 1.2s linear infinite;
|
||||
animation-delay: .3s
|
||||
}
|
||||
|
||||
.spinner_zVee {
|
||||
animation: spinner_GWy6 1.2s linear infinite, spinner_Qw4x 1.2s linear infinite;
|
||||
animation-delay: .45s
|
||||
}
|
||||
|
||||
@keyframes spinner_GWy6 {
|
||||
|
||||
0%,
|
||||
50% {
|
||||
width: 9px;
|
||||
height: 9px
|
||||
}
|
||||
|
||||
10% {
|
||||
width: 11px;
|
||||
height: 11px
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spinner_BNNO {
|
||||
|
||||
0%,
|
||||
50% {
|
||||
x: 1.5px;
|
||||
y: 1.5px
|
||||
}
|
||||
|
||||
10% {
|
||||
x: .5px;
|
||||
y: .5px
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spinner_pVqn {
|
||||
|
||||
0%,
|
||||
50% {
|
||||
x: 13.5px;
|
||||
y: 1.5px
|
||||
}
|
||||
|
||||
10% {
|
||||
x: 12.5px;
|
||||
y: .5px
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spinner_6uKB {
|
||||
|
||||
0%,
|
||||
50% {
|
||||
x: 13.5px;
|
||||
y: 13.5px
|
||||
}
|
||||
|
||||
10% {
|
||||
x: 12.5px;
|
||||
y: 12.5px
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spinner_Qw4x {
|
||||
|
||||
0%,
|
||||
50% {
|
||||
x: 1.5px;
|
||||
y: 13.5px
|
||||
}
|
||||
|
||||
10% {
|
||||
x: .5px;
|
||||
y: 12.5px
|
||||
}
|
||||
}
|
||||
1
web/img/favicon.svg
Normal file
1
web/img/favicon.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg id="Capa_1" enable-background="new 0 0 511.962 511.962" height="512" viewBox="0 0 511.962 511.962" width="512" xmlns="http://www.w3.org/2000/svg"><g><path d="m489.965 120.063c0-5.77-3.31-11.028-8.512-13.524l-218.984-105.063c-4.102-1.967-8.875-1.967-12.977 0l-218.985 105.063c-5.202 2.496-8.511 7.755-8.511 13.524l-.003 271.834c0 5.77 3.31 11.028 8.512 13.524l218.989 105.064c2.051.983 4.27 1.476 6.488 1.476 2.219 0 4.438-.492 6.488-1.476l218.989-105.064c5.202-2.496 8.512-7.755 8.512-13.524z" fill="#4e6ba6"/><path d="m489.965 120.063c0-5.77-3.31-11.028-8.512-13.524l-218.984-105.063c-2.051-.984-4.269-1.476-6.488-1.476v511.962c2.219 0 4.438-.492 6.488-1.476l218.989-105.064c5.202-2.496 8.512-7.755 8.512-13.524z" fill="#28487a"/><path d="m425.812 160.441c0-2.27-.519-4.457-1.457-6.432l-336.701-.095c-.967 1.999-1.504 4.22-1.504 6.526l-.002 191.081c0 5.769 3.31 11.028 8.512 13.524l154.833 74.285c2.051.983 4.27 1.476 6.488 1.476 2.219 0 4.438-.492 6.488-1.476l154.834-74.285c5.202-2.496 8.512-7.755 8.512-13.524z" fill="#8dc2eb"/><path d="m424.354 154.009h-168.373v286.798c2.219 0 4.438-.492 6.488-1.476l154.834-74.285c5.202-2.496 8.512-7.755 8.512-13.524l-.003-191.081c0-2.27-.52-4.458-1.458-6.432z" fill="#5e9ff6"/><path d="m417.3 146.916-154.831-74.284c-4.102-1.967-8.875-1.967-12.977 0l-154.831 74.284c-3.122 1.498-5.555 3.996-7.007 6.998l168.328 80.812 168.374-80.717c-1.448-3.044-3.9-5.579-7.056-7.093z" fill="#ecf9fd"/><path d="m417.3 146.916-154.831-74.284c-2.051-.983-4.27-1.476-6.488-1.476v163.569l168.374-80.717c-1.447-3.043-3.899-5.578-7.055-7.092z" fill="#d9f3fc"/></g></svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
34
web/index.html
Normal file
34
web/index.html
Normal file
@@ -0,0 +1,34 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
[1]
|
||||
<meta http-equiv="Cache-Control" content="no-cache" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="stylesheet" type="text/css" href="/css/style.css" />
|
||||
<link rel="icon" href="/img/favicon.svg" type="image/x-icon">
|
||||
[2]
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="page">
|
||||
<div id="content">
|
||||
<svg id="spinner" width="64" height="64" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect class="spinner_LWk7" fill="#0c8aeb" x="1.5" y="1.5" rx="1" width="9" height="9"/>
|
||||
<rect class="spinner_yOMU" fill="#0c8aeb" x="13.5" y="1.5" rx="1" width="9" height="9"/>
|
||||
<rect class="spinner_KS4S" fill="#0c8aeb" x="13.5" y="13.5" rx="1" width="9" height="9"/>
|
||||
<rect class="spinner_zVee" fill="#0c8aeb" x="1.5" y="13.5" rx="1" width="9" height="9"/>
|
||||
</svg>
|
||||
<h1 id="info">[3]</h1>
|
||||
</div>
|
||||
<div id="empty">
|
||||
</div>
|
||||
<footer id="footer">
|
||||
[4]<br />
|
||||
[5]
|
||||
</footer>
|
||||
</div>
|
||||
<script type="text/javascript" src="/js/script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
130
web/js/script.js
Normal file
130
web/js/script.js
Normal file
@@ -0,0 +1,130 @@
|
||||
var request;
|
||||
var interval = 1000;
|
||||
|
||||
function getInfo() {
|
||||
|
||||
var url = "/msg.html";
|
||||
|
||||
try {
|
||||
|
||||
if (window.XMLHttpRequest) {
|
||||
request = new XMLHttpRequest();
|
||||
} else {
|
||||
throw "XMLHttpRequest not available!";
|
||||
}
|
||||
|
||||
request.onreadystatechange = processInfo;
|
||||
request.open("GET", url, true);
|
||||
request.send();
|
||||
|
||||
} catch (e) {
|
||||
var err = "Error: " + e.message;
|
||||
console.log(err);
|
||||
setError(err);
|
||||
}
|
||||
}
|
||||
|
||||
function processInfo() {
|
||||
try {
|
||||
if (request.readyState != 4) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var msg = request.responseText;
|
||||
if (msg == null || msg.length == 0) {
|
||||
setInfo("Booting DSM instance", true);
|
||||
schedule();
|
||||
return false;
|
||||
}
|
||||
|
||||
var notFound = (request.status == 404);
|
||||
|
||||
if (request.status == 200) {
|
||||
if (msg.toLowerCase().indexOf("<html>") !== -1) {
|
||||
notFound = true;
|
||||
} else {
|
||||
if (msg.toLowerCase().indexOf("href=") !== -1) {
|
||||
var div = document.createElement("div");
|
||||
div.innerHTML = msg;
|
||||
var url = div.querySelector("a").href;
|
||||
setTimeout(() => {
|
||||
window.location.assign(url);
|
||||
}, 3000);
|
||||
setInfo(msg);
|
||||
return true;
|
||||
} else {
|
||||
setInfo(msg);
|
||||
schedule();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (notFound) {
|
||||
setInfo("Connecting to web portal", true);
|
||||
reload();
|
||||
return true;
|
||||
}
|
||||
|
||||
setError("Error: Received statuscode " + request.status);
|
||||
schedule();
|
||||
return false;
|
||||
|
||||
} catch (e) {
|
||||
var err = "Error: " + e.message;
|
||||
console.log(err);
|
||||
setError(err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function setInfo(msg, loading, error) {
|
||||
|
||||
try {
|
||||
if (msg == null || msg.length == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var el = document.getElementById("spinner");
|
||||
|
||||
error = !!error;
|
||||
if (!error) {
|
||||
el.style.visibility = 'visible';
|
||||
} else {
|
||||
el.style.visibility = 'hidden';
|
||||
}
|
||||
|
||||
loading = !!loading;
|
||||
if (loading) {
|
||||
msg = "<p class=\"loading\">" + msg + "</p>";
|
||||
}
|
||||
|
||||
el = document.getElementById("info");
|
||||
|
||||
if (el.innerHTML != msg) {
|
||||
el.innerHTML = msg;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
} catch (e) {
|
||||
console.log("Error: " + e.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function setError(text) {
|
||||
return setInfo(text, false, true);
|
||||
}
|
||||
|
||||
function schedule() {
|
||||
setTimeout(getInfo, interval);
|
||||
}
|
||||
|
||||
function reload() {
|
||||
setTimeout(() => {
|
||||
document.location.reload();
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
schedule();
|
||||
33
web/nginx.conf
Normal file
33
web/nginx.conf
Normal file
@@ -0,0 +1,33 @@
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
listen 5000 default_server;
|
||||
listen [::]:5000 default_server;
|
||||
|
||||
autoindex on;
|
||||
tcp_nodelay on;
|
||||
server_tokens off;
|
||||
absolute_redirect off;
|
||||
|
||||
error_log /dev/null;
|
||||
access_log /dev/null;
|
||||
|
||||
include /etc/nginx/mime.types;
|
||||
|
||||
gzip on;
|
||||
gzip_vary on;
|
||||
gzip_proxied any;
|
||||
gzip_comp_level 5;
|
||||
gzip_min_length 500;
|
||||
gzip_disable "msie6";
|
||||
gzip_types text/css text/javascript text/xml text/plain text/x-component application/javascript application/json application/xml application/rss+xml font/truetype font/opentype application/vnd.ms-fontobject image/svg+xml;
|
||||
|
||||
add_header Cache-Control "no-cache";
|
||||
|
||||
location / {
|
||||
|
||||
root /run/shm;
|
||||
index index.html;
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user