mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-31 04:12:10 +03:00
feat(server): support reverse-proxy basePath deployment (#5992)
* feat(server): support reverse-proxy basePath deployment Adds OMNIROUTE_BASE_PATH (opt-in, empty by default) to next.config.mjs using Next.js's native basePath support so a deployment behind a reverse-proxy subpath (e.g. https://host/omniroute/) works without manual header stripping. Next.js strips the configured prefix from nextUrl.pathname before route classification, so classifyRoute() and isLocalOnlyPath() keep matching un-prefixed paths. The two hardcoded auth redirect targets in src/server/authz/pipeline.ts (root "/" -> "/dashboard" and unauthenticated dashboard -> "/login") now prefix with request.nextUrl.basePath so they stay inside the deployed subpath. Default empty basePath is a no-op for existing root-path deployments. Co-authored-by: zocomputer <help@zocomputer.com> Inspired-by: https://github.com/decolua/9router/pull/1810 * docs(env): document OMNIROUTE_BASE_PATH in .env.example + ENVIRONMENT.md; restore changelog * docs(env): document AUGGIE_BIN + CLI_AUGGIE_BIN (base-red from #5972 auggie) --------- Co-authored-by: zocomputer <help@zocomputer.com>
This commit is contained in:
committed by
GitHub
parent
f984bef4ec
commit
4e16a491a1
@@ -73,6 +73,11 @@ DISABLE_SQLITE_AUTO_BACKUP=false
|
||||
# Default: 20128
|
||||
PORT=20128
|
||||
|
||||
# Base path (URL subpath) when serving OmniRoute behind a reverse proxy under a subpath.
|
||||
# Used by: next.config.mjs — sets Next.js `basePath`; auth redirects are basePath-aware.
|
||||
# Default: "" (served at the domain root). Example: /omniroute to serve under https://host/omniroute
|
||||
# OMNIROUTE_BASE_PATH=
|
||||
|
||||
# Split-port mode: serve Dashboard and API on separate ports for network isolation.
|
||||
# Used by: src/lib/runtime/ports.ts — overrides PORT for each service.
|
||||
# API_PORT=20129
|
||||
@@ -568,6 +573,8 @@ NEXT_PUBLIC_ENABLE_SOCKS5_PROXY=true
|
||||
# CLI_CONTINUE_BIN=cn
|
||||
# CLI_QODER_BIN=qoder
|
||||
# CLI_QWEN_BIN=qwen
|
||||
# CLI_AUGGIE_BIN=auggie
|
||||
# AUGGIE_BIN=auggie
|
||||
|
||||
# Override the Hermes Agent home directory (where OmniRoute reads/writes the
|
||||
# Hermes CLI config). Matches the env var the Hermes PowerShell installer sets
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
- **feat(providers):** add Nube.sh as an OpenAI-compatible (API-key) provider. (thanks @whale9820)
|
||||
- **feat(providers):** add Charm Hyper as an OpenAI-compatible (API-key) provider. (thanks @whale9820)
|
||||
- **feat(providers):** add SumoPod and X5Lab as OpenAI-compatible (API-key) providers. (thanks @rigelra15)
|
||||
- **feat(server):** support reverse-proxy subpath deployment via OMNIROUTE_BASE_PATH (basePath-aware auth redirects). (thanks @SillyHippy)
|
||||
|
||||
### 🔧 Bug Fixes
|
||||
|
||||
|
||||
@@ -117,6 +117,7 @@ OmniRoute uses **SQLite** (via `better-sqlite3`) for all persistence. These vari
|
||||
| Variable | Default | Source File | Description |
|
||||
| ------------------------------------------- | ------------------------------- | ------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `PORT` | `20128` | `src/lib/runtime/ports.ts` | Primary port for both Dashboard UI and API endpoints (single-port mode). |
|
||||
| `OMNIROUTE_BASE_PATH` | _(empty = root)_ | `next.config.mjs` | URL subpath for serving OmniRoute behind a reverse proxy under a subpath (sets Next.js `basePath`; auth redirects are basePath-aware). E.g. `/omniroute`. |
|
||||
| `API_PORT` | _(unset)_ | `src/lib/runtime/ports.ts` | When set, serves the `/v1/*` proxy API on this separate port. |
|
||||
| `API_HOST` | `0.0.0.0` | `src/lib/runtime/ports.ts` | Bind address for the API port. |
|
||||
| `DASHBOARD_PORT` | _(unset)_ | `src/lib/runtime/ports.ts` | When set, serves the Dashboard UI on this separate port. |
|
||||
@@ -353,6 +354,8 @@ Controls how OmniRoute discovers and launches CLI sidecars (Claude Code, Codex,
|
||||
| `CLI_QODER_BIN` | `qoder` | `src/shared/services/cliRuntime.ts` | Custom path to Qoder CLI binary. |
|
||||
| `CLI_QWEN_BIN` | `qwen` | `src/shared/services/cliRuntime.ts` | Custom path to the Qwen Code CLI binary. |
|
||||
| `CLI_DEVIN_BIN` | `devin` | `open-sse/executors/devin-cli.ts` | Custom path to the Devin CLI binary (v3.8.0). Used by the Windsurf/Devin executor. |
|
||||
| `AUGGIE_BIN` | `auggie` | `open-sse/executors/auggie.ts` | Absolute-path override for the Augment (Auggie) CLI binary used by the local `auggie` provider. Falls back to `CLI_AUGGIE_BIN`, then a PATH lookup. |
|
||||
| `CLI_AUGGIE_BIN` | `auggie` | `open-sse/executors/auggie.ts` | Alias override for the Augment (Auggie) CLI binary path (checked after `AUGGIE_BIN`). |
|
||||
| `HERMES_HOME` | `~/.hermes` | `src/lib/cli-helper/config-generator/hermesHome.ts` | Hermes Agent home directory where OmniRoute reads/writes the Hermes CLI config. Matches the env var the Hermes PowerShell installer sets on Windows (`%LOCALAPPDATA%\hermes`). |
|
||||
|
||||
### CLI Profile Auto-Sync
|
||||
|
||||
@@ -94,6 +94,13 @@ function readTimeoutMs(...values) {
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
// Opt-in subpath deployment behind a reverse proxy (e.g. nginx/Caddy serving
|
||||
// OmniRoute under https://host/omniroute/). Empty by default so root-path
|
||||
// deployments are unaffected. Next.js strips this prefix from `pathname`
|
||||
// before route matching, so authz classification (classifyRoute/isLocalOnlyPath)
|
||||
// keeps operating on un-prefixed paths — see src/server/authz/pipeline.ts for
|
||||
// the two redirect call sites that re-add it via `request.nextUrl.basePath`.
|
||||
basePath: process.env.OMNIROUTE_BASE_PATH || "",
|
||||
distDir,
|
||||
// Turbopack config: redirect native modules to stubs at build time
|
||||
turbopack: {
|
||||
|
||||
@@ -148,7 +148,7 @@ async function refreshDashboardSessionIfNeeded(
|
||||
}
|
||||
|
||||
function dashboardLoginRedirect(request: NextRequest, requestId: string): NextResponse {
|
||||
const response = NextResponse.redirect(new URL("/login", request.url));
|
||||
const response = NextResponse.redirect(new URL(`${request.nextUrl.basePath}/login`, request.url));
|
||||
response.cookies.delete("auth_token");
|
||||
stampRouteResponse(response, requestId, "MANAGEMENT");
|
||||
applyCorsHeaders(response, request);
|
||||
@@ -223,7 +223,9 @@ export async function runAuthzPipeline(
|
||||
const requestId = generateRequestId();
|
||||
|
||||
if (pathname === "/") {
|
||||
const response = NextResponse.redirect(new URL("/dashboard", request.url));
|
||||
const response = NextResponse.redirect(
|
||||
new URL(`${request.nextUrl.basePath}/dashboard`, request.url)
|
||||
);
|
||||
return stampRouteResponse(response, requestId, "MANAGEMENT");
|
||||
}
|
||||
|
||||
|
||||
@@ -138,6 +138,50 @@ test("runAuthzPipeline redirects unauthenticated /home/* nested paths to login (
|
||||
assert.equal(response.headers.get("x-omniroute-route-class"), "MANAGEMENT");
|
||||
});
|
||||
|
||||
// PR #1810 (upstream 9router): reverse-proxy subpath deployment via
|
||||
// OMNIROUTE_BASE_PATH. Next.js strips the basePath from nextUrl.pathname
|
||||
// before route classification, so the redirect targets must re-add it via
|
||||
// request.nextUrl.basePath to stay inside the deployed subpath.
|
||||
test("runAuthzPipeline prefixes the root-to-dashboard redirect with basePath when set", async () => {
|
||||
await forceAuthRequired();
|
||||
|
||||
const req = new NextRequest("http://localhost/omniroute/", {
|
||||
nextConfig: { basePath: "/omniroute" },
|
||||
});
|
||||
|
||||
const response = await pipeline.runAuthzPipeline(req, { enforce: true });
|
||||
|
||||
assert.equal(response.status, 307);
|
||||
assert.equal(response.headers.get("location"), "http://localhost/omniroute/dashboard");
|
||||
});
|
||||
|
||||
test("runAuthzPipeline prefixes the dashboard login redirect with basePath when set", async () => {
|
||||
await forceAuthRequired();
|
||||
|
||||
const req = new NextRequest("http://localhost/omniroute/dashboard", {
|
||||
nextConfig: { basePath: "/omniroute" },
|
||||
});
|
||||
|
||||
const response = await pipeline.runAuthzPipeline(req, { enforce: true });
|
||||
|
||||
assert.equal(response.status, 307);
|
||||
assert.equal(response.headers.get("location"), "http://localhost/omniroute/login");
|
||||
assert.equal(response.headers.get("x-omniroute-route-class"), "MANAGEMENT");
|
||||
});
|
||||
|
||||
test("runAuthzPipeline leaves redirect targets unprefixed when basePath is empty", async () => {
|
||||
await forceAuthRequired();
|
||||
|
||||
const req = new NextRequest("http://localhost/dashboard", {
|
||||
nextConfig: { basePath: "" },
|
||||
});
|
||||
|
||||
const response = await pipeline.runAuthzPipeline(req, { enforce: true });
|
||||
|
||||
assert.equal(response.status, 307);
|
||||
assert.equal(response.headers.get("location"), "http://localhost/login");
|
||||
});
|
||||
|
||||
test("runAuthzPipeline allows onboarding when login is required but no password exists", async () => {
|
||||
delete process.env.INITIAL_PASSWORD;
|
||||
await settingsDb.updateSettings({
|
||||
|
||||
Reference in New Issue
Block a user