Download deploy/openwebui-subpath/Caddyfile.subpath from SushantGautam/simpleaudit-studio: direct link, hf CLI and curl.
- Browser
- Download file 6.41 kB
-
https://huggingface.co/spaces/SushantGautam/simpleaudit-studio/resolve/main/deploy/openwebui-subpath/Caddyfile.subpath
- Command line
-
hf download hf://spaces/SushantGautam/simpleaudit-studio/deploy/openwebui-subpath/Caddyfile.subpath
-
curl -L -o Caddyfile.subpath https://huggingface.co/spaces/SushantGautam/simpleaudit-studio/resolve/main/deploy/openwebui-subpath/Caddyfile.subpath
6.41 kB
| # ============================================================================= | |
| # SimpleAudit Studio β single-origin chat routing (subpath Open WebUI) | |
| # | |
| # The routing shape enabled by the subpath fork built via | |
| # deploy/openwebui-subpath/build.sh (published as | |
| # ghcr.io/sushantgautam/open-webui:v0.12.4.5-subpath). It replaces the old | |
| # cross-origin iframe model (Caddyfile.chat on :8801) with one origin / one | |
| # port: | |
| # | |
| # https://studio.example.com | |
| # βββ /chat/... -> Open WebUI (subpath build, served AT /chat) | |
| # βββ /playground/... -> Studio's wrapper page (model-picker bar + iframe of /chat/) | |
| # βββ everything -> Studio (Django) | |
| # | |
| # Only THIS container publishes a port. Studio (web) and Open WebUI publish | |
| # none β everything the browser sees goes through here. | |
| # | |
| # THE CRITICAL DETAIL (verified empirically): use `handle`, NOT `handle_path`. | |
| # handle_path /chat/* => strips the prefix and forwards /foo, but the | |
| # subpath fork serves its socket at the FULL path | |
| # /chat/ws/socket.io (engineio matches the raw path, | |
| # not root_path-relative) -> the socket 404s. | |
| # handle /chat/* => forwards /chat/foo as-is, which is exactly what | |
| # a WEBUI_SUBPATH=/chat build expects. | |
| # | |
| # ============================================================================= | |
| { | |
| auto_https off | |
| admin off | |
| } | |
| :{$STUDIO_PORT:8000} { | |
| # Never let a client supply its own identity. | |
| request_header -X-Studio-Email | |
| request_header -X-Studio-Name | |
| request_header -X-Studio-Role | |
| # Studio's /playground/ iframe points at the bare subpath with query params | |
| # (/chat?models=...). `handle /chat/*` does NOT match a bare /chat, so | |
| # send it to /chat/ (query preserved) before route selection. | |
| rewrite /chat /chat/ | |
| # Studio's chat embed stylesheet: the subpath build requests | |
| # /chat/static/custom.css on every page. Caddy intercepts it (before the | |
| # /chat/* proxy) and proxies it to Studio's /chat/css-mask, which serves | |
| # chat/embed.css normally and chat/embed_admin.css for an admin's iframe | |
| # session (stamped by /chat/authz when the page loads with ?embed=admin). | |
| # The session cookie rides the request, so no Caddy-side state is needed. | |
| handle /chat/static/custom.css { | |
| reverse_proxy {$STUDIO_UPSTREAM:web:8000} { | |
| rewrite /chat/css-mask | |
| } | |
| } | |
| # Studio's chat embed mask script: the subpath build's static/loader.js | |
| # is a 0-byte no-op (the SPA's first <script>; SvelteKit hydration comes | |
| # from the separate entry/start bundle). Caddy intercepts it and proxies | |
| # it to Studio's /chat/loader-mask, which serves chat/embed_admin.js. The | |
| # script self-gates on the PAGE URL carrying ?embed=admin, so only the | |
| # admin workspace iframes (Knowledge, Tools) are masked; plain /chat/ and | |
| # the top-level /chat/workspace pages keep their full menu. | |
| handle /chat/static/loader.js { | |
| reverse_proxy {$STUDIO_UPSTREAM:web:8000} { | |
| rewrite /chat/loader-mask | |
| } | |
| } | |
| # Subpath Open WebUI. `handle` (NOT handle_path) = no prefix strip. | |
| # | |
| # The first handler is the forward-auth to Studio's /chat/authz, in the | |
| # EXPANDED form (reverse_proxy + handle_response) β verified end-to-end: | |
| # Studio's identity headers are set onto the request and survive to OWUI, | |
| # forged client values are stripped at site level before this runs, and a | |
| # 401 from authz redirects the browser to the Studio login. The auth call | |
| # lives INSIDE this handle block on purpose: Caddy sorts top-level routes | |
| # by directive order (forward_auth=11, request_header=12, handle=46, | |
| # reverse_proxy=47), so a top-level auth middleware runs AFTER the | |
| # site-level header strip and a top-level reverse_proxy auth runs AFTER | |
| # this terminal handle β both orderings silently break. In a subroute, | |
| # handlers run in file order, so auth runs first, then OWUI. | |
| handle /chat/* { | |
| reverse_proxy {$STUDIO_UPSTREAM:web:8000} { | |
| # Always GET, so the incoming request's body is not consumed. | |
| method GET | |
| # Point at the Studio auth endpoint. | |
| rewrite /chat/authz | |
| # Forward the original method and URI (they get rewritten above). | |
| header_up X-Forwarded-Method {method} | |
| header_up X-Forwarded-Uri {uri} | |
| # The rewrite above drops the page's query from the auth call, | |
| # so the ?embed=admin flag (admin workspace iframes only) rides | |
| # here as a header. Studio's /chat/authz stamps the session | |
| # admin from it; that is what makes /chat/static/custom.css | |
| # (css-mask) serve the admin skin with the tab-bar mask. | |
| # Client-forged values are stripped at site level. | |
| header_up X-Studio-Embed {uri.query.embed} | |
| # On success, set the trusted identity headers on the original | |
| # request. Client-forged values were already stripped at site | |
| # level, and the placeholder is empty unless Studio signed it. | |
| @good status 2xx | |
| handle_response @good { | |
| request_header X-Studio-Email {rp.header.X-Studio-Email} | |
| request_header X-Studio-Name {rp.header.X-Studio-Name} | |
| request_header X-Studio-Role {rp.header.X-Studio-Role} | |
| } | |
| # Signed out: send the browser to the Studio login, not a bare 401 | |
| # and not to /chat/ itself (that would just re-auth and bounce). | |
| @signedout status 401 | |
| handle_response @signedout { | |
| redir {$STUDIO_URL:http://localhost:8000}/login/ 302 | |
| } | |
| } | |
| reverse_proxy open-webui:{$SIMPLEAUDIT_CHAT_UPSTREAM_PORT:8080} | |
| } | |
| # The subpath fork's KnowledgeBase "Back" button navigates to the | |
| # root-relative /workspace/knowledge (a missed ${base} prefix in the fork | |
| # build) β a hard GET that would 404 on Studio. Repair it at the front | |
| # door: 308 the browser onto the subpath, which re-enters the /chat/* | |
| # handle (forward-auth + OWUI) and lands on the collection list. A 308 | |
| # (not an internal rewrite) because the SPA is built with base=/chat and | |
| # its client router reload-loops on an out-of-base URL. The target must | |
| # lead with a placeholder (the env prefix): Caddy's caddyfile parser | |
| # misreads a leading '/' as a matcher. {http.request.uri} preserves the | |
| # query string. Dead code once the fork is rebuilt with the ${base} | |
| # prefix. | |
| handle /workspace/* { | |
| redir {env.WEBUI_SUBPATH:/chat}{http.request.uri} 308 | |
| } | |
| # Everything else -> Studio (Django). The subpath fork never emits | |
| # root-relative refs, so nothing from OWUI falls through here. | |
| handle { | |
| reverse_proxy {$STUDIO_UPSTREAM:web:8000} | |
| } | |
| } | |