From d9112b797f3d782241cb26c70c0f60031cada8d5 Mon Sep 17 00:00:00 2001 From: catborise Date: Fri, 25 Sep 2026 23:11:52 +0300 Subject: [PATCH] fix(console): fix novncd startup in container and prevent double slashes in websocket url --- conf/runit/10_webvirtcloud_init.sh | 11 ++++++++ conf/runit/novncd.sh | 32 +++++++++++++++-------- console/novncd | 17 ++++++++++++ console/templates/console-spice-full.html | 4 +-- console/templates/console-vnc-lite.html | 10 ++++++- console/views.py | 3 +++ webvirtcloud/settings.py.template | 2 +- 7 files changed, 64 insertions(+), 15 deletions(-) diff --git a/conf/runit/10_webvirtcloud_init.sh b/conf/runit/10_webvirtcloud_init.sh index d44e1d5..31f9c29 100755 --- a/conf/runit/10_webvirtcloud_init.sh +++ b/conf/runit/10_webvirtcloud_init.sh @@ -20,6 +20,17 @@ if [ -z "$SECRET_KEY" ]; then fi fi +# Apply optional WebSocket configuration from environment +if [ -n "$WS_PUBLIC_PORT" ]; then + sed -i "s|^WS_PUBLIC_PORT = .*|WS_PUBLIC_PORT = $WS_PUBLIC_PORT|" "$APP_DIR/webvirtcloud/settings.py" +fi +if [ -n "$WS_PUBLIC_HOST" ]; then + sed -i "s|^WS_PUBLIC_HOST = .*|WS_PUBLIC_HOST = \"$WS_PUBLIC_HOST\"|" "$APP_DIR/webvirtcloud/settings.py" +fi +if [ -n "$WS_PUBLIC_PATH" ]; then + sed -i "s|^WS_PUBLIC_PATH = .*|WS_PUBLIC_PATH = \"$WS_PUBLIC_PATH\"|" "$APP_DIR/webvirtcloud/settings.py" +fi + # Persist SQLite database in DATA_DIR if [ ! -f "$DATA_DIR/db.sqlite3" ] && [ -f "$APP_DIR/db.sqlite3" ] && [ ! -L "$APP_DIR/db.sqlite3" ]; then mv "$APP_DIR/db.sqlite3" "$DATA_DIR/db.sqlite3" diff --git a/conf/runit/novncd.sh b/conf/runit/novncd.sh index b071864..203acf8 100755 --- a/conf/runit/novncd.sh +++ b/conf/runit/novncd.sh @@ -1,18 +1,28 @@ #!/bin/sh -# `/sbin/setuser www-data` runs the given command as the user `www-data`. -RUNAS=$(which setuser) -[ -z "$RUNAS" ] && RUNAS="$(which sudo) -u" -USER=www-data +USER="www-data" +DJANGO_PROJECT="/srv/webvirtcloud" +PYTHON="$DJANGO_PROJECT/venv/bin/python3" +NOVNCD="$DJANGO_PROJECT/console/novncd" +LOG="/var/log/novncd.log" -DJANGO_PROJECT=/srv/webvirtcloud -PYTHON=$DJANGO_PROJECT/venv/bin/python3 -NOVNCD=$DJANGO_PROJECT/console/novncd +cd "$DJANGO_PROJECT" || exit 1 # make novncd debug, verbose #PARAMS="-d -v" -LOG=/var/log/novncd.log - -cd $DJANGO_PROJECT || exit -exec "$RUNAS" "$USER" "$PYTHON" "$NOVNCD" "$PARAMS" >> $LOG 2>&1 +if [ -x /sbin/setuser ]; then + if [ -n "$PARAMS" ]; then + # shellcheck disable=SC2086 + exec /sbin/setuser "$USER" "$PYTHON" "$NOVNCD" $PARAMS >> "$LOG" 2>&1 + else + exec /sbin/setuser "$USER" "$PYTHON" "$NOVNCD" >> "$LOG" 2>&1 + fi +elif command -v su >/dev/null 2>&1; then + if [ -n "$PARAMS" ]; then + # shellcheck disable=SC2086 + exec su -s /bin/sh "$USER" -c "exec \"$PYTHON\" \"$NOVNCD\" $PARAMS" >> "$LOG" 2>&1 + else + exec su -s /bin/sh "$USER" -c "exec \"$PYTHON\" \"$NOVNCD\"" >> "$LOG" 2>&1 + fi +fi diff --git a/console/novncd b/console/novncd index 14c7a7f..402e3cd 100755 --- a/console/novncd +++ b/console/novncd @@ -143,6 +143,7 @@ class CompatibilityMixIn(object): # NoVNC uses it's own convention that forward token # from the request to a cookie header, we should check # also for this behavior + token = None hcookie = self.headers.get("cookie") if hcookie: @@ -159,6 +160,22 @@ class CompatibilityMixIn(object): if "token" in cookie: token = cookie["token"].value + # Fallback to query parameter if token not found in cookie + if not token and hasattr(self, "path"): + from urllib.parse import parse_qs, urlparse + + try: + parsed_url = urlparse(self.path) + query_params = parse_qs(parsed_url.query) + if "token" in query_params: + token = query_params["token"][0] + except Exception: + pass + + if not token: + self.msg("No console token provided in cookie or query parameters") + return + ( connhost, connport, diff --git a/console/templates/console-spice-full.html b/console/templates/console-spice-full.html index 0dfed9f..5be3967 100644 --- a/console/templates/console-spice-full.html +++ b/console/templates/console-spice-full.html @@ -61,8 +61,8 @@ sc.stop(); } - // uri = scheme + host + ":" + port; - uri = scheme + "{{ ws_host }}:{{ ws_port }}{{ ws_path }}"; + var path = '{{ ws_path }}'; + uri = scheme + "{{ ws_host }}:{{ ws_port }}" + (path ? (path[0] == '/' ? path : ('/' + path)) : '/'); document.getElementById('connectButton').innerHTML = "Stop"; document.getElementById('connectButton').onclick = disconnect; diff --git a/console/templates/console-vnc-lite.html b/console/templates/console-vnc-lite.html index 3da589a..69ee6cf 100755 --- a/console/templates/console-vnc-lite.html +++ b/console/templates/console-vnc-lite.html @@ -194,7 +194,15 @@ if (port) { url += ':' + port; } - url += '/' + path; + if (path) { + url += path.startsWith('/') ? path : ('/' + path); + } else { + url += '/'; + } + const token = readQueryVariable('token', '{{ token }}'); + if (token) { + url += (url.includes('?') ? '&' : '?') + 'token=' + encodeURIComponent(token); + } // Creating a new RFB object will start a new connection rfb = new RFB(document.getElementById('noVNC_container'), url, diff --git a/console/views.py b/console/views.py index f530df1..528e809 100644 --- a/console/views.py +++ b/console/views.py @@ -86,6 +86,9 @@ def console(request): if ":" in ws_host: ws_host = re.sub(":[0-9]+", "", ws_host) + if ws_path: + ws_path = ws_path.strip("/") + "/" if ws_path.strip("/") else "" + if console_type == "vnc" or console_type == "spice": console_page = "console-" + console_type + "-" + view_type + ".html" response = render(request, console_page, locals()) diff --git a/webvirtcloud/settings.py.template b/webvirtcloud/settings.py.template index 71e84f2..c4c3e16 100644 --- a/webvirtcloud/settings.py.template +++ b/webvirtcloud/settings.py.template @@ -213,7 +213,7 @@ WS_PUBLIC_PORT = 6080 WS_PUBLIC_HOST = None # Websock public path -WS_PUBLIC_PATH = "/novncd/" +WS_PUBLIC_PATH = "novncd/" # Websock Certificate for SSL WS_CERT = None