PORT is read in one place, and it has no default
officer-url.mjs is now the only file in the tree that touches process.env.PORT.
Twenty-two others read it and supplied their own default; a value with
twenty-two sources is not configuration, it is twenty-two things to keep in sync,
and they had already drifted three ways.
It throws when PORT is unset rather than guessing. A default only covers the case
where .env was never loaded — which is not a machine anyone wants running,
because POSTGRES_URL is missing in the same breath. What the default bought was a
process that starts, binds somewhere unexpected, and fails later for a reason
that does not name the cause. Same posture as jwt.ts with JWT_SECRET.
It is .mjs, not .ts, and that is the whole reason this could be one file. pm2
launches officer-pty with node (ecosystem.config.cjs) and everything else with
bun; node cannot import TypeScript, so a .ts module would have left the pty
sidecar holding the only surviving copy of the default — precisely the thing
being removed. allowJs is already on, so the TS callers still get types. Verified
both runtimes import it, and that PUBLIC_URL-style overrides still work.
It also exports API_URL and OFFICER_API_URL, because nineteen sidecars were
independently building `ws://127.0.0.1:${PORT}` and two more were building the
http form. Those are one listener described in two protocols — no sidecar binds
anything — so they belong beside the port rather than being rediscovered per
file.
server.tsx now takes PORT as a number, so Number(PORT) at the serve site is gone.
Not typechecked (empty node_modules, frozen installs). Every edited file parses
under `bun build --no-bundle`; node and bun both load the new module; the unset
and non-numeric paths were exercised; the pm2 profile still loads.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+2
-2
@@ -4,6 +4,7 @@ import { serve } from 'bun';
|
||||
import { honoServer, PROTECTED_API_PREFIXES, UNPROTECTED_API_PREFIXES } from './servers/hono';
|
||||
import { assertCapabilityTotality } from './servers/capabilities/totality';
|
||||
import { assertInstallLayout } from './servers/data-path';
|
||||
import { PORT } from './servers/officer-url.mjs';
|
||||
import { assertSecretsClosed } from './servers/os-user';
|
||||
import { resolveHomeDir } from './servers/user-home';
|
||||
import { resolveAuthToken } from './servers/auth-token';
|
||||
@@ -23,7 +24,6 @@ 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 = '9000' } = process.env;
|
||||
|
||||
// Build static file routes from public/
|
||||
const publicRoutes: Record<string, (req: Request) => Response> = {};
|
||||
@@ -250,7 +250,7 @@ async function upgradeWs(
|
||||
}
|
||||
|
||||
const server = serve({
|
||||
port: Number(PORT),
|
||||
port: PORT,
|
||||
idleTimeout: 60,
|
||||
maxRequestBodySize: 1024 * 1024 * 1024 * 50, // 50 GB
|
||||
routes: {
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
} from 'officerdb';
|
||||
import { introduceAgentPanel } from '../agent-handoff/deliver';
|
||||
import { logger } from './logger';
|
||||
import { OFFICER_API_URL } from '../../officer-url.mjs';
|
||||
|
||||
/**
|
||||
* The browser's half of the address book: name a panel, look up what a panel is, rename, remove.
|
||||
@@ -23,7 +24,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 ?? '9000'}`;
|
||||
const API_ORIGIN = OFFICER_API_URL;
|
||||
|
||||
export function registerAgentPanelRoutes(router: Hono<any>): void {
|
||||
// GET /chat/agent-panels?dashboardId=… — the address book for one dashboard.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { sign } from '@@/jwt';
|
||||
import { PORT, OFFICER_API_URL } from '../../officer-url.mjs';
|
||||
|
||||
const { PORT = '9000', PUBLIC_URL } = process.env;
|
||||
const { PUBLIC_URL } = process.env;
|
||||
|
||||
const PUBLIC_HOST = (() => {
|
||||
try {
|
||||
@@ -24,7 +25,7 @@ type TaskApiUser = { id: number; email: string; username?: string | null };
|
||||
export async function buildTaskApiEnv(user: TaskApiUser): Promise<Record<string, string>> {
|
||||
const token = await sign({ id: user.id, email: user.email, username: user.username ?? '' }, '12h');
|
||||
return {
|
||||
OFFICER_API_URL: `http://127.0.0.1:${PORT}`,
|
||||
OFFICER_API_URL,
|
||||
OFFICER_API_HOST: PUBLIC_HOST ?? `127.0.0.1:${PORT}`,
|
||||
OFFICER_AUTH_TOKEN: token,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
// Where the app is, and the only place that knows.
|
||||
//
|
||||
// ── Why this file is .mjs ──
|
||||
//
|
||||
// Every process is bun except `officer-pty`, which pm2 launches with node (see
|
||||
// ecosystem.config.cjs). Node cannot import TypeScript, so a .ts module here would have left the pty
|
||||
// sidecar with its own copy — which is exactly the thing this file exists to end. Plain JS is
|
||||
// importable by both, and `allowJs` in tsconfig.json means the TS callers still get types.
|
||||
//
|
||||
// ── Why there is no default ──
|
||||
//
|
||||
// There were twenty-two, and they disagreed: 5000 in the app and nineteen sidecars, 9010 in
|
||||
// user-instance.ts, 9000 in .env.example. Each was defensible where it was written and none was
|
||||
// visible from the others.
|
||||
//
|
||||
// A default is a guess at a value that .env always supplies. The one case it covers — PORT genuinely
|
||||
// unset — is not a machine anyone wants running: it means .env was not loaded, so POSTGRES_URL is
|
||||
// missing too and nothing works anyway. What a default buys there is a process that starts, binds
|
||||
// somewhere unexpected and fails later for a reason that does not name the cause.
|
||||
//
|
||||
// So: no default anywhere, and this throws. Same posture as jwt.ts with JWT_SECRET.
|
||||
|
||||
const raw = process.env.PORT;
|
||||
|
||||
if (!raw) {
|
||||
throw new Error(
|
||||
[
|
||||
'PORT is not set.',
|
||||
'',
|
||||
'Officer reads it from .env, which bun auto-loads from the working directory. If this is a pm2',
|
||||
'process, the `cwd` pin in ecosystem.profile.cjs is what puts it there — a PORT this empty',
|
||||
'usually means .env was never loaded at all, and POSTGRES_URL is missing too.',
|
||||
'',
|
||||
'Set PORT in .env (officer-setup writes 9000), or start from the repo root.',
|
||||
].join('\n'),
|
||||
);
|
||||
}
|
||||
|
||||
const parsed = Number(raw);
|
||||
|
||||
if (!Number.isInteger(parsed) || parsed < 1 || parsed > 65535) {
|
||||
throw new Error(`PORT must be an integer between 1 and 65535, but it is ${JSON.stringify(raw)}`);
|
||||
}
|
||||
|
||||
/** The port the app binds. Everything else addresses it; nothing else binds. */
|
||||
export const PORT = parsed;
|
||||
|
||||
// The app serves HTTP and WebSocket on ONE listener, so these are the same port in two protocols.
|
||||
// Sidecars append their own path — `/api/sidecar/register` for the registration socket.
|
||||
//
|
||||
// The env overrides are kept because they are explicit escape hatches rather than defaults: nothing
|
||||
// sets either today, and a value that is present is a deliberate act rather than a guess.
|
||||
export const API_URL = process.env.API_URL ?? `ws://127.0.0.1:${PORT}`;
|
||||
export const OFFICER_API_URL = process.env.OFFICER_API_URL ?? `http://127.0.0.1:${PORT}`;
|
||||
@@ -3,6 +3,7 @@ import { createSidecarConnector } from '../connect';
|
||||
import { startRadicale, davPaths } from './radicale';
|
||||
import { listCollections, listEvents, listContacts } from './collections';
|
||||
import { DATA_PATH } from '../../data-path';
|
||||
import { API_URL } from '../../officer-url.mjs';
|
||||
|
||||
// The officer-caldav sidecar. Owns the whole CalDAV/CardDAV contract: it supervises Radicale, owns the
|
||||
// collection storage under DATA_PATH/dav, and exposes two very different doors.
|
||||
@@ -23,7 +24,6 @@ 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 ?? '9000'}`;
|
||||
|
||||
/** Grab an ephemeral free port by briefly binding one and releasing it. */
|
||||
function getFreePort(): number {
|
||||
|
||||
@@ -2,8 +2,8 @@ import type { SidecarCommand, SidecarEvent } from '../protocol';
|
||||
import { loadState, flushAndSave, acquireLock, releaseLock, getState } from './state';
|
||||
import { startAnthropicProxy, getProxySecret, ensureProxySecret } from './proxy';
|
||||
import { createSidecarConnector } from '../connect';
|
||||
import { API_URL } from '../../officer-url.mjs';
|
||||
|
||||
const API_URL = process.env.API_URL ?? `ws://127.0.0.1:${process.env.PORT ?? '9000'}`;
|
||||
|
||||
// ── Startup ──
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import { createSidecarConnector } from '../connect';
|
||||
import { sign } from '../../jwt';
|
||||
import { getUserByEmail, getOwnerUser, getEmailAccounts } from 'officerdb';
|
||||
import { DATA_PATH } from '../../data-path';
|
||||
import { API_URL, OFFICER_API_URL } from '../../officer-url.mjs';
|
||||
|
||||
// PM2 starts this sidecar with no user in its env, so resolve the owner from the database rather than
|
||||
// being told who to run as by the main server — one less thing that has to come from `officer` before
|
||||
@@ -70,9 +71,6 @@ const email = dbUser.email;
|
||||
// 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');
|
||||
|
||||
// Mint a long-lived JWT for this user so tools (e.g. gmail) can call back to dev-platform as them
|
||||
|
||||
@@ -4,8 +4,8 @@ import { initEmailIdle, stopEmailIdle } from './email-idle';
|
||||
import { broadcastEmailNew } from './routes';
|
||||
import { startEmailServer } from './http';
|
||||
import { createSidecarConnector } from '../connect';
|
||||
import { API_URL } from '../../officer-url.mjs';
|
||||
|
||||
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
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
normalizeBase,
|
||||
probe,
|
||||
} from './upstream';
|
||||
import { API_URL } from '../../officer-url.mjs';
|
||||
|
||||
// The officer-gitea sidecar. Owns the whole Gitea contract: the instance URL and the personal access
|
||||
// token. The platform side is a thin auth-gated forwarder holding no Gitea credentials.
|
||||
@@ -42,7 +43,6 @@ 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 ?? '9000'}`;
|
||||
|
||||
// Everything under /api/v1 the UI legitimately needs.
|
||||
//
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { SidecarCommand, SidecarEvent } from '../protocol';
|
||||
import { createSidecarConnector } from '../connect';
|
||||
import { handleOfficerRoute } from './routes';
|
||||
import { MIN_VERSION_LABEL } from './version';
|
||||
import { API_URL } from '../../officer-url.mjs';
|
||||
|
||||
// The officer-headscale sidecar. Owns the whole Headscale contract for Officer: the registered servers and
|
||||
// their admin API keys, the >=0.29 version floor, and every multi-call composition the UI needs. The platform
|
||||
@@ -54,7 +55,6 @@ 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 ?? '9000'}`;
|
||||
|
||||
/** Grab an ephemeral free port by briefly binding one and releasing it. */
|
||||
function getFreePort(): number {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { createSidecarConnector } from '../connect';
|
||||
import { handleConfigRoute, noteProbe, probe } from './config';
|
||||
import { handleOfficerRoute } from './routes';
|
||||
import { getConfig } from './upstream';
|
||||
import { API_URL } from '../../officer-url.mjs';
|
||||
|
||||
// The officer-invoiceshelf sidecar. Owns the whole InvoiceShelf contract for Officer: the instance URL, the
|
||||
// Sanctum API token, and the `company` header that scopes every request. The platform API is a thin
|
||||
@@ -51,7 +52,6 @@ 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 ?? '9000'}`;
|
||||
|
||||
/** Grab an ephemeral free port by briefly binding one and releasing it. */
|
||||
function getFreePort(): number {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { createSidecarConnector } from '../connect';
|
||||
import { handleConfigRoute, noteProbe } from './config';
|
||||
import { handleBytesRoute, handleOfficerRoute } from './routes';
|
||||
import { getConfig, probe } from './upstream';
|
||||
import { API_URL } from '../../officer-url.mjs';
|
||||
|
||||
// The officer-jellyfin sidecar. Owns the whole Jellyfin contract for Officer: the instance URL, the access
|
||||
// token, the Jellyfin user it belongs to and the DeviceId its sessions are keyed by. The platform API is a
|
||||
@@ -43,7 +44,6 @@ 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 ?? '9000'}`;
|
||||
|
||||
/** Grab an ephemeral free port by briefly binding one and releasing it. */
|
||||
function getFreePort(): number {
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { SidecarCommand, SidecarEvent } from '../protocol';
|
||||
import { createSidecarConnector } from '../connect';
|
||||
import { getServiceConnection, saveServiceConnection, deleteServiceConnection, recordServiceProbe } from 'officerdb';
|
||||
import { callMemos, getMemosConfig, invalidateMemosConfig, normalizeBase, probe } from './upstream';
|
||||
import { API_URL } from '../../officer-url.mjs';
|
||||
|
||||
// The officer-memos sidecar. Owns the whole Memos contract: the instance URL and the personal access
|
||||
// token. The platform side is a thin auth-gated forwarder holding no Memos credentials.
|
||||
@@ -22,7 +23,6 @@ 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 ?? '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
|
||||
|
||||
@@ -39,6 +39,7 @@ import {
|
||||
type FavoriteKind,
|
||||
} from 'officerdb';
|
||||
import { DATA_PATH } from '../../data-path';
|
||||
import { API_URL } from '../../officer-url.mjs';
|
||||
|
||||
|
||||
// ── Per-user state validation ──
|
||||
@@ -114,7 +115,6 @@ 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 ?? '9000'}`;
|
||||
|
||||
// ── Audio-streaming HTTP server ──
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import { handleDeviceRoute } from './devices';
|
||||
import { resolveNotifyUser } from './resolve-user';
|
||||
import { closeApnsSessions } from './apns';
|
||||
import type { Notification, NotifyType } from './types';
|
||||
import { API_URL } from '../../officer-url.mjs';
|
||||
|
||||
// The officer-notify sidecar. The one place anything leaves this machine to tell the owner something.
|
||||
//
|
||||
@@ -26,7 +27,6 @@ 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 ?? '9000'}`;
|
||||
|
||||
const VALID_TYPES: NotifyType[] = ['job', 'mail', 'agent', 'download', 'test'];
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import { createSessionLogStore } from '../claude/session-log';
|
||||
import type { SidecarCommand, SidecarEvent } from '../protocol';
|
||||
import type { RunnerMessage } from './serve-runner';
|
||||
import { runOpenCodeTurnOnServe, killServeTurn, listRunningServeTurns, stopAllServeTurns } from './serve-runner';
|
||||
import { API_URL } from '../../officer-url.mjs';
|
||||
|
||||
// The OpenCode sidecar (officer-opencode). Same philosophy as officer-claude: a singleton process that
|
||||
// OWNS its runtime — here, an `opencode serve` — registers with the API server, and answers commands. It
|
||||
@@ -18,7 +19,6 @@ import { runOpenCodeTurnOnServe, killServeTurn, listRunningServeTurns, stopAllSe
|
||||
// CRUD only, with turns spawned as `opencode run --dir <cwd>` 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 ?? '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;
|
||||
|
||||
@@ -4,6 +4,7 @@ import { handleConfigRoute, noteProbe, probe } from './config';
|
||||
import { handleLockedRoute } from './locked';
|
||||
import { handleOfficerRoute } from './routes';
|
||||
import { getConfig } from './upstream';
|
||||
import { API_URL } from '../../officer-url.mjs';
|
||||
|
||||
// The officer-photos sidecar. Owns the whole Immich contract for Officer: the instance URL and the API key.
|
||||
// The platform API is a thin auth-gated forwarder (src/servers/api/photos/router.ts) holding no Immich
|
||||
@@ -46,7 +47,6 @@ 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 ?? '9000'}`;
|
||||
|
||||
/** Grab an ephemeral free port by briefly binding one and releasing it. */
|
||||
function getFreePort(): number {
|
||||
|
||||
@@ -18,8 +18,8 @@ import * as store from './sessions.mjs';
|
||||
import { startServer } from './server.mjs';
|
||||
|
||||
import 'dotenv/config';
|
||||
import { API_URL } from '../../officer-url.mjs';
|
||||
|
||||
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];
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { resetStaleSoulseekBrowses } from 'officerdb';
|
||||
import { getSlskdConfig, stripHopByHop } from './upstream';
|
||||
import { handleConfigRoute, probe } from './config';
|
||||
import { handleOfficerRoute } from './officer';
|
||||
import { API_URL } from '../../officer-url.mjs';
|
||||
|
||||
// The officer-slskd sidecar. Same philosophy as officer-vault / officer-music: a singleton process that
|
||||
// registers with the API server and OWNS a contract — here, a reverse-proxy to a self-hosted slskd
|
||||
@@ -37,7 +38,6 @@ 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 ?? '9000'}`;
|
||||
|
||||
/** Grab an ephemeral free port by briefly binding one and releasing it. */
|
||||
function getFreePort(): number {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { handleConfigRoute } from './config';
|
||||
import { handleOfficerRoute } from './routes';
|
||||
import { probe } from './rpc';
|
||||
import { getTransmissionConfig } from './upstream';
|
||||
import { API_URL } from '../../officer-url.mjs';
|
||||
|
||||
// The officer-transmission sidecar. Owns the whole Transmission contract for Officer: the daemon URL and
|
||||
// credentials, the X-Transmission-Session-Id CSRF handshake, and the translation from Transmission's
|
||||
@@ -45,7 +46,6 @@ 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 ?? '9000'}`;
|
||||
|
||||
/** Grab an ephemeral free port by briefly binding one and releasing it. */
|
||||
function getFreePort(): number {
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { ServerWebSocket } from 'bun';
|
||||
import type { SidecarCommand, SidecarEvent } from '../protocol';
|
||||
import { createSidecarConnector } from '../connect';
|
||||
import { getVaultBase, getVaultWsBase, stripHopByHop, redactPath } from './upstream';
|
||||
import { API_URL } from '../../officer-url.mjs';
|
||||
|
||||
// The officer-vault sidecar. Same philosophy as the other officer-* sidecars: a singleton process that
|
||||
// registers with the API server and OWNS a contract — here, a transparent reverse-proxy to a self-hosted
|
||||
@@ -21,7 +22,6 @@ 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 ?? '9000'}`;
|
||||
|
||||
/** Grab an ephemeral free port by briefly binding one and releasing it. */
|
||||
function getFreePort(): number {
|
||||
|
||||
@@ -2,8 +2,8 @@ import type { SidecarCommand, SidecarEvent } from '../protocol';
|
||||
import * as vncManager from './vnc-manager';
|
||||
import { createSidecarConnector } from '../connect';
|
||||
import { getOwnerHomeDir } from '@@/data-path';
|
||||
import { API_URL } from '../../officer-url.mjs';
|
||||
|
||||
const API_URL = process.env.API_URL ?? `ws://127.0.0.1:${process.env.PORT ?? '9000'}`;
|
||||
|
||||
// ── Command handlers ──
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import { handleOfficerRoute } from './routes';
|
||||
import { getChainSource, getConfig, hasStoreKey } from './upstream';
|
||||
import { lockAll } from './keys';
|
||||
import { invalidateAll } from './resolve';
|
||||
import { API_URL } from '../../officer-url.mjs';
|
||||
|
||||
// The officer-wallet sidecar. A bitcoin wallet in the shape Zeus models one — several interchangeable
|
||||
// backends behind one interface — but server-side, with the key material held here and nowhere else.
|
||||
@@ -81,7 +82,6 @@ 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 ?? '9000'}`;
|
||||
|
||||
/** Grab an ephemeral free port by briefly binding one and releasing it. */
|
||||
function getFreePort(): number {
|
||||
|
||||
Reference in New Issue
Block a user