feat: enhance runtime port management and configuration

- Updated .env.example to include optional production ports for API and dashboard.
- Modified docker-compose files to utilize dynamic port configuration.
- Introduced runtime-env.mjs for centralized port resolution and environment variable management.
- Refactored run-next.mjs and run-standalone.mjs to leverage new runtime port handling.
- Enhanced route.ts and apiBridgeServer.ts to utilize dynamic ports for improved API integration.
- Updated OAuth configuration to reflect changes in port management.
This commit is contained in:
Steven Rafferty
2026-02-26 15:43:02 +00:00
parent b941515c5a
commit 344e602b26
11 changed files with 106 additions and 75 deletions

View File

@@ -26,9 +26,12 @@ SQLITE_CLEAN_LEGACY_FILES=true
# Canonical/base port (keeps backward compatibility)
PORT=20128
# Optional split ports:
# API_PORT=20128
# API_PORT=20129
# API_HOST=0.0.0.0
# DASHBOARD_PORT=20128
# Optional Docker production host publish ports:
# PROD_DASHBOARD_PORT=20130
# PROD_API_PORT=20131
NODE_ENV=production
INSTANCE_NAME=omniroute

View File

@@ -28,6 +28,7 @@ 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
COPY --from=builder /app/scripts/runtime-env.mjs ./runtime-env.mjs
EXPOSE 20128

View File

