import process from "node:process"; import { Redis } from "ioredis"; import { Registry as MetricsRegistry } from "prom-client"; import { MemoryRedis } from "../memory-redis.js"; import { pinboardEnv, redisEndpointForLogs } from "../env.js"; import { PostgresDurabilityStore } from "../postgres-durability.js"; import { logger, serializeError } from "../logger.js"; import { servePinboard } from "./serve.js"; import { parsePort } from "./port.js"; import { installPinoLogger } from "./logger.js"; import { installProcessObservability } from "./runtime.js"; const createRedis = (url: string): Redis | MemoryRedis => url === "memory:" ? new MemoryRedis() : new Redis(url); // The activities index enabling resurrection after a redis wipe. const durabilityStore = async (): Promise< PostgresDurabilityStore | undefined > => { const url = process.env["PINBOARD_DURABILITY_POSTGRES_URL"]; if (url === undefined || url === "") return undefined; const pg = await import("pg").catch(() => { throw new Error("PINBOARD_DURABILITY_POSTGRES_URL requires the pg package"); }); return new PostgresDurabilityStore( new pg.default.Pool({ connectionString: url }), ); }; async function main(): Promise { installPinoLogger(); let port: number; let parsed: pinboardEnv.Result; let store: PostgresDurabilityStore | undefined; try { port = parsePort(process.env["PORT"]); parsed = pinboardEnv(process.env); store = await durabilityStore(); } catch (error) { logger.error({ err: serializeError(error) }, "invalid configuration"); process.exit(1); } const { redisUrl, options } = parsed; if (store !== undefined) { options.durability = { ...options.durability, store }; } const redis = createRedis(redisUrl); const metricsRegistry = new MetricsRegistry(); const pinboard = servePinboard({ ...options, redis, onDisplaced: () => { logger.error( { operation: "license" }, "displaced by a higher-epoch fleet; exiting", ); process.exit(1); }, metricsRegistry, }); installProcessObservability({ workers: async () => (await pinboard.registry.listWorkers()).length, }); const { server } = pinboard; server.listen(port, () => { logger.info( { operation: "listen", port, redis: redisEndpointForLogs(redisUrl) }, "pinboard listening", ); }); let shuttingDown = false; const shutdown = (): void => { if (shuttingDown) return; shuttingDown = true; logger.info({ operation: "drain" }, "pinboard draining"); const timer = setTimeout(() => process.exit(0), 10_000); timer.unref(); void pinboard.close().then(() => process.exit(0)); }; process.on("SIGTERM", shutdown); process.on("SIGINT", shutdown); } void main();