From d190b3ba87d04558a8d54a49bc1cca7fcadaf1c4 Mon Sep 17 00:00:00 2001 From: Kroese Date: Wed, 8 Oct 2025 21:28:26 +0200 Subject: [PATCH] feat: Use websocket for status messages (#1051) --- Dockerfile | 4 +- src/entry.sh | 4 +- src/install.sh | 3 - src/network.sh | 38 +++++++----- src/proc.sh | 48 +------------- src/progress.sh | 2 +- src/reset.sh | 148 ++++++++++++++++++-------------------------- src/server.sh | 39 ++++++++++++ src/socket.sh | 11 ++++ src/start.sh | 4 +- src/utils.sh | 61 ++++++++++++++++++ web/conf/nginx.conf | 14 +++++ web/js/script.js | 126 +++++++++++++++++++++++++++---------- 13 files changed, 311 insertions(+), 191 deletions(-) create mode 100644 src/server.sh create mode 100644 src/socket.sh diff --git a/Dockerfile b/Dockerfile index 6651782..317d7a3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -37,13 +37,15 @@ RUN set -eu && \ xz-utils \ iptables \ iproute2 \ - apt-utils \ dnsmasq \ fakeroot \ + apt-utils \ net-tools \ e2fsprogs \ qemu-utils \ + websocketd \ iputils-ping \ + inotify-tools \ ca-certificates \ netcat-openbsd \ qemu-system-x86 && \ diff --git a/src/entry.sh b/src/entry.sh index ae40dc7..fce3756 100755 --- a/src/entry.sh +++ b/src/entry.sh @@ -1,14 +1,16 @@ #!/usr/bin/env bash set -Eeuo pipefail +: "${PLATFORM:="x64"}" : "${APP:="Virtual DSM"}" : "${SUPPORT:="https://github.com/vdsm/virtual-dsm"}" cd /run -. start.sh # Placeholder +. start.sh # Startup hook . utils.sh # Load functions . reset.sh # Initialize system +. server.sh # Start webserver . install.sh # Run installation . disk.sh # Initialize disks . display.sh # Initialize graphics diff --git a/src/install.sh b/src/install.sh index 6abe518..f7ec7f4 100644 --- a/src/install.sh +++ b/src/install.sh @@ -301,7 +301,4 @@ fi mv -f "$BOOT" "$STORAGE/$BASE.boot.img" rm -rf "$TMP" -html "Booting DSM instance..." -sleep 1.2 - return 0 diff --git a/src/network.sh b/src/network.sh index f4cd095..936a1b4 100644 --- a/src/network.sh +++ b/src/network.sh @@ -176,7 +176,7 @@ configureDNS() { fi if [[ "$DNSMASQ_DEBUG" == [Yy1]* ]]; then - tail -fn +0 "$log" & + tail -fn +0 "$log" --pid=$$ & fi return 0 @@ -231,6 +231,7 @@ getHostPorts() { [ -z "$list" ] && list="$COM_PORT" || list+=",$COM_PORT" [ -z "$list" ] && list="$CHR_PORT" || list+=",$CHR_PORT" + [ -z "$list" ] && list="$WSD_PORT" || list+=",$WSD_PORT" fi @@ -327,7 +328,7 @@ configurePasst() { fi if [[ "$PASST_DEBUG" == [Yy1]* ]]; then - tail -fn +0 "$log" & + tail -fn +0 "$log" --pid=$$ & else if [[ "$DEBUG" == [Yy1]* ]]; then [ -f "$log" ] && cat "$log" && echo "" @@ -502,14 +503,24 @@ closeBridge() { return 0 } +closeWeb() { + + # Shutdown nginx + nginx -s stop 2> /dev/null + fWait "nginx" + + # Shutdown websocket + local pid="/var/run/websocketd.pid" + [ -s "$pid" ] && pKill "$(<"$pid")" + rm -f "$pid" + + return 0 +} + closeNetwork() { if [[ "${WEB:-}" != [Nn]* && "$DHCP" == [Yy1]* ]]; then - - # Shutdown nginx - nginx -s stop 2> /dev/null - fWait "nginx" - + closeWeb fi [[ "$NETWORK" == [Nn]* ]] && return 0 @@ -718,22 +729,19 @@ if [[ "$IP" == "172.17."* ]]; then warn "your container IP starts with 172.17.* which will cause conflicts when you install the Container Manager package inside DSM!" fi +MSG="Booting DSM instance..." +html "$MSG" + if [[ "$DHCP" == [Yy1]* ]]; then # Configure for macvtap interface configureDHCP || exit 20 - MSG="Booting DSM instance..." - html "$MSG" - else if [[ "${WEB:-}" != [Nn]* ]]; then - - # Shutdown nginx - nginx -s stop 2> /dev/null - fWait "nginx" - + sleep 1.2 + closeWeb fi case "${NETWORK,,}" in diff --git a/src/proc.sh b/src/proc.sh index b323fda..a36e5a7 100644 --- a/src/proc.sh +++ b/src/proc.sh @@ -3,7 +3,6 @@ set -Eeuo pipefail # Docker environment variables -: "${KVM:="Y"}" : "${HOST_CPU:=""}" : "${CPU_FLAGS:=""}" : "${CPU_MODEL:=""}" @@ -26,52 +25,7 @@ else esac fi -if [[ "$KVM" == [Nn]* ]]; then - warn "KVM acceleration is disabled, this will cause the machine to run about 10 times slower!" -else - if [[ "${ARCH,,}" != "amd64" ]]; then - KVM="N" - warn "your CPU architecture is ${ARCH^^} and cannot provide KVM acceleration for x64 instructions, so the machine will run about 10 times slower." - fi -fi - -if [[ "$KVM" != [Nn]* ]]; then - - KVM_ERR="" - - if [ ! -e /dev/kvm ]; then - KVM_ERR="(/dev/kvm is missing)" - else - if ! sh -c 'echo -n > /dev/kvm' &> /dev/null; then - KVM_ERR="(/dev/kvm is unwriteable)" - else - flags=$(sed -ne '/^flags/s/^.*: //p' /proc/cpuinfo) - if ! grep -qw "vmx\|svm" <<< "$flags"; then - KVM_ERR="(not enabled in BIOS)" - fi - fi - fi - - if [ -n "$KVM_ERR" ]; then - KVM="N" - if [[ "$OSTYPE" =~ ^darwin ]]; then - warn "you are using macOS which has no KVM support, so the machine will run about 10 times slower." - else - kernel=$(uname -a) - case "${kernel,,}" in - *"microsoft"* ) - error "Please bind '/dev/kvm' as a volume in the optional container settings when using Docker Desktop." ;; - *"synology"* ) - error "Please make sure that Synology VMM (Virtual Machine Manager) is installed and that '/dev/kvm' is binded to this container." ;; - *) - error "KVM acceleration is not available $KVM_ERR, this will cause the machine to run about 10 times slower." - error "See the FAQ for possible causes, or disable acceleration by adding the \"KVM=N\" variable (not recommended)." ;; - esac - [[ "$DEBUG" != [Yy1]* ]] && exit 88 - fi - fi - -fi +flags=$(sed -ne '/^flags/s/^.*: //p' /proc/cpuinfo) if [[ "$KVM" != [Nn]* ]]; then diff --git a/src/progress.sh b/src/progress.sh index 9c7549f..6f47329 100644 --- a/src/progress.sh +++ b/src/progress.sh @@ -25,7 +25,7 @@ do if [ -s "$file" ]; then bytes=$(du -sb "$file" | cut -f1) if (( bytes > 1000 )); then - if [ -z "$total" ] || [[ "$total" == "0" ]]; then + if [ -z "$total" ] || [[ "$total" == "0" ]] || [ "$bytes" -gt "$total" ]; then size=$(numfmt --to=iec --suffix=B "$bytes" | sed -r 's/([A-Z])/ \1/') else size="$(echo "$bytes" "$total" | awk '{printf "%.1f", $1 * 100 / $2}')" diff --git a/src/reset.sh b/src/reset.sh index 3abed80..e9430e8 100644 --- a/src/reset.sh +++ b/src/reset.sh @@ -9,7 +9,8 @@ trap 'error "Status $? while: $BASH_COMMAND (line $LINENO/$BASH_LINENO)"' ERR # Docker environment variables -: "${TZ:=""}" # System local timezone +: "${TZ:=""}" # System timezone +: "${KVM:="Y"}" # KVM acceleration : "${DEBUG:="N"}" # Disable debugging mode : "${COUNTRY:=""}" # Country code for mirror : "${CONSOLE:="N"}" # Disable console mode @@ -141,6 +142,63 @@ if [[ "$RAM_CHECK" != [Nn]* ]] && (( (RAM_WANTED + RAM_SPARE) > RAM_AVAIL )); th info "$msg" fi +# Check KVM support + +if [[ "${PLATFORM,,}" == "x64" ]]; then + TARGET="amd64" +else + TARGET="arm64" +fi + +if [[ "$KVM" == [Nn]* ]]; then + warn "KVM acceleration is disabled, this will cause the machine to run about 10 times slower!" +else + if [[ "${ARCH,,}" != "$TARGET" ]]; then + KVM="N" + warn "your CPU architecture is ${ARCH^^} and cannot provide KVM acceleration for ${PLATFORM^^} instructions, so the machine will run about 10 times slower." + fi +fi + +if [[ "$KVM" != [Nn]* ]]; then + + KVM_ERR="" + + if [ ! -e /dev/kvm ]; then + KVM_ERR="(/dev/kvm is missing)" + else + if ! sh -c 'echo -n > /dev/kvm' &> /dev/null; then + KVM_ERR="(/dev/kvm is unwriteable)" + else + if [[ "${PLATFORM,,}" == "x64" ]]; then + flags=$(sed -ne '/^flags/s/^.*: //p' /proc/cpuinfo) + if ! grep -qw "vmx\|svm" <<< "$flags"; then + KVM_ERR="(not enabled in BIOS)" + fi + fi + fi + fi + + if [ -n "$KVM_ERR" ]; then + KVM="N" + if [[ "$OSTYPE" =~ ^darwin ]]; then + warn "you are using macOS which has no KVM support, so the machine will run about 10 times slower." + else + kernel=$(uname -a) + case "${kernel,,}" in + *"microsoft"* ) + error "Please bind '/dev/kvm' as a volume in the optional container settings when using Docker Desktop." ;; + *"synology"* ) + error "Please make sure that Synology VMM (Virtual Machine Manager) is installed and that '/dev/kvm' is binded to this container." ;; + *) + error "KVM acceleration is not available $KVM_ERR, this will cause the machine to run about 10 times slower." + error "See the FAQ for possible causes, or disable acceleration by adding the \"KVM=N\" variable (not recommended)." ;; + esac + [[ "$DEBUG" != [Yy1]* ]] && exit 88 + fi + fi + +fi + # Cleanup files rm -f /run/shm/qemu.* rm -f /run/shm/dsm.url @@ -149,92 +207,4 @@ rm -f /run/shm/dsm.url rm -rf /tmp/dsm rm -rf "$STORAGE/tmp" -getCountry() { - local url=$1 - local query=$2 - local rc json result - - { json=$(curl -m 5 -H "Accept: application/json" -sfk "$url"); rc=$?; } || : - (( rc != 0 )) && return 0 - - { result=$(echo "$json" | jq -r "$query" 2> /dev/null); rc=$?; } || : - (( rc != 0 )) && return 0 - - [[ ${#result} -ne 2 ]] && return 0 - [[ "${result^^}" == "XX" ]] && return 0 - - COUNTRY="${result^^}" - - return 0 -} - -setCountry() { - - [[ "${TZ,,}" == "asia/harbin" ]] && COUNTRY="CN" - [[ "${TZ,,}" == "asia/beijing" ]] && COUNTRY="CN" - [[ "${TZ,,}" == "asia/urumqi" ]] && COUNTRY="CN" - [[ "${TZ,,}" == "asia/kashgar" ]] && COUNTRY="CN" - [[ "${TZ,,}" == "asia/shanghai" ]] && COUNTRY="CN" - [[ "${TZ,,}" == "asia/chongqing" ]] && COUNTRY="CN" - - [ -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.ipquery.io/?format=json" ".location.country_code" - [ -z "$COUNTRY" ] && getCountry "https://api.myip.com" ".cc" - - return 0 -} - -addPackage() { - local pkg=$1 - local desc=$2 - - if apt-mark showinstall | grep -qx "$pkg"; then - return 0 - fi - - MSG="Installing $desc..." - info "$MSG" && html "$MSG" - - [ -z "$COUNTRY" ] && setCountry - - if [[ "${COUNTRY^^}" == "CN" ]]; then - sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list.d/debian.sources - fi - - DEBIAN_FRONTEND=noninteractive apt-get -qq update - DEBIAN_FRONTEND=noninteractive apt-get -qq --no-install-recommends -y install "$pkg" > /dev/null - - return 0 -} - -: "${COM_PORT:="2210"}" # Comm port -: "${MON_PORT:="7100"}" # Monitor port -: "${WEB_PORT:="5000"}" # Webserver port -: "${CHR_PORT:="12345"}" # Character port - -cp -r /var/www/* /run/shm -html "Starting $APP for $ENGINE..." - -if [[ "${WEB:-}" != [Nn]* ]]; then - - mkdir -p /etc/nginx/sites-enabled - cp /etc/nginx/default.conf /etc/nginx/sites-enabled/web.conf - - sed -i "s/listen 5000 default_server;/listen $WEB_PORT default_server;/g" /etc/nginx/sites-enabled/web.conf - - # shellcheck disable=SC2143 - if [ -f /proc/net/if_inet6 ] && [ -n "$(ifconfig -a | grep inet6)" ]; then - - sed -i "s/listen $WEB_PORT default_server;/listen [::]:$WEB_PORT default_server ipv6only=off;/g" /etc/nginx/sites-enabled/web.conf - - fi - - # Start webserver - nginx -e stderr - -fi - return 0 diff --git a/src/server.sh b/src/server.sh new file mode 100644 index 0000000..cea8c29 --- /dev/null +++ b/src/server.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +: "${COM_PORT:="2210"}" # Comm port +: "${MON_PORT:="7100"}" # Monitor port +: "${WEB_PORT:="5000"}" # Webserver port +: "${CHR_PORT:="12345"}" # Character port +: "${WSD_PORT:="8004"}" # Websockets port + +cp -r /var/www/* /run/shm +rm -f /var/run/websocketd.pid + +html "Starting $APP for $ENGINE..." + +if [[ "${WEB:-}" != [Nn]* ]]; then + + mkdir -p /etc/nginx/sites-enabled + cp /etc/nginx/default.conf /etc/nginx/sites-enabled/web.conf + + sed -i "s/listen 5000 default_server;/listen $WEB_PORT default_server;/g" /etc/nginx/sites-enabled/web.conf + sed -i "s/proxy_pass http:\/\/127.0.0.1:8004\/;/proxy_pass http:\/\/127.0.0.1:$WSD_PORT\/;/g" /etc/nginx/sites-enabled/web.conf + + # shellcheck disable=SC2143 + if [ -f /proc/net/if_inet6 ] && [ -n "$(ifconfig -a | grep inet6)" ]; then + + sed -i "s/listen $WEB_PORT default_server;/listen [::]:$WEB_PORT default_server ipv6only=off;/g" /etc/nginx/sites-enabled/web.conf + + fi + + # Start webserver + nginx -e stderr + + # Start websocket server + websocketd --address 127.0.0.1 --port="$WSD_PORT" /run/socket.sh >/var/log/websocketd.log & + echo "$!" > /var/run/websocketd.pid + +fi + +return 0 diff --git a/src/socket.sh b/src/socket.sh new file mode 100644 index 0000000..f843ab7 --- /dev/null +++ b/src/socket.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +path="/run/shm/msg.html" + +inotifywait -m "$path" | + while read -r fp event fn; do + case "${event,,}" in + "modify" ) echo -n "s: " && cat "$path" ;; + esac + done diff --git a/src/start.sh b/src/start.sh index 55f6874..1b63c84 100644 --- a/src/start.sh +++ b/src/start.sh @@ -1,4 +1,6 @@ #!/usr/bin/env bash set -Eeuo pipefail -# Override this placeholder file using a Docker bind to execute a script during startup! +# You can override this hook to execute a script before startup! + +return 0 diff --git a/src/utils.sh b/src/utils.sh index e158f55..9fa4929 100644 --- a/src/utils.sh +++ b/src/utils.sh @@ -159,4 +159,65 @@ hasDisk() { return 1 } +getCountry() { + local url=$1 + local query=$2 + local rc json result + + { json=$(curl -m 5 -H "Accept: application/json" -sfk "$url"); rc=$?; } || : + (( rc != 0 )) && return 0 + + { result=$(echo "$json" | jq -r "$query" 2> /dev/null); rc=$?; } || : + (( rc != 0 )) && return 0 + + [[ ${#result} -ne 2 ]] && return 0 + [[ "${result^^}" == "XX" ]] && return 0 + + COUNTRY="${result^^}" + + return 0 +} + +setCountry() { + + [[ "${TZ,,}" == "asia/harbin" ]] && COUNTRY="CN" + [[ "${TZ,,}" == "asia/beijing" ]] && COUNTRY="CN" + [[ "${TZ,,}" == "asia/urumqi" ]] && COUNTRY="CN" + [[ "${TZ,,}" == "asia/kashgar" ]] && COUNTRY="CN" + [[ "${TZ,,}" == "asia/shanghai" ]] && COUNTRY="CN" + [[ "${TZ,,}" == "asia/chongqing" ]] && COUNTRY="CN" + + [ -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.ipquery.io/?format=json" ".location.country_code" + [ -z "$COUNTRY" ] && getCountry "https://api.myip.com" ".cc" + + return 0 +} + +addPackage() { + local pkg=$1 + local desc=$2 + + if apt-mark showinstall | grep -qx "$pkg"; then + return 0 + fi + + MSG="Installing $desc..." + info "$MSG" && html "$MSG" + + [ -z "$COUNTRY" ] && setCountry + + if [[ "${COUNTRY^^}" == "CN" ]]; then + sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list.d/debian.sources + fi + + DEBIAN_FRONTEND=noninteractive apt-get -qq update + DEBIAN_FRONTEND=noninteractive apt-get -qq --no-install-recommends -y install "$pkg" > /dev/null + + return 0 +} + return 0 diff --git a/web/conf/nginx.conf b/web/conf/nginx.conf index 6c9971b..ee98c2a 100644 --- a/web/conf/nginx.conf +++ b/web/conf/nginx.conf @@ -27,4 +27,18 @@ server { index index.html; } + + location /status { + + proxy_http_version 1.1; + + proxy_set_header Connection 'upgrade'; + proxy_set_header Upgrade $http_upgrade; + + proxy_buffering off; + proxy_read_timeout 3600s; + proxy_send_timeout 3600s; + + proxy_pass http://127.0.0.1:8004/; + } } diff --git a/web/js/script.js b/web/js/script.js index 670e1d4..937e627 100644 --- a/web/js/script.js +++ b/web/js/script.js @@ -1,4 +1,5 @@ var request; +var booting = false; var interval = 1000; function getInfo() { @@ -6,7 +7,6 @@ function getInfo() { var url = "msg.html"; try { - if (window.XMLHttpRequest) { request = new XMLHttpRequest(); } else { @@ -18,22 +18,49 @@ function getInfo() { request.send(); } catch (e) { - var err = "Error: " + e.message; - console.log(err); - setError(err); + setError("Error: " + e.message); } } +function getURL() { + + var protocol = window.location.protocol === "https:" ? "wss:" : "ws:"; + var path = window.location.pathname.replace(/[^/]*$/, '').replace(/\/$/, ''); + + return protocol + "//" + window.location.host + path; +} + +function processMsg(msg) { + + 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; +} + 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(); + + if (booting) { + schedule(); + return true; + } + + window.location.reload(); return false; } @@ -43,20 +70,11 @@ function processInfo() { if (msg.toLowerCase().indexOf("") !== -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); + processMsg(msg); + if (msg.toLowerCase().indexOf("href=") == -1) { schedule(); - return true; } + return true; } } @@ -67,31 +85,38 @@ function processInfo() { } setError("Error: Received statuscode " + request.status); - schedule(); return false; } catch (e) { - var err = "Error: " + e.message; - console.log(err); - setError(err); + setError("Error: " + e.message); return false; } } function setInfo(msg, loading, error) { - try { + if (msg == null || msg.length == 0) { return false; } - var el = document.getElementById("spinner"); + if (msg.includes("Booting ")) { + booting = true; + } + + var el = document.getElementById("info"); + + if (el.innerText == msg || el.innerHTML == msg) { + return true; + } + + var spin = document.getElementById("spinner"); error = !!error; if (!error) { - el.style.visibility = 'visible'; + spin.style.visibility = 'visible'; } else { - el.style.visibility = 'hidden'; + spin.style.visibility = 'hidden'; } loading = !!loading; @@ -99,12 +124,7 @@ function setInfo(msg, loading, error) { msg = "

" + msg + "

"; } - el = document.getElementById("info"); - - if (el.innerHTML != msg) { - el.innerHTML = msg; - } - + el.innerHTML = msg; return true; } catch (e) { @@ -114,6 +134,7 @@ function setInfo(msg, loading, error) { } function setError(text) { + console.warn(text); return setInfo(text, false, true); } @@ -123,8 +144,47 @@ function schedule() { function reload() { setTimeout(() => { - document.location.reload(); + window.location.reload(); }, 3000); } +function connect() { + + var wsUrl = getURL() + "/status"; + var ws = new WebSocket(wsUrl); + + ws.onmessage = function(e) { + + var pos = e.data.indexOf(":"); + var cmd = e.data.substring(0, pos); + var msg = e.data.substring(pos + 2); + + switch (cmd) { + case "s": + processMsg(msg); + break; + case "e": + setError(msg); + break; + default: + console.warn("Unknown event: " + cmd); + break; + } + }; + + ws.onclose = function(e) { + setTimeout(function() { + connect(); + }, interval); + }; + + ws.onerror = function(e) { + ws.close(); + if (!booting) { + window.location.reload(); + } + }; +} + schedule(); +connect();