@@ -67,12 +67,12 @@ function parsePort(value, fallback) {
let port = parsePort(process.env.PORT || "20128", 20128);
const portIdx = args.indexOf("--port");
if (portIdx !== -1 && args[portIdx + 1]) {
const parsed = parseInt(args[portIdx + 1], 10);
if (isNaN(parsed)) {
const cliPort = parsePort(args[portIdx + 1], null);
if (cliPort === null) {
console.error("\x1b[31m✖ Invalid port number\x1b[0m");
process.exit(1);
}
port = parsed;
port = cliPort;
}
const apiPort = parsePort(process.env.API_PORT || String(port), port);

View File

@@ -22,11 +22,15 @@ services:
env_file: .env
environment:
- NODE_ENV=production
- PORT=20128
- PORT=${PORT:-20128}
- DASHBOARD_PORT=${DASHBOARD_PORT:-${PORT:-20128}}
- API_PORT=${API_PORT:-20129}
- API_HOST=${API_HOST:-0.0.0.0}
- HOSTNAME=0.0.0.0
- DATA_DIR=/app/data
ports:
- "20130:20128"
- "${PROD_DASHBOARD_PORT:-20130}:${DASHBOARD_PORT:-${PORT:-20128}}"
- "${PROD_API_PORT:-20131}:${API_PORT:-20129}"
volumes:
- omniroute-prod-data:/app/data
healthcheck:

View File

@@ -20,6 +20,10 @@ x-common: &common
env_file: .env
environment:
- DATA_DIR=/app/data # Must match the volume mount below
- PORT=${PORT:-20128}
- DASHBOARD_PORT=${DASHBOARD_PORT:-${PORT:-20128}}
- API_PORT=${API_PORT:-20129}
- API_HOST=${API_HOST:-0.0.0.0}
volumes:
- omniroute-data:/app/data
healthcheck:
@@ -45,7 +49,8 @@ services:
target: runner-base
image: omniroute:base
ports:
- "${PORT:-20128}:20128"
- "${DASHBOARD_PORT:-${PORT:-20128}}:${DASHBOARD_PORT:-${PORT:-20128}}"
- "${API_PORT:-20129}:${API_PORT:-20129}"
profiles:
- base
@@ -58,7 +63,8 @@ services:
target: runner-cli
image: omniroute:cli
ports:
- "${PORT:-20128}:20128"
- "${DASHBOARD_PORT:-${PORT:-20128}}:${DASHBOARD_PORT:-${PORT:-20128}}"
- "${API_PORT:-20129}:${API_PORT:-20129}"
profiles:
- cli
@@ -71,8 +77,14 @@ services:
target: runner-base
image: omniroute:base
ports:
- "${PORT:-20128}:20128"
- "${DASHBOARD_PORT:-${PORT:-20128}}:${DASHBOARD_PORT:-${PORT:-20128}}"
- "${API_PORT:-20129}:${API_PORT:-20129}"
environment:
- DATA_DIR=/app/data
- PORT=${PORT:-20128}
- DASHBOARD_PORT=${DASHBOARD_PORT:-${PORT:-20128}}
- API_PORT=${API_PORT:-20129}
- API_HOST=${API_HOST:-0.0.0.0}
- CLI_MODE=host
- CLI_EXTRA_PATHS=/host-local/bin:/host-node/bin
- CLI_CONFIG_HOME=/host-home

View File

@@ -1,41 +1,22 @@
#!/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;
}
import {
resolveRuntimePorts,
withRuntimePortEnv,
spawnWithForwardedSignals,
} from "./runtime-env.mjs";
const mode = process.argv[2] === "start" ? "start" : "dev";
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 runtimePorts = resolveRuntimePorts();
const { dashboardPort } = runtimePorts;
const args = ["./node_modules/next/dist/bin/next", mode, "--port", String(dashboardPort)];
if (mode === "dev") {
args.splice(2, 0, "--webpack");
}
const child = spawn(process.execPath, args, {
spawnWithForwardedSignals(process.execPath, args, {
stdio: "inherit",
env: {
...process.env,
OMNIROUTE_PORT: String(basePort),
PORT: String(dashboardPort),
DASHBOARD_PORT: String(dashboardPort),
API_PORT: String(apiPort),
},
env: withRuntimePortEnv(process.env, runtimePorts),
});
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"));

View File

@@ -1,34 +1,14 @@
#!/usr/bin/env node
import { spawn } from "node:child_process";
import {
resolveRuntimePorts,
withRuntimePortEnv,
spawnWithForwardedSignals,
} from "./runtime-env.mjs";
function parsePort(value, fallback) {
const parsed = Number.parseInt(String(value), 10);
return Number.isFinite(parsed) && parsed > 0 && parsed <= 65535 ? parsed : fallback;
}
const runtimePorts = resolveRuntimePorts();
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"], {
spawnWithForwardedSignals("node", ["server.js"], {
stdio: "inherit",
env: {
...process.env,
OMNIROUTE_PORT: String(basePort),
PORT: String(dashboardPort),
DASHBOARD_PORT: String(dashboardPort),
API_PORT: String(apiPort),
},
env: withRuntimePortEnv(process.env, runtimePorts),
});
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"));

43
scripts/runtime-env.mjs Normal file
View File

@@ -0,0 +1,43 @@
import { spawn } from "node:child_process";
export function parsePort(value, fallback) {
const parsed = Number.parseInt(String(value), 10);
return Number.isFinite(parsed) && parsed > 0 && parsed <= 65535 ? parsed : fallback;
}
export function resolveRuntimePorts() {
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);
return { basePort, apiPort, dashboardPort };
}
export function withRuntimePortEnv(env, runtimePorts) {
const { basePort, apiPort, dashboardPort } = runtimePorts;
return {
...env,
OMNIROUTE_PORT: String(basePort),
PORT: String(dashboardPort),
DASHBOARD_PORT: String(dashboardPort),
API_PORT: String(apiPort),
};
}
export function spawnWithForwardedSignals(command, args, options = {}) {
const child = spawn(command, args, options);
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"));
return child;
}

View File

@@ -2,6 +2,7 @@ import { NextResponse } from "next/server";
import fs from "fs/promises";
import path from "path";
import os from "os";
import { getRuntimePorts } from "@/lib/runtime/ports";
/**
* POST /api/cli-tools/guide-settings/:toolId
@@ -37,6 +38,7 @@ export async function POST(request, { params }) {
* Merges with existing config if present.
*/
async function saveContinueConfig({ baseUrl, apiKey, model }) {
const { apiPort } = getRuntimePorts();
const configPath = path.join(os.homedir(), ".continue", "config.json");
const configDir = path.dirname(configPath);
@@ -82,8 +84,8 @@ async function saveContinueConfig({ baseUrl, apiKey, model }) {
(m.omnirouteManaged === true ||
normalizeApiBase(m.apiBase) === normalizedBaseUrl.toLowerCase() ||
normalizeApiBase(m.apiBase).includes("omniroute") ||
normalizeApiBase(m.apiBase).includes("localhost:20128") ||
normalizeApiBase(m.apiBase).includes("127.0.0.1:20128") ||
normalizeApiBase(m.apiBase).includes(`localhost:${apiPort}`) ||
normalizeApiBase(m.apiBase).includes(`127.0.0.1:${apiPort}`) ||
String(m.apiKey || "")
.toLowerCase()
.includes("sk_omniroute"))

View File

@@ -36,15 +36,21 @@ function proxyRequest(req: IncomingMessage, res: ServerResponse, dashboardPort:
if (res.headersSent) return;
res.writeHead(502, { "content-type": "application/json" });
res.end(
JSON.stringify({ error: "api_bridge_unavailable", detail: String(error.message || error) })
JSON.stringify({
error: "api_bridge_unavailable",
detail: String(error.message || error),
})
);
});
req.on("aborted", () => {
targetReq.destroy();
});
req.pipe(targetReq);
}
declare global {
var __omnirouteApiBridgeStarted: boolean | undefined;
}
@@ -54,7 +60,7 @@ export function initApiBridgeServer(): void {
const { apiPort, dashboardPort } = getRuntimePorts();
if (apiPort === dashboardPort) return;
const host = process.env.API_HOST || "0.0.0.0";
const host = process.env.API_HOST || "127.0.0.1";
const server = http.createServer((req, res) => {
const rawUrl = req.url || "/";

View File

@@ -5,6 +5,8 @@
* with the running OmniRoute server when saving tokens.
*/
import { getRuntimePorts } from "@/lib/runtime/ports";
interface ServerCredentials {
server: string;
token: string;
@@ -12,11 +14,8 @@ interface ServerCredentials {
}
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}`;
const { dashboardPort } = getRuntimePorts();
return `http://localhost:${dashboardPort}`;
}
/**