mirror of
https://github.com/vdsm/virtual-dsm.git
synced 2025-11-06 18:13:43 +08:00
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
78594098cc | ||
|
|
3c31bc91e4 | ||
|
|
72141bab7a | ||
|
|
bc52463aa4 | ||
|
|
9fa68908a9 | ||
|
|
740dbec1b1 | ||
|
|
440d203730 | ||
|
|
1a83c67e2c | ||
|
|
34a707a2a5 | ||
|
|
cabb2cdfc9 | ||
|
|
dc52ccf172 | ||
|
|
bdd7fec3c3 | ||
|
|
bd8b03d089 | ||
|
|
a10588b0ce | ||
|
|
3503b86e12 | ||
|
|
9e124980cd | ||
|
|
675aa5e122 | ||
|
|
2a62d4e938 | ||
|
|
9cbb51cc86 | ||
|
|
7790f81d15 | ||
|
|
fdbff4879b | ||
|
|
739e679a66 | ||
|
|
0dea507d85 | ||
|
|
4f524c47d8 | ||
|
|
0c74201eb4 | ||
|
|
1e13258bc9 |
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
@@ -8,6 +8,10 @@ on:
|
||||
paths-ignore:
|
||||
- '**/*.md'
|
||||
- '**/*.yml'
|
||||
- '**/*.js'
|
||||
- '**/*.css'
|
||||
- '**/*.html'
|
||||
- 'web/**'
|
||||
- '.gitignore'
|
||||
- '.dockerignore'
|
||||
- '.github/**'
|
||||
|
||||
14
.github/workflows/check.yml
vendored
14
.github/workflows/check.yml
vendored
@@ -7,8 +7,18 @@ jobs:
|
||||
name: shellcheck
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Run ShellCheck
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Run ShellCheck
|
||||
uses: ludeeus/action-shellcheck@master
|
||||
env:
|
||||
SHELLCHECK_OPTS: -x --source-path=src -e SC2001 -e SC2034 -e SC2064 -e SC2317 -e SC2153 -e SC2028
|
||||
-
|
||||
name: Lint Dockerfile
|
||||
uses: hadolint/hadolint-action@v3.1.0
|
||||
with:
|
||||
dockerfile: Dockerfile
|
||||
ignore: DL3008,DL3003,DL3006
|
||||
failure-threshold: warning
|
||||
|
||||
2
.github/workflows/hub.yml
vendored
2
.github/workflows/hub.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
-
|
||||
name: Docker Hub Description
|
||||
uses: peter-evans/dockerhub-description@v3
|
||||
uses: peter-evans/dockerhub-description@v4
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
1
.github/workflows/test.yml
vendored
1
.github/workflows/test.yml
vendored
@@ -3,6 +3,7 @@ on:
|
||||
pull_request:
|
||||
paths:
|
||||
- '**/*.sh'
|
||||
- 'Dockerfile'
|
||||
- '.github/workflows/test.yml'
|
||||
- '.github/workflows/check.yml'
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ Virtual DSM in a docker container.
|
||||
|
||||
- Multiple disks
|
||||
- KVM acceleration
|
||||
- GPU pass-through
|
||||
- Upgrades supported
|
||||
|
||||
## Usage
|
||||
@@ -47,7 +46,7 @@ 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
|
||||
docker run -it --rm -p 5000:5000 --device=/dev/kvm --cap-add NET_ADMIN --stop-timeout 120 vdsm/virtual-dsm
|
||||
```
|
||||
|
||||
## FAQ
|
||||
@@ -56,13 +55,11 @@ docker run -it --rm -p 5000:5000 --device=/dev/kvm --cap-add NET_ADMIN --stop-ti
|
||||
|
||||
Very simple! These are the steps:
|
||||
|
||||
- Start the container and get some coffee.
|
||||
|
||||
- Connect to port 5000 of the container in your web browser.
|
||||
- Start the container and connect to [port 5000](http://localhost:5000) using 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!
|
||||
Enjoy your brand new machine, and don't forget to star this repo!
|
||||
|
||||
* ### How do I change the size of the disk?
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ set -Eeuo pipefail
|
||||
|
||||
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}"
|
||||
CPU_OPTS="-cpu $CPU_FLAGS -smp $CPU_CORES,sockets=1,dies=1,cores=$CPU_CORES,threads=1"
|
||||
MAC_OPTS="-machine type=q35,usb=off,vmport=off,dump-guest-core=off,hpet=off${KVM_OPTS}"
|
||||
DEV_OPTS="-device virtio-balloon-pci,id=balloon0,bus=pcie.0,addr=0x4"
|
||||
DEV_OPTS="$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"
|
||||
|
||||
14
src/disk.sh
14
src/disk.sh
@@ -80,7 +80,7 @@ getSize() {
|
||||
isCow() {
|
||||
local FS=$1
|
||||
|
||||
if [[ "${FS,,}" == "xfs" || "${FS,,}" == "zfs" || "${FS,,}" == "btrfs" || "${FS,,}" == "bcachefs" ]]; then
|
||||
if [[ "${FS,,}" == "xfs" || "${FS,,}" == "btrfs" || "${FS,,}" == "bcachefs" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
@@ -112,8 +112,8 @@ createDisk() {
|
||||
fi
|
||||
fi
|
||||
|
||||
MSG="Creating a $DISK_TYPE $DISK_DESC image in $DISK_FMT format with a size of $DISK_SPACE..."
|
||||
info "$MSG" && html "$MSG"
|
||||
html "Creating a $DISK_DESC image..."
|
||||
info "Creating a $DISK_SPACE $DISK_TYPE $DISK_DESC image in $DISK_FMT format..."
|
||||
|
||||
local FAIL="Could not create a $DISK_TYPE $DISK_FMT $DISK_DESC image of $DISK_SPACE ($DISK_FILE)"
|
||||
|
||||
@@ -266,8 +266,8 @@ convertDisk() {
|
||||
fi
|
||||
fi
|
||||
|
||||
MSG="Converting $DISK_DESC to $DST_FMT, please wait until completed..."
|
||||
info "$MSG" && html "$MSG"
|
||||
html "Converting $DISK_DESC to $DST_FMT..."
|
||||
info "Converting $DISK_DESC to $DST_FMT, please wait until completed..."
|
||||
|
||||
local CONV_FLAGS="-p"
|
||||
local DISK_PARAM="$DISK_ALLOC"
|
||||
@@ -306,8 +306,8 @@ convertDisk() {
|
||||
fi
|
||||
fi
|
||||
|
||||
MSG="Conversion of $DISK_DESC to $DST_FMT completed succesfully!"
|
||||
info "$MSG" && html "$MSG"
|
||||
html "Conversion of $DISK_DESC completed..."
|
||||
info "Conversion of $DISK_DESC to $DST_FMT completed succesfully!"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -15,7 +15,8 @@ if [[ "$GPU" != [Yy1]* ]] || [[ "$ARCH" != "amd64" ]]; then
|
||||
|
||||
fi
|
||||
|
||||
DISPLAY_OPTS="-display egl-headless,rendernode=/dev/dri/renderD128 -vga $VGA"
|
||||
DISPLAY_OPTS="-display egl-headless,rendernode=/dev/dri/renderD128"
|
||||
DISPLAY_OPTS="$DISPLAY_OPTS -vga $VGA"
|
||||
|
||||
[ ! -d /dev/dri ] && mkdir -m 755 /dev/dri
|
||||
|
||||
|
||||
@@ -18,11 +18,13 @@ cd /run
|
||||
|
||||
trap - ERR
|
||||
|
||||
info "Booting $APP using $VERS..."
|
||||
[[ "$DEBUG" == [Yy1]* ]] && echo "Arguments: $ARGS" && echo
|
||||
|
||||
if [[ "$CONSOLE" == [Yy]* ]]; then
|
||||
exec qemu-system-x86_64 ${ARGS:+ $ARGS}
|
||||
fi
|
||||
|
||||
[[ "$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
|
||||
|
||||
|
||||
@@ -105,6 +105,7 @@ RDC="$STORAGE/dsm.rd"
|
||||
if [ ! -f "$RDC" ]; then
|
||||
|
||||
MSG="Downloading installer..."
|
||||
PRG="Downloading installer ([P])..."
|
||||
info "Install: $MSG" && html "$MSG"
|
||||
|
||||
RD="$TMP/rd.gz"
|
||||
@@ -112,7 +113,11 @@ if [ ! -f "$RDC" ]; then
|
||||
VERIFY="b4215a4b213ff5154db0488f92c87864"
|
||||
LOC="$DL/release/7.0.1/42218/DSM_VirtualDSM_42218.pat"
|
||||
|
||||
rm -f "$RD"
|
||||
/run/progress.sh "$RD" "$PRG" &
|
||||
{ curl -r "$POS" -sfk -S -o "$RD" "$LOC"; rc=$?; } || :
|
||||
|
||||
fKill "progress.sh"
|
||||
(( rc != 0 )) && error "Failed to download $LOC, reason: $rc" && exit 60
|
||||
|
||||
SUM=$(md5sum "$RD" | cut -f 1 -d " ")
|
||||
@@ -123,7 +128,11 @@ if [ ! -f "$RDC" ]; then
|
||||
rm "$RD"
|
||||
rm -f "$PAT"
|
||||
|
||||
html "$MSG"
|
||||
/run/progress.sh "$PAT" "$PRG" &
|
||||
{ wget "$LOC" -O "$PAT" -q --no-check-certificate --show-progress "$PROGRESS"; rc=$?; } || :
|
||||
|
||||
fKill "progress.sh"
|
||||
(( rc != 0 )) && error "Failed to download $LOC , reason: $rc" && exit 60
|
||||
|
||||
tar --extract --file="$PAT" --directory="$(dirname "$RD")"/. "$(basename "$RD")"
|
||||
@@ -174,8 +183,11 @@ fi
|
||||
|
||||
rm -rf "$TMP" && mkdir -p "$TMP"
|
||||
|
||||
MSG="Downloading $BASE.pat..."
|
||||
info "Install: $MSG" && html "$MSG"
|
||||
info "Install: Downloading $BASE.pat..."
|
||||
|
||||
MSG="Downloading DSM..."
|
||||
PRG="Downloading DSM ([P])..."
|
||||
html "$MSG"
|
||||
|
||||
PAT="/$BASE.pat"
|
||||
rm -f "$PAT"
|
||||
@@ -186,7 +198,11 @@ if [[ "$URL" == "file://"* ]]; then
|
||||
|
||||
else
|
||||
|
||||
/run/progress.sh "$PAT" "$PRG" &
|
||||
|
||||
{ wget "$URL" -O "$PAT" -q --no-check-certificate --show-progress "$PROGRESS"; rc=$?; } || :
|
||||
|
||||
fKill "progress.sh"
|
||||
(( rc != 0 )) && error "Failed to download $URL , reason: $rc" && exit 69
|
||||
|
||||
fi
|
||||
@@ -249,7 +265,7 @@ 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
|
||||
if [[ "${FS,,}" == "xfs" || "${FS,,}" == "btrfs" || "${FS,,}" == "bcachefs" ]]; then
|
||||
{ chattr +C "$SYSTEM"; } || :
|
||||
FA=$(lsattr "$SYSTEM")
|
||||
if [[ "$FA" != *"C"* ]]; then
|
||||
|
||||
@@ -3,8 +3,8 @@ set -Eeuo pipefail
|
||||
|
||||
# Docker environment variables
|
||||
|
||||
: "${MAC:=""}"
|
||||
: "${DHCP:="N"}"
|
||||
: "${MAC:="02:11:32:AA:BB:CC"}"
|
||||
|
||||
: "${VM_NET_DEV:=""}"
|
||||
: "${VM_NET_TAP:="dsm"}"
|
||||
@@ -33,7 +33,7 @@ configureDHCP() {
|
||||
fi
|
||||
|
||||
while ! ip link set "$VM_NET_TAP" up; do
|
||||
info "Waiting for address to become available..."
|
||||
info "Waiting for MAC address $VM_NET_MAC to become available..."
|
||||
sleep 2
|
||||
done
|
||||
|
||||
@@ -83,7 +83,9 @@ configureDNS() {
|
||||
DNSMASQ_OPTS=$(echo "$DNSMASQ_OPTS" | sed 's/\t/ /g' | tr -s ' ' | sed 's/^ *//')
|
||||
|
||||
[[ "$DEBUG" == [Yy1]* ]] && set -x
|
||||
$DNSMASQ ${DNSMASQ_OPTS:+ $DNSMASQ_OPTS}
|
||||
if ! $DNSMASQ ${DNSMASQ_OPTS:+ $DNSMASQ_OPTS}; then
|
||||
error "Failed to start dnsmasq, reason: $?" && exit 29
|
||||
fi
|
||||
{ set +x; } 2>/dev/null
|
||||
[[ "$DEBUG" == [Yy1]* ]] && echo
|
||||
|
||||
@@ -115,7 +117,6 @@ configureNAT() {
|
||||
# Create a bridge with a static IP for the VM guest
|
||||
|
||||
VM_NET_IP='20.20.20.21'
|
||||
[[ "$DEBUG" == [Yy1]* ]] && set -x
|
||||
|
||||
{ ip link add dev dockerbridge type bridge ; rc=$?; } || :
|
||||
|
||||
@@ -126,7 +127,7 @@ configureNAT() {
|
||||
ip address add ${VM_NET_IP%.*}.1/24 broadcast ${VM_NET_IP%.*}.255 dev dockerbridge
|
||||
|
||||
while ! ip link set dockerbridge up; do
|
||||
info "Waiting for address to become available..."
|
||||
info "Waiting for IP address to become available..."
|
||||
sleep 2
|
||||
done
|
||||
|
||||
@@ -134,7 +135,7 @@ configureNAT() {
|
||||
ip tuntap add dev "$VM_NET_TAP" mode tap
|
||||
|
||||
while ! ip link set "$VM_NET_TAP" up promisc on; do
|
||||
info "Waiting for tap to become available..."
|
||||
info "Waiting for TAP to become available..."
|
||||
sleep 2
|
||||
done
|
||||
|
||||
@@ -153,9 +154,6 @@ configureNAT() {
|
||||
iptables -A POSTROUTING -t mangle -p udp --dport bootpc -j CHECKSUM --checksum-fill || true
|
||||
fi
|
||||
|
||||
{ set +x; } 2>/dev/null
|
||||
[[ "$DEBUG" == [Yy1]* ]] && echo
|
||||
|
||||
NET_OPTS="-netdev tap,ifname=$VM_NET_TAP,script=no,downscript=no,id=hostnet0"
|
||||
|
||||
{ exec 40>>/dev/vhost-net; rc=$?; } 2>/dev/null || :
|
||||
@@ -209,14 +207,20 @@ getInfo() {
|
||||
error "$ADD_ERR -e \"VM_NET_DEV=NAME\" to specify another interface name." && exit 27
|
||||
fi
|
||||
|
||||
VM_NET_MAC="${VM_NET_MAC//-/:}"
|
||||
if [ -z "$VM_NET_MAC" ]; then
|
||||
# Generate MAC address based on Docker container ID in hostname
|
||||
VM_NET_MAC=$(echo "$HOST" | md5sum | sed 's/^\(..\)\(..\)\(..\)\(..\)\(..\).*$/02:11:32:\3:\4:\5/')
|
||||
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
|
||||
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}')
|
||||
@@ -240,22 +244,22 @@ getInfo
|
||||
html "Initializing network..."
|
||||
|
||||
if [[ "$DEBUG" == [Yy1]* ]]; then
|
||||
info "Container IP is $IP with gateway $GATEWAY on interface $VM_NET_DEV" && echo
|
||||
info "Host: $HOST IP: $IP Gateway: $GATEWAY Interface: $VM_NET_DEV MAC: $VM_NET_MAC"
|
||||
[ -f /etc/resolv.conf ] && grep '^nameserver*' /etc/resolv.conf
|
||||
echo
|
||||
fi
|
||||
|
||||
if [[ "$DHCP" == [Yy1]* ]]; then
|
||||
|
||||
if [[ "$GATEWAY" == "172."* ]]; then
|
||||
if [[ "$DEBUG" != [Yy1]* ]]; then
|
||||
if [[ "$GATEWAY" == "172."* ]] && [[ "$DEBUG" != [Yy1]* ]]; then
|
||||
error "You can only enable DHCP while the container is on a macvlan network!" && exit 26
|
||||
fi
|
||||
fi
|
||||
|
||||
# Configuration for DHCP IP
|
||||
configureDHCP
|
||||
|
||||
MSG="Please wait while discovering IP..."
|
||||
html "$MSG" "2000"
|
||||
MSG="Booting DSM instance..."
|
||||
html "$MSG"
|
||||
|
||||
else
|
||||
|
||||
|
||||
@@ -98,7 +98,6 @@ terminal() {
|
||||
|
||||
_graceful_shutdown() {
|
||||
|
||||
local cnt=0
|
||||
local code=$?
|
||||
local pid url response
|
||||
|
||||
@@ -144,6 +143,8 @@ _graceful_shutdown() {
|
||||
|
||||
fi
|
||||
|
||||
local cnt=0
|
||||
|
||||
while [ "$cnt" -lt "$QEMU_TIMEOUT" ]; do
|
||||
|
||||
! isAlive "$pid" && break
|
||||
|
||||
@@ -7,6 +7,7 @@ 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/shm/dsm.url"
|
||||
info="/run/shm/msg.html"
|
||||
page="/run/shm/index.html"
|
||||
address="/run/shm/qemu.ip"
|
||||
shutdown="/run/shm/qemu.end"
|
||||
@@ -80,6 +81,7 @@ if [[ "$location" != "20.20"* ]]; then
|
||||
HTML="${HTML/\[5\]/}"
|
||||
|
||||
echo "$HTML" > "$page"
|
||||
echo "$body" > "$info"
|
||||
|
||||
else
|
||||
|
||||
|
||||
17
src/proc.sh
17
src/proc.sh
@@ -5,8 +5,8 @@ set -Eeuo pipefail
|
||||
|
||||
: "${KVM:="Y"}"
|
||||
: "${HOST_CPU:=""}"
|
||||
: "${CPU_FLAGS:=""}"
|
||||
: "${CPU_MODEL:="host"}"
|
||||
: "${CPU_FEATURES:="+ssse3,+sse4.1,+sse4.2"}"
|
||||
|
||||
[ "$ARCH" != "amd64" ] && KVM="N"
|
||||
|
||||
@@ -37,6 +37,7 @@ fi
|
||||
|
||||
if [[ "$KVM" != [Nn]* ]]; then
|
||||
|
||||
CPU_FEATURES="kvm=on"
|
||||
KVM_OPTS=",accel=kvm -enable-kvm"
|
||||
|
||||
if ! grep -qE '^flags.* (sse4_2)' /proc/cpuinfo; then
|
||||
@@ -48,15 +49,23 @@ if [[ "$KVM" != [Nn]* ]]; then
|
||||
else
|
||||
|
||||
KVM_OPTS=""
|
||||
CPU_FEATURES="+ssse3,+sse4.1,+sse4.2"
|
||||
|
||||
if [[ "${CPU_MODEL,,}" == "host"* ]]; then
|
||||
|
||||
if [[ "$CPU_MODEL" == "host"* ]]; then
|
||||
if [[ "$ARCH" == "amd64" ]]; then
|
||||
CPU_MODEL="max,$CPU_FEATURES"
|
||||
CPU_MODEL="max"
|
||||
else
|
||||
CPU_MODEL="qemu64,$CPU_FEATURES"
|
||||
CPU_MODEL="qemu64"
|
||||
fi
|
||||
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$CPU_FLAGS" ]; then
|
||||
CPU_FLAGS="$CPU_MODEL,$CPU_FEATURES"
|
||||
else
|
||||
CPU_FLAGS="$CPU_MODEL,$CPU_FEATURES,$CPU_FLAGS"
|
||||
fi
|
||||
|
||||
if [ -z "$HOST_CPU" ]; then
|
||||
|
||||
32
src/progress.sh
Normal file
32
src/progress.sh
Normal file
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
escape () {
|
||||
local s
|
||||
s=${1//&/\&}
|
||||
s=${s//</\<}
|
||||
s=${s//>/\>}
|
||||
s=${s//'"'/\"}
|
||||
printf -- %s "$s"
|
||||
return 0
|
||||
}
|
||||
|
||||
file="$1"
|
||||
body=$(escape "$2")
|
||||
info="/run/shm/msg.html"
|
||||
|
||||
if [[ "$body" == *"..." ]]; then
|
||||
body="<p class=\"loading\">${body/.../}</p>"
|
||||
fi
|
||||
|
||||
while true
|
||||
do
|
||||
if [ -f "$file" ]; then
|
||||
bytes=$(du -sb "$file" | cut -f1)
|
||||
if (( bytes > 1000 )); then
|
||||
size=$(echo "$bytes" | numfmt --to=iec --suffix=B | sed -r 's/([A-Z])/ \1/')
|
||||
echo "${body//(\[P\])/($size)}"> "$info"
|
||||
fi
|
||||
fi
|
||||
sleep 1 & wait $!
|
||||
done
|
||||
25
src/reset.sh
25
src/reset.sh
@@ -29,30 +29,32 @@ echo
|
||||
# Helper variables
|
||||
|
||||
STORAGE="/storage"
|
||||
INFO="/run/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>"
|
||||
|
||||
HOST=$(hostname -s)
|
||||
KERNEL=$(uname -r | cut -b 1)
|
||||
MINOR=$(uname -r | cut -d '.' -f2)
|
||||
ARCH=$(dpkg --print-architecture)
|
||||
VERS=$(qemu-system-x86_64 --version | head -n 1 | cut -d '(' -f 1)
|
||||
|
||||
# Check system
|
||||
|
||||
if [ ! -d "/dev/shm" ]; then
|
||||
error "Directory /dev/shm not found!" && exit 14
|
||||
else
|
||||
[ ! -d "/run/shm" ] && ln -s /dev/shm /run/shm
|
||||
fi
|
||||
|
||||
# Check folder
|
||||
|
||||
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/shm/qemu.*
|
||||
rm -f /run/shm/dsm.url
|
||||
@@ -118,6 +120,7 @@ html()
|
||||
{
|
||||
local title
|
||||
local body
|
||||
local script
|
||||
local footer
|
||||
|
||||
title=$(escape "$APP")
|
||||
@@ -129,10 +132,7 @@ html()
|
||||
body="<p class=\"loading\">${body/.../}</p>"
|
||||
fi
|
||||
|
||||
local timeout="4999"
|
||||
[ -n "${2:-}" ] && timeout="$2"
|
||||
local script="<script>setTimeout(() => { document.location.reload(); }, $timeout);</script>"
|
||||
[[ "$timeout" == "0" ]] && script=""
|
||||
[ -n "${2:-}" ] && script="$2" || script=""
|
||||
|
||||
local HTML
|
||||
HTML=$(<"$TEMPLATE")
|
||||
@@ -143,6 +143,7 @@ html()
|
||||
HTML="${HTML/\[5\]/$FOOTER2}"
|
||||
|
||||
echo "$HTML" > "$PAGE"
|
||||
echo "$body" > "$INFO"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -9,6 +9,21 @@ set -Eeuo pipefail
|
||||
: "${HOST_MODEL:=""}"
|
||||
: "${GUEST_SERIAL:=""}"
|
||||
|
||||
if [ -n "$HOST_MAC" ]; then
|
||||
|
||||
HOST_MAC="${HOST_MAC//-/:}"
|
||||
|
||||
if [[ ${#HOST_MAC} == 12 ]]; then
|
||||
m="$HOST_MAC"
|
||||
HOST_MAC="${m:0:2}:${m:2:2}:${m:4:2}:${m:6:2}:${m:8:2}:${m:10:2}"
|
||||
fi
|
||||
|
||||
if [[ ${#HOST_MAC} != 17 ]]; then
|
||||
error "Invalid HOST_MAC address: '$HOST_MAC', should be 12 or 17 digits long!" && exit 28
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
HOST_ARGS=()
|
||||
HOST_ARGS+=("-cpu=$CPU_CORES")
|
||||
HOST_ARGS+=("-cpu_arch=$HOST_CPU")
|
||||
|
||||
167
web/css/style.css
Normal file
167
web/css/style.css
Normal file
@@ -0,0 +1,167 @@
|
||||
body {
|
||||
color: white;
|
||||
background-color: #125bdb;
|
||||
font-smoothing: antialiased;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
font-family: Verdana, Geneva, sans-serif;
|
||||
}
|
||||
|
||||
#info {
|
||||
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
#content {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
footer {
|
||||
width: 98%;
|
||||
position: fixed;
|
||||
bottom: 0px;
|
||||
height: 40px;
|
||||
text-align: center;
|
||||
color: #0c8aeb;
|
||||
text-shadow: 0 0 1px #0c8aeb;
|
||||
}
|
||||
|
||||
#empty {
|
||||
height: 40px;
|
||||
/* Same height as footer */
|
||||
}
|
||||
|
||||
a,
|
||||
a:hover,
|
||||
a:active,
|
||||
a:visited {
|
||||
color: white;
|
||||
}
|
||||
|
||||
footer a:link,
|
||||
footer a:visited,
|
||||
footer a:active {
|
||||
color: #0c8aeb;
|
||||
}
|
||||
|
||||
footer a:hover {
|
||||
color: #73e6ff;
|
||||
}
|
||||
|
||||
.loading:after {
|
||||
content: " .";
|
||||
animation: dots 1s steps(5, end) infinite;
|
||||
}
|
||||
|
||||
@keyframes dots {
|
||||
|
||||
0%,
|
||||
20% {
|
||||
color: rgba(0, 0, 0, 0);
|
||||
text-shadow: 0.25em 0 0 rgba(0, 0, 0, 0), 0.5em 0 0 rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
40% {
|
||||
color: white;
|
||||
text-shadow: 0.25em 0 0 rgba(0, 0, 0, 0), 0.5em 0 0 rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
60% {
|
||||
text-shadow: 0.25em 0 0 white, 0.5em 0 0 rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
80%,
|
||||
100% {
|
||||
text-shadow: 0.25em 0 0 white, 0.5em 0 0 white;
|
||||
}
|
||||
}
|
||||
|
||||
.spinner_LWk7 {
|
||||
animation: spinner_GWy6 1.2s linear infinite, spinner_BNNO 1.2s linear infinite
|
||||
}
|
||||
|
||||
.spinner_yOMU {
|
||||
animation: spinner_GWy6 1.2s linear infinite, spinner_pVqn 1.2s linear infinite;
|
||||
animation-delay: .15s
|
||||
}
|
||||
|
||||
.spinner_KS4S {
|
||||
animation: spinner_GWy6 1.2s linear infinite, spinner_6uKB 1.2s linear infinite;
|
||||
animation-delay: .3s
|
||||
}
|
||||
|
||||
.spinner_zVee {
|
||||
animation: spinner_GWy6 1.2s linear infinite, spinner_Qw4x 1.2s linear infinite;
|
||||
animation-delay: .45s
|
||||
}
|
||||
|
||||
@keyframes spinner_GWy6 {
|
||||
|
||||
0%,
|
||||
50% {
|
||||
width: 9px;
|
||||
height: 9px
|
||||
}
|
||||
|
||||
10% {
|
||||
width: 11px;
|
||||
height: 11px
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spinner_BNNO {
|
||||
|
||||
0%,
|
||||
50% {
|
||||
x: 1.5px;
|
||||
y: 1.5px
|
||||
}
|
||||
|
||||
10% {
|
||||
x: .5px;
|
||||
y: .5px
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spinner_pVqn {
|
||||
|
||||
0%,
|
||||
50% {
|
||||
x: 13.5px;
|
||||
y: 1.5px
|
||||
}
|
||||
|
||||
10% {
|
||||
x: 12.5px;
|
||||
y: .5px
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spinner_6uKB {
|
||||
|
||||
0%,
|
||||
50% {
|
||||
x: 13.5px;
|
||||
y: 13.5px
|
||||
}
|
||||
|
||||
10% {
|
||||
x: 12.5px;
|
||||
y: 12.5px
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spinner_Qw4x {
|
||||
|
||||
0%,
|
||||
50% {
|
||||
x: 1.5px;
|
||||
y: 13.5px
|
||||
}
|
||||
|
||||
10% {
|
||||
x: .5px;
|
||||
y: 12.5px
|
||||
}
|
||||
}
|
||||
1
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 |
@@ -5,22 +5,30 @@
|
||||
[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="style.css" />
|
||||
<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-container">
|
||||
<div id="content-wrap">
|
||||
<h1>[3]</h1>
|
||||
<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-space">
|
||||
<div id="empty">
|
||||
</div>
|
||||
<div id="footer">
|
||||
<footer id="footer">
|
||||
[4]<br />
|
||||
[5]
|
||||
</footer>
|
||||
</div>
|
||||
</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();
|
||||
@@ -1,59 +0,0 @@
|
||||
body {
|
||||
color: white;
|
||||
background-color: #125bdb;
|
||||
font-family: Verdana, Arial, sans-serif;
|
||||
}
|
||||
|
||||
#content-wrap {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
margin-top: 100px;
|
||||
}
|
||||
|
||||
#footer {
|
||||
width: 98%;
|
||||
position: fixed;
|
||||
bottom: 0px;
|
||||
height: 40px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#empty-space {
|
||||
height: 40px;
|
||||
/* Same height as footer */
|
||||
}
|
||||
|
||||
a,
|
||||
a:hover,
|
||||
a:active,
|
||||
a:visited {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user