From e72cae483067aba96d6e6e9f8f448bac63071804 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Wed, 12 Aug 2026 23:38:15 +0000 Subject: [PATCH] one default port, and it is 9000 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every PORT fallback in the tree now says 9000. There were three answers to one question, each defensible where it was written and none of them visible from the others: server.tsx and 19 sidecars 5000 a default from before there was an installer user-instance.ts 9010 what scripts/setup-old/setup.sh really wrote .env.example 9000 what we told people to write 5000 goes first because macOS binds it — AirPlay Receiver has owned it since Monterey, so a dev server there fails to bind or gets shadowed by something that answers. All 22 sites moved together, which is the point. Changing the app alone would have turned a consistent-but-wrong default into a split one: the app on 9000 while nineteen sidecars still dialled 5000. 9010 was the interesting one. It was the only value that ever matched a real machine, because it is what the old installer wrote — and it was in the single file whose disagreement would have broken chat alone, with nothing else looking wrong. Its own comment records the same bug being fixed once already, within the file, by a change that left it disagreeing with everything outside it. Note what these defaults actually are: the sidecars bind nothing. user-instance.ts has no listener at all — it builds ws:// and http:// URLs that both address the app's single listener. So every one of these numbers is a guess at where the app is, for a value that .env always supplies. Worth removing rather than aligning, which is a separate change. Not typechecked (empty node_modules, frozen installs). Every edited file parses under `bun build --no-bundle`; the pm2 profile loads. Co-Authored-By: Claude Opus 5 (1M context) --- ecosystem-files/ecosystem.profile.cjs | 2 +- src/server.tsx | 2 +- src/servers/api/chat/agent-panels-routes.ts | 2 +- src/servers/api/tasks/task-api-env.ts | 2 +- src/servers/sidecar/caldav/index.ts | 2 +- src/servers/sidecar/claude/index.ts | 2 +- src/servers/sidecar/claude/user-instance.ts | 13 ++++++++++--- src/servers/sidecar/connect.ts | 2 +- src/servers/sidecar/email/index.ts | 2 +- src/servers/sidecar/gitea/index.ts | 2 +- src/servers/sidecar/headscale/index.ts | 2 +- src/servers/sidecar/invoiceshelf/index.ts | 2 +- src/servers/sidecar/jellyfin/index.ts | 2 +- src/servers/sidecar/memos/index.ts | 2 +- src/servers/sidecar/music/index.ts | 2 +- src/servers/sidecar/notify/index.ts | 2 +- src/servers/sidecar/opencode/index.ts | 2 +- src/servers/sidecar/photos/index.ts | 2 +- src/servers/sidecar/pty/index.mjs | 2 +- src/servers/sidecar/slskd/index.ts | 2 +- src/servers/sidecar/transmission/index.ts | 2 +- src/servers/sidecar/vault/index.ts | 2 +- src/servers/sidecar/vnc/index.ts | 2 +- src/servers/sidecar/wallet/index.ts | 2 +- 24 files changed, 33 insertions(+), 26 deletions(-) diff --git a/ecosystem-files/ecosystem.profile.cjs b/ecosystem-files/ecosystem.profile.cjs index f56b3eb7..1592ffb8 100644 --- a/ecosystem-files/ecosystem.profile.cjs +++ b/ecosystem-files/ecosystem.profile.cjs @@ -69,7 +69,7 @@ function defineProfile({ file, include, excluded }) { // `cwd` is pinned because Bun auto-loads .env from the working directory (and the pty sidecar does // `import 'dotenv/config'`). Without it, starting pm2 from anywhere but the repo root silently falls - // back to PORT=5000 with no POSTGRES_URL. + // back to the default PORT with no POSTGRES_URL. // // It also decides where the install is. src/servers/data-path.ts derives OFFICER_ROOT as the PARENT of // the working directory, and data/, capabilities/ and dockers/ hang off that — so a wrong cwd does not diff --git a/src/server.tsx b/src/server.tsx index f0a8829b..aaec895c 100644 --- a/src/server.tsx +++ b/src/server.tsx @@ -23,7 +23,7 @@ import './servers/api/chat/opencode/sidecar-server'; // subscribe to the opencod import type { SidecarRegistration } from './servers/sidecar/registration-protocol'; import { toShellUsername } from './servers/data-path'; -const { PORT = '5000' } = process.env; +const { PORT = '9000' } = process.env; // Build static file routes from public/ const publicRoutes: Record Response> = {}; diff --git a/src/servers/api/chat/agent-panels-routes.ts b/src/servers/api/chat/agent-panels-routes.ts index c96673b2..9d6c2e48 100644 --- a/src/servers/api/chat/agent-panels-routes.ts +++ b/src/servers/api/chat/agent-panels-routes.ts @@ -23,7 +23,7 @@ import { logger } from './logger'; /** A name has to survive being typed into a prompt and into a shell, so keep it boring. */ const NAME_RE = /^[a-z0-9][a-z0-9-]{0,30}[a-z0-9]$|^[a-z0-9]$/; -const API_ORIGIN = `http://127.0.0.1:${process.env.PORT ?? '5000'}`; +const API_ORIGIN = `http://127.0.0.1:${process.env.PORT ?? '9000'}`; export function registerAgentPanelRoutes(router: Hono): void { // GET /chat/agent-panels?dashboardId=… — the address book for one dashboard. diff --git a/src/servers/api/tasks/task-api-env.ts b/src/servers/api/tasks/task-api-env.ts index d8334e93..912a250a 100644 --- a/src/servers/api/tasks/task-api-env.ts +++ b/src/servers/api/tasks/task-api-env.ts @@ -1,6 +1,6 @@ import { sign } from '@@/jwt'; -const { PORT = '5000', PUBLIC_URL } = process.env; +const { PORT = '9000', PUBLIC_URL } = process.env; const PUBLIC_HOST = (() => { try { diff --git a/src/servers/sidecar/caldav/index.ts b/src/servers/sidecar/caldav/index.ts index 2e680e27..f04ef86c 100644 --- a/src/servers/sidecar/caldav/index.ts +++ b/src/servers/sidecar/caldav/index.ts @@ -23,7 +23,7 @@ import { DATA_PATH } from '../../data-path'; // machine-facing interface. Same split officer-email already uses. // ───────────────────────────────────────────────────────────────────────────────────────────────── -const API_URL = process.env.API_URL ?? `ws://127.0.0.1:${process.env.PORT ?? '5000'}`; +const API_URL = process.env.API_URL ?? `ws://127.0.0.1:${process.env.PORT ?? '9000'}`; /** Grab an ephemeral free port by briefly binding one and releasing it. */ function getFreePort(): number { diff --git a/src/servers/sidecar/claude/index.ts b/src/servers/sidecar/claude/index.ts index 5379631d..39f861a5 100644 --- a/src/servers/sidecar/claude/index.ts +++ b/src/servers/sidecar/claude/index.ts @@ -3,7 +3,7 @@ import { loadState, flushAndSave, acquireLock, releaseLock, getState } from './s import { startAnthropicProxy, getProxySecret, ensureProxySecret } from './proxy'; import { createSidecarConnector } from '../connect'; -const API_URL = process.env.API_URL ?? `ws://127.0.0.1:${process.env.PORT ?? '5000'}`; +const API_URL = process.env.API_URL ?? `ws://127.0.0.1:${process.env.PORT ?? '9000'}`; // ── Startup ── diff --git a/src/servers/sidecar/claude/user-instance.ts b/src/servers/sidecar/claude/user-instance.ts index 8a9f032d..455c8d20 100644 --- a/src/servers/sidecar/claude/user-instance.ts +++ b/src/servers/sidecar/claude/user-instance.ts @@ -61,9 +61,16 @@ async function resolveOwner() { const dbUser = await resolveOwner(); const email = dbUser.email; -// Same officer instance for both, so the fallback port has to agree. It used to default to 5000 for the -// WebSocket and 9010 for the REST base, which would have split them apart if PORT were ever unset. -const OFFICER_PORT = process.env.PORT ?? '9010'; +// Both URLs address the SAME officer instance — this file binds nothing, and the app serves HTTP and +// WebSocket on one listener. So the fallback port has to agree, and twice it did not: 5000 for the +// WebSocket against 9010 for the REST base here, and then 9010 here against 5000 in the app and every +// other sidecar. The second one was the worse half, because chat is the only thing that would have +// broken and nothing else would have looked wrong. +// +// 9000 everywhere now, matching server.tsx and .env.example. 9010 was the old installer's default +// (scripts/setup-old/setup.sh), which is why it was the only one of the three that ever matched a real +// machine. +const OFFICER_PORT = process.env.PORT ?? '9000'; const API_URL = process.env.API_URL ?? `ws://127.0.0.1:${OFFICER_PORT}`; const OFFICER_API_URL = process.env.OFFICER_API_URL ?? `http://127.0.0.1:${OFFICER_PORT}`; const MCP_SERVER_SCRIPT = resolve(import.meta.dir, '../../mcp-tool-server.ts'); diff --git a/src/servers/sidecar/connect.ts b/src/servers/sidecar/connect.ts index 2632c289..da49c237 100644 --- a/src/servers/sidecar/connect.ts +++ b/src/servers/sidecar/connect.ts @@ -5,7 +5,7 @@ type AnyCommand = SidecarCommand; type AnyEvent = SidecarEvent; type SidecarConnectorConfig = { - apiUrl: string; // ws://127.0.0.1:5000/api/sidecar/register + apiUrl: string; // ws://127.0.0.1:9000/api/sidecar/register name: string; capabilities: string[]; onCommand: (cmd: AnyCommand, reply: (msg: AnyEvent) => void) => void; diff --git a/src/servers/sidecar/email/index.ts b/src/servers/sidecar/email/index.ts index d3b529e4..f53323ff 100644 --- a/src/servers/sidecar/email/index.ts +++ b/src/servers/sidecar/email/index.ts @@ -5,7 +5,7 @@ import { broadcastEmailNew } from './routes'; import { startEmailServer } from './http'; import { createSidecarConnector } from '../connect'; -const API_URL = process.env.API_URL ?? `ws://127.0.0.1:${process.env.PORT ?? '5000'}`; +const API_URL = process.env.API_URL ?? `ws://127.0.0.1:${process.env.PORT ?? '9000'}`; // The sidecar used to reach BACK into the platform's queue over this socket to get a sync run — // enqueueViaWs / listJobsViaWs and a pending-response map. Syncs run in this process now diff --git a/src/servers/sidecar/gitea/index.ts b/src/servers/sidecar/gitea/index.ts index 5ac8bc3e..713d2286 100644 --- a/src/servers/sidecar/gitea/index.ts +++ b/src/servers/sidecar/gitea/index.ts @@ -42,7 +42,7 @@ import { // sidecar from being a general-purpose SSRF hop into whatever else is on that host. // ───────────────────────────────────────────────────────────────────────────────────────────────── -const API_URL = process.env.API_URL ?? `ws://127.0.0.1:${process.env.PORT ?? '5000'}`; +const API_URL = process.env.API_URL ?? `ws://127.0.0.1:${process.env.PORT ?? '9000'}`; // Everything under /api/v1 the UI legitimately needs. // diff --git a/src/servers/sidecar/headscale/index.ts b/src/servers/sidecar/headscale/index.ts index 62f5b370..a99bf8fe 100644 --- a/src/servers/sidecar/headscale/index.ts +++ b/src/servers/sidecar/headscale/index.ts @@ -54,7 +54,7 @@ import { MIN_VERSION_LABEL } from './version'; // — the mistake the Soulseek panels made with 37 raw upstream calls. Every quirk is absorbed here. // ───────────────────────────────────────────────────────────────────────────────────────────────── -const API_URL = process.env.API_URL ?? `ws://127.0.0.1:${process.env.PORT ?? '5000'}`; +const API_URL = process.env.API_URL ?? `ws://127.0.0.1:${process.env.PORT ?? '9000'}`; /** Grab an ephemeral free port by briefly binding one and releasing it. */ function getFreePort(): number { diff --git a/src/servers/sidecar/invoiceshelf/index.ts b/src/servers/sidecar/invoiceshelf/index.ts index d5a3d15c..f962efb2 100644 --- a/src/servers/sidecar/invoiceshelf/index.ts +++ b/src/servers/sidecar/invoiceshelf/index.ts @@ -51,7 +51,7 @@ import { getConfig } from './upstream'; // update/*, installation/*, mail config, settings writes, ownership transfer — is deliberately unreachable. // ───────────────────────────────────────────────────────────────────────────────────────────────── -const API_URL = process.env.API_URL ?? `ws://127.0.0.1:${process.env.PORT ?? '5000'}`; +const API_URL = process.env.API_URL ?? `ws://127.0.0.1:${process.env.PORT ?? '9000'}`; /** Grab an ephemeral free port by briefly binding one and releasing it. */ function getFreePort(): number { diff --git a/src/servers/sidecar/jellyfin/index.ts b/src/servers/sidecar/jellyfin/index.ts index 3d28c22b..f6c64385 100644 --- a/src/servers/sidecar/jellyfin/index.ts +++ b/src/servers/sidecar/jellyfin/index.ts @@ -43,7 +43,7 @@ import { getConfig, probe } from './upstream'; // general proxy, and why HLS forces it to keep Jellyfin's own paths. // ───────────────────────────────────────────────────────────────────────────────────────────────── -const API_URL = process.env.API_URL ?? `ws://127.0.0.1:${process.env.PORT ?? '5000'}`; +const API_URL = process.env.API_URL ?? `ws://127.0.0.1:${process.env.PORT ?? '9000'}`; /** Grab an ephemeral free port by briefly binding one and releasing it. */ function getFreePort(): number { diff --git a/src/servers/sidecar/memos/index.ts b/src/servers/sidecar/memos/index.ts index 2f5f357d..f62fb645 100644 --- a/src/servers/sidecar/memos/index.ts +++ b/src/servers/sidecar/memos/index.ts @@ -22,7 +22,7 @@ import { callMemos, getMemosConfig, invalidateMemosConfig, normalizeBase, probe // SSRF hop into whatever else is on that host. // ───────────────────────────────────────────────────────────────────────────────────────────────── -const API_URL = process.env.API_URL ?? `ws://127.0.0.1:${process.env.PORT ?? '5000'}`; +const API_URL = process.env.API_URL ?? `ws://127.0.0.1:${process.env.PORT ?? '9000'}`; // Everything under /api/v1 the UI legitimately needs. Auth routes are excluded on purpose: signin and // signout would mint or destroy sessions on the instance, and this sidecar authenticates with a stored diff --git a/src/servers/sidecar/music/index.ts b/src/servers/sidecar/music/index.ts index 4ed268f3..09eebf59 100644 --- a/src/servers/sidecar/music/index.ts +++ b/src/servers/sidecar/music/index.ts @@ -114,7 +114,7 @@ const asKeys = (v: unknown): string[] | null => // `v` = per-album version stamp; unchanged `v` ⇒ nothing changed ⇒ the phone can skip re-downloading. // ───────────────────────────────────────────────────────────────────────────────────────────────── -const API_URL = process.env.API_URL ?? `ws://127.0.0.1:${process.env.PORT ?? '5000'}`; +const API_URL = process.env.API_URL ?? `ws://127.0.0.1:${process.env.PORT ?? '9000'}`; // ── Audio-streaming HTTP server ── diff --git a/src/servers/sidecar/notify/index.ts b/src/servers/sidecar/notify/index.ts index 1086e064..ae06cce3 100644 --- a/src/servers/sidecar/notify/index.ts +++ b/src/servers/sidecar/notify/index.ts @@ -26,7 +26,7 @@ import type { Notification, NotifyType } from './types'; // the visible string is composed here rather than sent by the producer. // ───────────────────────────────────────────────────────────────────────────────────────────────── -const API_URL = process.env.API_URL ?? `ws://127.0.0.1:${process.env.PORT ?? '5000'}`; +const API_URL = process.env.API_URL ?? `ws://127.0.0.1:${process.env.PORT ?? '9000'}`; const VALID_TYPES: NotifyType[] = ['job', 'mail', 'agent', 'download', 'test']; diff --git a/src/servers/sidecar/opencode/index.ts b/src/servers/sidecar/opencode/index.ts index aac33861..347154f8 100644 --- a/src/servers/sidecar/opencode/index.ts +++ b/src/servers/sidecar/opencode/index.ts @@ -18,7 +18,7 @@ import { runOpenCodeTurnOnServe, killServeTurn, listRunningServeTurns, stopAllSe // CRUD only, with turns spawned as `opencode run --dir ` subprocesses — that path was deleted on // 2026-08-10 once the serve had streaming, mid-turn injection and interrupt working end to end. -const API_URL = process.env.API_URL ?? `ws://127.0.0.1:${process.env.PORT ?? '5000'}`; +const API_URL = process.env.API_URL ?? `ws://127.0.0.1:${process.env.PORT ?? '9000'}`; const OPENCODE_BIN = process.env.OPENCODE_BIN || join(homedir(), '.opencode', 'bin', 'opencode'); const SERVE_CWD = join(DATA_PATH, 'opencode_server'); const HEALTH_TIMEOUT_MS = 20_000; diff --git a/src/servers/sidecar/photos/index.ts b/src/servers/sidecar/photos/index.ts index 03ed4f5b..655220e8 100644 --- a/src/servers/sidecar/photos/index.ts +++ b/src/servers/sidecar/photos/index.ts @@ -46,7 +46,7 @@ import { getConfig } from './upstream'; // ETag included. The administrative half of Immich's API is unreachable — see routes.ts for the list. // ───────────────────────────────────────────────────────────────────────────────────────────────── -const API_URL = process.env.API_URL ?? `ws://127.0.0.1:${process.env.PORT ?? '5000'}`; +const API_URL = process.env.API_URL ?? `ws://127.0.0.1:${process.env.PORT ?? '9000'}`; /** Grab an ephemeral free port by briefly binding one and releasing it. */ function getFreePort(): number { diff --git a/src/servers/sidecar/pty/index.mjs b/src/servers/sidecar/pty/index.mjs index 46f99fb1..7f1dd9f1 100644 --- a/src/servers/sidecar/pty/index.mjs +++ b/src/servers/sidecar/pty/index.mjs @@ -19,7 +19,7 @@ import { startServer } from './server.mjs'; import 'dotenv/config'; -const API_URL = process.env.API_URL ?? `ws://127.0.0.1:${process.env.PORT ?? '5000'}`; +const API_URL = process.env.API_URL ?? `ws://127.0.0.1:${process.env.PORT ?? '9000'}`; const REGISTER_URL = `${API_URL}/api/sidecar/register`; const RECONNECT_DELAYS = [200, 500, 1000, 2000, 4000, 8000, 15000]; diff --git a/src/servers/sidecar/slskd/index.ts b/src/servers/sidecar/slskd/index.ts index 6b3c1e1c..46646eef 100644 --- a/src/servers/sidecar/slskd/index.ts +++ b/src/servers/sidecar/slskd/index.ts @@ -37,7 +37,7 @@ import { handleOfficerRoute } from './officer'; // (src/servers/sidecar/vault/index.ts) once the client needs real-time updates. SignalR carries its // credential as an `?access_token=` query param on the socket, so the key injection differs from HTTP. -const API_URL = process.env.API_URL ?? `ws://127.0.0.1:${process.env.PORT ?? '5000'}`; +const API_URL = process.env.API_URL ?? `ws://127.0.0.1:${process.env.PORT ?? '9000'}`; /** Grab an ephemeral free port by briefly binding one and releasing it. */ function getFreePort(): number { diff --git a/src/servers/sidecar/transmission/index.ts b/src/servers/sidecar/transmission/index.ts index 96b36948..0457fe5d 100644 --- a/src/servers/sidecar/transmission/index.ts +++ b/src/servers/sidecar/transmission/index.ts @@ -45,7 +45,7 @@ import { getTransmissionConfig } from './upstream'; // platform, and which daemon to talk to is per-owner data. // ───────────────────────────────────────────────────────────────────────────────────────────────── -const API_URL = process.env.API_URL ?? `ws://127.0.0.1:${process.env.PORT ?? '5000'}`; +const API_URL = process.env.API_URL ?? `ws://127.0.0.1:${process.env.PORT ?? '9000'}`; /** Grab an ephemeral free port by briefly binding one and releasing it. */ function getFreePort(): number { diff --git a/src/servers/sidecar/vault/index.ts b/src/servers/sidecar/vault/index.ts index 7919116e..c80b7e87 100644 --- a/src/servers/sidecar/vault/index.ts +++ b/src/servers/sidecar/vault/index.ts @@ -21,7 +21,7 @@ import { getVaultBase, getVaultWsBase, stripHopByHop, redactPath } from './upstr // The server listens on a random loopback port, reported to the API on connect so it can route here. // ───────────────────────────────────────────────────────────────────────────────────────────────── -const API_URL = process.env.API_URL ?? `ws://127.0.0.1:${process.env.PORT ?? '5000'}`; +const API_URL = process.env.API_URL ?? `ws://127.0.0.1:${process.env.PORT ?? '9000'}`; /** Grab an ephemeral free port by briefly binding one and releasing it. */ function getFreePort(): number { diff --git a/src/servers/sidecar/vnc/index.ts b/src/servers/sidecar/vnc/index.ts index 9a7eca10..2cbe5fa1 100644 --- a/src/servers/sidecar/vnc/index.ts +++ b/src/servers/sidecar/vnc/index.ts @@ -3,7 +3,7 @@ import * as vncManager from './vnc-manager'; import { createSidecarConnector } from '../connect'; import { getOwnerHomeDir } from '@@/data-path'; -const API_URL = process.env.API_URL ?? `ws://127.0.0.1:${process.env.PORT ?? '5000'}`; +const API_URL = process.env.API_URL ?? `ws://127.0.0.1:${process.env.PORT ?? '9000'}`; // ── Command handlers ── diff --git a/src/servers/sidecar/wallet/index.ts b/src/servers/sidecar/wallet/index.ts index 6581ee5b..304aed85 100644 --- a/src/servers/sidecar/wallet/index.ts +++ b/src/servers/sidecar/wallet/index.ts @@ -81,7 +81,7 @@ import { invalidateAll } from './resolve'; // WALLET_LOCKED from signing paths only; every read above keeps working. // ───────────────────────────────────────────────────────────────────────────────────────────────── -const API_URL = process.env.API_URL ?? `ws://127.0.0.1:${process.env.PORT ?? '5000'}`; +const API_URL = process.env.API_URL ?? `ws://127.0.0.1:${process.env.PORT ?? '9000'}`; /** Grab an ephemeral free port by briefly binding one and releasing it. */ function getFreePort(): number {