Skip to content

nginx: route /switch and /status to selkies supervisor (dual-mode)#154

Draft
DL6ER wants to merge 4 commits intolinuxserver:masterfrom
DL6ER:fix/nginx-supervisor-routes
Draft

nginx: route /switch and /status to selkies supervisor (dual-mode)#154
DL6ER wants to merge 4 commits intolinuxserver:masterfrom
DL6ER:fix/nginx-supervisor-routes

Conversation

@DL6ER
Copy link
Copy Markdown

@DL6ER DL6ER commented Apr 25, 2026

linuxserver.io


  • I have read the contributing guideline and understand that I have made the correct modifications

Description:

Add nginx routes for the dual-mode supervisor endpoints exposed by selkies when SELKIES_ENABLE_DUAL_MODE=true:

  • POST /switch — switch active streaming mode (websockets ↔ webrtc)
  • GET /status — report current mode + run state

Plumbing changes:

  • new SELKIES_SUPERVISOR_PORT env var in init-nginx/run (default 8082, matching the current selkies hardcoded value)
  • new SUPERVISOR_PORT sed substitution in default.conf
  • regex location ~ ^/(switch|status)$ added to both server blocks

Benefits of this PR and context:

The selkies-dashboard sidebar uses POST /switch to drive its "Streaming Mode" selector. Without nginx routing, the browser sees an HTML 404 from nginx and the dashboard cannot switch mode at runtime (in some downstream forks the dropdown is hidden until /status responds).

Code reference (selkies-project/selkies src/selkies/__main__.py):

api_task = asyncio.create_task(create_api_server(manager, host='localhost', port=8082))
logger.info("Dual mode enabled: Supervisor API server started on port 8082")
...
app.add_routes([
    web.post('/switch', handle_switch),
    web.get('/status', handle_status)
])

How Has This Been Tested?

Built locally on top of master (debian-trixie), composed an debian-xfce downstream image, ran with:

docker run -d \
  -p 3010:3000 \
  -e PUID=1000 -e PGID=1000 -e TZ=Europe/Berlin \
  -e SELKIES_ENABLE_DUAL_MODE=true \
  -e CUSTOM_WS_PORT=8081 \
  -e SELKIES_TURN_HOST=... -e SELKIES_TURN_PORT=3478 \
  -e SELKIES_TURN_PROTOCOL=udp -e SELKIES_TURN_SHARED_SECRET=... \
  webtop:patched

Validated:

$ curl https://<host>/status
{"current_mode": "webrtc", "status": "running"}

$ curl -X POST -H 'Content-Type: application/json' \
       -d '{"mode":"webrtc"}' https://<host>/switch
{"error": "'webrtc' mode is already running"}     # 409 if same mode
{"message": "Switched to 'webrtc' mode"}          # 200 if changed

Browser end-to-end: dashboard sidebar mode selector becomes interactive, switching modes triggers re-connection in the new mode without container restart.

Source / References:

  • Supervisor implementation: selkies-project/selkies src/selkies/__main__.py, the create_api_server function and the api_task creation in run().

Note: the selkies side hardcodes the supervisor port to 8082. Setting SELKIES_SUPERVISOR_PORT here only affects nginx; until selkies grows a matching env var the value must remain 8082. There is also an unrelated default port collision (CUSTOM_WS_PORT=8082 matches the hardcoded supervisor port), so dual-mode users currently need to set CUSTOM_WS_PORT=8081. I field PR against selkies-project/selkies to address both the port-default and an env-var for the supervisor:

When `SELKIES_ENABLE_DUAL_MODE=true` selkies starts a small aiohttp
supervisor on `localhost:8082` exposing two endpoints:

  POST /switch  - switch the active streaming mode (websockets <-> webrtc)
  GET  /status  - report which mode is currently running

The selkies-dashboard sidebar uses these to drive the "Streaming Mode"
selector. Without nginx routes the browser only sees an HTML 404 from
nginx and the dashboard's mode selector silently disappears (the
related dual-mode dropdown is gated on a successful /status response in
some downstream forks).

