mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
feat: enhance port configuration and API bridge support
- Updated .env.example to include optional split ports for API and dashboard. - Modified docker-compose files to dynamically use the configured ports. - Introduced a new script (run-standalone.mjs) for running the server with environment-specific ports. - Implemented an API bridge server to handle OpenAI-compatible routes when using split ports. - Updated README and CLI tool documentation to reflect changes in port usage and configuration. - Enhanced various components to utilize the new port configuration, ensuring backward compatibility.
This commit is contained in:
@@ -23,7 +23,11 @@ SQLITE_MAX_SIZE_MB=2048
|
||||
SQLITE_CLEAN_LEGACY_FILES=true
|
||||
|
||||
# Recommended runtime variables
|
||||
# Canonical/base port (keeps backward compatibility)
|
||||
PORT=20128
|
||||
# Optional split ports:
|
||||
# API_PORT=20128
|
||||
# DASHBOARD_PORT=20128
|
||||
NODE_ENV=production
|
||||
INSTANCE_NAME=omniroute
|
||||
|
||||
|
||||
@@ -27,13 +27,14 @@ RUN mkdir -p /app/data
|
||||
COPY --from=builder /app/public ./public
|
||||
COPY --from=builder /app/.next/static ./.next/static
|
||||
COPY --from=builder /app/.next/standalone ./
|
||||
COPY --from=builder /app/scripts/run-standalone.mjs ./run-standalone.mjs
|
||||
|
||||
EXPOSE 20128
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
|
||||
CMD node -e "fetch('http://127.0.0.1:20128/api/settings').then(r=>{if(!r.ok)throw r.status}).catch(()=>process.exit(1))"
|
||||
CMD node -e "const p=process.env.DASHBOARD_PORT||process.env.PORT||'20128';fetch('http://127.0.0.1:'+p+'/api/settings').then(r=>{if(!r.ok)throw r.status}).catch(()=>process.exit(1))"
|
||||
|
||||
CMD ["node", "server.js"]
|
||||
CMD ["node", "run-standalone.mjs"]
|
||||
|
||||
FROM runner-base AS runner-cli
|
||||
|
||||
@@ -45,4 +46,3 @@ RUN apt-get update \
|
||||
|
||||
# Install CLI tools globally. Separate layer from apt for better cache reuse.
|
||||
RUN npm install -g --no-audit --no-fund @openai/codex @anthropic-ai/claude-code droid openclaw@latest
|
||||
|
||||
|
||||
31
README.md
31
README.md
@@ -168,12 +168,22 @@ omniroute
|
||||
|
||||
🎉 Dashboard opens at `http://localhost:20128`
|
||||
|
||||
| Command | Description |
|
||||
| ----------------------- | --------------------------------- |
|
||||
| `omniroute` | Start server (default port 20128) |
|
||||
| `omniroute --port 3000` | Use custom port |
|
||||
| `omniroute --no-open` | Don't auto-open browser |
|
||||
| `omniroute --help` | Show help |
|
||||
| Command | Description |
|
||||
| ----------------------- | ----------------------------------------------------------- |
|
||||
| `omniroute` | Start server (`PORT=20128`, API and dashboard on same port) |
|
||||
| `omniroute --port 3000` | Set canonical/API port to 3000 |
|
||||
| `omniroute --no-open` | Don't auto-open browser |
|
||||
| `omniroute --help` | Show help |
|
||||
|
||||
Optional split-port mode:
|
||||
|
||||
```bash
|
||||
PORT=20128 DASHBOARD_PORT=20129 omniroute
|
||||
# API: http://localhost:20128/v1
|
||||
# Dashboard: http://localhost:20129
|
||||
```
|
||||
|
||||
When ports are split, the API port serves only OpenAI-compatible routes (`/v1`, `/chat/completions`, `/responses`, `/models`, `/codex/*`).
|
||||
|
||||
**2. Connect a FREE provider:**
|
||||
|
||||
@@ -195,7 +205,7 @@ Claude Code/Codex/Gemini CLI/OpenClaw/Cursor/Cline Settings:
|
||||
```bash
|
||||
cp .env.example .env
|
||||
npm install
|
||||
PORT=20128 NEXT_PUBLIC_BASE_URL=http://localhost:20128 npm run dev
|
||||
PORT=20128 DASHBOARD_PORT=20129 NEXT_PUBLIC_BASE_URL=http://localhost:20129 npm run dev
|
||||
```
|
||||
|
||||
---
|
||||
@@ -978,9 +988,12 @@ Se não quiser criar credenciais próprias agora, ainda é possível usar o flux
|
||||
- Switch primary model to GLM/MiniMax
|
||||
- Use free tier (Gemini CLI, iFlow) for non-critical tasks
|
||||
|
||||
**Dashboard opens on wrong port**
|
||||
**Dashboard/API ports are wrong**
|
||||
|
||||
- Set `PORT=20128` and `NEXT_PUBLIC_BASE_URL=http://localhost:20128`
|
||||
- `PORT` is the canonical base port (and API port by default)
|
||||
- `API_PORT` overrides only OpenAI-compatible API listener
|
||||
- `DASHBOARD_PORT` overrides only dashboard/Next.js listener
|
||||
- Set `NEXT_PUBLIC_BASE_URL` to your dashboard/public URL (for OAuth callbacks)
|
||||
|
||||
**Cloud sync errors**
|
||||
|
||||
|
||||
@@ -30,18 +30,18 @@ if (args.includes("--help") || args.includes("-h")) {
|
||||
|
||||
\x1b[1mUsage:\x1b[0m
|
||||
omniroute Start the server
|
||||
omniroute --port <port> Use custom port (default: 20128)
|
||||
omniroute --port <port> Use custom API port (default: 20128)
|
||||
omniroute --no-open Don't open browser automatically
|
||||
omniroute --help Show this help
|
||||
omniroute --version Show version
|
||||
|
||||
\x1b[1mAfter starting:\x1b[0m
|
||||
Dashboard: http://localhost:<port>
|
||||
API: http://localhost:<port>/v1
|
||||
Dashboard: http://localhost:<dashboard-port>
|
||||
API: http://localhost:<api-port>/v1
|
||||
|
||||
\x1b[1mConnect your tools:\x1b[0m
|
||||
Set your CLI tool (Cursor, Cline, Codex, etc.) to use:
|
||||
\x1b[33mhttp://localhost:20128/v1\x1b[0m
|
||||
\x1b[33mhttp://localhost:<api-port>/v1\x1b[0m
|
||||
`);
|
||||
process.exit(0);
|
||||
}
|
||||
@@ -58,17 +58,26 @@ if (args.includes("--version") || args.includes("-v")) {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Parse --port
|
||||
let port = 20128;
|
||||
function parsePort(value, fallback) {
|
||||
const parsed = parseInt(String(value), 10);
|
||||
return Number.isFinite(parsed) && parsed > 0 && parsed <= 65535 ? parsed : fallback;
|
||||
}
|
||||
|
||||
// Parse --port (canonical/base port)
|
||||
let port = parsePort(process.env.PORT || "20128", 20128);
|
||||
const portIdx = args.indexOf("--port");
|
||||
if (portIdx !== -1 && args[portIdx + 1]) {
|
||||
port = parseInt(args[portIdx + 1], 10);
|
||||
if (isNaN(port)) {
|
||||
const parsed = parseInt(args[portIdx + 1], 10);
|
||||
if (isNaN(parsed)) {
|
||||
console.error("\x1b[31m✖ Invalid port number\x1b[0m");
|
||||
process.exit(1);
|
||||
}
|
||||
port = parsed;
|
||||
}
|
||||
|
||||
const apiPort = parsePort(process.env.API_PORT || String(port), port);
|
||||
const dashboardPort = parsePort(process.env.DASHBOARD_PORT || String(port), port);
|
||||
|
||||
const noOpen = args.includes("--no-open");
|
||||
|
||||
// ── Banner ─────────────────────────────────────────────────
|
||||
@@ -85,13 +94,8 @@ console.log(`
|
||||
const serverJs = join(APP_DIR, "server.js");
|
||||
|
||||
if (!existsSync(serverJs)) {
|
||||
console.error(
|
||||
"\x1b[31m✖ Server not found at:\x1b[0m",
|
||||
serverJs,
|
||||
);
|
||||
console.error(
|
||||
" This usually means the package was not built correctly.",
|
||||
);
|
||||
console.error("\x1b[31m✖ Server not found at:\x1b[0m", serverJs);
|
||||
console.error(" This usually means the package was not built correctly.");
|
||||
console.error(" Try reinstalling: npm install -g omniroute");
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -101,7 +105,10 @@ console.log(` \x1b[2m⏳ Starting server...\x1b[0m\n`);
|
||||
|
||||
const env = {
|
||||
...process.env,
|
||||
PORT: String(port),
|
||||
OMNIROUTE_PORT: String(port),
|
||||
PORT: String(dashboardPort),
|
||||
DASHBOARD_PORT: String(dashboardPort),
|
||||
API_PORT: String(apiPort),
|
||||
HOSTNAME: "0.0.0.0",
|
||||
NODE_ENV: "production",
|
||||
};
|
||||
@@ -119,7 +126,10 @@ server.stdout.on("data", (data) => {
|
||||
process.stdout.write(text);
|
||||
|
||||
// Detect server ready
|
||||
if (!started && (text.includes("Ready") || text.includes("started") || text.includes("listening"))) {
|
||||
if (
|
||||
!started &&
|
||||
(text.includes("Ready") || text.includes("started") || text.includes("listening"))
|
||||
) {
|
||||
started = true;
|
||||
onReady();
|
||||
}
|
||||
@@ -156,16 +166,17 @@ process.on("SIGTERM", shutdown);
|
||||
|
||||
// ── On ready ───────────────────────────────────────────────
|
||||
async function onReady() {
|
||||
const url = `http://localhost:${port}`;
|
||||
const dashboardUrl = `http://localhost:${dashboardPort}`;
|
||||
const apiUrl = `http://localhost:${apiPort}`;
|
||||
|
||||
console.log(`
|
||||
\x1b[32m✔ OmniRoute is running!\x1b[0m
|
||||
|
||||
\x1b[1m Dashboard:\x1b[0m ${url}
|
||||
\x1b[1m API Base:\x1b[0m ${url}/v1
|
||||
\x1b[1m Dashboard:\x1b[0m ${dashboardUrl}
|
||||
\x1b[1m API Base:\x1b[0m ${apiUrl}/v1
|
||||
|
||||
\x1b[2m Point your CLI tool (Cursor, Cline, Codex) to:\x1b[0m
|
||||
\x1b[33m ${url}/v1\x1b[0m
|
||||
\x1b[33m ${apiUrl}/v1\x1b[0m
|
||||
|
||||
\x1b[2m Press Ctrl+C to stop\x1b[0m
|
||||
`);
|
||||
@@ -173,7 +184,7 @@ async function onReady() {
|
||||
if (!noOpen) {
|
||||
try {
|
||||
const open = await import("open");
|
||||
await open.default(url);
|
||||
await open.default(dashboardUrl);
|
||||
} catch {
|
||||
// open is optional — if not available, just skip
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ services:
|
||||
"CMD",
|
||||
"node",
|
||||
"-e",
|
||||
"fetch('http://127.0.0.1:20128/api/settings').then(r=>{if(!r.ok)throw r.status}).catch(()=>process.exit(1))",
|
||||
"const p=process.env.DASHBOARD_PORT||process.env.PORT||'20128';fetch('http://127.0.0.1:'+p+'/api/settings').then(r=>{if(!r.ok)throw r.status}).catch(()=>process.exit(1))",
|
||||
]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
|
||||
@@ -28,7 +28,7 @@ x-common: &common
|
||||
"CMD",
|
||||
"node",
|
||||
"-e",
|
||||
"fetch('http://127.0.0.1:20128/api/settings').then(r=>{if(!r.ok)throw r.status}).catch(()=>process.exit(1))",
|
||||
"const p=process.env.DASHBOARD_PORT||process.env.PORT||'20128';fetch('http://127.0.0.1:'+p+'/api/settings').then(r=>{if(!r.ok)throw r.status}).catch(()=>process.exit(1))",
|
||||
]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
|
||||
@@ -42,10 +42,10 @@
|
||||
},
|
||||
"homepage": "https://omniroute.online",
|
||||
"scripts": {
|
||||
"dev": "next dev --webpack --port 20128",
|
||||
"dev": "OMNIROUTE_PORT=${PORT:-20128} next dev --webpack --port ${DASHBOARD_PORT:-${PORT:-20128}}",
|
||||
"build": "next build --webpack",
|
||||
"build:cli": "node scripts/prepublish.mjs",
|
||||
"start": "next start --port 20128",
|
||||
"start": "OMNIROUTE_PORT=${PORT:-20128} next start --port ${DASHBOARD_PORT:-${PORT:-20128}}",
|
||||
"lint": "eslint .",
|
||||
"test": "node --test tests/unit/*.test.mjs",
|
||||
"test:unit": "node --import tsx/esm --test tests/unit/*.test.mjs",
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { defineConfig, devices } from "@playwright/test";
|
||||
|
||||
const dashboardPort = process.env.DASHBOARD_PORT || process.env.PORT || "20128";
|
||||
const dashboardBaseUrl = `http://localhost:${dashboardPort}`;
|
||||
|
||||
export default defineConfig({
|
||||
testDir: "./tests/e2e",
|
||||
fullyParallel: true,
|
||||
@@ -8,7 +11,7 @@ export default defineConfig({
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
reporter: process.env.CI ? "github" : "html",
|
||||
use: {
|
||||
baseURL: "http://localhost:20128",
|
||||
baseURL: dashboardBaseUrl,
|
||||
trace: "on-first-retry",
|
||||
screenshot: "only-on-failure",
|
||||
},
|
||||
@@ -20,7 +23,7 @@ export default defineConfig({
|
||||
],
|
||||
webServer: {
|
||||
command: process.env.CI ? "npm start" : "npm run dev",
|
||||
url: "http://localhost:20128",
|
||||
url: dashboardBaseUrl,
|
||||
reuseExistingServer: !process.env.CI,
|
||||
timeout: 120_000,
|
||||
},
|
||||
|
||||
34
scripts/run-standalone.mjs
Normal file
34
scripts/run-standalone.mjs
Normal file
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { spawn } from "node:child_process";
|
||||
|
||||
function parsePort(value, fallback) {
|
||||
const parsed = Number.parseInt(String(value), 10);
|
||||
return Number.isFinite(parsed) && parsed > 0 && parsed <= 65535 ? parsed : fallback;
|
||||
}
|
||||
|
||||
const basePort = parsePort(process.env.PORT || "20128", 20128);
|
||||
const apiPort = parsePort(process.env.API_PORT || String(basePort), basePort);
|
||||
const dashboardPort = parsePort(process.env.DASHBOARD_PORT || String(basePort), basePort);
|
||||
|
||||
const child = spawn("node", ["server.js"], {
|
||||
stdio: "inherit",
|
||||
env: {
|
||||
...process.env,
|
||||
OMNIROUTE_PORT: String(basePort),
|
||||
PORT: String(dashboardPort),
|
||||
DASHBOARD_PORT: String(dashboardPort),
|
||||
API_PORT: String(apiPort),
|
||||
},
|
||||
});
|
||||
|
||||
child.on("exit", (code, signal) => {
|
||||
if (signal) {
|
||||
process.kill(process.pid, signal);
|
||||
return;
|
||||
}
|
||||
process.exit(code ?? 0);
|
||||
});
|
||||
|
||||
process.on("SIGINT", () => child.kill("SIGINT"));
|
||||
process.on("SIGTERM", () => child.kill("SIGTERM"));
|
||||
@@ -30,6 +30,7 @@ export default function CLIToolsPageClient({ machineId }) {
|
||||
const [apiKeys, setApiKeys] = useState([]);
|
||||
const [toolStatuses, setToolStatuses] = useState({});
|
||||
const [statusesLoaded, setStatusesLoaded] = useState(false);
|
||||
const [apiBaseUrl, setApiBaseUrl] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
fetchConnections();
|
||||
@@ -44,6 +45,12 @@ export default function CLIToolsPageClient({ machineId }) {
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
setCloudEnabled(data.cloudEnabled || false);
|
||||
if (typeof window !== "undefined") {
|
||||
const protocol = window.location.protocol;
|
||||
const hostname = window.location.hostname;
|
||||
const apiPort = data?.apiPort || 20128;
|
||||
setApiBaseUrl(`${protocol}//${hostname}:${apiPort}`);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Error loading cloud settings:", error);
|
||||
@@ -141,6 +148,9 @@ export default function CLIToolsPageClient({ machineId }) {
|
||||
if (cloudEnabled && CLOUD_URL) {
|
||||
return CLOUD_URL;
|
||||
}
|
||||
if (apiBaseUrl) {
|
||||
return apiBaseUrl;
|
||||
}
|
||||
if (typeof window !== "undefined") {
|
||||
return window.location.origin;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ export default function OnboardingWizard() {
|
||||
const router = useRouter();
|
||||
const [step, setStep] = useState(0);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [apiEndpoint, setApiEndpoint] = useState("http://localhost:20128/api/v1");
|
||||
|
||||
// Security step state
|
||||
const [password, setPassword] = useState("");
|
||||
@@ -42,11 +43,20 @@ export default function OnboardingWizard() {
|
||||
|
||||
// Check if setup is already complete
|
||||
useEffect(() => {
|
||||
const resolveApiEndpoint = (apiPort) => {
|
||||
if (typeof window === "undefined") return;
|
||||
const protocol = window.location.protocol;
|
||||
const hostname = window.location.hostname;
|
||||
const effectiveApiPort = apiPort || 20128;
|
||||
setApiEndpoint(`${protocol}//${hostname}:${effectiveApiPort}/api/v1`);
|
||||
};
|
||||
|
||||
const checkSetup = async () => {
|
||||
try {
|
||||
const res = await fetch("/api/settings");
|
||||
if (res.ok) {
|
||||
const settings = await res.json();
|
||||
resolveApiEndpoint(settings?.apiPort);
|
||||
if (settings.setupComplete) {
|
||||
router.replace("/dashboard");
|
||||
return;
|
||||
@@ -396,7 +406,7 @@ export default function OnboardingWizard() {
|
||||
</p>
|
||||
<div className="bg-white/[0.03] rounded-xl p-4 border border-white/[0.06] text-left">
|
||||
<p className="text-xs text-text-muted mb-2 font-medium">Your endpoint:</p>
|
||||
<code className="text-sm text-primary">http://localhost:20128/api/v1</code>
|
||||
<code className="text-sm text-primary">{apiEndpoint}</code>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -68,9 +68,7 @@ async function saveContinueConfig({ baseUrl, apiKey, model }) {
|
||||
const existingIdx = models.findIndex(
|
||||
(m) =>
|
||||
m.apiBase &&
|
||||
(m.apiBase.includes("localhost:20128") ||
|
||||
m.apiBase.includes("omniroute") ||
|
||||
m.title === model)
|
||||
(m.apiBase.includes("localhost") || m.apiBase.includes("omniroute") || m.title === model)
|
||||
);
|
||||
|
||||
if (existingIdx >= 0) {
|
||||
|
||||
@@ -33,7 +33,7 @@ async function checkToolConfigStatus(toolId: string): Promise<string> {
|
||||
case "kilo":
|
||||
// Generic check: look for any OmniRoute-related URL in the config
|
||||
const configStr = JSON.stringify(config).toLowerCase();
|
||||
return configStr.includes("omniroute") || configStr.includes("20128")
|
||||
return configStr.includes("omniroute") || configStr.includes("localhost")
|
||||
? "configured"
|
||||
: "not_configured";
|
||||
default:
|
||||
|
||||
@@ -2,6 +2,7 @@ import { NextResponse } from "next/server";
|
||||
import { getSettings, updateSettings } from "@/lib/localDb";
|
||||
import bcrypt from "bcryptjs";
|
||||
import { updateSettingsSchema, validateBody } from "@/shared/validation/schemas";
|
||||
import { getRuntimePorts } from "@/lib/runtime/ports";
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
@@ -9,11 +10,15 @@ export async function GET() {
|
||||
const { password, ...safeSettings } = settings;
|
||||
|
||||
const enableRequestLogs = process.env.ENABLE_REQUEST_LOGS === "true";
|
||||
const runtimePorts = getRuntimePorts();
|
||||
|
||||
return NextResponse.json({
|
||||
...safeSettings,
|
||||
enableRequestLogs,
|
||||
hasPassword: !!password || !!process.env.INITIAL_PASSWORD,
|
||||
runtimePorts,
|
||||
apiPort: runtimePorts.apiPort,
|
||||
dashboardPort: runtimePorts.dashboardPort,
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("Error getting settings:", error);
|
||||
|
||||
@@ -29,6 +29,9 @@ export async function register() {
|
||||
const { initGracefulShutdown } = await import("@/lib/gracefulShutdown");
|
||||
initGracefulShutdown();
|
||||
|
||||
const { initApiBridgeServer } = await import("@/lib/apiBridgeServer");
|
||||
initApiBridgeServer();
|
||||
|
||||
// Compliance: Initialize audit_log table + cleanup expired logs
|
||||
try {
|
||||
const { initAuditLog, cleanupExpiredLogs } = await import("@/lib/compliance/index");
|
||||
|
||||
91
src/lib/apiBridgeServer.ts
Normal file
91
src/lib/apiBridgeServer.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import http from "node:http";
|
||||
import type { IncomingMessage, ServerResponse } from "node:http";
|
||||
import { getRuntimePorts } from "@/lib/runtime/ports";
|
||||
|
||||
const OPENAI_COMPAT_PATHS = [
|
||||
/^\/v1(?:\/|$)/,
|
||||
/^\/chat\/completions(?:\?|$)/,
|
||||
/^\/responses(?:\?|$)/,
|
||||
/^\/models(?:\?|$)/,
|
||||
/^\/codex(?:\/|\?|$)/,
|
||||
];
|
||||
|
||||
function isOpenAiCompatiblePath(pathname: string): boolean {
|
||||
return OPENAI_COMPAT_PATHS.some((pattern) => pattern.test(pathname));
|
||||
}
|
||||
|
||||
function proxyRequest(req: IncomingMessage, res: ServerResponse, dashboardPort: number): void {
|
||||
const targetReq = http.request(
|
||||
{
|
||||
hostname: "127.0.0.1",
|
||||
port: dashboardPort,
|
||||
method: req.method,
|
||||
path: req.url,
|
||||
headers: {
|
||||
...req.headers,
|
||||
host: `127.0.0.1:${dashboardPort}`,
|
||||
},
|
||||
},
|
||||
(targetRes) => {
|
||||
res.writeHead(targetRes.statusCode || 502, targetRes.headers);
|
||||
targetRes.pipe(res);
|
||||
}
|
||||
);
|
||||
|
||||
targetReq.on("error", (error) => {
|
||||
if (res.headersSent) return;
|
||||
res.writeHead(502, { "content-type": "application/json" });
|
||||
res.end(
|
||||
JSON.stringify({ error: "api_bridge_unavailable", detail: String(error.message || error) })
|
||||
);
|
||||
});
|
||||
|
||||
req.pipe(targetReq);
|
||||
}
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line no-var
|
||||
var __omnirouteApiBridgeStarted: boolean | undefined;
|
||||
}
|
||||
|
||||
export function initApiBridgeServer(): void {
|
||||
if (globalThis.__omnirouteApiBridgeStarted) return;
|
||||
|
||||
const { apiPort, dashboardPort } = getRuntimePorts();
|
||||
if (apiPort === dashboardPort) return;
|
||||
|
||||
const host = process.env.HOSTNAME || "0.0.0.0";
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
const rawUrl = req.url || "/";
|
||||
const pathname = rawUrl.split("?")[0] || "/";
|
||||
|
||||
if (!isOpenAiCompatiblePath(pathname)) {
|
||||
res.writeHead(404, { "content-type": "application/json" });
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
error: "not_found",
|
||||
message: "API port only serves OpenAI-compatible routes.",
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
proxyRequest(req, res, dashboardPort);
|
||||
});
|
||||
|
||||
server.on("error", (error: NodeJS.ErrnoException) => {
|
||||
if (error?.code === "EADDRINUSE") {
|
||||
console.warn(
|
||||
`[API Bridge] Port ${apiPort} is already in use. API bridge disabled. (dashboard: ${dashboardPort})`
|
||||
);
|
||||
return;
|
||||
}
|
||||
console.warn("[API Bridge] Failed to start:", error?.message || error);
|
||||
});
|
||||
|
||||
server.listen(apiPort, host, () => {
|
||||
globalThis.__omnirouteApiBridgeStarted = true;
|
||||
console.log(`[API Bridge] Listening on ${host}:${apiPort} -> dashboard:${dashboardPort}`);
|
||||
});
|
||||
}
|
||||
@@ -11,13 +11,21 @@ interface ServerCredentials {
|
||||
userId: string;
|
||||
}
|
||||
|
||||
function getDefaultApiServer() {
|
||||
const basePort = Number.parseInt(process.env.OMNIROUTE_PORT || process.env.PORT || "20128", 10);
|
||||
const fallbackPort = Number.isFinite(basePort) ? basePort : 20128;
|
||||
const apiPort = Number.parseInt(process.env.API_PORT || String(fallbackPort), 10);
|
||||
const effectivePort = Number.isFinite(apiPort) ? apiPort : fallbackPort;
|
||||
return `http://localhost:${effectivePort}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get server credentials from environment variables.
|
||||
* Used by OAuth CLI services to save tokens to the running server.
|
||||
*/
|
||||
export function getServerCredentials(): ServerCredentials {
|
||||
return {
|
||||
server: process.env.OMNIROUTE_SERVER || process.env.SERVER_URL || "http://localhost:20128",
|
||||
server: process.env.OMNIROUTE_SERVER || process.env.SERVER_URL || getDefaultApiServer(),
|
||||
token: process.env.OMNIROUTE_TOKEN || process.env.CLI_TOKEN || "",
|
||||
userId: process.env.OMNIROUTE_USER_ID || process.env.CLI_USER_ID || "cli",
|
||||
};
|
||||
|
||||
32
src/lib/runtime/ports.ts
Normal file
32
src/lib/runtime/ports.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
const DEFAULT_PORT = 20128;
|
||||
|
||||
function parsePort(value: string | undefined, fallback: number): number {
|
||||
if (!value) return fallback;
|
||||
const parsed = Number.parseInt(String(value), 10);
|
||||
if (!Number.isFinite(parsed) || parsed < 1 || parsed > 65535) return fallback;
|
||||
return parsed;
|
||||
}
|
||||
|
||||
export type RuntimePorts = {
|
||||
port: number;
|
||||
apiPort: number;
|
||||
dashboardPort: number;
|
||||
apiPortExplicit: boolean;
|
||||
dashboardPortExplicit: boolean;
|
||||
};
|
||||
|
||||
export function getRuntimePorts(): RuntimePorts {
|
||||
// OMNIROUTE_PORT preserves the user's canonical PORT in wrapped runtimes
|
||||
// where Next.js requires process.env.PORT to be the dashboard listener port.
|
||||
const basePort = parsePort(process.env.OMNIROUTE_PORT || process.env.PORT, DEFAULT_PORT);
|
||||
const apiPortExplicit = !!process.env.API_PORT;
|
||||
const dashboardPortExplicit = !!process.env.DASHBOARD_PORT;
|
||||
|
||||
return {
|
||||
port: basePort,
|
||||
apiPort: parsePort(process.env.API_PORT, basePort),
|
||||
dashboardPort: parsePort(process.env.DASHBOARD_PORT, basePort),
|
||||
apiPortExplicit,
|
||||
dashboardPortExplicit,
|
||||
};
|
||||
}
|
||||
@@ -1,11 +1,14 @@
|
||||
import { getConsistentMachineId } from "@/shared/utils/machineId";
|
||||
import { isCloudEnabled } from "@/lib/localDb";
|
||||
import { getRuntimePorts } from "@/lib/runtime/ports";
|
||||
|
||||
const { dashboardPort } = getRuntimePorts();
|
||||
|
||||
const INTERNAL_BASE_URL =
|
||||
process.env.BASE_URL ||
|
||||
process.env.NEXT_PUBLIC_BASE_URL ||
|
||||
process.env.NEXT_PUBLIC_APP_URL ||
|
||||
"http://localhost:20128";
|
||||
`http://localhost:${dashboardPort}`;
|
||||
|
||||
/**
|
||||
* Cloud sync scheduler
|
||||
|
||||
3
src/types/global.d.ts
vendored
3
src/types/global.d.ts
vendored
@@ -13,6 +13,9 @@ declare namespace NodeJS {
|
||||
PROMPT_CACHE_MAX_SIZE?: string;
|
||||
PROMPT_CACHE_TTL_MS?: string;
|
||||
NEXT_PUBLIC_CLOUD_URL?: string;
|
||||
API_PORT?: string;
|
||||
DASHBOARD_PORT?: string;
|
||||
OMNIROUTE_PORT?: string;
|
||||
NODE_ENV?: "development" | "production" | "test";
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user