diff --git a/.env.example b/.env.example index 3205c40..b6c0ae7 100644 --- a/.env.example +++ b/.env.example @@ -3,7 +3,14 @@ DATABASE_URL=postgres://openbot:openbot@localhost:5432/openbot # development. Production refuses to start with this key. # openssl rand -base64 32 KEY_ENCRYPTION_KEY=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= -PORT=3001 +# Where the API listens. SERVER_PORT is the name start.sh, Vite, and the ports table already use. +# PORT is the older alias; the process still honours it when SERVER_PORT is unset, so an existing +# .env that only sets PORT keeps working. +SERVER_PORT=3001 +# PORT=3001 +# +# Where the app is served. start.sh and Vite both read this; the default is 3010. +# APP_PORT=3010 TENANT_PACKAGE_DIR=../examples/fintech # What this deployment calls itself, when more than one shares an Intelligence project. A copy of a # deployment made for development uses the same project key, and threads are listed per Bot with diff --git a/app/vite.config.ts b/app/vite.config.ts index 9ec5e21..31fa717 100644 --- a/app/vite.config.ts +++ b/app/vite.config.ts @@ -4,6 +4,9 @@ import { tanstackRouter } from "@tanstack/router-plugin/vite"; import react from "@vitejs/plugin-react"; import { defineConfig } from "vite"; +const apiPort = + process.env.SERVER_PORT?.trim() || process.env.PORT?.trim() || "3001"; + export default defineConfig({ plugins: [tanstackRouter(), react(), tailwindcss()], resolve: { @@ -18,7 +21,7 @@ export default defineConfig({ // `ws: true` is required for the live screen. Without it Vite answers the upgrade request with // the app's HTML and the socket fails with an opaque error that looks like a server problem. "/api": { - target: `http://localhost:${process.env.SERVER_PORT ?? "3001"}`, + target: `http://localhost:${apiPort}`, ws: true, }, }, diff --git a/docs/configuration.md b/docs/configuration.md index 0657fb2..791da46 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -32,7 +32,7 @@ All four Intelligence values are required together. Missing any of them stops se | Variable | Default | Meaning | | -------------------- | ---------------------------------- | ------------------------------------------------------------------- | -| `PORT` | `3001` | API server port. | +| `SERVER_PORT` | `3001` | API server port. `PORT` is the same setting when `SERVER_PORT` is unset. | | `NODE_ENV` | unset | `production` enables startup refusals for local-only settings. | | `TENANT_PACKAGE_DIR` | `../examples/fintech` | Tenant package directory, resolved from `server/`. | | `DEPLOYMENT_ID` | the tenant package's id | Names this deployment inside a shared Intelligence project. | diff --git a/scripts/start.sh b/scripts/start.sh index 5f8ad63..75f185f 100755 --- a/scripts/start.sh +++ b/scripts/start.sh @@ -26,7 +26,9 @@ setting() { } APP_PORT="$(setting APP_PORT 3010)" -SERVER_PORT="$(setting SERVER_PORT 3001)" +# The documented name first, then PORT, matching the API process. An existing .env that only +# renamed PORT would otherwise have start.sh health-checking 3001 while the server listened elsewhere. +SERVER_PORT="$(setting SERVER_PORT "$(setting PORT 3001)")" COMPUTER_PORT="$(setting COMPUTER_PORT 4100)" BOT_PORT="$(setting BOT_PORT 4200)" LANGGRAPH_PORT="$(setting LANGGRAPH_PORT 4201)" diff --git a/server/src/index.ts b/server/src/index.ts index 3fc067a..4506c48 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -37,6 +37,7 @@ import { resolveModelApiKey, } from "./credentials"; import { createDatabase } from "./db/client"; +import { resolveListenPort } from "./listen-port"; import { createPluginStore } from "./plugins/store"; import { createPackageStatusReader, @@ -103,7 +104,7 @@ const identifyActor: IdentifyActor = async (request) => { }; const config = loadConfig(); -const port = Number.parseInt(process.env.PORT ?? "3001", 10); +const port = resolveListenPort(); const database = createDatabase(config.databaseUrl); await initializeDevActorUser(database, config.devNoAuth); // The vault, built before the agent store because a customer's agent may sit behind a key and that diff --git a/server/src/listen-port.ts b/server/src/listen-port.ts new file mode 100644 index 0000000..1a61f36 --- /dev/null +++ b/server/src/listen-port.ts @@ -0,0 +1,16 @@ +/** + * Where this process should listen. + * + * The ports table, `scripts/start.sh`, and the Vite `/api` proxy all name `SERVER_PORT`. The process + * used to read only `PORT`, which is what `.env.example` used to set, so a clone that followed the + * docs moved the proxy and the health check but not the listener. + * + * `SERVER_PORT` first, then `PORT`, then 3001. Existing files that only set `PORT` keep working. + */ +export function resolveListenPort( + environment: Record = process.env, +): number { + const raw = + environment.SERVER_PORT?.trim() || environment.PORT?.trim() || "3001"; + return Number.parseInt(raw, 10); +} diff --git a/server/tests/listen-port.test.ts b/server/tests/listen-port.test.ts new file mode 100644 index 0000000..a744d25 --- /dev/null +++ b/server/tests/listen-port.test.ts @@ -0,0 +1,25 @@ +import { describe, expect, test } from "bun:test"; +import { resolveListenPort } from "../src/listen-port"; + +describe("API listen port", () => { + test("defaults to 3001", () => { + expect(resolveListenPort({})).toBe(3001); + }); + + test("honours PORT, which existing .env files already set", () => { + expect(resolveListenPort({ PORT: "4001" })).toBe(4001); + }); + + test("honours SERVER_PORT, which is the name the docs and start.sh use", () => { + expect(resolveListenPort({ SERVER_PORT: "4002" })).toBe(4002); + }); + + test("prefers SERVER_PORT when both are set, so the documented name actually moves the listener", () => { + expect(resolveListenPort({ SERVER_PORT: "4002", PORT: "3001" })).toBe(4002); + }); + + test("treats blank values as unset", () => { + expect(resolveListenPort({ SERVER_PORT: " ", PORT: "4001" })).toBe(4001); + expect(resolveListenPort({ SERVER_PORT: "", PORT: "" })).toBe(3001); + }); +});