This adds:
- a new init-nginx env var `SELKIES_SUPERVISOR_PORT` (default 8082) and
  a sed substitution for `SUPERVISOR_PORT` in default.conf, so the port
  becomes configurable;
- a regex location `^/(switch|status)$` that proxies both endpoints to
  127.0.0.1:SUPERVISOR_PORT in both server blocks.

Note the existing `CWS` default (`CUSTOM_WS_PORT=8082`) conflicts with
the supervisor's hardcoded port in selkies (also 8082). Users enabling
dual mode currently need to set `CUSTOM_WS_PORT=8081`. Tracking the
supervisor-port-default question in selkies-project/selkies separately;
this PR only adds the nginx-side plumbing so the routes exist as soon
as selkies grows a configurable port.
Copilot AI review requested due to automatic review settings April 25, 2026 16:01
@LinuxServer-CI LinuxServer-CI moved this to PRs Ready For Team Review in Issue & PR Tracker Apr 25, 2026
CPORT="${CUSTOM_PORT:-3000}"
CHPORT="${CUSTOM_HTTPS_PORT:-3001}"
CWS="${CUSTOM_WS_PORT:-8082}"
SUP="${SELKIES_SUPERVISOR_PORT:-8082}"
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

follow-up patches default to 8084 once selkies-project/selkies#237 lands, converting this to a draft right now

@DL6ER DL6ER marked this pull request as draft April 25, 2026 16:03
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds nginx routing for Selkies dual-mode “supervisor” API endpoints so the dashboard can switch streaming modes and query run status when SELKIES_ENABLE_DUAL_MODE=true.

Changes:

  • Add SELKIES_SUPERVISOR_PORT (default 8082) to the nginx init script and substitute it into the templated nginx config.
  • Route POST /switch and GET /status to the supervisor API in both HTTP and HTTPS server blocks.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
root/etc/s6-overlay/s6-rc.d/init-nginx/run Adds env var + sed substitution to inject supervisor port into nginx config.
root/defaults/default.conf Adds nginx locations to proxy /switch and /status to the supervisor API.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread root/defaults/default.conf
Comment thread root/defaults/default.conf
Copilot caught that the new /switch + /status location forwards only
Host, while every other proxied location in this config (/devmode,
SUBFOLDERwebsocket) also passes X-Real-IP, X-Forwarded-For, and
X-Forwarded-Proto. The supervisor logs the requesting peer for the
mode-switch audit trail; without these headers everything looks like
127.0.0.1. Mirror the existing forwarding-header set in both server
blocks (no timeouts added — these are short HTTP requests, not WS).
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread root/defaults/default.conf Outdated
Comment thread root/defaults/default.conf Outdated
Comment thread root/etc/s6-overlay/s6-rc.d/init-nginx/run
…trips

Three follow-up issues from review:

1. The `# Selkies dual-mode supervisor ...` comment block above each
   location would be destroyed by the existing
   `if [ ! -z ${PASSWORD+x} ]; then sed -i 's/#//g' ...; fi` in
   init-nginx/run, which strips ALL `#` characters from the rendered
   config to uncomment the auth_basic lines. After the strip, our
   comment lines would parse as nginx directives and break startup.
   Drop the explanatory comments — the PR body already documents the
   intent, and nginx has no out-of-band comment syntax that survives
   the strip.

2. The regex `^/(switch|status)$` matched only at the document root,
   so a non-default SUBFOLDER deployment (e.g. SUBFOLDER=/foo/) would
   miss `/foo/switch` entirely. Switch to `^SUBFOLDER(switch|status)$`
   (rendered to `^/(switch|status)$` for the default `/` case) and
   `proxy_pass http://127.0.0.1:SUPERVISOR_PORT/$1` so the selkies
   supervisor — which serves at the root — sees the original
   `/switch` or `/status` regardless of nginx-side prefix.

