diff --git a/api/src/unraid-api/unraid-file-modifier/modifications/__test__/snapshots/rc.nginx.modified.snapshot b/api/src/unraid-api/unraid-file-modifier/modifications/__test__/snapshots/rc.nginx.modified.snapshot index 7c555fda54..3b24e4777c 100644 --- a/api/src/unraid-api/unraid-file-modifier/modifications/__test__/snapshots/rc.nginx.modified.snapshot +++ b/api/src/unraid-api/unraid-file-modifier/modifications/__test__/snapshots/rc.nginx.modified.snapshot @@ -383,6 +383,17 @@ build_locations(){ include fastcgi_params; } # + # SSO endpoints (public) + location /auth/sso { + allow all; + proxy_pass http://unix:/var/run/unraid-core.sock:; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + # # Redirect to login page on failed authentication (401) # error_page 401 @401; @@ -417,10 +428,22 @@ build_locations(){ # # my servers proxy # + location /graphql/api { + allow all; + proxy_pass http://unix:/var/run/unraid-api.sock:; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } location /graphql { allow all; error_log /dev/null crit; - proxy_pass http://unix:/var/run/unraid-api.sock:/graphql; + if ($http_upgrade = "websocket") { + rewrite ^/graphql$ /graphql/socket break; + } + proxy_pass http://unix:/var/run/unraid-core.sock:; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header Upgrade $http_upgrade; diff --git a/api/src/unraid-api/unraid-file-modifier/modifications/patches/rc-nginx.patch b/api/src/unraid-api/unraid-file-modifier/modifications/patches/rc-nginx.patch index fbac95e0bc..acbd35f4b6 100644 --- a/api/src/unraid-api/unraid-file-modifier/modifications/patches/rc-nginx.patch +++ b/api/src/unraid-api/unraid-file-modifier/modifications/patches/rc-nginx.patch @@ -44,7 +44,57 @@ Index: /etc/rc.d/rc.nginx T=' ' if check && [[ $1 == lo ]]; then if [[ $IPV4 == yes ]]; then -@@ -566,11 +584,11 @@ +@@ -363,10 +381,21 @@ + allow all; + try_files /login.php =404; + include fastcgi_params; + } + # ++ # SSO endpoints (public) ++ location /auth/sso { ++ allow all; ++ proxy_pass http://unix:/var/run/unraid-core.sock:; ++ proxy_http_version 1.1; ++ proxy_set_header Host $host; ++ proxy_set_header X-Real-IP $remote_addr; ++ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; ++ proxy_set_header X-Forwarded-Proto $scheme; ++ } ++ # + # Redirect to login page on failed authentication (401) + # + error_page 401 @401; + location @401 { + return 302 $scheme://$http_host/login; +@@ -397,14 +426,26 @@ + nchan_stub_status; + } + # + # my servers proxy + # ++ location /graphql/api { ++ allow all; ++ proxy_pass http://unix:/var/run/unraid-api.sock:; ++ proxy_http_version 1.1; ++ proxy_set_header Host $host; ++ proxy_set_header X-Real-IP $remote_addr; ++ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; ++ proxy_set_header X-Forwarded-Proto $scheme; ++ } + location /graphql { + allow all; + error_log /dev/null crit; +- proxy_pass http://unix:/var/run/unraid-api.sock:/graphql; ++ if ($http_upgrade = "websocket") { ++ rewrite ^/graphql$ /graphql/socket break; ++ } ++ proxy_pass http://unix:/var/run/unraid-core.sock:; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_cache_bypass $http_upgrade; +@@ -566,11 +607,11 @@ # extract common name from cert CERTNAME=$(openssl x509 -noout -subject -nameopt multiline -in $CERTPATH | sed -n 's/ *commonName *= //p') # define CSP frame-ancestors for cert @@ -57,7 +107,7 @@ Index: /etc/rc.d/rc.nginx WANIP6=$(curl https://wanip6.unraid.net/ 2>/dev/null) fi if [[ $CERTNAME == *\.myunraid\.net ]]; then -@@ -660,14 +678,14 @@ +@@ -660,14 +701,14 @@ echo "NGINX_WANFQDN=\"$WANFQDN\"" >>$INI echo "NGINX_WANFQDN6=\"$WANFQDN6\"" >>$INI # defined if ts_bundle.pem present: diff --git a/api/src/unraid-api/unraid-file-modifier/modifications/rc-nginx.modification.ts b/api/src/unraid-api/unraid-file-modifier/modifications/rc-nginx.modification.ts index 6b1c717a3a..28d396f890 100644 --- a/api/src/unraid-api/unraid-file-modifier/modifications/rc-nginx.modification.ts +++ b/api/src/unraid-api/unraid-file-modifier/modifications/rc-nginx.modification.ts @@ -29,9 +29,9 @@ export default class RcNginxModification extends FileModification { throw new Error(`File ${this.filePath} not found.`); } const fileContent = await readFile(this.filePath, 'utf8'); - if (!fileContent.includes('MYSERVERS=')) { - throw new Error(`MYSERVERS not found in the file; incorrect target?`); - } + // if (!fileContent.includes('MYSERVERS=')) { + // throw new Error(`MYSERVERS not found in the file; incorrect target?`); + // } let newContent = fileContent.replace( 'MYSERVERS="/boot/config/plugins/dynamix.my.servers/myservers.cfg"', @@ -68,6 +68,27 @@ check_remote_access(){ `if [[ -L /usr/local/sbin/unraid-api ]] && check_remote_access; then` ); + newContent = newContent.replace( + 'proxy_pass http://unix:/var/run/unraid-api.sock:/graphql;', + 'if ($http_upgrade = "websocket") {\n\t rewrite ^/graphql$ /graphql/socket break;\n\t }\n\t proxy_pass http://unix:/var/run/unraid-core.sock:;' + ); + + if (!newContent.includes('location /auth/sso')) { + newContent = newContent.replace( + '\t# Redirect to login page on failed authentication (401)\n', + // prettier-ignore + `\t# SSO endpoints (public)\n\tlocation /auth/sso {\n\t allow all;\n\t proxy_pass http://unix:/var/run/unraid-core.sock:;\n\t proxy_http_version 1.1;\n\t proxy_set_header Host $host;\n\t proxy_set_header X-Real-IP $remote_addr;\n\t proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n\t proxy_set_header X-Forwarded-Proto $scheme;\n\t}\n\t#\n\t# Redirect to login page on failed authentication (401)\n` + ); + } + + if (!newContent.includes('location /graphql/api')) { + newContent = newContent.replace( + '\t# my servers proxy\n\t#\n\tlocation /graphql {', + // prettier-ignore + `\t# my servers proxy\n\t#\n\tlocation /graphql/api {\n\t allow all;\n\t proxy_pass http://unix:/var/run/unraid-api.sock:;\n\t proxy_http_version 1.1;\n\t proxy_set_header Host $host;\n\t proxy_set_header X-Real-IP $remote_addr;\n\t proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n\t proxy_set_header X-Forwarded-Proto $scheme;\n\t}\n\tlocation /graphql {` + ); + } + newContent = newContent.replace( 'for NET in ${!NET_FQDN6[@]}; do', 'for NET in "${!NET_FQDN6[@]}"; do' @@ -91,7 +112,7 @@ check_remote_access(){ } async shouldApply(): Promise { - const { shouldApply, reason } = await super.shouldApply(); + const { shouldApply, reason } = await super.shouldApply({ checkOsVersion: false }); return { shouldApply, reason, diff --git a/plugin/plugins/dynamix.unraid.net.plg b/plugin/plugins/dynamix.unraid.net.plg index 6e8a60e695..6e795a94b3 100755 --- a/plugin/plugins/dynamix.unraid.net.plg +++ b/plugin/plugins/dynamix.unraid.net.plg @@ -9,6 +9,10 @@ + + + + @@ -52,6 +56,12 @@ exit 0 &txz_sha256; + + + &core_txz_url; + &core_txz_sha256; + + @@ -320,6 +330,21 @@ exit 0 fi fi + # Stop and remove Unraid Core package + if [ -x "/etc/rc.d/rc.unraid" ]; then + echo "Stopping Unraid Core..." + /etc/rc.d/rc.unraid stop || echo "Warning: Failed to stop Unraid Core" + fi + + core_pkg_installed=$(ls -1 /var/log/packages/unraid-* 2>/dev/null | head -1) + if [ -n "$core_pkg_installed" ]; then + core_pkg_basename=$(basename "$core_pkg_installed") + echo "Removing core package: $core_pkg_basename" + removepkg --terse "$core_pkg_basename" + else + echo "No Unraid Core package found" + fi + # File restoration function echo "Restoring files..." @@ -404,6 +429,9 @@ exit 0 PKG_FILE="&source;" # Full path to the package file including .txz extension PKG_URL="&txz_url;" # URL where package was downloaded from PKG_NAME="&txz_name;" # Name of the package file + CORE_PKG_FILE="&core_source;" + CORE_PKG_URL="&core_txz_url;" + CORE_PKG_NAME="&core_txz_name;" CONNECT_API_VERSION="&api_version;" # Version of API included with Connect @@ -599,6 +658,13 @@ echo "If no additional messages appear within 30 seconds, it is safe to refresh /etc/rc.d/rc.unraid-api start echo "Unraid API service started" +if [ -x "/etc/rc.d/rc.unraid" ]; then + echo "Starting Unraid Core service" + /etc/rc.d/rc.unraid start + echo "Unraid Core service started" +else + echo "Warning: rc.unraid not found; core service not started" +fi echo "✅ Installation is complete, it is safe to close this window" echo exit 0 diff --git a/plugin/source/dynamix.unraid.net/etc/rc.d/rc.unraid b/plugin/source/dynamix.unraid.net/etc/rc.d/rc.unraid new file mode 100755 index 0000000000..80f636e20c --- /dev/null +++ b/plugin/source/dynamix.unraid.net/etc/rc.d/rc.unraid @@ -0,0 +1,89 @@ +#!/bin/bash +# /etc/rc.d/rc.unraid +# Unraid Phoenix Application Service + +APP_DIR="/usr/local/unraid" +RELEASE_BIN="$APP_DIR/_build/prod/rel/unraid/bin/unraid" +CONFIG_DIR="/boot/config/unraid" +SOCKET_PATH="/var/run/unraid-core.sock" +LOG_PATH="${UNRAID_LOG_PATH:-/var/log/unraid-core.log}" + +# Load user env if exists +[ -f "$CONFIG_DIR/env" ] && source "$CONFIG_DIR/env" + +# Ensure config and log directories exist +mkdir -p "$CONFIG_DIR" +mkdir -p "$(dirname "$LOG_PATH")" +touch "$LOG_PATH" + +# Generate secret_key_base if not exists +if [ ! -f "$CONFIG_DIR/secret_key_base" ]; then + head -c 64 /dev/urandom | base64 | tr -d '\n' > "$CONFIG_DIR/secret_key_base" + chmod 600 "$CONFIG_DIR/secret_key_base" +fi + +export SECRET_KEY_BASE=$(cat "$CONFIG_DIR/secret_key_base") +export RELEASE_COOKIE=$(cat "$CONFIG_DIR/secret_key_base" | head -c 20) +export UNRAID_CONFIG_DIR="$CONFIG_DIR" +export RUN_ERL_LOG="${RUN_ERL_LOG:-$LOG_PATH}" +export RELEASE_LOG_DIR="${RELEASE_LOG_DIR:-$(dirname "$LOG_PATH")}" +export RELEASE_NODE="${UNRAID_RELEASE_NODE:-unraid}" +export RELEASE_DISTRIBUTION="${UNRAID_RELEASE_DISTRIBUTION:-sname}" +export RELEASE_MODE="${UNRAID_RELEASE_MODE:-interactive}" + +# Import user's runtime.exs if exists +[ -f "$CONFIG_DIR/runtime.exs" ] && export RELEASE_CONFIG_DIR="$CONFIG_DIR" + +# Socket/port configuration +if [ -n "${UNRAID_PORT:-}" ]; then + export PHX_PORT="$UNRAID_PORT" +else + export PHX_SOCKET="${UNRAID_SOCKET:-$SOCKET_PATH}" +fi + +start() { + echo -n "Starting Unraid... " + [ -S "$SOCKET_PATH" ] && rm -f "$SOCKET_PATH" + "$RELEASE_BIN" daemon + echo "done" +} + +stop() { + echo -n "Stopping Unraid... " + "$RELEASE_BIN" stop 2>/dev/null || true + [ -S "$SOCKET_PATH" ] && rm -f "$SOCKET_PATH" + echo "done" +} + +restart() { + stop + sleep 2 + start +} + +status() { + "$RELEASE_BIN" pid >/dev/null 2>&1 && echo "Running" || echo "Stopped" +} + +rollback() { + if [ -d "/usr/local/unraid.prev" ]; then + echo "Rolling back to previous version..." + stop + rm -rf /usr/local/unraid + mv /usr/local/unraid.prev /usr/local/unraid + start + echo "Rollback complete" + else + echo "No previous version available" + exit 1 + fi +} + +case "${1:-}" in + start) start ;; + stop) stop ;; + restart) restart ;; + status) status ;; + rollback) rollback ;; + *) echo "Usage: $0 {start|stop|restart|status|rollback}" ;; +esac diff --git a/plugin/source/dynamix.unraid.net/etc/rc.d/rc6.d/K30unraid-core b/plugin/source/dynamix.unraid.net/etc/rc.d/rc6.d/K30unraid-core new file mode 100644 index 0000000000..82a004bbcd --- /dev/null +++ b/plugin/source/dynamix.unraid.net/etc/rc.d/rc6.d/K30unraid-core @@ -0,0 +1,7 @@ +#!/bin/sh +# Stop Unraid Core on shutdown/reboot + +if [ -x /etc/rc.d/rc.unraid ]; then + echo "Stopping Unraid Core..." + /etc/rc.d/rc.unraid stop +fi diff --git a/plugin/source/dynamix.unraid.net/install/doinst.sh b/plugin/source/dynamix.unraid.net/install/doinst.sh index e18f5f64eb..79b6e2292f 100644 --- a/plugin/source/dynamix.unraid.net/install/doinst.sh +++ b/plugin/source/dynamix.unraid.net/install/doinst.sh @@ -6,7 +6,7 @@ backup_file_if_exists() { fi } -for f in etc/rc.d/rc6.d/K*unraid-api etc/rc.d/rc6.d/K*flash-backup; do +for f in etc/rc.d/rc6.d/K*unraid-api etc/rc.d/rc6.d/K*unraid-core etc/rc.d/rc6.d/K*flash-backup; do [ -e "$f" ] && chmod 755 "$f" done diff --git a/plugin/source/dynamix.unraid.net/usr/local/share/dynamix.unraid.net/install/scripts/verify_install.sh b/plugin/source/dynamix.unraid.net/usr/local/share/dynamix.unraid.net/install/scripts/verify_install.sh index 0731bd976e..107d44aa87 100755 --- a/plugin/source/dynamix.unraid.net/usr/local/share/dynamix.unraid.net/install/scripts/verify_install.sh +++ b/plugin/source/dynamix.unraid.net/usr/local/share/dynamix.unraid.net/install/scripts/verify_install.sh @@ -42,10 +42,12 @@ echo "Performing comprehensive installation verification..." # Define critical files to check (POSIX-compliant, no arrays) CRITICAL_FILES="/usr/local/bin/unraid-api /etc/rc.d/rc.unraid-api +/etc/rc.d/rc.unraid /usr/local/emhttp/plugins/dynamix.my.servers/scripts/gitflash_log" # Define critical directories to check (POSIX-compliant, no arrays) CRITICAL_DIRS="/usr/local/unraid-api +/usr/local/unraid /var/log/unraid-api /usr/local/emhttp/plugins/dynamix.my.servers /usr/local/emhttp/plugins/dynamix.unraid.net @@ -159,6 +161,14 @@ else SHUTDOWN_ERRORS=$((SHUTDOWN_ERRORS + 1)) fi +# Check for unraid-core shutdown script +if [ -x "/etc/rc.d/rc6.d/K30unraid-core" ]; then + printf '✓ Shutdown script for unraid-core exists and is executable\n' +else + printf '✗ Shutdown script for unraid-core missing or not executable\n' + SHUTDOWN_ERRORS=$((SHUTDOWN_ERRORS + 1)) +fi + # Check for rc0.d symlink or directory if [ -L "/etc/rc.d/rc0.d" ]; then printf '✓ rc0.d symlink exists\n' @@ -206,4 +216,4 @@ else echo "Please review the errors above and contact support if needed." # We don't exit with error as this is just a verification script exit 0 -fi \ No newline at end of file +fi diff --git a/web/src/components/sso/useSsoAuth.ts b/web/src/components/sso/useSsoAuth.ts index 2b3d1f2573..1806b1fcd4 100644 --- a/web/src/components/sso/useSsoAuth.ts +++ b/web/src/components/sso/useSsoAuth.ts @@ -63,6 +63,8 @@ export function useSsoAuth() { }; const navigateToProvider = (providerId: string) => { + currentState.value = 'loading'; + error.value = null; // Generate state token for CSRF protection const state = generateStateToken(); @@ -85,7 +87,7 @@ export function useSsoAuth() { const hashToken = hashParams.get('token'); const hashError = hashParams.get('error'); - // Then check query parameters (for OAuth code/state from provider redirects) + // Then check query parameters (for error/token fallback) const search = new URLSearchParams(window.location.search); const code = search.get('code') ?? ''; const state = search.get('state') ?? ''; @@ -129,6 +131,10 @@ export function useSsoAuth() { currentState.value = 'error'; error.value = t('sso.useSsoAuth.invalidCallbackParameters'); } + + if (window.location.pathname !== '/login') { + return; + } } catch (err) { console.error('Error fetching token', err); currentState.value = 'error'; diff --git a/web/src/helpers/create-apollo-client.ts b/web/src/helpers/create-apollo-client.ts index f3e0c0adaf..c45c5491a9 100644 --- a/web/src/helpers/create-apollo-client.ts +++ b/web/src/helpers/create-apollo-client.ts @@ -43,8 +43,11 @@ const wsEndpoint = new URL(httpEndpoint); wsEndpoint.protocol = wsEndpoint.protocol === 'https:' ? 'wss:' : 'ws:'; const DEV_MODE = (globalThis as unknown as { __DEV__: boolean }).__DEV__ ?? false; +const csrfToken = globalThis.csrf_token ?? '0000000000000000'; +wsEndpoint.searchParams.set('_csrf_token', csrfToken); + const headers = { - 'x-csrf-token': globalThis.csrf_token ?? '0000000000000000', + 'x-csrf-token': csrfToken, }; const httpLink = createHttpLink({