// Bundled by scripts/bundle-server.mjs into build/server.mjs (ESM, only // better-sqlite3 external). This is the ONLY module allowed to touch server // internals: the db singleton lives inside this bundle, so anything stateful // (auth bootstrap, getDb) must be exported from here rather than imported // from server/src by the main bundle (which would get a second, empty copy). // // The server sources live in this same repo (../../server). Keep these // relative imports in sync with the repo-root default in main.ts. import '../../server/src/env.js'; import crypto from 'node:crypto'; import type { Server } from 'node:http'; import { createApp } from '../../server/src/app.js'; import { initDb, getDb, getUnifiedApiKey } from '../../server/src/db/index.js'; import { startHealthChecker } from '../../server/src/services/health.js'; import { userCount, createUser, createSession } from '../../server/src/services/auth.js'; export { getDb, getUnifiedApiKey }; export interface StartOptions { dbPath: string; clientDist: string; host: string; preferredPort: number; } export interface ServerHandle { server: Server; port: number; } export async function startServer(opts: StartOptions): Promise { process.env.CLIENT_DIST = opts.clientDist; initDb(opts.dbPath); const app = createApp(); const { server, port } = await listenWithScan(app, opts.host, opts.preferredPort); startHealthChecker(); return { server, port }; } // The dashboard window authenticates as a hidden machine user. The password // is random and never shown — sessions are minted directly against the DB. export function ensureSessionToken(): string { if (userCount() === 0) { createUser('desktop@localhost', crypto.randomBytes(24).toString('hex')); } const first = getDb().prepare('SELECT id FROM users ORDER BY id ASC LIMIT 1').get() as { id: number }; return createSession(first.id); } async function listenWithScan( app: ReturnType, host: string, start: number, attempts = 50, ): Promise<{ server: Server; port: number }> { for (let port = start; port < start + attempts; port++) { const server = await tryListen(app, host, port); if (server) return { server, port }; } throw new Error(`No free port found in ${start}–${start + attempts - 1}`); } function tryListen(app: ReturnType, host: string, port: number): Promise { return new Promise((resolve) => { const server = app.listen(port, host); server.once('listening', () => resolve(server)); server.once('error', (err: NodeJS.ErrnoException) => { if (err.code === 'EADDRINUSE') resolve(null); else resolve(null); }); }); }