3. Add an inline doc comment in init-nginx/run explaining what
   SELKIES_SUPERVISOR_PORT does (nginx proxy target only, mirrors the
   selkies-side port when SELKIES_ENABLE_DUAL_MODE is on).
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread root/defaults/default.conf Outdated
Follow-up to review feedback: the previous attempt used a regex
location with `$1` capture and SUBFOLDER substituted into the regex
itself. Two real risks with that approach:

1. SUBFOLDER is treated as literal text by sed but as regex once
   substituted, so a subfolder containing regex metacharacters
   (e.g. `.`) would change match semantics.

2. proxy_pass with a variable in the URI ($1) switches nginx out of
   prefix-strip mode and drops the query string unless explicitly
   appended via `\$is_args\$args`. /switch and /status do not use
   query strings today, but the trap is real.

Replace the single regex location with two `location =` exact-match
blocks per server (switch and status), each with a static
proxy_pass URL. SUBFOLDER stays a pure prefix substitution, no regex
involved; the static path means full URI rewriting works with all
request shapes; `=` is the highest-priority match in nginx so dispatch
is also fastest. Pattern matches the existing SUBFOLDERwebsocket /
SUBFOLDERwebsockets / SUBFOLDERfiles convention in this same file.
@DL6ER DL6ER requested a review from Copilot April 25, 2026 16:22
DL6ER added a commit to DL6ER/docker-baseimage-selkies that referenced this pull request Apr 25, 2026
# Conflicts:
#	root/defaults/default.conf
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +10 to +13
# Port of the selkies dual-mode supervisor API (/switch and /status).
# Only consulted when SELKIES_ENABLE_DUAL_MODE=true on the selkies side;
# this value drives nginx routing only, not the selkies bind port.
SUP="${SELKIES_SUPERVISOR_PORT:-8082}"
Copy link

Copilot AI Apr 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The shell variable name SUP is not descriptive and makes it harder to follow what’s being substituted into the nginx template. Consider renaming it to something explicit like SUPERVISOR_PORT (or SELKIES_SUPERVISOR_PORT_NGINX) and use that in the sed replacement for readability/maintainability.

Copilot uses AI. Check for mistakes.
Comment on lines +11 to +13
# Only consulted when SELKIES_ENABLE_DUAL_MODE=true on the selkies side;
# this value drives nginx routing only, not the selkies bind port.
SUP="${SELKIES_SUPERVISOR_PORT:-8082}"
Copy link

Copilot AI Apr 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SELKIES_SUPERVISOR_PORT is currently a footgun: selkies hardcodes the supervisor bind port to 8082, so setting this env var to any other value will silently break /switch and /status routing (nginx will proxy to a port where nothing is listening). Consider either hardcoding 8082 in nginx until selkies supports configuring it, or explicitly validating the value and warning/ignoring anything other than 8082.

Suggested change
# Only consulted when SELKIES_ENABLE_DUAL_MODE=true on the selkies side;
# this value drives nginx routing only, not the selkies bind port.
SUP="${SELKIES_SUPERVISOR_PORT:-8082}"
# Selkies currently binds the supervisor API to 8082 only; this script
# configures nginx routing and must not allow unsupported values here.
if [ -n "${SELKIES_SUPERVISOR_PORT+x}" ] && [ "${SELKIES_SUPERVISOR_PORT}" != "8082" ]; then
printf 'WARNING: Ignoring unsupported SELKIES_SUPERVISOR_PORT=%s; selkies currently binds the supervisor API to 8082 only.\n' "${SELKIES_SUPERVISOR_PORT}" >&2
fi
SUP="8082"

