diff --git a/.env.example b/.env.example index dbf9feed..346d985b 100644 --- a/.env.example +++ b/.env.example @@ -46,8 +46,11 @@ DB_BACKUP_KEEP=7 # Minutes between repeat health-fail alerts from jobs-worker (default 15). HEALTH_ALERT_COOLDOWN_MIN=15 -# --- EMULATOR API --- -EMULATOR_API_URL=http://127.0.0.1:3001 +# --- RCON (Low Latency Loop) --- +RCON_HOST=127.0.0.1 +RCON_PORT=3003 +EMU_PORT=3004 +RCON_TIMEOUT_MS=2000 # --- EMAIL & NOTIFICATIONS --- SMTP_HOST= diff --git a/src/env.ts b/src/env.ts index 5dd1b81a..9d30081b 100644 --- a/src/env.ts +++ b/src/env.ts @@ -40,11 +40,17 @@ const schema = z SMTP_USER: z.string().optional(), SMTP_PASSWORD: z.string().optional(), SMTP_FROM: z.string().optional(), - // Emulator transport selection. "api" uses an HTTP endpoint that accepts the same + // RCON link to the Arcturus emulator (raw-JSON TCP protocol). + RCON_HOST: z.string().default("127.0.0.1"), + RCON_PORT: z.coerce.number().int().positive().default(3001), + RCON_TIMEOUT_MS: z.coerce.number().int().positive().default(10_000), + RCON_MAX_RETRIES: z.coerce.number().int().positive().default(3), + // Emulator transport selection. "rcon" uses the raw-JSON TCP protocol + // above; "api" uses an HTTP endpoint that accepts the same // `{"key":...,"data":...}` payload (for emulators without RCON). - EMULATOR_MODE: z.enum(["rcon", "api"]).default("api"), + EMULATOR_MODE: z.enum(["rcon", "api"]).default("rcon"), // HTTP endpoint used when EMULATOR_MODE=api. Required in that mode. - EMULATOR_API_URL: z.string().url(), + EMULATOR_API_URL: z.string().url().optional(), // Optional bearer/key auth for the API transport. When set, it is sent // as `${EMULATOR_API_KEY_HEADER}: ${EMULATOR_API_KEY}` (default header // "Authorization"). diff --git a/src/lib/services/alert.ts b/src/lib/services/alert.ts index 5aa16340..38319bd4 100644 --- a/src/lib/services/alert.ts +++ b/src/lib/services/alert.ts @@ -223,8 +223,10 @@ export function emulatorOffline(detail?: string): Promise { severity: "critical", message: detail ? `Emulator appears offline: ${detail}` - : "Emulator appears offline — emulator API connection could not be established.", + : "Emulator appears offline — RCON connection could not be established.", context: { + rconHost: env.RCON_HOST, + rconPort: env.RCON_PORT, ...(detail ? { detail } : {}), }, }); diff --git a/src/lib/services/rcon.ts b/src/lib/services/rcon.ts index 6844d982..db15302d 100644 --- a/src/lib/services/rcon.ts +++ b/src/lib/services/rcon.ts @@ -225,20 +225,24 @@ export function httpTransport(opts: { * misconfiguration surfaces immediately instead of on first command. */ export function createEmulatorTransport(): RconTransport { - const mode = env.EMULATOR_MODE ?? "api"; - if (mode === "api") { + if (env.EMULATOR_MODE === "api") { + if (!env.EMULATOR_API_URL) { + throw new Error("EMULATOR_MODE=api requires EMULATOR_API_URL to be set"); + } return httpTransport({ - url: env.EMULATOR_API_URL ?? "http://127.0.0.1:3001", - timeoutMs: 10_000, + url: env.EMULATOR_API_URL, + timeoutMs: env.RCON_TIMEOUT_MS, apiKey: env.EMULATOR_API_KEY, apiKeyHeader: env.EMULATOR_API_KEY_HEADER, - maxRetries: 3, + maxRetries: env.RCON_MAX_RETRIES, }); } - // API mode only; RCON transport removed. - throw new Error( - "EMULATOR_MODE=rcon is no longer supported; use EMULATOR_MODE=api", - ); + return tcpTransport({ + host: env.RCON_HOST, + port: env.RCON_PORT, + timeoutMs: env.RCON_TIMEOUT_MS, + maxRetries: env.RCON_MAX_RETRIES, + }); } const globalForRcon = globalThis as unknown as { rcon?: RconClient };