Copilot uses AI. Check for mistakes.
Comment on lines +51 to +60
location = SUBFOLDERswitch {
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;
proxy_http_version 1.1;
proxy_buffering off;
proxy_pass http://127.0.0.1:SUPERVISOR_PORT/switch;
}
location = SUBFOLDERstatus {
Copy link

Copilot AI Apr 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description says a single regex location (location ~ ^/(switch|status)$) was added, but the nginx template actually adds two exact-match locations (location = ...switch and location = ...status). Please update the PR description to match the implementation (or change the config to the documented regex approach) so future maintainers aren’t misled.

Copilot uses AI. Check for mistakes.
@LinuxServer-CI
Copy link
Copy Markdown
Collaborator

I am a bot, here are the test results for this PR:
https://ci-tests.linuxserver.io/lspipepr/selkies-base/debiantrixie-aa141909-pkg-aa141909-dev-a835c89db23d341851a0a7bd4d322966e63cc444-pr-154/index.html
https://ci-tests.linuxserver.io/lspipepr/selkies-base/debiantrixie-aa141909-pkg-aa141909-dev-a835c89db23d341851a0a7bd4d322966e63cc444-pr-154/shellcheck-result.xml

Tag Passed
amd64-debiantrixie-aa141909-pkg-aa141909-dev-a835c89db23d341851a0a7bd4d322966e63cc444-pr-154
arm64v8-debiantrixie-aa141909-pkg-aa141909-dev-a835c89db23d341851a0a7bd4d322966e63cc444-pr-154

@LinuxServer-CI
Copy link
Copy Markdown
Collaborator

I am a bot, here are the test results for this PR:
https://ci-tests.linuxserver.io/lspipepr/selkies-base/debiantrixie-aa141909-pkg-aa141909-dev-7c23db78c67bab47774327e2a54c75c630e0cdb9-pr-154/index.html
https://ci-tests.linuxserver.io/lspipepr/selkies-base/debiantrixie-aa141909-pkg-aa141909-dev-7c23db78c67bab47774327e2a54c75c630e0cdb9-pr-154/shellcheck-result.xml

Tag Passed
amd64-debiantrixie-aa141909-pkg-aa141909-dev-7c23db78c67bab47774327e2a54c75c630e0cdb9-pr-154
arm64v8-debiantrixie-aa141909-pkg-aa141909-dev-7c23db78c67bab47774327e2a54c75c630e0cdb9-pr-154

@LinuxServer-CI
Copy link
Copy Markdown
Collaborator

I am a bot, here are the test results for this PR:
https://ci-tests.linuxserver.io/lspipepr/selkies-base/debiantrixie-aa141909-pkg-aa141909-dev-de8f481ec257fc277781ed3987ff06f8ce76ae89-pr-154/index.html
https://ci-tests.linuxserver.io/lspipepr/selkies-base/debiantrixie-aa141909-pkg-aa141909-dev-de8f481ec257fc277781ed3987ff06f8ce76ae89-pr-154/shellcheck-result.xml

Tag Passed
amd64-debiantrixie-aa141909-pkg-aa141909-dev-de8f481ec257fc277781ed3987ff06f8ce76ae89-pr-154
arm64v8-debiantrixie-aa141909-pkg-aa141909-dev-de8f481ec257fc277781ed3987ff06f8ce76ae89-pr-154

@LinuxServer-CI
Copy link
Copy Markdown
Collaborator

I am a bot, here are the test results for this PR:
https://ci-tests.linuxserver.io/lspipepr/selkies-base/debiantrixie-aa141909-pkg-aa141909-dev-a1216e74982e2d4dc5ffbc4a00f9d532cd24778b-pr-154/index.html
https://ci-tests.linuxserver.io/lspipepr/selkies-base/debiantrixie-aa141909-pkg-aa141909-dev-a1216e74982e2d4dc5ffbc4a00f9d532cd24778b-pr-154/shellcheck-result.xml

Tag Passed
amd64-debiantrixie-aa141909-pkg-aa141909-dev-a1216e74982e2d4dc5ffbc4a00f9d532cd24778b-pr-154
arm64v8-debiantrixie-aa141909-pkg-aa141909-dev-a1216e74982e2d4dc5ffbc4a00f9d532cd24778b-pr-154

@thelamer
Copy link
Copy Markdown
Member

Just a heads up the dual mode is not something planned for a bit. (this might sit for a couple weeks) The current plan is to get a Selkies image from the Selkies namespace and have it replace their current glx/egl desktop images. Once that has been distributed and tested out we will be layering on WebRTC functionality on the LSIO side using optional environment variables and port forwards.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: PRs Ready For Team Review

Development

Successfully merging this pull request may close these issues.

4 participants