home-hosted 0.6.1 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","names":[],"sources":["../src/cli/args.ts","../src/cli/io.ts","../src/helpers/cookies.ts","../src/helpers/factory.ts","../src/shared/contracts.ts","../src/helpers/openapi.ts","../src/helpers/validator.ts","../src/middleware/loopback.ts","../src/helpers/atomic.ts","../src/config/secrets.ts","../src/services/auth.ts","../src/middleware/auth.ts","../src/helpers/bind.ts","../src/services/exposure.ts","../src/api/auth/$.routes.ts","../src/helpers/logger.ts","../src/helpers/validate.ts","../src/helpers/paths.ts","../src/services/state.ts","../src/api/backups.ts","../src/helpers/deferred.ts","../src/api/control.ts","../src/api/events.ts","../src/api/health.ts","../src/api/logs.ts","../src/api/metrics.ts","../src/providers/telegram.ts","../src/api/notifications.ts","../src/config/migrations.ts","../src/config/schema.ts","../src/helpers/version.ts","../src/config/parse.ts","../src/config/seed.ts","../src/config/store.ts","../src/api/servers/$.routes.ts","../src/api/settings.ts","../src/api/state.ts","../src/api/static.ts","../src/api/tls.ts","../src/helpers/error.ts","../src/openapi.ts","../src/app.ts","../src/helpers/daemon.ts","../src/helpers/open.ts","../src/helpers/template.ts","../src/providers/port.ts","../src/helpers/env-file.ts","../src/providers/archive.ts","../src/helpers/backoff.ts","../src/providers/health-check.ts","../src/providers/proc.ts","../src/providers/identity.ts","../src/providers/process.ts","../src/services/dependencies.ts","../src/services/log-buffer.ts","../src/services/supervisor.ts","../src/shared/generated.ts","../src/services/backups.ts","../src/services/config-watch.ts","../src/services/control-server.ts","../src/services/events.ts","../src/services/history.ts","../src/providers/host.ts","../src/services/host-monitor.ts","../src/services/log-files.ts","../src/services/notifications.ts","../src/services/tls.ts","../src/services/ui.ts","../src/providers/ui-release.ts","../src/services/ui-update.ts","../src/index.ts","../src/cli/up.ts","../src/cli/down.ts","../src/cli/restart.ts","../src/cli/status.ts","../src/cli/set-password.ts","../src/cli/set-token.ts","../src/cli/migrate.ts","../src/services/init.ts","../src/cli/init.ts","../src/cli/ui-switch.ts","../src/cli/ui-update.ts","../src/cli/ui-revert.ts","../src/cli.ts"],"sourcesContent":["import type { ArgsDef } from 'citty'\nimport path from 'node:path'\nimport process from 'node:process'\n\n/**\n * The argument work that happens *before* citty: `--home`/`--project` have to\n * change the environment before any `#src` module resolves a path, and the\n * curated surface (`help`, `version`, `unknown command`) has to stay byte-for-\n * byte what it always was. Both live here so they can be tested without a\n * terminal, a daemon or a spawned process; this module imports node builtins\n * only and never reads the state directories itself.\n */\n\nexport interface DirFlags {\n project?: string\n home?: string\n}\n\nexport interface DirFlagResult extends DirFlags {\n rest: string[]\n error?: string\n}\n\n/** `--home`/`--project` are handled for every command, so they are peeled off first. */\nexport function extractDirFlags(argv: string[]): DirFlagResult {\n const rest: string[] = []\n let project: string | undefined\n let home: string | undefined\n\n for (let index = 0; index < argv.length; index++) {\n const arg = argv[index]!\n const equals = arg.indexOf('=')\n const name = equals === -1 ? arg : arg.slice(0, equals)\n if (name !== '--project' && name !== '--home') {\n rest.push(arg)\n continue\n }\n const value = equals === -1 ? argv[++index] : arg.slice(equals + 1)\n if (value === undefined || value.length === 0)\n return { rest, project, home, error: `${name} needs a directory` }\n if (name === '--project')\n project = value\n else\n home = value\n }\n\n return { rest, project, home }\n}\n\n/** Set before any state module is imported, so it decides where state lives. */\nexport function applyDirFlags(flags: DirFlags): void {\n if (flags.project !== undefined)\n process.env.HHOSTED_PROJECT = path.resolve(flags.project)\n if (flags.home !== undefined)\n process.env.HHOSTED_HOME = path.resolve(flags.home)\n}\n\nexport type Invocation\n = | { kind: 'help', command?: string }\n | { kind: 'version' }\n | { kind: 'command', argv: string[] }\n | { kind: 'unknown', command: string }\n\nconst HELP_TOKENS = new Set(['help', '--help', '-h'])\nconst VERSION_TOKENS = new Set(['version', '--version', '-v'])\n// After a command, only the flag forms count: a bare `help` may be the value of an\n// option (`init --name help`), and answering that with the usage text would skip\n// the command instead of running it.\nconst HELP_FLAGS = new Set(['--help', '-h'])\nconst VERSION_FLAGS = new Set(['--version', '-v'])\n\n/**\n * What the stripped argv means, before citty sees it.\n *\n * `help`/`version` are commands here, not flags, and a help or version flag on a\n * command is answered the same way instead of being parsed as one of that\n * command's options. A help flag after a command keeps that command's name, so\n * the curated text can stay scoped to it; the bare `help` (or a top-level flag)\n * is the whole reference. A first token that starts with `-` is the one-shot\n * form (`home-hosted -p 4000`), so `up` is prepended. Anything else has to name\n * a command, which keeps the old `unknown command:` text exact.\n */\nexport function resolveInvocation(argv: string[], commands: readonly string[]): Invocation {\n if (argv.length === 0)\n return { kind: 'command', argv: ['up'] }\n\n const first = argv[0]!\n if (HELP_TOKENS.has(first))\n return { kind: 'help' }\n if (VERSION_TOKENS.has(first))\n return { kind: 'version' }\n\n if (first.startsWith('-'))\n return { kind: 'command', argv: ['up', ...argv] }\n\n if (!commands.includes(first))\n return { kind: 'unknown', command: first }\n\n for (const arg of argv.slice(1)) {\n if (HELP_FLAGS.has(arg))\n return { kind: 'help', command: first }\n if (VERSION_FLAGS.has(arg))\n return { kind: 'version' }\n }\n\n return { kind: 'command', argv }\n}\n\nconst CAMEL = /[A-Z]/g\n\n/** citty takes camelCase definitions; the flag a person types is kebab-case. */\nfunction kebab(name: string): string {\n return name.replace(CAMEL, match => `-${match.toLowerCase()}`)\n}\n\nfunction aliasesOf(def: ArgsDef[string]): string[] {\n if (def === undefined || !('alias' in def) || def.alias === undefined)\n return []\n return Array.isArray(def.alias) ? def.alias : [def.alias]\n}\n\nfunction findArg(argsDef: ArgsDef, name: string): ArgsDef[string] | undefined {\n for (const [key, def] of Object.entries(argsDef)) {\n if (def === undefined)\n continue\n const names = new Set([key, kebab(key), ...aliasesOf(def)])\n if (names.has(name))\n return def\n // `--no-<flag>` is citty's negation of a declared boolean, never an option.\n if (def.type === 'boolean' && (name === `no-${key}` || name === `no-${kebab(key)}`))\n return def\n }\n return undefined\n}\n\n/**\n * citty parses permissively (`strict: false`), so a mistyped flag would quietly do\n * nothing — `--autostart` where `--no-autostart` was meant would start the panel\n * with the wrong policy. This keeps the refusal the command line always had, from\n * citty's own definitions rather than a second list.\n *\n * A command that declares no arguments parses its own argv (and rejects its own\n * unknown flags), so it is left alone. Returns the message to print, or null.\n */\nexport function rejectUnknownFlags(argv: string[], argsDef: ArgsDef | undefined): string | null {\n if (argsDef === undefined || Object.keys(argsDef).length === 0)\n return null\n\n for (let index = 0; index < argv.length; index++) {\n const token = argv[index]!\n if (token === '--')\n return null\n if (!token.startsWith('-') || token.length === 1)\n return `Unexpected argument '${token}'`\n\n const body = token.startsWith('--') ? token.slice(2) : token.slice(1)\n const equals = body.indexOf('=')\n const name = equals === -1 ? body : body.slice(0, equals)\n const def = name.length === 0 ? undefined : findArg(argsDef, name)\n if (def === undefined)\n return `Unknown option '${token}'`\n // A string option consumes the next token, unless it was given inline.\n if (def.type === 'string' && equals === -1 && argv[index + 1] === undefined)\n return `Option '${token}' needs a value`\n if (def.type === 'string' && equals === -1)\n index += 1\n }\n\n return null\n}\n\nexport interface UpFlags {\n config?: string\n port?: number\n host?: string\n autostart: boolean\n open: boolean\n foreground: boolean\n printConfig: boolean\n}\n\n/**\n * The daemon gets the same instructions, but never `--foreground` (that is what\n * makes it the daemon) and never `--print-config` (that one is answered in the\n * calling process).\n */\nexport function buildDaemonArgv(flags: UpFlags): string[] {\n const args: string[] = ['up', '--foreground']\n if (flags.config !== undefined)\n args.push('--config', flags.config)\n if (flags.port !== undefined)\n args.push('--port', String(flags.port))\n if (flags.host !== undefined)\n args.push('--host', flags.host)\n if (!flags.autostart)\n args.push('--no-autostart')\n if (flags.open)\n args.push('--open')\n return args\n}\n","import process from 'node:process'\nimport readline from 'node:readline'\n\n/**\n * The bits every command shares: the colours, the failure shape, and the one\n * place readline is set up. Imported by `src/cli.ts` and by the command modules\n * it loads lazily, so it may not read the state directories — the only imports\n * here are node builtins.\n */\n\nexport const isTty = (): boolean => process.stdout.isTTY === true\n\nexport const paint = (code: string, text: string): string => (isTty() ? `\\x1B[${code}m${text}\\x1B[0m` : text)\nexport const dim = (text: string): string => paint('2', text)\nexport const bold = (text: string): string => paint('1', text)\nexport const cyan = (text: string): string => paint('36', text)\nexport const green = (text: string): string => paint('32', text)\n/** A section title in the help output. */\nexport const heading = (text: string): string => paint('1;4', text)\n\n/** The seam `ui-switch` takes, so the command stays testable without a terminal. */\nexport const style = { bold, dim, green }\n\n/** One shape for every failure: a red `error <message>` on stderr and exit 1. */\nexport function fail(message: string): never {\n process.stderr.write(`${paint('31', 'error')} ${message}\\n`)\n process.exit(1)\n}\n\nexport function delay(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms))\n}\n\n/** A plain y/N question, for decisions that are not secrets. */\nexport function prompt(question: string): Promise<string> {\n return new Promise((resolve) => {\n const rl = readline.createInterface({ input: process.stdin, output: process.stdout })\n rl.question(question, (answer) => {\n rl.close()\n resolve(answer)\n })\n })\n}\n\n/** Reads a line with echo suppressed, so the password never lands in scrollback. */\nexport function promptHidden(question: string): Promise<string> {\n return new Promise((resolve) => {\n const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: true })\n const onData = (): void => {\n readline.clearLine(process.stdout, 0)\n readline.cursorTo(process.stdout, 0)\n process.stdout.write(question)\n }\n\n process.stdin.on('data', onData)\n rl.question(question, (answer) => {\n process.stdin.off('data', onData)\n rl.close()\n process.stdout.write('\\n')\n resolve(answer)\n })\n })\n}\n\n/** Asks a yes/no question with a default, so an empty answer is a real answer. */\nexport async function confirm(question: string, fallback: boolean): Promise<boolean> {\n const answer = (await prompt(`${question} ${fallback ? '[Y/n]' : '[y/N]'} `)).trim().toLowerCase()\n if (answer.length === 0)\n return fallback\n return answer === 'y' || answer === 'yes'\n}\n","/** Minimal cookie helpers, enough for one httpOnly session cookie. */\nexport interface CookieOptions {\n maxAgeMs?: number\n httpOnly?: boolean\n sameSite?: 'Strict' | 'Lax' | 'None'\n secure?: boolean\n path?: string\n}\n\nexport function parseCookies(header: string | null | undefined): Record<string, string> {\n const cookies: Record<string, string> = {}\n if (!header)\n return cookies\n for (const part of header.split(';')) {\n const separator = part.indexOf('=')\n if (separator < 0)\n continue\n const name = part.slice(0, separator).trim()\n if (name.length === 0)\n continue\n const value = part.slice(separator + 1).trim()\n try {\n cookies[name] = decodeURIComponent(value)\n }\n catch {\n cookies[name] = value\n }\n }\n return cookies\n}\n\nexport function serializeCookie(name: string, value: string, options: CookieOptions = {}): string {\n const parts = [`${name}=${encodeURIComponent(value)}`]\n parts.push(`Path=${options.path ?? '/'}`)\n if (options.maxAgeMs !== undefined)\n parts.push(`Max-Age=${Math.max(0, Math.floor(options.maxAgeMs / 1000))}`)\n if (options.httpOnly !== false)\n parts.push('HttpOnly')\n parts.push(`SameSite=${options.sameSite ?? 'Strict'}`)\n if (options.secure)\n parts.push('Secure')\n return parts.join('; ')\n}\n","import { createFactory } from 'hono/factory'\n\n/**\n * Every route is built from this factory and chained (`app.get(...).post(...)`)\n * so Hono keeps the full route map in its type — which is what `AppType` exports\n * for `hc<AppType>` clients and for the generated OpenAPI document.\n */\nexport const appFactory = createFactory()\n","import { type } from 'arktype'\n\n/**\n * Schemas shared by the control plane and the SPA: the server entry shape, the\n * control panel's own settings, API DTOs and SSE frames. Nothing here knows\n * about a particular server — an entry carries its own command, args, env and\n * bootstrap.\n */\n\n/** `local` -> 127.0.0.1, `lan` -> 0.0.0.0, or an explicit IPv4 to bind. */\nexport const bindSchema = type('\"local\" | \"lan\" | /^\\\\d{1,3}(?:\\\\.\\\\d{1,3}){3}$/')\nexport type Bind = typeof bindSchema.infer\n\n/** Parses a bind value (`local` | `lan` | ipv4); null when it is not one. */\nexport function parseBind(value: string): Bind | null {\n const parsed = bindSchema(value)\n return parsed instanceof type.errors ? null : parsed\n}\n\n/** `null` means \"no port\": no readiness probe, no health supervision, no preflight. */\nexport const portSchema = type('1 <= number.integer <= 65535 | null')\n\n/**\n * What to do when something already listens on the entry's port. `follow` and\n * `reclaim` only ever act on a holder proven to be this entry's own detached\n * successor (the `HHOSTED_SERVER_ID` marker, POSIX only); `kill` is the blunt one:\n * it stops any holder except the panel's own process tree.\n */\nexport const onPortConflictSchema = type.enumerated('block', 'warn', 'follow', 'reclaim', 'kill')\nexport type OnPortConflict = typeof onPortConflictSchema.infer\n\n/** The stored forms carry the default; a patch must not (see the patch schemas below). */\nexport const onPortConflictDefaultSchema = onPortConflictSchema.default('block')\n\nexport const restartSchema = type({\n enabled: 'boolean = true',\n maxRetries: 'number.integer >= 0 = 3',\n baseDelayMs: 'number >= 0 = 1000',\n factor: 'number >= 1 = 2',\n maxDelayMs: 'number >= 0 = 30000',\n /** A process alive this long is considered healthy again and the retry counter resets. */\n resetAfterMs: 'number >= 0 = 60000',\n}).onUndeclaredKey('reject')\n\nexport type RestartConfig = typeof restartSchema.infer\n\nexport const httpCheckSchema = type({\n /** Path on the server's own port, e.g. `/healthz`. */\n path: 'string = \"/\"',\n method: '\"GET\" | \"HEAD\" = \"GET\"',\n /** Exact status to accept; `null` (or omitted) means any status below `expectStatusBelow`. */\n expectStatus: 'number.integer | null?',\n expectStatusBelow: 'number.integer = 400',\n /** Substring that must appear in the response body. */\n expectBody: 'string = \"\"',\n}).onUndeclaredKey('reject')\nexport type HttpCheckConfig = typeof httpCheckSchema.infer\n\n/** Restart guards for the process tree. */\nexport const resourcesSchema = type({\n /** Restart when the tree's RSS exceeds this; 0 disables. */\n maxRssBytes: 'number.integer >= 0 = 0',\n}).onUndeclaredKey('reject')\nexport type ResourcesConfig = typeof resourcesSchema.infer\n\nexport const healthSchema = type({\n enabled: 'boolean = true',\n /** `port` = TCP connect only; `http` = fetch `http.path` and assert the response. */\n mode: '\"port\" | \"http\" = \"port\"',\n http: httpCheckSchema.default(() => ({})),\n intervalMs: 'number >= 500 = 5000',\n timeoutMs: 'number >= 100 = 1500',\n /** Consecutive failed probes before the warning state is shown. */\n unhealthyThreshold: 'number.integer >= 1 = 3',\n /** 0 disables it; otherwise a port stuck unhealthy this long forces a restart. */\n forceRestartAfterMs: 'number >= 0 = 0',\n /** How long to wait for the port to accept connections after spawn. */\n startTimeoutMs: 'number >= 0 = 20000',\n}).onUndeclaredKey('reject')\n\nexport type HealthConfig = typeof healthSchema.infer\n\nexport const stopSchema = type({\n signal: '\"SIGTERM\" | \"SIGINT\" | \"SIGKILL\" = \"SIGTERM\"',\n killGroup: 'boolean = true',\n graceMs: 'number >= 0 = 5000',\n /** Last resort for wrappers that detach their real server. */\n killPortHolders: 'boolean = false',\n}).onUndeclaredKey('reject')\n\nexport type StopConfig = typeof stopSchema.infer\n\nexport const bootstrapSchema = type({\n command: 'string',\n args: type('string[]').default(() => []),\n env: type('Record<string, string>').default(() => ({})),\n timeoutMs: 'number >= 1000 = 120000',\n /** Run once per `up` session; whatever it installs persists on disk. */\n runOnce: 'boolean = true',\n}).onUndeclaredKey('reject')\nexport type BootstrapConfig = typeof bootstrapSchema.infer\nexport const bootstrapOrNullSchema = bootstrapSchema.or(type('null'))\n\nexport const logBufferLinesSchema = type('50 <= number.integer <= 100000')\n\nexport const serverSchema = type({\n id: '/^[a-z0-9][a-z0-9_-]*$/',\n label: 'string?',\n enabled: 'boolean = true',\n autostart: 'boolean = false',\n command: 'string >= 1',\n args: type('string[]').default(() => []),\n cwd: 'string = \".\"',\n env: type('Record<string, string>').default(() => ({})),\n /**\n * `ENV=path` pairs: exported to the process (overriding `env`) *and* the path\n * is backed up automatically — one declaration for data directories.\n */\n dataEnvs: type('Record<string, string>').default(() => ({})),\n bootstrap: bootstrapOrNullSchema.optional(),\n port: portSchema.optional(),\n bind: bindSchema.default(() => 'local' as const),\n onPortConflict: onPortConflictDefaultSchema,\n restart: restartSchema.default(() => ({})),\n health: healthSchema.default(() => ({})),\n stop: stopSchema.default(() => ({})),\n logBufferLines: logBufferLinesSchema.default(() => 500),\n /** Ids this server needs running first (and healthy); stopped in reverse order. */\n dependsOn: type('string[]').default(() => []),\n /** Optional KEY=value file loaded at spawn; its values override `env`. */\n envFile: 'string = \"\"',\n resources: resourcesSchema.default(() => ({})),\n /** Paths included in backups for this server (templates allowed). */\n backupPaths: type('string[]').default(() => []),\n /**\n * Skip well-known build output and dependency directories (`node_modules`,\n * `dist`, `.next`, caches, …) inside the paths this entry declares.\n */\n backupIgnoreGenerated: 'boolean = true',\n}).onUndeclaredKey('reject')\nexport type ServerConfig = Omit<typeof serverSchema.infer, 'port'> & { port: number | null }\n\n/**\n * Authentication for the control panel itself. The password never lives here —\n * only the policy does; its scrypt hash sits in a git-ignored secrets file.\n */\nexport const authSchema = type({\n enabled: 'boolean = true',\n sessionTtlMs: 'number >= 60000 = 604800000',\n /** `auto` adds `Secure` when the request arrived over https (proxy-aware). */\n cookieSecure: '\"auto\" | \"always\" | \"never\" = \"auto\"',\n /** Trust `x-forwarded-*` from a reverse proxy; also drives the client IP. */\n trustProxy: 'boolean = false',\n maxLoginAttempts: 'number.integer >= 1 = 5',\n lockoutMs: 'number >= 1000 = 60000',\n}).onUndeclaredKey('reject')\nexport type AuthConfig = typeof authSchema.infer\n\n/** Outbound crash/health notifications. The bot token lives in the secrets file. */\nexport const telegramSchema = type({\n enabled: 'boolean = false',\n chatId: 'string = \"\"',\n onCrash: 'boolean = true',\n onUnhealthy: 'boolean = true',\n onForcedRestart: 'boolean = true',\n onRecovered: 'boolean = false',\n /** Host vitals breaches (disk, memory, swap, load, temperature). */\n onHost: 'boolean = true',\n /** Per server *and* reason, so a flapping server cannot spam the chat. */\n cooldownMs: 'number >= 0 = 120000',\n}).onUndeclaredKey('reject')\nexport type TelegramConfig = typeof telegramSchema.infer\n\nexport const notificationsSchema = type({\n telegram: telegramSchema.default(() => ({})),\n}).onUndeclaredKey('reject')\nexport type NotificationsConfig = typeof notificationsSchema.infer\n\n/** On-disk log retention for the Logs page. */\nexport const logsSchema = type({\n persist: 'boolean = true',\n /** Per server, before rotating to `.1`, `.2`, ... */\n maxBytes: '10000 <= number <= 100000000 = 2000000',\n keep: '1 <= number.integer <= 10 = 3',\n}).onUndeclaredKey('reject')\nexport type LogsConfig = typeof logsSchema.infer\n\nexport const tlsSchema = type({\n enabled: 'boolean = false',\n}).onUndeclaredKey('reject')\nexport type TlsConfig = typeof tlsSchema.infer\n\n/** Host-level vitals and their alert thresholds. */\nexport const hostSchema = type({\n enabled: 'boolean = true',\n intervalMs: 'number >= 5000 = 15000',\n /** Filesystems reported and alerted on; templates and `~` are expanded. */\n diskPaths: type('string[]').default(() => ['.']),\n /** 0 disables an individual alert. */\n diskUsedPercent: 'number >= 0 = 90',\n memoryUsedPercent: 'number >= 0 = 90',\n swapUsedPercent: 'number >= 0 = 50',\n loadPerCpu: 'number >= 0 = 2',\n tempCelsius: 'number >= 0 = 85',\n}).onUndeclaredKey('reject')\nexport type HostConfig = typeof hostSchema.infer\n\n/** Tar archives of config, secrets, TLS and declared data paths. */\nexport const backupsSchema = type({\n enabled: 'boolean = true',\n dir: 'string = \".backups\"',\n keep: 'number.integer >= 1 = 5',\n /** Extra paths in every backup, in addition to each server's `backupPaths`. */\n includePaths: type('string[]').default(() => []),\n}).onUndeclaredKey('reject')\nexport type BackupsConfig = typeof backupsSchema.infer\n\nexport const controlSchema = type({\n /** What the panel calls itself; the stock UI shows it in the sidebar. */\n label: '1 <= string <= 60 = \"home-hosted\"',\n port: '1 <= number.integer <= 65535 = 3999',\n /** Where the control panel itself listens; keep it `local` unless you mean it. */\n host: bindSchema.default(() => 'local' as const),\n openBrowser: 'boolean = false',\n auth: authSchema.default(() => ({})),\n tls: tlsSchema.default(() => ({})),\n}).onUndeclaredKey('reject')\nexport type ControlConfig = typeof controlSchema.infer\n\n/** Applied to every server entry; whatever an entry sets wins. */\nexport const defaultsSchema = type({\n enabled: 'boolean = true',\n autostart: 'boolean = false',\n bind: bindSchema.default(() => 'local' as const),\n onPortConflict: onPortConflictDefaultSchema,\n restart: restartSchema.default(() => ({})),\n health: healthSchema.default(() => ({})),\n stop: stopSchema.default(() => ({})),\n logBufferLines: logBufferLinesSchema.default(() => 500),\n}).onUndeclaredKey('reject')\nexport type ServerDefaults = typeof defaultsSchema.infer\n\n// Patch variants stay default-free: an API client sends only what it changes, so\n// a partial nested group must not silently pull in the code defaults.\nconst restartPatchSchema = type({\n enabled: 'boolean?',\n maxRetries: 'number.integer >= 0?',\n baseDelayMs: 'number >= 0?',\n factor: 'number >= 1?',\n maxDelayMs: 'number >= 0?',\n resetAfterMs: 'number >= 0?',\n}).onUndeclaredKey('reject')\n\nconst httpCheckPatchSchema = type({\n path: 'string?',\n method: '\"GET\" | \"HEAD\"?',\n expectStatus: 'number.integer | null?',\n expectStatusBelow: 'number.integer?',\n expectBody: 'string?',\n}).onUndeclaredKey('reject')\n\nconst resourcesPatchSchema = type({\n maxRssBytes: 'number.integer >= 0?',\n}).onUndeclaredKey('reject')\n\nconst healthPatchSchema = type({\n enabled: 'boolean?',\n mode: '\"port\" | \"http\"?',\n http: httpCheckPatchSchema.optional(),\n intervalMs: 'number >= 500?',\n timeoutMs: 'number >= 100?',\n unhealthyThreshold: 'number.integer >= 1?',\n forceRestartAfterMs: 'number >= 0?',\n startTimeoutMs: 'number >= 0?',\n}).onUndeclaredKey('reject')\n\nconst stopPatchSchema = type({\n signal: '\"SIGTERM\" | \"SIGINT\" | \"SIGKILL\"?',\n killGroup: 'boolean?',\n graceMs: 'number >= 0?',\n killPortHolders: 'boolean?',\n}).onUndeclaredKey('reject')\n\nconst authPatchSchema = type({\n enabled: 'boolean?',\n sessionTtlMs: 'number >= 60000?',\n cookieSecure: '\"auto\" | \"always\" | \"never\"?',\n trustProxy: 'boolean?',\n maxLoginAttempts: 'number.integer >= 1?',\n lockoutMs: 'number >= 1000?',\n}).onUndeclaredKey('reject')\n\nconst telegramPatchSchema = type({\n enabled: 'boolean?',\n chatId: 'string?',\n onCrash: 'boolean?',\n onUnhealthy: 'boolean?',\n onForcedRestart: 'boolean?',\n onRecovered: 'boolean?',\n onHost: 'boolean?',\n cooldownMs: 'number >= 0?',\n}).onUndeclaredKey('reject')\n\nconst notificationsPatchSchema = type({\n telegram: telegramPatchSchema.optional(),\n}).onUndeclaredKey('reject')\n\nconst hostPatchSchema = type({\n enabled: 'boolean?',\n intervalMs: 'number >= 5000?',\n diskPaths: type('string[]').optional(),\n diskUsedPercent: 'number >= 0?',\n memoryUsedPercent: 'number >= 0?',\n swapUsedPercent: 'number >= 0?',\n loadPerCpu: 'number >= 0?',\n tempCelsius: 'number >= 0?',\n}).onUndeclaredKey('reject')\n\nconst backupsPatchSchema = type({\n enabled: 'boolean?',\n dir: 'string?',\n keep: 'number.integer >= 1?',\n includePaths: type('string[]').optional(),\n}).onUndeclaredKey('reject')\n\nconst logsPatchSchema = type({\n persist: 'boolean?',\n maxBytes: '10000 <= number <= 100000000?',\n keep: '1 <= number.integer <= 10?',\n}).onUndeclaredKey('reject')\n\nconst tlsPatchSchema = type({\n enabled: 'boolean?',\n}).onUndeclaredKey('reject')\n\nconst controlPatchSchema = type({\n label: '1 <= string <= 60?',\n port: '1 <= number.integer <= 65535?',\n host: bindSchema.optional(),\n openBrowser: 'boolean?',\n auth: authPatchSchema.optional(),\n tls: tlsPatchSchema.optional(),\n}).onUndeclaredKey('reject')\n\nconst defaultsPatchSchema = type({\n enabled: 'boolean?',\n autostart: 'boolean?',\n bind: bindSchema.optional(),\n onPortConflict: onPortConflictSchema.optional(),\n restart: restartPatchSchema.optional(),\n health: healthPatchSchema.optional(),\n stop: stopPatchSchema.optional(),\n logBufferLines: logBufferLinesSchema.optional(),\n}).onUndeclaredKey('reject')\n\nconst editableFields = {\n label: 'string?',\n enabled: 'boolean?',\n autostart: 'boolean?',\n command: 'string?',\n args: 'string[]?',\n cwd: 'string?',\n env: 'Record<string, string>?',\n dataEnvs: 'Record<string, string>?',\n bootstrap: bootstrapOrNullSchema.optional(),\n port: portSchema.optional(),\n bind: bindSchema.optional(),\n onPortConflict: onPortConflictSchema.optional(),\n restart: restartPatchSchema.optional(),\n health: healthPatchSchema.optional(),\n stop: stopPatchSchema.optional(),\n logBufferLines: logBufferLinesSchema.optional(),\n dependsOn: type('string[]').optional(),\n envFile: 'string?',\n resources: resourcesPatchSchema.optional(),\n backupPaths: type('string[]').optional(),\n backupIgnoreGenerated: 'boolean?',\n} as const\n\nexport const serverPatchSchema = type(editableFields).onUndeclaredKey('reject')\nexport type ServerPatch = typeof serverPatchSchema.infer\n\nexport const serverCreateSchema = type({\n id: '/^[a-z0-9][a-z0-9_-]*$/',\n ...editableFields,\n command: 'string',\n}).onUndeclaredKey('reject')\nexport type ServerCreate = typeof serverCreateSchema.infer\n\n/** Edits to the panel's own control block and to the global server defaults. */\nexport const settingsPatchSchema = type({\n control: controlPatchSchema.optional(),\n defaults: defaultsPatchSchema.optional(),\n logs: logsPatchSchema.optional(),\n notifications: notificationsPatchSchema.optional(),\n host: hostPatchSchema.optional(),\n backups: backupsPatchSchema.optional(),\n}).onUndeclaredKey('reject')\nexport type SettingsPatch = typeof settingsPatchSchema.infer\n\nexport const authStatusSchema = type({\n enabled: 'boolean',\n passwordSet: 'boolean',\n passwordUpdatedAt: 'number | null',\n /**\n * An API token is set; it is shown here as a flag, never as a value. Optional so\n * a freshly built UI still parses the state of a panel process that predates it:\n * an upgrade replaces `uis/stock/dist` on disk while the old process keeps\n * serving, and a missing key must not blank the whole app.\n */\n apiTokenSet: 'boolean?',\n /** Still the boot-time default; the login page says so and exposure stays blocked. */\n usingDefaultPassword: 'boolean',\n /** The panel currently listens beyond loopback. */\n exposed: 'boolean',\n /** Non-null when that exposure is not backed by a password. */\n blockedReason: 'string | null',\n sessionTtlMs: 'number',\n cookieSecure: 'string',\n trustProxy: 'boolean',\n maxLoginAttempts: 'number',\n lockoutMs: 'number',\n})\nexport type AuthStatus = typeof authStatusSchema.infer\n\nexport const tlsStatusSchema = type({\n enabled: 'boolean',\n certPresent: 'boolean',\n subject: 'string | null',\n issuer: 'string | null',\n validFrom: 'string | null',\n validTo: 'string | null',\n daysRemaining: 'number | null',\n fingerprint: 'string | null',\n keyMatches: 'boolean | null',\n error: 'string | null',\n})\nexport type TlsStatus = typeof tlsStatusSchema.infer\n\nexport const telegramStatusSchema = type({\n enabled: 'boolean',\n tokenSet: 'boolean',\n chatId: 'string',\n onCrash: 'boolean',\n onUnhealthy: 'boolean',\n onForcedRestart: 'boolean',\n onRecovered: 'boolean',\n /** Host vitals breaches (disk, memory, swap, load, temperature). */\n onHost: 'boolean',\n cooldownMs: 'number',\n /** Last delivery outcome, for the settings page. */\n lastResult: 'string | null',\n lastResultAt: 'number | null',\n})\nexport type TelegramStatus = typeof telegramStatusSchema.infer\n\nexport const notificationViewSchema = type({\n telegram: telegramStatusSchema,\n})\nexport type NotificationView = typeof notificationViewSchema.infer\n\nexport const sessionViewSchema = type({\n authenticated: 'boolean',\n authRequired: 'boolean',\n passwordSet: 'boolean',\n /** A long-lived API token is configured; optional for the same reason as above. */\n apiTokenSet: 'boolean?',\n usingDefaultPassword: 'boolean',\n /** The boot-time password, exposed only while it is still in use. */\n defaultPassword: 'string | null',\n sessionTtlMs: 'number',\n})\nexport type SessionView = typeof sessionViewSchema.infer\n\nexport const loginSchema = type({ password: 'string' }).onUndeclaredKey('reject')\nexport type LoginRequest = typeof loginSchema.infer\n\n/** Any non-empty password is allowed; only a sanity cap on the length. */\nexport const passwordValueSchema = type('1 <= string <= 512')\n\nexport const passwordSchema = type({\n currentPassword: 'string?',\n newPassword: passwordValueSchema,\n}).onUndeclaredKey('reject')\nexport type PasswordRequest = typeof passwordSchema.infer\n\nexport const serverStatusSchema = type('\"stopped\" | \"starting\" | \"running\" | \"stopping\" | \"backoff\" | \"crashed\" | \"conflict\"')\nexport type ServerStatus = typeof serverStatusSchema.infer\n\nexport const healthStateSchema = type('\"disabled\" | \"unknown\" | \"healthy\" | \"unhealthy\"')\nexport type HealthState = typeof healthStateSchema.infer\n\nexport const portStateSchema = type('\"unknown\" | \"free\" | \"in-use\"')\nexport type PortState = typeof portStateSchema.infer\n\nexport const logStreamSchema = type('\"stdout\" | \"stderr\" | \"system\"')\nexport type LogStream = typeof logStreamSchema.infer\n\nexport const logLineSchema = type({\n ts: 'number',\n stream: logStreamSchema,\n text: 'string',\n})\nexport type LogLine = typeof logLineSchema.infer\n\nexport const historyEventSchema = type({\n serverId: 'string',\n ts: 'number',\n type: '\"start\" | \"exit\" | \"crash\" | \"forced-restart\" | \"unhealthy\" | \"recovered\"',\n detail: 'string',\n /** How long the process had been up, recorded on exit and crash. */\n runtimeMs: 'number?',\n})\nexport type HistoryEvent = typeof historyEventSchema.infer\n\n/** Rolling window stats derived from the persisted event log. */\nexport const serverHistorySchema = type({\n windowMs: 'number',\n /** Share of the window the process was up (null when nothing is known yet). */\n uptimeRatio: 'number | null',\n restarts: 'number',\n crashes: 'number',\n forcedRestarts: 'number',\n lastCrashAt: 'number | null',\n lastExitAt: 'number | null',\n lastRuntimeMs: 'number | null',\n events: historyEventSchema.array(),\n})\nexport type ServerHistory = typeof serverHistorySchema.infer\n\nexport const processResourcesSchema = type({\n cpuPercent: 'number | null',\n /** RSS of the process and its descendants. */\n rssBytes: 'number | null',\n processes: 'number',\n sampledAt: 'number',\n})\nexport type ProcessResources = typeof processResourcesSchema.infer\n\nexport const hostDiskSchema = type({\n path: 'string',\n totalBytes: 'number',\n freeBytes: 'number',\n usedPercent: 'number',\n})\n\nexport const hostViewSchema = type({\n enabled: 'boolean',\n cpus: 'number',\n loadAvg: type('number[]'),\n uptimeMs: 'number',\n memoryUsedPercent: 'number',\n swapUsedPercent: 'number',\n tempCelsius: 'number | null',\n disks: hostDiskSchema.array(),\n /** Human readable threshold breaches, for the banner and notifications. */\n alerts: type('string[]'),\n sampledAt: 'number | null',\n})\nexport type HostView = typeof hostViewSchema.infer\n\nexport const backupFileSchema = type({\n name: 'string',\n sizeBytes: 'number',\n createdAt: 'number',\n /** The archive carries a password-protected payload. */\n encrypted: 'boolean',\n})\n\nexport type BackupFile = typeof backupFileSchema.infer\n\n/** One declared data path, with the reason it will (or will not) be captured. */\nexport const backupPathSchema = type({\n path: 'string',\n /** Who declared it: `global`, `<serverId>:backupPaths` or `<serverId>:<ENV>`. */\n origin: 'string',\n /** false when a parent path already covers it, or it would swallow the archive dir. */\n included: 'boolean',\n note: 'string | null',\n /** The declaring entry asked for generated directories to be skipped. */\n ignoreGenerated: 'boolean?',\n})\nexport type BackupPath = typeof backupPathSchema.infer\n\nexport const backupsViewSchema = type({\n enabled: 'boolean',\n dir: 'string',\n keep: 'number',\n /** Extra paths from the config, in addition to each server's own. */\n includePaths: type('string[]'),\n /** Every declared path that will be picked up, for the UI to show. */\n paths: backupPathSchema.array(),\n files: backupFileSchema.array(),\n})\nexport type BackupsView = typeof backupsViewSchema.infer\n\n/** One restorable slice of an archive: the panel's own state or a data path. */\nexport const restoreItemSchema = type({\n /** `config` | `secrets` | `tls`, or the data path itself. */\n id: 'string',\n label: 'string',\n kind: '\"config\" | \"secrets\" | \"tls\" | \"data\"',\n /** false when the current config does not declare it, or it is not in the archive. */\n restorable: 'boolean',\n /** Echo of the request's selection, so the checkboxes round-trip. */\n selected: 'boolean',\n note: 'string | null',\n})\nexport type RestoreItem = typeof restoreItemSchema.infer\n\nexport const restorePlanSchema = type({\n dryRun: 'boolean',\n encrypted: 'boolean',\n /** The archive needs a password (none or a wrong one was supplied). */\n needsPassword: 'boolean',\n items: restoreItemSchema.array(),\n applied: type('string[]'),\n skipped: type('string[]'),\n /** Only the panel's own listener needs a restart; its servers are re-read live. */\n restartRequired: 'boolean',\n /** The panel re-read the restored config within this same restore. */\n reloaded: 'boolean',\n error: 'string?',\n})\nexport type RestorePlan = typeof restorePlanSchema.infer\n\nexport const backupCreateSchema = type({\n /** Optional: encrypts the archive. Never stored. */\n password: passwordValueSchema.optional(),\n}).onUndeclaredKey('reject')\nexport type BackupCreate = typeof backupCreateSchema.infer\n\nexport const restoreRequestSchema = type({\n name: 'string?',\n password: passwordValueSchema.optional(),\n /** Item ids to restore; omitted means every restorable item. */\n include: type('string[]').optional(),\n}).onUndeclaredKey('reject')\nexport type RestoreRequest = typeof restoreRequestSchema.infer\n\n/** Runtime view of a server: its effective config plus everything observed. */\nexport const serverViewSchema = type({\n id: 'string',\n config: serverSchema,\n bindHost: 'string',\n url: 'string | null',\n status: serverStatusSchema,\n health: healthStateSchema,\n portState: portStateSchema,\n pid: 'number | null',\n /** True when the process serving this entry was adopted, not spawned by the panel. */\n adopted: 'boolean?',\n startedAt: 'number | null',\n exitCode: 'number | null',\n exitSignal: 'string | null',\n restarts: 'number',\n maxRetries: 'number',\n lastError: 'string | null',\n nextRetryAt: 'number | null',\n unhealthySince: 'number | null',\n bufferedLines: 'number',\n history: serverHistorySchema,\n /** Last health probe latency (TCP connect or HTTP request). */\n responseMs: 'number | null',\n resources: processResourcesSchema.or(type('null')),\n})\n// `config` is emitted normalized (port is always `number | null`, never absent),\n// while the schema accepts both forms so a hand-written payload still validates.\nexport type ServerView = Omit<typeof serverViewSchema.infer, 'config'> & { config: ServerConfig }\n\nexport const controlViewSchema = type({\n /** The configured panel name, for the shell to render. */\n label: 'string',\n port: 'number',\n /** The configured bind value (`local` | `lan` | ipv4). */\n host: 'string',\n /** The address actually bound. */\n bindHost: 'string',\n url: 'string',\n openBrowser: 'boolean',\n /** The live listener differs from the configured host/port. */\n restartRequired: 'boolean',\n protocol: 'string',\n auth: authStatusSchema,\n tls: tlsStatusSchema,\n})\nexport type ControlView = typeof controlViewSchema.infer\n\nexport const appStateSchema = type({\n control: controlViewSchema,\n defaults: defaultsSchema,\n logs: logsSchema,\n notifications: notificationViewSchema,\n host: hostViewSchema,\n backups: backupsViewSchema,\n configPath: 'string',\n configError: 'string | null',\n /** The directory the panel was started from; relative entry paths use it. */\n projectDir: 'string',\n /** `HHOSTED_HOME`: every file home-hosted owns lives under here. */\n dataRoot: 'string',\n logsDir: 'string',\n servers: serverViewSchema.array(),\n})\nexport type AppState = Omit<typeof appStateSchema.infer, 'servers'> & { servers: ServerView[] }\n\nexport const sseMessageSchema = type({\n type: '\"hello\" | \"state\" | \"log\" | \"server\"',\n ts: 'number',\n serverId: 'string?',\n state: appStateSchema.optional(),\n server: serverViewSchema.optional(),\n lines: logLineSchema.array().optional(),\n})\nexport type SseMessage = typeof sseMessageSchema.infer\n\nexport const logQuerySchema = type({\n limit: 'string?',\n})\n\n/**\n * What a `free-port` attempt did. The pids are reported so the UI can say which\n * process left, and which listeners were deliberately left alone because this\n * panel supervises them.\n */\nexport const freePortResultSchema = type({\n ok: 'boolean',\n port: 'number | null',\n /** Asked to leave with SIGTERM, and the ones that ignored it. */\n terminated: 'number[]',\n forced: 'number[]',\n /** Listeners this panel supervises; never signalled. */\n skipped: 'number[]',\n /** The port answers no more after the attempt. */\n free: 'boolean',\n})\nexport type FreePortResult = typeof freePortResultSchema.infer\n\nexport const logHistoryQuerySchema = type({\n tail: 'string?',\n /** Case-insensitive substring filter over the tail window. */\n search: 'string?',\n stream: '\"stdout\" | \"stderr\" | \"system\"?',\n})\n\nexport const logFileInfoSchema = type({\n name: 'string',\n sizeBytes: 'number',\n})\n\nexport const logServerViewSchema = type({\n serverId: 'string',\n label: 'string',\n status: serverStatusSchema,\n enabled: 'boolean',\n sizeBytes: 'number',\n files: logFileInfoSchema.array(),\n})\nexport type LogServerView = typeof logServerViewSchema.infer\n\nexport const logServersViewSchema = type({\n servers: logServerViewSchema.array(),\n})\n\nexport const logHistoryViewSchema = type({\n serverId: 'string',\n enabled: 'boolean',\n sizeBytes: 'number',\n files: type('string[]'),\n /** How many lines the search looked at, or null when not searching. */\n searched: 'number | null',\n lines: logLineSchema.array(),\n})\nexport type LogHistoryView = typeof logHistoryViewSchema.infer\n\nexport type TelegramToken = typeof telegramTokenSchema.infer\n\nexport const notificationActionSchema = type({\n /** Optional override, so the token can be tested before it is saved. */\n botToken: 'string?',\n chatId: 'string?',\n}).onUndeclaredKey('reject')\nexport type NotificationAction = typeof notificationActionSchema.infer\n\nexport const telegramTokenSchema = type({\n botToken: 'string >= 1',\n}).onUndeclaredKey('reject')\n\n/** The single error envelope every route answers failures with. */\nexport const apiErrorSchema = type({\n message: 'string',\n /** Stable, machine-readable; `AUTH_REQUIRED` also drives the login redirect. */\n code: 'string',\n detail: 'unknown',\n}).onUndeclaredKey('reject')\nexport type ApiError = typeof apiErrorSchema.infer\n\n/**\n * A user-supplied UI, as the settings page shows it.\n *\n * Every field is optional, and none of them is a fallback: a `ui.json` may be written by\n * the panel (which adds `uploadedAt`/`files`) **or dropped in by hand** following\n * `docs/UI_CREATION.md`, which documents only the author-facing fields. Requiring ours\n * meant a hand-written file parsed to nothing at all, taking `repo`/`tag` with it and\n * silently disabling `ui-update` for exactly the UI that declared itself.\n */\nexport const uiMetaSchema = type({\n 'name?': 'string',\n 'version?': 'string | null',\n /** Set by the panel, not by the author. */\n 'uploadedAt?': 'number',\n /** Counted by the panel, not declared. */\n 'files?': 'number.integer >= 1',\n /** `owner/name` of the UI's own repository, for `ui-update`. */\n 'repo?': 'string',\n /** The release tag this build came from, e.g. `v0.6.0`. */\n 'tag?': 'string',\n /** The release asset name, e.g. `home-hosted-ui-noc-console`. */\n 'asset?': 'string',\n /** When the UI was built, in unix epoch seconds. */\n 'unix?': 'number.integer >= 0',\n})\nexport type UiMeta = typeof uiMetaSchema.infer\n\nexport const uiStatusSchema = type({\n /** A user-supplied UI is being served instead of the stock one. */\n custom: 'boolean',\n /** Where that UI lives, whether or not it exists yet. */\n dir: 'string',\n meta: uiMetaSchema.or(type('null')),\n})\nexport type UiStatus = typeof uiStatusSchema.infer\n\nexport const tlsUploadSchema = type({\n certificate: 'string >= 1',\n privateKey: 'string >= 1',\n}).onUndeclaredKey('reject')\nexport type TlsUpload = typeof tlsUploadSchema.infer\n\n/** `GET /api/settings`: the panel's own configuration, as the settings page reads it. */\nexport const settingsViewSchema = type({\n control: controlViewSchema,\n defaults: defaultsSchema,\n logs: logsSchema,\n notifications: notificationViewSchema,\n host: hostSchema,\n backups: backupsViewSchema,\n ui: uiStatusSchema,\n})\nexport type SettingsView = typeof settingsViewSchema.infer\n\n/** `PATCH /api/settings` answers with the saved view, plus where the listener lands. */\nexport const settingsSavedSchema = settingsViewSchema.and(type({\n /** The listener is moving; reconnect at `targetUrl` when it stops being null. */\n rebinding: 'boolean',\n targetUrl: 'string | null',\n}))\nexport type SettingsSaved = typeof settingsSavedSchema.infer\n","import type { StandardSchemaV1 } from '@standard-schema/spec'\nimport { resolver } from 'hono-openapi'\nimport { apiErrorSchema } from '#src/shared/contracts'\n\n/**\n * A JSON response body for `describeRoute`. The resolver needs a real Standard\n * Schema, so only ArkType schemas go in here — never a hand-written JSON schema.\n */\nexport function jsonBody(schema: StandardSchemaV1) {\n return { 'application/json': { schema: resolver(schema as never) } }\n}\n\n/** The envelope every failing request gets (see `src/helpers/error.ts`). */\nexport const ERROR_RESPONSES = {\n 400: { description: 'The request was rejected', content: jsonBody(apiErrorSchema) },\n 401: { description: 'No valid session', content: jsonBody(apiErrorSchema) },\n 404: { description: 'Unknown id', content: jsonBody(apiErrorSchema) },\n} as const\n","import type { StandardSchemaV1 } from '@standard-schema/spec'\nimport type { ValidationTargets } from 'hono'\nimport { DetailedError } from '@namesmt/utils'\nimport { validator as standardValidator } from 'hono-openapi'\n\n/**\n * ArkType-backed request validation. On success Hono stores the *parsed* value,\n * so `c.req.valid('json')` is fully typed and already normalized; on failure it\n * becomes a `DetailedError`, which the global error handler turns into the one\n * error envelope this API speaks.\n */\nexport function validate<Target extends keyof ValidationTargets, Schema extends StandardSchemaV1>(target: Target, schema: Schema) {\n return standardValidator(target, schema, (result) => {\n if (result.success === false)\n throw new DetailedError('validation failed', { statusCode: 400, detail: normalizeIssues(result.error) })\n })\n}\n\n/** ArkType issues serialize poorly, so only the fields a client can act on survive. */\nfunction normalizeIssues(error: StandardSchemaV1.FailureResult['issues']): Array<{ path: string, message: string }> {\n return error.map((issue) => {\n const path = (issue.path ?? [])\n .map(segment => (typeof segment === 'object' && segment !== null && 'key' in segment ? String(segment.key) : String(segment)))\n .join('.')\n return { path, message: issue.message }\n })\n}\n","/** Client address helpers shared by the auth guard and the setup routes. */\n\nexport function requestIp(c: { req: { raw: unknown } }): string | null {\n // srvx resolves this hop-aware from `trustProxy` + x-forwarded-for.\n const raw = c.req.raw as { ip?: string } | undefined\n return raw?.ip ?? null\n}\n\nexport function isLoopback(address: string | null): boolean {\n if (!address)\n return false\n return address === '127.0.0.1' || address === '::1' || address === '::ffff:127.0.0.1'\n}\n\nexport function isLoopbackRequest(c: { req: { raw: unknown } }): boolean {\n return isLoopback(requestIp(c))\n}\n","import fs from 'node:fs'\nimport path from 'node:path'\nimport process from 'node:process'\n\nexport interface WriteFileOptions {\n /** Applied to the temp file before the rename, so secrets never exist world-readable. */\n mode?: number\n}\n\n/**\n * Write via a temp file in the same directory, then rename: readers never see a\n * half-written file, and a crash cannot truncate the previous config.\n */\nexport function writeFileAtomic(file: string, content: string, options: WriteFileOptions = {}): void {\n fs.mkdirSync(path.dirname(file), { recursive: true })\n const tmp = `${file}.${process.pid}.tmp`\n fs.writeFileSync(tmp, content, options.mode === undefined ? undefined : { mode: options.mode })\n if (options.mode !== undefined)\n fs.chmodSync(tmp, options.mode)\n fs.renameSync(tmp, file)\n}\n","import { Buffer } from 'node:buffer'\nimport crypto from 'node:crypto'\nimport fs from 'node:fs'\nimport { writeFileAtomic } from '#src/helpers/atomic'\n\n/** Node's scrypt defaults, pinned so a hash stays verifiable across versions. */\nconst COST = { N: 16384, r: 8, p: 1 } as const\nconst KEYLEN = 64\nconst SALT_BYTES = 16\n\nexport interface PasswordRecord {\n algo: 'scrypt'\n salt: string\n hash: string\n keylen: number\n cost: { N: number, r: number, p: number }\n updatedAt: number\n /** Set for the boot-time default, so the UI can say so and exposure stays blocked. */\n isDefault?: boolean\n}\n\n/**\n * An API token for scripts and agents. Tokens are high-entropy randoms, so a\n * plain SHA-256 is the right hash — scrypt would only make every request pay\n * for a slow KDF it does not need. `hint` is the readable head, kept so a human\n * can tell two tokens apart without the file ever holding the whole secret.\n */\nexport interface ApiTokenRecord {\n algo: 'sha256'\n hash: string\n hint: string\n updatedAt: number\n}\n\ninterface SecretsFile {\n version: 3\n password: PasswordRecord | null\n apiToken: ApiTokenRecord | null\n telegram: { botToken: string } | null\n}\n\nexport interface ScryptCost { N: number, r: number, p: number }\n\nexport function deriveKey(password: string, salt: Buffer, cost: ScryptCost, keylen: number): Buffer {\n return crypto.scryptSync(password.normalize('NFKC'), salt, keylen, { ...cost })\n}\n\nexport function hashPassword(password: string, options: { now?: number, isDefault?: boolean } = {}): PasswordRecord {\n const salt = crypto.randomBytes(SALT_BYTES)\n return {\n algo: 'scrypt',\n salt: salt.toString('base64'),\n hash: deriveKey(password, salt, COST, KEYLEN).toString('base64'),\n keylen: KEYLEN,\n cost: { ...COST },\n updatedAt: options.now ?? Date.now(),\n ...(options.isDefault === true ? { isDefault: true } : {}),\n }\n}\n\nexport function verifyPassword(password: string, record: PasswordRecord): boolean {\n const expected = Buffer.from(record.hash, 'base64')\n let actual: Buffer\n try {\n actual = deriveKey(password, Buffer.from(record.salt, 'base64'), record.cost, record.keylen)\n }\n catch {\n return false\n }\n if (actual.length !== expected.length)\n return false\n return crypto.timingSafeEqual(actual, expected)\n}\n\n/** Visible head of a generated token, so a token is recognisable on sight. */\nexport const API_TOKEN_PREFIX = 'hh_'\nconst API_TOKEN_BYTES = 32\nconst API_TOKEN_HINT_CHARS = 8\n\nexport function generateApiToken(): string {\n return `${API_TOKEN_PREFIX}${crypto.randomBytes(API_TOKEN_BYTES).toString('base64url')}`\n}\n\nexport function hashApiToken(token: string): string {\n return crypto.createHash('sha256').update(token, 'utf8').digest('base64')\n}\n\nexport function verifyApiToken(token: string, record: ApiTokenRecord): boolean {\n if (token.length === 0)\n return false\n const expected = Buffer.from(record.hash, 'base64')\n const actual = Buffer.from(hashApiToken(token), 'base64')\n // A hand-edited or truncated secrets file must not make every request throw.\n if (actual.length !== expected.length)\n return false\n return crypto.timingSafeEqual(actual, expected)\n}\n\nexport function apiTokenRecord(token: string, now = Date.now()): ApiTokenRecord {\n return {\n algo: 'sha256',\n hash: hashApiToken(token),\n hint: token.slice(0, API_TOKEN_HINT_CHARS),\n updatedAt: now,\n }\n}\n\n/**\n * The password hash and the API token are secrets, so they live outside\n * `servers.config.json` (which is tracked) in a 0600 file that is git-ignored.\n */\nexport class SecretsStore {\n private cache: SecretsFile | null = null\n private cacheKey = ''\n\n constructor(private readonly file: string) {}\n\n get path(): string {\n return this.file\n }\n\n /**\n * Re-reads whenever the file changes on disk, so a password set by\n * `pnpm run set-password` (or another process) takes effect without\n * restarting `up`.\n */\n load(): SecretsFile {\n const key = this.statKey()\n if (this.cache !== null && key === this.cacheKey)\n return this.cache\n this.cache = this.read()\n this.cacheKey = key\n return this.cache\n }\n\n private statKey(): string {\n try {\n const stats = fs.statSync(this.file)\n return `${stats.mtimeMs}:${stats.size}`\n }\n catch {\n return 'missing'\n }\n }\n\n get password(): PasswordRecord | null {\n return this.load().password\n }\n\n get passwordUpdatedAt(): number | null {\n return this.password?.updatedAt ?? null\n }\n\n get passwordSet(): boolean {\n return this.password !== null\n }\n\n get usingDefaultPassword(): boolean {\n return this.password?.isDefault === true\n }\n\n get apiToken(): ApiTokenRecord | null {\n return this.load().apiToken\n }\n\n get apiTokenSet(): boolean {\n return this.apiToken !== null\n }\n\n /** The readable head of the stored token, or null when none is set. */\n get apiTokenHint(): string | null {\n return this.apiToken?.hint ?? null\n }\n\n get telegramToken(): string | null {\n return this.load().telegram?.botToken ?? null\n }\n\n get telegramTokenSet(): boolean {\n return (this.telegramToken ?? '').length > 0\n }\n\n setPassword(password: string, options: { isDefault?: boolean } = {}): PasswordRecord {\n const record = hashPassword(password, options)\n this.save({ ...this.load(), password: record })\n return record\n }\n\n /** Creates the default password only when none exists yet. */\n ensureDefaultPassword(password: string): PasswordRecord | null {\n if (this.passwordSet)\n return null\n return this.setPassword(password, { isDefault: true })\n }\n\n clearPassword(): void {\n this.save({ ...this.load(), password: null })\n }\n\n setApiToken(token: string): ApiTokenRecord {\n const record = apiTokenRecord(token)\n this.save({ ...this.load(), apiToken: record })\n return record\n }\n\n clearApiToken(): void {\n this.save({ ...this.load(), apiToken: null })\n }\n\n setTelegramToken(token: string | null): void {\n const trimmed = token?.trim() ?? ''\n this.save({ ...this.load(), telegram: trimmed.length > 0 ? { botToken: trimmed } : null })\n }\n\n private read(): SecretsFile {\n if (!fs.existsSync(this.file))\n return { version: 3, password: null, apiToken: null, telegram: null }\n try {\n const parsed = JSON.parse(fs.readFileSync(this.file, 'utf8')) as Partial<SecretsFile>\n return {\n version: 3,\n // A version-2 file simply has no token, so it reads as \"none set\".\n password: parsed?.password ?? null,\n apiToken: parsed?.apiToken?.hash ? parsed.apiToken : null,\n telegram: parsed?.telegram?.botToken ? { botToken: parsed.telegram.botToken } : null,\n }\n }\n catch {\n // A corrupt secrets file must not silently authenticate anyone.\n return { version: 3, password: null, apiToken: null, telegram: null }\n }\n }\n\n private save(contents: SecretsFile): void {\n writeFileAtomic(this.file, `${JSON.stringify(contents, null, 2)}\\n`, { mode: 0o600 })\n this.cache = contents\n }\n}\n","import type { SecretsStore } from '#src/config/secrets'\nimport type { AuthConfig, SessionView } from '#src/shared/contracts'\nimport crypto from 'node:crypto'\nimport { verifyApiToken, verifyPassword } from '#src/config/secrets'\nimport { parseCookies } from '#src/helpers/cookies'\n\nexport const SESSION_COOKIE = 'hh_session'\n\n/** Created on first boot when no password exists; exposure stays blocked until it changes. */\nexport const DEFAULT_PASSWORD = 'hh'\n\nconst MAX_SESSIONS = 100\nconst MAX_LOCKOUT_MS = 15 * 60_000\n/** Bounds on the per-IP bookkeeping, which an attacker can otherwise grow. */\nconst MAX_ATTEMPT_RECORDS = 10_000\nconst ATTEMPT_RECORD_TTL_MS = 60 * 60_000\n\nexport interface SessionRecord {\n token: string\n createdAt: number\n expiresAt: number\n lastSeenAt: number\n ip: string | null\n}\n\ninterface AttemptRecord {\n failures: number\n blockedUntil: number\n blocks: number\n /** For expiring idle records: with `trustProxy` the key is client-chosen. */\n lastAttemptAt: number\n}\n\nexport type LoginOutcome\n = | { ok: true, status: 200, token: string, maxAgeMs: number }\n | { ok: false, status: 401 | 409 | 429, error: string, retryAfterMs?: number }\n\n/** Which credential a request presented. */\nexport type AuthMethod = 'cookie' | 'token'\n\nexport interface AuthIdentity {\n authenticated: boolean\n method: AuthMethod | null\n /** The session record, when the cookie resolved to one. */\n session: SessionRecord | null\n}\n\nexport const ANONYMOUS: AuthIdentity = { authenticated: false, method: null, session: null }\n\n/**\n * `Authorization: Bearer <token>`; the scheme is case-insensitive per RFC 7235,\n * and the credential is taken whole so a stray space cannot silently shorten it.\n */\nexport function bearerToken(header: string | null | undefined): string | null {\n if (!header)\n return null\n const [scheme, ...rest] = header.trim().split(/\\s+/)\n if (scheme?.toLowerCase() !== 'bearer')\n return null\n const token = rest.join('')\n return token.length > 0 ? token : null\n}\n\n/**\n * Authentication for the control panel.\n *\n * Two credentials are accepted: the browser's session cookie, and a long-lived\n * API token for scripts and agents. They carry the same authority on purpose —\n * a token that could do less than a signed-in browser would only be surprising.\n *\n * The password hash lives in the git-ignored secrets file; sessions live only in\n * memory, so restarting `up` invalidates every session, while a token outlives\n * the process until it is cleared.\n */\nexport class AuthService {\n private readonly sessions = new Map<string, SessionRecord>()\n private readonly attempts = new Map<string, AttemptRecord>()\n private readonly timer: NodeJS.Timeout\n\n constructor(\n private readonly secrets: SecretsStore,\n private readonly getConfig: () => AuthConfig,\n ) {\n this.timer = setInterval(() => this.cleanup(), 60_000)\n this.timer.unref()\n }\n\n get passwordSet(): boolean {\n return this.secrets.passwordSet\n }\n\n get passwordUpdatedAt(): number | null {\n return this.secrets.passwordUpdatedAt\n }\n\n /** Still the boot-time default: the login page says so, exposure stays blocked. */\n get usingDefaultPassword(): boolean {\n return this.secrets.usingDefaultPassword\n }\n\n /** The feature is on. */\n isEnabled(): boolean {\n return this.getConfig().enabled\n }\n\n /** A password is set, so a browser has something to sign in with. */\n isArmed(): boolean {\n return this.getConfig().enabled && this.secrets.passwordSet\n }\n\n /** Kept as the \"should the guard demand a session\" predicate. */\n isRequired(): boolean {\n return this.isArmed()\n }\n\n get apiTokenSet(): boolean {\n return this.secrets.apiTokenSet\n }\n\n /** The readable head of the stored token, never the token itself. */\n get apiTokenHint(): string | null {\n return this.secrets.apiTokenHint\n }\n\n /** Compared against the stored SHA-256, in constant time. */\n validateApiToken(token: string | null): boolean {\n if (token === null)\n return false\n const record = this.secrets.apiToken\n if (record === null)\n return false\n return verifyApiToken(token, record)\n }\n\n /**\n * Resolves whichever credential the request carried. The token is only\n * consulted when no session matched, so a stale cookie cannot mask it.\n */\n authenticate(credentials: { cookieToken: string | null, bearerToken: string | null }): AuthIdentity {\n const session = this.validate(credentials.cookieToken)\n if (session !== null)\n return { authenticated: true, method: 'cookie', session }\n if (this.validateApiToken(credentials.bearerToken))\n return { authenticated: true, method: 'token', session: null }\n return ANONYMOUS\n }\n\n sessionView(authenticated: boolean): SessionView {\n return {\n authenticated,\n authRequired: this.isRequired(),\n passwordSet: this.secrets.passwordSet,\n apiTokenSet: this.secrets.apiTokenSet,\n usingDefaultPassword: this.secrets.usingDefaultPassword,\n defaultPassword: this.secrets.usingDefaultPassword ? DEFAULT_PASSWORD : null,\n sessionTtlMs: this.getConfig().sessionTtlMs,\n }\n }\n\n tokenFromCookie(cookieHeader: string | null | undefined): string | null {\n return parseCookies(cookieHeader)[SESSION_COOKIE] ?? null\n }\n\n /** Sliding expiry: an active panel stays logged in, an idle one does not. */\n validate(token: string | null): SessionRecord | null {\n if (!token)\n return null\n const session = this.sessions.get(token)\n if (!session)\n return null\n\n const now = Date.now()\n if (session.expiresAt <= now) {\n this.sessions.delete(token)\n return null\n }\n\n session.lastSeenAt = now\n session.expiresAt = now + this.getConfig().sessionTtlMs\n return session\n }\n\n verifyCurrentPassword(password: string): boolean {\n const record = this.secrets.password\n if (record === null)\n return false\n return verifyPassword(password, record)\n }\n\n login(password: string, ip: string | null): LoginOutcome {\n const config = this.getConfig()\n const key = ip ?? 'unknown'\n const now = Date.now()\n const attempt = this.attempts.get(key)\n\n if (attempt && attempt.blockedUntil > now) {\n const retryAfterMs = attempt.blockedUntil - now\n return {\n ok: false,\n status: 429,\n error: `too many failed attempts, retry in ${Math.ceil(retryAfterMs / 1000)}s`,\n retryAfterMs,\n }\n }\n\n const record = this.secrets.password\n if (record === null) {\n return { ok: false, status: 409, error: 'no password is set yet' }\n }\n\n if (!verifyPassword(password, record)) {\n const failures = (attempt?.failures ?? 0) + 1\n if (failures >= config.maxLoginAttempts) {\n const blocks = (attempt?.blocks ?? 0) + 1\n const blockedUntil = Date.now() + Math.min(config.lockoutMs * 2 ** (blocks - 1), MAX_LOCKOUT_MS)\n this.attempts.set(key, { failures: 0, blockedUntil, blocks, lastAttemptAt: Date.now() })\n }\n else {\n this.attempts.set(key, { failures, blockedUntil: 0, blocks: attempt?.blocks ?? 0, lastAttemptAt: Date.now() })\n }\n return { ok: false, status: 401, error: 'invalid password' }\n }\n\n this.attempts.delete(key)\n if (this.sessions.size >= MAX_SESSIONS) {\n const oldest = [...this.sessions.values()].sort((a, b) => a.lastSeenAt - b.lastSeenAt)[0]\n if (oldest)\n this.sessions.delete(oldest.token)\n }\n\n const token = crypto.randomBytes(32).toString('base64url')\n this.sessions.set(token, {\n token,\n createdAt: now,\n expiresAt: now + config.sessionTtlMs,\n lastSeenAt: now,\n ip,\n })\n\n return { ok: true, status: 200, token, maxAgeMs: config.sessionTtlMs }\n }\n\n logout(token: string | null): void {\n if (token)\n this.sessions.delete(token)\n }\n\n logoutAll(): void {\n this.sessions.clear()\n }\n\n /**\n * Changing the password must not leave old sessions valid. The session that\n * made the change is kept — being signed out of the page you just used is not\n * a security requirement, and it makes a successful change look like a failure.\n */\n setPassword(password: string, options: { isDefault?: boolean, keepToken?: string | null } = {}): void {\n this.secrets.setPassword(password, options)\n this.logoutOthers(options.keepToken ?? null)\n }\n\n /** Drops every session except one, which is how a password change stays signed in. */\n private logoutOthers(keep: string | null): void {\n if (keep === null) {\n this.sessions.clear()\n return\n }\n for (const token of [...this.sessions.keys()]) {\n if (token !== keep)\n this.sessions.delete(token)\n }\n }\n\n /** Creates the boot-time default only when nothing is set yet. */\n ensureDefaultPassword(password: string): boolean {\n const created = this.secrets.ensureDefaultPassword(password) !== null\n if (created)\n this.logoutAll()\n return created\n }\n\n clearPassword(): void {\n this.secrets.clearPassword()\n this.logoutAll()\n }\n\n activeSessions(): number {\n return this.sessions.size\n }\n\n dispose(): void {\n clearInterval(this.timer)\n this.sessions.clear()\n }\n\n private cleanup(): void {\n const now = Date.now()\n for (const [token, session] of this.sessions) {\n if (session.expiresAt <= now)\n this.sessions.delete(token)\n }\n for (const [key, attempt] of this.attempts) {\n // Idle records go, whether or not they were ever blocked — a failed login\n // from an address that never comes back must not be remembered forever.\n if (now - attempt.lastAttemptAt >= ATTEMPT_RECORD_TTL_MS)\n this.attempts.delete(key)\n }\n\n if (this.attempts.size > MAX_ATTEMPT_RECORDS) {\n const oldest = [...this.attempts.entries()]\n .sort((a, b) => a[1].lastAttemptAt - b[1].lastAttemptAt)\n .slice(0, this.attempts.size - MAX_ATTEMPT_RECORDS)\n for (const [key] of oldest) this.attempts.delete(key)\n }\n }\n}\n","import type { Context, MiddlewareHandler } from 'hono'\nimport type { AuthIdentity, AuthService } from '#src/services/auth'\nimport { DetailedError } from '@namesmt/utils'\nimport { isLoopbackRequest } from '#src/middleware/loopback'\nimport { bearerToken, SESSION_COOKIE } from '#src/services/auth'\n\n/** Endpoints the SPA needs before it can show a login form. */\nconst PUBLIC_PATHS = new Set(['/api/auth/login', '/api/auth/session'])\n\n/** 401s from the guard carry this code so the SPA can route to the login view. */\nexport const AUTH_REQUIRED_CODE = 'AUTH_REQUIRED'\n\n/**\n * The one place that reads a request's credentials, so the guard, the session\n * route and `/healthz` can never disagree about who is calling.\n */\nexport function requestIdentity(c: Context, auth: AuthService): AuthIdentity {\n return auth.authenticate({\n cookieToken: auth.tokenFromCookie(c.req.header('cookie')),\n bearerToken: bearerToken(c.req.header('authorization')),\n })\n}\n\nexport interface AuthGuardDeps {\n auth: AuthService\n /** Test hook: bind the guard to an explicit cookie header / ip. */\n now?: () => number\n}\n\n/**\n * Guards every `/api/*` route. The SPA shell stays public (it holds no data),\n * so the browser can load the app and show the login screen.\n *\n * A request may prove itself with the session cookie or with an API token\n * (`Authorization: Bearer …`), which is what lets a script or an agent drive the\n * panel without a browser. The token also works while auth is enabled but no\n * password is set — that state otherwise only trusts this machine.\n *\n * State-changing requests that carry an `Origin` must come from this same host:\n * with `SameSite=Strict` cookies that closes the cross-site CSRF path.\n */\nexport function createAuthGuard(deps: AuthGuardDeps): MiddlewareHandler {\n return async (c, next) => {\n const path = c.req.path\n\n if (path.startsWith('/api')) {\n const method = c.req.method\n if (method !== 'GET' && method !== 'HEAD') {\n const origin = c.req.header('origin')\n if (origin !== undefined) {\n // `Host` is required by HTTP/1.1 but not guaranteed to be set by every\n // client, so fall back to the authority the server itself resolved.\n const requestHost = c.req.header('host') ?? new URL(c.req.url).host\n let originHost: string | null = null\n try {\n originHost = new URL(origin).host\n }\n catch {\n originHost = null\n }\n if (originHost === null || originHost !== requestHost)\n throw new DetailedError('cross-origin request rejected', { statusCode: 403, code: 'CROSS_ORIGIN' })\n }\n }\n\n if (!PUBLIC_PATHS.has(path)) {\n const identity = requestIdentity(c, deps.auth)\n\n if (deps.auth.isArmed()) {\n if (!identity.authenticated) {\n if (deps.auth.apiTokenSet)\n c.header('WWW-Authenticate', 'Bearer realm=\"home-hosted\"')\n throw new DetailedError('authentication required', { statusCode: 401, code: AUTH_REQUIRED_CODE })\n }\n }\n else if (deps.auth.isEnabled()) {\n // Enabled but not armed: only an API token or this machine may look. A\n // proxied request must set `trustProxy` to be seen as remote, otherwise\n // it is indistinguishable from a local one.\n if (!identity.authenticated && !isLoopbackRequest(c)) {\n throw new DetailedError('authentication is enabled but no password is set — set one from the machine running the panel', {\n statusCode: 401,\n code: 'AUTH_UNARMED',\n })\n }\n }\n }\n }\n\n await next()\n }\n}\n\nexport { SESSION_COOKIE }\n\nexport { isLoopbackRequest, requestIp } from '#src/middleware/loopback'\n","import os from 'node:os'\n\nexport function lanAddress(): string | null {\n for (const entries of Object.values(os.networkInterfaces())) {\n for (const entry of entries ?? []) {\n if (entry.family === 'IPv4' && !entry.internal)\n return entry.address\n }\n }\n return null\n}\n\nexport function bindHost(bind: string): string {\n if (bind === 'local')\n return '127.0.0.1'\n if (bind === 'lan')\n return '0.0.0.0'\n return bind\n}\n\n/** Address a human should open, which is never `0.0.0.0`. */\nexport function displayHost(bind: string): string {\n if (bind === 'local')\n return '127.0.0.1'\n if (bind === 'lan')\n return lanAddress() ?? '127.0.0.1'\n return bind\n}\n\n/** True when the bind value makes the port reachable from outside this machine. */\nexport function isExposed(bind: string): boolean {\n return bindHost(bind) !== '127.0.0.1'\n}\n","import type { ControlConfig } from '#src/shared/contracts'\nimport { isExposed } from '#src/helpers/bind'\n\nexport interface ExposureState {\n /** The control panel listens beyond loopback. */\n exposed: boolean\n /** Non-null when that exposure is not backed by a password. */\n blockedReason: string | null\n}\n\n/**\n * Exposing the panel beyond loopback is only allowed with authentication fully\n * configured — this is checked at startup, on every settings write, and shown in\n * the UI, so the three can never disagree.\n */\nexport function checkExposure(control: ControlConfig, passwordSet: boolean, usingDefaultPassword = false): ExposureState {\n const exposed = isExposed(control.host)\n if (!exposed)\n return { exposed, blockedReason: null }\n\n if (!control.auth.enabled && !passwordSet) {\n return {\n exposed,\n blockedReason: `the control panel is bound to ${control.host} but authentication is disabled and no password is set`,\n }\n }\n if (!control.auth.enabled) {\n return { exposed, blockedReason: `the control panel is bound to ${control.host} but authentication is disabled` }\n }\n if (!passwordSet) {\n return {\n exposed,\n blockedReason: `the control panel is bound to ${control.host} but no password is set (run \\`pnpm run set-password\\`)`,\n }\n }\n if (usingDefaultPassword) {\n return {\n exposed,\n blockedReason: `the control panel is bound to ${control.host} but still uses the default password — change it first`,\n }\n }\n return { exposed, blockedReason: null }\n}\n","import type { Context } from 'hono'\nimport type { AppDeps } from '#src/app'\nimport type { LoginRequest, PasswordRequest } from '#src/shared/contracts'\nimport { DetailedError } from '@namesmt/utils'\nimport { describeRoute } from 'hono-openapi'\nimport { serializeCookie } from '#src/helpers/cookies'\nimport { appFactory } from '#src/helpers/factory'\nimport { ERROR_RESPONSES, jsonBody } from '#src/helpers/openapi'\nimport { validate } from '#src/helpers/validator'\nimport { AUTH_REQUIRED_CODE, requestIdentity, SESSION_COOKIE } from '#src/middleware/auth'\nimport { isLoopbackRequest, requestIp } from '#src/middleware/loopback'\nimport { checkExposure } from '#src/services/exposure'\nimport { loginSchema, passwordSchema, sessionViewSchema } from '#src/shared/contracts'\n\n/** `Secure` only helps over TLS, and would break plain http on a LAN. */\nfunction secureCookie(c: Context, deps: AppDeps): boolean {\n const mode = deps.store.config.control.auth.cookieSecure\n if (mode === 'always')\n return true\n if (mode === 'never')\n return false\n return new URL(c.req.url).protocol === 'https:'\n}\n\nexport function createAuthRoute(deps: AppDeps) {\n return appFactory.createApp()\n .get(\n '/auth/session',\n describeRoute({\n tags: ['auth'],\n summary: 'Who this request is, and how the panel is protected',\n responses: { 200: { description: 'Session', content: jsonBody(sessionViewSchema) } },\n }),\n c => c.json(deps.auth.sessionView(requestIdentity(c, deps.auth).authenticated)),\n )\n\n .post(\n '/auth/login',\n describeRoute({\n tags: ['auth'],\n summary: 'Exchange the panel password for a session cookie',\n responses: {\n 200: { description: 'Signed in', content: jsonBody(sessionViewSchema) },\n 400: ERROR_RESPONSES[400],\n 401: { description: 'Wrong password, or locked out (see `Retry-After`)' },\n },\n }),\n validate('json', loginSchema),\n (c) => {\n const body: LoginRequest = c.req.valid('json')\n const outcome = deps.auth.login(body.password, requestIp(c))\n\n if (!outcome.ok) {\n if (outcome.retryAfterMs !== undefined)\n c.header('Retry-After', String(Math.ceil(outcome.retryAfterMs / 1000)))\n throw new DetailedError(outcome.error, { statusCode: outcome.status, code: 'LOGIN_FAILED' })\n }\n\n c.header('Set-Cookie', serializeCookie(SESSION_COOKIE, outcome.token, {\n maxAgeMs: outcome.maxAgeMs,\n secure: secureCookie(c, deps),\n sameSite: 'Strict',\n httpOnly: true,\n }))\n\n return c.json(deps.auth.sessionView(true))\n },\n )\n\n .post(\n '/auth/logout',\n describeRoute({\n tags: ['auth'],\n summary: 'Drop the session cookie',\n responses: { 200: { description: 'Signed out' } },\n }),\n (c) => {\n deps.auth.logout(deps.auth.tokenFromCookie(c.req.header('cookie')))\n c.header('Set-Cookie', serializeCookie(SESSION_COOKIE, '', {\n maxAgeMs: 0,\n secure: secureCookie(c, deps),\n sameSite: 'Strict',\n httpOnly: true,\n }))\n return c.json({ ok: true })\n },\n )\n\n /**\n * First-time setup is allowed from loopback without a session (there is\n * nothing to authenticate against yet); every later change needs the session\n * and* the current password.\n */\n .post(\n '/auth/password',\n describeRoute({\n tags: ['auth'],\n summary: 'Set, change or enable the panel password',\n responses: { 200: { description: 'Updated' }, 400: ERROR_RESPONSES[400], 401: ERROR_RESPONSES[401] },\n }),\n validate('json', passwordSchema),\n (c) => {\n const body: PasswordRequest = c.req.valid('json')\n const identity = requestIdentity(c, deps.auth)\n const hadPassword = deps.auth.passwordSet\n const firstSetup = !hadPassword && isLoopbackRequest(c)\n\n if (!identity.authenticated && !firstSetup)\n throw new DetailedError('authentication required', { statusCode: 401, code: AUTH_REQUIRED_CODE })\n\n // The current password is demanded of every credential, so a stolen API\n // token cannot rewrite the password it would then need to be recovered from.\n if (identity.authenticated && hadPassword) {\n if (body.currentPassword === undefined)\n throw new DetailedError('currentPassword is required to change an existing password', { statusCode: 400, code: 'CURRENT_PASSWORD_REQUIRED' })\n if (!deps.auth.verifyCurrentPassword(body.currentPassword))\n throw new DetailedError('current password is incorrect', { statusCode: 401, code: 'CURRENT_PASSWORD_WRONG' })\n }\n\n // Keep the caller signed in: every *other* session is dropped. A token\n // caller holds no session, so this signs every browser out instead.\n deps.auth.setPassword(body.newPassword, { keepToken: identity.session?.token ?? null })\n\n // A password that is not enforced protects nothing, so the first setup enables it.\n let enabled = deps.store.config.control.auth.enabled\n if (!enabled) {\n deps.store.updateControl({ auth: { enabled: true } })\n enabled = true\n }\n\n return c.json({ ok: true, enabled, sessionsInvalidated: true })\n },\n )\n\n .delete(\n '/auth/password',\n describeRoute({\n tags: ['auth'],\n summary: 'Clear the password and turn authentication off',\n responses: { 200: { description: 'Cleared' }, 400: ERROR_RESPONSES[400], 401: ERROR_RESPONSES[401] },\n }),\n (c) => {\n if (!requestIdentity(c, deps.auth).authenticated)\n throw new DetailedError('authentication required', { statusCode: 401, code: AUTH_REQUIRED_CODE })\n\n const exposure = checkExposure(deps.store.config.control, false)\n if (exposure.exposed) {\n throw new DetailedError('refusing to clear the password while the control panel is bound beyond loopback — set the bind back to local first', {\n statusCode: 400,\n code: 'EXPOSED_WITHOUT_PASSWORD',\n })\n }\n\n deps.auth.clearPassword()\n deps.store.updateControl({ auth: { enabled: false } })\n return c.json({ ok: true })\n },\n )\n}\n","import type { ConsolaInstance } from 'consola'\nimport { createConsola, LogLevels } from 'consola'\nimport { isDevelopment } from 'std-env'\n\n/**\n * Note: this logger will log the `debug` level logs in development mode.\n *\n * For actual debug logs with `NODE_DEBUG`, it is recommended to use the `debug` package.\n */\nexport const logger: ConsolaInstance = createConsola(\n {\n level: isDevelopment ? LogLevels.debug : undefined,\n },\n)\n","import type { StandardSchemaV1 } from '@standard-schema/spec'\nimport { DetailedError } from '@namesmt/utils'\nimport { type } from 'arktype'\n\n/**\n * Validates a value that is not a request target (a query string, a URL\n * parameter, a payload read by hand) and fails with the standard error envelope.\n * Request bodies and queries that a route reads once go through the `validate()`\n * middleware instead, which also carries the type into the handler.\n */\nexport function parseOrThrow<T>(schema: (input: unknown) => unknown, input: unknown, label: string): T {\n const result = schema(input)\n if (result instanceof type.errors) {\n throw new DetailedError(`${label}: ${result.summary}`, {\n statusCode: 400,\n code: 'INVALID_INPUT',\n detail: result.issues.map(issue => ({ path: issue.path.join('.'), message: issue.message })),\n })\n }\n return result as T\n}\n\n/** ArkType is a Standard Schema, so the middleware accepts it as-is. */\nexport type ValidatorSchema = StandardSchemaV1\n","import os from 'node:os'\nimport path from 'node:path'\nimport process from 'node:process'\n\n/**\n * Where home-hosted keeps everything it owns: the servers config, the secrets\n * file, logs, TLS material, backups and the runtime file. `HHOSTED_HOME`\n * overrides it — which is how a project repo keeps its own state directory\n * while the package itself ships no configuration at all.\n */\nexport function resolveDataRoot(): string {\n const override = process.env.HHOSTED_HOME\n if (override !== undefined && override.length > 0)\n return path.resolve(override)\n return path.join(os.homedir(), '.home-hosted')\n}\n\nexport const dataRoot = resolveDataRoot()\n\n/**\n * The directory home-hosted was started from. Relative entry paths (`cwd`,\n * declared data directories) resolve against it, so the project's own launcher\n * decides the base instead of wherever the package happens to be installed.\n * `HHOSTED_PROJECT` pins it explicitly.\n */\nexport function resolveProjectDir(): string {\n const override = process.env.HHOSTED_PROJECT\n if (override !== undefined && override.length > 0)\n return path.resolve(override)\n return process.cwd()\n}\n\nexport const projectDir = resolveProjectDir()\n\nexport const defaultConfigPath = path.join(dataRoot, 'servers.config.json')\n/** Regenerated for editor autocomplete; kept beside the config it describes. */\nexport const configSchemaPath = path.join(dataRoot, 'servers.config.schema.json')\n/** Password hash + bot token; written with mode 0600. */\nexport const defaultSecretsPath = path.join(dataRoot, '.control-secrets.json')\n/** Rotated per-server JSONL logs. */\nexport const defaultLogsDir = path.join(dataRoot, '.logs')\n/** Persisted restart/crash history. */\nexport const defaultHistoryPath = path.join(dataRoot, '.logs', 'history.json')\n/** Uploaded TLS PEM pair (the key is written 0600). */\nexport const defaultTlsDir = path.join(dataRoot, '.tls')\n/** `run.json` records the live control plane; the log captures its console. */\nexport const runtimePath = path.join(dataRoot, 'run.json')\nexport const daemonLogPath = path.join(dataRoot, '.logs', 'home-hosted.log')\n\n/** Expands `~` and resolves relative paths against `base`, for config-declared paths. */\nexport function resolveUserPath(target: string, base = projectDir): string {\n let value = target\n if (value === '~')\n value = os.homedir()\n else if (value.startsWith('~/') || value.startsWith('~\\\\'))\n value = path.join(os.homedir(), value.slice(2))\n return path.isAbsolute(value) ? value : path.resolve(base, value)\n}\n","import type { ConfigStore } from '#src/config/store'\nimport type { AuthService } from '#src/services/auth'\nimport type { BackupService } from '#src/services/backups'\nimport type { ControlEndpoint } from '#src/services/control-server'\nimport type { HostMonitor } from '#src/services/host-monitor'\nimport type { NotificationService } from '#src/services/notifications'\nimport type { TlsStore } from '#src/services/tls'\nimport type { AppState, BackupsView, ControlConfig, ControlView, HostView, ServerDefaults, ServerView } from '#src/shared/contracts'\nimport { dataRoot, projectDir } from '#src/helpers/paths'\nimport { checkExposure } from '#src/services/exposure'\n\nexport interface BuildStateDeps {\n store: ConfigStore\n auth: AuthService\n control: ControlEndpoint\n tls: TlsStore\n notifications: NotificationService\n hostMonitor: HostMonitor\n backups: BackupService\n logsDir: string\n views: ServerView[]\n}\n\n/**\n * The panel's own settings are derived here — configured values from the store,\n * live values from the listener, security state from the auth service and the\n * certificate pair from disk — so the UI never has to reason about the\n * differences.\n */\nexport function buildControlView(\n store: ConfigStore,\n auth: AuthService,\n control: ControlEndpoint,\n tls: TlsStore,\n): ControlView {\n const config: ControlConfig = store.config.control\n const exposure = checkExposure(config, auth.passwordSet, auth.usingDefaultPassword)\n\n return {\n label: config.label,\n port: config.port,\n host: config.host,\n bindHost: control.bindHost,\n url: control.url,\n protocol: control.protocol,\n openBrowser: config.openBrowser,\n restartRequired: control.port !== config.port || control.host !== config.host,\n auth: {\n enabled: config.auth.enabled,\n passwordSet: auth.passwordSet,\n passwordUpdatedAt: auth.passwordUpdatedAt,\n apiTokenSet: auth.apiTokenSet,\n usingDefaultPassword: auth.usingDefaultPassword,\n exposed: exposure.exposed,\n blockedReason: exposure.blockedReason,\n sessionTtlMs: config.auth.sessionTtlMs,\n cookieSecure: config.auth.cookieSecure,\n trustProxy: config.auth.trustProxy,\n maxLoginAttempts: config.auth.maxLoginAttempts,\n lockoutMs: config.auth.lockoutMs,\n },\n tls: tls.status(config.tls.enabled),\n }\n}\n\nexport function buildDefaults(store: ConfigStore): ServerDefaults {\n return store.defaults\n}\n\nexport function buildBackupsView(store: ConfigStore, backups: BackupService): BackupsView {\n return {\n enabled: store.config.backups.enabled,\n dir: backups.directory,\n keep: store.config.backups.keep,\n includePaths: store.config.backups.includePaths,\n paths: backups.paths,\n files: backups.list(),\n }\n}\n\nexport function buildHostView(hostMonitor: HostMonitor): HostView {\n return hostMonitor.view\n}\n\nexport function buildAppState(deps: BuildStateDeps): AppState {\n return {\n control: buildControlView(deps.store, deps.auth, deps.control, deps.tls),\n defaults: buildDefaults(deps.store),\n logs: deps.store.config.logs,\n notifications: { telegram: deps.notifications.status() },\n host: buildHostView(deps.hostMonitor),\n backups: buildBackupsView(deps.store, deps.backups),\n configPath: deps.store.path,\n configError: deps.store.configError,\n projectDir,\n dataRoot,\n logsDir: deps.logsDir,\n servers: deps.views,\n }\n}\n","import type { AppDeps } from '#src/app'\nimport type { BackupCreate, RestoreRequest } from '#src/shared/contracts'\nimport { Buffer } from 'node:buffer'\nimport crypto from 'node:crypto'\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport { DetailedError } from '@namesmt/utils'\nimport { type } from 'arktype'\nimport { describeRoute } from 'hono-openapi'\nimport { appFactory } from '#src/helpers/factory'\nimport { logger } from '#src/helpers/logger'\nimport { ERROR_RESPONSES, jsonBody } from '#src/helpers/openapi'\nimport { parseOrThrow } from '#src/helpers/validate'\nimport { validate } from '#src/helpers/validator'\nimport { buildBackupsView } from '#src/services/state'\nimport { backupCreateSchema, backupsViewSchema, restorePlanSchema, restoreRequestSchema } from '#src/shared/contracts'\n\n/** Uploads are buffered in memory by `parseBody`, so they get a hard ceiling. */\nconst MAX_UPLOAD_BYTES = 256 * 1024 * 1024\n\nconst nameParam = type({ name: 'string >= 1' })\n\n/** A backup that failed to be created is a bad request, not a server fault. */\nfunction backupFailed(error: string | undefined): DetailedError {\n return new DetailedError(error ?? 'the backup failed', { statusCode: 400, code: 'BACKUP_FAILED' })\n}\n\nexport function createBackupsRoute(deps: AppDeps) {\n return appFactory.createApp()\n .get(\n '/backups',\n describeRoute({\n tags: ['backups'],\n summary: 'Archives on disk, and the paths a backup would capture',\n responses: { 200: { description: 'Backups', content: jsonBody(backupsViewSchema) } },\n }),\n c => c.json(buildBackupsView(deps.store, deps.backups)),\n )\n\n .post(\n '/backups',\n describeRoute({\n tags: ['backups'],\n summary: 'Create a backup (optionally password-protected)',\n responses: { 200: { description: 'Created' }, 400: ERROR_RESPONSES[400] },\n }),\n validate('json', backupCreateSchema),\n async (c) => {\n const body: BackupCreate = c.req.valid('json')\n const result = await deps.backups.create({ password: body.password })\n if (!result.ok)\n throw backupFailed(result.error)\n return c.json({ file: result.file, files: deps.backups.list() })\n },\n )\n\n .get(\n '/backups/:name/download',\n describeRoute({\n tags: ['backups'],\n summary: 'Download one archive',\n responses: { 200: { description: 'the archive (application/zip)' }, 404: ERROR_RESPONSES[404] },\n }),\n validate('param', nameParam),\n (c) => {\n const file = deps.backups.resolve(c.req.valid('param').name)\n if (file === null)\n throw new DetailedError('unknown backup', { statusCode: 404, code: 'UNKNOWN_BACKUP' })\n\n const stats = fs.statSync(file)\n return c.body(fs.readFileSync(file), 200, {\n 'Content-Type': 'application/zip',\n 'Content-Length': String(stats.size),\n 'Content-Disposition': `attachment; filename=\"${path.basename(file)}\"`,\n })\n },\n )\n\n .delete(\n '/backups/:name',\n describeRoute({\n tags: ['backups'],\n summary: 'Delete one archive',\n responses: { 200: { description: 'Removed' }, 404: ERROR_RESPONSES[404] },\n }),\n validate('param', nameParam),\n (c) => {\n if (!deps.backups.remove(c.req.valid('param').name))\n throw new DetailedError('unknown backup', { statusCode: 404, code: 'UNKNOWN_BACKUP' })\n return c.json({ ok: true, files: deps.backups.list() })\n },\n )\n\n /**\n * Restore from a stored backup (`{ \"name\": \"...\" }`) or an uploaded one.\n * Without `confirm` it answers with the plan and changes nothing; `password`\n * unlocks a protected archive and `include` selects the items to apply.\n */\n .post('/backups/restore', describeRoute({\n tags: ['backups'],\n summary: 'Plan or apply a restore from a stored or uploaded archive',\n responses: { 200: { description: 'The plan', content: jsonBody(restorePlanSchema) }, 400: ERROR_RESPONSES[400], 404: ERROR_RESPONSES[404] },\n }), async (c) => {\n const confirm = c.req.query('confirm') === 'true'\n const contentType = c.req.header('content-type') ?? ''\n\n let archive: string | null = null\n let uploadedTo: string | null = null\n let request: RestoreRequest\n\n try {\n if (contentType.includes('multipart/form-data')) {\n const declared = Number.parseInt(c.req.header('content-length') ?? '0', 10)\n if (Number.isFinite(declared) && declared > MAX_UPLOAD_BYTES)\n throw new DetailedError(`the upload is larger than ${Math.round(MAX_UPLOAD_BYTES / 1024 / 1024)}MB`, { statusCode: 413, code: 'UPLOAD_TOO_LARGE' })\n\n const body = await c.req.parseBody()\n const file = body.file\n if (!(file instanceof File))\n throw new DetailedError('expected a `file` field with the archive', { statusCode: 400, code: 'MISSING_FILE' })\n if (file.size > MAX_UPLOAD_BYTES)\n throw new DetailedError(`the upload is larger than ${Math.round(MAX_UPLOAD_BYTES / 1024 / 1024)}MB`, { statusCode: 413, code: 'UPLOAD_TOO_LARGE' })\n\n const uploads = path.join(deps.backups.directory, 'uploads')\n fs.mkdirSync(uploads, { recursive: true })\n uploadedTo = path.join(uploads, `upload-${crypto.randomUUID()}.zip`)\n // 0600: the archive holds whatever the config declares as data, possibly in\n // the clear, and sits here until the request finishes.\n fs.writeFileSync(uploadedTo, Buffer.from(await file.arrayBuffer()), { mode: 0o600 })\n archive = uploadedTo\n // A multipart body can only carry strings, so the selection is JSON.\n const rawInclude = typeof body.include === 'string' && body.include.length > 0 ? body.include : null\n let include: unknown\n if (rawInclude !== null) {\n try {\n include = JSON.parse(rawInclude)\n }\n catch {\n throw new DetailedError('`include` must be a JSON array of item ids', { statusCode: 400, code: 'INVALID_INCLUDE' })\n }\n }\n request = parseOrThrow<RestoreRequest>(restoreRequestSchema, {\n ...(typeof body.password === 'string' ? { password: body.password } : {}),\n ...(rawInclude === null ? {} : { include }),\n }, 'body')\n }\n else {\n request = parseOrThrow<RestoreRequest>(restoreRequestSchema, await c.req.json().catch(() => ({})), 'body')\n if (request.name === undefined)\n throw new DetailedError('expected a backup name or a file upload', { statusCode: 400, code: 'MISSING_ARCHIVE' })\n archive = deps.backups.resolve(request.name)\n if (archive === null)\n throw new DetailedError('unknown backup', { statusCode: 404, code: 'UNKNOWN_BACKUP' })\n }\n\n const plan = await deps.backups.restore(archive, {\n confirm,\n password: request.password,\n include: request.include,\n })\n // A wrong or missing password is a prompt, not a failure.\n if (plan.needsPassword)\n return c.json(plan)\n if (plan.error !== undefined)\n throw new DetailedError(plan.error, { statusCode: 400, code: 'RESTORE_FAILED', detail: { items: plan.items, applied: plan.applied, skipped: plan.skipped } })\n if (confirm)\n logger.info(`restored from ${path.basename(archive)}: ${plan.applied.join(', ')}`)\n\n return c.json(plan)\n }\n finally {\n // An uploaded archive is only needed for this request.\n if (uploadedTo !== null)\n fs.rmSync(uploadedTo, { force: true })\n }\n })\n}\n","/**\n * Runs a task after the current response has been written.\n *\n * Moving the control listener closes the connection serving the request that\n * asked for the move, so those steps must happen once the response is out.\n */\nexport function afterResponse(task: () => Promise<void>, onError?: (error: unknown) => void): void {\n setImmediate(() => {\n void task().catch((error: unknown) => {\n onError?.(error)\n })\n })\n}\n","import type { AppDeps } from '#src/app'\nimport { DetailedError } from '@namesmt/utils'\nimport { describeRoute } from 'hono-openapi'\nimport { afterResponse } from '#src/helpers/deferred'\nimport { appFactory } from '#src/helpers/factory'\nimport { logger } from '#src/helpers/logger'\nimport { isLoopbackRequest } from '#src/middleware/loopback'\n\n/**\n * The local control channel used by `home-hosted down`.\n *\n * It is deliberately outside `/api` (so no session is needed) and guarded by a\n * token that only the owner of `run.json` can read, plus a loopback check: a\n * different local user, or anyone on the network, gets nothing.\n */\nexport function createControlRoute(deps: AppDeps) {\n return appFactory.createApp()\n .post(\n '/shutdown',\n describeRoute({\n tags: ['panel'],\n summary: 'Stop the panel and everything it supervises (local token required)',\n responses: { 200: { description: 'Stopping' }, 403: { description: 'Bad token, or not a local caller' } },\n }),\n (c) => {\n const token = c.req.header('x-home-hosted-token')\n if (token === undefined || token !== deps.runtimeToken)\n throw new DetailedError('invalid token', { statusCode: 403, code: 'INVALID_TOKEN' })\n if (!isLoopbackRequest(c))\n throw new DetailedError('only this machine may stop the control panel', { statusCode: 403, code: 'NOT_LOOPBACK' })\n\n // Deferred: the answer has to reach `down` before the process goes away.\n afterResponse(deps.onShutdown, error => logger.error('shutdown failed', error))\n\n return c.json({ ok: true })\n },\n )\n}\n","import type { AppDeps } from '#src/app'\nimport type { SseMessage } from '#src/shared/contracts'\nimport { type } from 'arktype'\nimport { describeRoute } from 'hono-openapi'\nimport { streamSSE } from 'hono/streaming'\nimport { appFactory } from '#src/helpers/factory'\nimport { validate } from '#src/helpers/validator'\n\nconst MAX_PENDING_WRITES = 200\nconst PING_INTERVAL_MS = 15000\n\nconst eventsQuery = type({\n /** Only this server's frames. */\n 'serverId?': 'string',\n /** `logs=0` drops log frames; the first frame is always the full state. */\n 'logs?': 'string',\n})\n\n/**\n * One SSE stream per subscriber. Pass `?serverId=<id>` to receive only that\n * server's messages; the first frame always carries the full state snapshot.\n */\nexport function createEventsRoute(deps: AppDeps) {\n return appFactory.createApp()\n .get(\n '/events',\n describeRoute({\n tags: ['panel'],\n summary: 'Panel state and log frames as server-sent events',\n responses: { 200: { description: 'text/event-stream' } },\n }),\n validate('query', eventsQuery),\n (c) => {\n const query = c.req.valid('query')\n const serverId = query.serverId ?? null\n const logOnly = query.logs !== '0'\n\n return streamSSE(c, async (stream) => {\n let pending = 0\n let queue: Promise<void> = Promise.resolve()\n let closed = false\n\n const send = (message: SseMessage): Promise<void> => {\n if (closed)\n return queue\n // A chatty child must not grow the queue without bound; state frames are\n // always kept, log frames are dropped once the client falls behind.\n if (message.type === 'log' && pending > MAX_PENDING_WRITES)\n return queue\n pending += 1\n queue = queue\n .then(() => stream.writeSSE({ event: message.type, data: JSON.stringify(message) }))\n .catch(() => {\n closed = true\n })\n .finally(() => {\n pending -= 1\n })\n return queue\n }\n\n const unsubscribe = deps.hub.subscribe(serverId, (message) => {\n if (!logOnly && message.type === 'log')\n return\n void send(message)\n })\n\n stream.onAbort(() => {\n closed = true\n unsubscribe()\n })\n\n await send({ type: 'hello', ts: Date.now(), state: deps.supervisor.getState() })\n\n while (true) {\n await stream.sleep(PING_INTERVAL_MS)\n if (closed)\n break\n await stream.writeSSE({ event: 'ping', data: String(Date.now()) })\n }\n })\n },\n )\n}\n","import type { AppDeps } from '#src/app'\nimport process from 'node:process'\nimport { type } from 'arktype'\nimport { describeRoute } from 'hono-openapi'\nimport { appFactory } from '#src/helpers/factory'\nimport { jsonBody } from '#src/helpers/openapi'\nimport { requestIdentity } from '#src/middleware/auth'\n\n/**\n * Liveness for external monitors. Mounted outside `/api`, so it answers without a\n * session: it reports whether the panel itself is serving, and 503 when an\n * autostart server has crashed. Server details are only included for an\n * authenticated caller.\n */\nexport function createHealthRoute(deps: AppDeps) {\n return appFactory.createApp()\n .get(\n '/healthz',\n describeRoute({\n tags: ['panel'],\n summary: 'Liveness for monitors — no session required',\n responses: {\n 200: {\n description: 'Serving',\n content: jsonBody(type({\n 'status': '\"ok\" | \"degraded\"',\n 'uptimeMs': 'number',\n 'servers?': type({ total: 'number', running: 'number', crashed: 'number', unhealthy: 'number' }),\n 'hostAlerts?': 'string[]',\n })),\n },\n 503: { description: 'An autostart server has crashed' },\n },\n }),\n (c) => {\n const state = deps.supervisor.getState()\n const broken = state.servers.filter(server => server.config.autostart && server.status === 'crashed')\n // Detail is for a signed-in browser or an API token; the status line itself\n // stays public, which is the whole point of a monitor endpoint.\n const authenticated = requestIdentity(c, deps.auth).authenticated\n\n return c.json({\n status: broken.length > 0 ? 'degraded' : 'ok',\n uptimeMs: Math.round(process.uptime() * 1000),\n ...(authenticated\n ? {\n servers: {\n total: state.servers.length,\n running: state.servers.filter(server => server.status === 'running').length,\n crashed: state.servers.filter(server => server.status === 'crashed').length,\n unhealthy: state.servers.filter(server => server.health === 'unhealthy').length,\n },\n hostAlerts: state.host.alerts,\n }\n : {}),\n }, broken.length > 0 ? 503 : 200)\n },\n )\n}\n","import type { AppDeps } from '#src/app'\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport { DetailedError } from '@namesmt/utils'\nimport { type } from 'arktype'\nimport { describeRoute } from 'hono-openapi'\nimport { appFactory } from '#src/helpers/factory'\nimport { ERROR_RESPONSES, jsonBody } from '#src/helpers/openapi'\nimport { validate } from '#src/helpers/validator'\nimport { logHistoryQuerySchema, logServersViewSchema } from '#src/shared/contracts'\n\n/** Bounds for the tail query, so a bad client cannot ask for the whole file. */\nconst MIN_TAIL = 50\nconst MAX_TAIL = 5000\nconst DEFAULT_TAIL = 500\n\nconst idParam = type({ id: 'string >= 1' })\nconst downloadQuery = type({ 'file?': 'string' })\n\nfunction unknownServer(id: string): DetailedError {\n return new DetailedError(`unknown server \"${id}\"`, { statusCode: 404, code: 'UNKNOWN_SERVER' })\n}\n\nexport function createLogsRoute(deps: AppDeps) {\n return appFactory.createApp()\n .get(\n '/logs',\n describeRoute({\n tags: ['logs'],\n summary: 'Every server with its on-disk log files',\n responses: { 200: { description: 'Log sources', content: jsonBody(logServersViewSchema) } },\n }),\n c => c.json({\n servers: deps.supervisor.views().map(server => ({\n serverId: server.id,\n label: server.config.label ?? server.id,\n status: server.status,\n ...deps.logFiles.info(server.id),\n })),\n }),\n )\n\n .get(\n '/logs/:id',\n describeRoute({\n tags: ['logs'],\n summary: 'Persisted log lines, with search and a stream filter',\n responses: { 200: { description: 'Lines' }, 404: ERROR_RESPONSES[404] },\n }),\n validate('param', idParam),\n validate('query', logHistoryQuerySchema),\n (c) => {\n const { id } = c.req.valid('param')\n if (!deps.store.getServer(id))\n throw unknownServer(id)\n\n const query = c.req.valid('query')\n const requested = query.tail === undefined ? DEFAULT_TAIL : Number.parseInt(query.tail, 10)\n const tail = Number.isNaN(requested) ? DEFAULT_TAIL : Math.min(Math.max(requested, MIN_TAIL), MAX_TAIL)\n\n const info = deps.logFiles.info(id)\n // Search reads a wider window than the display tail, otherwise a match older\n // than the last N lines would look like \"no results\".\n const search = query.search?.trim() ?? ''\n const window = search.length > 0 ? Math.max(tail, MAX_TAIL) : tail\n\n let lines = deps.logFiles.readTail(id, window)\n if (query.stream !== undefined && query.stream.length > 0)\n lines = lines.filter(line => line.stream === query.stream)\n if (search.length > 0) {\n const needle = search.toLowerCase()\n lines = lines.filter(line => line.text.toLowerCase().includes(needle))\n }\n\n return c.json({\n serverId: id,\n enabled: info.enabled,\n sizeBytes: info.sizeBytes,\n files: info.files.map(file => file.name),\n searched: search.length > 0 ? window : null,\n lines: lines.slice(-tail),\n })\n },\n )\n\n /** Raw file download; the name is checked against the rotation allowlist. */\n .get(\n '/logs/:id/download',\n describeRoute({\n tags: ['logs'],\n summary: 'Download one rotated log file',\n responses: { 200: { description: 'application/x-ndjson' }, 404: ERROR_RESPONSES[404] },\n }),\n validate('param', idParam),\n validate('query', downloadQuery),\n (c) => {\n const { id } = c.req.valid('param')\n if (!deps.store.getServer(id))\n throw unknownServer(id)\n\n const requested = c.req.valid('query').file ?? `${id}.log`\n const known = deps.logFiles.info(id).files.map(file => file.name)\n if (!known.includes(requested))\n throw new DetailedError('unknown log file', { statusCode: 404, code: 'UNKNOWN_LOG_FILE' })\n\n const file = path.join(deps.logFiles.directory, requested)\n const body = fs.readFileSync(file)\n return c.body(body, 200, {\n 'Content-Type': 'application/x-ndjson; charset=utf-8',\n 'Content-Length': String(body.byteLength),\n 'Content-Disposition': `attachment; filename=\"${requested}\"`,\n })\n },\n )\n\n .delete(\n '/logs/:id',\n describeRoute({\n tags: ['logs'],\n summary: 'Delete the persisted logs of one server',\n responses: { 200: { description: 'Cleared' }, 404: ERROR_RESPONSES[404] },\n }),\n validate('param', idParam),\n (c) => {\n const { id } = c.req.valid('param')\n if (!deps.store.getServer(id))\n throw unknownServer(id)\n deps.logFiles.clear(id)\n return c.json({ ok: true })\n },\n )\n}\n","import type { AppDeps } from '#src/app'\nimport { describeRoute } from 'hono-openapi'\nimport { appFactory } from '#src/helpers/factory'\n\n/**\n * Prometheus text for whatever wants to scrape the panel (Beszel, Grafana,\n * `curl`). Lives under `/api`, so it needs a session like every other route.\n */\nexport function createMetricsRoute(deps: AppDeps) {\n return appFactory.createApp()\n .get('/metrics', describeRoute({\n tags: ['panel'],\n summary: 'Prometheus text for whatever scrapes the panel',\n responses: { 200: { description: 'text/plain; version=0.0.4' } },\n }), (c) => {\n const state = deps.supervisor.getState()\n const lines: string[] = []\n\n const metric = (name: string, help: string, samples: string[]): void => {\n if (samples.length === 0)\n return\n lines.push(`# HELP ${name} ${help}`, `# TYPE ${name} gauge`, ...samples)\n }\n\n metric('hh_control_up', 'Control plane is serving', ['hh_control_up 1'])\n metric('hh_servers_total', 'Configured servers', [`hh_servers_total ${state.servers.length}`])\n\n const up = state.servers.map(server => `hh_server_up{server=\"${server.id}\"} ${server.status === 'running' ? 1 : 0}`)\n metric('hh_server_up', 'Server process is running', up)\n\n const restarts = state.servers.map(server => `hh_server_restarts_total{server=\"${server.id}\"} ${server.restarts}`)\n metric('hh_server_restarts_total', 'Restarts since the control plane started', restarts)\n\n const crashes = state.servers.map(server => `hh_server_crashes_24h{server=\"${server.id}\"} ${server.history.crashes}`)\n metric('hh_server_crashes_24h', 'Crashes in the last 24 hours', crashes)\n\n const uptime = state.servers\n .filter(server => server.history.uptimeRatio !== null)\n .map(server => `hh_server_uptime_ratio_24h{server=\"${server.id}\"} ${server.history.uptimeRatio!.toFixed(4)}`)\n metric('hh_server_uptime_ratio_24h', 'Share of the last 24 hours the server was up', uptime)\n\n const response = state.servers\n .filter(server => server.responseMs !== null)\n .map(server => `hh_server_response_ms{server=\"${server.id}\"} ${server.responseMs}`)\n metric('hh_server_response_ms', 'Last health probe latency in milliseconds', response)\n\n const rss = state.servers\n .filter(server => server.resources?.rssBytes != null)\n .map(server => `hh_server_rss_bytes{server=\"${server.id}\"} ${server.resources!.rssBytes}`)\n metric('hh_server_rss_bytes', 'RSS of the server process tree', rss)\n\n const cpu = state.servers\n .filter(server => server.resources?.cpuPercent != null)\n .map(server => `hh_server_cpu_percent{server=\"${server.id}\"} ${server.resources!.cpuPercent}`)\n metric('hh_server_cpu_percent', 'CPU percent of the server process tree', cpu)\n\n const disks = state.host.disks.map(disk => `hh_host_disk_used_percent{mount=\"${disk.path}\"} ${disk.usedPercent.toFixed(2)}`)\n metric('hh_host_disk_used_percent', 'Disk usage percent per configured path', disks)\n\n metric('hh_host_memory_used_percent', 'Memory usage percent', [`hh_host_memory_used_percent ${state.host.memoryUsedPercent.toFixed(2)}`])\n metric('hh_host_swap_used_percent', 'Swap usage percent', [`hh_host_swap_used_percent ${state.host.swapUsedPercent.toFixed(2)}`])\n metric('hh_host_load1_per_cpu', '1 minute load average per cpu', [\n `hh_host_load1_per_cpu ${((state.host.loadAvg[0] ?? 0) / Math.max(1, state.host.cpus)).toFixed(3)}`,\n ])\n\n return c.text(`${lines.join('\\n')}\\n`, 200, { 'Content-Type': 'text/plain; version=0.0.4; charset=utf-8' })\n })\n}\n","import type { Bot } from 'grammy'\nimport { autoRetry } from '@grammyjs/auto-retry'\nimport { Bot as GrammyBot } from 'grammy'\n\n/**\n * Telegram Bot API access, built on grammY.\n *\n * grammY is used for what it is good at — a typed, retrying, extensible Bot API\n * client — while the bot itself stays outbound-only for now. If inbound commands\n * or a webhook are ever wanted, the same instance can host handlers without\n * touching the notification code.\n *\n * `autoRetry` handles Telegram's 429 `retry_after` (and other transient failures)\n * so callers get either a result or a real error.\n */\n\nconst bots = new Map<string, Bot>()\n\nexport function getBot(token: string): Bot {\n const cached = bots.get(token)\n if (cached)\n return cached\n\n const bot = new GrammyBot(token, { client: { timeoutSeconds: 10 } })\n bot.api.config.use(autoRetry({ maxRetryAttempts: 3, maxDelaySeconds: 20 }))\n bots.set(token, bot)\n return bot\n}\n\n/** Drops cached clients; used when the token changes or the service shuts down. */\nexport function forgetBots(): void {\n bots.clear()\n}\n\nexport function escapeHtml(value: string): string {\n return value.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')\n}\n\nexport function formatTelegramMessage(title: string, lines: string[]): string {\n const body = lines.filter(line => line.length > 0).map(line => `• ${escapeHtml(line)}`).join('\\n')\n return `<b>${escapeHtml(title)}</b>${body.length > 0 ? `\\n${body}` : ''}`\n}\n\nexport interface TelegramOutcome {\n ok: boolean\n error?: string\n}\n\n/** Turns a grammY error into something worth showing in the settings page. */\nexport function describeTelegramError(error: unknown): string {\n if (typeof error === 'object' && error !== null) {\n const candidate = error as { error_code?: number, description?: string, message?: string, parameters?: { retry_after?: number } }\n const description = candidate.description ?? candidate.message\n if (description) {\n const code = candidate.error_code === undefined ? '' : ` (${candidate.error_code})`\n const retry = candidate.parameters?.retry_after === undefined ? '' : `, retry in ${candidate.parameters.retry_after}s`\n return `${description}${code}${retry}`\n }\n }\n return error instanceof Error ? error.message : String(error)\n}\n\nexport async function sendTelegramMessage(token: string, chatId: string, html: string): Promise<TelegramOutcome> {\n try {\n await getBot(token).api.sendMessage(chatId, html, {\n parse_mode: 'HTML',\n link_preview_options: { is_disabled: true },\n })\n return { ok: true }\n }\n catch (error) {\n return { ok: false, error: describeTelegramError(error) }\n }\n}\n\nexport async function verifyTelegramToken(token: string): Promise<{ ok: boolean, username?: string, error?: string }> {\n try {\n const me = await getBot(token).api.getMe()\n return { ok: true, username: me.username }\n }\n catch (error) {\n return { ok: false, error: describeTelegramError(error) }\n }\n}\n\nexport interface TelegramChat {\n id: number | string\n title: string\n}\n\n/**\n * Recent chats that talked to the bot, so a chat id can be picked instead of\n * hunted down by hand. Telegram only reports chats with pending updates, so the\n * caller is told to message the bot first.\n */\nexport async function listTelegramChats(token: string): Promise<{ ok: boolean, chats: TelegramChat[], error?: string }> {\n try {\n const updates = await getBot(token).api.getUpdates({\n limit: 100,\n allowed_updates: ['message', 'channel_post', 'edited_message'],\n })\n\n const chats = new Map<string, TelegramChat>()\n for (const update of updates) {\n const chat = update.message?.chat ?? update.channel_post?.chat ?? update.edited_message?.chat\n if (!chat)\n continue\n const title = 'title' in chat && chat.title\n ? chat.title\n : 'username' in chat && chat.username\n ? `@${chat.username}`\n : 'first_name' in chat && chat.first_name\n ? chat.first_name\n : 'private chat'\n chats.set(String(chat.id), { id: chat.id, title })\n }\n\n return { ok: true, chats: [...chats.values()] }\n }\n catch (error) {\n return { ok: false, chats: [], error: describeTelegramError(error) }\n }\n}\n","import type { AppDeps } from '#src/app'\nimport { DetailedError } from '@namesmt/utils'\nimport { type } from 'arktype'\nimport { describeRoute } from 'hono-openapi'\nimport { appFactory } from '#src/helpers/factory'\nimport { ERROR_RESPONSES, jsonBody } from '#src/helpers/openapi'\nimport { validate } from '#src/helpers/validator'\nimport { forgetBots } from '#src/providers/telegram'\nimport { notificationActionSchema, telegramTokenSchema } from '#src/shared/contracts'\n\n/**\n * The bot token is written straight to the secrets file and never into\n * `servers.config.json`, so notification *policy* and the *credential* stay\n * separate.\n */\nexport function createNotificationsRoute(deps: AppDeps) {\n return appFactory.createApp()\n .put(\n '/notifications/token',\n describeRoute({\n tags: ['notifications'],\n summary: 'Store the Telegram bot token (verified first)',\n responses: { 200: { description: 'Stored' }, 400: ERROR_RESPONSES[400] },\n }),\n validate('json', telegramTokenSchema),\n async (c) => {\n const { botToken } = c.req.valid('json')\n const verified = await deps.notifications.verifyToken(botToken)\n if (!verified.ok)\n throw new DetailedError(`telegram rejected the token: ${verified.error ?? 'unknown error'}`, { statusCode: 400, code: 'TELEGRAM_TOKEN_REJECTED' })\n\n deps.secrets.setTelegramToken(botToken)\n forgetBots()\n return c.json({ ok: true, username: verified.username ?? null })\n },\n )\n\n .delete(\n '/notifications/token',\n describeRoute({\n tags: ['notifications'],\n summary: 'Forget the Telegram bot token',\n responses: { 200: { description: 'Removed' } },\n }),\n (c) => {\n deps.secrets.setTelegramToken(null)\n forgetBots()\n return c.json({ ok: true })\n },\n )\n\n .post(\n '/notifications/test',\n describeRoute({\n tags: ['notifications'],\n summary: 'Send a test message',\n responses: { 200: { description: 'Sent or refused' }, 400: ERROR_RESPONSES[400] },\n }),\n validate('json', notificationActionSchema),\n async (c) => {\n const result = await deps.notifications.sendTest(c.req.valid('json'))\n if (!result.ok)\n throw new DetailedError(result.error ?? 'the test message failed', { statusCode: 400, code: 'TELEGRAM_SEND_FAILED' })\n return c.json(result)\n },\n )\n\n .post(\n '/notifications/detect-chats',\n describeRoute({\n tags: ['notifications'],\n summary: 'List the chats the bot can see',\n responses: {\n 200: { description: 'Chats', content: jsonBody(type({ chats: type({ id: 'string | number', title: 'string' }).array() })) },\n 400: ERROR_RESPONSES[400],\n },\n }),\n validate('json', notificationActionSchema),\n async (c) => {\n const result = await deps.notifications.detectChats(c.req.valid('json'))\n if (!result.ok)\n throw new DetailedError(result.error ?? 'could not list chats', { statusCode: 400, code: 'TELEGRAM_LIST_FAILED' })\n return c.json({ chats: result.chats })\n },\n )\n}\n","import type { RawConfig } from '#src/config/schema'\n\n/**\n * The config shape this release understands. Bump it only for a change that an\n * older release cannot simply ignore, and add the step that lifts the previous\n * shape to it in `configMigrations` — then a config that needs it refuses to\n * start until `home-hosted migrate` has run.\n *\n * 1 — the shape that has been in use up to and including 0.3.0, plus the `meta`\n * block this constant was introduced with. Unstamped files are schema 1.\n */\nexport const CONFIG_SCHEMA = 1\n\nexport interface ConfigMigration {\n /** The schema this step produces; steps run in ascending order. */\n to: number\n /** One line, printed by `migrate` before anything is written. */\n describe: string\n apply: (config: RawConfig) => RawConfig\n}\n\n/**\n * Every published migration, oldest first. Keep this list small and permanent:\n * a config may arrive from any earlier release, so a step is never removed.\n */\nexport const configMigrations: ConfigMigration[] = []\n\nexport interface MigrationPlan {\n from: number\n to: number\n steps: ConfigMigration[]\n /** The file was written by a release newer than this one. */\n tooNew: boolean\n}\n\nexport interface MigrationOptions {\n /** Defaults to every migration this release ships. */\n migrations?: ConfigMigration[]\n /** The schema to reach; defaults to what this release understands. */\n to?: number\n}\n\n/** What would have to run to bring `from` up to `to`. */\nexport function planConfigMigrations(from: number, options: MigrationOptions = {}): MigrationPlan {\n const to = options.to ?? CONFIG_SCHEMA\n const migrations = options.migrations ?? configMigrations\n const steps = migrations\n .filter(migration => migration.to > from && migration.to <= to)\n .sort((a, b) => a.to - b.to)\n return { from, to, steps, tooNew: from > to }\n}\n\n/** Applies the plan in order; the caller owns writing the result. */\nexport function applyConfigMigrations(config: RawConfig, from: number, options: MigrationOptions = {}): { config: RawConfig, applied: ConfigMigration[] } {\n const { steps } = planConfigMigrations(from, options)\n let current = config\n for (const step of steps)\n current = step.apply(current)\n return { config: current, applied: steps }\n}\n","import type { ServerConfig } from '#src/shared/contracts'\nimport { type } from 'arktype'\nimport {\n backupsSchema,\n controlSchema,\n defaultsSchema,\n hostSchema,\n logsSchema,\n notificationsSchema,\n serverSchema,\n} from '#src/shared/contracts'\n\nexport { backupsSchema, controlSchema, defaultsSchema, hostSchema, logsSchema, notificationsSchema, serverSchema }\nexport type { ServerConfig } from '#src/shared/contracts'\n\n/**\n * Which release wrote the file, and the config shape it wrote. Optional so a\n * config from before the stamp still reads, and so an archive made by an older\n * release still restores.\n */\nexport const metaSchema = type({\n writtenBy: 'string = \"\"',\n schema: 'number.integer >= 1 = 1',\n}).onUndeclaredKey('reject')\n\n/** The shape of `servers.config.json`: control panel settings, defaults, servers. */\nexport const configSchema = type({\n $schema: 'string?',\n meta: metaSchema.optional(),\n control: controlSchema.default(() => ({})),\n defaults: defaultsSchema.default(() => ({})),\n logs: logsSchema.default(() => ({})),\n notifications: notificationsSchema.default(() => ({})),\n host: hostSchema.default(() => ({})),\n backups: backupsSchema.default(() => ({})),\n servers: serverSchema.array().default(() => []),\n}).onUndeclaredKey('reject')\n\n/** Same shape as the schema output, with each server `port` normalized to `null` when unset. */\nexport type ResolvedConfig = Omit<typeof configSchema.infer, 'servers'> & { servers: ServerConfig[] }\n\n/** Every key `configSchema` knows, for reporting blocks a newer release added. */\nexport const CONFIG_KEYS = ['$schema', 'meta', 'control', 'defaults', 'logs', 'notifications', 'host', 'backups', 'servers'] as const\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/**\n * Resolves one entry against the panel's server defaults. A group (`restart`,\n * `health`, `health.http`, `stop`) merges key by key, so an entry that decides one\n * member does not silently fall back to the *schema* default for the others — which\n * is the whole point of the panel having defaults at all.\n */\nexport function mergeDefaults(\n defaults: Record<string, unknown>,\n entry: Record<string, unknown>,\n): Record<string, unknown> {\n const merged: Record<string, unknown> = { ...entry }\n for (const [key, value] of Object.entries(defaults)) {\n const current = merged[key]\n if (current === undefined)\n merged[key] = value\n else if (isRecord(value) && isRecord(current))\n merged[key] = mergeDefaults(value, current)\n }\n return merged\n}\n\n/** The on-disk shape: everything optional except `servers`, defaults applied per entry. */\nexport interface RawConfig {\n $schema?: string\n meta?: { writtenBy?: string, schema?: number }\n control?: Record<string, unknown>\n defaults?: Record<string, unknown>\n logs?: Record<string, unknown>\n notifications?: Record<string, unknown>\n host?: Record<string, unknown>\n backups?: Record<string, unknown>\n servers?: Record<string, unknown>[]\n}\n","import fs from 'node:fs'\nimport path from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nlet cached: string | null = null\n\n/**\n * The running release, read from the package manifest.\n *\n * The caller may be `src/**` under tsx or the built `dist/cli.js`, so the manifest\n * is found by walking up rather than by a fixed relative path. `src/cli.ts` keeps\n * its own read because it may only import node builtins statically.\n */\nexport function appVersion(): string {\n if (cached !== null)\n return cached\n\n let dir = path.dirname(fileURLToPath(import.meta.url))\n for (let depth = 0; depth < 4; depth++) {\n try {\n const manifest = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf8')) as { name?: string, version?: string }\n if (manifest.name === 'home-hosted' && typeof manifest.version === 'string') {\n cached = manifest.version\n return cached\n }\n }\n catch {\n // no manifest here; keep walking up\n }\n const parent = path.dirname(dir)\n if (parent === dir)\n break\n dir = parent\n }\n\n cached = '0.0.0'\n return cached\n}\n","import type { MigrationOptions } from '#src/config/migrations'\nimport type { RawConfig, ResolvedConfig } from '#src/config/schema'\nimport type { ServerConfig } from '#src/shared/contracts'\nimport { type } from 'arktype'\nimport { CONFIG_SCHEMA, planConfigMigrations } from '#src/config/migrations'\nimport {\n backupsSchema,\n CONFIG_KEYS,\n controlSchema,\n defaultsSchema,\n hostSchema,\n logsSchema,\n mergeDefaults,\n notificationsSchema,\n\n serverSchema,\n} from '#src/config/schema'\nimport { appVersion } from '#src/helpers/version'\n\nexport interface ConfigParse {\n /** Null when something made the config unusable; `errors` says why. */\n config: ResolvedConfig | null\n /** Blocking problems: what the panel must not start on. */\n errors: string[]\n /** Keys a newer release wrote that this one does not know, by path. */\n unknownKeys: string[]\n /** Real but non-blocking problems: supervision still runs, the file is still used. */\n warnings: string[]\n /** The shape the file declares; an unstamped file reads as the current one. */\n schemaVersion: number\n /** What wrote it, when the file says. */\n writtenBy: string | null\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\ntype Validator = (input: unknown) => unknown\n\nconst GROUPS: ReadonlyArray<readonly [string, Validator]> = [\n ['control', controlSchema as unknown as Validator],\n ['defaults', defaultsSchema as unknown as Validator],\n ['logs', logsSchema as unknown as Validator],\n ['notifications', notificationsSchema as unknown as Validator],\n ['host', hostSchema as unknown as Validator],\n ['backups', backupsSchema as unknown as Validator],\n]\n\n/** ArkType reports an unrecognized key with this problem, carrying its path. */\nconst UNDECLARED = 'must be removed'\n\nfunction deleteAtPath(root: Record<string, unknown>, path: readonly (string | number)[]): void {\n let node: unknown = root\n for (const key of path.slice(0, -1)) {\n node = Array.isArray(node) ? node[Number(key)] : isRecord(node) ? node[key] : undefined\n if (node === undefined)\n return\n }\n const last = path[path.length - 1]\n if (last === undefined)\n return\n if (isRecord(node))\n delete node[String(last)]\n else if (Array.isArray(node) && typeof last === 'number')\n node.splice(last, 1)\n}\n\n/**\n * Validates one object against one schema, tolerating keys the schema does not\n * know: a config written by a newer release has to keep working here, and losing\n * the *whole group* to schema defaults would silently reset the listener port,\n * the bind and the auth policy over a single unrecognized key.\n *\n * Only unrecognized keys are dropped, and each one is reported. Anything else is\n * a real problem, returned for the caller to refuse on.\n */\nfunction parseTolerant(\n value: unknown,\n schema: Validator,\n prefix: string,\n unknownKeys: string[],\n): { value: unknown, error: string | null } {\n const candidate = structuredClone(value)\n\n for (let pass = 0; pass < 25; pass++) {\n const parsed = schema(candidate)\n if (!(parsed instanceof type.errors))\n return { value: parsed, error: null }\n\n const problems = parsed as unknown as Array<{ path: (string | number)[], problem: string }>\n const removable = problems.filter(problem => problem.problem === UNDECLARED)\n if (removable.length === 0)\n return { value: null, error: parsed.summary }\n\n if (!isRecord(candidate))\n return { value: null, error: parsed.summary }\n\n for (const problem of removable) {\n const at = `${prefix}.${problem.path.join('.')}`\n if (!unknownKeys.includes(at))\n unknownKeys.push(at)\n deleteAtPath(candidate, problem.path)\n }\n }\n\n return { value: null, error: 'too many unrecognized keys to ignore' }\n}\n\n/**\n * Reads a `servers.config.json` from any release. Unrecognized keys are reported\n * and left on disk; blocking problems — including a file this release cannot\n * understand — land in `errors` and leave `config` null.\n */\nexport function parseConfig(raw: unknown, options: MigrationOptions = {}): ConfigParse {\n const unknownKeys: string[] = []\n const errors: string[] = []\n const warnings: string[] = []\n const result: ConfigParse = { config: null, errors, unknownKeys, warnings, schemaVersion: CONFIG_SCHEMA, writtenBy: null }\n\n if (!isRecord(raw)) {\n errors.push('the config must contain a JSON object')\n return result\n }\n\n const meta = isRecord(raw.meta) ? raw.meta : null\n const schemaVersion = typeof meta?.schema === 'number' ? meta.schema : CONFIG_SCHEMA\n const writtenBy = typeof meta?.writtenBy === 'string' && meta.writtenBy.length > 0 ? meta.writtenBy : null\n result.schemaVersion = schemaVersion\n result.writtenBy = writtenBy\n\n if (schemaVersion > CONFIG_SCHEMA) {\n errors.push(`written by home-hosted ${writtenBy ?? 'a newer release'} (config schema ${schemaVersion}); this release understands schema ${CONFIG_SCHEMA}`)\n return result\n }\n\n const { steps } = planConfigMigrations(schemaVersion, options)\n if (steps.length > 0) {\n errors.push(`config schema ${schemaVersion} needs ${steps.length} migration${steps.length === 1 ? '' : 's'} before this release can use it`)\n return result\n }\n\n for (const key of Object.keys(raw)) {\n if (!(CONFIG_KEYS as readonly string[]).includes(key))\n unknownKeys.push(key)\n }\n\n const groups: Record<string, unknown> = {}\n for (const [name, schema] of GROUPS) {\n const parsed = parseTolerant(raw[name] ?? {}, schema, name, unknownKeys)\n if (parsed.error !== null)\n errors.push(`${name}: ${parsed.error}`)\n groups[name] = parsed.value ?? schema({})\n }\n\n const defaults = groups.defaults as ResolvedConfig['defaults']\n const servers: ServerConfig[] = []\n const seen = new Set<string>()\n const rawServers = Array.isArray(raw.servers) ? raw.servers : []\n\n rawServers.forEach((entry, index) => {\n const label = `servers[${index}]`\n const merged = isRecord(entry) ? mergeDefaults(defaults, entry) : entry\n const parsed = parseTolerant(merged, serverSchema as unknown as Validator, label, unknownKeys)\n if (parsed.error !== null) {\n const id = isRecord(entry) ? entry.id : undefined\n errors.push(`${label}${typeof id === 'string' ? ` (\"${id}\")` : ''}: ${parsed.error}`)\n return\n }\n const server = parsed.value as ServerConfig\n if (seen.has(server.id)) {\n errors.push(`${label}: duplicate id \"${server.id}\"`)\n return\n }\n seen.add(server.id)\n servers.push({ ...server, port: server.port ?? null })\n })\n\n // Dangling dependencies and cycles are reported, never fatal: supervision still runs.\n warnings.push(...validateDependencies(servers))\n\n if (errors.length > 0)\n return result\n\n result.config = {\n meta: { writtenBy: writtenBy ?? '', schema: schemaVersion },\n control: groups.control as ResolvedConfig['control'],\n defaults,\n logs: groups.logs as ResolvedConfig['logs'],\n notifications: groups.notifications as ResolvedConfig['notifications'],\n host: groups.host as ResolvedConfig['host'],\n backups: groups.backups as ResolvedConfig['backups'],\n servers,\n } as ResolvedConfig\n\n return result\n}\n\n/** Dangling dependencies and cycles are reported, not fatal: supervision still runs. */\nfunction validateDependencies(servers: ServerConfig[]): string[] {\n const ids = new Set(servers.map(server => server.id))\n const errors: string[] = []\n\n for (const server of servers) {\n for (const dependency of server.dependsOn) {\n if (dependency === server.id)\n errors.push(`\"${server.id}\" depends on itself`)\n else if (!ids.has(dependency))\n errors.push(`\"${server.id}\" depends on unknown server \"${dependency}\"`)\n }\n }\n\n const visiting = new Set<string>()\n const settled = new Set<string>()\n const byId = new Map(servers.map(server => [server.id, server]))\n const walk = (id: string): void => {\n if (settled.has(id))\n return\n if (visiting.has(id)) {\n errors.push(`dependency cycle through \"${id}\"`)\n return\n }\n visiting.add(id)\n for (const dependency of byId.get(id)?.dependsOn ?? []) walk(dependency)\n visiting.delete(id)\n settled.add(id)\n }\n for (const server of servers) walk(server.id)\n\n return [...new Set(errors)]\n}\n\n/** Adds the stamp every write carries, so the next release can tell what wrote the file. */\nexport function stampConfig(draft: RawConfig): RawConfig {\n const { $schema, meta: _meta, ...rest } = draft\n return {\n ...($schema === undefined ? {} : { $schema }),\n meta: { writtenBy: appVersion(), schema: CONFIG_SCHEMA },\n ...rest,\n }\n}\n","import type { RawConfig } from '#src/config/schema'\n\n/**\n * Written when a data directory has no config yet (`$HHOSTED_HOME/servers.config.json`).\n *\n * It stays empty on purpose: home-hosted ships no servers of its own, so what a\n * user supervises is theirs to declare. The rest of the file is default policy,\n * which the settings page can change.\n */\nexport const SEED_CONFIG: RawConfig = {\n $schema: './servers.config.schema.json',\n control: {\n port: 3999,\n host: 'local',\n openBrowser: false,\n },\n defaults: {\n enabled: true,\n autostart: false,\n bind: 'local',\n onPortConflict: 'block',\n },\n servers: [],\n}\n","import type { ConfigMigration } from '#src/config/migrations'\nimport type { RawConfig, ResolvedConfig, ServerConfig } from '#src/config/schema'\nimport type {\n BackupsConfig,\n ControlConfig,\n HostConfig,\n LogsConfig,\n NotificationsConfig,\n ServerDefaults,\n ServerPatch,\n SettingsPatch,\n} from '#src/shared/contracts'\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport { type } from 'arktype'\nimport { CONFIG_SCHEMA, planConfigMigrations } from '#src/config/migrations'\nimport { parseConfig, stampConfig } from '#src/config/parse'\nimport {\n backupsSchema,\n configSchema,\n controlSchema,\n defaultsSchema,\n hostSchema,\n logsSchema,\n mergeDefaults,\n notificationsSchema,\n serverSchema,\n} from '#src/config/schema'\nimport { SEED_CONFIG } from '#src/config/seed'\nimport { writeFileAtomic } from '#src/helpers/atomic'\nimport { configSchemaPath } from '#src/helpers/paths'\n\n/** Nested groups a patch merges into instead of replacing. */\nconst SERVER_MERGE_KEYS = new Set(['restart', 'health', 'stop'])\nconst CONTROL_MERGE_KEYS = new Set(['auth', 'tls'])\nconst NOTIFICATION_MERGE_KEYS = new Set(['telegram'])\nconst EMPTY_MERGE_KEYS = new Set<string>()\n\nexport class ConfigError extends Error {\n override name = 'ConfigError'\n}\n\nfunction formatErrors(errors: type.errors): string {\n return errors.summary\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction applyPatch(target: Record<string, unknown>, patch: Record<string, unknown>, mergeKeys: Set<string>): void {\n for (const [key, value] of Object.entries(patch)) {\n if (value === undefined)\n continue\n if (mergeKeys.has(key) && isRecord(value) && isRecord(target[key])) {\n target[key] = mergeGroup(target[key], value)\n continue\n }\n target[key] = value\n }\n}\n\n/**\n * Merges one nested group recursively — `health.http` is a group of its own, and\n * replacing it wholesale would silently reset the siblings a partial patch never\n * mentioned. An explicit `null` removes a key, which is how a schema-optional\n * field is cleared.\n */\nfunction mergeGroup(target: Record<string, unknown>, patch: Record<string, unknown>): Record<string, unknown> {\n const merged = { ...target }\n for (const [key, value] of Object.entries(patch)) {\n if (value === undefined)\n continue\n if (value === null) {\n delete merged[key]\n continue\n }\n if (isRecord(value) && isRecord(merged[key])) {\n merged[key] = mergeGroup(merged[key] as Record<string, unknown>, value)\n continue\n }\n merged[key] = value\n }\n return merged\n}\n\nexport class ConfigStore {\n private raw: RawConfig = {}\n /** The bytes behind the live config, so a watcher can tell a real edit from our own write. */\n private lastText: string | null = null\n private resolvedConfig!: ResolvedConfig\n private error: string | null = null\n private warnings: string[] = []\n private schemaVersion = CONFIG_SCHEMA\n private readonly listeners = new Set<() => void>()\n\n constructor(private readonly file: string, private readonly seed: RawConfig = SEED_CONFIG) {}\n\n get path(): string {\n return this.file\n }\n\n get config(): ResolvedConfig {\n return this.resolvedConfig\n }\n\n get configError(): string | null {\n return this.error\n }\n\n /** Keys a newer release wrote that this one ignores; nothing to refuse over. */\n get configWarnings(): string[] {\n return [...this.warnings]\n }\n\n /** The shape the file declares, as last read. */\n get configSchemaVersion(): number {\n return this.schemaVersion\n }\n\n /** Steps that would have to run before this release could use the file. */\n get pendingMigrations(): ConfigMigration[] {\n return planConfigMigrations(this.schemaVersion).steps\n }\n\n get servers(): ServerConfig[] {\n return this.resolvedConfig.servers\n }\n\n get defaults(): ResolvedConfig['defaults'] {\n return this.resolvedConfig.defaults\n }\n\n get rawConfig(): RawConfig {\n return structuredClone(this.raw)\n }\n\n onChange(listener: () => void): () => void {\n this.listeners.add(listener)\n return () => this.listeners.delete(listener)\n }\n\n getServer(id: string): ServerConfig | undefined {\n return this.servers.find(server => server.id === id)\n }\n\n /**\n * Reads the file and tells the listeners, so whatever wrote it — the settings\n * page, a restored backup, or the file watcher — becomes the live config.\n */\n load(): void {\n this.read()\n this.notify()\n }\n\n /**\n * The watcher's entry point: re-reads the file only when its bytes changed.\n *\n * `changed` means the file on disk is different from what the live config was\n * read from, `applied` means that difference was accepted — a file this release\n * cannot read is reported and the config already running is left alone, which is\n * what keeps an editor's typo from stopping every server.\n */\n reloadFromDisk(): { changed: boolean, applied: boolean, error: string | null } {\n let text: string\n try {\n text = fs.readFileSync(this.file, 'utf8')\n }\n catch {\n // Deleting the file is not an edit of it: the seed is written on a first\n // load, never under a running panel.\n this.error = `${path.basename(this.file)} is gone`\n return { changed: false, applied: false, error: this.error }\n }\n\n if (text === this.lastText) {\n // These are the bytes the live config came from, so whatever went wrong in\n // between (the file was gone, or was edited into something unusable and then\n // put back) is over. The listeners have to hear about it: the error is part of\n // the state they publish, and a notice for a file that is fine again is a lie.\n if (this.error !== null) {\n this.error = null\n this.load()\n }\n return { changed: false, applied: false, error: null }\n }\n\n const before = this.resolvedConfig\n this.load()\n return { changed: true, applied: this.resolvedConfig !== before, error: this.error }\n }\n\n private read(): void {\n if (!fs.existsSync(this.file)) {\n // A missing file gets the seed, stamped and written out for the user to edit.\n const seed = stampConfig(structuredClone(this.seed))\n const text = `${JSON.stringify(seed, null, 2)}\\n`\n writeFileAtomic(this.file, text)\n this.lastText = text\n this.raw = seed\n this.apply(seed)\n return\n }\n\n let text: string\n let parsed: unknown\n try {\n text = fs.readFileSync(this.file, 'utf8')\n parsed = JSON.parse(text)\n }\n catch (error) {\n this.error = `cannot parse ${path.basename(this.file)}: ${(error as Error).message}`\n // The same rule `apply` follows for a value it rejects: a file that cannot be\n // trusted never replaces a config this process is already running — neither the\n // running one nor the `raw` one every write patches, or the next settings save\n // would write a config with no servers in it. Only a first load has nothing to keep.\n if (this.resolvedConfig === undefined) {\n this.raw = {}\n this.resolvedConfig = this.resolveFallback()\n }\n return\n }\n\n this.lastText = text\n this.apply(parsed as RawConfig)\n }\n\n private notify(): void {\n for (const listener of this.listeners) listener()\n }\n\n updateServer(id: string, patch: ServerPatch): ServerConfig {\n const index = this.raw.servers?.findIndex(entry => entry.id === id) ?? -1\n if (index < 0)\n throw new ConfigError(`unknown server \"${id}\"`)\n\n const draft = structuredClone(this.raw)\n const entry = draft.servers![index]!\n\n applyPatch(entry, patch as Record<string, unknown>, SERVER_MERGE_KEYS)\n\n const validated = this.validateServer(entry, `servers[${index}]`)\n this.commit(draft)\n return validated\n }\n\n updateControl(patch: NonNullable<SettingsPatch['control']>): ControlConfig {\n const draft = structuredClone(this.raw)\n draft.control = { ...(draft.control ?? {}) }\n applyPatch(draft.control, patch as Record<string, unknown>, CONTROL_MERGE_KEYS)\n\n const control = controlSchema(draft.control)\n if (control instanceof type.errors)\n throw new ConfigError(`control: ${formatErrors(control)}`)\n\n this.commit(draft)\n return control\n }\n\n updateLogs(patch: NonNullable<SettingsPatch['logs']>): LogsConfig {\n const draft = structuredClone(this.raw)\n draft.logs = { ...(draft.logs ?? {}) }\n applyPatch(draft.logs, patch as Record<string, unknown>, EMPTY_MERGE_KEYS)\n\n const logs = logsSchema(draft.logs)\n if (logs instanceof type.errors)\n throw new ConfigError(`logs: ${formatErrors(logs)}`)\n\n this.commit(draft)\n return logs\n }\n\n updateNotifications(patch: NonNullable<SettingsPatch['notifications']>): NotificationsConfig {\n const draft = structuredClone(this.raw)\n draft.notifications = { ...(draft.notifications ?? {}) }\n applyPatch(draft.notifications, patch as Record<string, unknown>, NOTIFICATION_MERGE_KEYS)\n\n const notifications = notificationsSchema(draft.notifications)\n if (notifications instanceof type.errors)\n throw new ConfigError(`notifications: ${formatErrors(notifications)}`)\n\n this.commit(draft)\n return notifications\n }\n\n updateHost(patch: NonNullable<SettingsPatch['host']>): HostConfig {\n const draft = structuredClone(this.raw)\n draft.host = { ...(draft.host ?? {}) }\n applyPatch(draft.host, patch as Record<string, unknown>, EMPTY_MERGE_KEYS)\n\n const host = hostSchema(draft.host)\n if (host instanceof type.errors)\n throw new ConfigError(`host: ${formatErrors(host)}`)\n\n this.commit(draft)\n return host\n }\n\n updateBackups(patch: NonNullable<SettingsPatch['backups']>): BackupsConfig {\n const draft = structuredClone(this.raw)\n draft.backups = { ...(draft.backups ?? {}) }\n applyPatch(draft.backups, patch as Record<string, unknown>, EMPTY_MERGE_KEYS)\n\n const backups = backupsSchema(draft.backups)\n if (backups instanceof type.errors)\n throw new ConfigError(`backups: ${formatErrors(backups)}`)\n\n this.commit(draft)\n return backups\n }\n\n updateDefaults(patch: NonNullable<SettingsPatch['defaults']>): ServerDefaults {\n const draft = structuredClone(this.raw)\n draft.defaults = { ...(draft.defaults ?? {}) }\n applyPatch(draft.defaults, patch as Record<string, unknown>, SERVER_MERGE_KEYS)\n\n const defaults = defaultsSchema(draft.defaults)\n if (defaults instanceof type.errors)\n throw new ConfigError(`defaults: ${formatErrors(defaults)}`)\n\n this.commit(draft)\n return defaults\n }\n\n addServer(input: Record<string, unknown>): ServerConfig {\n const draft = structuredClone(this.raw)\n draft.servers ??= []\n if (draft.servers.some(entry => entry.id === input.id)) {\n throw new ConfigError(`server \"${String(input.id)}\" already exists`)\n }\n\n const index = draft.servers.length\n draft.servers.push(structuredClone(input))\n const validated = this.validateServer(draft.servers[index]!, `servers[${index}]`)\n this.commit(draft)\n return validated\n }\n\n removeServer(id: string): void {\n const draft = structuredClone(this.raw)\n const before = draft.servers?.length ?? 0\n draft.servers = (draft.servers ?? []).filter(entry => entry.id !== id)\n if (draft.servers.length === before)\n throw new ConfigError(`unknown server \"${id}\"`)\n this.commit(draft)\n }\n\n /** Regenerates `servers.config.schema.json` for editor autocomplete. */\n writeJsonSchema(): void {\n const schema = JSON.stringify(configSchema.toJsonSchema(), null, 2)\n const current = fs.existsSync(configSchemaPath) ? fs.readFileSync(configSchemaPath, 'utf8') : null\n if (current !== schema)\n writeFileAtomic(configSchemaPath, schema)\n }\n\n private validateServer(entry: Record<string, unknown>, label: string): ServerConfig {\n const parsed = serverSchema(mergeDefaults(this.defaults, entry))\n if (parsed instanceof type.errors)\n throw new ConfigError(`${label}: ${formatErrors(parsed)}`)\n return { ...parsed, port: parsed.port ?? null }\n }\n\n private commit(draft: RawConfig): void {\n // Every write carries the stamp, so the next release can tell what wrote it.\n const stamped = stampConfig(draft)\n const text = `${JSON.stringify(stamped, null, 2)}\\n`\n writeFileAtomic(this.file, text)\n this.lastText = text\n this.raw = stamped\n this.apply(stamped)\n this.notify()\n }\n\n private resolveFallback(): ResolvedConfig {\n const control = controlSchema({})\n const defaults = defaultsSchema({})\n if (control instanceof type.errors || defaults instanceof type.errors) {\n throw new ConfigError('internal: default config failed validation')\n }\n const logs = logsSchema({})\n const notifications = notificationsSchema({})\n const host = hostSchema({})\n const backups = backupsSchema({})\n if (logs instanceof type.errors || notifications instanceof type.errors || host instanceof type.errors || backups instanceof type.errors) {\n throw new ConfigError('internal: default settings failed validation')\n }\n return { control, defaults, logs, notifications, host, backups, servers: [] }\n }\n\n private apply(raw: RawConfig): void {\n this.raw = raw\n const parsed = parseConfig(raw)\n this.error = parsed.errors.length > 0 ? parsed.errors.join('; ') : null\n this.schemaVersion = parsed.schemaVersion\n this.warnings = [\n ...parsed.warnings,\n ...(parsed.unknownKeys.length === 0\n ? []\n : [`${path.basename(this.file)} carries ${parsed.unknownKeys.length} unrecognized key(s) this release ignores: ${parsed.unknownKeys.join(', ')}`]),\n ]\n // A file that cannot be trusted never replaces a config this process is already\n // running: a bad edit must not disturb supervision or blank the panel. It only\n // falls back to defaults when there is nothing good to keep (a first load).\n if (parsed.config !== null)\n this.resolvedConfig = parsed.config\n else if (this.resolvedConfig === undefined)\n this.resolvedConfig = this.resolveFallback()\n }\n}\n","import type { AppDeps } from '#src/app'\nimport { DetailedError } from '@namesmt/utils'\nimport { type } from 'arktype'\nimport { describeRoute } from 'hono-openapi'\nimport { streamSSE } from 'hono/streaming'\nimport { ConfigError } from '#src/config/store'\nimport { appFactory } from '#src/helpers/factory'\nimport { ERROR_RESPONSES, jsonBody } from '#src/helpers/openapi'\nimport { validate } from '#src/helpers/validator'\nimport { freePortResultSchema, logQuerySchema, serverCreateSchema, serverPatchSchema, serverViewSchema } from '#src/shared/contracts'\n\nconst idParam = type({ id: 'string >= 1' })\n/** Pending SSE writes per connection; log frames are dropped past it, state is not. */\nconst MAX_PENDING_WRITES = 200\nconst serverResponse = type({ server: serverViewSchema })\nconst serversResponse = type({ servers: serverViewSchema.array() })\nconst okResponse = type({ ok: 'boolean' })\n\n/** Unknown ids are 404; a server that exists but cannot start is a 409. */\nfunction statusFor(result: { ok: boolean, error?: string }): 200 | 404 | 409 {\n if (result.ok)\n return 200\n return result.error?.startsWith('unknown server') ? 404 : 409\n}\n\nfunction unknownServer(id: string): DetailedError {\n return new DetailedError(`unknown server \"${id}\"`, { statusCode: 404, code: 'UNKNOWN_SERVER' })\n}\n\nexport function createServersRoute(deps: AppDeps) {\n return appFactory.createApp()\n .get(\n '/',\n describeRoute({\n tags: ['servers'],\n summary: 'Every supervised server, with its live state',\n responses: { 200: { description: 'The servers', content: jsonBody(serversResponse) } },\n }),\n c => c.json({ servers: deps.supervisor.views() }),\n )\n\n .post(\n '/',\n describeRoute({\n tags: ['servers'],\n summary: 'Add a server',\n responses: {\n 201: { description: 'Created', content: jsonBody(serverResponse) },\n 400: ERROR_RESPONSES[400],\n },\n }),\n validate('json', serverCreateSchema),\n (c) => {\n const body = c.req.valid('json')\n try {\n return c.json({ server: deps.store.addServer(body) }, 201)\n }\n catch (error) {\n if (error instanceof ConfigError)\n throw new DetailedError(error.message, { statusCode: 400, code: 'INVALID_SERVER' })\n throw error\n }\n },\n )\n\n // Registered before `/:id` so the literal segments always win.\n .post(\n '/start-all',\n describeRoute({ tags: ['servers'], summary: 'Start every enabled server', responses: { 200: { description: 'The servers', content: jsonBody(serversResponse) } } }),\n async (c) => {\n await deps.supervisor.startAll()\n return c.json({ servers: deps.supervisor.views() })\n },\n )\n\n .post(\n '/stop-all',\n describeRoute({ tags: ['servers'], summary: 'Stop every server', responses: { 200: { description: 'The servers', content: jsonBody(serversResponse) } } }),\n async (c) => {\n await deps.supervisor.stopAll()\n return c.json({ servers: deps.supervisor.views() })\n },\n )\n\n .get(\n '/:id',\n describeRoute({ tags: ['servers'], summary: 'One server', responses: { 200: { description: 'The server', content: jsonBody(serverResponse) }, 404: ERROR_RESPONSES[404] } }),\n validate('param', idParam),\n (c) => {\n const { id } = c.req.valid('param')\n const server = deps.supervisor.views().find(entry => entry.id === id)\n if (!server)\n throw unknownServer(id)\n return c.json({ server })\n },\n )\n\n .get(\n '/:id/logs',\n describeRoute({ tags: ['servers'], summary: 'Buffered log lines from memory', responses: { 200: { description: 'Lines' }, 404: ERROR_RESPONSES[404] } }),\n validate('param', idParam),\n validate('query', logQuerySchema),\n (c) => {\n const { id } = c.req.valid('param')\n if (!deps.store.getServer(id))\n throw unknownServer(id)\n\n const { limit } = c.req.valid('query')\n const parsed = limit === undefined ? Number.NaN : Number.parseInt(limit, 10)\n // Clamped: a negative or huge value must not slice from the wrong end.\n const bounded = Number.isNaN(parsed) ? undefined : Math.min(Math.max(parsed, 1), 100_000)\n return c.json({ lines: deps.supervisor.logLines(id, bounded) })\n },\n )\n\n .get(\n '/:id/stream',\n describeRoute({ tags: ['servers'], summary: 'Server state and logs as server-sent events', responses: { 200: { description: 'text/event-stream' }, 404: ERROR_RESPONSES[404] } }),\n validate('param', idParam),\n (c) => {\n const { id } = c.req.valid('param')\n if (!deps.store.getServer(id))\n throw unknownServer(id)\n\n return streamSSE(c, async (stream) => {\n let closed = false\n let pending = 0\n let queue: Promise<void> = Promise.resolve()\n const send = (data: string, event: string): void => {\n if (closed)\n return\n // Same rule as the panel-wide stream: a slow client loses the chatty\n // server's log frames before it grows an unbounded write queue.\n if (event === 'log' && pending > MAX_PENDING_WRITES)\n return\n pending += 1\n queue = queue.then(() => stream.writeSSE({ event, data })).catch(() => {\n closed = true\n }).finally(() => {\n pending -= 1\n })\n }\n\n const unsubscribe = deps.hub.subscribe(id, (message) => {\n send(JSON.stringify(message), message.type)\n })\n stream.onAbort(() => {\n closed = true\n unsubscribe()\n })\n\n const server = deps.supervisor.views().find(entry => entry.id === id)\n send(JSON.stringify({ type: 'server', ts: Date.now(), serverId: id, server }), 'server')\n send(JSON.stringify({\n type: 'log',\n ts: Date.now(),\n serverId: id,\n lines: deps.supervisor.logLines(id, 200),\n }), 'log')\n\n while (true) {\n await stream.sleep(15000)\n if (closed)\n break\n await stream.writeSSE({ event: 'ping', data: String(Date.now()) })\n }\n })\n },\n )\n\n .post(\n '/:id/start',\n describeRoute({ tags: ['servers'], summary: 'Start a server', responses: { 200: { description: 'Result' }, 404: ERROR_RESPONSES[404] } }),\n validate('param', idParam),\n async (c) => {\n const result = await deps.supervisor.start(c.req.valid('param').id)\n return c.json(result, statusFor(result))\n },\n )\n\n .post(\n '/:id/stop',\n describeRoute({ tags: ['servers'], summary: 'Stop a server', responses: { 200: { description: 'Result' }, 404: ERROR_RESPONSES[404] } }),\n validate('param', idParam),\n async (c) => {\n const result = await deps.supervisor.stop(c.req.valid('param').id)\n return c.json(result, result.ok ? 200 : 404)\n },\n )\n\n .post(\n '/:id/restart',\n describeRoute({ tags: ['servers'], summary: 'Restart a server', responses: { 200: { description: 'Result' }, 404: ERROR_RESPONSES[404] } }),\n validate('param', idParam),\n async (c) => {\n const result = await deps.supervisor.restart(c.req.valid('param').id)\n return c.json(result, statusFor(result))\n },\n )\n\n .post(\n '/:id/clear-logs',\n describeRoute({ tags: ['servers'], summary: 'Forget the buffered log lines', responses: { 200: { description: 'Cleared', content: jsonBody(okResponse) } } }),\n validate('param', idParam),\n (c) => {\n deps.supervisor.clearLogs(c.req.valid('param').id)\n return c.json({ ok: true })\n },\n )\n\n /**\n * The escape hatch for `port x is already in use (pid x)`: it re-lists the\n * listeners itself, so what is killed is the process holding the port now —\n * never a pid quoted in an old message, and never one this panel supervises.\n */\n .post(\n '/:id/free-port',\n describeRoute({\n tags: ['servers'],\n summary: 'Ask whatever holds this server\\'s port to stop',\n responses: {\n 200: { description: 'What was signalled', content: jsonBody(freePortResultSchema) },\n 404: ERROR_RESPONSES[404],\n 409: { description: 'Nothing to free, or the holder is supervised by this panel' },\n },\n }),\n validate('param', idParam),\n async (c) => {\n const { id } = c.req.valid('param')\n const result = await deps.supervisor.freePort(id)\n if (!result.ok)\n throw new DetailedError(result.error ?? `could not free the port for \"${id}\"`, { statusCode: statusFor(result), code: 'FREE_PORT_FAILED' })\n return c.json(result)\n },\n )\n\n .patch(\n '/:id',\n describeRoute({ tags: ['servers'], summary: 'Edit a server', responses: { 200: { description: 'The server', content: jsonBody(serverResponse) }, 400: ERROR_RESPONSES[400], 404: ERROR_RESPONSES[404] } }),\n validate('param', idParam),\n validate('json', serverPatchSchema),\n (c) => {\n try {\n return c.json({ server: deps.store.updateServer(c.req.valid('param').id, c.req.valid('json')) })\n }\n catch (error) {\n if (error instanceof ConfigError) {\n const status = error.message.startsWith('unknown server') ? 404 : 400\n throw new DetailedError(error.message, { statusCode: status, code: status === 404 ? 'UNKNOWN_SERVER' : 'INVALID_SERVER' })\n }\n throw error\n }\n },\n )\n\n .delete(\n '/:id',\n describeRoute({ tags: ['servers'], summary: 'Stop and remove a server', responses: { 200: { description: 'Removed', content: jsonBody(okResponse) }, 404: ERROR_RESPONSES[404] } }),\n validate('param', idParam),\n async (c) => {\n const { id } = c.req.valid('param')\n try {\n await deps.supervisor.stop(id)\n deps.store.removeServer(id)\n return c.json({ ok: true })\n }\n catch (error) {\n if (error instanceof ConfigError)\n throw unknownServer(id)\n throw error\n }\n },\n )\n}\n","import type { AppDeps } from '#src/app'\nimport type { SettingsPatch } from '#src/shared/contracts'\nimport { Buffer } from 'node:buffer'\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport { DetailedError } from '@namesmt/utils'\nimport { describeRoute } from 'hono-openapi'\nimport { ConfigError } from '#src/config/store'\nimport { displayHost } from '#src/helpers/bind'\nimport { afterResponse } from '#src/helpers/deferred'\nimport { appFactory } from '#src/helpers/factory'\nimport { logger } from '#src/helpers/logger'\nimport { ERROR_RESPONSES, jsonBody } from '#src/helpers/openapi'\nimport { validate } from '#src/helpers/validator'\nimport { checkExposure } from '#src/services/exposure'\nimport { buildBackupsView, buildControlView } from '#src/services/state'\nimport { settingsPatchSchema, settingsSavedSchema, settingsViewSchema } from '#src/shared/contracts'\n\n/** Uploads are buffered in memory by `parseBody`, so they get a hard ceiling. */\nconst MAX_UI_UPLOAD_BYTES = 128 * 1024 * 1024\n\n/** The archive's name is only a label, so it never reaches the filesystem. */\nfunction sanitizeName(name: string): string {\n const cleaned = name.replace(/\\.zip$/i, '').replace(/[^\\w.-]+/g, '-').replace(/^-+|-+$/g, '')\n return cleaned.length > 0 ? cleaned.slice(0, 60) : 'custom-ui'\n}\n\nexport function createSettingsRoute(deps: AppDeps) {\n return appFactory.createApp()\n .get(\n '/settings',\n describeRoute({\n tags: ['panel'],\n summary: 'The panel, server defaults, logs, notifications, host and backups',\n responses: { 200: { description: 'The settings', content: jsonBody(settingsViewSchema) } },\n }),\n c => c.json({\n control: buildControlView(deps.store, deps.auth, deps.controlServer.endpoint, deps.tls),\n defaults: deps.store.defaults,\n logs: deps.store.config.logs,\n notifications: { telegram: deps.notifications.status() },\n host: deps.store.config.host,\n backups: buildBackupsView(deps.store, deps.backups),\n ui: deps.ui.status(),\n }),\n )\n\n .patch('/settings', describeRoute({\n tags: ['panel'],\n summary: 'Edit the panel, server defaults, logs, notifications, host and backups',\n responses: { 200: { description: 'Saved; the listener may be moving', content: jsonBody(settingsSavedSchema) }, 400: ERROR_RESPONSES[400] },\n }), validate('json', settingsPatchSchema), async (c) => {\n const patch: SettingsPatch = c.req.valid('json')\n const current = deps.store.config.control\n\n // Refuse an exposure that is not backed by a password before writing anything.\n const exposure = checkExposure(\n {\n ...current,\n host: patch.control?.host ?? current.host,\n auth: { ...current.auth, enabled: patch.control?.auth?.enabled ?? current.auth.enabled },\n },\n deps.auth.passwordSet,\n deps.auth.usingDefaultPassword,\n )\n if (exposure.blockedReason !== null)\n throw new DetailedError(exposure.blockedReason, { statusCode: 400, code: 'EXPOSURE_BLOCKED' })\n\n const previous = { trustProxy: current.auth.trustProxy, tlsEnabled: current.tls.enabled }\n try {\n if (patch.defaults !== undefined)\n deps.store.updateDefaults(patch.defaults)\n if (patch.logs !== undefined)\n deps.store.updateLogs(patch.logs)\n if (patch.notifications !== undefined)\n deps.store.updateNotifications(patch.notifications)\n if (patch.host !== undefined)\n deps.store.updateHost(patch.host)\n if (patch.backups !== undefined)\n deps.store.updateBackups(patch.backups)\n if (patch.control !== undefined)\n deps.store.updateControl(patch.control)\n }\n catch (error) {\n if (error instanceof ConfigError)\n throw new DetailedError(error.message, { statusCode: 400, code: 'INVALID_SETTINGS' })\n throw error\n }\n\n const next = deps.store.config.control\n const endpointChanged = next.host !== deps.controlServer.endpoint.host || next.port !== deps.controlServer.endpoint.port\n const proxyChanged = next.auth.trustProxy !== previous.trustProxy\n const tlsChanged = next.tls.enabled !== previous.tlsEnabled\n let targetUrl: string | null = null\n\n if (endpointChanged || proxyChanged || tlsChanged) {\n // Moving the listener kills the connection serving this very response, so\n // it happens after the response is written. A failure reverts the config\n // and shows up as `restartRequired` in the next state frame.\n const nextProtocol = tlsChanged ? (next.tls.enabled ? 'https' : 'http') : deps.controlServer.endpoint.protocol\n targetUrl = `${nextProtocol}://${displayHost(next.host)}:${next.port}`\n\n afterResponse(async () => {\n const result = endpointChanged\n ? await deps.controlServer.rebind({ host: next.host, port: next.port })\n : await deps.controlServer.restart()\n\n if (result.ok) {\n logger.info(`control panel listening on ${deps.controlServer.endpoint.url}`)\n return\n }\n\n logger.error(`could not move the control panel: ${result.error ?? 'unknown error'}`)\n deps.store.updateControl({\n host: deps.controlServer.endpoint.host,\n port: deps.controlServer.endpoint.port,\n ...(proxyChanged ? { auth: { trustProxy: previous.trustProxy } } : {}),\n ...(tlsChanged ? { tls: { enabled: previous.tlsEnabled } } : {}),\n })\n }, error => logger.error('control panel move failed', error))\n }\n\n return c.json({\n // `control` describes the listener that is live *right now*; `targetUrl`\n // is where it is about to be, which is what the client should open.\n control: buildControlView(deps.store, deps.auth, deps.controlServer.endpoint, deps.tls),\n defaults: deps.store.defaults,\n logs: deps.store.config.logs,\n notifications: { telegram: deps.notifications.status() },\n host: deps.store.config.host,\n backups: buildBackupsView(deps.store, deps.backups),\n ui: deps.ui.status(),\n rebinding: endpointChanged || proxyChanged || tlsChanged,\n targetUrl,\n })\n })\n\n /**\n * Replace the panel's UI with an uploaded static build. The archive is validated\n * and staged before it is swapped in, so a bad upload changes nothing.\n */\n .post('/settings/ui', describeRoute({\n tags: ['panel'],\n summary: 'Replace the panel UI with an uploaded static build',\n responses: { 200: { description: 'Installed' }, 400: ERROR_RESPONSES[400], 413: { description: 'Too large' } },\n }), async (c) => {\n const declared = Number.parseInt(c.req.header('content-length') ?? '0', 10)\n if (Number.isFinite(declared) && declared > MAX_UI_UPLOAD_BYTES)\n throw new DetailedError(`the upload is larger than ${Math.round(MAX_UI_UPLOAD_BYTES / 1024 / 1024)}MB`, { statusCode: 413, code: 'UPLOAD_TOO_LARGE' })\n\n const body = await c.req.parseBody()\n const file = body.file\n if (!(file instanceof File))\n throw new DetailedError('expected a `file` field with the UI archive', { statusCode: 400, code: 'MISSING_FILE' })\n if (file.size > MAX_UI_UPLOAD_BYTES)\n throw new DetailedError(`the upload is larger than ${Math.round(MAX_UI_UPLOAD_BYTES / 1024 / 1024)}MB`, { statusCode: 413, code: 'UPLOAD_TOO_LARGE' })\n\n const staging = path.join(path.dirname(deps.ui.directory), `.ui-upload-${Date.now()}.zip`)\n try {\n await fs.promises.writeFile(staging, Buffer.from(await file.arrayBuffer()))\n const result = await deps.ui.install(staging, sanitizeName(file.name))\n if (!result.ok)\n throw new DetailedError(result.error, { statusCode: 400, code: 'INVALID_UI' })\n\n logger.info(`UI replaced with ${result.meta.name} (${result.meta.files} files)`)\n return c.json({ ok: true, meta: result.meta, ui: deps.ui.status() })\n }\n finally {\n fs.rmSync(staging, { force: true })\n }\n })\n\n /** Back to the stock UI. */\n .delete('/settings/ui', describeRoute({\n tags: ['panel'],\n summary: 'Go back to the stock UI',\n responses: { 200: { description: 'Reverted' } },\n }), (c) => {\n const removed = deps.ui.revert()\n logger.info(removed ? 'custom UI removed — the stock panel is back' : 'no custom UI was installed')\n return c.json({ ok: true, removed, ui: deps.ui.status() })\n })\n}\n","import type { AppDeps } from '#src/app'\nimport { describeRoute } from 'hono-openapi'\nimport { appFactory } from '#src/helpers/factory'\nimport { jsonBody } from '#src/helpers/openapi'\nimport { appStateSchema } from '#src/shared/contracts'\n\n/** The whole panel in one payload: config, live server state and host vitals. */\nexport function createStateRoute(deps: AppDeps) {\n return appFactory.createApp()\n .get(\n '/state',\n describeRoute({\n tags: ['panel'],\n summary: 'Full snapshot of the panel',\n responses: { 200: { description: 'The snapshot', content: jsonBody(appStateSchema) } },\n }),\n c => c.json(deps.supervisor.getState()),\n )\n}\n","import type { Context } from 'hono'\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport { Hono } from 'hono'\n\nconst CONTENT_TYPES: Record<string, string> = {\n '.html': 'text/html; charset=utf-8',\n '.js': 'text/javascript; charset=utf-8',\n '.mjs': 'text/javascript; charset=utf-8',\n '.css': 'text/css; charset=utf-8',\n '.json': 'application/json; charset=utf-8',\n '.map': 'application/json; charset=utf-8',\n '.svg': 'image/svg+xml',\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.gif': 'image/gif',\n '.webp': 'image/webp',\n '.ico': 'image/x-icon',\n '.woff': 'font/woff',\n '.woff2': 'font/woff2',\n '.ttf': 'font/ttf',\n '.txt': 'text/plain; charset=utf-8',\n}\n\nexport interface StaticRouteOptions {\n /** Resolved per request, so a UI installed at runtime takes effect on refresh. */\n dir: string | (() => string)\n entry?: string\n}\n\n/**\n * Serves the built control UI and falls back to `index.html` for client routes,\n * so a deep URL like `/servers/static` works after a refresh.\n */\nexport function createStaticRoute(options: StaticRouteOptions): Hono {\n const route = new Hono()\n const entry = options.entry ?? 'index.html'\n const currentRoot = (): string => path.resolve(typeof options.dir === 'function' ? options.dir() : options.dir)\n\n route.get('*', async (c) => {\n const root = currentRoot()\n const pathname = safeDecode(new URL(c.req.url).pathname)\n if (pathname === null)\n return c.text('bad path', 400)\n\n const file = resolveWithin(root, pathname)\n if (file !== null) {\n const response = await serveFile(c, file, pathname)\n if (response !== null)\n return response\n }\n\n const indexFile = path.join(root, entry)\n if (fs.existsSync(indexFile)) {\n const response = await serveFile(c, indexFile, '/')\n if (response !== null)\n return response\n }\n\n return c.text('no UI is installed — build one and upload it under Settings → Interface', 503)\n })\n\n return route\n}\n\nfunction safeDecode(value: string): string | null {\n try {\n return decodeURIComponent(value)\n }\n catch {\n return null\n }\n}\n\nfunction resolveWithin(root: string, pathname: string): string | null {\n const resolved = path.resolve(root, `.${pathname}`)\n if (resolved !== root && !resolved.startsWith(`${root}${path.sep}`))\n return null\n return resolved\n}\n\nasync function serveFile(c: Context, file: string, pathname: string): Promise<Response | null> {\n let stats: fs.Stats\n try {\n stats = await fs.promises.stat(file)\n }\n catch {\n return null\n }\n if (!stats.isFile())\n return null\n\n const body = await fs.promises.readFile(file)\n const ext = path.extname(file).toLowerCase()\n const immutable = pathname.startsWith('/assets/')\n const payload = body.buffer.slice(body.byteOffset, body.byteOffset + body.byteLength) as ArrayBuffer\n\n return c.body(payload, 200, {\n 'Content-Type': CONTENT_TYPES[ext] ?? 'application/octet-stream',\n 'Content-Length': String(stats.size),\n 'Cache-Control': immutable ? 'public, max-age=31536000, immutable' : 'no-cache',\n })\n}\n","import type { AppDeps } from '#src/app'\nimport { DetailedError } from '@namesmt/utils'\nimport { describeRoute } from 'hono-openapi'\nimport { afterResponse } from '#src/helpers/deferred'\nimport { appFactory } from '#src/helpers/factory'\nimport { logger } from '#src/helpers/logger'\nimport { ERROR_RESPONSES } from '#src/helpers/openapi'\nimport { validate } from '#src/helpers/validator'\nimport { buildControlView } from '#src/services/state'\nimport { tlsUploadSchema } from '#src/shared/contracts'\n\n/**\n * Uploads the PEM pair used for https on the control panel.\n *\n * When TLS is already enabled the listener has to be rebuilt with the new pair,\n * which kills the connection serving this request — so the swap is deferred, and\n * the response tells the client where the panel will be.\n */\nexport function createTlsRoute(deps: AppDeps) {\n const review = () => ({\n control: buildControlView(deps.store, deps.auth, deps.controlServer.endpoint, deps.tls),\n rebinding: deps.store.config.control.tls.enabled,\n targetUrl: deps.controlServer.endpoint.url,\n })\n\n return appFactory.createApp()\n .post(\n '/settings/tls',\n describeRoute({\n tags: ['tls'],\n summary: 'Upload the certificate and key the panel should serve',\n responses: { 200: { description: 'Stored; the panel may be moving to https' }, 400: ERROR_RESPONSES[400] },\n }),\n validate('json', tlsUploadSchema),\n (c) => {\n const body = c.req.valid('json')\n const saved = deps.tls.save(body.certificate, body.privateKey)\n if (!saved.ok)\n throw new DetailedError(saved.error ?? 'the certificate pair was rejected', { statusCode: 400, code: 'INVALID_CERTIFICATE' })\n\n if (deps.store.config.control.tls.enabled) {\n afterResponse(async () => {\n const result = await deps.controlServer.restart()\n if (!result.ok)\n logger.error(`could not reload TLS: ${result.error ?? 'unknown error'}`)\n else logger.info(`control panel listening on ${deps.controlServer.endpoint.url} (https)`)\n }, error => logger.error('tls reload failed', error))\n }\n\n return c.json(review())\n },\n )\n\n .delete(\n '/settings/tls',\n describeRoute({\n tags: ['tls'],\n summary: 'Remove the certificate pair',\n responses: { 200: { description: 'Removed' } },\n }),\n (c) => {\n deps.tls.clear()\n if (deps.store.config.control.tls.enabled) {\n afterResponse(async () => {\n await deps.controlServer.restart()\n }, error => logger.error('tls reload failed', error))\n }\n return c.json(review())\n },\n )\n}\n","import type { DetailedError } from '@namesmt/utils'\nimport type { ErrorHandler as HonoErrorHandler } from 'hono'\nimport type { ContentfulStatusCode } from 'hono/utils/http-status'\nimport { HTTPException } from 'hono/http-exception'\nimport { logger } from '#src/helpers/logger'\n\n/**\n * The one error envelope this API speaks:\n *\n * ```json\n * { \"message\": \"human readable\", \"code\": \"MACHINE_READABLE\", \"detail\": … }\n * ```\n *\n * `@namesmt/utils`' `DetailedError` is the preferred way to fail — it carries the\n * status, a stable code and structured detail — so a client (and the OpenAPI\n * schema) can rely on the shape.\n */\nexport interface ApiErrorBody {\n message: string\n code: string\n detail?: unknown\n}\n\nexport const errorHandler: HonoErrorHandler = (error, c) => {\n const body = toErrorBody(error)\n const status = statusOf(error)\n\n if (status >= 500)\n logger.error(`${c.req.method} ${new URL(c.req.url).pathname} failed:`, error)\n else\n logger.debug(`${c.req.method} ${new URL(c.req.url).pathname} → ${status} ${body.message}`)\n\n return c.json(body, status)\n}\n\nfunction toErrorBody(error: unknown): ApiErrorBody {\n if (error instanceof HTTPException)\n return { message: error.message, code: 'HTTP_EXCEPTION' }\n\n // `DetailedError` can come from this code or from Hono's own parsing helpers,\n // so a name check is safer than `instanceof` across module instances.\n if (isDetailedError(error)) {\n return {\n message: error.message,\n code: error.code ?? 'DETAILED_ERROR',\n ...(error.detail === undefined ? {} : { detail: error.detail }),\n }\n }\n\n if (error instanceof Error)\n return { message: error.message, code: error.name === 'Error' ? 'INTERNAL_ERROR' : error.name.toUpperCase() }\n\n return { message: String(error), code: 'INTERNAL_ERROR' }\n}\n\nfunction isDetailedError(error: unknown): error is DetailedError {\n return error instanceof Error && error.name === 'DetailedError' && 'statusCode' in error\n}\n\nfunction statusOf(error: unknown): ContentfulStatusCode {\n const candidate = (error as { statusCode?: unknown, status?: unknown })?.statusCode ?? (error as { status?: unknown })?.status\n const status = typeof candidate === 'number' ? candidate : 500\n return status >= 400 && status <= 599 ? (status as ContentfulStatusCode) : 500\n}\n","import fs from 'node:fs'\nimport { Scalar } from '@scalar/hono-api-reference'\nimport { openAPIRouteHandler } from 'hono-openapi'\nimport { appFactory } from '#src/helpers/factory'\n\nconst PREFIX = '/openapi'\n\n/**\n * The machine-readable contract, generated from the same ArkType schemas the\n * routes validate with — one source of truth, no second set of DTOs to drift.\n * `/openapi/ui` is a browsable reference (Scalar) and needs no session, since a\n * UI author has to be able to read it before they can log in.\n */\n/** The version the package was built with, so the spec never drifts from it. */\nfunction packageVersion(): string {\n try {\n const manifest = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf8')) as { version?: string }\n return manifest.version ?? '0.0.0'\n }\n catch {\n return '0.0.0'\n }\n}\n\nexport function setupOpenAPI(app: Parameters<typeof openAPIRouteHandler>[0]) {\n return appFactory.createApp()\n .get(\n `${PREFIX}/spec.json`,\n openAPIRouteHandler(app, {\n documentation: {\n info: {\n title: 'home-hosted',\n version: packageVersion(),\n description: 'Control plane for the processes you host at home: servers, logs, vitals, backups and settings.',\n },\n tags: [\n { name: 'panel', description: 'Snapshot, health and the local shutdown channel' },\n { name: 'servers', description: 'The processes being supervised' },\n { name: 'logs', description: 'Live and persisted logs' },\n { name: 'backups', description: 'Archives of config, secrets, TLS and data paths' },\n { name: 'auth', description: 'Sessions and the panel password' },\n { name: 'notifications', description: 'Telegram delivery' },\n { name: 'tls', description: 'The panel certificate' },\n ],\n },\n }),\n )\n .get(\n `${PREFIX}/ui`,\n Scalar({ theme: 'deepSpace', url: `${PREFIX}/spec.json` }),\n )\n}\n","import type { SecretsStore } from '#src/config/secrets'\nimport type { ConfigStore } from '#src/config/store'\nimport type { AuthService } from '#src/services/auth'\nimport type { BackupService } from '#src/services/backups'\nimport type { ControlServer } from '#src/services/control-server'\nimport type { EventHub } from '#src/services/events'\nimport type { LogFiles } from '#src/services/log-files'\nimport type { NotificationService } from '#src/services/notifications'\nimport type { Supervisor } from '#src/services/supervisor'\nimport type { TlsStore } from '#src/services/tls'\nimport type { UiService } from '#src/services/ui'\nimport { createAuthRoute } from '#src/api/auth/$.routes'\nimport { createBackupsRoute } from '#src/api/backups'\nimport { createControlRoute } from '#src/api/control'\nimport { createEventsRoute } from '#src/api/events'\nimport { createHealthRoute } from '#src/api/health'\nimport { createLogsRoute } from '#src/api/logs'\nimport { createMetricsRoute } from '#src/api/metrics'\nimport { createNotificationsRoute } from '#src/api/notifications'\nimport { createServersRoute } from '#src/api/servers/$.routes'\nimport { createSettingsRoute } from '#src/api/settings'\nimport { createStateRoute } from '#src/api/state'\nimport { createStaticRoute } from '#src/api/static'\nimport { createTlsRoute } from '#src/api/tls'\nimport { errorHandler } from '#src/helpers/error'\nimport { appFactory } from '#src/helpers/factory'\nimport { logger } from '#src/helpers/logger'\nimport { createAuthGuard } from '#src/middleware/auth'\nimport { setupOpenAPI } from '#src/openapi'\n\nexport interface AppDeps {\n store: ConfigStore\n supervisor: Supervisor\n hub: EventHub\n auth: AuthService\n secrets: SecretsStore\n controlServer: ControlServer\n tls: TlsStore\n logFiles: LogFiles\n notifications: NotificationService\n backups: BackupService\n ui: UiService\n /** Token for the local `down` command, and the graceful stop it asks for. */\n runtimeToken: string\n onShutdown: () => Promise<void>\n}\n\n/**\n * The root app: middleware and sub-routes only, never a handler of its own.\n *\n * The whole thing is one chained expression on purpose — that is what keeps the\n * route map in the type, which `AppType` hands to `hc<AppType>` clients and to\n * `setupOpenAPI`.\n */\nexport function createRootApp(deps: AppDeps) {\n const app = appFactory.createApp()\n .use('*', async (c, next) => {\n const started = Date.now()\n await next()\n logger.debug(`${c.req.method} ${new URL(c.req.url).pathname} ${c.res.status} ${Date.now() - started}ms`)\n })\n\n .onError(errorHandler)\n\n // Outside `/api`, so a local `down` needs no session — but it needs the token\n // from `run.json` and a loopback peer. Registered before the guard by design.\n .route('/_hh', createControlRoute(deps))\n\n .use('/api/*', createAuthGuard({ auth: deps.auth }))\n\n .route('/api', createAuthRoute(deps))\n .route('/api', createStateRoute(deps))\n .route('/api', createEventsRoute(deps))\n .route('/api', createSettingsRoute(deps))\n .route('/api', createTlsRoute(deps))\n .route('/api', createLogsRoute(deps))\n .route('/api', createNotificationsRoute(deps))\n .route('/api', createMetricsRoute(deps))\n .route('/api', createBackupsRoute(deps))\n .route('/api/servers', createServersRoute(deps))\n\n .route('/', createHealthRoute(deps))\n\n // The spec is generated from the finished route table (and must be routed\n // *before* the static catch-all, which would otherwise swallow it).\n const documented = app.route('/', setupOpenAPI(app))\n return documented.route('/', createStaticRoute({ dir: () => deps.ui.resolveDir() }))\n}\n\n/** What a typed client (`hc<AppType>`) and the OpenAPI document are built from. */\nexport type AppType = ReturnType<typeof createRootApp>\n","import { randomBytes } from 'node:crypto'\nimport fs from 'node:fs'\nimport http from 'node:http'\nimport https from 'node:https'\nimport process from 'node:process'\nimport { type } from 'arktype'\nimport { writeFileAtomic } from '#src/helpers/atomic'\nimport { runtimePath } from '#src/helpers/paths'\n\n/**\n * `run.json` is how `status`/`down` find the live control plane and how they are\n * allowed to stop it without a password: the file is mode 0600, and the token in\n * it is what `POST /_hh/shutdown` checks.\n */\nexport const runtimeSchema = type({\n version: 'string',\n pid: 'number.integer >= 1',\n /** The address that was actually bound, for humans. */\n url: 'string',\n /** Always a reachable loopback address, for probes (`lan` binds to 0.0.0.0). */\n probeUrl: 'string',\n protocol: 'string',\n port: '1 <= number.integer <= 65535',\n bindHost: 'string',\n startedAt: 'number',\n projectDir: 'string',\n dataRoot: 'string',\n configPath: 'string',\n logFile: 'string',\n token: 'string >= 1',\n}).onUndeclaredKey('reject')\nexport type Runtime = typeof runtimeSchema.infer\n\nexport function readRuntime(): Runtime | null {\n try {\n const parsed = runtimeSchema(JSON.parse(fs.readFileSync(runtimePath, 'utf8')))\n return parsed instanceof type.errors ? null : parsed\n }\n catch {\n return null\n }\n}\n\nexport function writeRuntime(runtime: Runtime): void {\n writeFileAtomic(runtimePath, `${JSON.stringify(runtime, null, 2)}\\n`, { mode: 0o600 })\n}\n\nexport function clearRuntime(): void {\n fs.rmSync(runtimePath, { force: true })\n}\n\nexport function newToken(): string {\n return randomBytes(32).toString('base64url')\n}\n\n/** Signal 0 only probes the pid; `EPERM` still means the process is there. */\nexport function isProcessAlive(pid: number): boolean {\n try {\n process.kill(pid, 0)\n return true\n }\n catch (error) {\n return (error as NodeJS.ErrnoException).code === 'EPERM'\n }\n}\n\nexport interface RuntimeProbe {\n /** The panel answered on its own port — stronger than \"the pid exists\". */\n reachable: boolean\n /** It answered, but reports a crashed autostart server (`/healthz` is 503). */\n degraded: boolean\n}\n\n/** A degraded panel is still answering: a 503 must not read as \"not running\". */\nexport async function probeRuntime(runtime: Runtime, timeoutMs = 2500): Promise<RuntimeProbe> {\n const status = await localRequest(runtime, '/healthz', 'GET', undefined, timeoutMs)\n return { reachable: status !== null, degraded: status === 503 }\n}\n\n/**\n * Asks the daemon to stop through its own endpoint, so supervised servers are\n * shut down cleanly on every platform (a bare signal is not graceful on Windows).\n */\nexport async function requestShutdown(runtime: Runtime, timeoutMs = 4000): Promise<boolean> {\n const status = await localRequest(runtime, '/_hh/shutdown', 'POST', runtime.token, timeoutMs)\n return status !== null && status >= 200 && status < 300\n}\n\n/**\n * Talks to the panel over loopback. Node's `fetch` cannot be told to accept the\n * self-signed certificate an uploaded TLS pair usually is, which would break\n * `status` and the graceful `down` — so this speaks http/https directly.\n */\nfunction localRequest(runtime: Runtime, path: string, method: 'GET' | 'POST', token: string | undefined, timeoutMs: number): Promise<number | null> {\n return new Promise((resolve) => {\n const url = new URL(`${runtime.probeUrl}${path}`)\n const secure = url.protocol === 'https:'\n const request = (secure ? https : http).request({\n hostname: url.hostname,\n port: url.port,\n path: url.pathname,\n method,\n // Only ever pointed at our own listener on this machine.\n ...(secure ? { rejectUnauthorized: false } : {}),\n headers: token === undefined ? {} : { 'x-home-hosted-token': token },\n timeout: timeoutMs,\n }, (response) => {\n response.resume()\n response.once('end', () => resolve(response.statusCode ?? null))\n })\n\n request.once('error', () => resolve(null))\n request.once('timeout', () => {\n request.destroy()\n resolve(null)\n })\n request.end()\n })\n}\n","import { exec } from 'node:child_process'\nimport process from 'node:process'\n\n/** Best-effort browser launch; a headless host simply logs instead. */\nexport function openBrowser(url: string): void {\n const command = process.platform === 'darwin'\n ? `open \"${url}\"`\n : process.platform === 'win32'\n ? `start \"\" \"${url}\"`\n : `xdg-open \"${url}\"`\n\n exec(command, { windowsHide: true }, () => {\n // No display / no handler: the URL is already printed, so this is not an error.\n })\n}\n","export type TemplateVars = Record<string, string | number>\n\n/**\n * Replaces `{name}` placeholders. Unknown placeholders are left untouched so a\n * typo surfaces in the child's args instead of silently becoming an empty string.\n */\nexport function resolveTemplate(value: string, vars: TemplateVars): string {\n return value.replace(/(?<!\\$)\\{([a-z][\\w-]*)\\}/gi, (match, name: string) => {\n const replacement = vars[name]\n return replacement === undefined ? match : String(replacement)\n })\n}\n\nexport function resolveTemplates<T extends string | string[]>(value: T, vars: TemplateVars): T {\n if (Array.isArray(value))\n return value.map(entry => resolveTemplate(entry, vars)) as T\n return resolveTemplate(value as string, vars) as T\n}\n\nexport function resolveRecord(record: Record<string, string>, vars: TemplateVars): Record<string, string> {\n return Object.fromEntries(\n Object.entries(record).map(([key, value]) => [key, resolveTemplate(value, vars)]),\n )\n}\n","import { execFile } from 'node:child_process'\nimport net from 'node:net'\nimport process from 'node:process'\nimport { promisify } from 'node:util'\n\nconst execFileAsync = promisify(execFile)\n\n/** True when something accepts TCP connections on host:port. */\nexport function probePort(host: string, port: number, timeoutMs = 1500): Promise<boolean> {\n return new Promise((resolve) => {\n const socket = net.connect({ host, port })\n const done = (result: boolean): void => {\n socket.removeAllListeners()\n socket.destroy()\n resolve(result)\n }\n socket.setTimeout(timeoutMs)\n socket.once('connect', () => done(true))\n socket.once('timeout', () => done(false))\n socket.once('error', () => done(false))\n })\n}\n\n/** A port is free when nothing is listening on it (loopback is enough to detect conflicts). */\nexport async function isPortFree(port: number, host = '127.0.0.1', timeoutMs = 1000): Promise<boolean> {\n return !(await probePort(host, port, timeoutMs))\n}\n\n/**\n * Kills whatever holds the port, minus `exclude` (the panel's own process tree).\n * Only used as a last resort for wrappers that spawn their real server detached,\n * where a process-group signal cannot reach it.\n */\nexport async function killPortHolders(port: number, exclude?: ReadonlySet<number>): Promise<number[]> {\n const pids = (await listPortHolders(port)).filter(pid => !exclude?.has(pid))\n for (const pid of pids) {\n try {\n process.kill(pid, 'SIGKILL')\n }\n catch {\n // already gone\n }\n }\n return pids\n}\n\n/** Signal 0 asks the OS whether the pid still exists, without touching it. */\nexport function isProcessAlive(pid: number): boolean {\n try {\n process.kill(pid, 0)\n return true\n }\n catch {\n return false\n }\n}\n\nfunction signalPid(pid: number, name: NodeJS.Signals): void {\n try {\n process.kill(pid, name)\n }\n catch {\n // already gone, or not ours to signal\n }\n}\n\nfunction delay(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms))\n}\n\n/**\n * Asks specific pids to leave, politely first: a stray dev server stops on\n * SIGTERM, and only what ignores it is killed. Deciding *which* pids may be\n * touched belongs to the caller — this only does the signalling.\n */\nexport async function terminatePids(\n pids: number[],\n options: { graceMs?: number } = {},\n): Promise<{ stopped: number[], forced: number[] }> {\n const graceMs = options.graceMs ?? 3000\n const targets = [...new Set(pids)]\n // Only what was actually there is claimed as stopped; a pid that had already\n // exited is neither ours to report nor ours to kill.\n const aliveBefore = targets.filter(isProcessAlive)\n\n for (const pid of aliveBefore) signalPid(pid, 'SIGTERM')\n\n const deadline = Date.now() + graceMs\n let alive = aliveBefore.filter(isProcessAlive)\n while (alive.length > 0 && Date.now() < deadline) {\n await delay(100)\n alive = alive.filter(isProcessAlive)\n }\n\n const stopped = aliveBefore.filter(pid => !alive.includes(pid))\n for (const pid of alive) signalPid(pid, 'SIGKILL')\n if (alive.length > 0 && graceMs > 0)\n await delay(150)\n\n return { stopped, forced: alive }\n}\n\n/** `netstat -ano` lines: ` TCP 127.0.0.1:4010 0.0.0.0:0 LISTENING 1234` */\nexport function parseNetstatListeners(output: string, port: number): number[] {\n const pids = new Set<number>()\n\n for (const line of output.split(/\\r?\\n/)) {\n const cells = line.trim().split(/\\s+/)\n if (cells.length < 5)\n continue\n const local = cells[1] ?? ''\n const state = cells[3] ?? ''\n const pid = Number.parseInt(cells[4] ?? '', 10)\n const localPort = Number.parseInt(local.slice(local.lastIndexOf(':') + 1), 10)\n if (state.toUpperCase() !== 'LISTENING' || localPort !== port)\n continue\n if (Number.isInteger(pid) && pid > 0 && pid !== process.pid)\n pids.add(pid)\n }\n\n return [...pids]\n}\n\nexport async function listPortHolders(port: number): Promise<number[]> {\n if (process.platform === 'win32') {\n try {\n const { stdout } = await execFileAsync('netstat', ['-ano', '-p', 'tcp'], { timeout: 5000 })\n return parseNetstatListeners(stdout, port)\n }\n catch {\n return []\n }\n }\n\n try {\n const { stdout } = await execFileAsync('lsof', ['-ti', `tcp:${port}`, '-sTCP:LISTEN'], { timeout: 3000 })\n return parsePids(stdout)\n }\n catch {\n // lsof missing or nothing listening\n }\n\n try {\n const { stdout } = await execFileAsync('fuser', [`${port}/tcp`], { timeout: 3000 })\n return parsePids(stdout)\n }\n catch {\n return []\n }\n}\n\nfunction parsePids(stdout: string): number[] {\n return [...new Set(\n stdout.split(/\\s+/)\n .map(entry => Number.parseInt(entry, 10))\n .filter(pid => Number.isInteger(pid) && pid > 0 && pid !== process.pid),\n )]\n}\n","import fs from 'node:fs'\nimport path from 'node:path'\n\n/** `KEY=value` files, with optional `export `, `#` comments and quoted values. */\nexport function parseEnvFile(text: string): Record<string, string> {\n const env: Record<string, string> = {}\n\n for (const raw of text.split('\\n')) {\n const line = raw.trim()\n if (line.length === 0 || line.startsWith('#'))\n continue\n\n const assignment = line.startsWith('export ') ? line.slice(7) : line\n const separator = assignment.indexOf('=')\n if (separator <= 0)\n continue\n\n const key = assignment.slice(0, separator).trim()\n let value = assignment.slice(separator + 1).trim()\n if (value.length > 1 && ((value.startsWith('\"') && value.endsWith('\"')) || (value.startsWith('\\'') && value.endsWith('\\'')))) {\n value = value.slice(1, -1)\n }\n env[key] = value\n }\n\n return env\n}\n\n/** Missing files are not an error: an env file is an optional override layer. */\nexport function loadEnvFile(file: string): { env: Record<string, string>, path: string, error: string | null } {\n try {\n return { env: parseEnvFile(fs.readFileSync(file, 'utf8')), path: file, error: null }\n }\n catch (error) {\n const code = (error as NodeJS.ErrnoException).code\n if (code === 'ENOENT')\n return { env: {}, path: file, error: null }\n return { env: {}, path: file, error: error instanceof Error ? error.message : String(error) }\n }\n}\n\nconst VARIABLE = /\\$\\{([A-Z_]\\w*)\\}/gi\n\n/** Expands `${VAR}` from the given vars; unknown references are left visible. */\nexport function expandEnv(value: string, vars: Record<string, string | undefined>): string {\n return value.replace(VARIABLE, (match, name: string) => vars[name] ?? match)\n}\n\nexport function expandEnvRecord(record: Record<string, string>, vars: Record<string, string | undefined>): Record<string, string> {\n return Object.fromEntries(Object.entries(record).map(([key, value]) => [key, expandEnv(value, vars)]))\n}\n\nexport function expandEnvList(values: string[], vars: Record<string, string | undefined>): string[] {\n return values.map(value => expandEnv(value, vars))\n}\n\nexport function resolveEnvFilePath(file: string, cwd: string): string {\n if (path.isAbsolute(file))\n return file\n return path.resolve(cwd, file)\n}\n","import type { FileEntry } from '@zip.js/zip.js'\nimport { Buffer } from 'node:buffer'\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport { Readable, Writable } from 'node:stream'\nimport { finished } from 'node:stream/promises'\nimport { BlobReader, configure, ZipReader, ZipWriter } from '@zip.js/zip.js'\n\n/**\n * `Readable.toWeb` is typed against `node:stream/web`, while zip.js declares the\n * global `ReadableStream` — the same objects under two declarations, and which\n * one wins depends on the tsconfig (the SPA's adds lib.dom). These aliases take\n * the type zip.js expects, whichever it is where this file is compiled.\n */\ntype ZipInput = Parameters<ZipWriter<unknown>['add']>[1]\ntype ZipOutput = ConstructorParameters<typeof ZipWriter>[0] & { abort: (reason?: unknown) => Promise<void> }\ntype ZipDataOutput = Parameters<FileEntry['getData']>[0]\n\n/**\n * Backups are ordinary zip files: the same container whether or not they are\n * password-protected, openable by any archive manager (including the one built\n * into Windows and macOS), and produced without a native binary — zip.js is pure\n * JavaScript.\n *\n * A password means WinZip AES-256 (`encryptionStrength: 3`, AE-2): strong, and\n * still standard, unlike the legacy ZipCrypto encryption.\n */\n\n// Deterministic, in-process codecs: a bundled CLI has no worker file to load.\nconfigure({ useWebWorkers: false })\n\nconst ZIPS = [\n [0x50, 0x4B, 0x03, 0x04],\n [0x50, 0x4B, 0x05, 0x06],\n [0x50, 0x4B, 0x07, 0x08],\n]\n\n/** Recognised by content, never by the file's name. */\nexport function isZipArchive(file: string): boolean {\n let fd: number | null = null\n try {\n fd = fs.openSync(file, 'r')\n const head = Buffer.alloc(4)\n const read = fs.readSync(fd, head, 0, 4, 0)\n return read === 4 && ZIPS.some(magic => magic.every((byte, index) => head[index] === byte))\n }\n catch {\n return false\n }\n finally {\n if (fd !== null)\n fs.closeSync(fd)\n }\n}\n\nexport interface ArchiveEntry {\n /** Forward-slash path inside the archive; directories end with `/`. */\n name: string\n directory: boolean\n encrypted: boolean\n symlink: boolean\n /** Uncompressed size, for callers that cap what they will extract. */\n size: number\n}\n\n/** The central directory is not encrypted, so this works without a password. */\nexport async function listZip(file: string): Promise<ArchiveEntry[]> {\n const reader = await open(file)\n try {\n const entries = await reader.getEntries()\n return entries.map(entry => ({\n name: entry.filename,\n directory: entry.directory === true,\n encrypted: entry.encrypted === true,\n symlink: entry.symlink === true,\n size: entry.uncompressedSize ?? 0,\n }))\n }\n finally {\n await reader.close()\n }\n}\n\n/** True when the archive rejected the password we used. */\nexport function isInvalidPassword(error: unknown): boolean {\n return error instanceof Error && /password/i.test(error.message)\n}\n\n/**\n * Writes the contents of `sourceDir` into `destination`, preserving the tree.\n * Symlinks are followed, so a link to a directory is captured as a directory and\n * a link loop cannot recurse forever. Streams from disk to disk: nothing is\n * buffered whole.\n */\nexport async function createZip(sourceDir: string, destination: string, options: { password?: string } = {}): Promise<void> {\n const output = fs.createWriteStream(destination, { mode: 0o600 })\n // Attached up front: the fd closes as part of the web stream ending, so a\n // listener added afterwards would wait forever.\n const flushed = finished(output)\n const writer = Writable.toWeb(output) as unknown as ZipOutput\n const zip = new ZipWriter(writer, {\n ...(options.password === undefined ? {} : { password: options.password, encryptionStrength: 3 as const }),\n level: 6,\n keepOrder: true,\n })\n\n try {\n for (const item of walk(sourceDir)) {\n if (item.directory)\n await zip.add(item.name, null, { directory: true })\n else if (item.size === 0)\n // An empty file carries no content to protect, and leaving it as a plain\n // AE-2 entry makes older tools (p7zip 16.02) report a CRC failure on it.\n await zip.add(item.name, null, { directory: false })\n else\n await zip.add(item.name, Readable.toWeb(fs.createReadStream(item.absolute)) as unknown as ZipInput)\n }\n await zip.close()\n await flushed\n }\n catch (error) {\n await writer.abort(error).catch(() => {})\n // The file stream also fails here (a full disk, a directory in the way), and\n // an unobserved rejection would take the whole control plane down.\n await flushed.catch(() => {})\n throw error\n }\n}\n\n/**\n * Extracts the given entries (already validated by the caller) into\n * `destination`. Symbolic links are never recreated — an archive is not allowed\n * to make the filesystem point somewhere else.\n */\nexport async function extractZip(\n file: string,\n destination: string,\n options: { names: string[], password?: string },\n): Promise<{ skipped: string[] }> {\n const reader = await open(file, options.password)\n const skipped: string[] = []\n\n try {\n const entries = new Map((await reader.getEntries()).map(entry => [entry.filename, entry]))\n\n for (const name of options.names) {\n const entry = entries.get(name)\n if (entry === undefined) {\n skipped.push(name)\n continue\n }\n\n const target = path.join(destination, name)\n if (entry.directory) {\n fs.mkdirSync(target, { recursive: true })\n continue\n }\n if (entry.symlink) {\n skipped.push(name)\n continue\n }\n\n fs.mkdirSync(path.dirname(target), { recursive: true })\n await entry.getData(Writable.toWeb(fs.createWriteStream(target)) as unknown as ZipDataOutput, writeOptions(entry, options.password))\n }\n }\n finally {\n await reader.close()\n }\n\n return { skipped }\n}\n\nfunction writeOptions(entry: FileEntry, password: string | undefined): { password?: string } {\n return entry.encrypted && password !== undefined ? { password } : {}\n}\n\nasync function open(file: string, password?: string): Promise<ZipReader<unknown>> {\n // A lazily-read Blob keeps a multi-gigabyte archive out of memory: zip.js only\n // pulls the byte ranges it needs.\n const blob = await fs.openAsBlob(file, { type: 'application/zip' })\n return password === undefined\n ? new ZipReader(new BlobReader(blob))\n : new ZipReader(new BlobReader(blob), { password })\n}\n\ninterface WalkedFile {\n name: string\n absolute: string\n directory: boolean\n size: number\n}\n\n/** Sorted, deterministic walk with symlinks resolved and directory loops broken. */\nfunction walk(root: string): WalkedFile[] {\n const files: WalkedFile[] = []\n const seen = new Set<string>()\n\n const visit = (absolute: string, name: string): void => {\n let stats: fs.Stats\n try {\n stats = fs.statSync(absolute)\n }\n catch {\n return\n }\n\n if (stats.isDirectory()) {\n const real = fs.realpathSync(absolute)\n if (seen.has(real))\n return\n seen.add(real)\n files.push({ name: `${name}/`, absolute, directory: true, size: 0 })\n for (const child of fs.readdirSync(absolute).sort())\n visit(path.join(absolute, child), `${name}/${child}`)\n return\n }\n\n if (stats.isFile())\n files.push({ name, absolute, directory: false, size: stats.size })\n }\n\n for (const child of fs.readdirSync(root).sort())\n visit(path.join(root, child), child)\n\n return files\n}\n","export interface BackoffOptions {\n baseDelayMs: number\n factor: number\n maxDelayMs: number\n}\n\n/** Exponential backoff: base * factor^(attempt - 1), capped at maxDelayMs. */\nexport function computeBackoff(attempt: number, options: BackoffOptions): number {\n const normalized = Math.max(1, Math.floor(attempt))\n const raw = options.baseDelayMs * options.factor ** (normalized - 1)\n if (!Number.isFinite(raw))\n return options.maxDelayMs\n return Math.min(Math.max(0, raw), options.maxDelayMs)\n}\n","import type { HttpCheckConfig } from '#src/shared/contracts'\nimport { probePort } from '#src/providers/port'\n\nexport interface HealthProbeResult {\n healthy: boolean\n ms: number\n detail: string\n}\n\n/** TCP connect timing, used for the default `port` mode and readiness. */\nexport async function probeTcp(host: string, port: number, timeoutMs: number): Promise<HealthProbeResult> {\n const started = Date.now()\n const accepting = await probePort(host, port, timeoutMs)\n const ms = Date.now() - started\n return { healthy: accepting, ms, detail: accepting ? 'port accepted a connection' : 'port did not accept a connection' }\n}\n\nexport interface HttpProbeOptions extends Pick<HttpCheckConfig, 'path' | 'method' | 'expectBody' | 'expectStatusBelow'> {\n /** `null` counts as \"not set\", so a value saved by the UI can be cleared again. */\n expectStatus?: number | null\n timeoutMs: number\n}\n\n/**\n * Fetches the configured path and asserts the response, so \"listening\" is not\n * mistaken for \"working\".\n */\nexport async function probeHttp(host: string, port: number, options: HttpProbeOptions): Promise<HealthProbeResult> {\n const url = `http://${host}:${port}${options.path.startsWith('/') ? options.path : `/${options.path}`}`\n const started = Date.now()\n\n try {\n const response = await fetch(url, {\n method: options.method,\n redirect: 'manual',\n signal: AbortSignal.timeout(options.timeoutMs),\n })\n const ms = Date.now() - started\n\n const expected = options.expectStatus ?? null\n if (expected !== null && response.status !== expected) {\n return { healthy: false, ms, detail: `expected status ${expected}, got ${response.status}` }\n }\n if (expected === null && response.status >= options.expectStatusBelow) {\n return { healthy: false, ms, detail: `status ${response.status} is >= ${options.expectStatusBelow}` }\n }\n\n if (options.expectBody.length > 0 && options.method !== 'HEAD') {\n const body = await response.text()\n if (!body.includes(options.expectBody)) {\n return { healthy: false, ms, detail: `body does not contain ${JSON.stringify(options.expectBody)}` }\n }\n }\n\n return { healthy: true, ms, detail: `HTTP ${response.status}` }\n }\n catch (error) {\n const ms = Date.now() - started\n const reason = error instanceof Error ? error.message : String(error)\n return { healthy: false, ms, detail: `request failed: ${reason}` }\n }\n}\n\nexport async function probeHealth(options: {\n mode: 'port' | 'http'\n hosts: string[]\n port: number\n timeoutMs: number\n http: Pick<HttpCheckConfig, 'path' | 'method' | 'expectBody' | 'expectStatusBelow'> & { expectStatus?: number | null }\n}): Promise<HealthProbeResult> {\n let last: HealthProbeResult = { healthy: false, ms: 0, detail: 'not probed' }\n\n // Try each candidate host in order (loopback first), so a server bound to one\n // specific address is still probed somewhere it actually listens.\n for (const host of options.hosts) {\n const result = options.mode === 'http'\n ? await probeHttp(host, options.port, { ...options.http, timeoutMs: options.timeoutMs })\n : await probeTcp(host, options.port, options.timeoutMs)\n if (result.healthy)\n return result\n last = result\n }\n\n return last\n}\n","import type { ProcessResources } from '#src/shared/contracts'\nimport { execFile } from 'node:child_process'\nimport fs from 'node:fs'\nimport process from 'node:process'\nimport { promisify } from 'node:util'\n\nconst execFileAsync = promisify(execFile)\n\ninterface ProcRow {\n pid: number\n ppid: number\n rssKb: number\n /** Cumulative CPU seconds, when the platform reports it (Linux, Windows). */\n cpuSeconds?: number\n /** Instantaneous/decaying CPU percent, when the platform reports it (macOS). */\n cpuPercent?: number\n}\n\nexport function parsePsOutput(text: string): ProcRow[] {\n const rows: ProcRow[] = []\n for (const line of text.split('\\n')) {\n const parts = line.trim().split(/\\s+/)\n if (parts.length < 4)\n continue\n const [pid, ppid, rss, cpu] = parts.map(entry => Number.parseFloat(entry))\n if (pid === undefined || ppid === undefined || rss === undefined || !Number.isFinite(pid))\n continue\n rows.push({ pid, ppid, rssKb: rss, cpuPercent: Number.isFinite(cpu) ? cpu : undefined })\n }\n return rows\n}\n\n/** CSV from `Get-CimInstance ... | ConvertTo-Csv`, or wmic's `/format:csv`. */\nexport function parseWindowsCsv(text: string): ProcRow[] {\n const rows: ProcRow[] = []\n const lines = text.split(/\\r?\\n/).filter(line => line.trim().length > 0)\n const header = lines.shift()\n if (header === undefined)\n return rows\n\n const columns = header.split(',').map(entry => entry.replace(/\"/g, '').trim().toLowerCase())\n const index = (name: string): number => columns.indexOf(name.toLowerCase())\n const pidAt = index('ProcessId')\n const ppidAt = index('ParentProcessId')\n const rssAt = index('WorkingSetSize')\n const kernelAt = index('KernelModeTime')\n const userAt = index('UserModeTime')\n\n for (const line of lines) {\n const cells = line.split(',').map(entry => entry.replace(/\"/g, '').trim())\n const pid = Number.parseInt(cells[pidAt] ?? '', 10)\n if (!Number.isFinite(pid))\n continue\n\n // A missing time column must read as \"unknown\", never as zero CPU.\n const kernel = kernelAt >= 0 ? Number.parseInt(cells[kernelAt] ?? '', 10) : Number.NaN\n const user = userAt >= 0 ? Number.parseInt(cells[userAt] ?? '', 10) : Number.NaN\n const hasTimes = Number.isFinite(kernel) && Number.isFinite(user)\n\n rows.push({\n pid,\n ppid: Number.parseInt(cells[ppidAt] ?? '', 10) || 0,\n // WorkingSetSize is bytes on Windows; the sampler sums kilobytes.\n rssKb: (Number.parseInt(cells[rssAt] ?? '', 10) || 0) / 1024,\n cpuSeconds: hasTimes ? (kernel + user) / 1e7 /* 100ns units */ : undefined,\n })\n }\n\n return rows\n}\n\nlet clockTicks: number | null = null\n\n/** Linux jiffies per second; `getconf` is POSIX, with the usual default behind it. */\nasync function getClockTicks(): Promise<number> {\n if (clockTicks !== null)\n return clockTicks\n try {\n const { stdout } = await execFileAsync('getconf', ['CLK_TCK'], { timeout: 2000 })\n const parsed = Number.parseInt(stdout.trim(), 10)\n clockTicks = Number.isFinite(parsed) && parsed > 0 ? parsed : 100\n }\n catch {\n clockTicks = 100\n }\n return clockTicks\n}\n\n/**\n * `/proc/<pid>/stat` needs care: the comm field is parenthesised and may itself\n * contain spaces or parentheses, so parsing starts after the last `)`.\n */\nfunction parseStat(pid: number, content: string): ProcRow | null {\n const close = content.lastIndexOf(')')\n if (close < 0)\n return null\n const fields = content.slice(close + 2).split(' ')\n const ppid = Number.parseInt(fields[1] ?? '', 10)\n const utime = Number.parseInt(fields[11] ?? '', 10)\n const stime = Number.parseInt(fields[12] ?? '', 10)\n const rssPages = Number.parseInt(fields[21] ?? '', 10)\n\n if (!Number.isFinite(ppid) || !Number.isFinite(utime) || !Number.isFinite(stime))\n return null\n return { pid, ppid, rssKb: Number.isFinite(rssPages) ? rssPages * 4 : 0, cpuSeconds: utime + stime }\n}\n\nasync function readLinux(): Promise<ProcRow[]> {\n const rows: ProcRow[] = []\n let names: string[] = []\n try {\n names = fs.readdirSync('/proc')\n }\n catch {\n return rows\n }\n\n for (const name of names) {\n if (!/^\\d+$/.test(name))\n continue\n const pid = Number.parseInt(name, 10)\n try {\n const row = parseStat(pid, fs.readFileSync(`/proc/${pid}/stat`, 'utf8'))\n if (row === null)\n continue\n // VmRSS is exact; the stat page count assumes a 4K page.\n try {\n const vmRss = /^VmRSS:\\s+(\\d+)\\s+kB/m.exec(fs.readFileSync(`/proc/${pid}/status`, 'utf8'))?.[1]\n if (vmRss !== undefined)\n row.rssKb = Number.parseInt(vmRss, 10)\n }\n catch {\n // Fall back to the page count.\n }\n rows.push(row)\n }\n catch {\n // Exited between listing and reading.\n }\n }\n\n return rows\n}\n\nasync function readPosix(): Promise<ProcRow[]> {\n const { stdout } = await execFileAsync('ps', ['-Ao', 'pid=,ppid=,rss=,%cpu='], { timeout: 5000, maxBuffer: 16 * 1024 * 1024 })\n return parsePsOutput(stdout)\n}\n\nasync function readWindows(): Promise<ProcRow[]> {\n const script = 'Get-CimInstance Win32_Process | Select-Object ProcessId,ParentProcessId,WorkingSetSize,KernelModeTime,UserModeTime | ConvertTo-Csv -NoTypeInformation'\n try {\n const { stdout } = await execFileAsync('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', script], {\n timeout: 8000,\n maxBuffer: 16 * 1024 * 1024,\n })\n return parseWindowsCsv(stdout)\n }\n catch {\n try {\n const { stdout } = await execFileAsync('wmic', [\n 'process',\n 'get',\n 'ProcessId,ParentProcessId,WorkingSetSize,KernelModeTime,UserModeTime',\n '/format:csv',\n ], { timeout: 8000, maxBuffer: 16 * 1024 * 1024 })\n return parseWindowsCsv(stdout)\n }\n catch {\n // Neither tool is available; resource sampling degrades to \"unknown\".\n return []\n }\n }\n}\n\nasync function readProcesses(): Promise<ProcRow[]> {\n if (process.platform === 'linux')\n return readLinux()\n if (process.platform === 'win32')\n return readWindows()\n return readPosix()\n}\n\nfunction collectTree(rootPid: number, children: Map<number, number[]>): number[] {\n const pids: number[] = []\n const stack = [rootPid]\n const seen = new Set<number>()\n\n while (stack.length > 0) {\n const pid = stack.pop()!\n if (seen.has(pid))\n continue\n seen.add(pid)\n pids.push(pid)\n for (const child of children.get(pid) ?? []) stack.push(child)\n }\n\n return pids\n}\n\n/**\n * Samples CPU and RSS for a process *and its descendants*.\n *\n * Descendants matter: a wrapper that spawns the real server detached (the\n * 9router CLI does) owns the tree, and only the tree's RSS means anything.\n *\n * Backends: `/proc` on Linux, `ps` on macOS/other POSIX, and Win32_Process via\n * PowerShell (wmic as a fallback) on Windows. When a backend cannot run, samples\n * are null rather than wrong.\n */\nexport class ProcessSampler {\n private readonly previous = new Map<number, { cpuSeconds: number, at: number }>()\n\n async sample(rootPid: number, now = Date.now()): Promise<ProcessResources | null> {\n const samples = await this.sampleMany([rootPid], now)\n return samples.get(rootPid) ?? null\n }\n\n async sampleMany(rootPids: number[], now = Date.now()): Promise<Map<number, ProcessResources | null>> {\n const results = new Map<number, ProcessResources | null>()\n if (rootPids.length === 0)\n return results\n\n let rows: ProcRow[] = []\n try {\n rows = await readProcesses()\n }\n catch {\n rows = []\n }\n\n const byPid = new Map(rows.map(row => [row.pid, row]))\n const children = new Map<number, number[]>()\n for (const row of rows) {\n const siblings = children.get(row.ppid) ?? []\n siblings.push(row.pid)\n children.set(row.ppid, siblings)\n }\n\n for (const rootPid of rootPids) {\n if (!byPid.has(rootPid)) {\n this.previous.delete(rootPid)\n results.set(rootPid, null)\n continue\n }\n\n const pids = collectTree(rootPid, children)\n let rssKb = 0\n let cpuSeconds: number | null = 0\n let percentAverage: number | null = null\n\n for (const pid of pids) {\n const row = byPid.get(pid)\n if (!row)\n continue\n rssKb += row.rssKb\n if (row.cpuSeconds === undefined)\n cpuSeconds = null\n else if (cpuSeconds !== null)\n cpuSeconds += row.cpuSeconds\n if (row.cpuPercent !== undefined)\n percentAverage = (percentAverage ?? 0) + row.cpuPercent\n }\n\n let cpuPercent: number | null = percentAverage\n if (cpuPercent === null && cpuSeconds !== null) {\n if (process.platform === 'linux') {\n const ticks = await getClockTicks()\n cpuSeconds /= ticks\n }\n\n const before = this.previous.get(rootPid)\n if (before !== undefined && now > before.at) {\n const elapsedSeconds = (now - before.at) / 1000\n const usedSeconds = cpuSeconds - before.cpuSeconds\n if (elapsedSeconds > 0 && usedSeconds >= 0)\n cpuPercent = (usedSeconds / elapsedSeconds) * 100\n }\n this.previous.set(rootPid, { cpuSeconds, at: now })\n }\n else {\n this.previous.delete(rootPid)\n }\n\n results.set(rootPid, {\n cpuPercent: cpuPercent === null ? null : Math.round(cpuPercent * 10) / 10,\n rssBytes: Math.round(rssKb * 1024),\n processes: pids.length,\n sampledAt: now,\n })\n }\n\n return results\n }\n\n forget(rootPid: number): void {\n this.previous.delete(rootPid)\n }\n}\n\n/**\n * Does this process carry the environment the supervisor gave the entry?\n *\n * A program that restarts itself — especially a plugin doing it — leaves behind a\n * detached process that still inherits `HHOSTED_SERVER_ID`, and that marker is what\n * tells a legitimate successor apart from a stranger squatting on the port.\n *\n * Linux reads `/proc`; macOS asks `ps -E`; Windows has no per-process environment,\n * so the answer is always \"no\" there and ownership falls back to the port policy.\n */\nexport async function processCarriesServerId(pid: number, serverId: string): Promise<boolean> {\n if (serverId.length === 0)\n return false\n const needle = `HHOSTED_SERVER_ID=${serverId}`\n\n if (process.platform === 'linux') {\n try {\n const raw = await fs.promises.readFile(`/proc/${pid}/environ`, 'utf8')\n return raw.split('\\0').includes(needle)\n }\n catch {\n return false\n }\n }\n\n if (process.platform === 'darwin') {\n try {\n const { stdout } = await execFileAsync('ps', ['-p', String(pid), '-E', '-ww', '-o', 'command='], { timeout: 3000 })\n // Values with spaces are unquoted in this output, so the marker is matched as a word.\n return new RegExp(`(?:^|\\\\s)${needle}(?:\\\\s|$)`).test(stdout)\n }\n catch {\n return false\n }\n }\n\n return false\n}\n","import { execFile } from 'node:child_process'\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport process from 'node:process'\nimport { promisify } from 'node:util'\nimport { processCarriesServerId } from '#src/providers/proc'\n\nconst execFileAsync = promisify(execFile)\n\n/** What the panel actually spawned for an entry: the argv it has to recognize again. */\nexport interface SpawnInfo {\n command: string\n args: string[]\n cwd: string\n}\n\n/**\n * Splits a command line into words, undoing the quoting Windows put there. `CommandLine`\n * is the raw string from the spawn call, so `\"C:\\a b\\x.cmd\" /c` is one argv entry plus\n * two words, and a quoted argument that contains spaces has to come back as one word.\n *\n * Windows also *escapes* a quote inside an argument as `\\\"` (Node does this for any argv\n * containing a quote), so the escape is undone here — otherwise every argument with a\n * quote in it survives as a stray backslash and never matches what the panel spawned.\n */\nexport function splitCommandLine(line: string): string[] {\n const words: string[] = []\n let current = ''\n let quoted = false\n let started = false\n\n for (const char of line.trim()) {\n if (char === '\"') {\n quoted = !quoted\n // A quote is both a delimiter and proof that a (possibly empty) word exists.\n started = true\n continue\n }\n if (!quoted && /\\s/.test(char)) {\n if (started)\n words.push(current)\n current = ''\n started = false\n continue\n }\n current += char\n started = true\n }\n\n if (started)\n words.push(current)\n return words\n}\n\nfunction comparable(target: string): string {\n // Quotes around a whole word are stripped; whitespace never is. `SpawnInfo.args` is the\n // argv `spawn` was handed, where a whitespace argument is still an argument, so trimming\n // here would make it compare equal to a missing one. A `\\\"` is Windows' escape for a\n // quote inside an argument, and the word it sits in is compared against the raw argv.\n const value = target.replace(/^\"(.*)\"$/, '$1').replace(/\\\\(?=\")/g, '')\n return process.platform === 'win32' ? value.toLowerCase() : value\n}\n\n/**\n * Literal comparison for an argument: the same text, modulo the case folding Windows needs\n * and the quoting a command line shuffles around.\n *\n * Deliberately *not* `sameWord`: folding an argument to its basename would let this\n * entry's `/srv/web/build/server.js` equal a stranger's `/tmp/evil/build/server.js`, and a\n * match here is what `reclaim` kills. Nor is it a plain string equality: reading an argv\n * back out of a Windows `CommandLine` cannot preserve quotes exactly — the OS escapes an\n * argument's own quote as `\\\"` and strips the structural ones — so quotes and backslashes\n * are dropped from both sides. That leaves the arguments' actual text, which is what the\n * match is about.\n */\nfunction sameArg(a: string, b: string): boolean {\n const normalize = (value: string): string => comparable(value).replace(/[\"\\\\]/g, '')\n return normalize(a) === normalize(b)\n}\n\n/** The same file spelled differently (`node`, `node.exe`, a relative path) compares equal. */\nfunction sameWord(a: string, b: string): boolean {\n const left = comparable(a)\n const right = comparable(b)\n if (left === right || path.basename(left) === path.basename(right))\n return true\n return path.extname(b) === '' && path.basename(left, path.extname(left)) === right\n}\n\n/**\n * True when the argv a process is running is the entry's own: the image must match where\n * `spawn` would have looked it up — the image Path, or the first word of the command line\n * — and the words after it must open with the entry's args, compared literally. So\n * `spawn --port 4000` also covers `spawn -p 4000 --extra`, which is what a self-restarting\n * wrapper does, while `/tmp/evil/server.js` never covers `/srv/web/server.js`.\n *\n * `words` must already be the process's own argv. A command-line *string* is only correct\n * on Windows, where the OS hands one out; `/proc/<pid>/cmdline` quotes are literal bytes\n * of an argument, so re-joining that argv into one string corrupts it.\n */\nexport function matchesSpawn(info: { words: string[], imagePath?: string | null }, spawn: SpawnInfo): boolean {\n const { words } = info\n const first = words[0] ?? ''\n\n // The image may be the first word, or absent from the command line entirely — a shim or\n // an interpreter reports its own image while the argv still carries what we passed.\n const imageMatches = sameWord(first, spawn.command)\n || (info.imagePath != null && info.imagePath !== '' && sameWord(info.imagePath, spawn.command))\n if (!imageMatches)\n return false\n\n // `>` rather than `>=`: a process has to have *more* words than we have args, so an\n // argument can never be satisfied by a word that is not there.\n const offset = sameWord(first, spawn.command) ? 1 : 0\n if (words.length < spawn.args.length + offset)\n return false\n\n return spawn.args.every((arg, index) => sameArg(words[index + offset]!, arg))\n}\n\n/** The argv of a pid on the platforms whose process table can answer it; null otherwise. */\nexport async function processArgv(pid: number): Promise<string[] | null> {\n if (process.platform === 'win32')\n return null\n\n if (process.platform === 'linux') {\n try {\n const raw = await fs.promises.readFile(`/proc/${pid}/cmdline`)\n const argv = raw.toString('utf8').split('\\0').filter(part => part.length > 0)\n return argv.length > 0 ? argv : null\n }\n catch {\n return null\n }\n }\n\n try {\n const { stdout } = await execFileAsync('ps', ['-p', String(pid), '-ww', '-o', 'command='], { timeout: 3000 })\n // macOS receives one line and has to split it back into words here. `ps` joins argv\n // with spaces and quotes none of them, so an argument that itself contains a space\n // cannot be told from two arguments — that entry then fails to match and blocks,\n // which is the safe direction to be wrong in.\n const words = splitCommandLine(stdout.trim())\n return words.length > 0 ? words : null\n }\n catch {\n return null\n }\n}\n\nconst windowsFilter = /^\\d+$/\n\n/**\n * The first two rows of `ConvertTo-Csv` output: the header and the first record. PowerShell\n * quotes and doubles its way around CSV, so `a,\"b\"\"c\"` is three fields with the second\n * reading `b\"c`.\n */\nfunction parseCsvRows(output: string): [string[] | null, string[] | null] {\n const rows: string[][] = []\n let row: string[] = []\n let field = ''\n let quoted = false\n\n for (let index = 0; index < output.length; index++) {\n const char = output[index]!\n if (quoted) {\n if (char !== '\"') {\n field += char\n continue\n }\n if (output[index + 1] === '\"') {\n field += '\"'\n index++\n continue\n }\n quoted = false\n continue\n }\n\n if (char === '\"') {\n quoted = true\n continue\n }\n if (char === ',') {\n row.push(field)\n field = ''\n continue\n }\n if (char === '\\n') {\n row.push(field.replace(/\\r$/, ''))\n rows.push(row)\n row = []\n field = ''\n continue\n }\n field += char\n }\n\n if (field.length > 0 || row.length > 0)\n rows.push([...row, field.replace(/\\r$/, '')])\n\n return [rows[0] ?? null, rows[1] ?? null]\n}\n\n/**\n * `Win32_Process` for one pid, or null when it cannot be read.\n *\n * The whole round trip — launching PowerShell, loading the CIM provider, serializing —\n * costs seconds on a cold runner, so the timeout is generous and the result is converted\n * to CSV rather than JSON: CSV survives a value that contains a quote or a newline, which\n * an argv legitimately can.\n */\nexport async function windowsProcessInfo(pid: number): Promise<{ commandLine: string, imagePath: string | null } | null> {\n if (!windowsFilter.test(String(pid)))\n return null\n\n try {\n const script = `Get-CimInstance Win32_Process -Filter \"ProcessId=${pid}\" | Select-Object CommandLine,ExecutablePath | ConvertTo-Csv -NoTypeInformation`\n const { stdout } = await execFileAsync('powershell', ['-NoProfile', '-NonInteractive', '-Command', script], { timeout: 20000, windowsHide: true })\n const [headers, values] = parseCsvRows(stdout)\n if (!headers || !values)\n return null\n\n const commandLineIndex = headers.indexOf('CommandLine')\n const imageIndex = headers.indexOf('ExecutablePath')\n const commandLine = commandLineIndex >= 0 ? values[commandLineIndex] : undefined\n if (commandLine === undefined || commandLine.length === 0)\n return null\n\n const imagePath = imageIndex >= 0 ? values[imageIndex] : undefined\n return { commandLine, imagePath: imagePath && imagePath.length > 0 ? imagePath : null }\n }\n catch {\n return null\n }\n}\n\n/**\n * Which of these pids look like this entry's own detached successor. The environment\n * marker is authoritative where the platform can read it; otherwise the answer rests on\n * the entry's own argv, which is the only signal a detached successor is obliged to keep\n * — and the only one Windows exposes at all.\n */\nexport async function identifyHolders(serverId: string, spawn: SpawnInfo, pids: number[]): Promise<number[]> {\n const found: number[] = []\n\n for (const pid of pids) {\n if (await processCarriesServerId(pid, serverId)) {\n found.push(pid)\n continue\n }\n\n // An empty argv proves nothing, so a marker is the only way in without it.\n if (spawn.args.length === 0)\n continue\n\n if (process.platform === 'win32') {\n const info = await windowsProcessInfo(pid)\n if (info && matchesSpawn({ words: splitCommandLine(info.commandLine), imagePath: info.imagePath }, spawn))\n found.push(pid)\n continue\n }\n\n const argv = await processArgv(pid)\n if (argv !== null && matchesSpawn({ words: argv }, spawn))\n found.push(pid)\n }\n\n return found\n}\n","import type { ChildProcess } from 'node:child_process'\nimport { execFile, spawn } from 'node:child_process'\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport process from 'node:process'\nimport { promisify } from 'node:util'\nimport { projectDir } from '#src/helpers/paths'\n\nconst execFileAsync = promisify(execFile)\n\nexport interface SpawnSpec {\n command: string\n args: string[]\n cwd: string\n env: Record<string, string>\n}\n\n/** Windows resolves a project-local bin to one of these shims, not the bare name. */\nconst WINDOWS_SHIM_EXTENSIONS = ['.cmd', '.exe', '.bat', '.ps1']\n\n/**\n * Resolves a bare command through the entry's own directory and the project's\n * `node_modules/.bin` first, so a server installed as a project dependency is\n * found even when the launcher's PATH has no pnpm-injected bin dir.\n */\nexport function resolveCommand(command: string, ...searchDirs: string[]): string {\n if (command.includes('/') || command.includes('\\\\'))\n return command\n\n const candidates = process.platform === 'win32' && path.extname(command) === ''\n ? [command, ...WINDOWS_SHIM_EXTENSIONS.map(extension => `${command}${extension}`)]\n : [command]\n\n for (const dir of searchDirs) {\n for (const candidate of candidates) {\n const local = path.join(dir, 'node_modules', '.bin', candidate)\n if (fs.existsSync(local))\n return local\n }\n }\n\n return command\n}\n\n/** Relative entry paths belong to the project that launched the panel. */\nexport function resolveCwd(cwd: string, base: string = projectDir): string {\n return path.resolve(base, cwd)\n}\n\n/** Node cannot spawn a Windows `.cmd`/`.bat` shim without a shell (EINVAL). */\nexport function needsShell(command: string): boolean {\n return process.platform === 'win32' && /\\.(?:cmd|bat)$/i.test(command)\n}\n\nexport function spawnManaged(spec: SpawnSpec): ChildProcess {\n return spawn(spec.command, spec.args, {\n cwd: spec.cwd,\n env: { ...process.env, ...spec.env },\n // Own process group: a stop can signal the whole tree with one kill(-pid).\n detached: true,\n stdio: ['ignore', 'pipe', 'pipe'],\n shell: needsShell(spec.command),\n windowsHide: true,\n })\n}\n\nexport interface TerminateOptions {\n signal: NodeJS.Signals\n killGroup: boolean\n graceMs: number\n}\n\n/** SIGTERM (default) to the child, escalating to SIGKILL after the grace period. */\nexport async function terminate(child: ChildProcess, options: TerminateOptions): Promise<'exited' | 'force-killed'> {\n if (child.exitCode !== null || child.signalCode !== null)\n return 'exited'\n\n const exited = waitForExit(child, options.graceMs)\n signalChild(child, options.signal, options.killGroup)\n\n if (await exited)\n return 'exited'\n\n signalChild(child, 'SIGKILL', options.killGroup)\n await waitForExit(child, 2000)\n return 'force-killed'\n}\n\n/**\n * Windows has no process groups and no SIGTERM: `taskkill /T` walks the tree and\n * `/F` is the only reliable way to stop a console process.\n */\nexport async function killTreeWindows(pid: number): Promise<void> {\n try {\n await execFileAsync('taskkill', ['/pid', String(pid), '/T', '/F'], { timeout: 5000 })\n }\n catch {\n // Already gone, or taskkill is unavailable.\n }\n}\n\nfunction signalChild(child: ChildProcess, signal: NodeJS.Signals, killGroup: boolean): void {\n const pid = child.pid\n if (pid === undefined)\n return\n signalPid(pid, signal, killGroup)\n}\n\nfunction signalPid(pid: number, signal: NodeJS.Signals, killGroup: boolean): void {\n if (process.platform === 'win32') {\n if (killGroup) {\n void killTreeWindows(pid)\n }\n else {\n try {\n process.kill(pid, signal)\n }\n catch {\n // already exited\n }\n }\n return\n }\n\n if (killGroup) {\n try {\n process.kill(-pid, signal)\n return\n }\n catch {\n // group already gone, fall through to the single pid\n }\n }\n\n try {\n process.kill(pid, signal)\n }\n catch {\n // already exited\n }\n}\n\nfunction alive(pid: number): boolean {\n try {\n process.kill(pid, 0)\n return true\n }\n catch {\n return false\n }\n}\n\n/**\n * The same shutdown a supervised child gets, for a process we adopted instead of\n * spawned: a detached successor is not our child, so it is signalled by pid (and\n * by process group when asked) and its exit is polled rather than awaited.\n */\nexport async function terminatePid(pid: number, options: TerminateOptions): Promise<'exited' | 'force-killed'> {\n if (!alive(pid))\n return 'exited'\n\n signalPid(pid, options.signal, options.killGroup)\n\n const deadline = Date.now() + Math.max(0, options.graceMs)\n while (Date.now() < deadline && alive(pid))\n await delay(50)\n if (!alive(pid))\n return 'exited'\n\n signalPid(pid, 'SIGKILL', options.killGroup)\n const hardDeadline = Date.now() + 2000\n while (Date.now() < hardDeadline && alive(pid))\n await delay(50)\n return 'force-killed'\n}\n\nfunction delay(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms))\n}\n\nfunction waitForExit(child: ChildProcess, timeoutMs: number): Promise<boolean> {\n if (child.exitCode !== null || child.signalCode !== null)\n return Promise.resolve(true)\n if (timeoutMs <= 0)\n return Promise.resolve(false)\n\n return new Promise((resolve) => {\n const timer = setTimeout(() => {\n child.removeListener('exit', onExit)\n resolve(false)\n }, timeoutMs)\n\n function onExit(): void {\n clearTimeout(timer)\n resolve(true)\n }\n\n child.once('exit', onExit)\n })\n}\n","import type { ServerConfig } from '#src/shared/contracts'\n\n/**\n * Orders servers so every dependency comes before its dependents.\n *\n * Cycles and unknown ids are ignored here — the config store reports them as\n * config errors — so this can never throw and stall supervision.\n */\nexport function orderByDependencies(servers: ServerConfig[]): ServerConfig[] {\n const byId = new Map(servers.map(server => [server.id, server]))\n const ordered: ServerConfig[] = []\n const visited = new Set<string>()\n\n const visit = (server: ServerConfig): void => {\n if (visited.has(server.id))\n return\n visited.add(server.id)\n for (const dependency of server.dependsOn) {\n const target = byId.get(dependency)\n if (target && target.id !== server.id)\n visit(target)\n }\n ordered.push(server)\n }\n\n for (const server of servers) visit(server)\n return ordered\n}\n\n/** The transitive dependencies of a server, nearest first. */\nexport function dependenciesOf(server: ServerConfig, servers: ServerConfig[]): ServerConfig[] {\n const byId = new Map(servers.map(entry => [entry.id, entry]))\n const found: ServerConfig[] = []\n const seen = new Set<string>()\n\n const walk = (current: ServerConfig): void => {\n for (const dependency of current.dependsOn) {\n if (seen.has(dependency))\n continue\n seen.add(dependency)\n const target = byId.get(dependency)\n if (!target)\n continue\n found.push(target)\n walk(target)\n }\n }\n\n walk(server)\n return found\n}\n\n/** Dependents that must stop before this server does. */\nexport function dependentsOf(server: ServerConfig, servers: ServerConfig[]): ServerConfig[] {\n return servers.filter(entry => entry.id !== server.id && dependenciesOf(entry, servers).some(d => d.id === server.id))\n}\n","import type { Buffer } from 'node:buffer'\nimport type { LogLine, LogStream } from '#src/shared/contracts'\n\n/** A partial line past this size is emitted rather than buffered further. */\nconst MAX_PENDING_CHARS = 64 * 1024\n\n/** Fixed-capacity line buffer; oldest lines are dropped first. */\nexport class LogBuffer {\n private lines: LogLine[] = []\n\n constructor(private capacity: number) {}\n\n push(line: LogLine): void {\n this.lines.push(line)\n if (this.lines.length > this.capacity)\n this.lines.splice(0, this.lines.length - this.capacity)\n }\n\n extend(lines: LogLine[]): void {\n for (const line of lines) this.push(line)\n }\n\n list(limit?: number): LogLine[] {\n if (limit === undefined || limit >= this.lines.length)\n return [...this.lines]\n return this.lines.slice(-limit)\n }\n\n clear(): void {\n this.lines = []\n }\n\n get size(): number {\n return this.lines.length\n }\n}\n\n/**\n * Splits a chunk into complete lines, keeping a trailing partial line buffered:\n * a child writing \"hel\" then \"lo\\n\" must surface one line, not two.\n */\nexport class LineSplitter {\n private pending = ''\n\n constructor(private readonly emit: (stream: LogStream, text: string) => void) {}\n\n push(stream: LogStream, chunk: string | Buffer): void {\n this.pending += chunk.toString()\n const parts = this.pending.split('\\n')\n this.pending = parts.pop() ?? ''\n for (const part of parts) this.emit(stream, part.replace(/\\r$/, ''))\n // A child that never sends a newline (a progress bar, one minified JSON blob)\n // must not grow this string without bound: past the cap it is emitted in pieces.\n while (this.pending.length > MAX_PENDING_CHARS) {\n this.emit(stream, this.pending.slice(0, MAX_PENDING_CHARS))\n this.pending = this.pending.slice(MAX_PENDING_CHARS)\n }\n }\n\n flush(stream: LogStream): void {\n if (this.pending.length === 0)\n return\n this.emit(stream, this.pending.replace(/\\r$/, ''))\n this.pending = ''\n }\n}\n","import type { ChildProcess } from 'node:child_process'\nimport type { ServerConfig } from '#src/config/schema'\nimport type { ConfigStore } from '#src/config/store'\nimport type { TemplateVars } from '#src/helpers/template'\nimport type { SpawnInfo } from '#src/providers/identity'\nimport type { ControlEndpoint } from '#src/services/control-server'\nimport type { EventHub } from '#src/services/events'\nimport type { HistoryStore } from '#src/services/history'\nimport type { HostMonitor } from '#src/services/host-monitor'\nimport type { LogFiles } from '#src/services/log-files'\nimport type { NotificationReason, NotificationService } from '#src/services/notifications'\nimport type {\n AppState,\n FreePortResult,\n HealthState,\n LogLine,\n LogStream,\n PortState,\n ProcessResources,\n ServerStatus,\n ServerView,\n} from '#src/shared/contracts'\nimport { spawn } from 'node:child_process'\nimport os from 'node:os'\nimport path from 'node:path'\nimport process from 'node:process'\nimport { computeBackoff } from '#src/helpers/backoff'\nimport { bindHost, displayHost, lanAddress } from '#src/helpers/bind'\nimport { expandEnvList, expandEnvRecord, loadEnvFile, resolveEnvFilePath } from '#src/helpers/env-file'\nimport { logger } from '#src/helpers/logger'\nimport { dataRoot, projectDir } from '#src/helpers/paths'\nimport { resolveRecord, resolveTemplates } from '#src/helpers/template'\nimport { probeHealth } from '#src/providers/health-check'\nimport { identifyHolders } from '#src/providers/identity'\nimport { isPortFree, isProcessAlive, killPortHolders, listPortHolders, probePort, terminatePids } from '#src/providers/port'\nimport { ProcessSampler } from '#src/providers/proc'\nimport { resolveCommand, resolveCwd, spawnManaged, terminate, terminatePid } from '#src/providers/process'\nimport { dependenciesOf, orderByDependencies } from '#src/services/dependencies'\nimport { LineSplitter, LogBuffer } from '#src/services/log-buffer'\n\nexport interface SupervisorOptions {\n configPath: string\n /** Live listener info, mutated by the control server when it rebinds. */\n control: ControlEndpoint\n /** Injected so SSE state frames carry the same view the API serves. */\n buildState: (views: ServerView[]) => AppState\n history: HistoryStore\n logFiles: LogFiles\n notifications: NotificationService\n hostMonitor: HostMonitor\n}\n\n/** Uptime/crash counters are reported over this window. */\nconst HISTORY_WINDOW_MS = 24 * 60 * 60 * 1000\nconst HISTORY_CACHE_MS = 5000\n\nexport interface StartResult {\n ok: boolean\n error?: string\n}\n\n/** What the port preflight found: nothing in the way, our own successor, or a blocker. */\ntype PreflightConflict\n = | { kind: 'free' }\n | { kind: 'adopt', pid: number }\n | { kind: 'blocked', error: string }\n\ninterface Entry {\n config: ServerConfig\n status: ServerStatus\n health: HealthState\n portState: PortState\n child: ChildProcess | null\n pid: number | null\n startedAt: number | null\n exitCode: number | null\n exitSignal: string | null\n restarts: number\n lastError: string | null\n nextRetryAt: number | null\n retryTimer: NodeJS.Timeout | null\n healthFailures: number\n unhealthySince: number | null\n lastProbeAt: number\n lastOccupancyProbeAt: number\n probing: boolean\n /** The running process is a detached successor we adopted, not a child we spawned. */\n adopted: boolean\n /** A start is in flight (set synchronously, unlike `status`). */\n starting: boolean\n stopping: boolean\n bootstrapDone: boolean\n logs: LogBuffer\n responseMs: number | null\n resources: ProcessResources | null\n resourcesSampledAt: number\n historyCache: { revision: number, at: number, summary: ServerView['history'] } | null\n}\n\nconst TICK_INTERVAL_MS = 1000\nconst PORT_STATE_INTERVAL_MS = 10000\nconst PORT_RELEASE_RECHECK_MS = 300\nconst RESOURCE_SAMPLE_INTERVAL_MS = 5000\n\nfunction delay(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms))\n}\n\n/** The placeholder set every server entry can use; shared with path resolvers. */\nexport function serverTemplateVars(config: ServerConfig): TemplateVars {\n return {\n id: config.id,\n label: config.label ?? config.id,\n port: config.port ?? '',\n host: bindHost(config.bind),\n displayHost: displayHost(config.bind),\n bind: config.bind,\n lanIp: lanAddress() ?? '127.0.0.1',\n cwd: resolveCwd(config.cwd),\n projectDir,\n dataRoot,\n home: os.homedir(),\n }\n}\n\nexport class Supervisor {\n private readonly sampler = new ProcessSampler()\n private readonly entries = new Map<string, Entry>()\n private readonly tickTimer: NodeJS.Timeout\n private disposed = false\n private lastStateSignature = ''\n\n constructor(\n private readonly store: ConfigStore,\n private readonly hub: EventHub,\n private readonly options: SupervisorOptions,\n ) {\n this.sync()\n this.store.onChange(() => this.sync())\n // A throw inside the tick must not become an unhandled rejection: on Node 24\n // that ends the process, and this timer is what keeps every server watched.\n this.tickTimer = setInterval(() => {\n void this.tick().catch((error: unknown) => logger.error('the supervisor tick failed', error))\n }, TICK_INTERVAL_MS)\n this.tickTimer.unref()\n }\n\n getState(): AppState {\n return this.options.buildState(this.views())\n }\n\n views(): ServerView[] {\n return [...this.entries.values()].map(entry => this.view(entry))\n }\n\n logLines(id: string, limit?: number): LogLine[] {\n return this.entries.get(id)?.logs.list(limit) ?? []\n }\n\n async startAll(options: { autostartOnly?: boolean } = {}): Promise<void> {\n const targets = [...this.entries.values()]\n .filter(entry => !options.autostartOnly || entry.config.autostart)\n .map(entry => entry.config)\n // Dependencies first; levels are still started concurrently.\n for (const config of orderByDependencies(targets)) {\n await this.start(config.id)\n }\n }\n\n async stopAll(): Promise<void> {\n const targets = orderByDependencies([...this.entries.values()].map(entry => entry.config)).reverse()\n for (const config of targets) {\n await this.stop(config.id)\n }\n }\n\n async start(id: string, options: { retry?: boolean } = {}): Promise<StartResult> {\n const entry = this.entries.get(id)\n if (!entry)\n return { ok: false, error: `unknown server \"${id}\"` }\n if (!entry.config.enabled)\n return { ok: false, error: `server \"${id}\" is disabled` }\n if (entry.status === 'running' || entry.status === 'starting' || entry.starting)\n return { ok: true }\n if (entry.stopping)\n return { ok: false, error: `server \"${id}\" is stopping` }\n\n // Set before the first await: two overlapping `start()` calls (a click and a\n // retry timer, say) would otherwise both reach `spawnEntry` and orphan one.\n entry.starting = true\n this.clearRetry(entry)\n if (!options.retry) {\n entry.restarts = 0\n entry.healthFailures = 0\n entry.unhealthySince = null\n }\n\n try {\n await this.startDependencies(entry)\n // A stop that arrived while we were waiting must win: the process must not\n // start and then be reported as stopped.\n if (entry.stopping || this.disposed)\n return { ok: false, error: `server \"${id}\" is stopping` }\n\n entry.lastError = null\n entry.status = 'starting'\n entry.health = entry.config.health.enabled ? 'unknown' : 'disabled'\n this.publishServer(entry)\n\n await this.runBootstrap(entry)\n if (entry.stopping)\n return { ok: false, error: `server \"${id}\" is stopping` }\n if (this.disposed)\n return { ok: false, error: 'supervisor is shutting down' }\n\n const conflict = await this.preflight(entry)\n if (conflict.kind === 'blocked')\n return { ok: false, error: conflict.error }\n if (conflict.kind === 'adopt')\n return this.adoptEntry(entry, conflict.pid)\n\n return this.spawnEntry(entry)\n }\n finally {\n entry.starting = false\n }\n }\n\n async stop(id: string): Promise<StartResult> {\n const entry = this.entries.get(id)\n if (!entry)\n return { ok: false, error: `unknown server \"${id}\"` }\n return this.stopEntry(entry)\n }\n\n async restart(id: string): Promise<StartResult> {\n await this.stop(id)\n return this.start(id)\n }\n\n /**\n * Frees the port this entry wants, by asking whatever listens on it to leave.\n *\n * The pid is never taken from a message: holders are listed again here, and a\n * listener this panel supervises is refused rather than killed — a port held by\n * a sibling entry is a configuration mistake, not a stray process. That also\n * keeps a stale `(pid 1234)` in an old banner from killing a recycled pid.\n */\n async freePort(id: string): Promise<FreePortResult & { error?: string }> {\n const entry = this.entries.get(id)\n const empty: FreePortResult & { error?: string } = { ok: false, port: null, terminated: [], forced: [], skipped: [], free: false }\n if (!entry)\n return { ...empty, error: `unknown server \"${id}\"` }\n\n const port = entry.config.port\n if (port === null)\n return { ...empty, error: `server \"${id}\" has no port configured` }\n if (entry.starting || entry.stopping)\n return { ...empty, port, error: `server \"${id}\" is busy — try again in a moment` }\n\n const { ours, foreign } = await this.portHolders(port)\n const holders = [...ours, ...foreign]\n\n if (holders.length === 0)\n return { ...empty, port, error: `nothing is listening on port ${port} any more` }\n if (foreign.length === 0) {\n const reason = `port ${port} is held by pid ${ours.join(', ')}, which this panel supervises — stop that server instead`\n return { ...empty, port, error: reason, skipped: ours }\n }\n\n this.log(entry, 'system', `freeing port ${port}: asking pid ${foreign.join(', ')} to stop`)\n const { stopped, forced } = await terminatePids(foreign, { graceMs: entry.config.stop.graceMs })\n if (forced.length > 0)\n this.log(entry, 'system', `pid ${forced.join(', ')} ignored SIGTERM and was killed`)\n\n // A held socket can take a moment to go away, so the port decides the outcome.\n const free = await this.waitForPortRelease(entry, port)\n if (free) {\n // The reason the entry was blocked is gone, so the banner goes too.\n if (entry.status === 'conflict') {\n entry.status = 'stopped'\n entry.lastError = null\n }\n entry.portState = 'free'\n this.log(entry, 'system', `port ${port} is free${ours.length > 0 ? ` (pid ${ours.join(', ')} left untouched)` : ''}`)\n this.publishServer(entry)\n }\n else {\n entry.lastError = `port ${port} is still in use after killing pid ${foreign.join(', ')}`\n this.log(entry, 'system', entry.lastError)\n this.publishServer(entry)\n }\n\n return { ok: true, port, terminated: stopped, forced, skipped: ours, free }\n }\n\n /**\n * Splits the port's listeners into the processes this panel owns and everyone\n * else. Only the foreign half may ever be signalled — a port held by a sibling\n * is a config mistake, not a stray process. That also keeps a stale `(pid 1234)`\n * in an old banner from killing a recycled pid.\n */\n private async portHolders(port: number): Promise<{ ours: number[], foreign: number[] }> {\n const supervised = this.supervisedPids()\n const holders = await listPortHolders(port)\n return {\n ours: holders.filter(pid => supervised.has(pid)),\n foreign: holders.filter(pid => !supervised.has(pid)),\n }\n }\n\n /** Pids of the child processes this panel owns, plus itself. */\n private supervisedPids(): Set<number> {\n const pids = new Set<number>([process.pid])\n for (const entry of this.entries.values()) {\n if (entry.pid !== null)\n pids.add(entry.pid)\n }\n return pids\n }\n\n /** The port's own answer, with the same short recheck the preflight uses. */\n private async waitForPortRelease(entry: Entry, port: number): Promise<boolean> {\n const freeOnAll = async (): Promise<boolean> => {\n const results = await Promise.all(this.occupancyHosts(entry).map(host => isPortFree(port, host)))\n return results.every(Boolean)\n }\n if (await freeOnAll())\n return true\n await delay(PORT_RELEASE_RECHECK_MS)\n return freeOnAll()\n }\n\n clearLogs(id: string): void {\n const entry = this.entries.get(id)\n if (!entry)\n return\n entry.logs.clear()\n this.publishServer(entry)\n }\n\n async dispose(): Promise<void> {\n this.disposed = true\n clearInterval(this.tickTimer)\n for (const entry of this.entries.values()) this.clearRetry(entry)\n await Promise.all([...this.entries.values()].map(entry => this.stopEntry(entry)))\n }\n\n /**\n * Starts whatever this server depends on and waits for it to accept\n * connections. A dependency that refuses to come up is logged and skipped\n * rather than blocking the dependent forever.\n */\n private async startDependencies(entry: Entry): Promise<void> {\n if (entry.config.dependsOn.length === 0)\n return\n\n for (const dependency of dependenciesOf(entry.config, this.store.servers)) {\n const target = this.entries.get(dependency.id)\n if (!target || !dependency.enabled)\n continue\n if (target.status === 'running' || target.child !== null)\n continue\n\n this.log(entry, 'system', `starting dependency \"${dependency.id}\" first`)\n await this.start(dependency.id)\n\n const deadline = Date.now() + dependency.health.startTimeoutMs\n const isReady = (): boolean => {\n const status = this.statusOf(target)\n if (status !== 'running')\n return false\n // A dependency that is listening but failing its check is not ready.\n return target.health !== 'unhealthy'\n }\n\n while (!isReady() && Date.now() < deadline) await delay(250)\n\n if (!isReady()) {\n const status = this.statusOf(target)\n this.log(entry, 'system', `dependency \"${dependency.id}\" is ${status}/${target.health} — starting anyway`)\n }\n }\n }\n\n /** Read through a method so TypeScript does not carry a stale narrowing across awaits. */\n private statusOf(entry: Entry): ServerStatus {\n return entry.status\n }\n\n /**\n * Summaries are rebuilt when history changes or every few seconds, because\n * `view()` runs on every state frame and the window math is O(events).\n */\n private summarizeHistory(entry: Entry): ServerView['history'] {\n const now = Date.now()\n const revision = this.options.history.revision\n const cached = entry.historyCache\n if (cached !== null && cached.revision === revision && now - cached.at < HISTORY_CACHE_MS)\n return cached.summary\n\n const runningSince = entry.child !== null && entry.startedAt !== null ? entry.startedAt : null\n const summary = this.options.history.summarize(entry.config.id, HISTORY_WINDOW_MS, now, runningSince)\n entry.historyCache = { revision, at: now, summary }\n return summary\n }\n\n private notify(entry: Entry, reason: NotificationReason, detail: string): void {\n this.options.notifications.notify({\n serverId: entry.config.id,\n label: entry.config.label ?? entry.config.id,\n reason,\n detail,\n })\n }\n\n private async stopEntry(entry: Entry): Promise<StartResult> {\n this.clearRetry(entry)\n entry.nextRetryAt = null\n\n // Set first: a start that is still bootstrapping (no child yet) checks this\n // after every await and aborts, instead of spawning behind our back.\n entry.stopping = true\n\n if (entry.child === null && entry.pid === null) {\n entry.stopping = false\n entry.status = 'stopped'\n this.publishServer(entry)\n return { ok: true }\n }\n\n entry.status = 'stopping'\n this.publishServer(entry)\n\n // An adopted successor is not our child: same shutdown, signalled by pid.\n const outcome = entry.child === null && entry.pid !== null\n ? await terminatePid(entry.pid, entry.config.stop)\n : await terminate(entry.child!, entry.config.stop)\n if (outcome === 'force-killed')\n this.log(entry, 'system', 'force-killed after grace period')\n\n const { port, stop } = entry.config\n if (stop.killPortHolders && port !== null) {\n // Only a stranger: a port held by a server this panel supervises is a config\n // mistake, not a leftover, and is never killed from here.\n const leftover = await killPortHolders(port, this.supervisedPids())\n if (leftover.length > 0)\n this.log(entry, 'system', `port ${port} was still held by pid ${leftover.join(', ')} — killed`)\n }\n\n entry.stopping = false\n entry.child = null\n entry.pid = null\n entry.adopted = false\n entry.status = 'stopped'\n this.log(entry, 'system', 'stopped')\n this.publishServer(entry)\n return { ok: true }\n }\n\n private createEntry(config: ServerConfig): Entry {\n return {\n config,\n status: 'stopped',\n health: config.health.enabled ? 'unknown' : 'disabled',\n portState: 'unknown',\n child: null,\n pid: null,\n startedAt: null,\n exitCode: null,\n exitSignal: null,\n restarts: 0,\n lastError: null,\n nextRetryAt: null,\n retryTimer: null,\n healthFailures: 0,\n unhealthySince: null,\n lastProbeAt: 0,\n lastOccupancyProbeAt: 0,\n probing: false,\n adopted: false,\n starting: false,\n stopping: false,\n bootstrapDone: !config.bootstrap,\n logs: new LogBuffer(config.logBufferLines),\n responseMs: null,\n resources: null,\n resourcesSampledAt: 0,\n historyCache: null,\n }\n }\n\n private sync(): void {\n const wanted = new Map(this.store.servers.map(server => [server.id, server]))\n\n for (const [id, entry] of [...this.entries]) {\n const config = wanted.get(id)\n if (!config) {\n this.entries.delete(id)\n void this.stopEntry(entry).catch((error: unknown) => {\n logger.error(`could not stop the removed server ${id}`, error)\n })\n continue\n }\n const bufferChanged = entry.config.logBufferLines !== config.logBufferLines\n entry.config = config\n if (bufferChanged) {\n const kept = entry.logs.list(config.logBufferLines)\n entry.logs = new LogBuffer(config.logBufferLines)\n entry.logs.extend(kept)\n }\n if (!config.enabled && this.isActive(entry)) {\n void this.stopEntry(entry).catch((error: unknown) => {\n logger.error(`could not stop the disabled server ${id}`, error)\n })\n }\n }\n\n for (const [id, config] of wanted) {\n if (!this.entries.has(id))\n this.entries.set(id, this.createEntry(config))\n }\n\n this.publishState()\n }\n\n private isActive(entry: Entry): boolean {\n // An adopted successor has no child of ours but is very much running, so it has to\n // count here — otherwise disabling the entry would leave it serving.\n return entry.child !== null || entry.pid !== null || entry.adopted || entry.status === 'backoff'\n }\n\n /** Loopback first, then the configured address, so a custom bind is still probed. */\n private probeHosts(entry: Entry): string[] {\n const primary = '127.0.0.1'\n const configured = displayHost(entry.config.bind)\n return configured === primary ? [primary] : [primary, configured]\n }\n\n /**\n * Where a port can actually be observed for this entry. A server bound to a\n * specific address is not reachable on loopback, and a `lan` bind is reachable\n * there *and* on this machine's LAN address.\n */\n private occupancyHosts(entry: Entry): string[] {\n const configured = bindHost(entry.config.bind)\n const candidates = configured === '0.0.0.0'\n ? ['127.0.0.1', lanAddress() ?? '127.0.0.1']\n : [configured, '127.0.0.1']\n return [...new Set(candidates)]\n }\n\n /** True when the port accepts a connection on any of the entry's addresses. */\n private async portAccepts(entry: Entry, port: number, timeoutMs: number): Promise<boolean> {\n const results = await Promise.all(this.occupancyHosts(entry).map(host => probePort(host, port, timeoutMs)))\n return results.some(Boolean)\n }\n\n /**\n * The port holder that is this entry's own detached successor — a program that\n * restarted itself leaves a process behind, and that process is the *same server*,\n * not a stranger to kill.\n *\n * The environment marker is authoritative where the platform can read it (Linux,\n * macOS). Failing that — Windows has no per-process environment at all — the entry's\n * own argv answers, which a successor keeps unless it re-execs under a different\n * image. Ambiguity is not resolved by guessing: two holders that both look like this\n * entry means we do not know which one is ours, so we act on neither.\n */\n private async ownPortHolder(entry: Entry, holders: number[]): Promise<number | null> {\n const spawn = this.resolveSpawn(entry)\n const candidates = await identifyHolders(entry.config.id, spawn, holders)\n\n if (candidates.length > 1) {\n this.log(entry, 'system', `pid ${candidates.join(', ')} all look like this entry: refusing to guess which is ours, set onPortConflict to \"kill\" to clear the port anyway`)\n return null\n }\n\n return candidates[0] ?? null\n }\n\n private async preflight(entry: Entry): Promise<PreflightConflict> {\n const port = entry.config.port\n if (port === null)\n return { kind: 'free' }\n\n // A listener that was just closed can still complete a handshake for a few\n // milliseconds, which is exactly the window a fast restart lands in — so a\n // busy-looking port gets a second look before it is treated as a conflict.\n // The configured address first: a server bound to a LAN ip is not \"free\" just\n // because nothing holds it on loopback.\n const hosts = this.occupancyHosts(entry)\n const freeOnAll = async (): Promise<boolean> => {\n const results = await Promise.all(hosts.map(host => isPortFree(port, host)))\n return results.every(Boolean)\n }\n\n let free = await freeOnAll()\n if (!free) {\n await delay(PORT_RELEASE_RECHECK_MS)\n free = await freeOnAll()\n }\n\n entry.portState = free ? 'free' : 'in-use'\n if (free)\n return { kind: 'free' }\n\n // One split serves every policy: the supervised half is never a target, and the\n // foreign half is where our own detached successor hides.\n const { ours, foreign } = await this.portHolders(port)\n const holders = [...ours, ...foreign]\n const own = await this.ownPortHolder(entry, foreign)\n const suffix = holders.length > 0 ? ` (pid ${holders.join(', ')})` : ''\n\n // `kill` asks for the port outright: whoever holds it goes, this entry's own\n // successor included. It never touches our own process tree — a port held by\n // the panel or a sibling stays a config mistake, exactly as `follow`/`reclaim`.\n if (entry.config.onPortConflict === 'kill') {\n if (ours.length > 0) {\n entry.status = 'conflict'\n entry.lastError = `port ${port} is held by pid ${ours.join(', ')}, which this panel supervises — stop that server instead`\n this.log(entry, 'system', `${entry.lastError} — not starting (onPortConflict: kill)`)\n this.publishServer(entry)\n return { kind: 'blocked', error: entry.lastError }\n }\n\n if (foreign.length === 0) {\n // The probe says busy but no listener could be listed: let the start decide.\n this.log(entry, 'system', `port ${port} looks busy but no listener could be found — starting anyway`)\n return { kind: 'free' }\n }\n\n this.log(entry, 'system', `port ${port} is held by pid ${foreign.join(', ')} — onPortConflict: kill, stopping the holder`)\n const { forced } = await terminatePids(foreign, { graceMs: entry.config.stop.graceMs })\n if (forced.length > 0)\n this.log(entry, 'system', `pid ${forced.join(', ')} ignored SIGTERM and was killed`)\n\n await delay(PORT_RELEASE_RECHECK_MS)\n if (await freeOnAll()) {\n entry.portState = 'free'\n return { kind: 'free' }\n }\n entry.status = 'conflict'\n entry.lastError = `port ${port} is still in use after killing pid ${foreign.join(', ')}`\n this.log(entry, 'system', entry.lastError)\n this.publishServer(entry)\n return { kind: 'blocked', error: entry.lastError }\n }\n\n // A detached restart of this same server. Following it keeps whatever the program\n // set up (at the cost of its output, which belongs to whoever spawned it);\n // reclaiming the port buys back a fully supervised process instead.\n if (own !== null && entry.config.onPortConflict === 'follow')\n return { kind: 'adopt', pid: own }\n\n if (own !== null && entry.config.onPortConflict === 'reclaim') {\n this.log(entry, 'system', `port ${port} is held by pid ${own}, a detached restart of this entry — replacing it with a supervised process`)\n const { forced } = await terminatePids([own], { graceMs: entry.config.stop.graceMs })\n if (forced.length > 0)\n this.log(entry, 'system', `pid ${forced.join(', ')} ignored SIGTERM and was killed`)\n await delay(PORT_RELEASE_RECHECK_MS)\n if (await freeOnAll()) {\n entry.portState = 'free'\n return { kind: 'free' }\n }\n entry.status = 'conflict'\n entry.lastError = `port ${port} is still in use after replacing pid ${own}`\n this.log(entry, 'system', entry.lastError)\n this.publishServer(entry)\n return { kind: 'blocked', error: entry.lastError }\n }\n\n const hint = own !== null\n ? ` — pid ${own} is a detached restart of this entry: set onPortConflict to \"follow\" to adopt it, \"reclaim\" to replace it with a supervised process, or \"kill\" to stop whatever holds the port`\n : ''\n\n // `follow` and `reclaim` refine `block`: never a stranger's port.\n if (entry.config.onPortConflict !== 'warn') {\n entry.status = 'conflict'\n entry.lastError = `port ${port} is already in use${suffix}${hint}`\n this.log(entry, 'system', `${entry.lastError} — not starting (onPortConflict: ${entry.config.onPortConflict})`)\n this.publishServer(entry)\n return { kind: 'blocked', error: entry.lastError }\n }\n\n this.log(entry, 'system', `warning: port ${port} is already in use${suffix}${hint} — starting anyway`)\n return { kind: 'free' }\n }\n\n /**\n * Takes over a detached successor: no spawn, no duplicate. The pid is supervised\n * from here on (liveness, health probe, resources, stop), while its output stays\n * wherever it was redirected.\n */\n private adoptEntry(entry: Entry, pid: number): StartResult {\n entry.adopted = true\n entry.pid = pid\n entry.child = null\n entry.status = 'running'\n entry.health = entry.config.health.enabled ? 'unknown' : 'disabled'\n entry.startedAt = Date.now()\n entry.lastError = null\n this.log(entry, 'system', `adopted pid ${pid}: a detached restart of this entry is already serving port ${entry.config.port}`)\n this.publishServer(entry)\n return { ok: true }\n }\n\n /** An adopted successor disappeared: fall back to the normal lifecycle. */\n private handleAdoptedExit(entry: Entry): void {\n if (entry.pid !== null)\n this.sampler.forget(entry.pid)\n const ranForMs = entry.startedAt === null ? 0 : Date.now() - entry.startedAt\n entry.adopted = false\n entry.pid = null\n entry.resources = null\n entry.responseMs = null\n entry.exitCode = null\n entry.exitSignal = null\n this.options.history.record(entry.config.id, {\n type: 'exit',\n detail: 'adopted process exited',\n runtimeMs: ranForMs,\n })\n this.log(entry, 'system', `the adopted process is gone after ${Math.max(1, Math.round(ranForMs / 1000))}s`)\n this.afterExit(entry, 'adopted process exited', ranForMs, false)\n }\n\n private async runBootstrap(entry: Entry): Promise<void> {\n const spec = entry.config.bootstrap\n if (!spec || (spec.runOnce && entry.bootstrapDone))\n return\n\n const vars = this.buildVars(entry)\n const cwd = resolveCwd(entry.config.cwd)\n const args = resolveTemplates(spec.args, vars)\n this.log(entry, 'system', `bootstrap: ${spec.command} ${args.join(' ')}`)\n\n const splitter = new LineSplitter((_stream, text) => {\n if (text.trim().length > 0)\n this.log(entry, 'system', `[bootstrap] ${text}`)\n })\n\n const child = spawn(resolveCommand(spec.command, cwd, projectDir), args, {\n cwd,\n env: { ...process.env, ...resolveRecord(spec.env, vars) },\n stdio: ['ignore', 'pipe', 'pipe'],\n windowsHide: true,\n })\n child.stdout?.on('data', chunk => splitter.push('stdout', chunk))\n child.stderr?.on('data', chunk => splitter.push('stderr', chunk))\n\n const code = await new Promise<number | null>((resolve) => {\n const timer = setTimeout(() => {\n this.log(entry, 'system', `bootstrap timed out after ${spec.timeoutMs}ms`)\n try {\n child.kill('SIGKILL')\n }\n catch {\n // already gone\n }\n }, spec.timeoutMs)\n child.once('exit', (exitCode) => {\n clearTimeout(timer)\n resolve(exitCode)\n })\n child.once('error', (error) => {\n clearTimeout(timer)\n this.log(entry, 'system', `bootstrap failed: ${(error as Error).message}`)\n resolve(null)\n })\n })\n\n entry.bootstrapDone = true\n if (code === 0)\n this.log(entry, 'system', 'bootstrap finished')\n else if (code !== null)\n this.log(entry, 'system', `bootstrap exited with code ${code} — continuing anyway`)\n }\n\n /**\n * Everything `spawn` needs for an entry: the resolved image, the expanded argv, the\n * cwd and the environment. The argv is also what the preflight recognizes the entry's\n * own detached successor by, so spawn and identification must never resolve it twice\n * with two different rules.\n */\n private resolveSpawn(entry: Entry): SpawnInfo & { env: Record<string, string>, loggedArgs: string[] } {\n const vars = this.buildVars(entry)\n const cwd = resolveCwd(entry.config.cwd)\n const command = resolveCommand(entry.config.command, cwd, projectDir)\n\n // A machine-local env file is layered over the tracked config and also feeds\n // `${VAR}` in args/env, so secrets stay out of servers.config.json.\n let fileEnv: Record<string, string> = {}\n if (entry.config.envFile.length > 0) {\n const file = resolveEnvFilePath(entry.config.envFile, cwd)\n const loaded = loadEnvFile(file)\n if (loaded.error !== null)\n this.log(entry, 'system', `env file ${file} could not be read: ${loaded.error}`)\n else if (Object.keys(loaded.env).length > 0)\n this.log(entry, 'system', `env file ${file} (${Object.keys(loaded.env).length} vars)`)\n fileEnv = loaded.env\n }\n\n const expansionVars: Record<string, string | undefined> = { ...process.env, ...fileEnv }\n // A data env's value is a directory this entry owns, so it is normalized to one\n // absolute native path: a config writes `{projectDir}/data`, and a Windows run would\n // otherwise hand the process a mixed `D:\\…\\app/data` that Backups has to re-resolve.\n const dataEnvs = resolveRecord(entry.config.dataEnvs, vars)\n const env: Record<string, string> = {\n // `envFile` is the machine-local layer, so it overrides the tracked `env`.\n ...expandEnvRecord(resolveRecord(entry.config.env, vars), expansionVars),\n ...fileEnv,\n // Data envs win over `env`: their value is the directory that gets backed\n // up, so the process has to be pointed at exactly that path.\n ...expandEnvRecord(dataEnvs, expansionVars),\n HHOSTED_SERVER_ID: entry.config.id,\n HHOSTED_CONTROL_PORT: String(this.options.control.port),\n }\n for (const key of Object.keys(dataEnvs))\n env[key] = path.resolve(cwd, env[key]!)\n\n return {\n command,\n args: expandEnvList(resolveTemplates(entry.config.args, vars), expansionVars),\n env,\n cwd,\n // Logged *before* `${VAR}` expansion: an argument like `${API_TOKEN}` must not\n // land in the ring buffer, the rotated files, SSE or Telegram.\n loggedArgs: resolveTemplates(entry.config.args, vars),\n }\n }\n\n private spawnEntry(entry: Entry): StartResult {\n const { command, args, env, cwd, loggedArgs } = this.resolveSpawn(entry)\n\n this.log(entry, 'system', `start: ${command} ${loggedArgs.join(' ')}`)\n\n let child: ChildProcess\n try {\n child = spawnManaged({ command, args, cwd, env })\n }\n catch (error) {\n entry.status = 'crashed'\n entry.lastError = (error as Error).message\n this.log(entry, 'system', `spawn failed: ${entry.lastError}`)\n this.publishServer(entry)\n return { ok: false, error: entry.lastError }\n }\n\n entry.child = child\n entry.pid = child.pid ?? null\n entry.startedAt = Date.now()\n this.options.history.record(entry.config.id, {\n type: 'start',\n detail: `${command} ${args.join(' ')}`.trim(),\n })\n entry.exitCode = null\n entry.exitSignal = null\n entry.lastProbeAt = 0\n entry.healthFailures = 0\n entry.unhealthySince = null\n this.publishServer(entry)\n\n const stdout = new LineSplitter((stream, text) => this.log(entry, stream, text))\n const stderr = new LineSplitter((stream, text) => this.log(entry, stream, text))\n child.stdout?.on('data', chunk => stdout.push('stdout', chunk))\n child.stderr?.on('data', chunk => stderr.push('stderr', chunk))\n\n child.once('error', (error) => {\n entry.lastError = (error as Error).message\n this.log(entry, 'system', `process error: ${entry.lastError}`)\n stdout.flush('stdout')\n stderr.flush('stderr')\n this.handleExit(entry, child, null, null)\n })\n\n child.once('exit', (code, signal) => {\n stdout.flush('stdout')\n stderr.flush('stderr')\n this.handleExit(entry, child, code, signal)\n })\n\n void this.awaitReadiness(entry, child)\n return { ok: true }\n }\n\n /** One probe using the configured mode (TCP or HTTP), with timing. */\n private async probeEntryHealth(entry: Entry): Promise<{ healthy: boolean, ms: number, detail: string }> {\n const { port, health } = entry.config\n if (port === null)\n return { healthy: true, ms: 0, detail: 'no port configured' }\n\n return probeHealth({\n mode: health.mode,\n hosts: this.probeHosts(entry),\n port,\n timeoutMs: health.timeoutMs,\n http: health.http,\n })\n }\n\n private async awaitReadiness(entry: Entry, child: ChildProcess): Promise<void> {\n const { port, health } = entry.config\n\n if (port === null) {\n if (entry.child !== child || entry.status !== 'starting')\n return\n entry.status = 'running'\n entry.health = health.enabled ? 'unknown' : 'disabled'\n this.log(entry, 'system', 'running (no port configured; readiness assumed on spawn)')\n this.publishServer(entry)\n return\n }\n\n const deadline = Date.now() + health.startTimeoutMs\n while (Date.now() < deadline) {\n if (entry.child !== child || entry.status !== 'starting' || this.disposed)\n return\n if (await this.portAccepts(entry, port, Math.min(health.timeoutMs, 1000))) {\n entry.portState = 'in-use'\n entry.health = health.enabled ? 'healthy' : 'disabled'\n entry.status = 'running'\n entry.lastProbeAt = Date.now()\n this.log(entry, 'system', `accepting connections on port ${port}`)\n this.publishServer(entry)\n return\n }\n await delay(300)\n }\n\n if (entry.child === child && entry.status === 'starting') {\n entry.status = 'running'\n entry.health = 'unhealthy'\n entry.unhealthySince = Date.now()\n this.log(entry, 'system', `no connection on port ${port} after ${health.startTimeoutMs}ms — supervising anyway`)\n this.publishServer(entry)\n }\n }\n\n private handleExit(entry: Entry, child: ChildProcess, code: number | null, signal: NodeJS.Signals | null): void {\n if (entry.child !== child)\n return\n if (entry.pid !== null)\n this.sampler.forget(entry.pid)\n entry.child = null\n entry.pid = null\n entry.adopted = false\n entry.resources = null\n entry.responseMs = null\n entry.exitCode = code\n entry.exitSignal = signal\n\n // Both null means the process never got off the ground — a missing command,\n // for instance — so its error is more useful than \"code null\".\n const neverStarted = code === null && signal === null && entry.lastError !== null\n const detail = neverStarted\n ? entry.lastError!\n : signal !== null ? `signal ${signal}` : `code ${code}`\n const ranForMs = entry.startedAt === null ? 0 : Date.now() - entry.startedAt\n\n // Recorded for *every* exit, not only the ones that end in `crashed`: the\n // rolling window (crashes, uptime, last exit) is built from these events.\n this.options.history.record(entry.config.id, {\n type: 'exit',\n detail,\n runtimeMs: ranForMs,\n })\n\n this.afterExit(entry, detail, ranForMs, neverStarted)\n }\n\n /**\n * What happens once a process is gone, whether it was a child we spawned or a\n * detached successor we adopted: back off and retry, or record the crash.\n */\n private afterExit(entry: Entry, detail: string, ranForMs: number, neverStarted: boolean): void {\n const ranFor = `${Math.max(1, Math.round(ranForMs / 1000))}s`\n\n if (entry.stopping) {\n entry.status = 'stopped'\n this.publishServer(entry)\n return\n }\n\n const restart = entry.config.restart\n if (ranForMs >= restart.resetAfterMs)\n entry.restarts = 0\n\n this.log(entry, 'system', neverStarted ? `did not start: ${detail}` : `exited with ${detail} after ${ranFor}`)\n entry.lastError = neverStarted ? detail : `exited with ${detail}`\n\n if (restart.enabled && entry.restarts < restart.maxRetries) {\n entry.restarts += 1\n const backoffMs = computeBackoff(entry.restarts, restart)\n entry.status = 'backoff'\n entry.nextRetryAt = Date.now() + backoffMs\n this.log(entry, 'system', `restart ${entry.restarts}/${restart.maxRetries} in ${backoffMs}ms`)\n entry.retryTimer = setTimeout(() => {\n entry.retryTimer = null\n void this.start(entry.config.id, { retry: true }).catch((error: unknown) => {\n logger.error(`could not restart ${entry.config.id}`, error)\n })\n }, backoffMs)\n entry.retryTimer.unref()\n }\n else {\n entry.status = 'crashed'\n entry.nextRetryAt = null\n entry.lastError = restart.enabled\n ? `gave up after ${restart.maxRetries} retries (${detail})`\n : `${detail} (automatic restart disabled)`\n this.log(entry, 'system', entry.lastError)\n this.options.history.record(entry.config.id, {\n type: 'crash',\n detail: entry.lastError,\n runtimeMs: ranForMs,\n })\n this.notify(entry, 'crash', entry.lastError)\n }\n\n this.publishServer(entry)\n }\n\n private async tick(): Promise<void> {\n if (this.disposed)\n return\n const now = Date.now()\n\n await this.options.hostMonitor.tick(now)\n await this.sampleResources(now)\n\n // Probes run concurrently: one slow server must not delay the others' health.\n await Promise.allSettled([...this.entries.values()].map(entry => this.probeEntry(entry, now)))\n\n for (const entry of this.entries.values()) {\n // An adopted process is not our child, so nothing tells us it died.\n if (entry.adopted && entry.pid !== null && !isProcessAlive(entry.pid)) {\n this.handleAdoptedExit(entry)\n continue\n }\n if (await this.enforceMemoryLimit(entry))\n continue\n if (this.shouldForceRestart(entry, now)) {\n await this.restart(entry.config.id)\n continue\n }\n if (entry.status === 'backoff' && entry.nextRetryAt !== null && now >= entry.nextRetryAt && entry.retryTimer === null) {\n void this.start(entry.config.id, { retry: true }).catch((error: unknown) => {\n logger.error(`could not restart ${entry.config.id}`, error)\n })\n }\n }\n\n this.publishState()\n }\n\n /** One scan of /proc covers every server; only live processes are sampled. */\n private async sampleResources(now: number): Promise<void> {\n const due = [...this.entries.values()].filter(entry =>\n entry.pid !== null\n && (entry.child !== null || entry.adopted)\n && now - entry.resourcesSampledAt >= RESOURCE_SAMPLE_INTERVAL_MS)\n if (due.length === 0)\n return\n\n for (const entry of due) entry.resourcesSampledAt = now\n try {\n const samples = await this.sampler.sampleMany(due.map(entry => entry.pid!))\n for (const entry of due) entry.resources = samples.get(entry.pid!) ?? null\n }\n catch {\n // Sampling is best effort; a missing /proc must not break supervision.\n }\n }\n\n private async probeEntry(entry: Entry, now: number): Promise<void> {\n const { port, health } = entry.config\n if (port === null || entry.probing)\n return\n\n entry.probing = true\n try {\n // Occupancy is shown even while stopped, so it keeps its own slower cadence\n // instead of sharing (and being skipped by) the health probe's timer.\n if (now - entry.lastOccupancyProbeAt >= PORT_STATE_INTERVAL_MS) {\n entry.lastOccupancyProbeAt = now\n const accepting = await this.portAccepts(entry, port, health.timeoutMs)\n entry.portState = accepting ? 'in-use' : 'free'\n }\n\n if (entry.status !== 'running' || !health.enabled)\n return\n if (now - entry.lastProbeAt < health.intervalMs)\n return\n\n const probe = await this.probeEntryHealth(entry)\n entry.lastProbeAt = now\n entry.responseMs = probe.ms\n entry.portState = probe.healthy ? 'in-use' : entry.portState\n\n if (probe.healthy) {\n if (entry.health === 'unhealthy') {\n this.log(entry, 'system', `${probe.detail} — healthy again (${probe.ms}ms)`)\n this.options.history.record(entry.config.id, { type: 'recovered', detail: probe.detail })\n this.notify(entry, 'recovered', probe.detail)\n }\n entry.health = 'healthy'\n entry.healthFailures = 0\n entry.unhealthySince = null\n return\n }\n\n entry.healthFailures += 1\n if (entry.healthFailures >= health.unhealthyThreshold) {\n if (entry.unhealthySince === null) {\n entry.unhealthySince = now\n this.log(entry, 'system', `unhealthy: ${probe.detail} (${entry.healthFailures} failed probes) — warning only`)\n this.options.history.record(entry.config.id, { type: 'unhealthy', detail: probe.detail })\n this.notify(entry, 'unhealthy', probe.detail)\n }\n entry.health = 'unhealthy'\n }\n }\n finally {\n entry.probing = false\n }\n }\n\n private async enforceMemoryLimit(entry: Entry): Promise<boolean> {\n const limit = entry.config.resources.maxRssBytes\n const rss = entry.resources?.rssBytes ?? null\n if (limit <= 0 || rss === null || entry.child === null || entry.status !== 'running' || rss <= limit)\n return false\n\n const detail = `process tree uses ${Math.round(rss / 1024 / 1024)}MB, over the ${Math.round(limit / 1024 / 1024)}MB limit`\n this.log(entry, 'system', `${detail} — restarting`)\n this.options.history.record(entry.config.id, { type: 'forced-restart', detail })\n this.notify(entry, 'rss', detail)\n await this.restart(entry.config.id)\n return true\n }\n\n private shouldForceRestart(entry: Entry, now: number): boolean {\n const { port, health } = entry.config\n if (port === null || entry.status !== 'running' || entry.health !== 'unhealthy')\n return false\n if (entry.unhealthySince === null || health.forceRestartAfterMs <= 0)\n return false\n if (now - entry.unhealthySince < health.forceRestartAfterMs)\n return false\n\n this.log(entry, 'system', `unhealthy for ${health.forceRestartAfterMs}ms — forcing a restart`)\n this.options.history.record(entry.config.id, { type: 'forced-restart', detail: `health check stayed unhealthy` })\n this.notify(entry, 'forced-restart', `unhealthy for ${Math.round(health.forceRestartAfterMs / 1000)}s`)\n return true\n }\n\n private clearRetry(entry: Entry): void {\n if (entry.retryTimer !== null) {\n clearTimeout(entry.retryTimer)\n entry.retryTimer = null\n }\n }\n\n private buildVars(entry: Entry): TemplateVars {\n return serverTemplateVars(entry.config)\n }\n\n private view(entry: Entry): ServerView {\n const config = entry.config\n const host = displayHost(config.bind)\n return {\n id: config.id,\n config,\n bindHost: bindHost(config.bind),\n url: config.port === undefined || config.port === null ? null : `http://${host}:${config.port}`,\n status: entry.status,\n health: entry.health,\n portState: entry.portState,\n pid: entry.pid,\n // Omitted when false: an optional ArkType property rejects an explicit undefined.\n ...(entry.adopted ? { adopted: true } : {}),\n startedAt: entry.startedAt,\n exitCode: entry.exitCode,\n exitSignal: entry.exitSignal,\n restarts: entry.restarts,\n maxRetries: config.restart.maxRetries,\n lastError: entry.lastError,\n nextRetryAt: entry.nextRetryAt,\n unhealthySince: entry.unhealthySince,\n bufferedLines: entry.logs.size,\n history: this.summarizeHistory(entry),\n responseMs: entry.responseMs,\n resources: entry.resources,\n }\n }\n\n private log(entry: Entry, stream: LogStream, text: string): void {\n const line: LogLine = { ts: Date.now(), stream, text }\n entry.logs.push(line)\n this.options.logFiles.append(entry.config.id, line)\n this.hub.publish({ type: 'log', ts: line.ts, serverId: entry.config.id, lines: [line] })\n if (stream === 'system')\n logger.debug(`[${entry.config.id}] ${text}`)\n }\n\n private publishServer(entry: Entry): void {\n if (!this.entries.has(entry.config.id))\n return\n this.hub.publish({\n type: 'server',\n ts: Date.now(),\n serverId: entry.config.id,\n server: this.view(entry),\n })\n }\n\n private publishState(): void {\n const state = this.getState()\n const signature = [\n state.configError ?? '',\n ...state.servers.map(server => [\n server.id,\n server.status,\n server.health,\n server.portState,\n server.pid,\n server.restarts,\n server.nextRetryAt,\n server.lastError,\n server.bufferedLines,\n // A new resource sample *is* news: the UI builds its charts by sampling\n // these frames, so leaving them out of the signature means a fleet where\n // nothing structural changes emits no frames at all — and every graph\n // stays empty until something else moves.\n server.responseMs,\n server.resources?.sampledAt,\n ].join(':')),\n ].join('|')\n\n if (signature === this.lastStateSignature)\n return\n this.lastStateSignature = signature\n this.hub.publish({ type: 'state', ts: Date.now(), state })\n }\n}\n","/**\n * Names that only ever hold build output or installed dependencies — the things\n * a `.gitignore` almost always lists.\n *\n * A backup walks a declared data directory and copies whatever it finds; on a\n * project directory that is mostly `node_modules` and framework caches, which\n * bloats the archive with content nobody needs to restore. An entry with\n * `backupIgnoreGenerated` (on by default) skips these.\n *\n * Matching is by exact name, on any segment, at any depth: `dist/` and\n * `app/node_modules/` are generated, `distributed/` and `my-node_modules/` are\n * not.\n *\n * `.git` is deliberately absent however much a `.gitignore` would not list it:\n * a package manager can reinstall `node_modules`, but nobody can restore a\n * commit that was never pushed.\n */\n\n/** Whole directories that are regenerated, never authored. */\nexport const GENERATED_DIRS: readonly string[] = [\n // Installed dependencies and package-manager stores.\n 'node_modules',\n 'bower_components',\n 'jspm_packages',\n '.yarn',\n '.pnpm',\n '.pnpm-store',\n '.npm',\n // Framework and bundler output.\n '.next',\n '.nuxt',\n '.svelte-kit',\n '.astro',\n '.output',\n '.vercel',\n '.netlify',\n '.angular',\n 'dist',\n 'build',\n 'out',\n // Caches.\n '.cache',\n '.parcel-cache',\n '.turbo',\n '.vite',\n '.rollup.cache',\n '.swc',\n // Test and coverage output.\n 'coverage',\n '.nyc_output',\n '__pycache__',\n '.pytest_cache',\n '.mypy_cache',\n '.ruff_cache',\n '.tox',\n // Other language toolchains.\n 'target',\n '.gradle',\n '.dart_tool',\n]\n\n/** One-off files worth skipping, by exact name. */\nexport const GENERATED_FILES: readonly string[] = [\n '.DS_Store',\n 'Thumbs.db',\n 'desktop.ini',\n '.eslintcache',\n '.stylelintcache',\n 'npm-debug.log',\n 'yarn-error.log',\n 'pnpm-debug.log',\n]\n\nconst DIR_NAME_SET = new Set(GENERATED_DIRS)\nconst FILE_NAME_SET = new Set(GENERATED_FILES)\n\n/**\n * True when a path *relative to the declared root* looks generated. Accepts\n * either separator, so a `path.relative` result can be passed straight in.\n */\nexport function isGeneratedPath(relative: string): boolean {\n const segments = relative.split(/[\\\\/]+/).filter(segment => segment.length > 0 && segment !== '.')\n if (segments.length === 0)\n return false\n if (FILE_NAME_SET.has(segments[segments.length - 1]!))\n return true\n return segments.some(segment => DIR_NAME_SET.has(segment))\n}\n","import type { BackupFile, BackupPath, BackupsConfig, ServerConfig } from '#src/shared/contracts'\nimport fs from 'node:fs'\nimport os from 'node:os'\nimport path from 'node:path'\nimport process from 'node:process'\nimport { parseConfig } from '#src/config/parse'\nimport { writeFileAtomic } from '#src/helpers/atomic'\nimport { expandEnv } from '#src/helpers/env-file'\nimport { dataRoot, projectDir, resolveUserPath } from '#src/helpers/paths'\nimport { resolveTemplate } from '#src/helpers/template'\nimport { createZip, extractZip, isInvalidPassword, isZipArchive, listZip } from '#src/providers/archive'\nimport { serverTemplateVars } from '#src/services/supervisor'\nimport { isGeneratedPath } from '#src/shared/generated'\n\nconst MANIFEST = 'manifest.json'\nconst ALLOWED_ROOTS = new Set(['config', 'secrets', 'tls', 'data'])\n/** Every archive this service writes is a zip, encrypted or not. */\nconst SUFFIX = '.zip'\n\n/**\n * Structural allowlist for archive entries. A regex alone is not enough: `..`\n * is made of allowed characters, so segments are checked explicitly and any\n * entry that could resolve outside the staging directory aborts the restore.\n */\nexport function isSafeArchiveEntry(entry: string): boolean {\n if (entry.startsWith('/') || entry.includes('\\0'))\n return false\n\n const cleaned = entry.replace(/^\\.\\//, '').replace(/\\/+$/, '')\n if (cleaned.length === 0)\n return true\n if (cleaned === 'manifest.json')\n return true\n\n const segments = cleaned.split('/')\n if (segments.some(segment => segment.length === 0 || segment === '.' || segment === '..'))\n return false\n if (!ALLOWED_ROOTS.has(segments[0]!))\n return false\n return segments.every(segment => /^[\\w.-]+$/.test(segment))\n}\n\n/** True when `child` is `parent` itself or lives underneath it. */\nexport function isInside(parent: string, child: string): boolean {\n if (parent === child)\n return true\n // `path.relative` is case-insensitive on Windows, and empty for a case-only\n // difference — which is still the same directory.\n const relative = path.relative(parent, child)\n return !path.isAbsolute(relative) && (relative.length === 0 || !relative.startsWith('..'))\n}\n\ninterface DeclaredPath {\n path: string\n origin: string\n depth: number\n order: number\n ignoreGenerated: boolean\n}\n\n/**\n * Every path a backup should capture: the global list, then each server's\n * `backupPaths` and the values of its `dataEnvs`. A path already covered by a\n * declared parent is reported but not captured, so an entry only has to name\n * the shallowest directory it cares about.\n */\nexport function resolveBackupPaths(servers: ServerConfig[], includePaths: string[] = []): BackupPath[] {\n const declared: DeclaredPath[] = []\n const globalVars = { projectDir, dataRoot, home: os.homedir() }\n\n const add = (value: string, origin: string, vars: Record<string, string | number>, ignoreGenerated: boolean): void => {\n if (value.trim().length === 0)\n return\n // Normalized, so a trailing slash or a doubled one cannot defeat the\n // parent/child comparison below.\n const resolved = path.normalize(resolveUserPath(expandEnv(resolveTemplate(value, vars), process.env)))\n declared.push({\n path: resolved,\n origin,\n depth: path.normalize(resolved).split(path.sep).filter(Boolean).length,\n order: declared.length,\n ignoreGenerated,\n })\n }\n\n // A global extra path is named by hand, so it is captured as it stands.\n for (const value of includePaths) add(value, 'global', globalVars, false)\n\n for (const config of servers) {\n const vars = serverTemplateVars(config)\n const ignoreGenerated = config.backupIgnoreGenerated !== false\n for (const [name, value] of Object.entries(config.dataEnvs)) add(value, `${config.id}:${name}`, vars, ignoreGenerated)\n for (const value of config.backupPaths) add(value, `${config.id}:backupPaths`, vars, ignoreGenerated)\n }\n\n // Shallowest first, so a parent always absorbs its descendants whatever order\n // the config declared them in; ties keep declaration order.\n const sorted = [...declared].sort((a, b) => a.depth - b.depth || a.order - b.order)\n\n return sorted.map((entry, index) => {\n const parent = sorted.slice(0, index).find(candidate => isInside(candidate.path, entry.path))\n if (parent === undefined)\n return { path: entry.path, origin: entry.origin, included: true, note: null, ignoreGenerated: entry.ignoreGenerated }\n const note = parent.path === entry.path\n ? `already declared by ${parent.origin}`\n : `covered by ${parent.path}`\n return { path: entry.path, origin: entry.origin, included: false, note, ignoreGenerated: entry.ignoreGenerated }\n })\n}\n\nexport interface BackupSources {\n configPath: string\n secretsPath: string\n tlsDir: string\n /** Declared paths, resolved, with their origin and inclusion verdict. */\n paths: BackupPath[]\n}\n\nexport interface BackupManifest {\n version: 1\n createdAt: number\n hostname: string\n /** `origin` is what lets a restore land under *this* machine's paths. */\n data: Array<{ slug: string, path: string, origin?: string }>\n}\n\nexport interface RestoreOptions {\n confirm: boolean\n /** Required for, and ignored by, archives that are not password-protected. */\n password?: string\n /** Item ids to restore; omitted means every restorable item. */\n include?: string[]\n}\n\nexport interface RestorePlan {\n dryRun: boolean\n encrypted: boolean\n needsPassword: boolean\n items: Array<{\n id: string\n label: string\n kind: 'config' | 'secrets' | 'tls' | 'data'\n restorable: boolean\n selected: boolean\n note: string | null\n }>\n applied: string[]\n skipped: string[]\n restartRequired: boolean\n /** The panel re-read the restored config in this same run. */\n reloaded: boolean\n error?: string\n}\n\n/** One place a restore may write a data path to, and what declared it. */\ninterface DeclaredTarget {\n path: string\n origin: string\n}\n\n/** The panel's own listener is the only thing a restart is needed for. */\nfunction controlBlock(configText: string | null): unknown {\n try {\n return (JSON.parse(configText ?? '{}') as { control?: unknown }).control ?? null\n }\n catch {\n return null\n }\n}\n\n/**\n * A restored config is accepted when this release can read it — through the same\n * tolerant parser the store uses, so an archive from a newer release keeps only\n * the keys this one understands instead of being refused outright.\n */\nfunction isUsableConfig(text: string | null): boolean {\n if (text === null)\n return false\n try {\n return parseConfig(JSON.parse(text)).config !== null\n }\n catch {\n return false\n }\n}\n\n/**\n * The data paths the archive's own config declares, resolved against *this*\n * machine — so a backup made with `{home}` templates restores under this user's\n * paths, and one restored onto a blank instance brings its servers with it.\n */\nfunction archiveTargets(configText: string | null): DeclaredTarget[] {\n if (configText === null)\n return []\n try {\n const parsed = parseConfig(JSON.parse(configText)).config\n if (parsed === null)\n return []\n return resolveBackupPaths(parsed.servers, parsed.backups.includePaths)\n .filter(entry => entry.included)\n .map(entry => ({ path: entry.path, origin: entry.origin }))\n }\n catch {\n return []\n }\n}\n\n/**\n * The manifest is written by us, but an uploaded archive's copy is attacker\n * controlled: never join an unvalidated slug into a path.\n */\nfunction safeSlug(slug: unknown): string | null {\n if (typeof slug !== 'string' || slug.length === 0 || slug.length > 80)\n return null\n if (!/^[\\w.-]+$/.test(slug) || slug === '.' || slug === '..')\n return null\n return slug\n}\n\n/** A flag is valid for one exact file revision, not for the name alone. */\nfunction cacheKey(file: { sizeBytes: number, createdAt: number }): string {\n return `${file.sizeBytes}:${file.createdAt}`\n}\n\n/** Stable, filesystem-safe name for a data path inside the archive. */\nexport function slugifyPath(target: string): string {\n const cleaned = target.replace(/[^A-Z0-9]+/gi, '-').replace(/^-+|-+$/g, '')\n return cleaned.length > 0 ? cleaned.slice(-80) : 'path'\n}\n\n/**\n * Archives of the control plane's own state plus whatever paths the config\n * declares. Two rules keep restore safe: the archive layout is an allowlist, and\n * a data path is only written back when the *current* config still declares it —\n * an uploaded archive can never choose where to write.\n *\n * A backup is always a zip; a password makes it a WinZip-AES one, so the same\n * file opens in any archive manager either way.\n */\nexport class BackupService {\n /**\n * Whether an archive is encrypted is only knowable by reading its central\n * directory, which is async while `list()` is not. The flags are cached here\n * and refreshed in the background, so a state frame stays cheap.\n */\n private readonly flags = new Map<string, { key: string, encrypted: boolean }>()\n private refreshing: Promise<void> | null = null\n\n constructor(\n private readonly options: {\n /** Relative `backups.dir` values resolve against it. */\n dataRoot: string\n getConfig: () => BackupsConfig\n getSources: () => BackupSources\n /** Called after this instance's own config was overwritten by a restore. */\n onConfigRestored?: () => void\n },\n ) {}\n\n /** Reads every archive once, so the first `list()` is already accurate. */\n async warm(): Promise<void> {\n await this.refresh()\n }\n\n get directory(): string {\n return this.resolveDir()\n }\n\n /** Declared paths with their verdict, as the UI shows them. */\n get paths(): BackupPath[] {\n const dir = path.resolve(this.resolveDir())\n return this.options.getSources().paths.map((entry) => {\n // Capturing a directory that contains the archive directory would make the\n // archive contain itself.\n if (isInside(entry.path, dir))\n return { ...entry, included: false, note: 'contains the backup directory' }\n return entry\n })\n }\n\n /** Only what actually goes into a backup. */\n get dataPaths(): string[] {\n return this.paths.filter(entry => entry.included).map(entry => entry.path)\n }\n\n list(): BackupFile[] {\n const files = this.scan()\n for (const file of files) {\n const cached = this.flags.get(file.name)\n if (cached === undefined || cached.key !== cacheKey(file))\n void this.scheduleRefresh()\n }\n\n return files.map((file) => {\n const cached = this.flags.get(file.name)\n return {\n ...file,\n encrypted: cached !== undefined && cached.key === cacheKey(file) ? cached.encrypted : false,\n }\n })\n }\n\n /** Validated absolute path for a download, or null when the name is not a backup. */\n resolve(name: string): string | null {\n if (!/^[A-Z0-9][\\w.-]*$/i.test(name) || name.includes('..'))\n return null\n const file = path.join(this.resolveDir(), name)\n return fs.existsSync(file) ? file : null\n }\n\n /** `password` encrypts the archive; it is never stored anywhere. */\n async create(options: { password?: string } = {}): Promise<{ ok: boolean, file?: BackupFile, error?: string }> {\n const config = this.options.getConfig()\n if (!config.enabled)\n return { ok: false, error: 'backups are disabled' }\n\n const password = options.password !== undefined && options.password.length > 0 ? options.password : null\n\n const dir = this.resolveDir()\n const sources = this.options.getSources()\n const staging = path.join(dir, `.staging-${Date.now()}`)\n const createdAt = Date.now()\n // Milliseconds matter: two backups in the same second must not collide.\n const name = `backup-${new Date(createdAt).toISOString().replace(/[:T]/g, '-').replace(/\\.\\d+Z$/, '')}-${createdAt % 1000}${SUFFIX}`\n const destination = path.join(dir, name)\n\n try {\n fs.mkdirSync(staging, { recursive: true })\n this.copyInto(staging, 'config/servers.config.json', sources.configPath)\n this.copyInto(staging, 'secrets/control-secrets.json', sources.secretsPath)\n this.copyInto(staging, 'tls', sources.tlsDir)\n\n const data: BackupManifest['data'] = []\n for (const declared of this.paths) {\n if (!declared.included || !fs.existsSync(declared.path))\n continue\n const slug = slugifyPath(declared.path)\n if (data.some(entry => entry.slug === slug))\n continue\n this.copyInto(staging, path.join('data', slug), declared.path, declared.ignoreGenerated === true)\n data.push({ slug, path: declared.path, origin: declared.origin })\n }\n\n const manifest: BackupManifest = { version: 1, createdAt, hostname: os.hostname(), data }\n fs.writeFileSync(path.join(staging, MANIFEST), `${JSON.stringify(manifest, null, 2)}\\n`)\n\n await createZip(staging, destination, password === null ? {} : { password })\n\n fs.rmSync(staging, { recursive: true, force: true })\n this.prune()\n\n const stats = fs.statSync(destination)\n this.flags.set(name, { key: `${stats.size}:${Math.round(stats.mtimeMs)}`, encrypted: password !== null })\n return { ok: true, file: { name, sizeBytes: stats.size, createdAt, encrypted: password !== null } }\n }\n catch (error) {\n fs.rmSync(staging, { recursive: true, force: true })\n fs.rmSync(destination, { force: true })\n return { ok: false, error: error instanceof Error ? error.message : String(error) }\n }\n }\n\n remove(name: string): boolean {\n const file = this.resolve(name)\n if (file === null)\n return false\n fs.rmSync(file, { force: true })\n this.flags.delete(name)\n return true\n }\n\n /**\n * Validates an archive, then (unless `confirm` is false) applies whichever of\n * its items were selected. Data paths come from the *current* config, never\n * from the archive's manifest.\n */\n async restore(archivePath: string, options: RestoreOptions): Promise<RestorePlan> {\n const password = options.password !== undefined && options.password.length > 0 ? options.password : null\n const plan: RestorePlan = {\n dryRun: !options.confirm,\n encrypted: false,\n needsPassword: false,\n items: [],\n applied: [],\n skipped: [],\n restartRequired: false,\n reloaded: false,\n }\n\n if (!isZipArchive(archivePath))\n return { ...plan, error: 'the archive is not a home-hosted backup (a zip file was expected)' }\n\n const staging = path.join(this.resolveDir(), `.restore-${Date.now()}`)\n\n try {\n // The central directory is readable without a password, so a backup can be\n // listed and its selection offered before the password is ever entered.\n let entries\n try {\n entries = await listZip(archivePath)\n }\n catch (error) {\n return { ...plan, error: `the archive could not be read: ${error instanceof Error ? error.message : String(error)}` }\n }\n\n plan.encrypted = entries.some(entry => entry.encrypted)\n if (plan.encrypted && password === null)\n return { ...plan, needsPassword: true, error: 'this backup is password-protected' }\n\n if (entries.length === 0)\n return { ...plan, error: 'the archive is empty' }\n if (entries.length > 100_000)\n return { ...plan, error: 'the archive has too many entries' }\n\n const invalid = entries.filter(entry => !isSafeArchiveEntry(entry.name))\n if (invalid.length > 0) {\n return { ...plan, error: `the archive contains unexpected entries (e.g. ${invalid.slice(0, 3).map(entry => entry.name).join(', ')})` }\n }\n\n fs.mkdirSync(staging, { recursive: true })\n const extracted = await extractZip(archivePath, staging, {\n names: entries.map(entry => entry.name),\n ...(password === null ? {} : { password }),\n })\n for (const name of extracted.skipped)\n plan.skipped.push(`${name} (symbolic link, skipped)`)\n\n const manifestPath = path.join(staging, MANIFEST)\n if (!fs.existsSync(manifestPath))\n return { ...plan, error: 'the archive has no manifest' }\n const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) as BackupManifest\n\n const sources = this.options.getSources()\n const configInArchive = path.join(staging, 'config', 'servers.config.json')\n const secretsInArchive = path.join(staging, 'secrets', 'control-secrets.json')\n const tlsInArchive = path.join(staging, 'tls')\n\n const selectedIds = options.include === undefined ? null : new Set(options.include)\n const actions = new Map<string, () => void>()\n\n const addItem = (item: RestorePlan['items'][number], apply: (() => void) | null): void => {\n if (apply === null) {\n plan.items.push({ ...item, selected: false })\n plan.skipped.push(`${item.label}${item.note === null ? '' : ` (${item.note})`}`)\n return\n }\n const selected = selectedIds === null || selectedIds.has(item.id)\n plan.items.push({ ...item, selected })\n if (selected) {\n actions.set(item.id, apply)\n }\n else {\n plan.skipped.push(`${item.label} (not selected)`)\n }\n }\n\n const archivedConfig = fs.existsSync(configInArchive) ? fs.readFileSync(configInArchive, 'utf8') : null\n // A config from an archive replaces the live one, so it has to validate\n // first — otherwise a malformed upload silently removes every server.\n const restoredConfig = isUsableConfig(archivedConfig) ? archivedConfig : null\n if (archivedConfig !== null && restoredConfig === null)\n plan.skipped.push('config/servers.config.json (the archive\\'s config is not valid)')\n if (restoredConfig !== null) {\n addItem({ id: 'config', label: 'config/servers.config.json', kind: 'config', restorable: true, selected: false, note: null }, () => {\n writeFileAtomic(sources.configPath, restoredConfig)\n })\n }\n if (fs.existsSync(secretsInArchive)) {\n const restored = fs.readFileSync(secretsInArchive, 'utf8')\n addItem({ id: 'secrets', label: 'secrets/control-secrets.json', kind: 'secrets', restorable: true, selected: false, note: null }, () => {\n writeFileAtomic(sources.secretsPath, restored, { mode: 0o600 })\n })\n }\n if (fs.existsSync(tlsInArchive)) {\n const files = fs.readdirSync(tlsInArchive).filter(file => fs.statSync(path.join(tlsInArchive, file)).isFile())\n addItem({ id: 'tls', label: 'tls/', kind: 'tls', restorable: true, selected: false, note: null }, () => {\n fs.mkdirSync(sources.tlsDir, { recursive: true })\n for (const file of files) {\n const from = path.join(tlsInArchive, file)\n const mode = file.endsWith('.key.pem') ? { mode: 0o600 } : {}\n writeFileAtomic(path.join(sources.tlsDir, file), fs.readFileSync(from, 'utf8'), mode)\n }\n })\n }\n\n // A data path is written to a path a *config* declares — this instance's, or\n // the one the archive brings. That second source is what makes a blank\n // instance restorable: the backup's own `servers.config.json` names its data\n // directories, so restoring the config restores the whole setup.\n const fromArchive = archiveTargets(restoredConfig)\n const candidates: DeclaredTarget[] = [\n // The restored config wins, because it is the one that will be live.\n ...(actions.has('config') ? fromArchive : []),\n ...this.paths.filter(entry => entry.included).map(entry => ({ path: entry.path, origin: entry.origin })),\n ]\n\n for (const entry of manifest.data ?? []) {\n const target = candidates.find(candidate => entry.origin !== undefined && candidate.origin === entry.origin)\n ?? candidates.find(candidate => candidate.path === entry.path)\n const from = path.join(staging, 'data', safeSlug(entry.slug) ?? slugifyPath(entry.path))\n const common = {\n id: `data:${entry.path}`,\n label: target?.path ?? entry.path,\n kind: 'data' as const,\n restorable: false,\n selected: false,\n note: null,\n }\n\n if (target === undefined) {\n const archiveOnly = fromArchive.some(candidate => candidate.origin === entry.origin)\n addItem({\n ...common,\n note: archiveOnly && !actions.has('config')\n ? 'declared by the backup\\'s config, which is not being restored'\n : 'not declared by this config, nor by the backup',\n }, null)\n continue\n }\n if (!fs.existsSync(from)) {\n addItem({ ...common, note: 'missing from the archive' }, null)\n continue\n }\n\n addItem(\n { ...common, restorable: true, note: target.path === entry.path ? null : `restored from ${entry.path}` },\n () => fs.cpSync(from, target.path, { recursive: true, force: true }),\n )\n }\n\n // The plan has to say whether a restart is needed even in a dry run: only\n // the panel's own listener does, the servers are re-read from the file.\n if (restoredConfig !== null && actions.has('config')) {\n const current = fs.existsSync(sources.configPath) ? fs.readFileSync(sources.configPath, 'utf8') : null\n plan.restartRequired = JSON.stringify(controlBlock(restoredConfig)) !== JSON.stringify(controlBlock(current))\n }\n\n if (!options.confirm) {\n // A dry run reports what *would* happen, so the UI can show the plan\n // and the selection before anything is written.\n plan.applied = [...actions.keys()].map(id => plan.items.find(item => item.id === id)!.label)\n return plan\n }\n\n for (const [id, apply] of actions) {\n apply()\n plan.applied.push(plan.items.find(item => item.id === id)!.label)\n }\n\n if (actions.has('config') && this.options.onConfigRestored !== undefined) {\n plan.reloaded = true\n // The panel re-reads the restored file here, so the servers it declares\n // exist immediately instead of after a restart.\n this.options.onConfigRestored()\n }\n\n return plan\n }\n catch (error) {\n // Extraction happens before anything is written, so a rejected password has\n // changed nothing at all.\n if (isInvalidPassword(error))\n return { ...plan, encrypted: true, needsPassword: true, error: 'the password is wrong' }\n // A restore is not transactional: say what already landed, so a failure\n // cannot look like nothing happened.\n const done = plan.applied.length > 0 ? ` — already applied: ${plan.applied.join(', ')}` : ''\n return { ...plan, error: `${error instanceof Error ? error.message : String(error)}${done}` }\n }\n finally {\n fs.rmSync(staging, { recursive: true, force: true })\n }\n }\n\n /** Newest-first listing, without the encryption flag, which needs a read. */\n private scan(): Array<Omit<BackupFile, 'encrypted'>> {\n const dir = this.resolveDir()\n let names: string[] = []\n try {\n names = fs.readdirSync(dir)\n }\n catch {\n return []\n }\n\n return names\n .filter(name => name.endsWith(SUFFIX))\n .flatMap((name) => {\n try {\n const stats = fs.statSync(path.join(dir, name))\n return [{ name, sizeBytes: stats.size, createdAt: Math.round(stats.mtimeMs) }]\n }\n catch {\n return []\n }\n })\n .sort((a, b) => b.createdAt - a.createdAt)\n }\n\n /** Single-flight: a state frame must never queue a pile of reads. */\n private scheduleRefresh(): Promise<void> {\n this.refreshing ??= this.refresh().finally(() => {\n this.refreshing = null\n })\n return this.refreshing\n }\n\n private async refresh(): Promise<void> {\n const dir = this.resolveDir()\n const listed = this.scan()\n\n for (const file of listed) {\n const key = cacheKey(file)\n if (this.flags.get(file.name)?.key === key)\n continue\n try {\n const entries = await listZip(path.join(dir, file.name))\n this.flags.set(file.name, { key, encrypted: entries.some(entry => entry.encrypted) })\n }\n catch {\n // Unreadable stays unmarked here; restoring it reports the real reason.\n this.flags.set(file.name, { key, encrypted: false })\n }\n }\n\n const present = new Set(listed.map(file => file.name))\n for (const name of [...this.flags.keys()]) {\n if (!present.has(name))\n this.flags.delete(name)\n }\n }\n\n private copyInto(staging: string, relative: string, source: string, ignoreGenerated = false): void {\n if (!fs.existsSync(source))\n return\n const target = path.join(staging, relative)\n fs.mkdirSync(path.dirname(target), { recursive: true })\n // Never copy the archive directory into itself, however broad a declared\n // path is (`fs.cpSync` would walk it while writing into it).\n const archiveDir = path.resolve(this.resolveDir())\n const root = path.resolve(source)\n fs.cpSync(source, target, {\n recursive: true,\n force: true,\n filter: (from) => {\n const resolved = path.resolve(from)\n if (isInside(archiveDir, resolved))\n return false\n if (!ignoreGenerated || resolved === root)\n return true\n return !isGeneratedPath(path.relative(root, resolved))\n },\n })\n }\n\n private prune(): void {\n const { keep } = this.options.getConfig()\n for (const file of this.list().slice(keep)) this.remove(file.name)\n }\n\n private resolveDir(): string {\n const configured = this.options.getConfig().dir\n return path.isAbsolute(configured) ? configured : path.resolve(this.options.dataRoot, configured)\n }\n}\n","import type { FSWatcher } from 'node:fs'\nimport fs from 'node:fs'\nimport path from 'node:path'\n\n/**\n * Tells the caller when the config file *may* have changed. Whether it really did\n * is the store's decision: it remembers the bytes it last read and the bytes it\n * wrote, so a save from the panel's own UI never reloads anything.\n *\n * The watch is on the file's directory, because that is what catches the common\n * edit — an editor writes a temporary file and renames it over the target, which\n * replaces the inode and would slip past a watch on the file itself. `fs.watch` is\n * not dependable on network mounts, so a slow poll backs it up: one small read\n * every couple of seconds costs nothing next to a state frame.\n */\n\nexport interface ConfigWatchOptions {\n /** The config file; its directory is what is actually watched. */\n file: string\n /** Called after a burst of changes settles. */\n onChange: () => void\n /** How long to wait for an editor to finish writing. */\n debounceMs?: number\n /** Backup poll interval in ms; 0 turns the poll off. */\n pollMs?: number\n /** A watch that cannot be established is reported; the poll still covers the file. */\n onError?: (error: unknown) => void\n}\n\nconst DEFAULT_DEBOUNCE_MS = 150\nconst DEFAULT_POLL_MS = 2000\n\nexport class ConfigWatch {\n private readonly debounceMs: number\n private readonly pollMs: number\n private watcher: FSWatcher | null = null\n private debounceTimer: NodeJS.Timeout | null = null\n private pollTimer: NodeJS.Timeout | null = null\n private disposed = false\n\n constructor(private readonly options: ConfigWatchOptions) {\n this.debounceMs = options.debounceMs ?? DEFAULT_DEBOUNCE_MS\n this.pollMs = options.pollMs ?? DEFAULT_POLL_MS\n }\n\n start(): void {\n if (this.disposed || this.watcher !== null)\n return\n\n try {\n this.watcher = fs.watch(path.dirname(this.options.file), (_event, filename) => {\n // Only our file: the directory is shared with the logs, the secrets file and\n // whatever else home-hosted keeps beside its config.\n if (filename !== null && filename !== path.basename(this.options.file))\n return\n this.schedule()\n })\n this.watcher.on('error', (error) => {\n // A watch can die with the mount it was opened on. Stop pretending it works\n // and let the poll carry the file.\n this.options.onError?.(error)\n this.closeWatcher()\n })\n this.watcher.unref()\n }\n catch (error) {\n this.options.onError?.(error)\n }\n\n if (this.pollMs > 0) {\n this.pollTimer = setInterval(() => this.check(), this.pollMs)\n this.pollTimer.unref()\n }\n }\n\n /** One check, exactly what the poll does — tests drive this instead of waiting. */\n check(): void {\n this.schedule()\n }\n\n dispose(): void {\n this.disposed = true\n if (this.debounceTimer !== null) {\n clearTimeout(this.debounceTimer)\n this.debounceTimer = null\n }\n if (this.pollTimer !== null) {\n clearInterval(this.pollTimer)\n this.pollTimer = null\n }\n this.closeWatcher()\n }\n\n /** One reload per burst: an editor writing in pieces is still one edit. */\n private schedule(): void {\n if (this.disposed || this.debounceTimer !== null)\n return\n this.debounceTimer = setTimeout(() => {\n this.debounceTimer = null\n if (!this.disposed)\n this.options.onChange()\n }, this.debounceMs)\n this.debounceTimer.unref()\n }\n\n private closeWatcher(): void {\n this.watcher?.close()\n this.watcher = null\n }\n}\n","import type { Server } from 'srvx'\nimport type { Bind } from '#src/shared/contracts'\nimport { serve } from 'srvx'\nimport { bindHost, displayHost } from '#src/helpers/bind'\nimport { isPortFree } from '#src/providers/port'\n\nexport interface ControlEndpoint {\n /** Configured bind value: `local` | `lan` | ipv4. */\n host: Bind\n port: number\n /** Address actually bound. */\n bindHost: string\n url: string\n protocol: 'http' | 'https'\n}\n\nexport interface ControlServerOptions {\n /** A thunk, so the app can be built after this server exists. */\n fetch: (request: Request) => Response | Promise<Response>\n /** Read per (re)bind, so a settings change applies without a restart. */\n trustProxy: () => boolean\n /** The uploaded PEM pair, or null for plain http. Read per (re)bind. */\n tls: () => { cert: string, key: string } | null\n}\n\nexport interface RebindResult {\n ok: boolean\n error?: string\n}\n\n/**\n * Owns the control panel's own listener, so the settings page can move it to a\n * new host/port without stopping the supervised servers.\n */\nexport class ControlServer {\n readonly endpoint: ControlEndpoint\n private server: Server | null = null\n\n constructor(\n private readonly options: ControlServerOptions,\n initial: { host: Bind, port: number, tls?: boolean },\n ) {\n this.endpoint = {\n host: initial.host,\n port: initial.port,\n bindHost: bindHost(initial.host),\n url: `${initial.tls ? 'https' : 'http'}://${displayHost(initial.host)}:${initial.port}`,\n protocol: initial.tls ? 'https' : 'http',\n }\n }\n\n get liveHost(): string {\n return this.endpoint.host\n }\n\n get livePort(): number {\n return this.endpoint.port\n }\n\n async start(): Promise<void> {\n await this.listenWithRetry(this.endpoint.host, this.endpoint.port)\n }\n\n /** Re-listens on the same endpoint, e.g. after `trustProxy` changed. */\n async restart(): Promise<RebindResult> {\n const { host, port } = this.endpoint\n await this.close()\n try {\n await this.listenWithRetry(host, port)\n return { ok: true }\n }\n catch (error) {\n return { ok: false, error: `restart failed: ${error instanceof Error ? error.message : String(error)}` }\n }\n }\n\n /**\n * Moves the listener. Preflights the new port, and restores the previous\n * endpoint if the new one refuses to bind — otherwise the panel would become\n * unreachable and need a manual restart.\n */\n async rebind(next: { host: Bind, port: number }): Promise<RebindResult> {\n if (next.host === this.endpoint.host && next.port === this.endpoint.port)\n return { ok: true }\n\n const previous = { host: this.endpoint.host, port: this.endpoint.port }\n if (next.port !== previous.port && !(await isPortFree(next.port))) {\n return { ok: false, error: `port ${next.port} is already in use` }\n }\n\n await this.close()\n try {\n await this.listenWithRetry(next.host, next.port)\n return { ok: true }\n }\n catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n try {\n await this.listenWithRetry(previous.host, previous.port)\n }\n catch {\n // Nothing left to fall back to; the caller surfaces the original error.\n }\n return { ok: false, error: `rebind failed: ${message}` }\n }\n }\n\n async close(force = true): Promise<void> {\n const server = this.server\n this.server = null\n if (!server)\n return\n try {\n await server.close(force)\n }\n catch {\n // Already gone.\n }\n }\n\n /** A just-closed listener can refuse a rebind for a moment, so retry briefly. */\n private async listenWithRetry(host: Bind, port: number, attempts = 3): Promise<void> {\n let lastError: unknown\n for (let attempt = 1; attempt <= attempts; attempt++) {\n try {\n await this.listen(host, port)\n return\n }\n catch (error) {\n lastError = error\n if (attempt < attempts)\n await new Promise(resolve => setTimeout(resolve, 200))\n }\n }\n throw lastError\n }\n\n private async listen(host: Bind, port: number): Promise<void> {\n const tls = this.options.tls()\n const server = serve({\n fetch: this.options.fetch,\n port,\n hostname: bindHost(host),\n trustProxy: this.options.trustProxy(),\n ...(tls === null ? {} : { tls: { cert: tls.cert, key: tls.key } }),\n })\n\n // Without a listener, a failed bind would surface as an unhandled 'error' event.\n const nodeServer = server.node?.server\n const failure = new Promise<Error>((resolve) => {\n nodeServer?.once('error', error => resolve(error as Error))\n })\n\n const outcome = await Promise.race([\n server.ready().then(() => null).catch((error: unknown) => error as Error),\n failure,\n ])\n if (outcome !== null)\n throw outcome\n\n this.server = server\n this.endpoint.host = host\n this.endpoint.port = port\n this.endpoint.bindHost = bindHost(host)\n this.endpoint.protocol = tls === null ? 'http' : 'https'\n this.endpoint.url = `${this.endpoint.protocol}://${displayHost(host)}:${port}`\n }\n}\n","import type { SseMessage } from '#src/shared/contracts'\n\nexport type EventListener = (message: SseMessage) => void\n\nconst ALL = '*'\n\n/** Fan-out for SSE subscribers, optionally scoped to a single server. */\nexport class EventHub {\n private readonly listeners = new Map<string, Set<EventListener>>()\n\n subscribe(serverId: string | null, listener: EventListener): () => void {\n const key = serverId ?? ALL\n const bucket = this.listeners.get(key) ?? new Set<EventListener>()\n bucket.add(listener)\n this.listeners.set(key, bucket)\n\n return () => {\n bucket.delete(listener)\n if (bucket.size === 0)\n this.listeners.delete(key)\n }\n }\n\n publish(message: SseMessage): void {\n this.dispatch(ALL, message)\n if (message.serverId)\n this.dispatch(message.serverId, message)\n }\n\n get subscriberCount(): number {\n let total = 0\n for (const bucket of this.listeners.values()) total += bucket.size\n return total\n }\n\n private dispatch(key: string, message: SseMessage): void {\n const bucket = this.listeners.get(key)\n if (!bucket)\n return\n for (const listener of [...bucket]) {\n try {\n listener(message)\n }\n catch {\n bucket.delete(listener)\n }\n }\n }\n}\n","import type { HistoryEvent, ServerHistory } from '#src/shared/contracts'\nimport fs from 'node:fs'\nimport { writeFileAtomic } from '#src/helpers/atomic'\n\nconst MAX_EVENTS = 5000\nconst SAVE_DEBOUNCE_MS = 2000\nconst EVENTS_IN_VIEW = 8\n\nexport type HistoryEventType = HistoryEvent['type']\n\n/**\n * Bounded event log per server, persisted as JSON so uptime and crash counts\n * survive a restart of the control plane.\n *\n * Uptime is derived from recorded runtimes (each exit stores how long the process\n * was up) rather than from sampling, so it stays accurate without a background\n * poller.\n */\nexport class HistoryStore {\n private events: HistoryEvent[] = []\n private saveTimer: NodeJS.Timeout | null = null\n private loaded = false\n /** Bumped on every record, so readers can cache their summaries. */\n private version = 0\n\n constructor(private readonly file: string) {}\n\n get revision(): number {\n return this.version\n }\n\n load(): void {\n if (this.loaded)\n return\n this.loaded = true\n try {\n const parsed = JSON.parse(fs.readFileSync(this.file, 'utf8')) as { events?: HistoryEvent[] }\n this.events = Array.isArray(parsed.events) ? parsed.events.slice(-MAX_EVENTS) : []\n }\n catch {\n this.events = []\n }\n }\n\n record(serverId: string, event: Omit<HistoryEvent, 'serverId' | 'ts'>, ts = Date.now()): void {\n this.load()\n this.events.push({ serverId, ts, ...event })\n this.version += 1\n if (this.events.length > MAX_EVENTS)\n this.events.splice(0, this.events.length - MAX_EVENTS)\n this.scheduleSave()\n }\n\n all(): HistoryEvent[] {\n this.load()\n return [...this.events]\n }\n\n /** `runningSince` adds the in-flight up-interval so a long-running server shows its real ratio. */\n summarize(serverId: string, windowMs: number, now = Date.now(), runningSince: number | null = null): ServerHistory {\n this.load()\n const since = now - windowMs\n const mine = this.events.filter(event => event.serverId === serverId)\n const recent = mine.filter(event => event.ts >= since)\n\n let upMs = 0\n for (const event of recent) {\n if (event.runtimeMs !== undefined)\n upMs += Math.min(event.runtimeMs, windowMs)\n }\n if (runningSince !== null)\n upMs += Math.max(0, now - Math.max(runningSince, since))\n\n const lastCrash = [...mine].reverse().find(event => event.type === 'crash')\n const lastExit = [...mine].reverse().find(event => event.type === 'exit' || event.type === 'crash')\n\n return {\n windowMs,\n uptimeRatio: mine.length === 0 ? null : Math.max(0, Math.min(1, upMs / windowMs)),\n restarts: recent.filter(event => event.type === 'start').length,\n crashes: recent.filter(event => event.type === 'crash').length,\n forcedRestarts: recent.filter(event => event.type === 'forced-restart').length,\n lastCrashAt: lastCrash?.ts ?? null,\n lastExitAt: lastExit?.ts ?? null,\n lastRuntimeMs: lastExit?.runtimeMs ?? null,\n events: mine.slice(-EVENTS_IN_VIEW),\n }\n }\n\n dispose(): void {\n if (this.saveTimer !== null)\n clearTimeout(this.saveTimer)\n this.saveTimer = null\n this.save()\n }\n\n private scheduleSave(): void {\n if (this.saveTimer !== null)\n return\n this.saveTimer = setTimeout(() => {\n this.saveTimer = null\n this.save()\n }, SAVE_DEBOUNCE_MS)\n this.saveTimer.unref()\n }\n\n private save(): void {\n try {\n writeFileAtomic(this.file, `${JSON.stringify({ version: 1, events: this.events })}\\n`)\n }\n catch {\n // History is best-effort; never let it break supervision.\n }\n }\n}\n","import type { HostConfig, HostView } from '#src/shared/contracts'\nimport { execFile } from 'node:child_process'\nimport fs from 'node:fs'\nimport os from 'node:os'\nimport path from 'node:path'\nimport process from 'node:process'\nimport { promisify } from 'node:util'\n\nconst execFileAsync = promisify(execFile)\n\n/** Swap usage per platform: /proc on Linux, sysctl on macOS, CIM on Windows. */\nasync function swapUsedPercent(): Promise<number> {\n if (process.platform === 'linux')\n return 0 // filled by memoryInfo below\n\n if (process.platform === 'darwin') {\n try {\n const { stdout } = await execFileAsync('sysctl', ['-n', 'vm.swapusage'], { timeout: 3000 })\n const total = /total\\s*=\\s*([\\d.]+)M/.exec(stdout)?.[1]\n const used = /used\\s*=\\s*([\\d.]+)M/.exec(stdout)?.[1]\n const totalMb = Number.parseFloat(total ?? '0')\n const usedMb = Number.parseFloat(used ?? '0')\n return totalMb > 0 ? (usedMb / totalMb) * 100 : 0\n }\n catch {\n return 0\n }\n }\n\n if (process.platform === 'win32') {\n try {\n const script = 'Get-CimInstance Win32_PageFileUsage | Select-Object AllocatedBaseSize,CurrentUsage | ConvertTo-Csv -NoTypeInformation'\n const { stdout } = await execFileAsync('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', script], { timeout: 5000 })\n const line = stdout.split(/\\r?\\n/).slice(1).find(entry => entry.trim().length > 0)\n const cells = (line ?? '').split(',').map(entry => entry.replace(/\"/g, '').trim())\n const totalMb = Number.parseFloat(cells[0] ?? '0')\n const usedMb = Number.parseFloat(cells[1] ?? '0')\n return totalMb > 0 ? (usedMb / totalMb) * 100 : 0\n }\n catch {\n return 0\n }\n }\n\n return 0\n}\n\n/** `/proc/meminfo` counts cache as available, which `os.freemem()` does not. */\nexport function memoryInfo(): { memoryUsedPercent: number, swapUsedPercent: number } {\n try {\n const info = fs.readFileSync('/proc/meminfo', 'utf8')\n const read = (key: string): number => Number.parseInt(new RegExp(`^${key}:\\\\s+(\\\\d+)`, 'm').exec(info)?.[1] ?? '0', 10)\n const total = read('MemTotal')\n const available = read('MemAvailable')\n const swapTotal = read('SwapTotal')\n const swapFree = read('SwapFree')\n\n return {\n memoryUsedPercent: total > 0 ? ((total - available) / total) * 100 : 0,\n swapUsedPercent: swapTotal > 0 ? ((swapTotal - swapFree) / swapTotal) * 100 : 0,\n }\n }\n catch {\n const total = os.totalmem()\n const free = os.freemem()\n return { memoryUsedPercent: total > 0 ? ((total - free) / total) * 100 : 0, swapUsedPercent: 0 }\n }\n}\n\n/**\n * Best-effort CPU temperature from Linux thermal zones / hwmon. macOS and\n * Windows expose no unprivileged sensor, so those platforms report null and the\n * UI simply hides the reading.\n */\nexport function cpuTemperature(): number | null {\n const readings: number[] = []\n\n const inspect = (file: string): void => {\n try {\n const raw = Number.parseInt(fs.readFileSync(file, 'utf8').trim(), 10)\n if (!Number.isFinite(raw))\n return\n const celsius = raw / 1000 // both interfaces report millidegrees\n if (celsius > 0 && celsius < 150)\n readings.push(celsius)\n }\n catch {\n // Absent on this machine.\n }\n }\n\n try {\n for (const zone of fs.readdirSync('/sys/class/thermal')) {\n if (zone.startsWith('thermal_zone'))\n inspect(path.join('/sys/class/thermal', zone, 'temp'))\n }\n }\n catch {\n // No thermal class.\n }\n\n try {\n for (const hwmon of fs.readdirSync('/sys/class/hwmon')) {\n const dir = path.join('/sys/class/hwmon', hwmon)\n for (const entry of fs.readdirSync(dir)) {\n if (/^temp\\d+_input$/.test(entry))\n inspect(path.join(dir, entry))\n }\n }\n }\n catch {\n // No hwmon.\n }\n\n return readings.length > 0 ? Math.max(...readings) : null\n}\n\nasync function diskUsage(target: string): Promise<HostView['disks'][number] | null> {\n try {\n const stats = await fs.promises.statfs(target)\n const totalBytes = stats.blocks * stats.bsize\n const freeBytes = stats.bavail * stats.bsize\n return {\n path: target,\n totalBytes,\n freeBytes,\n usedPercent: totalBytes > 0 ? ((totalBytes - freeBytes) / totalBytes) * 100 : 0,\n }\n }\n catch {\n return null\n }\n}\n\n/**\n * Samples the machine itself: the failures a home server actually dies from are\n * a full disk, exhausted memory or a runaway load — none of which a port probe\n * can see.\n */\nexport async function sampleHost(config: HostConfig, resolvePath: (target: string) => string): Promise<HostView> {\n const cpus = os.cpus().length || 1\n const loadAvg = os.loadavg()\n const memory = memoryInfo()\n // `os.loadavg()` is always zero on Windows, so per-cpu load would alert forever.\n if (process.platform === 'win32')\n loadAvg.fill(0)\n if (memory.swapUsedPercent === 0 && process.platform !== 'linux') {\n memory.swapUsedPercent = await swapUsedPercent()\n }\n const tempCelsius = cpuTemperature()\n\n const disks = (await Promise.all(config.diskPaths.map(entry => diskUsage(resolvePath(entry))))).filter(\n (disk): disk is HostView['disks'][number] => disk !== null,\n )\n\n const alerts: string[] = []\n for (const disk of disks) {\n if (config.diskUsedPercent > 0 && disk.usedPercent >= config.diskUsedPercent) {\n alerts.push(`disk ${disk.path} is ${disk.usedPercent.toFixed(1)}% full`)\n }\n }\n if (config.memoryUsedPercent > 0 && memory.memoryUsedPercent >= config.memoryUsedPercent) {\n alerts.push(`memory is ${memory.memoryUsedPercent.toFixed(1)}% used`)\n }\n if (config.swapUsedPercent > 0 && memory.swapUsedPercent >= config.swapUsedPercent) {\n alerts.push(`swap is ${memory.swapUsedPercent.toFixed(1)}% used`)\n }\n const loadPerCpu = Number(loadAvg[0] ?? 0) / cpus\n if (config.loadPerCpu > 0 && loadPerCpu >= config.loadPerCpu) {\n alerts.push(`load ${loadPerCpu.toFixed(2)}/cpu exceeds ${config.loadPerCpu}`)\n }\n if (tempCelsius !== null && config.tempCelsius > 0 && tempCelsius >= config.tempCelsius) {\n alerts.push(`cpu temperature is ${tempCelsius.toFixed(0)}°C`)\n }\n\n return {\n enabled: config.enabled,\n cpus,\n loadAvg: [...loadAvg],\n uptimeMs: os.uptime() * 1000,\n memoryUsedPercent: memory.memoryUsedPercent,\n swapUsedPercent: memory.swapUsedPercent,\n tempCelsius,\n disks,\n alerts,\n sampledAt: Date.now(),\n }\n}\n\nexport function emptyHostView(config: HostConfig): HostView {\n return {\n enabled: config.enabled,\n cpus: os.cpus().length || 1,\n loadAvg: [0, 0, 0],\n uptimeMs: os.uptime() * 1000,\n memoryUsedPercent: 0,\n swapUsedPercent: 0,\n tempCelsius: null,\n disks: [],\n alerts: [],\n sampledAt: null,\n }\n}\n","import type { NotificationService } from '#src/services/notifications'\nimport type { HostConfig, HostView } from '#src/shared/contracts'\nimport { emptyHostView, sampleHost } from '#src/providers/host'\n\n/**\n * Samples host vitals on their own (slower) interval and turns threshold\n * breaches into one notification per transition, not one per sample.\n */\nexport class HostMonitor {\n private current: HostView\n private lastSampleAt = 0\n private alerting = false\n\n constructor(\n private readonly getConfig: () => HostConfig,\n private readonly resolvePath: (target: string) => string,\n private readonly notifications: NotificationService,\n ) {\n this.current = emptyHostView(getConfig())\n }\n\n get view(): HostView {\n return this.current\n }\n\n /** Cheap when the interval has not elapsed; safe to call every tick. */\n async tick(now = Date.now()): Promise<void> {\n const config = this.getConfig()\n if (!config.enabled) {\n if (this.current.enabled)\n this.current = { ...this.current, enabled: false }\n return\n }\n if (now - this.lastSampleAt < config.intervalMs)\n return\n\n this.lastSampleAt = now\n this.current = await sampleHost(config, this.resolvePath)\n\n if (this.current.alerts.length > 0) {\n if (!this.alerting) {\n this.alerting = true\n this.notifications.notify({\n serverId: 'host',\n label: 'Host',\n reason: 'host',\n detail: this.current.alerts.join('; '),\n })\n }\n return\n }\n\n if (this.alerting) {\n this.alerting = false\n this.notifications.notify({\n serverId: 'host',\n label: 'Host',\n reason: 'host-recovered',\n detail: 'every host threshold is back to normal',\n })\n }\n }\n}\n","import type { LogLine, LogsConfig } from '#src/shared/contracts'\nimport { Buffer } from 'node:buffer'\nimport fs from 'node:fs'\nimport path from 'node:path'\n\n/**\n * Append-only JSONL per server, one line per log entry, rotated by size.\n *\n * JSONL keeps the tail readable without parsing a stream, and appending needs no\n * rewrite of the existing file. Writes are batched on a short timer so a chatty\n * child cannot turn into a syscall per line.\n */\nconst FLUSH_INTERVAL_MS = 250\nconst MAX_PENDING_LINES = 500\nconst READ_CHUNK_BYTES = 256 * 1024\n\nexport interface LogFileInfo {\n name: string\n sizeBytes: number\n}\n\nexport class LogFiles {\n private readonly pending = new Map<string, LogLine[]>()\n private timer: NodeJS.Timeout | null = null\n private closed = false\n\n constructor(\n private readonly dir: string,\n private readonly getConfig: () => LogsConfig,\n ) {}\n\n get directory(): string {\n return this.dir\n }\n\n append(serverId: string, line: LogLine): void {\n if (this.closed || !this.getConfig().persist)\n return\n\n const bucket = this.pending.get(serverId) ?? []\n bucket.push(line)\n this.pending.set(serverId, bucket)\n\n if (bucket.length >= MAX_PENDING_LINES) {\n this.flush()\n return\n }\n this.timer ??= setTimeout(() => {\n this.timer = null\n this.flush()\n }, FLUSH_INTERVAL_MS)\n this.timer.unref()\n }\n\n flush(): void {\n if (this.pending.size === 0)\n return\n\n const batches = [...this.pending.entries()]\n this.pending.clear()\n\n for (const [serverId, lines] of batches) {\n try {\n this.write(serverId, lines)\n }\n catch {\n // Logging must never take the control plane down.\n }\n }\n }\n\n info(serverId: string): { enabled: boolean, sizeBytes: number, files: LogFileInfo[] } {\n const config = this.getConfig()\n const files: LogFileInfo[] = []\n let sizeBytes = 0\n\n for (const file of this.rotateTargets(serverId)) {\n try {\n const stats = fs.statSync(file)\n files.push({ name: path.basename(file), sizeBytes: stats.size })\n if (file === this.currentPath(serverId))\n sizeBytes = stats.size\n }\n catch {\n // Not rotated there yet.\n }\n }\n\n return { enabled: config.persist, sizeBytes, files }\n }\n\n /** Reads the last `tail` lines, newest file first, padding from one rotation back. */\n readTail(serverId: string, tail: number): LogLine[] {\n const sources = [this.currentPath(serverId), this.rotatedPath(serverId, 1)]\n const lines: LogLine[] = []\n\n for (const file of sources) {\n if (lines.length >= tail)\n break\n const chunk = this.readTailChunk(file, tail - lines.length)\n lines.unshift(...chunk)\n }\n\n return lines.slice(-tail)\n }\n\n clear(serverId: string): void {\n for (const file of this.rotateTargets(serverId)) {\n try {\n fs.rmSync(file, { force: true })\n }\n catch {\n // Nothing to remove.\n }\n }\n }\n\n dispose(): void {\n this.closed = true\n if (this.timer !== null)\n clearTimeout(this.timer)\n this.timer = null\n this.flush()\n }\n\n private write(serverId: string, lines: LogLine[]): void {\n const { maxBytes } = this.getConfig()\n const file = this.currentPath(serverId)\n fs.mkdirSync(this.dir, { recursive: true })\n\n // A batch is split at the size limit: writing it whole would sail past\n // maxBytes long before the next rotation check runs.\n let encoded: string[] = []\n let bytes = 0\n\n const commit = (): void => {\n if (encoded.length === 0)\n return\n const payload = `${encoded.join('\\n')}\\n`\n const currentSize = fs.existsSync(file) ? fs.statSync(file).size : 0\n if (currentSize + Buffer.byteLength(payload) > maxBytes)\n this.rotate(serverId)\n fs.appendFileSync(this.currentPath(serverId), payload)\n encoded = []\n bytes = 0\n }\n\n for (const line of lines) {\n const json = JSON.stringify(line)\n const size = Buffer.byteLength(json) + 1\n if (bytes > 0 && bytes + size > maxBytes)\n commit()\n encoded.push(json)\n bytes += size\n }\n\n commit()\n }\n\n private rotate(serverId: string): void {\n const { keep } = this.getConfig()\n for (let index = keep - 1; index >= 1; index--) {\n const from = this.rotatedPath(serverId, index)\n if (!fs.existsSync(from))\n continue\n fs.renameSync(from, this.rotatedPath(serverId, index + 1))\n }\n if (fs.existsSync(this.currentPath(serverId))) {\n fs.renameSync(this.currentPath(serverId), this.rotatedPath(serverId, 1))\n }\n }\n\n private readTailChunk(file: string, tail: number): LogLine[] {\n let handle: number\n try {\n handle = fs.openSync(file, 'r')\n }\n catch {\n return []\n }\n\n try {\n const size = fs.fstatSync(handle).size\n const length = Math.min(size, READ_CHUNK_BYTES)\n const buffer = Buffer.alloc(length)\n fs.readSync(handle, buffer, 0, length, size - length)\n\n const text = buffer.toString('utf8')\n // A mid-file cut can leave a partial first line, which is dropped.\n const raw = text.split('\\n').filter(entry => entry.trim().length > 0)\n const parsed: LogLine[] = []\n for (const entry of raw.slice(size > length ? 1 : 0)) {\n try {\n parsed.push(JSON.parse(entry) as LogLine)\n }\n catch {\n // Partial line from a rotation boundary.\n }\n }\n return parsed.slice(-tail)\n }\n finally {\n fs.closeSync(handle)\n }\n }\n\n private currentPath(serverId: string): string {\n return path.join(this.dir, `${serverId}.log`)\n }\n\n private rotatedPath(serverId: string, index: number): string {\n return path.join(this.dir, `${serverId}.log.${index}`)\n }\n\n private rotateTargets(serverId: string): string[] {\n const { keep } = this.getConfig()\n const targets = [this.currentPath(serverId)]\n for (let index = 1; index <= keep; index++) targets.push(this.rotatedPath(serverId, index))\n return targets\n }\n}\n","import type { SecretsStore } from '#src/config/secrets'\nimport type { LogsConfig, NotificationsConfig, TelegramStatus } from '#src/shared/contracts'\nimport { logger } from '#src/helpers/logger'\nimport { formatTelegramMessage, listTelegramChats, sendTelegramMessage, verifyTelegramToken } from '#src/providers/telegram'\n\nexport type NotificationReason = 'crash' | 'unhealthy' | 'forced-restart' | 'recovered' | 'rss' | 'host' | 'host-recovered'\n\nexport interface NotificationEvent {\n serverId: string\n label: string\n reason: NotificationReason\n detail: string\n}\n\nconst REASON_LABEL: Record<NotificationReason, string> = {\n 'crash': 'gave up restarting',\n 'unhealthy': 'health check failing',\n 'forced-restart': 'force restarted',\n 'recovered': 'recovered',\n 'rss': 'exceeded its memory limit',\n 'host': 'host thresholds breached',\n 'host-recovered': 'host thresholds recovered',\n}\n\nconst TITLE: Record<NotificationReason, string> = {\n 'crash': '🔴 server down',\n 'unhealthy': '🟠 server unhealthy',\n 'forced-restart': '🔁 server force restarted',\n 'recovered': '🟢 server recovered',\n 'rss': '🔴 server over its memory limit',\n 'host': '🟠 host warning',\n 'host-recovered': '🟢 host recovered',\n}\n\n/**\n * Fans supervision events out to notification transports.\n *\n * Telegram is the only transport so far. The bot token never leaves the secrets\n * file, and every send is rate-limited per server *and* reason so a flapping\n * server cannot flood the chat.\n */\nexport class NotificationService {\n private readonly cooldowns = new Map<string, number>()\n private lastResult: string | null = null\n private lastResultAt: number | null = null\n\n constructor(\n private readonly secrets: SecretsStore,\n private readonly getConfig: () => NotificationsConfig,\n private readonly getLogsConfig: () => LogsConfig,\n ) {}\n\n get telegramTokenSet(): boolean {\n return this.secrets.telegramTokenSet\n }\n\n status(): TelegramStatus {\n const telegram = this.getConfig().telegram\n return {\n enabled: telegram.enabled,\n tokenSet: this.secrets.telegramTokenSet,\n chatId: telegram.chatId,\n onCrash: telegram.onCrash,\n onUnhealthy: telegram.onUnhealthy,\n onForcedRestart: telegram.onForcedRestart,\n onRecovered: telegram.onRecovered,\n onHost: telegram.onHost,\n cooldownMs: telegram.cooldownMs,\n lastResult: this.lastResult,\n lastResultAt: this.lastResultAt,\n }\n }\n\n /** Enabled for this reason *and* outside its cooldown window. */\n shouldNotify(event: NotificationEvent, now = Date.now()): boolean {\n const telegram = this.getConfig().telegram\n if (!telegram.enabled)\n return false\n\n const reasonEnabled = {\n 'crash': telegram.onCrash,\n 'unhealthy': telegram.onUnhealthy,\n 'forced-restart': telegram.onForcedRestart,\n 'recovered': telegram.onRecovered,\n 'rss': telegram.onCrash,\n 'host': telegram.onHost,\n 'host-recovered': telegram.onHost,\n }[event.reason]\n if (!reasonEnabled)\n return false\n\n const until = this.cooldowns.get(`${event.serverId}:${event.reason}`) ?? 0\n return !(telegram.cooldownMs > 0 && until > now)\n }\n\n /** Starts the cooldown window for this event, so a flapping server stays quiet. */\n markSent(event: NotificationEvent, now = Date.now()): void {\n this.cooldowns.set(`${event.serverId}:${event.reason}`, now + this.getConfig().telegram.cooldownMs)\n }\n\n /** Fire-and-forget by design: supervision must never wait on a chat API. */\n notify(event: NotificationEvent): void {\n void this.dispatch(event).catch((error: unknown) => {\n logger.warn(`notification failed: ${error instanceof Error ? error.message : String(error)}`)\n })\n }\n\n async dispatch(event: NotificationEvent): Promise<boolean> {\n // `shouldNotify` owns the toggles and the cooldown, so the policy lives once.\n if (!this.shouldNotify(event))\n return false\n this.markSent(event)\n\n return this.sendTelegram(\n formatTelegramMessage(TITLE[event.reason], [\n `${event.label} (${event.serverId}) ${REASON_LABEL[event.reason]}`,\n event.detail,\n ]),\n )\n }\n\n /** Used by the \"send test\" button in settings. */\n async sendTest(overrides: { botToken?: string, chatId?: string } = {}): Promise<{ ok: boolean, error?: string }> {\n const chatId = overrides.chatId ?? this.getConfig().telegram.chatId\n if (chatId.length === 0)\n return { ok: false, error: 'no chat id configured' }\n\n const token = this.resolveToken(overrides.botToken)\n if (token === null)\n return { ok: false, error: 'no bot token configured' }\n\n const result = await sendTelegramMessage(\n token,\n chatId,\n formatTelegramMessage('✅ home-hosted test', ['notifications are wired up correctly']),\n )\n this.remember(result.ok ? 'test message sent' : result.error ?? 'test failed')\n return result\n }\n\n async detectChats(overrides: { botToken?: string } = {}): Promise<{ ok: boolean, chats: Array<{ id: number | string, title: string }>, error?: string }> {\n const token = this.resolveToken(overrides.botToken)\n if (token === null)\n return { ok: false, chats: [], error: 'no bot token configured' }\n\n const result = await listTelegramChats(token)\n this.remember(result.ok ? `${result.chats.length} chat(s) found` : result.error ?? 'detect failed')\n return result\n }\n\n /** Verifies a token without sending anything. */\n async verifyToken(token: string): Promise<{ ok: boolean, username?: string, error?: string }> {\n return verifyTelegramToken(token)\n }\n\n private resolveToken(tokenOverride?: string): string | null {\n const token = tokenOverride?.trim() ?? this.secrets.telegramToken ?? ''\n return token.length > 0 ? token : null\n }\n\n private async sendTelegram(html: string): Promise<boolean> {\n const telegram = this.getConfig().telegram\n if (telegram.chatId.length === 0) {\n this.remember('no chat id configured')\n return false\n }\n\n const token = this.resolveToken()\n if (token === null) {\n this.remember('no bot token configured')\n return false\n }\n\n const result = await sendTelegramMessage(token, telegram.chatId, html)\n this.remember(result.ok ? 'sent' : result.error ?? 'send failed')\n return result.ok\n }\n\n private remember(message: string): void {\n this.lastResult = message\n this.lastResultAt = Date.now()\n }\n}\n","import type { TlsStatus } from '#src/shared/contracts'\nimport { Buffer } from 'node:buffer'\nimport { createPrivateKey, createPublicKey, X509Certificate } from 'node:crypto'\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport { writeFileAtomic } from '#src/helpers/atomic'\n\n/**\n * Stores an uploaded PEM pair and reports what it contains.\n *\n * The key is written 0600 and both files stay out of git. Nothing here binds a\n * socket — the control server reads the pair and hands it to srvx.\n */\nexport class TlsStore {\n private cached: { cert: string, key: string } | null = null\n private cachedMtime = ''\n\n constructor(private readonly dir: string) {}\n\n get directory(): string {\n return this.dir\n }\n\n get certPath(): string {\n return path.join(this.dir, 'control.crt.pem')\n }\n\n get keyPath(): string {\n return path.join(this.dir, 'control.key.pem')\n }\n\n get present(): boolean {\n return fs.existsSync(this.certPath) && fs.existsSync(this.keyPath)\n }\n\n /** Returns the PEM pair, re-read when the files change on disk. */\n load(): { cert: string, key: string } | null {\n if (!this.present) {\n this.cached = null\n return null\n }\n\n const key = [this.certPath, this.keyPath].map((file) => {\n try {\n return `${fs.statSync(file).mtimeMs}`\n }\n catch {\n return 'x'\n }\n }).join(':')\n\n if (this.cached !== null && key === this.cachedMtime)\n return this.cached\n\n try {\n this.cached = {\n cert: fs.readFileSync(this.certPath, 'utf8'),\n key: fs.readFileSync(this.keyPath, 'utf8'),\n }\n this.cachedMtime = key\n return this.cached\n }\n catch {\n this.cached = null\n return null\n }\n }\n\n save(certificate: string, privateKey: string): { ok: boolean, error?: string } {\n const validation = validatePair(certificate, privateKey)\n if (!validation.ok)\n return { ok: false, error: validation.error }\n\n fs.mkdirSync(this.dir, { recursive: true })\n writeFileAtomic(this.certPath, `${certificate.trimEnd()}\\n`)\n writeFileAtomic(this.keyPath, `${privateKey.trimEnd()}\\n`, { mode: 0o600 })\n this.cached = null\n this.cachedMtime = ''\n return { ok: true }\n }\n\n clear(): void {\n for (const file of [this.certPath, this.keyPath]) {\n try {\n fs.rmSync(file, { force: true })\n }\n catch {\n // Nothing to remove.\n }\n }\n this.cached = null\n }\n\n status(enabled: boolean): TlsStatus {\n const base: TlsStatus = {\n enabled,\n certPresent: this.present,\n subject: null,\n issuer: null,\n validFrom: null,\n validTo: null,\n daysRemaining: null,\n fingerprint: null,\n keyMatches: null,\n error: null,\n }\n\n if (!this.present) {\n return enabled ? { ...base, error: 'TLS is enabled but no certificate has been uploaded' } : base\n }\n\n const pair = this.load()\n if (pair === null)\n return { ...base, error: 'the stored certificate could not be read' }\n\n try {\n const x509 = new X509Certificate(pair.cert)\n const validTo = new Date(x509.validTo)\n const daysRemaining = Math.floor((validTo.getTime() - Date.now()) / 86_400_000)\n return {\n ...base,\n subject: x509.subject.replace(/\\n/g, ', '),\n issuer: x509.issuer.replace(/\\n/g, ', '),\n validFrom: new Date(x509.validFrom).toISOString(),\n validTo: validTo.toISOString(),\n daysRemaining,\n fingerprint: x509.fingerprint256,\n keyMatches: validatePair(pair.cert, pair.key).ok,\n error: daysRemaining < 0 ? 'the certificate has expired' : null,\n }\n }\n catch (error) {\n return { ...base, error: `invalid certificate: ${error instanceof Error ? error.message : String(error)}` }\n }\n }\n}\n\n/** Checks the certificate parses, is time-valid, and matches the private key. */\nexport function validatePair(certificate: string, privateKey: string): { ok: boolean, error?: string } {\n let x509: X509Certificate\n try {\n x509 = new X509Certificate(certificate)\n }\n catch (error) {\n return { ok: false, error: `certificate is not a valid PEM: ${error instanceof Error ? error.message : String(error)}` }\n }\n\n try {\n const key = createPrivateKey(privateKey)\n const fromKey = createPublicKey(key).export({ type: 'spki', format: 'der' })\n const fromCert = x509.publicKey.export({ type: 'spki', format: 'der' })\n if (!Buffer.from(fromKey).equals(Buffer.from(fromCert))) {\n return { ok: false, error: 'the private key does not match the certificate' }\n }\n }\n catch (error) {\n return { ok: false, error: `private key is not a valid PEM: ${error instanceof Error ? error.message : String(error)}` }\n }\n\n if (new Date(x509.validTo).getTime() < Date.now()) {\n return { ok: false, error: `the certificate expired on ${x509.validTo}` }\n }\n\n return { ok: true }\n}\n","import type { ArchiveEntry } from '#src/providers/archive'\nimport type { UiMeta, UiStatus } from '#src/shared/contracts'\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport process from 'node:process'\nimport { type } from 'arktype'\nimport { writeFileAtomic } from '#src/helpers/atomic'\nimport { extractZip, isZipArchive, listZip } from '#src/providers/archive'\nimport { uiMetaSchema } from '#src/shared/contracts'\n\n/**\n * The panel's UI is replaceable: the stock SPA ships in the package, and a user\n * can put their own build in `$HHOSTED_HOME/.ui` — by hand, or by uploading a zip\n * in the settings page. Everything that serves files asks `resolveDir()` per\n * request, so an install (or `home-hosted ui-revert`) applies on the next refresh.\n */\n\nconst META = 'ui.json'\n/** A UI is static files; these caps keep a hostile or accidental archive harmless. */\nconst MAX_ENTRIES = 20_000\nconst MAX_BYTES = 512 * 1024 * 1024\nconst MAX_NAME = 120\n\n/** What a UI author may declare in a root `ui.json` inside their archive. */\nconst manifestSchema = type({\n 'name?': 'string',\n 'version?': 'string',\n 'repo?': 'string',\n 'tag?': 'string',\n 'asset?': 'string',\n 'unix?': 'number.integer >= 0',\n})\n\n/**\n * A UI archive is a static site: relative paths only, no traversal, no absolute\n * paths, no drive letters, and an `index.html` to serve. Symlinks are dropped by\n * the extractor.\n */\nexport function isSafeUiEntry(entry: string): boolean {\n if (entry.length === 0 || entry.length > MAX_NAME)\n return false\n if (entry.startsWith('/') || entry.includes('\\\\') || entry.includes('\\0'))\n return false\n\n const cleaned = entry.replace(/^\\.\\//, '').replace(/\\/+$/, '')\n if (cleaned.length === 0)\n return false\n return cleaned.split('/').every(segment => segment !== '' && segment !== '.' && segment !== '..' && !segment.includes(':'))\n}\n\nexport type UiInstallResult\n = | { ok: true, meta: UiMeta }\n | { ok: false, error: string }\n\nexport class UiService {\n constructor(private readonly options: { dataRoot: string, stockDir?: string }) {}\n\n /** `$HHOSTED_HOME/.ui` — the only place a user UI is ever read from. */\n get directory(): string {\n return path.join(this.options.dataRoot, '.ui')\n }\n\n /** True when a user UI is installed and complete. */\n get custom(): boolean {\n return fs.existsSync(path.join(this.directory, 'index.html'))\n }\n\n /** What the panel should serve right now. */\n resolveDir(): string {\n if (this.custom)\n return this.directory\n return this.options.stockDir ?? this.directory\n }\n\n status(): UiStatus {\n return { custom: this.custom, dir: this.directory, meta: this.readMeta() }\n }\n\n readMeta(): UiMeta | null {\n try {\n const parsed = uiMetaSchema(JSON.parse(fs.readFileSync(path.join(this.directory, META), 'utf8')))\n return parsed instanceof type.errors ? null : parsed\n }\n catch {\n return null\n }\n }\n\n /**\n * Installs a UI from a zip. The archive is extracted beside `.ui` and only then\n * swapped in, so a failed upload leaves the previous UI (or the stock one)\n * serving.\n */\n async install(archivePath: string, fallbackName = 'custom-ui', installedTag?: string): Promise<UiInstallResult> {\n if (!isZipArchive(archivePath))\n return { ok: false, error: 'the upload is not a zip archive' }\n\n const staging = path.join(this.options.dataRoot, `.ui-staging-${Date.now()}`)\n\n try {\n return await this.stage(archivePath, staging, fallbackName, installedTag)\n }\n catch (error) {\n return { ok: false, error: error instanceof Error ? error.message : String(error) }\n }\n finally {\n fs.rmSync(staging, { recursive: true, force: true })\n }\n }\n\n /** Removes the user UI, putting the stock panel back. */\n revert(): boolean {\n const existed = fs.existsSync(this.directory)\n fs.rmSync(this.directory, { recursive: true, force: true })\n return existed\n }\n\n private async stage(archivePath: string, staging: string, fallbackName: string, installedTag?: string): Promise<UiInstallResult> {\n let entries: ArchiveEntry[]\n try {\n entries = await listZip(archivePath)\n }\n catch (error) {\n return { ok: false, error: `the archive could not be read: ${error instanceof Error ? error.message : String(error)}` }\n }\n\n if (entries.length === 0)\n return { ok: false, error: 'the archive is empty' }\n if (entries.length > MAX_ENTRIES)\n return { ok: false, error: `the archive has more than ${MAX_ENTRIES} entries` }\n\n const bytes = entries.reduce((total, entry) => total + entry.size, 0)\n if (bytes > MAX_BYTES)\n return { ok: false, error: `the archive is larger than ${Math.round(MAX_BYTES / 1024 / 1024)}MB uncompressed` }\n\n const unusable = entries.find(entry => !isSafeUiEntry(entry.name))\n if (unusable !== undefined)\n return { ok: false, error: `the archive contains an unusable path: ${unusable.name}` }\n\n fs.mkdirSync(staging, { recursive: true })\n await extractZip(archivePath, staging, { names: entries.map(entry => entry.name) })\n\n const root = resolveRoot(staging)\n if (root === null)\n return { ok: false, error: 'the archive has no index.html at its root' }\n\n const manifest = readManifest(root)\n const meta: UiMeta = {\n name: manifest?.name ?? fallbackName,\n version: manifest?.version ?? null,\n uploadedAt: Date.now(),\n files: countFiles(root),\n // The declared identity is carried into the installed metadata, so `ui-update` can\n // still tell which release this UI came from long after the zip is gone. Omitted\n // rather than defaulted: a UI that declares nothing has nothing to say here.\n ...(manifest?.repo === undefined ? {} : { repo: manifest.repo }),\n // The caller that fetched this from a release knows the tag better than the archive\n // does: a UI zip is built *before* the release is cut, so its own `tag` is the\n // previous release at best. Recording the archive's value here is what made a panel\n // re-download and re-install the same UI on every boot.\n ...(installedTag !== undefined ? { tag: installedTag } : manifest?.tag === undefined ? {} : { tag: manifest.tag }),\n ...(manifest?.asset === undefined ? {} : { asset: manifest.asset }),\n ...(manifest?.unix === undefined ? {} : { unix: manifest.unix }),\n }\n\n // A unique `previous` per attempt: two installers used to share one name, and the\n // second's cleanup could delete the first's only copy of the user's UI.\n const previous = `${this.directory}.previous-${process.pid}-${Date.now()}`\n if (fs.existsSync(this.directory))\n fs.renameSync(this.directory, previous)\n\n try {\n fs.renameSync(root, this.directory)\n writeFileAtomic(path.join(this.directory, META), `${JSON.stringify(meta, null, 2)}\\n`)\n }\n catch (error) {\n // Put the old UI back before reporting. If that restore itself fails, keep\n // `previous` on disk and say where it is — deleting it would destroy the only copy.\n try {\n fs.rmSync(this.directory, { recursive: true, force: true })\n if (fs.existsSync(previous))\n fs.renameSync(previous, this.directory)\n fs.rmSync(previous, { recursive: true, force: true })\n }\n catch {\n return { ok: false, error: `${error instanceof Error ? error.message : String(error)} — the previous UI is kept at ${previous}` }\n }\n return { ok: false, error: error instanceof Error ? error.message : String(error) }\n }\n\n // Only now is `previous` disposable.\n fs.rmSync(previous, { recursive: true, force: true })\n\n return { ok: true, meta }\n }\n}\n\n/**\n * Where the site actually starts: the archive root, or a single wrapper directory\n * (`zip -r ui.zip dist` is a common way to build one).\n */\nfunction resolveRoot(staging: string): string | null {\n if (fs.existsSync(path.join(staging, 'index.html')))\n return staging\n\n const directories = fs.readdirSync(staging, { withFileTypes: true }).filter(entry => entry.isDirectory())\n if (directories.length !== 1)\n return null\n\n const inner = path.join(staging, directories[0]!.name)\n return fs.existsSync(path.join(inner, 'index.html')) ? inner : null\n}\n\nfunction readManifest(root: string): typeof manifestSchema.infer | null {\n try {\n const parsed = manifestSchema(JSON.parse(fs.readFileSync(path.join(root, META), 'utf8')))\n return parsed instanceof type.errors ? null : parsed\n }\n catch {\n return null\n }\n}\n\nfunction countFiles(root: string): number {\n let total = 0\n for (const entry of fs.readdirSync(root, { withFileTypes: true, recursive: true })) {\n if (entry.isFile() && entry.name !== META)\n total += 1\n }\n return Math.max(1, total)\n}\n","import fs from 'node:fs'\nimport os from 'node:os'\nimport path from 'node:path'\n/**\n * The GitHub-release side of installing a UI: which repo, which tag, which asset, how\n * to read the release metadata and how to download one. `ui-switch` picks a release by\n * hand and `ui-update` follows what an installed UI declared, but both go through this\n * one implementation — the failure messages and the asset matching are the same problem.\n */\n\nexport const DEFAULT_REPO = 'NamesMT/home-hosted'\n/** Mirrors the cap `UiService` enforces uncompressed — the same body, before it is parsed. */\nexport const MAX_DOWNLOAD_BYTES = 512 * 1024 * 1024\n\nexport interface RepoSlug {\n owner: string\n name: string\n}\n\nexport interface GithubAsset {\n name?: string\n url?: string\n browser_download_url?: string\n}\n\nexport interface GithubRelease {\n tag_name?: string\n name?: string\n draft?: boolean\n prerelease?: boolean\n published_at?: string\n assets?: GithubAsset[]\n}\n\n/** What both commands need from the CLI that owns the readline prompts and the colours. */\nexport interface UiSourceIo {\n write: (text: string) => void\n prompt?: (question: string) => Promise<string>\n style: {\n bold: (text: string) => string\n dim: (text: string) => string\n green: (text: string) => string\n }\n}\n\nexport interface UiSourceContext {\n io: UiSourceIo\n version: string\n token: string | null\n /** Suppress progress lines: the startup hook runs with nobody watching. */\n quiet?: boolean\n}\n\n/** `owner/name`, the only slug GitHub releases are addressed by. */\nexport function parseRepoSlug(value: string): RepoSlug | null {\n const match = /^([\\w.-]+)\\/([\\w.-]+)$/.exec(value.trim())\n if (match === null)\n return null\n return { owner: match[1]!, name: match[2]! }\n}\n\nexport function repoSlug(repo: RepoSlug): string {\n return `${repo.owner}/${repo.name}`\n}\n\n/** The repo the default tag rule is about; `DEFAULT_REPO` is its only definition. */\nexport function isOwnRepo(repo: RepoSlug): boolean {\n return repoSlug(repo).toLowerCase() === DEFAULT_REPO.toLowerCase()\n}\n\n/**\n * Our own release tag matches this CLI's version, so a UI is paired with the panel\n * it was built for. Another repo has no such pairing, so its latest release is used.\n * `null` means \"latest\".\n */\nexport function defaultReleaseTag(repo: RepoSlug, version: string): string | null {\n return isOwnRepo(repo) ? `v${version}` : null\n}\n\nexport function releaseApiUrl(repo: RepoSlug, tag: string | null): string {\n const base = `https://api.github.com/repos/${repo.owner}/${repo.name}/releases`\n if (tag === null || tag.length === 0 || tag === 'latest')\n return `${base}/latest`\n return `${base}/tags/${encodeURIComponent(tag)}`\n}\n\n/** Every published release, newest first, as GitHub orders them. */\nexport function releasesApiUrl(repo: RepoSlug, perPage = 30): string {\n return `https://api.github.com/repos/${repo.owner}/${repo.name}/releases?per_page=${perPage}`\n}\n\n/** Generous and predictable: a UI bundle is an asset whose name ends in `.zip`. */\nexport function isUiAsset(name: string): boolean {\n return /\\.zip$/i.test(name.trim())\n}\n\n/**\n * Resolves a wanted asset: an exact name, a case-insensitive name, or a single\n * unambiguous substring (`--asset stock` for `home-hosted-ui-stock.zip`).\n */\nexport function matchAsset(names: readonly string[], query: string): { ok: true, name: string } | { ok: false, error: string } {\n const wanted = query.trim()\n if (wanted.length === 0)\n return { ok: false, error: 'no asset name was given' }\n\n const exact = names.find(name => name === wanted)\n if (exact !== undefined)\n return { ok: true, name: exact }\n\n const lower = wanted.toLowerCase()\n const insensitive = names.filter(name => name.toLowerCase() === lower)\n if (insensitive.length === 1)\n return { ok: true, name: insensitive[0]! }\n\n const partial = names.filter(name => name.toLowerCase().includes(lower))\n if (partial.length === 0)\n return { ok: false, error: `no asset matches \"${wanted}\" (available: ${names.join(', ') || 'none'})` }\n if (partial.length > 1)\n return { ok: false, error: `\"${wanted}\" matches more than one asset: ${partial.join(', ')} — use the full name` }\n return { ok: true, name: partial[0]! }\n}\n\n/** `^https?://` means a URL; anything else is a filesystem path with `~` expanded. */\nexport function parseFileSource(value: string): { kind: 'url', url: string } | { kind: 'path', path: string } {\n const trimmed = value.trim()\n if (/^https?:\\/\\//i.test(trimmed))\n return { kind: 'url', url: trimmed }\n return { kind: 'path', path: expandHome(trimmed) }\n}\n\nfunction expandHome(value: string): string {\n if (value === '~')\n return os.homedir()\n if (value.startsWith('~/') || value.startsWith('~\\\\'))\n return path.join(os.homedir(), value.slice(2))\n return value\n}\n\n/**\n * A token is only ever sent to GitHub: `--file <url>` may point anywhere, and a\n * credential must not leak to a host the user did not vouch for.\n */\nexport function isGithubHost(url: string): boolean {\n try {\n const host = new URL(url).hostname.toLowerCase()\n return host === 'github.com' || host.endsWith('.github.com')\n || host === 'githubusercontent.com' || host.endsWith('.githubusercontent.com')\n }\n catch {\n return false\n }\n}\n\n/**\n * A hung connection must not become a hung command. `fetch` has no default timeout, and\n * the startup hook is fire-and-forget: without this a stalled request would keep that\n * promise (and its temp directory) pending for as long as the process lives.\n */\nconst REQUEST_TIMEOUT_MS = 30_000\nconst DOWNLOAD_TIMEOUT_MS = 120_000\n\nfunction timeoutSignal(ms: number): AbortSignal | undefined {\n // `AbortSignal.timeout` exists from Node 17.3; the engines field requires 24.\n return typeof AbortSignal !== 'undefined' && typeof AbortSignal.timeout === 'function'\n ? AbortSignal.timeout(ms)\n : undefined\n}\n\nfunction isTimeout(error: unknown): boolean {\n return error instanceof Error && (error.name === 'TimeoutError' || error.name === 'AbortError')\n}\n\n/** What `UiService` names the install when the archive carries no `ui.json`. */\nexport function fallbackUiName(source: string): string {\n const base = path.basename(source).replace(/\\.zip$/i, '')\n const stripped = base.replace(/^home-hosted-ui-/i, '')\n return stripped.length > 0 ? stripped : 'custom-ui'\n}\n\n/** One release's assets, or a message that says what was wrong with the answer. */\nexport async function fetchRelease(repo: RepoSlug, tag: string | null, context: UiSourceContext): Promise<{ tag: string, assets: GithubAsset[] }> {\n const url = releaseApiUrl(repo, tag)\n let response: Response\n try {\n response = await fetch(url, { headers: apiHeaders(context), signal: timeoutSignal(REQUEST_TIMEOUT_MS) })\n }\n catch (error) {\n throw new Error(isTimeout(error) ? `GitHub did not answer within ${REQUEST_TIMEOUT_MS / 1000}s` : `could not reach GitHub: ${describeError(error)}`)\n }\n\n if (!response.ok)\n throw new Error(describeReleaseFailure(response.status, repo, tag))\n\n const body = await response.json() as GithubRelease\n return { tag: body.tag_name ?? tag ?? 'latest', assets: Array.isArray(body.assets) ? body.assets : [] }\n}\n\n/** Every published release, newest first. Drafts and unusable entries are dropped. */\nexport async function fetchReleases(repo: RepoSlug, context: UiSourceContext, perPage = 30): Promise<Array<{ tag: string, assets: GithubAsset[], publishedAt: string | null }>> {\n let response: Response\n try {\n response = await fetch(releasesApiUrl(repo, perPage), { headers: apiHeaders(context), signal: timeoutSignal(REQUEST_TIMEOUT_MS) })\n }\n catch (error) {\n throw new Error(isTimeout(error) ? `GitHub did not answer within ${REQUEST_TIMEOUT_MS / 1000}s` : `could not reach GitHub: ${describeError(error)}`)\n }\n\n if (!response.ok)\n throw new Error(describeReleaseFailure(response.status, repo, null))\n\n const body = await response.json()\n if (!Array.isArray(body))\n return []\n\n return (body as GithubRelease[])\n .filter(release => release.draft !== true && typeof release.tag_name === 'string')\n .map(release => ({\n tag: release.tag_name!,\n assets: Array.isArray(release.assets) ? release.assets : [],\n publishedAt: typeof release.published_at === 'string' ? release.published_at : null,\n }))\n}\n\nexport function assetDownloadUrl(asset: GithubAsset): string {\n const url = asset.url ?? asset.browser_download_url\n if (url === undefined || url.length === 0)\n throw new Error(`the release metadata for \"${asset.name ?? 'an asset'}\" carries no download URL`)\n return url\n}\n\n/** Streams to `os.tmpdir()` so a large body is never buffered in memory. */\nexport async function downloadToTemp(url: string, headers: Record<string, string>, context: UiSourceContext): Promise<{ dir: string, file: string }> {\n let response: Response\n try {\n response = await fetch(url, { headers, redirect: 'follow', signal: timeoutSignal(DOWNLOAD_TIMEOUT_MS) })\n }\n catch (error) {\n throw new Error(isTimeout(error) ? `the download stalled for ${DOWNLOAD_TIMEOUT_MS / 1000}s: ${url}` : `could not reach ${url}: ${describeError(error)}`)\n }\n\n if (!response.ok)\n throw new Error(describeDownloadFailure(response.status, url))\n\n const declared = Number(response.headers.get('content-length') ?? '0')\n if (!Number.isFinite(declared) || declared < 0)\n throw new Error(`the download from ${url} reported an unusable size`)\n if (declared > MAX_DOWNLOAD_BYTES)\n throw new Error(tooLargeMessage(declared))\n if (response.body === null)\n throw new Error(`the download from ${url} had no body`)\n\n const dir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'hh-ui-'))\n const file = path.join(dir, 'ui.zip')\n const handle = await fs.promises.open(file, 'w')\n let received = 0\n\n try {\n for await (const chunk of response.body) {\n received += chunk.length\n if (received > MAX_DOWNLOAD_BYTES)\n throw new Error(tooLargeMessage(received))\n await handle.write(chunk)\n }\n }\n catch (error) {\n await handle.close()\n fs.rmSync(dir, { recursive: true, force: true })\n throw error instanceof Error ? error : new Error(String(error))\n }\n await handle.close()\n\n if (context.quiet !== true)\n context.io.write(`${context.io.style.dim(`downloaded ${formatBytes(received)}`)}\\n`)\n return { dir, file }\n}\n\nexport function apiHeaders(context: UiSourceContext): Record<string, string> {\n const headers: Record<string, string> = {\n 'accept': 'application/vnd.github+json',\n 'user-agent': `home-hosted/${context.version}`,\n 'x-github-api-version': '2022-11-28',\n }\n if (context.token !== null && context.token.length > 0)\n headers.authorization = `Bearer ${context.token}`\n return headers\n}\n\nexport function noAssetsMessage(where: string, skipped: string[], tag: string | null): string {\n const lines = [`no usable UI assets in ${where} (expected a .zip)`]\n lines.push(skipped.length > 0 ? ` skipped: ${skipped.join(', ')}` : ' the release has no assets at all')\n if (tag !== null && tag !== 'latest')\n lines.push(' installing a different version silently is worse than failing: try the newest release with --tag latest')\n return lines.join('\\n')\n}\n\nexport function describeReleaseFailure(status: number, repo: RepoSlug, tag: string | null): string {\n const slug = repoSlug(repo)\n if (status === 404) {\n if (tag !== null && tag !== 'latest')\n return `no release tagged \"${tag}\" in ${slug}\\n list what exists with: home-hosted ui-switch --repo ${slug} --tag latest --list`\n return `no repository or published release at ${slug}\\n check the --repo slug (owner/name), and that the repository is public`\n }\n if (status === 403 || status === 429)\n return `GitHub refused the request (HTTP ${status}) — the unauthenticated API rate limit is per address.\\n set a token to raise it: --token <token>, or GITHUB_TOKEN / GH_TOKEN`\n if (status === 401)\n return 'GitHub rejected the token (HTTP 401) — check --token, GITHUB_TOKEN or GH_TOKEN'\n return `GitHub answered HTTP ${status} while reading the release of ${slug}`\n}\n\nfunction describeDownloadFailure(status: number, url: string): string {\n const github = isGithubHost(url)\n if (status === 404) {\n return github\n ? `the asset is gone (HTTP 404) — ${url}\\n the release may have been rebuilt since it was listed; run the command again`\n : `nothing is served at that URL (HTTP 404) — ${url}`\n }\n if (status === 403 || status === 429) {\n return github\n ? `GitHub refused the download (HTTP ${status}) — a token raises the rate limit: --token <token>, or GITHUB_TOKEN / GH_TOKEN`\n : `the host refused the download (HTTP ${status}) — ${url}`\n }\n if (status === 401 && github)\n return 'GitHub rejected the token on the download (HTTP 401) — check --token, GITHUB_TOKEN or GH_TOKEN'\n return `the download failed (HTTP ${status}) — ${url}`\n}\n\nfunction tooLargeMessage(bytes: number): string {\n return `the download is ${formatBytes(bytes)}, larger than the ${MAX_DOWNLOAD_BYTES / 1024 / 1024}MB a UI may be`\n}\n\nexport function formatBytes(bytes: number): string {\n if (bytes < 1024)\n return `${bytes} B`\n if (bytes < 1024 * 1024)\n return `${Math.round(bytes / 1024)} KB`\n return `${(bytes / 1024 / 1024).toFixed(1)} MB`\n}\n\nexport function describeError(error: unknown): string {\n return error instanceof Error ? error.message : String(error)\n}\n\n/**\n * Tag ordering: `v1.2.3` beats `v1.2.2`, a prerelease loses to its release, and anything\n * that is not a version sorts below every version that is. Enough to answer \"is this\n * release newer than the one the UI came from\" without a semver dependency.\n */\nexport function compareTags(a: string, b: string): number {\n const left = parseTag(a)\n const right = parseTag(b)\n if (left === null && right === null)\n return a.localeCompare(b)\n if (left === null)\n return -1\n if (right === null)\n return 1\n\n for (let index = 0; index < 3; index++) {\n const difference = (left.parts[index] ?? 0) - (right.parts[index] ?? 0)\n if (difference !== 0)\n return difference\n }\n\n // A release outranks its own prereleases, and two prereleases are ordered by their\n // identifiers — `rc.2` after `rc.1`. Comparing only \"is it a prerelease\" made every\n // pair of them equal, so `ui-update` hid each one from the other.\n if (left.prerelease.length === 0 && right.prerelease.length === 0)\n return 0\n if (left.prerelease.length === 0)\n return 1\n if (right.prerelease.length === 0)\n return -1\n\n for (let index = 0; index < Math.max(left.prerelease.length, right.prerelease.length); index++) {\n const one = left.prerelease[index]\n const two = right.prerelease[index]\n if (one === undefined)\n return -1\n if (two === undefined)\n return 1\n if (one === two)\n continue\n const numeric = /^\\d+$/\n if (numeric.test(one) && numeric.test(two))\n return Number(one) - Number(two)\n // Numeric identifiers rank below alphanumeric ones, per semver.\n if (numeric.test(one))\n return -1\n if (numeric.test(two))\n return 1\n return one.localeCompare(two)\n }\n return 0\n}\n\nfunction parseTag(tag: string): { parts: number[], prerelease: string[] } | null {\n const match = /^v?(\\d+)(?:\\.(\\d+))?(?:\\.(\\d+))?(?:-([0-9A-Za-z.-]+))?(?:\\+.*)?$/.exec(tag.trim())\n if (match === null)\n return null\n return {\n parts: [Number(match[1]), Number(match[2] ?? 0), Number(match[3] ?? 0)],\n prerelease: match[4] === undefined ? [] : match[4].split('.'),\n }\n}\n","import type { UiSourceContext } from '#src/providers/ui-release'\nimport type { UiService } from '#src/services/ui'\nimport process from 'node:process'\nimport { logger } from '#src/helpers/logger'\nimport { appVersion } from '#src/helpers/version'\nimport {\n assetDownloadUrl,\n DEFAULT_REPO,\n downloadToTemp,\n fetchRelease,\n isOwnRepo,\n isUiAsset,\n matchAsset,\n parseRepoSlug,\n} from '#src/providers/ui-release'\n\n/**\n * Keeping an *official* UI paired with the panel that serves it, without a person\n * having to notice. A UI from `NamesMT/home-hosted` is built for a release, and this\n * panel knows its own release, so \"the wrong tag\" is a fact rather than a preference —\n * unlike someone else's UI, where only the person can say what they want.\n *\n * Only ever installed by tag, never by guess, and every failure is a log line: this runs\n * at startup, and a UI problem must never stop a panel from serving.\n */\n\nexport type UiSyncResult = { kind: 'not-custom' }\n | { kind: 'foreign' }\n | { kind: 'no-identity' }\n | { kind: 'current', tag: string }\n | { kind: 'updated', tag: string }\n | { kind: 'failed', error: string }\n\n/**\n * `ui.json` says which release this build came from. Anything else — an unofficial UI,\n * or one that never declared itself — is left for `home-hosted ui-update`.\n */\nexport function officialTagFor(\n meta: { repo?: string, tag?: string } | null,\n runningVersion: string,\n): { tag: string, repo: string } | null {\n if (meta === null || typeof meta.repo !== 'string')\n return null\n const repo = parseRepoSlug(meta.repo)\n if (repo === null || !isOwnRepo(repo))\n return null\n return { tag: `v${runningVersion}`, repo: `${repo.owner}/${repo.name}` }\n}\n\nexport async function syncOfficialUi(ui: UiService, runningVersion = appVersion()): Promise<UiSyncResult> {\n if (!ui.custom)\n return { kind: 'not-custom' }\n\n const meta = ui.readMeta()\n if (meta === null)\n return { kind: 'no-identity' }\n\n const repo = meta.repo === undefined ? null : parseRepoSlug(meta.repo)\n if (repo === null)\n return { kind: 'no-identity' }\n if (!isOwnRepo(repo))\n return { kind: 'foreign' }\n\n const target = `v${runningVersion}`\n if (meta.tag === target)\n return { kind: 'current', tag: target }\n\n const context: UiSourceContext = {\n io: { write: () => {}, style: { bold: (t: string) => t, dim: (t: string) => t, green: (t: string) => t } },\n version: runningVersion,\n // The one unattended call may use a token like the interactive commands do; without\n // one, a shared or CI address hits GitHub's 60/hour unauthenticated limit.\n token: process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN ?? null,\n quiet: true,\n }\n\n try {\n const release = await fetchRelease(repo, target, context)\n const names = release.assets.map(asset => asset.name ?? '').filter(isUiAsset)\n if (names.length === 0)\n throw new Error(`no UI asset in ${DEFAULT_REPO}@${release.tag}`)\n\n // The asset this UI came from, or the only one on offer when it never said.\n const wanted = meta.asset ?? ''\n const matched = wanted.length > 0 ? matchAsset(names, wanted) : { ok: true as const, name: names[0]! }\n if (!matched.ok)\n throw new Error(matched.error)\n\n const asset = release.assets.find(entry => entry.name === matched.name)\n if (asset === undefined)\n throw new Error(`no asset named ${matched.name} in ${release.tag}`)\n\n const download = await downloadToTemp(assetDownloadUrl(asset), {\n 'accept': 'application/octet-stream',\n 'user-agent': `home-hosted/${runningVersion}`,\n }, context)\n\n try {\n // The tag is the release we just fetched, never what the archive claims.\n const result = await ui.install(download.file, matched.name.replace(/\\.zip$/i, ''), release.tag)\n if (!result.ok)\n throw new Error(result.error)\n }\n finally {\n const fs = await import('node:fs')\n fs.rmSync(download.dir, { recursive: true, force: true })\n }\n\n return { kind: 'updated', tag: release.tag }\n }\n catch (error) {\n return { kind: 'failed', error: error instanceof Error ? error.message : String(error) }\n }\n}\n\n/**\n * The startup hook. Never awaited by the caller and never allowed to throw: the panel\n * serves the UI it already has while this runs, and the next request picks up the new\n * one, because `UiService.resolveDir()` is read per request.\n */\nexport function autoUpdateOfficialUi(ui: UiService, runningVersion = appVersion()): void {\n if (!ui.custom)\n return\n\n const meta = ui.readMeta()\n const plan = officialTagFor(meta, runningVersion)\n if (plan === null || meta?.tag === plan.tag)\n return\n\n logger.info(`ui: ${meta?.name ?? 'custom UI'} ${meta?.version ?? ''} came from ${meta?.tag ?? 'an unknown release'}; this panel is ${plan.tag} — updating`)\n\n void syncOfficialUi(ui, runningVersion).then((result) => {\n if (result.kind === 'updated')\n logger.info(`ui: updated to ${result.tag} — refresh the browser`)\n else if (result.kind === 'failed')\n logger.warn(`ui: could not update to ${plan.tag}: ${result.error}`)\n }).catch((error: unknown) => {\n logger.warn(`ui: could not update: ${error instanceof Error ? error.message : String(error)}`)\n })\n}\n","import type { AppType } from '#src/app'\nimport type { Runtime } from '#src/helpers/daemon'\nimport fs from 'node:fs'\nimport os from 'node:os'\nimport path from 'node:path'\nimport process from 'node:process'\nimport { fileURLToPath } from 'node:url'\nimport { createRootApp } from '#src/app'\nimport { SecretsStore } from '#src/config/secrets'\nimport { SEED_CONFIG } from '#src/config/seed'\nimport { ConfigStore } from '#src/config/store'\nimport { clearRuntime, isProcessAlive, newToken, readRuntime, writeRuntime } from '#src/helpers/daemon'\nimport { logger } from '#src/helpers/logger'\nimport { openBrowser } from '#src/helpers/open'\nimport {\n daemonLogPath,\n dataRoot,\n defaultConfigPath,\n defaultHistoryPath,\n defaultLogsDir,\n defaultSecretsPath,\n defaultTlsDir,\n projectDir,\n resolveUserPath,\n} from '#src/helpers/paths'\nimport { resolveTemplate } from '#src/helpers/template'\nimport { appVersion } from '#src/helpers/version'\nimport { isPortFree } from '#src/providers/port'\nimport { AuthService, DEFAULT_PASSWORD } from '#src/services/auth'\nimport { BackupService, resolveBackupPaths } from '#src/services/backups'\nimport { ConfigWatch } from '#src/services/config-watch'\nimport { ControlServer } from '#src/services/control-server'\nimport { EventHub } from '#src/services/events'\nimport { checkExposure } from '#src/services/exposure'\nimport { HistoryStore } from '#src/services/history'\nimport { HostMonitor } from '#src/services/host-monitor'\nimport { LogFiles } from '#src/services/log-files'\nimport { NotificationService } from '#src/services/notifications'\nimport { buildAppState } from '#src/services/state'\nimport { Supervisor } from '#src/services/supervisor'\nimport { TlsStore } from '#src/services/tls'\nimport { UiService } from '#src/services/ui'\nimport { autoUpdateOfficialUi } from '#src/services/ui-update'\nimport { parseBind } from '#src/shared/contracts'\n\n/** The package root: one level above this file, whether it is `src/` or `dist/`. */\nexport const packageRoot = path.resolve(fileURLToPath(new URL('..', import.meta.url)))\n\nfunction packageVersion(): string {\n try {\n const manifest = JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8')) as { version?: string }\n return manifest.version ?? '0.0.0'\n }\n catch {\n return '0.0.0'\n }\n}\n\nexport interface ControlPlaneOptions {\n /** `--config`, or `$HHOSTED_HOME/servers.config.json`. */\n configPath?: string\n /** One-off overrides, persisted only after every guard below passes. */\n port?: number\n host?: string\n autostart: boolean\n open: boolean\n /** Print the effective config and exit without starting anything. */\n printConfig: boolean\n}\n\n/** A probe target for a `lan` bind, which is not connectable as `0.0.0.0`. */\nfunction probeHostFor(bindHost: string): string {\n return bindHost === '0.0.0.0' || bindHost === '::' ? '127.0.0.1' : bindHost\n}\n\n/**\n * Runs the control plane in *this* process until it is stopped. `home-hosted up`\n * detaches a child that calls this; `--foreground` (systemd, docker) calls it\n * directly.\n */\nexport async function runControlPlane(options: ControlPlaneOptions): Promise<void> {\n const existing = readRuntime()\n if (existing !== null && existing.pid !== process.pid && isProcessAlive(existing.pid)) {\n logger.error(`already running (pid ${existing.pid}) at ${existing.url} — run \\`home-hosted down\\` first`)\n process.exit(1)\n }\n if (existing !== null)\n clearRuntime()\n\n const configPath = options.configPath ?? defaultConfigPath\n const store = new ConfigStore(configPath, SEED_CONFIG)\n store.load()\n store.writeJsonSchema()\n\n // A config this release cannot read is refused rather than run with defaults: the\n // groups would fall back silently, and for `control` that means a different port,\n // bind and auth policy than the file asked for. Pinning the release that wrote it,\n // or migrating, is the way forward — see the Compatibility section of AGENTS.md.\n // Nothing is written before this point, so a refused start leaves the file alone.\n if (store.configError !== null) {\n logger.error(`refusing to start: ${store.configError}`)\n logger.info(`fix ${store.path}, or install the release that wrote it`)\n process.exit(1)\n }\n if (store.pendingMigrations.length > 0) {\n logger.error(`refusing to start: ${store.path} needs ${store.pendingMigrations.length} migration(s) before ${appVersion()} can use it`)\n logger.info('run `home-hosted migrate` to see and apply them')\n process.exit(1)\n }\n\n const secrets = new SecretsStore(defaultSecretsPath)\n const auth = new AuthService(secrets, () => store.config.control.auth)\n const tls = new TlsStore(defaultTlsDir)\n const logFiles = new LogFiles(defaultLogsDir, () => store.config.logs)\n const history = new HistoryStore(defaultHistoryPath)\n const notifications = new NotificationService(\n secrets,\n () => store.config.notifications,\n () => store.config.logs,\n )\n const hostMonitor = new HostMonitor(\n () => store.config.host,\n target => resolveUserPath(resolveTemplate(target, { projectDir, dataRoot, home: os.homedir() })),\n notifications,\n )\n // Restoring a backup replaces the config file, which no store write covers: the\n // hook below re-reads it and brings the restored autostart entries up, so a\n // blank instance ends up running the setup the archive carried.\n let onConfigRestored: (() => void) | undefined\n const backups = new BackupService({\n dataRoot,\n getConfig: () => store.config.backups,\n getSources: () => ({\n configPath: store.path,\n secretsPath: secrets.path,\n tlsDir: tls.directory,\n paths: resolveBackupPaths(store.servers, store.config.backups.includePaths),\n }),\n onConfigRestored: () => onConfigRestored?.(),\n })\n\n // Auth is on by default, so a first boot needs *a* password; the default is\n // deliberately weak and flagged, which keeps LAN/exposure binding blocked (and\n // is announced on the login page) until it is changed.\n if (!auth.passwordSet) {\n auth.ensureDefaultPassword(DEFAULT_PASSWORD)\n logger.warn(`no password was set — created the default \"${DEFAULT_PASSWORD}\"; change it in Settings → Authentication`)\n }\n\n const configured = store.config.control\n const intendedHost = options.host === undefined ? configured.host : parseBind(options.host)\n if (intendedHost === null) {\n logger.error(`invalid control host: ${String(options.host)} (expected local, lan or an ipv4 address)`)\n process.exit(1)\n }\n\n const intended = { host: intendedHost, port: options.port ?? configured.port }\n if (!Number.isInteger(intended.port) || intended.port <= 0 || intended.port > 65535) {\n logger.error(`invalid control port: ${String(options.port)}`)\n process.exit(1)\n }\n\n if (options.printConfig) {\n process.stdout.write(`${JSON.stringify(store.config, null, 2)}\\n`)\n return\n }\n\n // Never serve the panel beyond loopback without a password behind it.\n const exposure = checkExposure({ ...configured, host: intended.host }, auth.passwordSet, auth.usingDefaultPassword)\n if (exposure.blockedReason !== null) {\n logger.error(`refusing to start: ${exposure.blockedReason}`)\n logger.info('bind the panel back to `local`, or set a password with `home-hosted set-password` and enable auth in the settings page')\n process.exit(1)\n }\n\n if (!(await isPortFree(intended.port))) {\n logger.error(`control port ${intended.port} is already in use — is another home-hosted running?`)\n process.exit(1)\n }\n\n if (intended.host !== configured.host || intended.port !== configured.port)\n store.updateControl({ host: intended.host, port: intended.port })\n\n const ui = new UiService({ dataRoot, stockDir: path.join(packageRoot, 'uis', 'stock', 'dist') })\n const hub = new EventHub()\n let app: AppType | undefined\n const token = newToken()\n\n const controlServer = new ControlServer(\n {\n fetch: (request) => {\n if (!app)\n throw new Error('the control app is not ready yet')\n return app.fetch(request)\n },\n trustProxy: () => store.config.control.auth.trustProxy,\n tls: () => (store.config.control.tls.enabled ? tls.load() : null),\n },\n { host: intended.host, port: intended.port, tls: store.config.control.tls.enabled },\n )\n\n const supervisor = new Supervisor(store, hub, {\n configPath: store.path,\n control: controlServer.endpoint,\n buildState: views => buildAppState({\n store,\n auth,\n control: controlServer.endpoint,\n tls,\n notifications,\n hostMonitor,\n backups,\n logsDir: logFiles.directory,\n views,\n }),\n history,\n logFiles,\n notifications,\n hostMonitor,\n })\n\n /**\n * A config edited by hand — a text editor, a `git checkout`, a config-management\n * tool — is picked up without a restart. A revision this release cannot read is\n * reported in the state frame and the panel keeps running what it had, so a typo\n * never stops a server. A definition that *did* change takes effect on that\n * entry's next start; a newly added entry with `autostart` starts now, the way it\n * would after a restart, and a removed one is stopped and forgotten.\n */\n let lastConfigError: string | null = store.configError\n const configWatch = new ConfigWatch({\n file: store.path,\n onChange: () => {\n const before = new Set(store.servers.map(server => server.id))\n const result = store.reloadFromDisk()\n\n // One line per change of state, not one per poll: the file stays bad until\n // somebody fixes it.\n if (store.configError !== lastConfigError) {\n if (store.configError === null)\n logger.info('the config file is readable again')\n else\n logger.error(`${store.configError} — keeping the config already running`)\n lastConfigError = store.configError\n }\n\n if (!result.applied) {\n if (result.changed && result.error === null)\n logger.info('the config file changed, but not in a way that changes the config')\n return\n }\n\n const added = store.servers.filter(server => !before.has(server.id))\n const removed = [...before].filter(id => !store.getServer(id))\n logger.info(`config reloaded from disk — ${store.servers.length} server(s)${added.length === 0 ? '' : `, ${added.length} added`}${removed.length === 0 ? '' : `, ${removed.length} removed`}`)\n\n // `--no-autostart` means \"do not start anything on your own\", and a reload is\n // not an exception to that.\n if (!options.autostart)\n return\n for (const server of added) {\n // A disabled entry is left alone even when the file says autostart.\n if (!server.enabled || !server.autostart)\n continue\n void supervisor.start(server.id).catch((error: unknown) => {\n logger.error(`could not start the added server ${server.id}`, error)\n })\n }\n },\n onError: error => logger.warn(`cannot watch ${path.basename(store.path)} for changes: ${error instanceof Error ? error.message : String(error)}`),\n })\n configWatch.start()\n\n let shuttingDown = false\n const shutdown = async (reason: string): Promise<void> => {\n if (shuttingDown)\n return\n shuttingDown = true\n logger.info(`${reason} — stopping ${supervisor.views().length} server(s)`)\n clearRuntime()\n configWatch.dispose()\n auth.dispose()\n await supervisor.dispose()\n logFiles.dispose()\n history.dispose()\n await controlServer.close(true)\n process.exit(0)\n }\n\n app = createRootApp({\n store,\n supervisor,\n hub,\n auth,\n secrets,\n controlServer,\n tls,\n logFiles,\n notifications,\n backups,\n ui,\n runtimeToken: token,\n onShutdown: () => shutdown('shutdown requested locally'),\n })\n\n await controlServer.start()\n // Reads every existing archive once, so the first state frame already shows\n // which backups are password-protected.\n await backups.warm()\n\n const endpoint = controlServer.endpoint\n const runtime: Runtime = {\n version: packageVersion(),\n pid: process.pid,\n url: endpoint.url,\n probeUrl: `${endpoint.protocol}://${probeHostFor(endpoint.bindHost)}:${endpoint.port}`,\n protocol: endpoint.protocol,\n port: endpoint.port,\n bindHost: endpoint.bindHost,\n startedAt: Date.now(),\n projectDir,\n dataRoot,\n configPath: store.path,\n logFile: daemonLogPath,\n token,\n }\n writeRuntime(runtime)\n\n logger.box(`home-hosted ${runtime.version}\\n${endpoint.url}`)\n logger.info(`config: ${store.path}`)\n logger.info(`secrets: ${secrets.path}${auth.passwordSet ? '' : ' (no password set)'}`)\n logger.info(`auth: ${auth.isRequired() ? 'required' : 'disabled'}${auth.usingDefaultPassword ? ' (default password)' : ''}${auth.apiTokenSet ? ' · API token set' : ''}${exposure.exposed ? ' · exposed beyond loopback' : ''}`)\n logger.info(`logs: ${store.config.logs.persist ? `${logFiles.directory} (max ${store.config.logs.maxBytes} B x ${store.config.logs.keep})` : 'memory only'}`)\n logger.info(`project: ${projectDir}`)\n if (ui.custom) {\n const meta = ui.status().meta\n logger.warn(`custom UI in use${meta === null ? '' : ` (${meta.name}${meta.version === null ? '' : ` ${meta.version}`})`} — if it breaks, run \\`home-hosted ui-revert\\``)\n // An official UI is paired with a release, so a panel upgrade re-pairs it without\n // asking. Never awaited: the UI already on disk keeps serving until it lands.\n autoUpdateOfficialUi(ui, runtime.version)\n }\n for (const warning of store.configWarnings)\n logger.warn(warning)\n for (const entry of supervisor.views())\n logger.info(` ${entry.id.padEnd(12)} ${entry.config.command} ${entry.config.args.join(' ')}`.trimEnd())\n\n if (configured.openBrowser || options.open)\n openBrowser(endpoint.url)\n\n if (options.autostart) {\n void supervisor.startAll({ autostartOnly: true }).catch((error: unknown) => {\n logger.error('autostart failed', error)\n })\n }\n\n onConfigRestored = () => {\n store.load()\n\n // The restored config is a config write like any other, so the exposure rule\n // applies: a backup taken from a local instance must not open a LAN panel.\n const exposure = checkExposure(store.config.control, auth.passwordSet, auth.usingDefaultPassword)\n if (exposure.blockedReason !== null) {\n logger.warn(`the restored config would expose the panel (${exposure.blockedReason}) — forcing authentication on`)\n store.updateControl({ auth: { enabled: true } })\n }\n\n logger.info(`config restored — ${store.servers.length} server(s) reloaded`)\n // `--no-autostart` means \"do not start anything on your own\", restores included.\n if (!options.autostart)\n return\n void supervisor.startAll({ autostartOnly: true }).catch((error: unknown) => {\n logger.error('could not start the restored servers', error)\n })\n }\n\n // A failure while tearing down must still end the process: a rejected promise\n // here would leave the panel half-stopped.\n const onSignal = (signal: string): void => {\n void shutdown(signal).catch((error: unknown) => {\n logger.error(`shutdown after ${signal} failed`, error)\n process.exit(1)\n })\n }\n process.on('SIGINT', () => onSignal('SIGINT'))\n process.on('SIGTERM', () => onSignal('SIGTERM'))\n}\n","import type { ChildProcess } from 'node:child_process'\nimport type { UpFlags } from '#src/cli/args'\nimport { spawn } from 'node:child_process'\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport process from 'node:process'\nimport { defineCommand } from 'citty'\nimport { buildDaemonArgv } from '#src/cli/args'\nimport { bold, delay, dim, fail, green, paint } from '#src/cli/io'\n\n/** `up` starts the panel; without `--foreground` it re-spawns itself detached. */\n\nconst DEFAULT_PORT = 3999\nconst LOG_ROTATE_BYTES = 5 * 1024 * 1024\n\nexport const upArgs = {\n config: { type: 'string', alias: 'c', description: 'servers config (default: <state>/servers.config.json)' },\n port: { type: 'string', alias: 'p', description: `control panel port (default: ${DEFAULT_PORT})` },\n host: { type: 'string', description: 'local | lan | an ipv4 address (default: local)' },\n autostart: { type: 'boolean', default: true, negativeDescription: 'do not start the entries marked autostart' },\n open: { type: 'boolean', description: 'open the panel in a browser once it is up' },\n foreground: { type: 'boolean', description: 'run in this process instead of detaching (systemd/docker)' },\n printConfig: { type: 'boolean', description: 'print the effective config and exit' },\n} as const\n\ninterface RawUpArgs {\n config?: string\n port?: string\n host?: string\n autostart?: boolean\n open?: boolean\n foreground?: boolean\n printConfig?: boolean\n}\n\n/** citty only parses; the port's range and the boolean defaults are decided here. */\nexport function toUpFlags(args: RawUpArgs): UpFlags {\n let port: number | undefined\n if (args.port !== undefined) {\n port = Number.parseInt(args.port, 10)\n if (!Number.isInteger(port) || port <= 0 || port > 65535)\n fail(`invalid port: ${args.port}`)\n }\n\n return {\n config: args.config,\n port,\n host: args.host,\n autostart: args.autostart !== false,\n open: args.open === true,\n foreground: args.foreground === true,\n printConfig: args.printConfig === true,\n }\n}\n\n/**\n * How to run this CLI again in the same runtime. Under tsx that means passing the\n * resolved loader too, because the daemon's working directory is the project's,\n * not the package's.\n */\nfunction runtimeArgs(): string[] {\n let resolved: string | null = null\n const resolveTsx = (): string => resolved ??= import.meta.resolve('tsx')\n\n return process.execArgv.map((arg) => {\n if (arg === 'tsx')\n return resolveTsx()\n if (arg.startsWith('--import=') && arg.slice('--import='.length) === 'tsx')\n return `--import=${resolveTsx()}`\n return arg\n })\n}\n\n/** One rotation is enough for a console log. */\nfunction rotateLog(file: string): void {\n try {\n if (fs.statSync(file).size < LOG_ROTATE_BYTES)\n return\n fs.rmSync(`${file}.1`, { force: true })\n fs.renameSync(file, `${file}.1`)\n }\n catch {\n // no log yet\n }\n}\n\nfunction tailLog(file: string, lines = 15): string {\n try {\n return fs.readFileSync(file, 'utf8').split('\\n').slice(-lines).join('\\n').trimEnd()\n }\n catch {\n return ''\n }\n}\n\nexport async function runUp(flags: UpFlags, entry: string): Promise<void> {\n const { runControlPlane } = await import('#src/index')\n\n // `--print-config` reports the effective config and returns; it never detaches,\n // because there would be a daemon left with nothing to serve.\n if (flags.foreground || flags.printConfig) {\n await runControlPlane({\n configPath: flags.config,\n port: flags.port,\n host: flags.host,\n autostart: flags.autostart,\n open: flags.open,\n printConfig: flags.printConfig,\n })\n return\n }\n\n const { clearRuntime, isProcessAlive, readRuntime } = await import('#src/helpers/daemon')\n const { daemonLogPath, dataRoot, projectDir } = await import('#src/helpers/paths')\n\n const existing = readRuntime()\n if (existing !== null && isProcessAlive(existing.pid)) {\n process.stdout.write(`${green('already running')} (pid ${existing.pid}) at ${existing.url}\\n`)\n process.stdout.write(`${dim('stop it with `home-hosted down`')}\\n`)\n return\n }\n if (existing !== null)\n clearRuntime()\n\n fs.mkdirSync(path.dirname(daemonLogPath), { recursive: true })\n rotateLog(daemonLogPath)\n const log = fs.openSync(daemonLogPath, 'a')\n\n const child = spawn(process.execPath, [...runtimeArgs(), entry, ...buildDaemonArgv(flags)], {\n detached: true,\n cwd: projectDir,\n env: { ...process.env, HHOSTED_HOME: dataRoot, HHOSTED_PROJECT: projectDir },\n stdio: ['ignore', log, log],\n windowsHide: true,\n })\n child.unref()\n fs.closeSync(log)\n\n const runtime = await waitForStartup(child)\n if (runtime === null) {\n const output = tailLog(daemonLogPath)\n process.stderr.write(`${paint('31', 'error')} the control panel did not start\\n`)\n if (output.length > 0)\n process.stderr.write(`${dim(`${daemonLogPath}:`)}\\n${output}\\n`)\n process.exit(1)\n }\n\n process.stdout.write(`${green('home-hosted is up')} (pid ${runtime.pid})\\n`)\n process.stdout.write(` ${bold(runtime.url)}\\n`)\n process.stdout.write(` ${dim(`project ${runtime.projectDir}`)}\\n`)\n process.stdout.write(` ${dim(`state ${runtime.dataRoot}`)}\\n`)\n process.stdout.write(` ${dim(`log ${runtime.logFile}`)}\\n`)\n}\n\nasync function waitForStartup(child: ChildProcess, timeoutMs = 20000) {\n const { readRuntime } = await import('#src/helpers/daemon')\n const deadline = Date.now() + timeoutMs\n\n for (;;) {\n if (child.exitCode !== null || child.signalCode !== null)\n return null\n\n const runtime = readRuntime()\n if (runtime !== null && runtime.pid === child.pid)\n return runtime\n\n if (Date.now() > deadline)\n return null\n await delay(150)\n }\n}\n\nexport function upCommand(entry: string) {\n return defineCommand({\n meta: { name: 'up', description: 'start the panel in the background (detached)' },\n args: upArgs,\n run: async ({ args }) => {\n await runUp(toUpFlags(args), entry)\n },\n })\n}\n","import { spawnSync } from 'node:child_process'\nimport process from 'node:process'\nimport { defineCommand } from 'citty'\nimport { delay, dim, green } from '#src/cli/io'\n\n/** `down` stops the panel and everything it supervises. */\n\nexport async function runDown(): Promise<void> {\n const { clearRuntime, isProcessAlive, readRuntime, requestShutdown } = await import('#src/helpers/daemon')\n\n const runtime = readRuntime()\n if (runtime === null) {\n process.stdout.write('home-hosted is not running\\n')\n return\n }\n if (!isProcessAlive(runtime.pid)) {\n clearRuntime()\n process.stdout.write('home-hosted is not running (removed a stale run.json)\\n')\n return\n }\n\n process.stdout.write(`stopping pid ${runtime.pid}…\\n`)\n // The panel's own endpoint stops supervised servers cleanly on every platform;\n // a signal is the fallback for a wedged or unreachable process.\n if (!(await requestShutdown(runtime)))\n signal(runtime.pid, 'SIGTERM')\n\n if (await waitForExit(runtime.pid, 20000)) {\n clearRuntime()\n process.stdout.write(`${green('stopped')}\\n`)\n return\n }\n\n process.stdout.write(`${dim('it did not stop in time — forcing')}\\n`)\n forceStop(runtime.pid)\n await waitForExit(runtime.pid, 5000)\n clearRuntime()\n process.stdout.write(`${green('stopped')} (forced)\\n`)\n}\n\nasync function waitForExit(pid: number, timeoutMs: number): Promise<boolean> {\n const { isProcessAlive } = await import('#src/helpers/daemon')\n const deadline = Date.now() + timeoutMs\n\n while (Date.now() < deadline) {\n if (!isProcessAlive(pid))\n return true\n await delay(200)\n }\n return !isProcessAlive(pid)\n}\n\nfunction signal(pid: number, name: NodeJS.Signals): void {\n try {\n process.kill(pid, name)\n }\n catch {\n // already gone\n }\n}\n\n/** Windows cannot deliver a graceful signal, so the whole tree is killed. */\nfunction forceStop(pid: number): void {\n if (process.platform === 'win32') {\n spawnSync('taskkill', ['/PID', String(pid), '/T', '/F'], { windowsHide: true })\n return\n }\n signal(pid, 'SIGKILL')\n}\n\nexport const downCommand = defineCommand({\n meta: { name: 'down', description: 'stop it, and everything it supervises' },\n run: async () => {\n await runDown()\n },\n})\n","import { defineCommand } from 'citty'\nimport { runDown } from '#src/cli/down'\nimport { runUp, toUpFlags, upArgs } from '#src/cli/up'\n\n/** `restart` is `down`, then `up` with exactly the flags it was given. */\n\nexport function restartCommand(entry: string) {\n return defineCommand({\n meta: { name: 'restart', description: 'down, then up' },\n args: upArgs,\n run: async ({ args }) => {\n await runDown()\n await runUp(toUpFlags(args), entry)\n },\n })\n}\n","import process from 'node:process'\nimport { defineCommand } from 'citty'\nimport { bold, dim, green, paint } from '#src/cli/io'\n\n/** `status` answers \"is it running, where, and how do I reach it\". */\n\nexport const statusArgs = {\n json: { type: 'boolean', description: 'print machine-readable JSON' },\n} as const\n\nexport async function runStatus(json: boolean): Promise<void> {\n const { isProcessAlive, probeRuntime, readRuntime } = await import('#src/helpers/daemon')\n const { UiService } = await import('#src/services/ui')\n const { dataRoot } = await import('#src/helpers/paths')\n const runtime = readRuntime()\n\n if (runtime === null) {\n if (json)\n process.stdout.write(`${JSON.stringify({ running: false }, null, 2)}\\n`)\n else\n process.stdout.write('home-hosted is not running\\n')\n process.exitCode = 1\n return\n }\n\n const running = isProcessAlive(runtime.pid)\n const probe = running ? await probeRuntime(runtime) : { reachable: false, degraded: false }\n\n if (json) {\n // The token is what authorises a local shutdown; a script only needs the rest.\n const { token: _token, ...safe } = runtime\n process.stdout.write(`${JSON.stringify({ running, answering: probe.reachable, degraded: probe.degraded, ...safe }, null, 2)}\\n`)\n if (!running)\n process.exitCode = 1\n return\n }\n\n const uptime = formatDuration(Date.now() - runtime.startedAt)\n const state = !running\n ? paint('31', 'stale (the process is gone)')\n : probe.degraded\n ? paint('33', 'running — a server needs attention')\n : probe.reachable ? green('running') : paint('33', 'running, but not answering')\n\n const ui = new UiService({ dataRoot })\n const rows: Array<[string, string]> = [\n ['status', state],\n ['pid', running ? `${runtime.pid} · up ${uptime}` : String(runtime.pid)],\n ['url', `${runtime.url} ${dim(`(${runtime.protocol})`)}`],\n ['version', runtime.version],\n ['project', runtime.projectDir],\n ['state', runtime.dataRoot],\n ['config', runtime.configPath],\n ['log', runtime.logFile],\n ['ui', ui.custom ? `custom — ${ui.status().meta?.name ?? 'installed'} (revert with \\`home-hosted ui-revert\\`)` : 'stock'],\n ]\n\n process.stdout.write(`${bold(`home-hosted ${runtime.version}`)}\\n`)\n for (const [label, value] of rows)\n process.stdout.write(` ${dim(label.padEnd(8))} ${value}\\n`)\n if (!running)\n process.exitCode = 1\n}\n\nfunction formatDuration(ms: number): string {\n const seconds = Math.max(0, Math.round(ms / 1000))\n if (seconds < 60)\n return `${seconds}s`\n const minutes = Math.floor(seconds / 60)\n if (minutes < 60)\n return `${minutes}m`\n const hours = Math.floor(minutes / 60)\n if (hours < 24)\n return `${hours}h ${minutes % 60}m`\n return `${Math.floor(hours / 24)}d ${hours % 24}h`\n}\n\nexport const statusCommand = defineCommand({\n meta: { name: 'status', description: 'is it running, where, and how to reach it' },\n args: statusArgs,\n run: async ({ args }) => {\n await runStatus(args.json === true)\n },\n})\n","import process from 'node:process'\nimport { defineCommand } from 'citty'\nimport { dim, fail, green, promptHidden } from '#src/cli/io'\nimport { SecretsStore } from '#src/config/secrets'\nimport { defaultSecretsPath } from '#src/helpers/paths'\n\n/** `set-password` sets the panel password without the API. */\n\nexport const setPasswordArgs = {\n clear: { type: 'boolean', description: 'remove the password, which disables authentication' },\n} as const\n\nexport async function runSetPassword(clear: boolean): Promise<void> {\n const store = new SecretsStore(defaultSecretsPath)\n\n if (clear) {\n store.clearPassword()\n process.stdout.write(`cleared the control panel password in ${defaultSecretsPath}\\n`)\n process.stdout.write(`${dim('authentication stays disabled until you enable it again in the settings page')}\\n`)\n return\n }\n\n const interactive = process.stdin.isTTY === true\n let password = process.env.HHOSTED_PASSWORD\n\n if (password === undefined && interactive) {\n password = await promptHidden('New control panel password: ')\n const again = await promptHidden('Repeat it: ')\n if (password !== again)\n fail('the passwords do not match')\n }\n\n if (password === undefined || password.length === 0) {\n fail('no password given: run interactively, or set HHOSTED_PASSWORD for a non-interactive run')\n }\n\n store.setPassword(password)\n process.stdout.write(`${green('password stored')} in ${defaultSecretsPath} (mode 0600)\\n`)\n if (password.length < 8)\n process.stdout.write(`${dim(`\"${password}\" is short — easy to guess if the panel is reachable beyond loopback`)}\\n`)\n process.stdout.write(`${dim('restart the panel for it to take effect: home-hosted restart')}\\n`)\n}\n\nexport const setPasswordCommand = defineCommand({\n meta: { name: 'set-password', description: 'set the panel password without the API' },\n args: setPasswordArgs,\n run: async ({ args }) => {\n await runSetPassword(args.clear === true)\n },\n})\n","import process from 'node:process'\nimport { defineCommand } from 'citty'\nimport { bold, dim, fail, green, promptHidden } from '#src/cli/io'\nimport { generateApiToken, SecretsStore } from '#src/config/secrets'\nimport { defaultSecretsPath } from '#src/helpers/paths'\n\n/** `set-token` sets the bearer credential scripts and agents use. */\n\nconst DEFAULT_PORT = 3999\n\nexport const setTokenArgs = {\n generate: { type: 'boolean', description: 'create a strong token and print it once' },\n clear: { type: 'boolean', description: 'remove the token, so it stops working' },\n} as const\n\nexport async function runSetToken(generate: boolean, clear: boolean): Promise<void> {\n if (generate && clear)\n fail('use either --generate or --clear, not both')\n\n const store = new SecretsStore(defaultSecretsPath)\n\n if (clear) {\n if (!store.apiTokenSet) {\n process.stdout.write('no API token is set — nothing to clear\\n')\n return\n }\n store.clearApiToken()\n process.stdout.write(`${green('API token cleared')} in ${defaultSecretsPath} — it stops working immediately\\n`)\n return\n }\n\n let token: string | null = process.env.HHOSTED_TOKEN ?? null\n if (generate)\n token = generateApiToken()\n else if (token === null && process.stdin.isTTY === true)\n token = await promptHidden('API token: ')\n if (token !== null)\n token = token.trim()\n if (token === null || token.length === 0) {\n fail('no token given: run `home-hosted set-token --generate`, set HHOSTED_TOKEN, or paste one interactively')\n }\n\n store.setApiToken(token)\n process.stdout.write(`${green(generate ? 'token generated' : 'token stored')} in ${defaultSecretsPath} (mode 0600)\\n`)\n if (generate) {\n process.stdout.write(` ${bold(token)}\\n`)\n process.stdout.write(`${dim(' shown once — only its SHA-256 is kept on disk, so copy it now')}\\n`)\n }\n else {\n process.stdout.write(`${dim(` stored as ${token.slice(0, 8)}… — the file keeps only its hash`)}\\n`)\n }\n\n const { readRuntime } = await import('#src/helpers/daemon')\n // Only the panel that owns *this* state directory is worth asking: guessing a\n // port would probe someone else's panel and call the mismatch a failure.\n const runtime = readRuntime()\n const base = (runtime?.url ?? `http://127.0.0.1:${DEFAULT_PORT}`).replace(/\\/+$/, '')\n\n if (runtime !== null) {\n const verified = await verifyToken(base, token)\n if (verified === true)\n process.stdout.write(`${dim(`verified: ${base}/api/auth/session accepted it`)}\\n`)\n else if (verified === false)\n process.stdout.write(`${dim(`the panel at ${base} did not accept it — is it running with this state directory?`)}\\n`)\n }\n\n process.stdout.write(`Use it from a script or an agent:\\n`)\n process.stdout.write(` ${bold(`curl -H \"Authorization: Bearer ${generate ? token : '<token>'}\" ${base}/api/state`)}\\n`)\n process.stdout.write(`${dim('It needs no restart, outlives sessions, and holds the same access as a signed-in browser.')}\\n`)\n process.stdout.write(`${dim('Remove it any time with: home-hosted set-token --clear')}\\n`)\n if (store.usingDefaultPassword)\n process.stdout.write(`${dim('The panel password is still the default — change it before the panel is reachable beyond loopback.')}\\n`)\n}\n\n/** Asks the live panel whether it accepts the token, without failing when it cannot. */\nasync function verifyToken(base: string, token: string): Promise<boolean | null> {\n // An https endpoint is usually TLS this project generated itself, which a plain\n // fetch refuses; the liveness probe in `helpers/daemon` is the one that knows how.\n if (!base.startsWith('http://'))\n return null\n try {\n const response = await fetch(`${base}/api/auth/session`, { headers: { authorization: `Bearer ${token}` } })\n if (!response.ok)\n return false\n const body = await response.json() as { authenticated?: boolean }\n return body.authenticated === true\n }\n catch {\n return null\n }\n}\n\nexport const setTokenCommand = defineCommand({\n meta: { name: 'set-token', description: 'set the API token that scripts and agents use' },\n args: setTokenArgs,\n run: async ({ args }) => {\n await runSetToken(args.generate === true, args.clear === true)\n },\n})\n","import fs from 'node:fs'\nimport path from 'node:path'\nimport process from 'node:process'\nimport { defineCommand } from 'citty'\nimport { dim, fail, green, prompt } from '#src/cli/io'\nimport { applyConfigMigrations, CONFIG_SCHEMA, planConfigMigrations } from '#src/config/migrations'\nimport { parseConfig, stampConfig } from '#src/config/parse'\nimport { writeFileAtomic } from '#src/helpers/atomic'\nimport { defaultConfigPath } from '#src/helpers/paths'\nimport { appVersion } from '#src/helpers/version'\n\n/**\n * `migrate` brings `servers.config.json` up to the schema this release understands.\n *\n * Deliberately loud and deliberate: it prints every step first, backs the file up\n * before writing, refuses to write a config it cannot read, and never runs on its\n * own — a detached daemon cannot prompt, so consent comes from `--yes`, from\n * `HHOSTED_MIGRATE=allow`, or from a person at a terminal.\n */\n\nexport const migrateArgs = {\n config: { type: 'string', alias: 'c', description: 'servers config (default: <state>/servers.config.json)' },\n dryRun: { type: 'boolean', description: 'print what would change, write nothing' },\n yes: { type: 'boolean', alias: 'y', description: 'apply without asking (or set HHOSTED_MIGRATE=allow)' },\n} as const\n\nexport async function runMigrate(config: string | undefined, dryRun: boolean, yes: boolean): Promise<void> {\n const file = config ?? defaultConfigPath\n if (!fs.existsSync(file))\n fail(`no config at ${file} — nothing to migrate`)\n\n let raw: Record<string, unknown>\n try {\n raw = JSON.parse(fs.readFileSync(file, 'utf8')) as Record<string, unknown>\n }\n catch (error) {\n fail(`cannot read ${file}: ${error instanceof Error ? error.message : String(error)}`)\n }\n\n const meta = typeof raw.meta === 'object' && raw.meta !== null ? raw.meta as { schema?: number, writtenBy?: string } : {}\n const from = typeof meta.schema === 'number' ? meta.schema : CONFIG_SCHEMA\n const plan = planConfigMigrations(from)\n\n if (plan.tooNew) {\n fail(`${file} was written by home-hosted ${meta.writtenBy ?? 'a newer release'} (config schema ${from});\\n this release understands schema ${plan.to}. Install that version, or edit the file yourself.`)\n }\n\n const stamped = stampConfig(raw)\n const upToDate = plan.steps.length === 0\n if (upToDate && !dryRun && JSON.stringify(stamped) !== JSON.stringify(raw)) {\n writeFileAtomic(file, `${JSON.stringify(stamped, null, 2)}\\n`)\n process.stdout.write(`${green('config stamped')} in ${file} — written by home-hosted ${appVersion()}, schema ${plan.to}\\n`)\n return\n }\n\n if (upToDate) {\n process.stdout.write(`config schema ${from} is already what home-hosted ${appVersion()} understands — nothing to migrate\\n`)\n return\n }\n\n process.stdout.write(`migrating ${file}: config schema ${from} → ${plan.to}\\n`)\n for (const [index, step] of plan.steps.entries())\n process.stdout.write(` ${index + 1}. ${step.describe}\\n`)\n\n if (dryRun) {\n process.stdout.write(`${dim(`nothing was written (--dry-run, ${plan.steps.length} step(s) pending)`)}\\n`)\n return\n }\n\n const consented = yes || (process.env.HHOSTED_MIGRATE ?? '').toLowerCase() === 'allow'\n if (!consented) {\n if (process.stdin.isTTY !== true) {\n fail(`this config needs ${plan.steps.length} migration(s) and this session cannot ask.\\n re-run with --yes, or set HHOSTED_MIGRATE=allow for unattended runs`)\n }\n const answer = await prompt(`Apply ${plan.steps.length} migration(s) to ${path.basename(file)}? [y/N] `)\n if (!/^yes$|^y$/i.test(answer.trim())) {\n process.stdout.write('cancelled — nothing was written\\n')\n return\n }\n }\n\n const { config: migrated, applied } = applyConfigMigrations(raw, from)\n const parsed = parseConfig(migrated)\n if (parsed.config === null) {\n fail(`the migration produced a config this release cannot read:\\n ${parsed.errors.join('\\n ')}`)\n }\n\n const backup = `${file}.bak`\n fs.copyFileSync(file, backup)\n writeFileAtomic(file, `${JSON.stringify(stampConfig(migrated), null, 2)}\\n`)\n process.stdout.write(`${green(`migrated to schema ${plan.to}`)} (${applied.length} step(s)) in ${file}\\n`)\n process.stdout.write(` ${dim(`previous file kept at ${backup}`)}\\n`)\n for (const key of parsed.unknownKeys)\n process.stdout.write(` ${dim(`still ignoring an unrecognized key: ${key}`)}\\n`)\n}\n\nexport const migrateCommand = defineCommand({\n meta: { name: 'migrate', description: 'bring the config up to this release\\'s schema' },\n args: migrateArgs,\n run: async ({ args }) => {\n await runMigrate(args.config, args.dryRun === true, args.yes === true)\n },\n})\n","import type { Stats } from 'node:fs'\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport { appVersion } from '#src/helpers/version'\n\nexport type PackageManager = 'pnpm' | 'npm' | 'yarn' | 'bun'\n\nexport interface InitOptions {\n dir: string\n name: string\n pm: PackageManager\n install: boolean\n git: boolean\n}\n\nexport interface InitResult {\n dir: string\n created: boolean\n files: string[]\n installed: boolean\n git: boolean\n}\n\n/** The scripts every scaffolded project gets; all of them keep state inside the project. */\nexport function projectScripts(): Record<string, string> {\n const withState = (args: string): string => `home-hosted ${args} --home ./state`\n return {\n 'up': withState('up'),\n 'down': withState('down'),\n 'restart': withState('restart'),\n 'status': withState('status'),\n 'set-password': withState('set-password'),\n 'set-token': withState('set-token'),\n 'migrate': withState('migrate'),\n }\n}\n\n/** `home-hosted` is pinned to the version that wrote the project, and open to newer ones. */\nexport function projectManifest(name: string): string {\n return `${JSON.stringify({\n name,\n version: '0.1.0',\n private: true,\n description: 'Servers supervised by home-hosted.',\n type: 'module',\n engines: { node: '>=24.0.0' },\n scripts: projectScripts(),\n dependencies: { 'home-hosted': `^${appVersion()}` },\n }, null, 2)}\\n`\n}\n\n/**\n * State is generated, so it stays out — except the file that declares the servers,\n * which is the one thing worth committing.\n */\nexport function projectGitignore(): string {\n return `node_modules/\n\n# home-hosted: state is local, the server definitions are tracked\nstate/*\n!state/servers.config.json\ndata/\n`\n}\n\nexport function isEmptyDir(dir: string): boolean {\n try {\n return fs.readdirSync(dir).every(entry => entry === '.git')\n }\n catch {\n return true\n }\n}\n\nfunction assertUsableDir(dir: string): void {\n let stats: Stats | null = null\n try {\n stats = fs.statSync(dir)\n }\n catch {\n return\n }\n if (!stats.isDirectory())\n throw new Error(`${dir} exists and is not a directory`)\n if (!isEmptyDir(dir))\n throw new Error(`${dir} is not empty — pick another directory, or empty it first`)\n}\n\n/** Writes the project skeleton. Nothing is installed or initialized here. */\nexport function scaffold(options: InitOptions): InitResult {\n const dir = path.resolve(options.dir)\n assertUsableDir(dir)\n\n const existed = fs.existsSync(dir)\n fs.mkdirSync(dir, { recursive: true })\n\n const files: Array<[string, string]> = [\n ['package.json', projectManifest(options.name)],\n ['.gitignore', projectGitignore()],\n ]\n for (const [name, contents] of files)\n fs.writeFileSync(path.join(dir, name), contents)\n\n return {\n dir,\n created: !existed,\n files: files.map(([name]) => name),\n installed: false,\n git: false,\n }\n}\n\nexport const PACKAGE_MANAGERS: PackageManager[] = ['pnpm', 'npm', 'yarn', 'bun']\n\n/** The package manager this machine actually has, preferring pnpm. */\nexport function detectPackageManager(exists: (command: string) => boolean): PackageManager {\n const found = PACKAGE_MANAGERS.find(pm => exists(pm))\n return found ?? 'npm'\n}\n\n/** How to run one of the project's scripts with a given package manager. */\nexport function runCommand(pm: PackageManager, script: string): string {\n return pm === 'npm' ? `npm run ${script}` : `${pm} run ${script}`\n}\n\n/** Install arguments for a scaffolded project (the manifest already lists the dependency). */\nexport function installArgs(pm: PackageManager): string[] {\n return pm === 'yarn' ? [] : ['install']\n}\n","import { spawnSync } from 'node:child_process'\nimport path from 'node:path'\nimport process from 'node:process'\nimport { defineCommand } from 'citty'\nimport { bold, confirm, dim, fail, green, paint, prompt } from '#src/cli/io'\nimport { detectPackageManager, installArgs, PACKAGE_MANAGERS, runCommand, scaffold } from '#src/services/init'\n\n/**\n * `init` scaffolds a project that keeps its whole setup — state, data and the\n * server definitions — inside its own directory. Interactive by nature, but\n * `--yes` takes every default so an agent or a CI job can run it unattended.\n */\n\nexport const initArgs = {\n dir: { type: 'string', description: 'where to scaffold (default: ./my-servers)' },\n name: { type: 'string', description: 'package name (default: the directory name)' },\n pm: { type: 'string', description: 'pnpm | npm | yarn | bun (default: the first one installed)' },\n install: { type: 'boolean', default: true, negativeDescription: 'write the files, install nothing' },\n yes: { type: 'boolean', alias: 'y', description: 'take every default, ask nothing' },\n} as const\n\nfunction which(command: string): boolean {\n const probe = spawnSync(command, ['--version'], { stdio: 'ignore', shell: process.platform === 'win32' })\n return probe.status === 0\n}\n\nexport async function runInit(options: { dir?: string, name?: string, pm?: string, noInstall: boolean, yes: boolean }): Promise<void> {\n const assumeYes = options.yes\n if (!assumeYes && process.stdin.isTTY !== true) {\n fail('init needs a terminal to ask in.\\n take the defaults with: home-hosted init --yes [--dir <dir>]')\n }\n\n const defaultDir = options.dir ?? './my-servers'\n const dir = assumeYes ? defaultDir : (await prompt(`Project directory (${defaultDir}) `)).trim() || defaultDir\n const defaultName = path.basename(path.resolve(dir))\n const name = options.name ?? (assumeYes ? defaultName : (await prompt(`Package name (${defaultName}) `)).trim() || defaultName)\n\n let pm = options.pm\n if (pm !== undefined && !(PACKAGE_MANAGERS as string[]).includes(pm))\n fail(`unknown package manager: ${pm} (expected one of ${PACKAGE_MANAGERS.join(', ')})`)\n const detected = detectPackageManager(which)\n if (pm === undefined)\n pm = assumeYes ? detected : (await prompt(`Package manager (${detected}) `)).trim() || detected\n\n const install = options.noInstall\n ? false\n : assumeYes || (await confirm('Install the dependencies now?', true))\n const git = assumeYes ? which('git') : which('git') && (await confirm('Initialize a git repository?', true))\n\n try {\n const result = scaffold({ dir, name, pm: pm as never, install, git })\n process.stdout.write(`${green('project created')} in ${result.dir}\\n`)\n for (const file of result.files)\n process.stdout.write(` ${dim(file)}\\n`)\n }\n catch (error) {\n fail(error instanceof Error ? error.message : String(error))\n }\n\n const target = path.resolve(dir)\n if (git) {\n spawnSync('git', ['init', '-q'], { cwd: target, stdio: 'inherit', shell: process.platform === 'win32' })\n process.stdout.write(` ${dim('git repository initialized')}\\n`)\n }\n\n if (install) {\n process.stdout.write(`${dim(`installing with ${pm}…`)}\\n`)\n const result = spawnSync(pm as string, installArgs(pm as never), {\n cwd: target,\n stdio: 'inherit',\n shell: process.platform === 'win32',\n })\n if (result.status !== 0) {\n process.stdout.write(`${paint('33', 'install failed')} — run it yourself in ${target}\\n`)\n }\n }\n\n // A path inside the working directory reads better relative; anything else absolute.\n const relative = path.relative(process.cwd(), target)\n const where = relative.length === 0 || relative.startsWith('..') ? target : relative\n const cd = relative.length === 0 ? '' : `cd ${where} && `\n process.stdout.write(`\\nNext:\\n`)\n process.stdout.write(` ${bold(`${cd}${runCommand(pm as never, 'up')}`)} start the panel (default password \\`hh\\`)\\n`)\n process.stdout.write(` ${dim('then change that password under Settings → Authentication, and add your servers')}\\n`)\n process.stdout.write(` ${dim(`${runCommand(pm as never, 'set-token')} --generate for scripts and agents`)}\\n`)\n}\n\nexport const initCommand = defineCommand({\n meta: { name: 'init', description: 'scaffold a project that keeps its state in the repo' },\n args: initArgs,\n run: async ({ args }) => {\n await runInit({\n dir: args.dir,\n name: args.name,\n pm: args.pm,\n noInstall: args.install === false,\n yes: args.yes === true,\n })\n },\n})\n","import type { GithubAsset } from '#src/providers/ui-release'\nimport fs from 'node:fs'\nimport process from 'node:process'\nimport { parseArgs } from 'node:util'\nimport { defineCommand } from 'citty'\nimport { prompt, style } from '#src/cli/io'\nimport {\n assetDownloadUrl,\n DEFAULT_REPO,\n defaultReleaseTag,\n downloadToTemp,\n fallbackUiName,\n fetchRelease,\n isGithubHost,\n isUiAsset,\n matchAsset,\n noAssetsMessage,\n parseFileSource,\n parseRepoSlug,\n} from '#src/providers/ui-release'\n\n/**\n * `home-hosted ui-switch` installs the panel's frontend UI without the settings\n * page: a GitHub release asset (the default), a local zip, or an http(s) URL.\n *\n * This module is only ever imported dynamically, after `--home` has been applied,\n * so the modules that read the state directories are imported inside the command\n * rather than at the top. The release-side helpers it shares with `ui-update` are\n * in `#src/providers/ui-release`, and re-exported here so existing importers keep working.\n */\n\nexport * from '#src/providers/ui-release'\n\n/** What the command needs from the CLI that owns the readline prompts and the colours. */\nexport interface UiSwitchIo {\n write: (text: string) => void\n prompt: (question: string) => Promise<string>\n style: {\n bold: (text: string) => string\n dim: (text: string) => string\n green: (text: string) => string\n }\n}\n\ninterface SwitchContext {\n io: UiSwitchIo\n version: string\n token: string | null\n}\n\nexport async function uiSwitch(argv: string[], io: UiSwitchIo): Promise<void> {\n const { values } = parseArgs({\n args: argv,\n options: {\n repo: { type: 'string' },\n tag: { type: 'string' },\n asset: { type: 'string' },\n file: { type: 'string' },\n list: { type: 'boolean' },\n token: { type: 'string' },\n yes: { type: 'boolean', short: 'y' },\n },\n allowPositionals: false,\n })\n\n const { appVersion } = await import('#src/helpers/version')\n const context: SwitchContext = {\n io,\n version: appVersion(),\n token: values.token ?? process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN ?? null,\n }\n\n if (values.file !== undefined) {\n if (values.repo !== undefined || values.tag !== undefined || values.asset !== undefined || values.list === true)\n throw new Error('--file installs a zip directly; it cannot be combined with --repo, --tag, --asset or --list')\n await installFromFile(values.file, context)\n return\n }\n\n const repo = parseRepoSlug(values.repo ?? DEFAULT_REPO)\n if (repo === null)\n throw new Error(`invalid --repo \"${values.repo}\" — expected an \"owner/name\" slug, e.g. ${DEFAULT_REPO}`)\n\n const requestedTag = values.tag ?? defaultReleaseTag(repo, context.version)\n const release = await fetchRelease(repo, requestedTag, context)\n const skipped = release.assets.filter(asset => !isUiAsset(asset.name ?? '')).map(asset => asset.name ?? '?')\n const usable = release.assets.filter(asset => isUiAsset(asset.name ?? ''))\n const where = `${repo.owner}/${repo.name}@${release.tag}`\n\n if (values.list === true) {\n if (usable.length === 0)\n throw new Error(noAssetsMessage(where, skipped, requestedTag))\n // One write, so `ui-switch --list | head` does not die on a broken pipe.\n const lines = [`${io.style.bold(where)} — ${usable.length} usable UI asset(s)`]\n for (const asset of usable)\n lines.push(` ${asset.name}`)\n if (skipped.length > 0)\n lines.push(io.style.dim(` skipped (not a .zip): ${skipped.join(', ')}`))\n io.write(`${lines.join('\\n')}\\n`)\n return\n }\n\n if (usable.length === 0)\n throw new Error(noAssetsMessage(where, skipped, requestedTag))\n\n const chosen = await chooseAsset(usable, values, context)\n if (chosen === null) {\n io.write('cancelled — nothing was installed\\n')\n return\n }\n\n await installFromUrl(assetDownloadUrl(chosen), fallbackUiName(chosen.name ?? ''), context)\n}\n\nasync function chooseAsset(assets: GithubAsset[], values: { asset?: string, yes?: boolean }, context: SwitchContext): Promise<GithubAsset | null> {\n const { io } = context\n\n if (values.asset !== undefined) {\n const matched = matchAsset(assets.map(asset => asset.name ?? ''), values.asset)\n if (!matched.ok)\n throw new Error(matched.error)\n return assets.find(asset => asset.name === matched.name)!\n }\n\n const interactive = process.stdin.isTTY === true && values.yes !== true\n if (!interactive) {\n if (assets.length === 1)\n return assets[0]!\n throw new Error([\n `${assets.length} UI assets are available and this session cannot ask which one:`,\n ...assets.map(asset => ` ${asset.name}`),\n ' pick one with --asset <name>, or list them with --list',\n ].join('\\n'))\n }\n\n // One write, so the listing is complete before the prompt (and a piped stdout\n // cannot interleave with it).\n io.write(`${[io.style.bold('Available UI assets'), ...assets.map((asset, index) => ` ${index + 1}) ${asset.name}`)].join('\\n')}\\n`)\n\n for (;;) {\n const answer = (await io.prompt(`Select an asset [1-${assets.length}] (empty to cancel) `)).trim()\n if (answer.length === 0)\n return null\n\n if (/^\\d+$/.test(answer)) {\n const index = Number.parseInt(answer, 10)\n if (index >= 1 && index <= assets.length)\n return assets[index - 1]!\n }\n\n const matched = matchAsset(assets.map(asset => asset.name ?? ''), answer)\n if (matched.ok)\n return assets.find(asset => asset.name === matched.name)!\n io.write(` ${io.style.dim(matched.error)}\\n`)\n }\n}\n\nasync function installFromFile(value: string, context: SwitchContext): Promise<void> {\n const source = parseFileSource(value)\n if (source.kind === 'path') {\n if (!fs.existsSync(source.path))\n throw new Error(`no file at ${source.path}`)\n if (!fs.statSync(source.path).isFile())\n throw new Error(`${source.path} is not a file`)\n await installArchive(source.path, fallbackUiName(source.path), context)\n return\n }\n await installFromUrl(source.url, fallbackUiName(new URL(source.url).pathname), context)\n}\n\nasync function installFromUrl(url: string, fallbackName: string, context: SwitchContext): Promise<void> {\n const headers: Record<string, string> = {\n 'accept': 'application/octet-stream',\n 'user-agent': `home-hosted/${context.version}`,\n }\n if (context.token !== null && context.token.length > 0 && isGithubHost(url))\n headers.authorization = `Bearer ${context.token}`\n\n const download = await downloadToTemp(url, headers, context)\n try {\n await installArchive(download.file, fallbackName, context)\n }\n finally {\n fs.rmSync(download.dir, { recursive: true, force: true })\n }\n}\n\nasync function installArchive(archivePath: string, fallbackName: string, context: SwitchContext): Promise<void> {\n const { UiService } = await import('#src/services/ui')\n const { dataRoot } = await import('#src/helpers/paths')\n const ui = new UiService({ dataRoot })\n const result = await ui.install(archivePath, fallbackName)\n\n if (!result.ok)\n throw new Error(`nothing was installed: ${result.error}`)\n\n const { meta } = result\n const label = meta.version === null ? meta.name : `${meta.name} ${meta.version}`\n context.io.write(`${context.io.style.green('UI installed')} — ${label}\\n`)\n context.io.write(` ${context.io.style.dim('state')} ${ui.directory}\\n`)\n context.io.write(` ${context.io.style.dim('files')} ${meta.files}\\n`)\n context.io.write(`refresh the browser to see it\\n`)\n}\n\n/**\n * The citty entry. `ui-switch` keeps parsing its own flags (and its own error\n * messages) inside `uiSwitch`; citty dispatches with the argv it was handed, so\n * the invocation changes and the helpers do not.\n */\nexport const uiSwitchCommand = defineCommand({\n meta: { name: 'ui-switch', description: 'install a UI from a release asset, a zip file or a URL' },\n run: async ({ rawArgs }) => {\n await uiSwitch(rawArgs, {\n write: text => process.stdout.write(text),\n prompt,\n style,\n })\n },\n})\n","import type { UiSourceContext } from '#src/providers/ui-release'\nimport type { UiMeta } from '#src/shared/contracts'\nimport fs from 'node:fs'\nimport process from 'node:process'\nimport { parseArgs } from 'node:util'\nimport { defineCommand } from 'citty'\nimport { prompt, style } from '#src/cli/io'\nimport {\n assetDownloadUrl,\n compareTags,\n DEFAULT_REPO,\n downloadToTemp,\n fetchRelease,\n fetchReleases,\n isOwnRepo,\n isUiAsset,\n matchAsset,\n parseRepoSlug,\n repoSlug,\n} from '#src/providers/ui-release'\n\n/**\n * `home-hosted ui-update` keeps an installed UI in step with the panel it talks to.\n *\n * Three cases, in the order they are decided:\n * the stock UI is always current by definition — nothing to do;\n * a UI from our own repo is *paired* with a release, so it is updated to the tag\n * of the running panel without asking (that pairing is the whole point);\n * anyone else's UI declares its own `repo`, and the person picks from the releases\n * that actually carry the asset they are using — newer ones, or `--old` for older.\n */\n\ninterface UpdateContext extends UiSourceContext {\n /** null when the session cannot be asked (not a TTY, or `--yes`). */\n ask: ((question: string) => Promise<string>) | null\n}\n\n/** The identity an installed UI declared about itself, when it declared one. */\nexport interface UiIdentity {\n name: string\n version: string | null\n repo: string | null\n tag: string | null\n asset: string | null\n unix: number | null\n}\n\nexport type UiUpdatePlan\n /** Nothing installed: the stock UI is served, and it is always current. */\n = { kind: 'stock' }\n /** Installed, but it never said where it came from, so there is nothing to follow. */\n | { kind: 'unidentifiable', identity: UiIdentity }\n /** Ours: paired with this panel's release. `target` already matches when current. */\n | { kind: 'official', identity: UiIdentity, target: string }\n /** Someone else's: the person chooses from `candidates`. */\n | { kind: 'choice', identity: UiIdentity, asset: string, candidates: UpdateCandidate[] }\n\nexport interface UpdateCandidate {\n tag: string\n asset: string\n}\n\n/**\n * Which of a release list applies to what is installed. A candidate is only offered when\n * its assets carry the asset the person is actually using, so a UI is never swapped for a\n * different flavour by an update.\n */\nexport function usableCandidates(\n releases: Array<{ tag: string, assets: Array<{ name?: string }> }>,\n asset: string,\n order: 'newer' | 'older',\n currentTag: string | null,\n): UpdateCandidate[] {\n const found: UpdateCandidate[] = []\n\n for (const release of releases) {\n const names = release.assets.map(entry => entry.name ?? '').filter(isUiAsset)\n const matched = matchAsset(names, asset)\n if (!matched.ok)\n continue\n if (currentTag !== null) {\n const difference = compareTags(release.tag, currentTag)\n if (order === 'newer' && difference <= 0)\n continue\n if (order === 'older' && difference >= 0)\n continue\n }\n found.push({ tag: release.tag, asset: matched.name })\n }\n\n return found\n}\n\n/** What the asset the UI is using is likely called, when it never declared one. */\nexport function impliedAsset(identity: UiIdentity): string | null {\n if (identity.asset !== null && identity.asset.length > 0)\n return identity.asset\n if (identity.name.length > 0)\n return `${identity.name}.zip`\n return null\n}\n\n/**\n * The decision, with no I/O in it: given what is installed and this panel's release,\n * what should `ui-update` do? `releases` is only consulted for someone else's UI.\n */\nexport function planUiUpdate(\n installed: UiIdentity | null,\n runningTag: string,\n releases: Array<{ tag: string, assets: Array<{ name?: string }> }> = [],\n order: 'newer' | 'older' = 'newer',\n): UiUpdatePlan {\n if (installed === null)\n return { kind: 'stock' }\n\n const repo = installed.repo === null ? null : parseRepoSlug(installed.repo)\n if (repo === null)\n return { kind: 'unidentifiable', identity: installed }\n\n if (isOwnRepo(repo))\n return { kind: 'official', identity: installed, target: runningTag }\n\n const asset = impliedAsset(installed)\n if (asset === null)\n return { kind: 'unidentifiable', identity: installed }\n\n return { kind: 'choice', identity: installed, asset, candidates: usableCandidates(releases, asset, order, installed.tag) }\n}\n\n/** `home-hosted ui-update` — see the module comment for the three cases. */\nexport async function uiUpdate(argv: string[], io: { write: (text: string) => void, prompt: (question: string) => Promise<string>, style: { bold: (text: string) => string, dim: (text: string) => string, green: (text: string) => string } }): Promise<void> {\n const { values } = parseArgs({\n args: argv,\n options: {\n tag: { type: 'string' },\n asset: { type: 'string' },\n token: { type: 'string' },\n yes: { type: 'boolean', short: 'y' },\n old: { type: 'boolean' },\n check: { type: 'boolean' },\n repo: { type: 'string' },\n },\n allowPositionals: false,\n })\n\n const { appVersion } = await import('#src/helpers/version')\n const { dataRoot } = await import('#src/helpers/paths')\n const { UiService } = await import('#src/services/ui')\n\n const context: UpdateContext = {\n io: { write: io.write, style: io.style },\n ask: process.stdin.isTTY === true && values.yes !== true ? io.prompt : null,\n version: appVersion(),\n token: values.token ?? process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN ?? null,\n }\n\n const ui = new UiService({ dataRoot })\n const status = ui.status()\n const installed = identityOf(status.meta)\n\n if (installed === null) {\n io.write(`${io.style.dim('the stock UI is in use — it ships with the panel and is always current')}\\n`)\n return\n }\n\n // `--repo` makes a UI that never declared one updateable, without reinstalling by hand.\n if (values.repo !== undefined) {\n const repo = parseRepoSlug(values.repo)\n if (repo === null)\n throw new Error(`invalid --repo \"${values.repo}\" — expected an \"owner/name\" slug, e.g. ${DEFAULT_REPO}`)\n installed.repo = repoSlug(repo)\n }\n\n const order = values.old === true ? 'older' : 'newer'\n const repo = installed.repo === null ? null : parseRepoSlug(installed.repo)\n\n // A request to install cannot be honoured without a repo to install from: saying so\n // beats discarding the flag and printing the \"does not say where it came from\" notice.\n if (values.tag !== undefined && repo === null) {\n throw new Error([\n '--tag needs a repository to fetch from, and this UI does not declare one',\n ` pass it: home-hosted ui-update --repo ${DEFAULT_REPO} --tag ${values.tag}`,\n ].join('\\n'))\n }\n\n // `--tag` names the release outright, so nothing has to be listed or chosen. `--check`\n // outranks it: the flag documents itself as installing nothing, so it must come first\n // or `--check --tag x` would install x.\n if (values.check === true && repo !== null) {\n if (isOwnRepo(repo)) {\n const target = values.tag ?? `v${context.version}`\n printCheck(io, installed, target)\n return\n }\n const releases = await fetchReleases(repo, context)\n const asset = impliedAsset(installed)\n printCheck(io, installed, null, asset === null ? [] : usableCandidates(releases, asset, order, installed.tag))\n return\n }\n\n if (values.tag !== undefined && repo !== null) {\n await installTag(repo, values.asset ?? installed.asset ?? null, values.tag, context)\n return\n }\n\n let releases: Array<{ tag: string, assets: Array<{ name?: string }> }> = []\n if (repo !== null && !isOwnRepo(repo))\n releases = await fetchReleases(repo, context)\n\n const plan = planUiUpdate(installed, `v${context.version}`, releases, order)\n if (plan.kind === 'unidentifiable') {\n io.write(`${io.style.bold('this UI does not say where it came from')} — nothing to follow automatically\\n`)\n printIdentity(io, plan.identity)\n io.write(` ${io.style.dim('point it at a repo to make this work: home-hosted ui-update --repo owner/name')}\\n`)\n return\n }\n\n if (plan.kind === 'official') {\n if (plan.identity.tag === plan.target) {\n io.write(`${io.style.green('already current')} — ${io.style.bold(plan.identity.name)} is at ${plan.target}\\n`)\n return\n }\n\n io.write(`${io.style.bold(`${plan.identity.name} ${plan.identity.version ?? ''}`.trim())} is on ${plan.identity.tag ?? 'an unknown tag'}; this panel is ${plan.target} — updating\\n`)\n await installTag(repo!, plan.identity.asset ?? values.asset ?? null, plan.target, context)\n return\n }\n\n // Someone else's UI: show what is installed, then what is actually available.\n if (plan.kind !== 'choice')\n return\n\n io.write(`${io.style.bold(plan.identity.name)} ${plan.identity.version ?? ''} — ${repoSlug(repo!)}@${plan.identity.tag ?? 'unknown tag'}\\n`)\n io.write(` ${io.style.dim(`asset: ${plan.asset}`)}\\n`)\n\n if (plan.candidates.length === 0) {\n const direction = order === 'newer' ? 'newer' : 'older'\n io.write(`no ${direction} releases carry ${plan.asset}\\n`)\n if (order === 'newer')\n io.write(` ${io.style.dim('list older releases with: home-hosted ui-update --old')}\\n`)\n return\n }\n\n const chosen = await chooseCandidate(plan.candidates, context, order)\n if (chosen === null) {\n io.write('cancelled — nothing was installed\\n')\n return\n }\n\n await installTag(repo!, chosen.asset, chosen.tag, context)\n}\n\nfunction identityOf(meta: UiMeta | null): UiIdentity | null {\n if (meta === null)\n return null\n return {\n // Both are declared by the author, so neither is ever guaranteed.\n name: meta.name ?? 'custom-ui',\n version: meta.version ?? null,\n repo: meta.repo ?? null,\n tag: meta.tag ?? null,\n asset: meta.asset ?? null,\n unix: meta.unix ?? null,\n }\n}\n\nfunction printIdentity(io: { write: (text: string) => void }, identity: UiIdentity): void {\n io.write(` name ${identity.name}\\n`)\n io.write(` version ${identity.version ?? 'unspecified'}\\n`)\n io.write(` repo ${identity.repo ?? 'unspecified'}${identity.tag === null ? '' : ` @ ${identity.tag}`}\\n`)\n if (identity.unix !== null)\n io.write(` built ${new Date(identity.unix * 1000).toISOString()}\\n`)\n}\n\nfunction printCheck(\n io: { write: (text: string) => void, style: { dim: (text: string) => string, green: (text: string) => string } },\n identity: UiIdentity,\n target: string | null,\n candidates: UpdateCandidate[] = [],\n): void {\n if (target !== null) {\n if (identity.tag === target) {\n io.write(`${io.style.green('up to date')} — ${identity.version ?? identity.name} at ${target}\\n`)\n return\n }\n io.write(`update available: ${identity.tag ?? 'unknown'} → ${target}\\n`)\n return\n }\n if (candidates.length === 0) {\n io.write(`up to date — no release carries ${identity.asset ?? identity.name}.zip\\n`)\n return\n }\n io.write(`${candidates.length} release(s) available:\\n`)\n for (const candidate of candidates)\n io.write(` ${candidate.tag} ${io.style.dim(candidate.asset)}\\n`)\n}\n\nasync function chooseCandidate(candidates: UpdateCandidate[], context: UpdateContext, order: 'newer' | 'older'): Promise<UpdateCandidate | null> {\n const headline = order === 'newer' ? 'Newer releases' : 'Older releases'\n const { io } = context\n io.write(`${io.style.bold(headline)}\\n`)\n for (const [index, candidate] of candidates.entries())\n io.write(` ${index + 1}) ${candidate.tag}\\n`)\n\n if (context.ask === null) {\n if (candidates.length === 1)\n return candidates[0]!\n throw new Error([\n `${candidates.length} releases are available and this session cannot ask which one:`,\n ...candidates.map(candidate => ` ${candidate.tag}`),\n ' run it in a terminal, or pass --tag <tag>',\n ].join('\\n'))\n }\n\n for (;;) {\n const answer = (await context.ask(`Select a release [1-${candidates.length}] (empty to cancel) `)).trim()\n if (answer.length === 0)\n return null\n if (/^\\d+$/.test(answer)) {\n const index = Number.parseInt(answer, 10)\n if (index >= 1 && index <= candidates.length)\n return candidates[index - 1]!\n }\n const byTag = candidates.find(candidate => candidate.tag === answer)\n if (byTag !== undefined)\n return byTag\n io.write(` ${io.style.dim(`no such choice — enter 1-${candidates.length} or a tag`)}\\n`)\n }\n}\n\n/** Fetches one release's asset and installs it, reporting what landed. */\nasync function installTag(\n repo: { owner: string, name: string },\n wantedAsset: string | null,\n tag: string,\n context: UpdateContext,\n): Promise<void> {\n const release = await fetchRelease(repo, tag, context)\n const names = release.assets.map(asset => asset.name ?? '').filter(isUiAsset)\n\n let assetName: string\n if (wantedAsset !== null && wantedAsset.length > 0) {\n const matched = matchAsset(names, wantedAsset)\n if (!matched.ok)\n throw new Error(`${matched.error}\\n in ${repo.owner}/${repo.name}@${release.tag}`)\n assetName = matched.name\n }\n else if (names.length === 1) {\n assetName = names[0]!\n }\n else {\n throw new Error(`${names.length} UI assets in ${repo.owner}/${repo.name}@${release.tag} — name one with --asset`)\n }\n\n const asset = release.assets.find(entry => entry.name === assetName)!\n const download = await downloadToTemp(assetDownloadUrl(asset), {\n 'accept': 'application/octet-stream',\n 'user-agent': `home-hosted/${context.version}`,\n ...(context.token !== null && context.token.length > 0 ? { authorization: `Bearer ${context.token}` } : {}),\n }, context)\n\n try {\n const { UiService } = await import('#src/services/ui')\n const { dataRoot } = await import('#src/helpers/paths')\n const result = await new UiService({ dataRoot }).install(download.file, assetName)\n\n if (!result.ok)\n throw new Error(`nothing was installed: ${result.error}`)\n\n const { meta } = result\n const { io } = context\n io.write(`${io.style.green('UI updated')} — ${meta.name} ${meta.version ?? ''} at ${release.tag}\\n`)\n io.write(` ${io.style.dim('refresh the browser to see it')}\\n`)\n }\n finally {\n fs.rmSync(download.dir, { recursive: true, force: true })\n }\n}\n\n/**\n * The citty entry. Like `ui-switch`, the parsing and the messages stay in the plain\n * function so they can be exercised without a terminal.\n */\nexport const uiUpdateCommand = defineCommand({\n meta: { name: 'ui-update', description: 'update the installed UI to match this panel, or pick a release' },\n run: async ({ rawArgs }) => {\n await uiUpdate(rawArgs, {\n write: text => process.stdout.write(text),\n prompt,\n style,\n })\n },\n})\n","import process from 'node:process'\nimport { defineCommand } from 'citty'\nimport { green } from '#src/cli/io'\nimport { dataRoot } from '#src/helpers/paths'\nimport { UiService } from '#src/services/ui'\n\n/** `ui-revert` drops a user-installed UI so the stock panel serves again. */\n\nexport async function runUiRevert(): Promise<void> {\n const ui = new UiService({ dataRoot })\n\n if (!ui.custom) {\n process.stdout.write('no custom UI is installed — the stock panel is already in use\\n')\n return\n }\n ui.revert()\n process.stdout.write(`${green('custom UI removed')} — the stock panel is back; refresh the browser\\n`)\n}\n\nexport const uiRevertCommand = defineCommand({\n meta: { name: 'ui-revert', description: 'go back to the stock control panel UI' },\n run: async () => {\n await runUiRevert()\n },\n})\n","import type { SubCommandsDef } from 'citty'\nimport fs from 'node:fs'\nimport process from 'node:process'\nimport { fileURLToPath } from 'node:url'\nimport { defineCommand, runCommand } from 'citty'\nimport { applyDirFlags, extractDirFlags, rejectUnknownFlags, resolveInvocation } from './cli/args'\nimport { cyan, dim, fail, heading } from './cli/io'\n\n/**\n * The command line, and nothing else. Two things happen before citty is asked\n * anything: `--home`/`--project` are peeled off and applied (because\n * `#src/helpers/paths.ts` resolves at import time), and the curated surface —\n * `help`, `version`, `unknown command`, and the `-p 4000` shorthand for `up` —\n * is decided. The command modules are then resolved lazily by citty, so their\n * own `#src` imports are safe: by the time one is imported, the directories are\n * already in the environment.\n *\n * The only static imports here are node builtins, citty, and the two path-free\n * local modules the pre-pass needs; every command (and so every state-reading\n * module) is a dynamic import behind `subCommands`.\n */\n\nconst CLI_ENTRY = fileURLToPath(import.meta.url)\n\n/**\n * The curated prose, kept here because citty cannot generate it. One named\n * piece per command, so the full reference and a single command's help are\n * composed from the same lines and can never drift apart. Every section lays\n * its left column out at the same width, so the two views read alike.\n */\n\nconst HEADER = 'home-hosted — a control panel for the processes on your home server'\n\n/** The one line the full reference lists for a command. */\nconst SYNOPSIS: Record<string, string> = {\n 'up': 'home-hosted up [options]',\n 'down': 'home-hosted down',\n 'restart': 'home-hosted restart [options]',\n 'status': 'home-hosted status [--json]',\n 'set-password': 'home-hosted set-password',\n 'set-token': 'home-hosted set-token',\n 'migrate': 'home-hosted migrate',\n 'init': 'home-hosted init',\n 'ui-switch': 'home-hosted ui-switch',\n 'ui-update': 'home-hosted ui-update',\n 'ui-revert': 'home-hosted ui-revert',\n}\n\nconst SUMMARIES: Record<string, string> = {\n 'up': 'start it in the background (detached)',\n 'down': 'stop it, and everything it supervises',\n 'restart': 'down, then up',\n 'status': 'is it running, where, and how to reach it',\n 'set-password': 'set the panel password without the API',\n 'set-token': 'set the API token that scripts and agents use',\n 'migrate': 'bring the config up to this release\\'s schema',\n 'init': 'scaffold a project that keeps its state in the repo',\n 'ui-switch': 'install a UI from a release asset, a zip file or a URL',\n 'ui-update': 'bring the installed UI up to date, or pick a release',\n 'ui-revert': 'go back to the stock control panel UI',\n}\n\n/** One option line, as the left column and the description that follows it. */\ntype OptionLine = [left: string, right: string]\n\n/** A heading and the option lines under it. */\ninterface OptionSection {\n heading: string\n lines: OptionLine[]\n}\n\n/** Every section lays its left column out at this width, so the two views align. */\nconst OPTION_WIDTH = 19\n\nconst UP_SECTION: OptionSection = {\n heading: 'Options for up/restart',\n lines: [\n ['-c, --config <file>', 'servers config (default: <state>/servers.config.json)'],\n ['-p, --port <port>', 'control panel port (default: 3999)'],\n ['--host <bind>', 'local | lan | an ipv4 address (default: local)'],\n ['--open', 'open the panel in a browser once it is up'],\n ['--no-autostart', 'do not start the entries marked autostart'],\n ['--foreground', 'run in this process instead of detaching (systemd/docker)'],\n ['--print-config', 'print the effective config and exit'],\n ],\n}\n\nconst STATUS_SECTION: OptionSection = {\n heading: 'Options for status',\n lines: [\n ['--json', 'print machine-readable JSON'],\n ],\n}\n\nconst SET_PASSWORD_SECTION: OptionSection = {\n heading: 'Options for set-password',\n lines: [\n ['--clear', 'remove the password, which disables authentication'],\n ],\n}\n\nconst SET_TOKEN_SECTION: OptionSection = {\n heading: 'Options for set-token',\n lines: [\n ['--generate', 'create a strong token and print it once'],\n ['--clear', 'remove the token, so it stops working'],\n ],\n}\n\nconst MIGRATE_SECTION: OptionSection = {\n heading: 'Options for migrate',\n lines: [\n ['--dry-run', 'print what would change, write nothing'],\n ['-y, --yes', 'apply without asking (or set HHOSTED_MIGRATE=allow)'],\n ],\n}\n\nconst INIT_SECTION: OptionSection = {\n heading: 'Options for init',\n lines: [\n ['--dir <dir>', 'where to scaffold (default: ./my-servers)'],\n ['--name <name>', 'package name (default: the directory name)'],\n ['--pm <manager>', 'pnpm | npm | yarn | bun (default: the first one installed)'],\n ['--no-install', 'write the files, install nothing'],\n ['-y, --yes', 'take every default, ask nothing'],\n ],\n}\n\nconst UI_SWITCH_SECTION: OptionSection = {\n heading: 'Options for ui-switch',\n lines: [\n ['--repo <owner/name>', 'release repo (default: NamesMT/home-hosted)'],\n ['--tag <tag>', 'release tag (default: this release\\'s tag, or latest for another repo)'],\n ['--asset <name>', 'asset to install (exact or unambiguous match)'],\n ['--file <path|url>', 'install a zip from a local path or an http(s) URL'],\n ['--list', 'list the usable assets and install nothing'],\n ['--token <token>', 'GitHub token (or GITHUB_TOKEN / GH_TOKEN)'],\n ['-y, --yes', 'take the only asset instead of asking'],\n ],\n}\n\nconst UI_UPDATE_SECTION: OptionSection = {\n heading: 'Options for ui-update',\n lines: [\n ['--check', 'report whether an update is available and install nothing'],\n ['--tag <tag>', 'install that release instead of asking'],\n ['--asset <name>', 'asset to install (defaults to the one in use)'],\n ['--old', 'list older releases instead of newer ones'],\n ['--repo <owner/name>', 'for a UI that does not declare its own repo'],\n ['--token <token>', 'GitHub token (or GITHUB_TOKEN / GH_TOKEN)'],\n ['-y, --yes', 'take the only release instead of asking'],\n ],\n}\n\nconst EVERYWHERE_SECTION: OptionSection = {\n heading: 'Everywhere',\n lines: [\n ['--home <dir>', 'state directory (default: $HHOSTED_HOME or ~/.home-hosted)'],\n ['--project <dir>', 'base for relative entry paths (default: the current directory)'],\n ['-h, --help', 'this text'],\n ['-v, --version', 'the version'],\n ],\n}\n\nconst ALIAS_SECTION: OptionSection = {\n heading: 'Alias',\n lines: [\n ['hh', 'the same CLI, on a machine where home-hosted is installed'],\n ],\n}\n\nconst ENVIRONMENT_SECTION: OptionSection = {\n heading: 'Environment',\n lines: [\n ['HHOSTED_HOME', 'where config, secrets, logs, TLS and backups live'],\n ['HHOSTED_PROJECT', 'base for relative entry paths'],\n ['HHOSTED_PASSWORD', 'the password for a non-interactive set-password'],\n ['HHOSTED_TOKEN', 'the token for a non-interactive set-token'],\n ['GITHUB_TOKEN', 'a GitHub token for ui-switch (GH_TOKEN also works)'],\n ],\n}\n\n/** The command list of the full reference, aligned as it always was. */\nfunction renderCommandList(): string {\n const width = Math.max(...Object.values(SYNOPSIS).map(synopsis => synopsis.length))\n return Object.keys(SYNOPSIS)\n .map((name) => {\n const synopsis = SYNOPSIS[name]!\n return ` ${cyan(synopsis)}${' '.repeat(width + 1 - synopsis.length)} ${SUMMARIES[name]}`\n })\n .join('\\n')\n}\n\nfunction renderSection(section: OptionSection): string {\n const lines = section.lines\n .map(([left, right]) => ` ${cyan(left)}${' '.repeat(Math.max(0, OPTION_WIDTH - left.length))} ${right}`)\n .join('\\n')\n return `${heading(section.heading)}\\n${lines}`\n}\n\n/** The trailer every command shares: the global flags, the alias and the environment. */\nconst SHARED_TRAILER = [\n renderSection(EVERYWHERE_SECTION),\n '',\n renderSection(ALIAS_SECTION),\n '',\n renderSection(ENVIRONMENT_SECTION),\n '',\n].join('\\n')\n\n/** The full reference: every command, every option, the trailer. */\nconst USAGE = [\n dim(HEADER),\n '',\n heading('Usage'),\n renderCommandList(),\n '',\n renderSection(UP_SECTION),\n '',\n renderSection(SET_PASSWORD_SECTION),\n '',\n renderSection(SET_TOKEN_SECTION),\n '',\n renderSection(MIGRATE_SECTION),\n '',\n renderSection(INIT_SECTION),\n '',\n renderSection(UI_SWITCH_SECTION),\n '',\n renderSection(STATUS_SECTION),\n '',\n SHARED_TRAILER,\n].join('\\n')\n\n// `restart` is `up` behind the scenes, so it reads `up`'s flags under its own heading.\nconst UP_COMMANDS = new Set(['up', 'restart'])\n\nconst SECTIONS: Record<string, OptionSection> = {\n 'status': STATUS_SECTION,\n 'set-password': SET_PASSWORD_SECTION,\n 'set-token': SET_TOKEN_SECTION,\n 'migrate': MIGRATE_SECTION,\n 'init': INIT_SECTION,\n 'ui-switch': UI_SWITCH_SECTION,\n 'ui-update': UI_UPDATE_SECTION,\n}\n\n/**\n * `home-hosted <command> --help`: the usage line, that command's own options,\n * and the shared trailer. A command with no options says so instead of printing\n * an empty heading.\n */\nexport function commandHelp(command: string): string {\n const synopsis = SYNOPSIS[command]\n if (synopsis === undefined)\n return USAGE\n\n const section = UP_COMMANDS.has(command) ? UP_SECTION : SECTIONS[command]\n const options = section === undefined ? dim('no options') : renderSection(section)\n\n return [\n dim(HEADER),\n '',\n cyan(synopsis),\n '',\n options,\n '',\n SHARED_TRAILER,\n ].join('\\n')\n}\n\nfunction manifestVersion(): string {\n try {\n const manifest = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf8')) as { version?: string }\n return manifest.version ?? '0.0.0'\n }\n catch {\n return '0.0.0'\n }\n}\n\nfunction version(): void {\n process.stdout.write(`${manifestVersion()}\\n`)\n}\n\n/**\n * citty owns dispatch and argument parsing. Each entry is a lazy import, so a\n * command's `#src` modules are only evaluated once `applyDirFlags()` has run.\n */\nconst COMMANDS = {\n 'up': () => import('#src/cli/up').then(module => module.upCommand(CLI_ENTRY)),\n 'down': () => import('#src/cli/down').then(module => module.downCommand),\n 'restart': () => import('#src/cli/restart').then(module => module.restartCommand(CLI_ENTRY)),\n 'status': () => import('#src/cli/status').then(module => module.statusCommand),\n 'set-password': () => import('#src/cli/set-password').then(module => module.setPasswordCommand),\n 'set-token': () => import('#src/cli/set-token').then(module => module.setTokenCommand),\n 'migrate': () => import('#src/cli/migrate').then(module => module.migrateCommand),\n 'init': () => import('#src/cli/init').then(module => module.initCommand),\n 'ui-switch': () => import('#src/cli/ui-switch').then(module => module.uiSwitchCommand),\n 'ui-update': () => import('#src/cli/ui-update').then(module => module.uiUpdateCommand),\n 'ui-revert': () => import('#src/cli/ui-revert').then(module => module.uiRevertCommand),\n} satisfies SubCommandsDef\n\nconst rootCommand = defineCommand({\n meta: {\n name: 'home-hosted',\n version: manifestVersion(),\n description: 'a control panel for the processes on your home server',\n },\n subCommands: COMMANDS,\n})\n\nconst commandNames = Object.keys(COMMANDS)\n\nasync function main(): Promise<void> {\n const dirFlags = extractDirFlags(process.argv.slice(2))\n if (dirFlags.error !== undefined)\n fail(dirFlags.error)\n applyDirFlags(dirFlags)\n\n const invocation = resolveInvocation(dirFlags.rest, commandNames)\n\n if (invocation.kind === 'help') {\n process.stdout.write(invocation.command === undefined ? USAGE : commandHelp(invocation.command))\n return\n }\n if (invocation.kind === 'version') {\n version()\n return\n }\n if (invocation.kind === 'unknown') {\n process.stderr.write(`unknown command: ${invocation.command}\\n\\n${USAGE}`)\n process.exit(1)\n }\n\n // citty parses permissively, so the refusal of a mistyped flag is asked of\n // citty's own definitions first; a command that declares none (ui-switch) keeps\n // its own parseArgs, which is strict already.\n const sub = COMMANDS[invocation.argv[0]! as keyof typeof COMMANDS]\n const command = typeof sub === 'function' ? await sub() : sub\n const problem = rejectUnknownFlags(invocation.argv.slice(1), command?.args)\n if (problem !== null)\n fail(problem)\n\n // `runCommand`, not `runMain`: citty's `runMain` prints its own usage and\n // `console.error`s the message before `process.exit(1)`, so a wrapper can never\n // turn a failure back into `fail()`'s red `error <message>`. Letting the error\n // out keeps every failure on the one shape this CLI has always had.\n try {\n await runCommand(rootCommand, { rawArgs: invocation.argv })\n }\n catch (error) {\n fail(error instanceof Error ? error.message : String(error))\n }\n}\n\nvoid main().catch((error: unknown) => {\n fail(error instanceof Error ? error.message : String(error))\n})\n"],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","names":[],"sources":["../src/cli/args.ts","../src/cli/io.ts","../src/helpers/cookies.ts","../src/helpers/factory.ts","../src/shared/contracts.ts","../src/helpers/openapi.ts","../src/helpers/validator.ts","../src/middleware/loopback.ts","../src/helpers/atomic.ts","../src/config/secrets.ts","../src/services/auth.ts","../src/middleware/auth.ts","../src/helpers/bind.ts","../src/services/exposure.ts","../src/api/auth/$.routes.ts","../src/helpers/logger.ts","../src/helpers/validate.ts","../src/helpers/paths.ts","../src/services/state.ts","../src/api/backups.ts","../src/helpers/deferred.ts","../src/api/control.ts","../src/api/events.ts","../src/api/health.ts","../src/api/logs.ts","../src/api/metrics.ts","../src/providers/telegram.ts","../src/api/notifications.ts","../src/config/migrations.ts","../src/config/schema.ts","../src/helpers/version.ts","../src/config/parse.ts","../src/config/seed.ts","../src/config/store.ts","../src/api/servers/$.routes.ts","../src/api/settings.ts","../src/api/state.ts","../src/api/static.ts","../src/api/tls.ts","../src/helpers/error.ts","../src/openapi.ts","../src/app.ts","../src/helpers/daemon.ts","../src/helpers/open.ts","../src/helpers/template.ts","../src/providers/port.ts","../src/helpers/env-file.ts","../src/providers/archive.ts","../src/helpers/backoff.ts","../src/providers/health-check.ts","../src/providers/proc.ts","../src/providers/identity.ts","../src/providers/process.ts","../src/services/dependencies.ts","../src/services/log-buffer.ts","../src/services/supervisor.ts","../src/shared/generated.ts","../src/services/backups.ts","../src/services/config-watch.ts","../src/services/control-server.ts","../src/services/events.ts","../src/services/history.ts","../src/providers/host.ts","../src/services/host-monitor.ts","../src/services/log-files.ts","../src/services/notifications.ts","../src/services/tls.ts","../src/services/ui.ts","../src/providers/ui-release.ts","../src/services/ui-update.ts","../src/index.ts","../src/cli/up.ts","../src/cli/down.ts","../src/cli/restart.ts","../src/cli/status.ts","../src/cli/set-password.ts","../src/cli/set-token.ts","../src/cli/migrate.ts","../src/services/init.ts","../src/cli/init.ts","../src/cli/ui-switch.ts","../src/cli/ui-update.ts","../src/cli/ui-revert.ts","../src/cli.ts"],"sourcesContent":["import type { ArgsDef } from 'citty'\nimport path from 'node:path'\nimport process from 'node:process'\n\n/**\n * The argument work that happens *before* citty: `--home`/`--project` have to\n * change the environment before any `#src` module resolves a path, and the\n * curated surface (`help`, `version`, `unknown command`) has to stay byte-for-\n * byte what it always was. Both live here so they can be tested without a\n * terminal, a daemon or a spawned process; this module imports node builtins\n * only and never reads the state directories itself.\n */\n\nexport interface DirFlags {\n project?: string\n home?: string\n}\n\nexport interface DirFlagResult extends DirFlags {\n rest: string[]\n error?: string\n}\n\n/** `--home`/`--project` are handled for every command, so they are peeled off first. */\nexport function extractDirFlags(argv: string[]): DirFlagResult {\n const rest: string[] = []\n let project: string | undefined\n let home: string | undefined\n\n for (let index = 0; index < argv.length; index++) {\n const arg = argv[index]!\n const equals = arg.indexOf('=')\n const name = equals === -1 ? arg : arg.slice(0, equals)\n if (name !== '--project' && name !== '--home') {\n rest.push(arg)\n continue\n }\n const value = equals === -1 ? argv[++index] : arg.slice(equals + 1)\n if (value === undefined || value.length === 0)\n return { rest, project, home, error: `${name} needs a directory` }\n if (name === '--project')\n project = value\n else\n home = value\n }\n\n return { rest, project, home }\n}\n\n/** Set before any state module is imported, so it decides where state lives. */\nexport function applyDirFlags(flags: DirFlags): void {\n if (flags.project !== undefined)\n process.env.HHOSTED_PROJECT = path.resolve(flags.project)\n if (flags.home !== undefined)\n process.env.HHOSTED_HOME = path.resolve(flags.home)\n}\n\nexport type Invocation\n = | { kind: 'help', command?: string }\n | { kind: 'version' }\n | { kind: 'command', argv: string[] }\n | { kind: 'unknown', command: string }\n\nconst HELP_TOKENS = new Set(['help', '--help', '-h'])\nconst VERSION_TOKENS = new Set(['version', '--version', '-v'])\n// After a command, only the flag forms count: a bare `help` may be the value of an\n// option (`init --name help`), and answering that with the usage text would skip\n// the command instead of running it.\nconst HELP_FLAGS = new Set(['--help', '-h'])\nconst VERSION_FLAGS = new Set(['--version', '-v'])\n\n/**\n * What the stripped argv means, before citty sees it.\n *\n * `help`/`version` are commands here, not flags, and a help or version flag on a\n * command is answered the same way instead of being parsed as one of that\n * command's options. A help flag after a command keeps that command's name, so\n * the curated text can stay scoped to it; the bare `help` (or a top-level flag)\n * is the whole reference. A first token that starts with `-` is the one-shot\n * form (`home-hosted -p 4000`), so `up` is prepended. Anything else has to name\n * a command, which keeps the old `unknown command:` text exact.\n */\nexport function resolveInvocation(argv: string[], commands: readonly string[]): Invocation {\n if (argv.length === 0)\n return { kind: 'command', argv: ['up'] }\n\n const first = argv[0]!\n if (HELP_TOKENS.has(first))\n return { kind: 'help' }\n if (VERSION_TOKENS.has(first))\n return { kind: 'version' }\n\n if (first.startsWith('-'))\n return { kind: 'command', argv: ['up', ...argv] }\n\n if (!commands.includes(first))\n return { kind: 'unknown', command: first }\n\n for (const arg of argv.slice(1)) {\n if (HELP_FLAGS.has(arg))\n return { kind: 'help', command: first }\n if (VERSION_FLAGS.has(arg))\n return { kind: 'version' }\n }\n\n return { kind: 'command', argv }\n}\n\nconst CAMEL = /[A-Z]/g\n\n/** citty takes camelCase definitions; the flag a person types is kebab-case. */\nfunction kebab(name: string): string {\n return name.replace(CAMEL, match => `-${match.toLowerCase()}`)\n}\n\nfunction aliasesOf(def: ArgsDef[string]): string[] {\n if (def === undefined || !('alias' in def) || def.alias === undefined)\n return []\n return Array.isArray(def.alias) ? def.alias : [def.alias]\n}\n\nfunction findArg(argsDef: ArgsDef, name: string): ArgsDef[string] | undefined {\n for (const [key, def] of Object.entries(argsDef)) {\n if (def === undefined)\n continue\n const names = new Set([key, kebab(key), ...aliasesOf(def)])\n if (names.has(name))\n return def\n // `--no-<flag>` is citty's negation of a declared boolean, never an option.\n if (def.type === 'boolean' && (name === `no-${key}` || name === `no-${kebab(key)}`))\n return def\n }\n return undefined\n}\n\n/**\n * citty parses permissively (`strict: false`), so a mistyped flag would quietly do\n * nothing — `--autostart` where `--no-autostart` was meant would start the panel\n * with the wrong policy. This keeps the refusal the command line always had, from\n * citty's own definitions rather than a second list.\n *\n * A command that declares no arguments parses its own argv (and rejects its own\n * unknown flags), so it is left alone. Returns the message to print, or null.\n */\nexport function rejectUnknownFlags(argv: string[], argsDef: ArgsDef | undefined): string | null {\n if (argsDef === undefined || Object.keys(argsDef).length === 0)\n return null\n\n for (let index = 0; index < argv.length; index++) {\n const token = argv[index]!\n if (token === '--')\n return null\n if (!token.startsWith('-') || token.length === 1)\n return `Unexpected argument '${token}'`\n\n const body = token.startsWith('--') ? token.slice(2) : token.slice(1)\n const equals = body.indexOf('=')\n const name = equals === -1 ? body : body.slice(0, equals)\n const def = name.length === 0 ? undefined : findArg(argsDef, name)\n if (def === undefined)\n return `Unknown option '${token}'`\n // A string option consumes the next token, unless it was given inline.\n if (def.type === 'string' && equals === -1 && argv[index + 1] === undefined)\n return `Option '${token}' needs a value`\n if (def.type === 'string' && equals === -1)\n index += 1\n }\n\n return null\n}\n\nexport interface UpFlags {\n config?: string\n port?: number\n host?: string\n autostart: boolean\n open: boolean\n foreground: boolean\n printConfig: boolean\n}\n\n/**\n * The daemon gets the same instructions, but never `--foreground` (that is what\n * makes it the daemon) and never `--print-config` (that one is answered in the\n * calling process).\n */\nexport function buildDaemonArgv(flags: UpFlags): string[] {\n const args: string[] = ['up', '--foreground']\n if (flags.config !== undefined)\n args.push('--config', flags.config)\n if (flags.port !== undefined)\n args.push('--port', String(flags.port))\n if (flags.host !== undefined)\n args.push('--host', flags.host)\n if (!flags.autostart)\n args.push('--no-autostart')\n if (flags.open)\n args.push('--open')\n return args\n}\n","import process from 'node:process'\nimport readline from 'node:readline'\n\n/**\n * The bits every command shares: the colours, the failure shape, and the one\n * place readline is set up. Imported by `src/cli.ts` and by the command modules\n * it loads lazily, so it may not read the state directories — the only imports\n * here are node builtins.\n */\n\nexport const isTty = (): boolean => process.stdout.isTTY === true\n\nexport const paint = (code: string, text: string): string => (isTty() ? `\\x1B[${code}m${text}\\x1B[0m` : text)\nexport const dim = (text: string): string => paint('2', text)\nexport const bold = (text: string): string => paint('1', text)\nexport const cyan = (text: string): string => paint('36', text)\nexport const green = (text: string): string => paint('32', text)\n/** A section title in the help output. */\nexport const heading = (text: string): string => paint('1;4', text)\n\n/** The seam `ui-switch` takes, so the command stays testable without a terminal. */\nexport const style = { bold, dim, green }\n\n/** One shape for every failure: a red `error <message>` on stderr and exit 1. */\nexport function fail(message: string): never {\n process.stderr.write(`${paint('31', 'error')} ${message}\\n`)\n process.exit(1)\n}\n\nexport function delay(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms))\n}\n\n/** A plain y/N question, for decisions that are not secrets. */\nexport function prompt(question: string): Promise<string> {\n return new Promise((resolve) => {\n const rl = readline.createInterface({ input: process.stdin, output: process.stdout })\n rl.question(question, (answer) => {\n rl.close()\n resolve(answer)\n })\n })\n}\n\n/** Reads a line with echo suppressed, so the password never lands in scrollback. */\nexport function promptHidden(question: string): Promise<string> {\n return new Promise((resolve) => {\n const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: true })\n const onData = (): void => {\n readline.clearLine(process.stdout, 0)\n readline.cursorTo(process.stdout, 0)\n process.stdout.write(question)\n }\n\n process.stdin.on('data', onData)\n rl.question(question, (answer) => {\n process.stdin.off('data', onData)\n rl.close()\n process.stdout.write('\\n')\n resolve(answer)\n })\n })\n}\n\n/** Asks a yes/no question with a default, so an empty answer is a real answer. */\nexport async function confirm(question: string, fallback: boolean): Promise<boolean> {\n const answer = (await prompt(`${question} ${fallback ? '[Y/n]' : '[y/N]'} `)).trim().toLowerCase()\n if (answer.length === 0)\n return fallback\n return answer === 'y' || answer === 'yes'\n}\n","/** Minimal cookie helpers, enough for one httpOnly session cookie. */\nexport interface CookieOptions {\n maxAgeMs?: number\n httpOnly?: boolean\n sameSite?: 'Strict' | 'Lax' | 'None'\n secure?: boolean\n path?: string\n}\n\nexport function parseCookies(header: string | null | undefined): Record<string, string> {\n const cookies: Record<string, string> = {}\n if (!header)\n return cookies\n for (const part of header.split(';')) {\n const separator = part.indexOf('=')\n if (separator < 0)\n continue\n const name = part.slice(0, separator).trim()\n if (name.length === 0)\n continue\n const value = part.slice(separator + 1).trim()\n try {\n cookies[name] = decodeURIComponent(value)\n }\n catch {\n cookies[name] = value\n }\n }\n return cookies\n}\n\nexport function serializeCookie(name: string, value: string, options: CookieOptions = {}): string {\n const parts = [`${name}=${encodeURIComponent(value)}`]\n parts.push(`Path=${options.path ?? '/'}`)\n if (options.maxAgeMs !== undefined)\n parts.push(`Max-Age=${Math.max(0, Math.floor(options.maxAgeMs / 1000))}`)\n if (options.httpOnly !== false)\n parts.push('HttpOnly')\n parts.push(`SameSite=${options.sameSite ?? 'Strict'}`)\n if (options.secure)\n parts.push('Secure')\n return parts.join('; ')\n}\n","import { createFactory } from 'hono/factory'\n\n/**\n * Every route is built from this factory and chained (`app.get(...).post(...)`)\n * so Hono keeps the full route map in its type — which is what `AppType` exports\n * for `hc<AppType>` clients and for the generated OpenAPI document.\n */\nexport const appFactory = createFactory()\n","import { type } from 'arktype'\n\n/**\n * Schemas shared by the control plane and the SPA: the server entry shape, the\n * control panel's own settings, API DTOs and SSE frames. Nothing here knows\n * about a particular server — an entry carries its own command, args, env and\n * bootstrap.\n */\n\n/** `local` -> 127.0.0.1, `lan` -> 0.0.0.0, or an explicit IPv4 to bind. */\nexport const bindSchema = type('\"local\" | \"lan\" | /^\\\\d{1,3}(?:\\\\.\\\\d{1,3}){3}$/')\nexport type Bind = typeof bindSchema.infer\n\n/** Parses a bind value (`local` | `lan` | ipv4); null when it is not one. */\nexport function parseBind(value: string): Bind | null {\n const parsed = bindSchema(value)\n return parsed instanceof type.errors ? null : parsed\n}\n\n/** `null` means \"no port\": no readiness probe, no health supervision, no preflight. */\nexport const portSchema = type('1 <= number.integer <= 65535 | null')\n\n/**\n * What to do when something already listens on the entry's port. `follow` and\n * `reclaim` only ever act on a holder proven to be this entry's own detached\n * successor (the `HHOSTED_SERVER_ID` marker, POSIX only); `kill` is the blunt one:\n * it stops any holder except the panel's own process tree.\n */\nexport const onPortConflictSchema = type.enumerated('block', 'warn', 'follow', 'reclaim', 'kill')\nexport type OnPortConflict = typeof onPortConflictSchema.infer\n\n/** The stored forms carry the default; a patch must not (see the patch schemas below). */\nexport const onPortConflictDefaultSchema = onPortConflictSchema.default('block')\n\nexport const restartSchema = type({\n enabled: 'boolean = true',\n maxRetries: 'number.integer >= 0 = 3',\n baseDelayMs: 'number >= 0 = 1000',\n factor: 'number >= 1 = 2',\n maxDelayMs: 'number >= 0 = 30000',\n /** A process alive this long is considered healthy again and the retry counter resets. */\n resetAfterMs: 'number >= 0 = 60000',\n}).onUndeclaredKey('reject')\n\nexport type RestartConfig = typeof restartSchema.infer\n\nexport const httpCheckSchema = type({\n /** Path on the server's own port, e.g. `/healthz`. */\n path: 'string = \"/\"',\n method: '\"GET\" | \"HEAD\" = \"GET\"',\n /** Exact status to accept; `null` (or omitted) means any status below `expectStatusBelow`. */\n expectStatus: 'number.integer | null?',\n expectStatusBelow: 'number.integer = 400',\n /** Substring that must appear in the response body. */\n expectBody: 'string = \"\"',\n}).onUndeclaredKey('reject')\nexport type HttpCheckConfig = typeof httpCheckSchema.infer\n\n/** Restart guards for the process tree. */\nexport const resourcesSchema = type({\n /** Restart when the tree's RSS exceeds this; 0 disables. */\n maxRssBytes: 'number.integer >= 0 = 0',\n}).onUndeclaredKey('reject')\nexport type ResourcesConfig = typeof resourcesSchema.infer\n\nexport const healthSchema = type({\n enabled: 'boolean = true',\n /** `port` = TCP connect only; `http` = fetch `http.path` and assert the response. */\n mode: '\"port\" | \"http\" = \"port\"',\n http: httpCheckSchema.default(() => ({})),\n intervalMs: 'number >= 500 = 5000',\n timeoutMs: 'number >= 100 = 1500',\n /** Consecutive failed probes before the warning state is shown. */\n unhealthyThreshold: 'number.integer >= 1 = 3',\n /** 0 disables it; otherwise a port stuck unhealthy this long forces a restart. */\n forceRestartAfterMs: 'number >= 0 = 0',\n /** How long to wait for the port to accept connections after spawn. */\n startTimeoutMs: 'number >= 0 = 20000',\n}).onUndeclaredKey('reject')\n\nexport type HealthConfig = typeof healthSchema.infer\n\nexport const stopSchema = type({\n signal: '\"SIGTERM\" | \"SIGINT\" | \"SIGKILL\" = \"SIGTERM\"',\n killGroup: 'boolean = true',\n graceMs: 'number >= 0 = 5000',\n /** Last resort for wrappers that detach their real server. */\n killPortHolders: 'boolean = false',\n}).onUndeclaredKey('reject')\n\nexport type StopConfig = typeof stopSchema.infer\n\nexport const bootstrapSchema = type({\n command: 'string',\n args: type('string[]').default(() => []),\n env: type('Record<string, string>').default(() => ({})),\n timeoutMs: 'number >= 1000 = 120000',\n /** Run once per `up` session; whatever it installs persists on disk. */\n runOnce: 'boolean = true',\n}).onUndeclaredKey('reject')\nexport type BootstrapConfig = typeof bootstrapSchema.infer\nexport const bootstrapOrNullSchema = bootstrapSchema.or(type('null'))\n\nexport const logBufferLinesSchema = type('50 <= number.integer <= 100000')\n\nexport const serverSchema = type({\n id: '/^[a-z0-9][a-z0-9_-]*$/',\n label: 'string?',\n enabled: 'boolean = true',\n autostart: 'boolean = false',\n command: 'string >= 1',\n args: type('string[]').default(() => []),\n cwd: 'string = \".\"',\n env: type('Record<string, string>').default(() => ({})),\n /**\n * `ENV=path` pairs: exported to the process (overriding `env`) *and* the path\n * is backed up automatically — one declaration for data directories.\n */\n dataEnvs: type('Record<string, string>').default(() => ({})),\n bootstrap: bootstrapOrNullSchema.optional(),\n port: portSchema.optional(),\n bind: bindSchema.default(() => 'local' as const),\n onPortConflict: onPortConflictDefaultSchema,\n restart: restartSchema.default(() => ({})),\n health: healthSchema.default(() => ({})),\n stop: stopSchema.default(() => ({})),\n logBufferLines: logBufferLinesSchema.default(() => 500),\n /** Ids this server needs running first (and healthy); stopped in reverse order. */\n dependsOn: type('string[]').default(() => []),\n /** Optional KEY=value file loaded at spawn; its values override `env`. */\n envFile: 'string = \"\"',\n resources: resourcesSchema.default(() => ({})),\n /** Paths included in backups for this server (templates allowed). */\n backupPaths: type('string[]').default(() => []),\n /**\n * Skip well-known build output and dependency directories (`node_modules`,\n * `dist`, `.next`, caches, …) inside the paths this entry declares.\n */\n backupIgnoreGenerated: 'boolean = true',\n}).onUndeclaredKey('reject')\nexport type ServerConfig = Omit<typeof serverSchema.infer, 'port'> & { port: number | null }\n\n/**\n * Authentication for the control panel itself. The password never lives here —\n * only the policy does; its scrypt hash sits in a git-ignored secrets file.\n */\nexport const authSchema = type({\n enabled: 'boolean = true',\n sessionTtlMs: 'number >= 60000 = 604800000',\n /** `auto` adds `Secure` when the request arrived over https (proxy-aware). */\n cookieSecure: '\"auto\" | \"always\" | \"never\" = \"auto\"',\n /** Trust `x-forwarded-*` from a reverse proxy; also drives the client IP. */\n trustProxy: 'boolean = false',\n maxLoginAttempts: 'number.integer >= 1 = 5',\n lockoutMs: 'number >= 1000 = 60000',\n}).onUndeclaredKey('reject')\nexport type AuthConfig = typeof authSchema.infer\n\n/** Outbound crash/health notifications. The bot token lives in the secrets file. */\nexport const telegramSchema = type({\n enabled: 'boolean = false',\n chatId: 'string = \"\"',\n onCrash: 'boolean = true',\n onUnhealthy: 'boolean = true',\n onForcedRestart: 'boolean = true',\n onRecovered: 'boolean = false',\n /** Host vitals breaches (disk, memory, swap, load, temperature). */\n onHost: 'boolean = true',\n /** Per server *and* reason, so a flapping server cannot spam the chat. */\n cooldownMs: 'number >= 0 = 120000',\n}).onUndeclaredKey('reject')\nexport type TelegramConfig = typeof telegramSchema.infer\n\nexport const notificationsSchema = type({\n telegram: telegramSchema.default(() => ({})),\n}).onUndeclaredKey('reject')\nexport type NotificationsConfig = typeof notificationsSchema.infer\n\n/** On-disk log retention for the Logs page. */\nexport const logsSchema = type({\n persist: 'boolean = true',\n /** Per server, before rotating to `.1`, `.2`, ... */\n maxBytes: '10000 <= number <= 100000000 = 2000000',\n keep: '1 <= number.integer <= 10 = 3',\n}).onUndeclaredKey('reject')\nexport type LogsConfig = typeof logsSchema.infer\n\nexport const tlsSchema = type({\n enabled: 'boolean = false',\n}).onUndeclaredKey('reject')\nexport type TlsConfig = typeof tlsSchema.infer\n\n/** Host-level vitals and their alert thresholds. */\nexport const hostSchema = type({\n enabled: 'boolean = true',\n intervalMs: 'number >= 5000 = 15000',\n /** Filesystems reported and alerted on; templates and `~` are expanded. */\n diskPaths: type('string[]').default(() => ['.']),\n /** 0 disables an individual alert. */\n diskUsedPercent: 'number >= 0 = 90',\n memoryUsedPercent: 'number >= 0 = 90',\n swapUsedPercent: 'number >= 0 = 50',\n loadPerCpu: 'number >= 0 = 2',\n tempCelsius: 'number >= 0 = 85',\n}).onUndeclaredKey('reject')\nexport type HostConfig = typeof hostSchema.infer\n\n/** Tar archives of config, secrets, TLS and declared data paths. */\nexport const backupsSchema = type({\n enabled: 'boolean = true',\n dir: 'string = \".backups\"',\n keep: 'number.integer >= 1 = 5',\n /** Extra paths in every backup, in addition to each server's `backupPaths`. */\n includePaths: type('string[]').default(() => []),\n}).onUndeclaredKey('reject')\nexport type BackupsConfig = typeof backupsSchema.infer\n\nexport const controlSchema = type({\n /** What the panel calls itself; the stock UI shows it in the sidebar. */\n label: '1 <= string <= 60 = \"home-hosted\"',\n port: '1 <= number.integer <= 65535 = 3999',\n /** Where the control panel itself listens; keep it `local` unless you mean it. */\n host: bindSchema.default(() => 'local' as const),\n openBrowser: 'boolean = false',\n auth: authSchema.default(() => ({})),\n tls: tlsSchema.default(() => ({})),\n}).onUndeclaredKey('reject')\nexport type ControlConfig = typeof controlSchema.infer\n\n/** Applied to every server entry; whatever an entry sets wins. */\nexport const defaultsSchema = type({\n enabled: 'boolean = true',\n autostart: 'boolean = false',\n bind: bindSchema.default(() => 'local' as const),\n onPortConflict: onPortConflictDefaultSchema,\n restart: restartSchema.default(() => ({})),\n health: healthSchema.default(() => ({})),\n stop: stopSchema.default(() => ({})),\n logBufferLines: logBufferLinesSchema.default(() => 500),\n}).onUndeclaredKey('reject')\nexport type ServerDefaults = typeof defaultsSchema.infer\n\n// Patch variants stay default-free: an API client sends only what it changes, so\n// a partial nested group must not silently pull in the code defaults.\nconst restartPatchSchema = type({\n enabled: 'boolean?',\n maxRetries: 'number.integer >= 0?',\n baseDelayMs: 'number >= 0?',\n factor: 'number >= 1?',\n maxDelayMs: 'number >= 0?',\n resetAfterMs: 'number >= 0?',\n}).onUndeclaredKey('reject')\n\nconst httpCheckPatchSchema = type({\n path: 'string?',\n method: '\"GET\" | \"HEAD\"?',\n expectStatus: 'number.integer | null?',\n expectStatusBelow: 'number.integer?',\n expectBody: 'string?',\n}).onUndeclaredKey('reject')\n\nconst resourcesPatchSchema = type({\n maxRssBytes: 'number.integer >= 0?',\n}).onUndeclaredKey('reject')\n\nconst healthPatchSchema = type({\n enabled: 'boolean?',\n mode: '\"port\" | \"http\"?',\n http: httpCheckPatchSchema.optional(),\n intervalMs: 'number >= 500?',\n timeoutMs: 'number >= 100?',\n unhealthyThreshold: 'number.integer >= 1?',\n forceRestartAfterMs: 'number >= 0?',\n startTimeoutMs: 'number >= 0?',\n}).onUndeclaredKey('reject')\n\nconst stopPatchSchema = type({\n signal: '\"SIGTERM\" | \"SIGINT\" | \"SIGKILL\"?',\n killGroup: 'boolean?',\n graceMs: 'number >= 0?',\n killPortHolders: 'boolean?',\n}).onUndeclaredKey('reject')\n\nconst authPatchSchema = type({\n enabled: 'boolean?',\n sessionTtlMs: 'number >= 60000?',\n cookieSecure: '\"auto\" | \"always\" | \"never\"?',\n trustProxy: 'boolean?',\n maxLoginAttempts: 'number.integer >= 1?',\n lockoutMs: 'number >= 1000?',\n}).onUndeclaredKey('reject')\n\nconst telegramPatchSchema = type({\n enabled: 'boolean?',\n chatId: 'string?',\n onCrash: 'boolean?',\n onUnhealthy: 'boolean?',\n onForcedRestart: 'boolean?',\n onRecovered: 'boolean?',\n onHost: 'boolean?',\n cooldownMs: 'number >= 0?',\n}).onUndeclaredKey('reject')\n\nconst notificationsPatchSchema = type({\n telegram: telegramPatchSchema.optional(),\n}).onUndeclaredKey('reject')\n\nconst hostPatchSchema = type({\n enabled: 'boolean?',\n intervalMs: 'number >= 5000?',\n diskPaths: type('string[]').optional(),\n diskUsedPercent: 'number >= 0?',\n memoryUsedPercent: 'number >= 0?',\n swapUsedPercent: 'number >= 0?',\n loadPerCpu: 'number >= 0?',\n tempCelsius: 'number >= 0?',\n}).onUndeclaredKey('reject')\n\nconst backupsPatchSchema = type({\n enabled: 'boolean?',\n dir: 'string?',\n keep: 'number.integer >= 1?',\n includePaths: type('string[]').optional(),\n}).onUndeclaredKey('reject')\n\nconst logsPatchSchema = type({\n persist: 'boolean?',\n maxBytes: '10000 <= number <= 100000000?',\n keep: '1 <= number.integer <= 10?',\n}).onUndeclaredKey('reject')\n\nconst tlsPatchSchema = type({\n enabled: 'boolean?',\n}).onUndeclaredKey('reject')\n\nconst controlPatchSchema = type({\n label: '1 <= string <= 60?',\n port: '1 <= number.integer <= 65535?',\n host: bindSchema.optional(),\n openBrowser: 'boolean?',\n auth: authPatchSchema.optional(),\n tls: tlsPatchSchema.optional(),\n}).onUndeclaredKey('reject')\n\nconst defaultsPatchSchema = type({\n enabled: 'boolean?',\n autostart: 'boolean?',\n bind: bindSchema.optional(),\n onPortConflict: onPortConflictSchema.optional(),\n restart: restartPatchSchema.optional(),\n health: healthPatchSchema.optional(),\n stop: stopPatchSchema.optional(),\n logBufferLines: logBufferLinesSchema.optional(),\n}).onUndeclaredKey('reject')\n\nconst editableFields = {\n label: 'string?',\n enabled: 'boolean?',\n autostart: 'boolean?',\n command: 'string?',\n args: 'string[]?',\n cwd: 'string?',\n env: 'Record<string, string>?',\n dataEnvs: 'Record<string, string>?',\n bootstrap: bootstrapOrNullSchema.optional(),\n port: portSchema.optional(),\n bind: bindSchema.optional(),\n onPortConflict: onPortConflictSchema.optional(),\n restart: restartPatchSchema.optional(),\n health: healthPatchSchema.optional(),\n stop: stopPatchSchema.optional(),\n logBufferLines: logBufferLinesSchema.optional(),\n dependsOn: type('string[]').optional(),\n envFile: 'string?',\n resources: resourcesPatchSchema.optional(),\n backupPaths: type('string[]').optional(),\n backupIgnoreGenerated: 'boolean?',\n} as const\n\nexport const serverPatchSchema = type(editableFields).onUndeclaredKey('reject')\nexport type ServerPatch = typeof serverPatchSchema.infer\n\nexport const serverCreateSchema = type({\n id: '/^[a-z0-9][a-z0-9_-]*$/',\n ...editableFields,\n command: 'string',\n}).onUndeclaredKey('reject')\nexport type ServerCreate = typeof serverCreateSchema.infer\n\n/** Edits to the panel's own control block and to the global server defaults. */\nexport const settingsPatchSchema = type({\n control: controlPatchSchema.optional(),\n defaults: defaultsPatchSchema.optional(),\n logs: logsPatchSchema.optional(),\n notifications: notificationsPatchSchema.optional(),\n host: hostPatchSchema.optional(),\n backups: backupsPatchSchema.optional(),\n}).onUndeclaredKey('reject')\nexport type SettingsPatch = typeof settingsPatchSchema.infer\n\nexport const authStatusSchema = type({\n enabled: 'boolean',\n passwordSet: 'boolean',\n passwordUpdatedAt: 'number | null',\n /**\n * An API token is set; it is shown here as a flag, never as a value. Optional so\n * a freshly built UI still parses the state of a panel process that predates it:\n * an upgrade replaces `uis/stock/dist` on disk while the old process keeps\n * serving, and a missing key must not blank the whole app.\n */\n apiTokenSet: 'boolean?',\n /** Still the boot-time default; the login page says so and exposure stays blocked. */\n usingDefaultPassword: 'boolean',\n /** The panel currently listens beyond loopback. */\n exposed: 'boolean',\n /** Non-null when that exposure is not backed by a password. */\n blockedReason: 'string | null',\n sessionTtlMs: 'number',\n cookieSecure: 'string',\n trustProxy: 'boolean',\n maxLoginAttempts: 'number',\n lockoutMs: 'number',\n})\nexport type AuthStatus = typeof authStatusSchema.infer\n\nexport const tlsStatusSchema = type({\n enabled: 'boolean',\n certPresent: 'boolean',\n subject: 'string | null',\n issuer: 'string | null',\n validFrom: 'string | null',\n validTo: 'string | null',\n daysRemaining: 'number | null',\n fingerprint: 'string | null',\n keyMatches: 'boolean | null',\n error: 'string | null',\n})\nexport type TlsStatus = typeof tlsStatusSchema.infer\n\nexport const telegramStatusSchema = type({\n enabled: 'boolean',\n tokenSet: 'boolean',\n chatId: 'string',\n onCrash: 'boolean',\n onUnhealthy: 'boolean',\n onForcedRestart: 'boolean',\n onRecovered: 'boolean',\n /** Host vitals breaches (disk, memory, swap, load, temperature). */\n onHost: 'boolean',\n cooldownMs: 'number',\n /** Last delivery outcome, for the settings page. */\n lastResult: 'string | null',\n lastResultAt: 'number | null',\n})\nexport type TelegramStatus = typeof telegramStatusSchema.infer\n\nexport const notificationViewSchema = type({\n telegram: telegramStatusSchema,\n})\nexport type NotificationView = typeof notificationViewSchema.infer\n\nexport const sessionViewSchema = type({\n authenticated: 'boolean',\n authRequired: 'boolean',\n passwordSet: 'boolean',\n /** A long-lived API token is configured; optional for the same reason as above. */\n apiTokenSet: 'boolean?',\n usingDefaultPassword: 'boolean',\n /** The boot-time password, exposed only while it is still in use. */\n defaultPassword: 'string | null',\n sessionTtlMs: 'number',\n})\nexport type SessionView = typeof sessionViewSchema.infer\n\nexport const loginSchema = type({ password: 'string' }).onUndeclaredKey('reject')\nexport type LoginRequest = typeof loginSchema.infer\n\n/** Any non-empty password is allowed; only a sanity cap on the length. */\nexport const passwordValueSchema = type('1 <= string <= 512')\n\nexport const passwordSchema = type({\n currentPassword: 'string?',\n newPassword: passwordValueSchema,\n}).onUndeclaredKey('reject')\nexport type PasswordRequest = typeof passwordSchema.infer\n\nexport const serverStatusSchema = type('\"stopped\" | \"starting\" | \"running\" | \"stopping\" | \"backoff\" | \"crashed\" | \"conflict\"')\nexport type ServerStatus = typeof serverStatusSchema.infer\n\nexport const healthStateSchema = type('\"disabled\" | \"unknown\" | \"healthy\" | \"unhealthy\"')\nexport type HealthState = typeof healthStateSchema.infer\n\nexport const portStateSchema = type('\"unknown\" | \"free\" | \"in-use\"')\nexport type PortState = typeof portStateSchema.infer\n\nexport const logStreamSchema = type('\"stdout\" | \"stderr\" | \"system\"')\nexport type LogStream = typeof logStreamSchema.infer\n\nexport const logLineSchema = type({\n ts: 'number',\n stream: logStreamSchema,\n text: 'string',\n})\nexport type LogLine = typeof logLineSchema.infer\n\nexport const historyEventSchema = type({\n serverId: 'string',\n ts: 'number',\n type: '\"start\" | \"exit\" | \"crash\" | \"forced-restart\" | \"unhealthy\" | \"recovered\"',\n detail: 'string',\n /** How long the process had been up, recorded on exit and crash. */\n runtimeMs: 'number?',\n})\nexport type HistoryEvent = typeof historyEventSchema.infer\n\n/** Rolling window stats derived from the persisted event log. */\nexport const serverHistorySchema = type({\n windowMs: 'number',\n /** Share of the window the process was up (null when nothing is known yet). */\n uptimeRatio: 'number | null',\n restarts: 'number',\n crashes: 'number',\n forcedRestarts: 'number',\n lastCrashAt: 'number | null',\n lastExitAt: 'number | null',\n lastRuntimeMs: 'number | null',\n events: historyEventSchema.array(),\n})\nexport type ServerHistory = typeof serverHistorySchema.infer\n\nexport const processResourcesSchema = type({\n cpuPercent: 'number | null',\n /** RSS of the process and its descendants. */\n rssBytes: 'number | null',\n processes: 'number',\n sampledAt: 'number',\n})\nexport type ProcessResources = typeof processResourcesSchema.infer\n\nexport const hostDiskSchema = type({\n path: 'string',\n totalBytes: 'number',\n freeBytes: 'number',\n usedPercent: 'number',\n})\n\nexport const hostViewSchema = type({\n enabled: 'boolean',\n cpus: 'number',\n loadAvg: type('number[]'),\n uptimeMs: 'number',\n memoryUsedPercent: 'number',\n swapUsedPercent: 'number',\n tempCelsius: 'number | null',\n disks: hostDiskSchema.array(),\n /** Human readable threshold breaches, for the banner and notifications. */\n alerts: type('string[]'),\n sampledAt: 'number | null',\n})\nexport type HostView = typeof hostViewSchema.infer\n\nexport const backupFileSchema = type({\n name: 'string',\n sizeBytes: 'number',\n createdAt: 'number',\n /** The archive carries a password-protected payload. */\n encrypted: 'boolean',\n})\n\nexport type BackupFile = typeof backupFileSchema.infer\n\n/** One declared data path, with the reason it will (or will not) be captured. */\nexport const backupPathSchema = type({\n path: 'string',\n /** Who declared it: `global`, `<serverId>:backupPaths` or `<serverId>:<ENV>`. */\n origin: 'string',\n /** false when a parent path already covers it, or it would swallow the archive dir. */\n included: 'boolean',\n note: 'string | null',\n /** The declaring entry asked for generated directories to be skipped. */\n ignoreGenerated: 'boolean?',\n})\nexport type BackupPath = typeof backupPathSchema.infer\n\nexport const backupsViewSchema = type({\n enabled: 'boolean',\n dir: 'string',\n keep: 'number',\n /** Extra paths from the config, in addition to each server's own. */\n includePaths: type('string[]'),\n /** Every declared path that will be picked up, for the UI to show. */\n paths: backupPathSchema.array(),\n files: backupFileSchema.array(),\n})\nexport type BackupsView = typeof backupsViewSchema.infer\n\n/** One restorable slice of an archive: the panel's own state or a data path. */\nexport const restoreItemSchema = type({\n /** `config` | `secrets` | `tls`, or the data path itself. */\n id: 'string',\n label: 'string',\n kind: '\"config\" | \"secrets\" | \"tls\" | \"data\"',\n /** false when the current config does not declare it, or it is not in the archive. */\n restorable: 'boolean',\n /** Echo of the request's selection, so the checkboxes round-trip. */\n selected: 'boolean',\n note: 'string | null',\n})\nexport type RestoreItem = typeof restoreItemSchema.infer\n\nexport const restorePlanSchema = type({\n dryRun: 'boolean',\n encrypted: 'boolean',\n /** The archive needs a password (none or a wrong one was supplied). */\n needsPassword: 'boolean',\n items: restoreItemSchema.array(),\n applied: type('string[]'),\n skipped: type('string[]'),\n /** Only the panel's own listener needs a restart; its servers are re-read live. */\n restartRequired: 'boolean',\n /** The panel re-read the restored config within this same restore. */\n reloaded: 'boolean',\n error: 'string?',\n})\nexport type RestorePlan = typeof restorePlanSchema.infer\n\nexport const backupCreateSchema = type({\n /** Optional: encrypts the archive. Never stored. */\n password: passwordValueSchema.optional(),\n}).onUndeclaredKey('reject')\nexport type BackupCreate = typeof backupCreateSchema.infer\n\nexport const restoreRequestSchema = type({\n name: 'string?',\n password: passwordValueSchema.optional(),\n /** Item ids to restore; omitted means every restorable item. */\n include: type('string[]').optional(),\n}).onUndeclaredKey('reject')\nexport type RestoreRequest = typeof restoreRequestSchema.infer\n\n/** Runtime view of a server: its effective config plus everything observed. */\nexport const serverViewSchema = type({\n id: 'string',\n config: serverSchema,\n bindHost: 'string',\n url: 'string | null',\n status: serverStatusSchema,\n health: healthStateSchema,\n portState: portStateSchema,\n pid: 'number | null',\n /** True when the process serving this entry was adopted, not spawned by the panel. */\n adopted: 'boolean?',\n startedAt: 'number | null',\n exitCode: 'number | null',\n exitSignal: 'string | null',\n restarts: 'number',\n maxRetries: 'number',\n lastError: 'string | null',\n nextRetryAt: 'number | null',\n unhealthySince: 'number | null',\n bufferedLines: 'number',\n history: serverHistorySchema,\n /** Last health probe latency (TCP connect or HTTP request). */\n responseMs: 'number | null',\n resources: processResourcesSchema.or(type('null')),\n})\n// `config` is emitted normalized (port is always `number | null`, never absent),\n// while the schema accepts both forms so a hand-written payload still validates.\nexport type ServerView = Omit<typeof serverViewSchema.infer, 'config'> & { config: ServerConfig }\n\nexport const controlViewSchema = type({\n /** The configured panel name, for the shell to render. */\n label: 'string',\n port: 'number',\n /** The configured bind value (`local` | `lan` | ipv4). */\n host: 'string',\n /** The address actually bound. */\n bindHost: 'string',\n url: 'string',\n openBrowser: 'boolean',\n /** The live listener differs from the configured host/port. */\n restartRequired: 'boolean',\n protocol: 'string',\n auth: authStatusSchema,\n tls: tlsStatusSchema,\n})\nexport type ControlView = typeof controlViewSchema.infer\n\nexport const appStateSchema = type({\n control: controlViewSchema,\n defaults: defaultsSchema,\n logs: logsSchema,\n notifications: notificationViewSchema,\n host: hostViewSchema,\n backups: backupsViewSchema,\n configPath: 'string',\n configError: 'string | null',\n /** The directory the panel was started from; relative entry paths use it. */\n projectDir: 'string',\n /** `HHOSTED_HOME`: every file home-hosted owns lives under here. */\n dataRoot: 'string',\n logsDir: 'string',\n servers: serverViewSchema.array(),\n})\nexport type AppState = Omit<typeof appStateSchema.infer, 'servers'> & { servers: ServerView[] }\n\nexport const sseMessageSchema = type({\n type: '\"hello\" | \"state\" | \"log\" | \"server\"',\n ts: 'number',\n serverId: 'string?',\n state: appStateSchema.optional(),\n server: serverViewSchema.optional(),\n lines: logLineSchema.array().optional(),\n})\nexport type SseMessage = typeof sseMessageSchema.infer\n\nexport const logQuerySchema = type({\n limit: 'string?',\n})\n\n/**\n * What a `free-port` attempt did. The pids are reported so the UI can say which\n * process left, and which listeners were deliberately left alone because this\n * panel supervises them.\n */\nexport const freePortResultSchema = type({\n ok: 'boolean',\n port: 'number | null',\n /** Asked to leave with SIGTERM, and the ones that ignored it. */\n terminated: 'number[]',\n forced: 'number[]',\n /** Listeners this panel supervises; never signalled. */\n skipped: 'number[]',\n /** The port answers no more after the attempt. */\n free: 'boolean',\n})\nexport type FreePortResult = typeof freePortResultSchema.infer\n\nexport const logHistoryQuerySchema = type({\n tail: 'string?',\n /** Case-insensitive substring filter over the tail window. */\n search: 'string?',\n stream: '\"stdout\" | \"stderr\" | \"system\"?',\n})\n\nexport const logFileInfoSchema = type({\n name: 'string',\n sizeBytes: 'number',\n})\n\nexport const logServerViewSchema = type({\n serverId: 'string',\n label: 'string',\n status: serverStatusSchema,\n enabled: 'boolean',\n sizeBytes: 'number',\n files: logFileInfoSchema.array(),\n})\nexport type LogServerView = typeof logServerViewSchema.infer\n\nexport const logServersViewSchema = type({\n servers: logServerViewSchema.array(),\n})\n\nexport const logHistoryViewSchema = type({\n serverId: 'string',\n enabled: 'boolean',\n sizeBytes: 'number',\n files: type('string[]'),\n /** How many lines the search looked at, or null when not searching. */\n searched: 'number | null',\n lines: logLineSchema.array(),\n})\nexport type LogHistoryView = typeof logHistoryViewSchema.infer\n\nexport type TelegramToken = typeof telegramTokenSchema.infer\n\nexport const notificationActionSchema = type({\n /** Optional override, so the token can be tested before it is saved. */\n botToken: 'string?',\n chatId: 'string?',\n}).onUndeclaredKey('reject')\nexport type NotificationAction = typeof notificationActionSchema.infer\n\nexport const telegramTokenSchema = type({\n botToken: 'string >= 1',\n}).onUndeclaredKey('reject')\n\n/** The single error envelope every route answers failures with. */\nexport const apiErrorSchema = type({\n message: 'string',\n /** Stable, machine-readable; `AUTH_REQUIRED` also drives the login redirect. */\n code: 'string',\n detail: 'unknown',\n}).onUndeclaredKey('reject')\nexport type ApiError = typeof apiErrorSchema.infer\n\n/**\n * A user-supplied UI, as the settings page shows it.\n *\n * Every field is optional, and none of them is a fallback: a `ui.json` may be written by\n * the panel (which adds `uploadedAt`/`files`) **or dropped in by hand** following\n * `docs/UI_CREATION.md`, which documents only the author-facing fields. Requiring ours\n * meant a hand-written file parsed to nothing at all, taking `repo`/`tag` with it and\n * silently disabling `ui-update` for exactly the UI that declared itself.\n */\nexport const uiMetaSchema = type({\n 'name?': 'string',\n 'version?': 'string | null',\n /** Set by the panel, not by the author. */\n 'uploadedAt?': 'number',\n /** Counted by the panel, not declared. */\n 'files?': 'number.integer >= 1',\n /** `owner/name` of the UI's own repository, for `ui-update`. */\n 'repo?': 'string',\n /** The release tag this build came from, e.g. `v0.6.0`. */\n 'tag?': 'string',\n /** The release asset name, e.g. `home-hosted-ui-noc-console`. */\n 'asset?': 'string',\n /** When the UI was built, in unix epoch seconds. */\n 'unix?': 'number.integer >= 0',\n})\nexport type UiMeta = typeof uiMetaSchema.infer\n\nexport const uiStatusSchema = type({\n /** A user-supplied UI is being served instead of the stock one. */\n custom: 'boolean',\n /** Where that UI lives, whether or not it exists yet. */\n dir: 'string',\n meta: uiMetaSchema.or(type('null')),\n})\nexport type UiStatus = typeof uiStatusSchema.infer\n\nexport const tlsUploadSchema = type({\n certificate: 'string >= 1',\n privateKey: 'string >= 1',\n}).onUndeclaredKey('reject')\nexport type TlsUpload = typeof tlsUploadSchema.infer\n\n/** `GET /api/settings`: the panel's own configuration, as the settings page reads it. */\nexport const settingsViewSchema = type({\n control: controlViewSchema,\n defaults: defaultsSchema,\n logs: logsSchema,\n notifications: notificationViewSchema,\n host: hostSchema,\n backups: backupsViewSchema,\n ui: uiStatusSchema,\n})\nexport type SettingsView = typeof settingsViewSchema.infer\n\n/** `PATCH /api/settings` answers with the saved view, plus where the listener lands. */\nexport const settingsSavedSchema = settingsViewSchema.and(type({\n /** The listener is moving; reconnect at `targetUrl` when it stops being null. */\n rebinding: 'boolean',\n targetUrl: 'string | null',\n}))\nexport type SettingsSaved = typeof settingsSavedSchema.infer\n","import type { StandardSchemaV1 } from '@standard-schema/spec'\nimport { resolver } from 'hono-openapi'\nimport { apiErrorSchema } from '#src/shared/contracts'\n\n/**\n * A JSON response body for `describeRoute`. The resolver needs a real Standard\n * Schema, so only ArkType schemas go in here — never a hand-written JSON schema.\n */\nexport function jsonBody(schema: StandardSchemaV1) {\n return { 'application/json': { schema: resolver(schema as never) } }\n}\n\n/** The envelope every failing request gets (see `src/helpers/error.ts`). */\nexport const ERROR_RESPONSES = {\n 400: { description: 'The request was rejected', content: jsonBody(apiErrorSchema) },\n 401: { description: 'No valid session', content: jsonBody(apiErrorSchema) },\n 404: { description: 'Unknown id', content: jsonBody(apiErrorSchema) },\n} as const\n","import type { StandardSchemaV1 } from '@standard-schema/spec'\nimport type { ValidationTargets } from 'hono'\nimport { DetailedError } from '@namesmt/utils'\nimport { validator as standardValidator } from 'hono-openapi'\n\n/**\n * ArkType-backed request validation. On success Hono stores the *parsed* value,\n * so `c.req.valid('json')` is fully typed and already normalized; on failure it\n * becomes a `DetailedError`, which the global error handler turns into the one\n * error envelope this API speaks.\n */\nexport function validate<Target extends keyof ValidationTargets, Schema extends StandardSchemaV1>(target: Target, schema: Schema) {\n return standardValidator(target, schema, (result) => {\n if (result.success === false)\n throw new DetailedError('validation failed', { statusCode: 400, detail: normalizeIssues(result.error) })\n })\n}\n\n/** ArkType issues serialize poorly, so only the fields a client can act on survive. */\nfunction normalizeIssues(error: StandardSchemaV1.FailureResult['issues']): Array<{ path: string, message: string }> {\n return error.map((issue) => {\n const path = (issue.path ?? [])\n .map(segment => (typeof segment === 'object' && segment !== null && 'key' in segment ? String(segment.key) : String(segment)))\n .join('.')\n return { path, message: issue.message }\n })\n}\n","/** Client address helpers shared by the auth guard and the setup routes. */\n\nexport function requestIp(c: { req: { raw: unknown } }): string | null {\n // srvx resolves this hop-aware from `trustProxy` + x-forwarded-for.\n const raw = c.req.raw as { ip?: string } | undefined\n return raw?.ip ?? null\n}\n\nexport function isLoopback(address: string | null): boolean {\n if (!address)\n return false\n return address === '127.0.0.1' || address === '::1' || address === '::ffff:127.0.0.1'\n}\n\nexport function isLoopbackRequest(c: { req: { raw: unknown } }): boolean {\n return isLoopback(requestIp(c))\n}\n","import fs from 'node:fs'\nimport path from 'node:path'\nimport process from 'node:process'\n\nexport interface WriteFileOptions {\n /** Applied to the temp file before the rename, so secrets never exist world-readable. */\n mode?: number\n}\n\n/**\n * Write via a temp file in the same directory, then rename: readers never see a\n * half-written file, and a crash cannot truncate the previous config.\n */\nexport function writeFileAtomic(file: string, content: string, options: WriteFileOptions = {}): void {\n fs.mkdirSync(path.dirname(file), { recursive: true })\n const tmp = `${file}.${process.pid}.tmp`\n fs.writeFileSync(tmp, content, options.mode === undefined ? undefined : { mode: options.mode })\n if (options.mode !== undefined)\n fs.chmodSync(tmp, options.mode)\n fs.renameSync(tmp, file)\n}\n","import { Buffer } from 'node:buffer'\nimport crypto from 'node:crypto'\nimport fs from 'node:fs'\nimport { writeFileAtomic } from '#src/helpers/atomic'\n\n/** Node's scrypt defaults, pinned so a hash stays verifiable across versions. */\nconst COST = { N: 16384, r: 8, p: 1 } as const\nconst KEYLEN = 64\nconst SALT_BYTES = 16\n\nexport interface PasswordRecord {\n algo: 'scrypt'\n salt: string\n hash: string\n keylen: number\n cost: { N: number, r: number, p: number }\n updatedAt: number\n /** Set for the boot-time default, so the UI can say so and exposure stays blocked. */\n isDefault?: boolean\n}\n\n/**\n * An API token for scripts and agents. Tokens are high-entropy randoms, so a\n * plain SHA-256 is the right hash — scrypt would only make every request pay\n * for a slow KDF it does not need. `hint` is the readable head, kept so a human\n * can tell two tokens apart without the file ever holding the whole secret.\n */\nexport interface ApiTokenRecord {\n algo: 'sha256'\n hash: string\n hint: string\n updatedAt: number\n}\n\ninterface SecretsFile {\n version: 3\n password: PasswordRecord | null\n apiToken: ApiTokenRecord | null\n telegram: { botToken: string } | null\n}\n\nexport interface ScryptCost { N: number, r: number, p: number }\n\nexport function deriveKey(password: string, salt: Buffer, cost: ScryptCost, keylen: number): Buffer {\n return crypto.scryptSync(password.normalize('NFKC'), salt, keylen, { ...cost })\n}\n\nexport function hashPassword(password: string, options: { now?: number, isDefault?: boolean } = {}): PasswordRecord {\n const salt = crypto.randomBytes(SALT_BYTES)\n return {\n algo: 'scrypt',\n salt: salt.toString('base64'),\n hash: deriveKey(password, salt, COST, KEYLEN).toString('base64'),\n keylen: KEYLEN,\n cost: { ...COST },\n updatedAt: options.now ?? Date.now(),\n ...(options.isDefault === true ? { isDefault: true } : {}),\n }\n}\n\nexport function verifyPassword(password: string, record: PasswordRecord): boolean {\n const expected = Buffer.from(record.hash, 'base64')\n let actual: Buffer\n try {\n actual = deriveKey(password, Buffer.from(record.salt, 'base64'), record.cost, record.keylen)\n }\n catch {\n return false\n }\n if (actual.length !== expected.length)\n return false\n return crypto.timingSafeEqual(actual, expected)\n}\n\n/** Visible head of a generated token, so a token is recognisable on sight. */\nexport const API_TOKEN_PREFIX = 'hh_'\nconst API_TOKEN_BYTES = 32\nconst API_TOKEN_HINT_CHARS = 8\n\nexport function generateApiToken(): string {\n return `${API_TOKEN_PREFIX}${crypto.randomBytes(API_TOKEN_BYTES).toString('base64url')}`\n}\n\nexport function hashApiToken(token: string): string {\n return crypto.createHash('sha256').update(token, 'utf8').digest('base64')\n}\n\nexport function verifyApiToken(token: string, record: ApiTokenRecord): boolean {\n if (token.length === 0)\n return false\n const expected = Buffer.from(record.hash, 'base64')\n const actual = Buffer.from(hashApiToken(token), 'base64')\n // A hand-edited or truncated secrets file must not make every request throw.\n if (actual.length !== expected.length)\n return false\n return crypto.timingSafeEqual(actual, expected)\n}\n\nexport function apiTokenRecord(token: string, now = Date.now()): ApiTokenRecord {\n return {\n algo: 'sha256',\n hash: hashApiToken(token),\n hint: token.slice(0, API_TOKEN_HINT_CHARS),\n updatedAt: now,\n }\n}\n\n/**\n * The password hash and the API token are secrets, so they live outside\n * `servers.config.json` (which is tracked) in a 0600 file that is git-ignored.\n */\nexport class SecretsStore {\n private cache: SecretsFile | null = null\n private cacheKey = ''\n\n constructor(private readonly file: string) {}\n\n get path(): string {\n return this.file\n }\n\n /**\n * Re-reads whenever the file changes on disk, so a password set by\n * `pnpm run set-password` (or another process) takes effect without\n * restarting `up`.\n */\n load(): SecretsFile {\n const key = this.statKey()\n if (this.cache !== null && key === this.cacheKey)\n return this.cache\n this.cache = this.read()\n this.cacheKey = key\n return this.cache\n }\n\n private statKey(): string {\n try {\n const stats = fs.statSync(this.file)\n return `${stats.mtimeMs}:${stats.size}`\n }\n catch {\n return 'missing'\n }\n }\n\n get password(): PasswordRecord | null {\n return this.load().password\n }\n\n get passwordUpdatedAt(): number | null {\n return this.password?.updatedAt ?? null\n }\n\n get passwordSet(): boolean {\n return this.password !== null\n }\n\n get usingDefaultPassword(): boolean {\n return this.password?.isDefault === true\n }\n\n get apiToken(): ApiTokenRecord | null {\n return this.load().apiToken\n }\n\n get apiTokenSet(): boolean {\n return this.apiToken !== null\n }\n\n /** The readable head of the stored token, or null when none is set. */\n get apiTokenHint(): string | null {\n return this.apiToken?.hint ?? null\n }\n\n get telegramToken(): string | null {\n return this.load().telegram?.botToken ?? null\n }\n\n get telegramTokenSet(): boolean {\n return (this.telegramToken ?? '').length > 0\n }\n\n setPassword(password: string, options: { isDefault?: boolean } = {}): PasswordRecord {\n const record = hashPassword(password, options)\n this.save({ ...this.load(), password: record })\n return record\n }\n\n /** Creates the default password only when none exists yet. */\n ensureDefaultPassword(password: string): PasswordRecord | null {\n if (this.passwordSet)\n return null\n return this.setPassword(password, { isDefault: true })\n }\n\n clearPassword(): void {\n this.save({ ...this.load(), password: null })\n }\n\n setApiToken(token: string): ApiTokenRecord {\n const record = apiTokenRecord(token)\n this.save({ ...this.load(), apiToken: record })\n return record\n }\n\n clearApiToken(): void {\n this.save({ ...this.load(), apiToken: null })\n }\n\n setTelegramToken(token: string | null): void {\n const trimmed = token?.trim() ?? ''\n this.save({ ...this.load(), telegram: trimmed.length > 0 ? { botToken: trimmed } : null })\n }\n\n private read(): SecretsFile {\n if (!fs.existsSync(this.file))\n return { version: 3, password: null, apiToken: null, telegram: null }\n try {\n const parsed = JSON.parse(fs.readFileSync(this.file, 'utf8')) as Partial<SecretsFile>\n return {\n version: 3,\n // A version-2 file simply has no token, so it reads as \"none set\".\n password: parsed?.password ?? null,\n apiToken: parsed?.apiToken?.hash ? parsed.apiToken : null,\n telegram: parsed?.telegram?.botToken ? { botToken: parsed.telegram.botToken } : null,\n }\n }\n catch {\n // A corrupt secrets file must not silently authenticate anyone.\n return { version: 3, password: null, apiToken: null, telegram: null }\n }\n }\n\n private save(contents: SecretsFile): void {\n writeFileAtomic(this.file, `${JSON.stringify(contents, null, 2)}\\n`, { mode: 0o600 })\n this.cache = contents\n }\n}\n","import type { SecretsStore } from '#src/config/secrets'\nimport type { AuthConfig, SessionView } from '#src/shared/contracts'\nimport crypto from 'node:crypto'\nimport { verifyApiToken, verifyPassword } from '#src/config/secrets'\nimport { parseCookies } from '#src/helpers/cookies'\n\nexport const SESSION_COOKIE = 'hh_session'\n\n/** Created on first boot when no password exists; exposure stays blocked until it changes. */\nexport const DEFAULT_PASSWORD = 'hh'\n\nconst MAX_SESSIONS = 100\nconst MAX_LOCKOUT_MS = 15 * 60_000\n/** Bounds on the per-IP bookkeeping, which an attacker can otherwise grow. */\nconst MAX_ATTEMPT_RECORDS = 10_000\nconst ATTEMPT_RECORD_TTL_MS = 60 * 60_000\n\nexport interface SessionRecord {\n token: string\n createdAt: number\n expiresAt: number\n lastSeenAt: number\n ip: string | null\n}\n\ninterface AttemptRecord {\n failures: number\n blockedUntil: number\n blocks: number\n /** For expiring idle records: with `trustProxy` the key is client-chosen. */\n lastAttemptAt: number\n}\n\nexport type LoginOutcome\n = | { ok: true, status: 200, token: string, maxAgeMs: number }\n | { ok: false, status: 401 | 409 | 429, error: string, retryAfterMs?: number }\n\n/** Which credential a request presented. */\nexport type AuthMethod = 'cookie' | 'token'\n\nexport interface AuthIdentity {\n authenticated: boolean\n method: AuthMethod | null\n /** The session record, when the cookie resolved to one. */\n session: SessionRecord | null\n}\n\nexport const ANONYMOUS: AuthIdentity = { authenticated: false, method: null, session: null }\n\n/**\n * `Authorization: Bearer <token>`; the scheme is case-insensitive per RFC 7235,\n * and the credential is taken whole so a stray space cannot silently shorten it.\n */\nexport function bearerToken(header: string | null | undefined): string | null {\n if (!header)\n return null\n const [scheme, ...rest] = header.trim().split(/\\s+/)\n if (scheme?.toLowerCase() !== 'bearer')\n return null\n const token = rest.join('')\n return token.length > 0 ? token : null\n}\n\n/**\n * Authentication for the control panel.\n *\n * Two credentials are accepted: the browser's session cookie, and a long-lived\n * API token for scripts and agents. They carry the same authority on purpose —\n * a token that could do less than a signed-in browser would only be surprising.\n *\n * The password hash lives in the git-ignored secrets file; sessions live only in\n * memory, so restarting `up` invalidates every session, while a token outlives\n * the process until it is cleared.\n */\nexport class AuthService {\n private readonly sessions = new Map<string, SessionRecord>()\n private readonly attempts = new Map<string, AttemptRecord>()\n private readonly timer: NodeJS.Timeout\n\n constructor(\n private readonly secrets: SecretsStore,\n private readonly getConfig: () => AuthConfig,\n ) {\n this.timer = setInterval(() => this.cleanup(), 60_000)\n this.timer.unref()\n }\n\n get passwordSet(): boolean {\n return this.secrets.passwordSet\n }\n\n get passwordUpdatedAt(): number | null {\n return this.secrets.passwordUpdatedAt\n }\n\n /** Still the boot-time default: the login page says so, exposure stays blocked. */\n get usingDefaultPassword(): boolean {\n return this.secrets.usingDefaultPassword\n }\n\n /** The feature is on. */\n isEnabled(): boolean {\n return this.getConfig().enabled\n }\n\n /** A password is set, so a browser has something to sign in with. */\n isArmed(): boolean {\n return this.getConfig().enabled && this.secrets.passwordSet\n }\n\n /** Kept as the \"should the guard demand a session\" predicate. */\n isRequired(): boolean {\n return this.isArmed()\n }\n\n get apiTokenSet(): boolean {\n return this.secrets.apiTokenSet\n }\n\n /** The readable head of the stored token, never the token itself. */\n get apiTokenHint(): string | null {\n return this.secrets.apiTokenHint\n }\n\n /** Compared against the stored SHA-256, in constant time. */\n validateApiToken(token: string | null): boolean {\n if (token === null)\n return false\n const record = this.secrets.apiToken\n if (record === null)\n return false\n return verifyApiToken(token, record)\n }\n\n /**\n * Resolves whichever credential the request carried. The token is only\n * consulted when no session matched, so a stale cookie cannot mask it.\n */\n authenticate(credentials: { cookieToken: string | null, bearerToken: string | null }): AuthIdentity {\n const session = this.validate(credentials.cookieToken)\n if (session !== null)\n return { authenticated: true, method: 'cookie', session }\n if (this.validateApiToken(credentials.bearerToken))\n return { authenticated: true, method: 'token', session: null }\n return ANONYMOUS\n }\n\n sessionView(authenticated: boolean): SessionView {\n return {\n authenticated,\n authRequired: this.isRequired(),\n passwordSet: this.secrets.passwordSet,\n apiTokenSet: this.secrets.apiTokenSet,\n usingDefaultPassword: this.secrets.usingDefaultPassword,\n defaultPassword: this.secrets.usingDefaultPassword ? DEFAULT_PASSWORD : null,\n sessionTtlMs: this.getConfig().sessionTtlMs,\n }\n }\n\n tokenFromCookie(cookieHeader: string | null | undefined): string | null {\n return parseCookies(cookieHeader)[SESSION_COOKIE] ?? null\n }\n\n /** Sliding expiry: an active panel stays logged in, an idle one does not. */\n validate(token: string | null): SessionRecord | null {\n if (!token)\n return null\n const session = this.sessions.get(token)\n if (!session)\n return null\n\n const now = Date.now()\n if (session.expiresAt <= now) {\n this.sessions.delete(token)\n return null\n }\n\n session.lastSeenAt = now\n session.expiresAt = now + this.getConfig().sessionTtlMs\n return session\n }\n\n verifyCurrentPassword(password: string): boolean {\n const record = this.secrets.password\n if (record === null)\n return false\n return verifyPassword(password, record)\n }\n\n login(password: string, ip: string | null): LoginOutcome {\n const config = this.getConfig()\n const key = ip ?? 'unknown'\n const now = Date.now()\n const attempt = this.attempts.get(key)\n\n if (attempt && attempt.blockedUntil > now) {\n const retryAfterMs = attempt.blockedUntil - now\n return {\n ok: false,\n status: 429,\n error: `too many failed attempts, retry in ${Math.ceil(retryAfterMs / 1000)}s`,\n retryAfterMs,\n }\n }\n\n const record = this.secrets.password\n if (record === null) {\n return { ok: false, status: 409, error: 'no password is set yet' }\n }\n\n if (!verifyPassword(password, record)) {\n const failures = (attempt?.failures ?? 0) + 1\n if (failures >= config.maxLoginAttempts) {\n const blocks = (attempt?.blocks ?? 0) + 1\n const blockedUntil = Date.now() + Math.min(config.lockoutMs * 2 ** (blocks - 1), MAX_LOCKOUT_MS)\n this.attempts.set(key, { failures: 0, blockedUntil, blocks, lastAttemptAt: Date.now() })\n }\n else {\n this.attempts.set(key, { failures, blockedUntil: 0, blocks: attempt?.blocks ?? 0, lastAttemptAt: Date.now() })\n }\n return { ok: false, status: 401, error: 'invalid password' }\n }\n\n this.attempts.delete(key)\n if (this.sessions.size >= MAX_SESSIONS) {\n const oldest = [...this.sessions.values()].sort((a, b) => a.lastSeenAt - b.lastSeenAt)[0]\n if (oldest)\n this.sessions.delete(oldest.token)\n }\n\n const token = crypto.randomBytes(32).toString('base64url')\n this.sessions.set(token, {\n token,\n createdAt: now,\n expiresAt: now + config.sessionTtlMs,\n lastSeenAt: now,\n ip,\n })\n\n return { ok: true, status: 200, token, maxAgeMs: config.sessionTtlMs }\n }\n\n logout(token: string | null): void {\n if (token)\n this.sessions.delete(token)\n }\n\n logoutAll(): void {\n this.sessions.clear()\n }\n\n /**\n * Changing the password must not leave old sessions valid. The session that\n * made the change is kept — being signed out of the page you just used is not\n * a security requirement, and it makes a successful change look like a failure.\n */\n setPassword(password: string, options: { isDefault?: boolean, keepToken?: string | null } = {}): void {\n this.secrets.setPassword(password, options)\n this.logoutOthers(options.keepToken ?? null)\n }\n\n /** Drops every session except one, which is how a password change stays signed in. */\n private logoutOthers(keep: string | null): void {\n if (keep === null) {\n this.sessions.clear()\n return\n }\n for (const token of [...this.sessions.keys()]) {\n if (token !== keep)\n this.sessions.delete(token)\n }\n }\n\n /** Creates the boot-time default only when nothing is set yet. */\n ensureDefaultPassword(password: string): boolean {\n const created = this.secrets.ensureDefaultPassword(password) !== null\n if (created)\n this.logoutAll()\n return created\n }\n\n clearPassword(): void {\n this.secrets.clearPassword()\n this.logoutAll()\n }\n\n activeSessions(): number {\n return this.sessions.size\n }\n\n dispose(): void {\n clearInterval(this.timer)\n this.sessions.clear()\n }\n\n private cleanup(): void {\n const now = Date.now()\n for (const [token, session] of this.sessions) {\n if (session.expiresAt <= now)\n this.sessions.delete(token)\n }\n for (const [key, attempt] of this.attempts) {\n // Idle records go, whether or not they were ever blocked — a failed login\n // from an address that never comes back must not be remembered forever.\n if (now - attempt.lastAttemptAt >= ATTEMPT_RECORD_TTL_MS)\n this.attempts.delete(key)\n }\n\n if (this.attempts.size > MAX_ATTEMPT_RECORDS) {\n const oldest = [...this.attempts.entries()]\n .sort((a, b) => a[1].lastAttemptAt - b[1].lastAttemptAt)\n .slice(0, this.attempts.size - MAX_ATTEMPT_RECORDS)\n for (const [key] of oldest) this.attempts.delete(key)\n }\n }\n}\n","import type { Context, MiddlewareHandler } from 'hono'\nimport type { AuthIdentity, AuthService } from '#src/services/auth'\nimport { DetailedError } from '@namesmt/utils'\nimport { isLoopbackRequest } from '#src/middleware/loopback'\nimport { bearerToken, SESSION_COOKIE } from '#src/services/auth'\n\n/** Endpoints the SPA needs before it can show a login form. */\nconst PUBLIC_PATHS = new Set(['/api/auth/login', '/api/auth/session'])\n\n/** 401s from the guard carry this code so the SPA can route to the login view. */\nexport const AUTH_REQUIRED_CODE = 'AUTH_REQUIRED'\n\n/**\n * The one place that reads a request's credentials, so the guard, the session\n * route and `/healthz` can never disagree about who is calling.\n */\nexport function requestIdentity(c: Context, auth: AuthService): AuthIdentity {\n return auth.authenticate({\n cookieToken: auth.tokenFromCookie(c.req.header('cookie')),\n bearerToken: bearerToken(c.req.header('authorization')),\n })\n}\n\nexport interface AuthGuardDeps {\n auth: AuthService\n /** Test hook: bind the guard to an explicit cookie header / ip. */\n now?: () => number\n}\n\n/**\n * Guards every `/api/*` route. The SPA shell stays public (it holds no data),\n * so the browser can load the app and show the login screen.\n *\n * A request may prove itself with the session cookie or with an API token\n * (`Authorization: Bearer …`), which is what lets a script or an agent drive the\n * panel without a browser. The token also works while auth is enabled but no\n * password is set — that state otherwise only trusts this machine.\n *\n * State-changing requests that carry an `Origin` must come from this same host:\n * with `SameSite=Strict` cookies that closes the cross-site CSRF path.\n */\nexport function createAuthGuard(deps: AuthGuardDeps): MiddlewareHandler {\n return async (c, next) => {\n const path = c.req.path\n\n if (path.startsWith('/api')) {\n const method = c.req.method\n if (method !== 'GET' && method !== 'HEAD') {\n const origin = c.req.header('origin')\n if (origin !== undefined) {\n // `Host` is required by HTTP/1.1 but not guaranteed to be set by every\n // client, so fall back to the authority the server itself resolved.\n const requestHost = c.req.header('host') ?? new URL(c.req.url).host\n let originHost: string | null = null\n try {\n originHost = new URL(origin).host\n }\n catch {\n originHost = null\n }\n if (originHost === null || originHost !== requestHost)\n throw new DetailedError('cross-origin request rejected', { statusCode: 403, code: 'CROSS_ORIGIN' })\n }\n }\n\n if (!PUBLIC_PATHS.has(path)) {\n const identity = requestIdentity(c, deps.auth)\n\n if (deps.auth.isArmed()) {\n if (!identity.authenticated) {\n if (deps.auth.apiTokenSet)\n c.header('WWW-Authenticate', 'Bearer realm=\"home-hosted\"')\n throw new DetailedError('authentication required', { statusCode: 401, code: AUTH_REQUIRED_CODE })\n }\n }\n else if (deps.auth.isEnabled()) {\n // Enabled but not armed: only an API token or this machine may look. A\n // proxied request must set `trustProxy` to be seen as remote, otherwise\n // it is indistinguishable from a local one.\n if (!identity.authenticated && !isLoopbackRequest(c)) {\n throw new DetailedError('authentication is enabled but no password is set — set one from the machine running the panel', {\n statusCode: 401,\n code: 'AUTH_UNARMED',\n })\n }\n }\n }\n }\n\n await next()\n }\n}\n\nexport { SESSION_COOKIE }\n\nexport { isLoopbackRequest, requestIp } from '#src/middleware/loopback'\n","import os from 'node:os'\n\nexport function lanAddress(): string | null {\n for (const entries of Object.values(os.networkInterfaces())) {\n for (const entry of entries ?? []) {\n if (entry.family === 'IPv4' && !entry.internal)\n return entry.address\n }\n }\n return null\n}\n\nexport function bindHost(bind: string): string {\n if (bind === 'local')\n return '127.0.0.1'\n if (bind === 'lan')\n return '0.0.0.0'\n return bind\n}\n\n/** Address a human should open, which is never `0.0.0.0`. */\nexport function displayHost(bind: string): string {\n if (bind === 'local')\n return '127.0.0.1'\n if (bind === 'lan')\n return lanAddress() ?? '127.0.0.1'\n return bind\n}\n\n/** True when the bind value makes the port reachable from outside this machine. */\nexport function isExposed(bind: string): boolean {\n return bindHost(bind) !== '127.0.0.1'\n}\n","import type { ControlConfig } from '#src/shared/contracts'\nimport { isExposed } from '#src/helpers/bind'\n\nexport interface ExposureState {\n /** The control panel listens beyond loopback. */\n exposed: boolean\n /** Non-null when that exposure is not backed by a password. */\n blockedReason: string | null\n}\n\n/**\n * Exposing the panel beyond loopback is only allowed with authentication fully\n * configured — this is checked at startup, on every settings write, and shown in\n * the UI, so the three can never disagree.\n */\nexport function checkExposure(control: ControlConfig, passwordSet: boolean, usingDefaultPassword = false): ExposureState {\n const exposed = isExposed(control.host)\n if (!exposed)\n return { exposed, blockedReason: null }\n\n if (!control.auth.enabled && !passwordSet) {\n return {\n exposed,\n blockedReason: `the control panel is bound to ${control.host} but authentication is disabled and no password is set`,\n }\n }\n if (!control.auth.enabled) {\n return { exposed, blockedReason: `the control panel is bound to ${control.host} but authentication is disabled` }\n }\n if (!passwordSet) {\n return {\n exposed,\n blockedReason: `the control panel is bound to ${control.host} but no password is set (run \\`pnpm run set-password\\`)`,\n }\n }\n if (usingDefaultPassword) {\n return {\n exposed,\n blockedReason: `the control panel is bound to ${control.host} but still uses the default password — change it first`,\n }\n }\n return { exposed, blockedReason: null }\n}\n","import type { Context } from 'hono'\nimport type { AppDeps } from '#src/app'\nimport type { LoginRequest, PasswordRequest } from '#src/shared/contracts'\nimport { DetailedError } from '@namesmt/utils'\nimport { describeRoute } from 'hono-openapi'\nimport { serializeCookie } from '#src/helpers/cookies'\nimport { appFactory } from '#src/helpers/factory'\nimport { ERROR_RESPONSES, jsonBody } from '#src/helpers/openapi'\nimport { validate } from '#src/helpers/validator'\nimport { AUTH_REQUIRED_CODE, requestIdentity, SESSION_COOKIE } from '#src/middleware/auth'\nimport { isLoopbackRequest, requestIp } from '#src/middleware/loopback'\nimport { checkExposure } from '#src/services/exposure'\nimport { loginSchema, passwordSchema, sessionViewSchema } from '#src/shared/contracts'\n\n/** `Secure` only helps over TLS, and would break plain http on a LAN. */\nfunction secureCookie(c: Context, deps: AppDeps): boolean {\n const mode = deps.store.config.control.auth.cookieSecure\n if (mode === 'always')\n return true\n if (mode === 'never')\n return false\n return new URL(c.req.url).protocol === 'https:'\n}\n\nexport function createAuthRoute(deps: AppDeps) {\n return appFactory.createApp()\n .get(\n '/auth/session',\n describeRoute({\n tags: ['auth'],\n summary: 'Who this request is, and how the panel is protected',\n responses: { 200: { description: 'Session', content: jsonBody(sessionViewSchema) } },\n }),\n c => c.json(deps.auth.sessionView(requestIdentity(c, deps.auth).authenticated)),\n )\n\n .post(\n '/auth/login',\n describeRoute({\n tags: ['auth'],\n summary: 'Exchange the panel password for a session cookie',\n responses: {\n 200: { description: 'Signed in', content: jsonBody(sessionViewSchema) },\n 400: ERROR_RESPONSES[400],\n 401: { description: 'Wrong password, or locked out (see `Retry-After`)' },\n },\n }),\n validate('json', loginSchema),\n (c) => {\n const body: LoginRequest = c.req.valid('json')\n const outcome = deps.auth.login(body.password, requestIp(c))\n\n if (!outcome.ok) {\n if (outcome.retryAfterMs !== undefined)\n c.header('Retry-After', String(Math.ceil(outcome.retryAfterMs / 1000)))\n throw new DetailedError(outcome.error, { statusCode: outcome.status, code: 'LOGIN_FAILED' })\n }\n\n c.header('Set-Cookie', serializeCookie(SESSION_COOKIE, outcome.token, {\n maxAgeMs: outcome.maxAgeMs,\n secure: secureCookie(c, deps),\n sameSite: 'Strict',\n httpOnly: true,\n }))\n\n return c.json(deps.auth.sessionView(true))\n },\n )\n\n .post(\n '/auth/logout',\n describeRoute({\n tags: ['auth'],\n summary: 'Drop the session cookie',\n responses: { 200: { description: 'Signed out' } },\n }),\n (c) => {\n deps.auth.logout(deps.auth.tokenFromCookie(c.req.header('cookie')))\n c.header('Set-Cookie', serializeCookie(SESSION_COOKIE, '', {\n maxAgeMs: 0,\n secure: secureCookie(c, deps),\n sameSite: 'Strict',\n httpOnly: true,\n }))\n return c.json({ ok: true })\n },\n )\n\n /**\n * First-time setup is allowed from loopback without a session (there is\n * nothing to authenticate against yet); every later change needs the session\n * and* the current password.\n */\n .post(\n '/auth/password',\n describeRoute({\n tags: ['auth'],\n summary: 'Set, change or enable the panel password',\n responses: { 200: { description: 'Updated' }, 400: ERROR_RESPONSES[400], 401: ERROR_RESPONSES[401] },\n }),\n validate('json', passwordSchema),\n (c) => {\n const body: PasswordRequest = c.req.valid('json')\n const identity = requestIdentity(c, deps.auth)\n const hadPassword = deps.auth.passwordSet\n const firstSetup = !hadPassword && isLoopbackRequest(c)\n\n if (!identity.authenticated && !firstSetup)\n throw new DetailedError('authentication required', { statusCode: 401, code: AUTH_REQUIRED_CODE })\n\n // The current password is demanded of every credential, so a stolen API\n // token cannot rewrite the password it would then need to be recovered from.\n if (identity.authenticated && hadPassword) {\n if (body.currentPassword === undefined)\n throw new DetailedError('currentPassword is required to change an existing password', { statusCode: 400, code: 'CURRENT_PASSWORD_REQUIRED' })\n if (!deps.auth.verifyCurrentPassword(body.currentPassword))\n throw new DetailedError('current password is incorrect', { statusCode: 401, code: 'CURRENT_PASSWORD_WRONG' })\n }\n\n // Keep the caller signed in: every *other* session is dropped. A token\n // caller holds no session, so this signs every browser out instead.\n deps.auth.setPassword(body.newPassword, { keepToken: identity.session?.token ?? null })\n\n // A password that is not enforced protects nothing, so the first setup enables it.\n let enabled = deps.store.config.control.auth.enabled\n if (!enabled) {\n deps.store.updateControl({ auth: { enabled: true } })\n enabled = true\n }\n\n return c.json({ ok: true, enabled, sessionsInvalidated: true })\n },\n )\n\n .delete(\n '/auth/password',\n describeRoute({\n tags: ['auth'],\n summary: 'Clear the password and turn authentication off',\n responses: { 200: { description: 'Cleared' }, 400: ERROR_RESPONSES[400], 401: ERROR_RESPONSES[401] },\n }),\n (c) => {\n if (!requestIdentity(c, deps.auth).authenticated)\n throw new DetailedError('authentication required', { statusCode: 401, code: AUTH_REQUIRED_CODE })\n\n const exposure = checkExposure(deps.store.config.control, false)\n if (exposure.exposed) {\n throw new DetailedError('refusing to clear the password while the control panel is bound beyond loopback — set the bind back to local first', {\n statusCode: 400,\n code: 'EXPOSED_WITHOUT_PASSWORD',\n })\n }\n\n deps.auth.clearPassword()\n deps.store.updateControl({ auth: { enabled: false } })\n return c.json({ ok: true })\n },\n )\n}\n","import type { ConsolaInstance } from 'consola'\nimport { createConsola, LogLevels } from 'consola'\nimport { isDevelopment } from 'std-env'\n\n/**\n * Note: this logger will log the `debug` level logs in development mode.\n *\n * For actual debug logs with `NODE_DEBUG`, it is recommended to use the `debug` package.\n */\nexport const logger: ConsolaInstance = createConsola(\n {\n level: isDevelopment ? LogLevels.debug : undefined,\n },\n)\n","import type { StandardSchemaV1 } from '@standard-schema/spec'\nimport { DetailedError } from '@namesmt/utils'\nimport { type } from 'arktype'\n\n/**\n * Validates a value that is not a request target (a query string, a URL\n * parameter, a payload read by hand) and fails with the standard error envelope.\n * Request bodies and queries that a route reads once go through the `validate()`\n * middleware instead, which also carries the type into the handler.\n */\nexport function parseOrThrow<T>(schema: (input: unknown) => unknown, input: unknown, label: string): T {\n const result = schema(input)\n if (result instanceof type.errors) {\n throw new DetailedError(`${label}: ${result.summary}`, {\n statusCode: 400,\n code: 'INVALID_INPUT',\n detail: result.issues.map(issue => ({ path: issue.path.join('.'), message: issue.message })),\n })\n }\n return result as T\n}\n\n/** ArkType is a Standard Schema, so the middleware accepts it as-is. */\nexport type ValidatorSchema = StandardSchemaV1\n","import os from 'node:os'\nimport path from 'node:path'\nimport process from 'node:process'\n\n/**\n * Where home-hosted keeps everything it owns: the servers config, the secrets\n * file, logs, TLS material, backups and the runtime file. `HHOSTED_HOME`\n * overrides it — which is how a project repo keeps its own state directory\n * while the package itself ships no configuration at all.\n */\nexport function resolveDataRoot(): string {\n const override = process.env.HHOSTED_HOME\n if (override !== undefined && override.length > 0)\n return path.resolve(override)\n return path.join(os.homedir(), '.home-hosted')\n}\n\nexport const dataRoot = resolveDataRoot()\n\n/**\n * The directory home-hosted was started from. Relative entry paths (`cwd`,\n * declared data directories) resolve against it, so the project's own launcher\n * decides the base instead of wherever the package happens to be installed.\n * `HHOSTED_PROJECT` pins it explicitly.\n */\nexport function resolveProjectDir(): string {\n const override = process.env.HHOSTED_PROJECT\n if (override !== undefined && override.length > 0)\n return path.resolve(override)\n return process.cwd()\n}\n\nexport const projectDir = resolveProjectDir()\n\nexport const defaultConfigPath = path.join(dataRoot, 'servers.config.json')\n/** Regenerated for editor autocomplete; kept beside the config it describes. */\nexport const configSchemaPath = path.join(dataRoot, 'servers.config.schema.json')\n/** Password hash + bot token; written with mode 0600. */\nexport const defaultSecretsPath = path.join(dataRoot, '.control-secrets.json')\n/** Rotated per-server JSONL logs. */\nexport const defaultLogsDir = path.join(dataRoot, '.logs')\n/** Persisted restart/crash history. */\nexport const defaultHistoryPath = path.join(dataRoot, '.logs', 'history.json')\n/** Uploaded TLS PEM pair (the key is written 0600). */\nexport const defaultTlsDir = path.join(dataRoot, '.tls')\n/** `run.json` records the live control plane; the log captures its console. */\nexport const runtimePath = path.join(dataRoot, 'run.json')\nexport const daemonLogPath = path.join(dataRoot, '.logs', 'home-hosted.log')\n\n/** Expands `~` and resolves relative paths against `base`, for config-declared paths. */\nexport function resolveUserPath(target: string, base = projectDir): string {\n let value = target\n if (value === '~')\n value = os.homedir()\n else if (value.startsWith('~/') || value.startsWith('~\\\\'))\n value = path.join(os.homedir(), value.slice(2))\n return path.isAbsolute(value) ? value : path.resolve(base, value)\n}\n","import type { ConfigStore } from '#src/config/store'\nimport type { AuthService } from '#src/services/auth'\nimport type { BackupService } from '#src/services/backups'\nimport type { ControlEndpoint } from '#src/services/control-server'\nimport type { HostMonitor } from '#src/services/host-monitor'\nimport type { NotificationService } from '#src/services/notifications'\nimport type { TlsStore } from '#src/services/tls'\nimport type { AppState, BackupsView, ControlConfig, ControlView, HostView, ServerDefaults, ServerView } from '#src/shared/contracts'\nimport { dataRoot, projectDir } from '#src/helpers/paths'\nimport { checkExposure } from '#src/services/exposure'\n\nexport interface BuildStateDeps {\n store: ConfigStore\n auth: AuthService\n control: ControlEndpoint\n tls: TlsStore\n notifications: NotificationService\n hostMonitor: HostMonitor\n backups: BackupService\n logsDir: string\n views: ServerView[]\n}\n\n/**\n * The panel's own settings are derived here — configured values from the store,\n * live values from the listener, security state from the auth service and the\n * certificate pair from disk — so the UI never has to reason about the\n * differences.\n */\nexport function buildControlView(\n store: ConfigStore,\n auth: AuthService,\n control: ControlEndpoint,\n tls: TlsStore,\n): ControlView {\n const config: ControlConfig = store.config.control\n const exposure = checkExposure(config, auth.passwordSet, auth.usingDefaultPassword)\n\n return {\n label: config.label,\n port: config.port,\n host: config.host,\n bindHost: control.bindHost,\n url: control.url,\n protocol: control.protocol,\n openBrowser: config.openBrowser,\n restartRequired: control.port !== config.port || control.host !== config.host,\n auth: {\n enabled: config.auth.enabled,\n passwordSet: auth.passwordSet,\n passwordUpdatedAt: auth.passwordUpdatedAt,\n apiTokenSet: auth.apiTokenSet,\n usingDefaultPassword: auth.usingDefaultPassword,\n exposed: exposure.exposed,\n blockedReason: exposure.blockedReason,\n sessionTtlMs: config.auth.sessionTtlMs,\n cookieSecure: config.auth.cookieSecure,\n trustProxy: config.auth.trustProxy,\n maxLoginAttempts: config.auth.maxLoginAttempts,\n lockoutMs: config.auth.lockoutMs,\n },\n tls: tls.status(config.tls.enabled),\n }\n}\n\nexport function buildDefaults(store: ConfigStore): ServerDefaults {\n return store.defaults\n}\n\nexport function buildBackupsView(store: ConfigStore, backups: BackupService): BackupsView {\n return {\n enabled: store.config.backups.enabled,\n dir: backups.directory,\n keep: store.config.backups.keep,\n includePaths: store.config.backups.includePaths,\n paths: backups.paths,\n files: backups.list(),\n }\n}\n\nexport function buildHostView(hostMonitor: HostMonitor): HostView {\n return hostMonitor.view\n}\n\nexport function buildAppState(deps: BuildStateDeps): AppState {\n return {\n control: buildControlView(deps.store, deps.auth, deps.control, deps.tls),\n defaults: buildDefaults(deps.store),\n logs: deps.store.config.logs,\n notifications: { telegram: deps.notifications.status() },\n host: buildHostView(deps.hostMonitor),\n backups: buildBackupsView(deps.store, deps.backups),\n configPath: deps.store.path,\n configError: deps.store.configError,\n projectDir,\n dataRoot,\n logsDir: deps.logsDir,\n servers: deps.views,\n }\n}\n","import type { AppDeps } from '#src/app'\nimport type { BackupCreate, RestoreRequest } from '#src/shared/contracts'\nimport { Buffer } from 'node:buffer'\nimport crypto from 'node:crypto'\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport { DetailedError } from '@namesmt/utils'\nimport { type } from 'arktype'\nimport { describeRoute } from 'hono-openapi'\nimport { appFactory } from '#src/helpers/factory'\nimport { logger } from '#src/helpers/logger'\nimport { ERROR_RESPONSES, jsonBody } from '#src/helpers/openapi'\nimport { parseOrThrow } from '#src/helpers/validate'\nimport { validate } from '#src/helpers/validator'\nimport { buildBackupsView } from '#src/services/state'\nimport { backupCreateSchema, backupsViewSchema, restorePlanSchema, restoreRequestSchema } from '#src/shared/contracts'\n\n/** Uploads are buffered in memory by `parseBody`, so they get a hard ceiling. */\nconst MAX_UPLOAD_BYTES = 256 * 1024 * 1024\n\nconst nameParam = type({ name: 'string >= 1' })\n\n/** A backup that failed to be created is a bad request, not a server fault. */\nfunction backupFailed(error: string | undefined): DetailedError {\n return new DetailedError(error ?? 'the backup failed', { statusCode: 400, code: 'BACKUP_FAILED' })\n}\n\nexport function createBackupsRoute(deps: AppDeps) {\n return appFactory.createApp()\n .get(\n '/backups',\n describeRoute({\n tags: ['backups'],\n summary: 'Archives on disk, and the paths a backup would capture',\n responses: { 200: { description: 'Backups', content: jsonBody(backupsViewSchema) } },\n }),\n c => c.json(buildBackupsView(deps.store, deps.backups)),\n )\n\n .post(\n '/backups',\n describeRoute({\n tags: ['backups'],\n summary: 'Create a backup (optionally password-protected)',\n responses: { 200: { description: 'Created' }, 400: ERROR_RESPONSES[400] },\n }),\n validate('json', backupCreateSchema),\n async (c) => {\n const body: BackupCreate = c.req.valid('json')\n const result = await deps.backups.create({ password: body.password })\n if (!result.ok)\n throw backupFailed(result.error)\n return c.json({ file: result.file, files: deps.backups.list() })\n },\n )\n\n .get(\n '/backups/:name/download',\n describeRoute({\n tags: ['backups'],\n summary: 'Download one archive',\n responses: { 200: { description: 'the archive (application/zip)' }, 404: ERROR_RESPONSES[404] },\n }),\n validate('param', nameParam),\n (c) => {\n const file = deps.backups.resolve(c.req.valid('param').name)\n if (file === null)\n throw new DetailedError('unknown backup', { statusCode: 404, code: 'UNKNOWN_BACKUP' })\n\n const stats = fs.statSync(file)\n return c.body(fs.readFileSync(file), 200, {\n 'Content-Type': 'application/zip',\n 'Content-Length': String(stats.size),\n 'Content-Disposition': `attachment; filename=\"${path.basename(file)}\"`,\n })\n },\n )\n\n .delete(\n '/backups/:name',\n describeRoute({\n tags: ['backups'],\n summary: 'Delete one archive',\n responses: { 200: { description: 'Removed' }, 404: ERROR_RESPONSES[404] },\n }),\n validate('param', nameParam),\n (c) => {\n if (!deps.backups.remove(c.req.valid('param').name))\n throw new DetailedError('unknown backup', { statusCode: 404, code: 'UNKNOWN_BACKUP' })\n return c.json({ ok: true, files: deps.backups.list() })\n },\n )\n\n /**\n * Restore from a stored backup (`{ \"name\": \"...\" }`) or an uploaded one.\n * Without `confirm` it answers with the plan and changes nothing; `password`\n * unlocks a protected archive and `include` selects the items to apply.\n */\n .post('/backups/restore', describeRoute({\n tags: ['backups'],\n summary: 'Plan or apply a restore from a stored or uploaded archive',\n responses: { 200: { description: 'The plan', content: jsonBody(restorePlanSchema) }, 400: ERROR_RESPONSES[400], 404: ERROR_RESPONSES[404] },\n }), async (c) => {\n const confirm = c.req.query('confirm') === 'true'\n const contentType = c.req.header('content-type') ?? ''\n\n let archive: string | null = null\n let uploadedTo: string | null = null\n let request: RestoreRequest\n\n try {\n if (contentType.includes('multipart/form-data')) {\n const declared = Number.parseInt(c.req.header('content-length') ?? '0', 10)\n if (Number.isFinite(declared) && declared > MAX_UPLOAD_BYTES)\n throw new DetailedError(`the upload is larger than ${Math.round(MAX_UPLOAD_BYTES / 1024 / 1024)}MB`, { statusCode: 413, code: 'UPLOAD_TOO_LARGE' })\n\n const body = await c.req.parseBody()\n const file = body.file\n if (!(file instanceof File))\n throw new DetailedError('expected a `file` field with the archive', { statusCode: 400, code: 'MISSING_FILE' })\n if (file.size > MAX_UPLOAD_BYTES)\n throw new DetailedError(`the upload is larger than ${Math.round(MAX_UPLOAD_BYTES / 1024 / 1024)}MB`, { statusCode: 413, code: 'UPLOAD_TOO_LARGE' })\n\n const uploads = path.join(deps.backups.directory, 'uploads')\n fs.mkdirSync(uploads, { recursive: true })\n uploadedTo = path.join(uploads, `upload-${crypto.randomUUID()}.zip`)\n // 0600: the archive holds whatever the config declares as data, possibly in\n // the clear, and sits here until the request finishes.\n fs.writeFileSync(uploadedTo, Buffer.from(await file.arrayBuffer()), { mode: 0o600 })\n archive = uploadedTo\n // A multipart body can only carry strings, so the selection is JSON.\n const rawInclude = typeof body.include === 'string' && body.include.length > 0 ? body.include : null\n let include: unknown\n if (rawInclude !== null) {\n try {\n include = JSON.parse(rawInclude)\n }\n catch {\n throw new DetailedError('`include` must be a JSON array of item ids', { statusCode: 400, code: 'INVALID_INCLUDE' })\n }\n }\n request = parseOrThrow<RestoreRequest>(restoreRequestSchema, {\n ...(typeof body.password === 'string' ? { password: body.password } : {}),\n ...(rawInclude === null ? {} : { include }),\n }, 'body')\n }\n else {\n request = parseOrThrow<RestoreRequest>(restoreRequestSchema, await c.req.json().catch(() => ({})), 'body')\n if (request.name === undefined)\n throw new DetailedError('expected a backup name or a file upload', { statusCode: 400, code: 'MISSING_ARCHIVE' })\n archive = deps.backups.resolve(request.name)\n if (archive === null)\n throw new DetailedError('unknown backup', { statusCode: 404, code: 'UNKNOWN_BACKUP' })\n }\n\n const plan = await deps.backups.restore(archive, {\n confirm,\n password: request.password,\n include: request.include,\n })\n // A wrong or missing password is a prompt, not a failure.\n if (plan.needsPassword)\n return c.json(plan)\n if (plan.error !== undefined)\n throw new DetailedError(plan.error, { statusCode: 400, code: 'RESTORE_FAILED', detail: { items: plan.items, applied: plan.applied, skipped: plan.skipped } })\n if (confirm)\n logger.info(`restored from ${path.basename(archive)}: ${plan.applied.join(', ')}`)\n\n return c.json(plan)\n }\n finally {\n // An uploaded archive is only needed for this request.\n if (uploadedTo !== null)\n fs.rmSync(uploadedTo, { force: true })\n }\n })\n}\n","/**\n * Runs a task after the current response has been written.\n *\n * Moving the control listener closes the connection serving the request that\n * asked for the move, so those steps must happen once the response is out.\n */\nexport function afterResponse(task: () => Promise<void>, onError?: (error: unknown) => void): void {\n setImmediate(() => {\n void task().catch((error: unknown) => {\n onError?.(error)\n })\n })\n}\n","import type { AppDeps } from '#src/app'\nimport { DetailedError } from '@namesmt/utils'\nimport { describeRoute } from 'hono-openapi'\nimport { afterResponse } from '#src/helpers/deferred'\nimport { appFactory } from '#src/helpers/factory'\nimport { logger } from '#src/helpers/logger'\nimport { isLoopbackRequest } from '#src/middleware/loopback'\n\n/**\n * The local control channel used by `home-hosted down`.\n *\n * It is deliberately outside `/api` (so no session is needed) and guarded by a\n * token that only the owner of `run.json` can read, plus a loopback check: a\n * different local user, or anyone on the network, gets nothing.\n */\nexport function createControlRoute(deps: AppDeps) {\n return appFactory.createApp()\n .post(\n '/shutdown',\n describeRoute({\n tags: ['panel'],\n summary: 'Stop the panel and everything it supervises (local token required)',\n responses: { 200: { description: 'Stopping' }, 403: { description: 'Bad token, or not a local caller' } },\n }),\n (c) => {\n const token = c.req.header('x-home-hosted-token')\n if (token === undefined || token !== deps.runtimeToken)\n throw new DetailedError('invalid token', { statusCode: 403, code: 'INVALID_TOKEN' })\n if (!isLoopbackRequest(c))\n throw new DetailedError('only this machine may stop the control panel', { statusCode: 403, code: 'NOT_LOOPBACK' })\n\n // Deferred: the answer has to reach `down` before the process goes away.\n afterResponse(deps.onShutdown, error => logger.error('shutdown failed', error))\n\n return c.json({ ok: true })\n },\n )\n}\n","import type { AppDeps } from '#src/app'\nimport type { SseMessage } from '#src/shared/contracts'\nimport { type } from 'arktype'\nimport { describeRoute } from 'hono-openapi'\nimport { streamSSE } from 'hono/streaming'\nimport { appFactory } from '#src/helpers/factory'\nimport { validate } from '#src/helpers/validator'\n\nconst MAX_PENDING_WRITES = 200\nconst PING_INTERVAL_MS = 15000\n\nconst eventsQuery = type({\n /** Only this server's frames. */\n 'serverId?': 'string',\n /** `logs=0` drops log frames; the first frame is always the full state. */\n 'logs?': 'string',\n})\n\n/**\n * One SSE stream per subscriber. Pass `?serverId=<id>` to receive only that\n * server's messages; the first frame always carries the full state snapshot.\n */\nexport function createEventsRoute(deps: AppDeps) {\n return appFactory.createApp()\n .get(\n '/events',\n describeRoute({\n tags: ['panel'],\n summary: 'Panel state and log frames as server-sent events',\n responses: { 200: { description: 'text/event-stream' } },\n }),\n validate('query', eventsQuery),\n (c) => {\n const query = c.req.valid('query')\n const serverId = query.serverId ?? null\n const logOnly = query.logs !== '0'\n\n return streamSSE(c, async (stream) => {\n let pending = 0\n let queue: Promise<void> = Promise.resolve()\n let closed = false\n\n const send = (message: SseMessage): Promise<void> => {\n if (closed)\n return queue\n // A chatty child must not grow the queue without bound; state frames are\n // always kept, log frames are dropped once the client falls behind.\n if (message.type === 'log' && pending > MAX_PENDING_WRITES)\n return queue\n pending += 1\n queue = queue\n .then(() => stream.writeSSE({ event: message.type, data: JSON.stringify(message) }))\n .catch(() => {\n closed = true\n })\n .finally(() => {\n pending -= 1\n })\n return queue\n }\n\n const unsubscribe = deps.hub.subscribe(serverId, (message) => {\n if (!logOnly && message.type === 'log')\n return\n void send(message)\n })\n\n stream.onAbort(() => {\n closed = true\n unsubscribe()\n })\n\n await send({ type: 'hello', ts: Date.now(), state: deps.supervisor.getState() })\n\n while (true) {\n await stream.sleep(PING_INTERVAL_MS)\n if (closed)\n break\n await stream.writeSSE({ event: 'ping', data: String(Date.now()) })\n }\n })\n },\n )\n}\n","import type { AppDeps } from '#src/app'\nimport process from 'node:process'\nimport { type } from 'arktype'\nimport { describeRoute } from 'hono-openapi'\nimport { appFactory } from '#src/helpers/factory'\nimport { jsonBody } from '#src/helpers/openapi'\nimport { requestIdentity } from '#src/middleware/auth'\n\n/**\n * Liveness for external monitors. Mounted outside `/api`, so it answers without a\n * session: it reports whether the panel itself is serving, and 503 when an\n * autostart server has crashed. Server details are only included for an\n * authenticated caller.\n */\nexport function createHealthRoute(deps: AppDeps) {\n return appFactory.createApp()\n .get(\n '/healthz',\n describeRoute({\n tags: ['panel'],\n summary: 'Liveness for monitors — no session required',\n responses: {\n 200: {\n description: 'Serving',\n content: jsonBody(type({\n 'status': '\"ok\" | \"degraded\"',\n 'uptimeMs': 'number',\n 'servers?': type({ total: 'number', running: 'number', crashed: 'number', unhealthy: 'number' }),\n 'hostAlerts?': 'string[]',\n })),\n },\n 503: { description: 'An autostart server has crashed' },\n },\n }),\n (c) => {\n const state = deps.supervisor.getState()\n const broken = state.servers.filter(server => server.config.autostart && server.status === 'crashed')\n // Detail is for a signed-in browser or an API token; the status line itself\n // stays public, which is the whole point of a monitor endpoint.\n const authenticated = requestIdentity(c, deps.auth).authenticated\n\n return c.json({\n status: broken.length > 0 ? 'degraded' : 'ok',\n uptimeMs: Math.round(process.uptime() * 1000),\n ...(authenticated\n ? {\n servers: {\n total: state.servers.length,\n running: state.servers.filter(server => server.status === 'running').length,\n crashed: state.servers.filter(server => server.status === 'crashed').length,\n unhealthy: state.servers.filter(server => server.health === 'unhealthy').length,\n },\n hostAlerts: state.host.alerts,\n }\n : {}),\n }, broken.length > 0 ? 503 : 200)\n },\n )\n}\n","import type { AppDeps } from '#src/app'\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport { DetailedError } from '@namesmt/utils'\nimport { type } from 'arktype'\nimport { describeRoute } from 'hono-openapi'\nimport { appFactory } from '#src/helpers/factory'\nimport { ERROR_RESPONSES, jsonBody } from '#src/helpers/openapi'\nimport { validate } from '#src/helpers/validator'\nimport { logHistoryQuerySchema, logServersViewSchema } from '#src/shared/contracts'\n\n/** Bounds for the tail query, so a bad client cannot ask for the whole file. */\nconst MIN_TAIL = 50\nconst MAX_TAIL = 5000\nconst DEFAULT_TAIL = 500\n\nconst idParam = type({ id: 'string >= 1' })\nconst downloadQuery = type({ 'file?': 'string' })\n\nfunction unknownServer(id: string): DetailedError {\n return new DetailedError(`unknown server \"${id}\"`, { statusCode: 404, code: 'UNKNOWN_SERVER' })\n}\n\nexport function createLogsRoute(deps: AppDeps) {\n return appFactory.createApp()\n .get(\n '/logs',\n describeRoute({\n tags: ['logs'],\n summary: 'Every server with its on-disk log files',\n responses: { 200: { description: 'Log sources', content: jsonBody(logServersViewSchema) } },\n }),\n c => c.json({\n servers: deps.supervisor.views().map(server => ({\n serverId: server.id,\n label: server.config.label ?? server.id,\n status: server.status,\n ...deps.logFiles.info(server.id),\n })),\n }),\n )\n\n .get(\n '/logs/:id',\n describeRoute({\n tags: ['logs'],\n summary: 'Persisted log lines, with search and a stream filter',\n responses: { 200: { description: 'Lines' }, 404: ERROR_RESPONSES[404] },\n }),\n validate('param', idParam),\n validate('query', logHistoryQuerySchema),\n (c) => {\n const { id } = c.req.valid('param')\n if (!deps.store.getServer(id))\n throw unknownServer(id)\n\n const query = c.req.valid('query')\n const requested = query.tail === undefined ? DEFAULT_TAIL : Number.parseInt(query.tail, 10)\n const tail = Number.isNaN(requested) ? DEFAULT_TAIL : Math.min(Math.max(requested, MIN_TAIL), MAX_TAIL)\n\n const info = deps.logFiles.info(id)\n // Search reads a wider window than the display tail, otherwise a match older\n // than the last N lines would look like \"no results\".\n const search = query.search?.trim() ?? ''\n const window = search.length > 0 ? Math.max(tail, MAX_TAIL) : tail\n\n let lines = deps.logFiles.readTail(id, window)\n if (query.stream !== undefined && query.stream.length > 0)\n lines = lines.filter(line => line.stream === query.stream)\n if (search.length > 0) {\n const needle = search.toLowerCase()\n lines = lines.filter(line => line.text.toLowerCase().includes(needle))\n }\n\n return c.json({\n serverId: id,\n enabled: info.enabled,\n sizeBytes: info.sizeBytes,\n files: info.files.map(file => file.name),\n searched: search.length > 0 ? window : null,\n lines: lines.slice(-tail),\n })\n },\n )\n\n /** Raw file download; the name is checked against the rotation allowlist. */\n .get(\n '/logs/:id/download',\n describeRoute({\n tags: ['logs'],\n summary: 'Download one rotated log file',\n responses: { 200: { description: 'application/x-ndjson' }, 404: ERROR_RESPONSES[404] },\n }),\n validate('param', idParam),\n validate('query', downloadQuery),\n (c) => {\n const { id } = c.req.valid('param')\n if (!deps.store.getServer(id))\n throw unknownServer(id)\n\n const requested = c.req.valid('query').file ?? `${id}.log`\n const known = deps.logFiles.info(id).files.map(file => file.name)\n if (!known.includes(requested))\n throw new DetailedError('unknown log file', { statusCode: 404, code: 'UNKNOWN_LOG_FILE' })\n\n const file = path.join(deps.logFiles.directory, requested)\n const body = fs.readFileSync(file)\n return c.body(body, 200, {\n 'Content-Type': 'application/x-ndjson; charset=utf-8',\n 'Content-Length': String(body.byteLength),\n 'Content-Disposition': `attachment; filename=\"${requested}\"`,\n })\n },\n )\n\n .delete(\n '/logs/:id',\n describeRoute({\n tags: ['logs'],\n summary: 'Delete the persisted logs of one server',\n responses: { 200: { description: 'Cleared' }, 404: ERROR_RESPONSES[404] },\n }),\n validate('param', idParam),\n (c) => {\n const { id } = c.req.valid('param')\n if (!deps.store.getServer(id))\n throw unknownServer(id)\n deps.logFiles.clear(id)\n return c.json({ ok: true })\n },\n )\n}\n","import type { AppDeps } from '#src/app'\nimport { describeRoute } from 'hono-openapi'\nimport { appFactory } from '#src/helpers/factory'\n\n/**\n * Prometheus text for whatever wants to scrape the panel (Beszel, Grafana,\n * `curl`). Lives under `/api`, so it needs a session like every other route.\n */\nexport function createMetricsRoute(deps: AppDeps) {\n return appFactory.createApp()\n .get('/metrics', describeRoute({\n tags: ['panel'],\n summary: 'Prometheus text for whatever scrapes the panel',\n responses: { 200: { description: 'text/plain; version=0.0.4' } },\n }), (c) => {\n const state = deps.supervisor.getState()\n const lines: string[] = []\n\n const metric = (name: string, help: string, samples: string[]): void => {\n if (samples.length === 0)\n return\n lines.push(`# HELP ${name} ${help}`, `# TYPE ${name} gauge`, ...samples)\n }\n\n metric('hh_control_up', 'Control plane is serving', ['hh_control_up 1'])\n metric('hh_servers_total', 'Configured servers', [`hh_servers_total ${state.servers.length}`])\n\n const up = state.servers.map(server => `hh_server_up{server=\"${server.id}\"} ${server.status === 'running' ? 1 : 0}`)\n metric('hh_server_up', 'Server process is running', up)\n\n const restarts = state.servers.map(server => `hh_server_restarts_total{server=\"${server.id}\"} ${server.restarts}`)\n metric('hh_server_restarts_total', 'Restarts since the control plane started', restarts)\n\n const crashes = state.servers.map(server => `hh_server_crashes_24h{server=\"${server.id}\"} ${server.history.crashes}`)\n metric('hh_server_crashes_24h', 'Crashes in the last 24 hours', crashes)\n\n const uptime = state.servers\n .filter(server => server.history.uptimeRatio !== null)\n .map(server => `hh_server_uptime_ratio_24h{server=\"${server.id}\"} ${server.history.uptimeRatio!.toFixed(4)}`)\n metric('hh_server_uptime_ratio_24h', 'Share of the last 24 hours the server was up', uptime)\n\n const response = state.servers\n .filter(server => server.responseMs !== null)\n .map(server => `hh_server_response_ms{server=\"${server.id}\"} ${server.responseMs}`)\n metric('hh_server_response_ms', 'Last health probe latency in milliseconds', response)\n\n const rss = state.servers\n .filter(server => server.resources?.rssBytes != null)\n .map(server => `hh_server_rss_bytes{server=\"${server.id}\"} ${server.resources!.rssBytes}`)\n metric('hh_server_rss_bytes', 'RSS of the server process tree', rss)\n\n const cpu = state.servers\n .filter(server => server.resources?.cpuPercent != null)\n .map(server => `hh_server_cpu_percent{server=\"${server.id}\"} ${server.resources!.cpuPercent}`)\n metric('hh_server_cpu_percent', 'CPU percent of the server process tree', cpu)\n\n const disks = state.host.disks.map(disk => `hh_host_disk_used_percent{mount=\"${disk.path}\"} ${disk.usedPercent.toFixed(2)}`)\n metric('hh_host_disk_used_percent', 'Disk usage percent per configured path', disks)\n\n metric('hh_host_memory_used_percent', 'Memory usage percent', [`hh_host_memory_used_percent ${state.host.memoryUsedPercent.toFixed(2)}`])\n metric('hh_host_swap_used_percent', 'Swap usage percent', [`hh_host_swap_used_percent ${state.host.swapUsedPercent.toFixed(2)}`])\n metric('hh_host_load1_per_cpu', '1 minute load average per cpu', [\n `hh_host_load1_per_cpu ${((state.host.loadAvg[0] ?? 0) / Math.max(1, state.host.cpus)).toFixed(3)}`,\n ])\n\n return c.text(`${lines.join('\\n')}\\n`, 200, { 'Content-Type': 'text/plain; version=0.0.4; charset=utf-8' })\n })\n}\n","import type { Bot } from 'grammy'\nimport { autoRetry } from '@grammyjs/auto-retry'\nimport { Bot as GrammyBot } from 'grammy'\n\n/**\n * Telegram Bot API access, built on grammY.\n *\n * grammY is used for what it is good at — a typed, retrying, extensible Bot API\n * client — while the bot itself stays outbound-only for now. If inbound commands\n * or a webhook are ever wanted, the same instance can host handlers without\n * touching the notification code.\n *\n * `autoRetry` handles Telegram's 429 `retry_after` (and other transient failures)\n * so callers get either a result or a real error.\n */\n\nconst bots = new Map<string, Bot>()\n\nexport function getBot(token: string): Bot {\n const cached = bots.get(token)\n if (cached)\n return cached\n\n const bot = new GrammyBot(token, { client: { timeoutSeconds: 10 } })\n bot.api.config.use(autoRetry({ maxRetryAttempts: 3, maxDelaySeconds: 20 }))\n bots.set(token, bot)\n return bot\n}\n\n/** Drops cached clients; used when the token changes or the service shuts down. */\nexport function forgetBots(): void {\n bots.clear()\n}\n\nexport function escapeHtml(value: string): string {\n return value.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')\n}\n\nexport function formatTelegramMessage(title: string, lines: string[]): string {\n const body = lines.filter(line => line.length > 0).map(line => `• ${escapeHtml(line)}`).join('\\n')\n return `<b>${escapeHtml(title)}</b>${body.length > 0 ? `\\n${body}` : ''}`\n}\n\nexport interface TelegramOutcome {\n ok: boolean\n error?: string\n}\n\n/** Turns a grammY error into something worth showing in the settings page. */\nexport function describeTelegramError(error: unknown): string {\n if (typeof error === 'object' && error !== null) {\n const candidate = error as { error_code?: number, description?: string, message?: string, parameters?: { retry_after?: number } }\n const description = candidate.description ?? candidate.message\n if (description) {\n const code = candidate.error_code === undefined ? '' : ` (${candidate.error_code})`\n const retry = candidate.parameters?.retry_after === undefined ? '' : `, retry in ${candidate.parameters.retry_after}s`\n return `${description}${code}${retry}`\n }\n }\n return error instanceof Error ? error.message : String(error)\n}\n\nexport async function sendTelegramMessage(token: string, chatId: string, html: string): Promise<TelegramOutcome> {\n try {\n await getBot(token).api.sendMessage(chatId, html, {\n parse_mode: 'HTML',\n link_preview_options: { is_disabled: true },\n })\n return { ok: true }\n }\n catch (error) {\n return { ok: false, error: describeTelegramError(error) }\n }\n}\n\nexport async function verifyTelegramToken(token: string): Promise<{ ok: boolean, username?: string, error?: string }> {\n try {\n const me = await getBot(token).api.getMe()\n return { ok: true, username: me.username }\n }\n catch (error) {\n return { ok: false, error: describeTelegramError(error) }\n }\n}\n\nexport interface TelegramChat {\n id: number | string\n title: string\n}\n\n/**\n * Recent chats that talked to the bot, so a chat id can be picked instead of\n * hunted down by hand. Telegram only reports chats with pending updates, so the\n * caller is told to message the bot first.\n */\nexport async function listTelegramChats(token: string): Promise<{ ok: boolean, chats: TelegramChat[], error?: string }> {\n try {\n const updates = await getBot(token).api.getUpdates({\n limit: 100,\n allowed_updates: ['message', 'channel_post', 'edited_message'],\n })\n\n const chats = new Map<string, TelegramChat>()\n for (const update of updates) {\n const chat = update.message?.chat ?? update.channel_post?.chat ?? update.edited_message?.chat\n if (!chat)\n continue\n const title = 'title' in chat && chat.title\n ? chat.title\n : 'username' in chat && chat.username\n ? `@${chat.username}`\n : 'first_name' in chat && chat.first_name\n ? chat.first_name\n : 'private chat'\n chats.set(String(chat.id), { id: chat.id, title })\n }\n\n return { ok: true, chats: [...chats.values()] }\n }\n catch (error) {\n return { ok: false, chats: [], error: describeTelegramError(error) }\n }\n}\n","import type { AppDeps } from '#src/app'\nimport { DetailedError } from '@namesmt/utils'\nimport { type } from 'arktype'\nimport { describeRoute } from 'hono-openapi'\nimport { appFactory } from '#src/helpers/factory'\nimport { ERROR_RESPONSES, jsonBody } from '#src/helpers/openapi'\nimport { validate } from '#src/helpers/validator'\nimport { forgetBots } from '#src/providers/telegram'\nimport { notificationActionSchema, telegramTokenSchema } from '#src/shared/contracts'\n\n/**\n * The bot token is written straight to the secrets file and never into\n * `servers.config.json`, so notification *policy* and the *credential* stay\n * separate.\n */\nexport function createNotificationsRoute(deps: AppDeps) {\n return appFactory.createApp()\n .put(\n '/notifications/token',\n describeRoute({\n tags: ['notifications'],\n summary: 'Store the Telegram bot token (verified first)',\n responses: { 200: { description: 'Stored' }, 400: ERROR_RESPONSES[400] },\n }),\n validate('json', telegramTokenSchema),\n async (c) => {\n const { botToken } = c.req.valid('json')\n const verified = await deps.notifications.verifyToken(botToken)\n if (!verified.ok)\n throw new DetailedError(`telegram rejected the token: ${verified.error ?? 'unknown error'}`, { statusCode: 400, code: 'TELEGRAM_TOKEN_REJECTED' })\n\n deps.secrets.setTelegramToken(botToken)\n forgetBots()\n return c.json({ ok: true, username: verified.username ?? null })\n },\n )\n\n .delete(\n '/notifications/token',\n describeRoute({\n tags: ['notifications'],\n summary: 'Forget the Telegram bot token',\n responses: { 200: { description: 'Removed' } },\n }),\n (c) => {\n deps.secrets.setTelegramToken(null)\n forgetBots()\n return c.json({ ok: true })\n },\n )\n\n .post(\n '/notifications/test',\n describeRoute({\n tags: ['notifications'],\n summary: 'Send a test message',\n responses: { 200: { description: 'Sent or refused' }, 400: ERROR_RESPONSES[400] },\n }),\n validate('json', notificationActionSchema),\n async (c) => {\n const result = await deps.notifications.sendTest(c.req.valid('json'))\n if (!result.ok)\n throw new DetailedError(result.error ?? 'the test message failed', { statusCode: 400, code: 'TELEGRAM_SEND_FAILED' })\n return c.json(result)\n },\n )\n\n .post(\n '/notifications/detect-chats',\n describeRoute({\n tags: ['notifications'],\n summary: 'List the chats the bot can see',\n responses: {\n 200: { description: 'Chats', content: jsonBody(type({ chats: type({ id: 'string | number', title: 'string' }).array() })) },\n 400: ERROR_RESPONSES[400],\n },\n }),\n validate('json', notificationActionSchema),\n async (c) => {\n const result = await deps.notifications.detectChats(c.req.valid('json'))\n if (!result.ok)\n throw new DetailedError(result.error ?? 'could not list chats', { statusCode: 400, code: 'TELEGRAM_LIST_FAILED' })\n return c.json({ chats: result.chats })\n },\n )\n}\n","import type { RawConfig } from '#src/config/schema'\n\n/**\n * The config shape this release understands. Bump it only for a change that an\n * older release cannot simply ignore, and add the step that lifts the previous\n * shape to it in `configMigrations` — then a config that needs it refuses to\n * start until `home-hosted migrate` has run.\n *\n * 1 — the shape that has been in use up to and including 0.3.0, plus the `meta`\n * block this constant was introduced with. Unstamped files are schema 1.\n */\nexport const CONFIG_SCHEMA = 1\n\nexport interface ConfigMigration {\n /** The schema this step produces; steps run in ascending order. */\n to: number\n /** One line, printed by `migrate` before anything is written. */\n describe: string\n apply: (config: RawConfig) => RawConfig\n}\n\n/**\n * Every published migration, oldest first. Keep this list small and permanent:\n * a config may arrive from any earlier release, so a step is never removed.\n */\nexport const configMigrations: ConfigMigration[] = []\n\nexport interface MigrationPlan {\n from: number\n to: number\n steps: ConfigMigration[]\n /** The file was written by a release newer than this one. */\n tooNew: boolean\n}\n\nexport interface MigrationOptions {\n /** Defaults to every migration this release ships. */\n migrations?: ConfigMigration[]\n /** The schema to reach; defaults to what this release understands. */\n to?: number\n}\n\n/** What would have to run to bring `from` up to `to`. */\nexport function planConfigMigrations(from: number, options: MigrationOptions = {}): MigrationPlan {\n const to = options.to ?? CONFIG_SCHEMA\n const migrations = options.migrations ?? configMigrations\n const steps = migrations\n .filter(migration => migration.to > from && migration.to <= to)\n .sort((a, b) => a.to - b.to)\n return { from, to, steps, tooNew: from > to }\n}\n\n/** Applies the plan in order; the caller owns writing the result. */\nexport function applyConfigMigrations(config: RawConfig, from: number, options: MigrationOptions = {}): { config: RawConfig, applied: ConfigMigration[] } {\n const { steps } = planConfigMigrations(from, options)\n let current = config\n for (const step of steps)\n current = step.apply(current)\n return { config: current, applied: steps }\n}\n","import type { ServerConfig } from '#src/shared/contracts'\nimport { type } from 'arktype'\nimport {\n backupsSchema,\n controlSchema,\n defaultsSchema,\n hostSchema,\n logsSchema,\n notificationsSchema,\n serverSchema,\n} from '#src/shared/contracts'\n\nexport { backupsSchema, controlSchema, defaultsSchema, hostSchema, logsSchema, notificationsSchema, serverSchema }\nexport type { ServerConfig } from '#src/shared/contracts'\n\n/**\n * Which release wrote the file, and the config shape it wrote. Optional so a\n * config from before the stamp still reads, and so an archive made by an older\n * release still restores.\n */\nexport const metaSchema = type({\n writtenBy: 'string = \"\"',\n schema: 'number.integer >= 1 = 1',\n}).onUndeclaredKey('reject')\n\n/** The shape of `servers.config.json`: control panel settings, defaults, servers. */\nexport const configSchema = type({\n $schema: 'string?',\n meta: metaSchema.optional(),\n control: controlSchema.default(() => ({})),\n defaults: defaultsSchema.default(() => ({})),\n logs: logsSchema.default(() => ({})),\n notifications: notificationsSchema.default(() => ({})),\n host: hostSchema.default(() => ({})),\n backups: backupsSchema.default(() => ({})),\n servers: serverSchema.array().default(() => []),\n}).onUndeclaredKey('reject')\n\n/** Same shape as the schema output, with each server `port` normalized to `null` when unset. */\nexport type ResolvedConfig = Omit<typeof configSchema.infer, 'servers'> & { servers: ServerConfig[] }\n\n/** Every key `configSchema` knows, for reporting blocks a newer release added. */\nexport const CONFIG_KEYS = ['$schema', 'meta', 'control', 'defaults', 'logs', 'notifications', 'host', 'backups', 'servers'] as const\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/**\n * Resolves one entry against the panel's server defaults. A group (`restart`,\n * `health`, `health.http`, `stop`) merges key by key, so an entry that decides one\n * member does not silently fall back to the *schema* default for the others — which\n * is the whole point of the panel having defaults at all.\n */\nexport function mergeDefaults(\n defaults: Record<string, unknown>,\n entry: Record<string, unknown>,\n): Record<string, unknown> {\n const merged: Record<string, unknown> = { ...entry }\n for (const [key, value] of Object.entries(defaults)) {\n const current = merged[key]\n if (current === undefined)\n merged[key] = value\n else if (isRecord(value) && isRecord(current))\n merged[key] = mergeDefaults(value, current)\n }\n return merged\n}\n\n/** The on-disk shape: everything optional except `servers`, defaults applied per entry. */\nexport interface RawConfig {\n $schema?: string\n meta?: { writtenBy?: string, schema?: number }\n control?: Record<string, unknown>\n defaults?: Record<string, unknown>\n logs?: Record<string, unknown>\n notifications?: Record<string, unknown>\n host?: Record<string, unknown>\n backups?: Record<string, unknown>\n servers?: Record<string, unknown>[]\n}\n","import fs from 'node:fs'\nimport path from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nlet cached: string | null = null\n\n/**\n * The running release, read from the package manifest.\n *\n * The caller may be `src/**` under tsx or the built `dist/cli.js`, so the manifest\n * is found by walking up rather than by a fixed relative path. `src/cli.ts` keeps\n * its own read because it may only import node builtins statically.\n */\nexport function appVersion(): string {\n if (cached !== null)\n return cached\n\n let dir = path.dirname(fileURLToPath(import.meta.url))\n for (let depth = 0; depth < 4; depth++) {\n try {\n const manifest = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf8')) as { name?: string, version?: string }\n if (manifest.name === 'home-hosted' && typeof manifest.version === 'string') {\n cached = manifest.version\n return cached\n }\n }\n catch {\n // no manifest here; keep walking up\n }\n const parent = path.dirname(dir)\n if (parent === dir)\n break\n dir = parent\n }\n\n cached = '0.0.0'\n return cached\n}\n","import type { MigrationOptions } from '#src/config/migrations'\nimport type { RawConfig, ResolvedConfig } from '#src/config/schema'\nimport type { ServerConfig } from '#src/shared/contracts'\nimport { type } from 'arktype'\nimport { CONFIG_SCHEMA, planConfigMigrations } from '#src/config/migrations'\nimport {\n backupsSchema,\n CONFIG_KEYS,\n controlSchema,\n defaultsSchema,\n hostSchema,\n logsSchema,\n mergeDefaults,\n notificationsSchema,\n\n serverSchema,\n} from '#src/config/schema'\nimport { appVersion } from '#src/helpers/version'\n\nexport interface ConfigParse {\n /** Null when something made the config unusable; `errors` says why. */\n config: ResolvedConfig | null\n /** Blocking problems: what the panel must not start on. */\n errors: string[]\n /** Keys a newer release wrote that this one does not know, by path. */\n unknownKeys: string[]\n /** Real but non-blocking problems: supervision still runs, the file is still used. */\n warnings: string[]\n /** The shape the file declares; an unstamped file reads as the current one. */\n schemaVersion: number\n /** What wrote it, when the file says. */\n writtenBy: string | null\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\ntype Validator = (input: unknown) => unknown\n\nconst GROUPS: ReadonlyArray<readonly [string, Validator]> = [\n ['control', controlSchema as unknown as Validator],\n ['defaults', defaultsSchema as unknown as Validator],\n ['logs', logsSchema as unknown as Validator],\n ['notifications', notificationsSchema as unknown as Validator],\n ['host', hostSchema as unknown as Validator],\n ['backups', backupsSchema as unknown as Validator],\n]\n\n/** ArkType reports an unrecognized key with this problem, carrying its path. */\nconst UNDECLARED = 'must be removed'\n\nfunction deleteAtPath(root: Record<string, unknown>, path: readonly (string | number)[]): void {\n let node: unknown = root\n for (const key of path.slice(0, -1)) {\n node = Array.isArray(node) ? node[Number(key)] : isRecord(node) ? node[key] : undefined\n if (node === undefined)\n return\n }\n const last = path[path.length - 1]\n if (last === undefined)\n return\n if (isRecord(node))\n delete node[String(last)]\n else if (Array.isArray(node) && typeof last === 'number')\n node.splice(last, 1)\n}\n\n/**\n * Validates one object against one schema, tolerating keys the schema does not\n * know: a config written by a newer release has to keep working here, and losing\n * the *whole group* to schema defaults would silently reset the listener port,\n * the bind and the auth policy over a single unrecognized key.\n *\n * Only unrecognized keys are dropped, and each one is reported. Anything else is\n * a real problem, returned for the caller to refuse on.\n */\nfunction parseTolerant(\n value: unknown,\n schema: Validator,\n prefix: string,\n unknownKeys: string[],\n): { value: unknown, error: string | null } {\n const candidate = structuredClone(value)\n\n for (let pass = 0; pass < 25; pass++) {\n const parsed = schema(candidate)\n if (!(parsed instanceof type.errors))\n return { value: parsed, error: null }\n\n const problems = parsed as unknown as Array<{ path: (string | number)[], problem: string }>\n const removable = problems.filter(problem => problem.problem === UNDECLARED)\n if (removable.length === 0)\n return { value: null, error: parsed.summary }\n\n if (!isRecord(candidate))\n return { value: null, error: parsed.summary }\n\n for (const problem of removable) {\n const at = `${prefix}.${problem.path.join('.')}`\n if (!unknownKeys.includes(at))\n unknownKeys.push(at)\n deleteAtPath(candidate, problem.path)\n }\n }\n\n return { value: null, error: 'too many unrecognized keys to ignore' }\n}\n\n/**\n * Reads a `servers.config.json` from any release. Unrecognized keys are reported\n * and left on disk; blocking problems — including a file this release cannot\n * understand — land in `errors` and leave `config` null.\n */\nexport function parseConfig(raw: unknown, options: MigrationOptions = {}): ConfigParse {\n const unknownKeys: string[] = []\n const errors: string[] = []\n const warnings: string[] = []\n const result: ConfigParse = { config: null, errors, unknownKeys, warnings, schemaVersion: CONFIG_SCHEMA, writtenBy: null }\n\n if (!isRecord(raw)) {\n errors.push('the config must contain a JSON object')\n return result\n }\n\n const meta = isRecord(raw.meta) ? raw.meta : null\n const schemaVersion = typeof meta?.schema === 'number' ? meta.schema : CONFIG_SCHEMA\n const writtenBy = typeof meta?.writtenBy === 'string' && meta.writtenBy.length > 0 ? meta.writtenBy : null\n result.schemaVersion = schemaVersion\n result.writtenBy = writtenBy\n\n if (schemaVersion > CONFIG_SCHEMA) {\n errors.push(`written by home-hosted ${writtenBy ?? 'a newer release'} (config schema ${schemaVersion}); this release understands schema ${CONFIG_SCHEMA}`)\n return result\n }\n\n const { steps } = planConfigMigrations(schemaVersion, options)\n if (steps.length > 0) {\n errors.push(`config schema ${schemaVersion} needs ${steps.length} migration${steps.length === 1 ? '' : 's'} before this release can use it`)\n return result\n }\n\n for (const key of Object.keys(raw)) {\n if (!(CONFIG_KEYS as readonly string[]).includes(key))\n unknownKeys.push(key)\n }\n\n const groups: Record<string, unknown> = {}\n for (const [name, schema] of GROUPS) {\n const parsed = parseTolerant(raw[name] ?? {}, schema, name, unknownKeys)\n if (parsed.error !== null)\n errors.push(`${name}: ${parsed.error}`)\n groups[name] = parsed.value ?? schema({})\n }\n\n const defaults = groups.defaults as ResolvedConfig['defaults']\n const servers: ServerConfig[] = []\n const seen = new Set<string>()\n const rawServers = Array.isArray(raw.servers) ? raw.servers : []\n\n rawServers.forEach((entry, index) => {\n const label = `servers[${index}]`\n const merged = isRecord(entry) ? mergeDefaults(defaults, entry) : entry\n const parsed = parseTolerant(merged, serverSchema as unknown as Validator, label, unknownKeys)\n if (parsed.error !== null) {\n const id = isRecord(entry) ? entry.id : undefined\n errors.push(`${label}${typeof id === 'string' ? ` (\"${id}\")` : ''}: ${parsed.error}`)\n return\n }\n const server = parsed.value as ServerConfig\n if (seen.has(server.id)) {\n errors.push(`${label}: duplicate id \"${server.id}\"`)\n return\n }\n seen.add(server.id)\n servers.push({ ...server, port: server.port ?? null })\n })\n\n // Dangling dependencies and cycles are reported, never fatal: supervision still runs.\n warnings.push(...validateDependencies(servers))\n\n if (errors.length > 0)\n return result\n\n result.config = {\n meta: { writtenBy: writtenBy ?? '', schema: schemaVersion },\n control: groups.control as ResolvedConfig['control'],\n defaults,\n logs: groups.logs as ResolvedConfig['logs'],\n notifications: groups.notifications as ResolvedConfig['notifications'],\n host: groups.host as ResolvedConfig['host'],\n backups: groups.backups as ResolvedConfig['backups'],\n servers,\n } as ResolvedConfig\n\n return result\n}\n\n/** Dangling dependencies and cycles are reported, not fatal: supervision still runs. */\nfunction validateDependencies(servers: ServerConfig[]): string[] {\n const ids = new Set(servers.map(server => server.id))\n const errors: string[] = []\n\n for (const server of servers) {\n for (const dependency of server.dependsOn) {\n if (dependency === server.id)\n errors.push(`\"${server.id}\" depends on itself`)\n else if (!ids.has(dependency))\n errors.push(`\"${server.id}\" depends on unknown server \"${dependency}\"`)\n }\n }\n\n const visiting = new Set<string>()\n const settled = new Set<string>()\n const byId = new Map(servers.map(server => [server.id, server]))\n const walk = (id: string): void => {\n if (settled.has(id))\n return\n if (visiting.has(id)) {\n errors.push(`dependency cycle through \"${id}\"`)\n return\n }\n visiting.add(id)\n for (const dependency of byId.get(id)?.dependsOn ?? []) walk(dependency)\n visiting.delete(id)\n settled.add(id)\n }\n for (const server of servers) walk(server.id)\n\n return [...new Set(errors)]\n}\n\n/** Adds the stamp every write carries, so the next release can tell what wrote the file. */\nexport function stampConfig(draft: RawConfig): RawConfig {\n const { $schema, meta: _meta, ...rest } = draft\n return {\n ...($schema === undefined ? {} : { $schema }),\n meta: { writtenBy: appVersion(), schema: CONFIG_SCHEMA },\n ...rest,\n }\n}\n","import type { RawConfig } from '#src/config/schema'\n\n/**\n * Written when a data directory has no config yet (`$HHOSTED_HOME/servers.config.json`).\n *\n * It stays empty on purpose: home-hosted ships no servers of its own, so what a\n * user supervises is theirs to declare. The rest of the file is default policy,\n * which the settings page can change.\n */\nexport const SEED_CONFIG: RawConfig = {\n $schema: './servers.config.schema.json',\n control: {\n port: 3999,\n host: 'local',\n openBrowser: false,\n },\n defaults: {\n enabled: true,\n autostart: false,\n bind: 'local',\n onPortConflict: 'block',\n },\n servers: [],\n}\n","import type { ConfigMigration } from '#src/config/migrations'\nimport type { RawConfig, ResolvedConfig, ServerConfig } from '#src/config/schema'\nimport type {\n BackupsConfig,\n ControlConfig,\n HostConfig,\n LogsConfig,\n NotificationsConfig,\n ServerDefaults,\n ServerPatch,\n SettingsPatch,\n} from '#src/shared/contracts'\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport { type } from 'arktype'\nimport { CONFIG_SCHEMA, planConfigMigrations } from '#src/config/migrations'\nimport { parseConfig, stampConfig } from '#src/config/parse'\nimport {\n backupsSchema,\n configSchema,\n controlSchema,\n defaultsSchema,\n hostSchema,\n logsSchema,\n mergeDefaults,\n notificationsSchema,\n serverSchema,\n} from '#src/config/schema'\nimport { SEED_CONFIG } from '#src/config/seed'\nimport { writeFileAtomic } from '#src/helpers/atomic'\nimport { configSchemaPath } from '#src/helpers/paths'\n\n/** Nested groups a patch merges into instead of replacing. */\nconst SERVER_MERGE_KEYS = new Set(['restart', 'health', 'stop'])\nconst CONTROL_MERGE_KEYS = new Set(['auth', 'tls'])\nconst NOTIFICATION_MERGE_KEYS = new Set(['telegram'])\nconst EMPTY_MERGE_KEYS = new Set<string>()\n\nexport class ConfigError extends Error {\n override name = 'ConfigError'\n}\n\nfunction formatErrors(errors: type.errors): string {\n return errors.summary\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction applyPatch(target: Record<string, unknown>, patch: Record<string, unknown>, mergeKeys: Set<string>): void {\n for (const [key, value] of Object.entries(patch)) {\n if (value === undefined)\n continue\n if (mergeKeys.has(key) && isRecord(value) && isRecord(target[key])) {\n target[key] = mergeGroup(target[key], value)\n continue\n }\n target[key] = value\n }\n}\n\n/**\n * Merges one nested group recursively — `health.http` is a group of its own, and\n * replacing it wholesale would silently reset the siblings a partial patch never\n * mentioned. An explicit `null` removes a key, which is how a schema-optional\n * field is cleared.\n */\nfunction mergeGroup(target: Record<string, unknown>, patch: Record<string, unknown>): Record<string, unknown> {\n const merged = { ...target }\n for (const [key, value] of Object.entries(patch)) {\n if (value === undefined)\n continue\n if (value === null) {\n delete merged[key]\n continue\n }\n if (isRecord(value) && isRecord(merged[key])) {\n merged[key] = mergeGroup(merged[key] as Record<string, unknown>, value)\n continue\n }\n merged[key] = value\n }\n return merged\n}\n\nexport class ConfigStore {\n private raw: RawConfig = {}\n /** The bytes behind the live config, so a watcher can tell a real edit from our own write. */\n private lastText: string | null = null\n private resolvedConfig!: ResolvedConfig\n private error: string | null = null\n private warnings: string[] = []\n private schemaVersion = CONFIG_SCHEMA\n private readonly listeners = new Set<() => void>()\n\n constructor(private readonly file: string, private readonly seed: RawConfig = SEED_CONFIG) {}\n\n get path(): string {\n return this.file\n }\n\n get config(): ResolvedConfig {\n return this.resolvedConfig\n }\n\n get configError(): string | null {\n return this.error\n }\n\n /** Keys a newer release wrote that this one ignores; nothing to refuse over. */\n get configWarnings(): string[] {\n return [...this.warnings]\n }\n\n /** The shape the file declares, as last read. */\n get configSchemaVersion(): number {\n return this.schemaVersion\n }\n\n /** Steps that would have to run before this release could use the file. */\n get pendingMigrations(): ConfigMigration[] {\n return planConfigMigrations(this.schemaVersion).steps\n }\n\n get servers(): ServerConfig[] {\n return this.resolvedConfig.servers\n }\n\n get defaults(): ResolvedConfig['defaults'] {\n return this.resolvedConfig.defaults\n }\n\n get rawConfig(): RawConfig {\n return structuredClone(this.raw)\n }\n\n onChange(listener: () => void): () => void {\n this.listeners.add(listener)\n return () => this.listeners.delete(listener)\n }\n\n getServer(id: string): ServerConfig | undefined {\n return this.servers.find(server => server.id === id)\n }\n\n /**\n * Reads the file and tells the listeners, so whatever wrote it — the settings\n * page, a restored backup, or the file watcher — becomes the live config.\n */\n load(): void {\n this.read()\n this.notify()\n }\n\n /**\n * The watcher's entry point: re-reads the file only when its bytes changed.\n *\n * `changed` means the file on disk is different from what the live config was\n * read from, `applied` means that difference was accepted — a file this release\n * cannot read is reported and the config already running is left alone, which is\n * what keeps an editor's typo from stopping every server.\n */\n reloadFromDisk(): { changed: boolean, applied: boolean, error: string | null } {\n let text: string\n try {\n text = fs.readFileSync(this.file, 'utf8')\n }\n catch {\n // Deleting the file is not an edit of it: the seed is written on a first\n // load, never under a running panel.\n this.error = `${path.basename(this.file)} is gone`\n return { changed: false, applied: false, error: this.error }\n }\n\n if (text === this.lastText) {\n // These are the bytes the live config came from, so whatever went wrong in\n // between (the file was gone, or was edited into something unusable and then\n // put back) is over. The listeners have to hear about it: the error is part of\n // the state they publish, and a notice for a file that is fine again is a lie.\n if (this.error !== null) {\n this.error = null\n this.load()\n }\n return { changed: false, applied: false, error: null }\n }\n\n const before = this.resolvedConfig\n this.load()\n return { changed: true, applied: this.resolvedConfig !== before, error: this.error }\n }\n\n private read(): void {\n if (!fs.existsSync(this.file)) {\n // A missing file gets the seed, stamped and written out for the user to edit.\n const seed = stampConfig(structuredClone(this.seed))\n const text = `${JSON.stringify(seed, null, 2)}\\n`\n writeFileAtomic(this.file, text)\n this.lastText = text\n this.raw = seed\n this.apply(seed)\n return\n }\n\n let text: string\n let parsed: unknown\n try {\n text = fs.readFileSync(this.file, 'utf8')\n parsed = JSON.parse(text)\n }\n catch (error) {\n this.error = `cannot parse ${path.basename(this.file)}: ${(error as Error).message}`\n // The same rule `apply` follows for a value it rejects: a file that cannot be\n // trusted never replaces a config this process is already running — neither the\n // running one nor the `raw` one every write patches, or the next settings save\n // would write a config with no servers in it. Only a first load has nothing to keep.\n if (this.resolvedConfig === undefined) {\n this.raw = {}\n this.resolvedConfig = this.resolveFallback()\n }\n return\n }\n\n this.lastText = text\n this.apply(parsed as RawConfig)\n }\n\n private notify(): void {\n for (const listener of this.listeners) listener()\n }\n\n updateServer(id: string, patch: ServerPatch): ServerConfig {\n const index = this.raw.servers?.findIndex(entry => entry.id === id) ?? -1\n if (index < 0)\n throw new ConfigError(`unknown server \"${id}\"`)\n\n const draft = structuredClone(this.raw)\n const entry = draft.servers![index]!\n\n applyPatch(entry, patch as Record<string, unknown>, SERVER_MERGE_KEYS)\n\n const validated = this.validateServer(entry, `servers[${index}]`)\n this.commit(draft)\n return validated\n }\n\n updateControl(patch: NonNullable<SettingsPatch['control']>): ControlConfig {\n const draft = structuredClone(this.raw)\n draft.control = { ...(draft.control ?? {}) }\n applyPatch(draft.control, patch as Record<string, unknown>, CONTROL_MERGE_KEYS)\n\n const control = controlSchema(draft.control)\n if (control instanceof type.errors)\n throw new ConfigError(`control: ${formatErrors(control)}`)\n\n this.commit(draft)\n return control\n }\n\n updateLogs(patch: NonNullable<SettingsPatch['logs']>): LogsConfig {\n const draft = structuredClone(this.raw)\n draft.logs = { ...(draft.logs ?? {}) }\n applyPatch(draft.logs, patch as Record<string, unknown>, EMPTY_MERGE_KEYS)\n\n const logs = logsSchema(draft.logs)\n if (logs instanceof type.errors)\n throw new ConfigError(`logs: ${formatErrors(logs)}`)\n\n this.commit(draft)\n return logs\n }\n\n updateNotifications(patch: NonNullable<SettingsPatch['notifications']>): NotificationsConfig {\n const draft = structuredClone(this.raw)\n draft.notifications = { ...(draft.notifications ?? {}) }\n applyPatch(draft.notifications, patch as Record<string, unknown>, NOTIFICATION_MERGE_KEYS)\n\n const notifications = notificationsSchema(draft.notifications)\n if (notifications instanceof type.errors)\n throw new ConfigError(`notifications: ${formatErrors(notifications)}`)\n\n this.commit(draft)\n return notifications\n }\n\n updateHost(patch: NonNullable<SettingsPatch['host']>): HostConfig {\n const draft = structuredClone(this.raw)\n draft.host = { ...(draft.host ?? {}) }\n applyPatch(draft.host, patch as Record<string, unknown>, EMPTY_MERGE_KEYS)\n\n const host = hostSchema(draft.host)\n if (host instanceof type.errors)\n throw new ConfigError(`host: ${formatErrors(host)}`)\n\n this.commit(draft)\n return host\n }\n\n updateBackups(patch: NonNullable<SettingsPatch['backups']>): BackupsConfig {\n const draft = structuredClone(this.raw)\n draft.backups = { ...(draft.backups ?? {}) }\n applyPatch(draft.backups, patch as Record<string, unknown>, EMPTY_MERGE_KEYS)\n\n const backups = backupsSchema(draft.backups)\n if (backups instanceof type.errors)\n throw new ConfigError(`backups: ${formatErrors(backups)}`)\n\n this.commit(draft)\n return backups\n }\n\n updateDefaults(patch: NonNullable<SettingsPatch['defaults']>): ServerDefaults {\n const draft = structuredClone(this.raw)\n draft.defaults = { ...(draft.defaults ?? {}) }\n applyPatch(draft.defaults, patch as Record<string, unknown>, SERVER_MERGE_KEYS)\n\n const defaults = defaultsSchema(draft.defaults)\n if (defaults instanceof type.errors)\n throw new ConfigError(`defaults: ${formatErrors(defaults)}`)\n\n this.commit(draft)\n return defaults\n }\n\n addServer(input: Record<string, unknown>): ServerConfig {\n const draft = structuredClone(this.raw)\n draft.servers ??= []\n if (draft.servers.some(entry => entry.id === input.id)) {\n throw new ConfigError(`server \"${String(input.id)}\" already exists`)\n }\n\n const index = draft.servers.length\n draft.servers.push(structuredClone(input))\n const validated = this.validateServer(draft.servers[index]!, `servers[${index}]`)\n this.commit(draft)\n return validated\n }\n\n removeServer(id: string): void {\n const draft = structuredClone(this.raw)\n const before = draft.servers?.length ?? 0\n draft.servers = (draft.servers ?? []).filter(entry => entry.id !== id)\n if (draft.servers.length === before)\n throw new ConfigError(`unknown server \"${id}\"`)\n this.commit(draft)\n }\n\n /** Regenerates `servers.config.schema.json` for editor autocomplete. */\n writeJsonSchema(): void {\n const schema = JSON.stringify(configSchema.toJsonSchema(), null, 2)\n const current = fs.existsSync(configSchemaPath) ? fs.readFileSync(configSchemaPath, 'utf8') : null\n if (current !== schema)\n writeFileAtomic(configSchemaPath, schema)\n }\n\n private validateServer(entry: Record<string, unknown>, label: string): ServerConfig {\n const parsed = serverSchema(mergeDefaults(this.defaults, entry))\n if (parsed instanceof type.errors)\n throw new ConfigError(`${label}: ${formatErrors(parsed)}`)\n return { ...parsed, port: parsed.port ?? null }\n }\n\n private commit(draft: RawConfig): void {\n // Every write carries the stamp, so the next release can tell what wrote it.\n const stamped = stampConfig(draft)\n const text = `${JSON.stringify(stamped, null, 2)}\\n`\n writeFileAtomic(this.file, text)\n this.lastText = text\n this.raw = stamped\n this.apply(stamped)\n this.notify()\n }\n\n private resolveFallback(): ResolvedConfig {\n const control = controlSchema({})\n const defaults = defaultsSchema({})\n if (control instanceof type.errors || defaults instanceof type.errors) {\n throw new ConfigError('internal: default config failed validation')\n }\n const logs = logsSchema({})\n const notifications = notificationsSchema({})\n const host = hostSchema({})\n const backups = backupsSchema({})\n if (logs instanceof type.errors || notifications instanceof type.errors || host instanceof type.errors || backups instanceof type.errors) {\n throw new ConfigError('internal: default settings failed validation')\n }\n return { control, defaults, logs, notifications, host, backups, servers: [] }\n }\n\n private apply(raw: RawConfig): void {\n this.raw = raw\n const parsed = parseConfig(raw)\n this.error = parsed.errors.length > 0 ? parsed.errors.join('; ') : null\n this.schemaVersion = parsed.schemaVersion\n this.warnings = [\n ...parsed.warnings,\n ...(parsed.unknownKeys.length === 0\n ? []\n : [`${path.basename(this.file)} carries ${parsed.unknownKeys.length} unrecognized key(s) this release ignores: ${parsed.unknownKeys.join(', ')}`]),\n ]\n // A file that cannot be trusted never replaces a config this process is already\n // running: a bad edit must not disturb supervision or blank the panel. It only\n // falls back to defaults when there is nothing good to keep (a first load).\n if (parsed.config !== null)\n this.resolvedConfig = parsed.config\n else if (this.resolvedConfig === undefined)\n this.resolvedConfig = this.resolveFallback()\n }\n}\n","import type { AppDeps } from '#src/app'\nimport { DetailedError } from '@namesmt/utils'\nimport { type } from 'arktype'\nimport { describeRoute } from 'hono-openapi'\nimport { streamSSE } from 'hono/streaming'\nimport { ConfigError } from '#src/config/store'\nimport { appFactory } from '#src/helpers/factory'\nimport { ERROR_RESPONSES, jsonBody } from '#src/helpers/openapi'\nimport { validate } from '#src/helpers/validator'\nimport { freePortResultSchema, logQuerySchema, serverCreateSchema, serverPatchSchema, serverViewSchema } from '#src/shared/contracts'\n\nconst idParam = type({ id: 'string >= 1' })\n/** Pending SSE writes per connection; log frames are dropped past it, state is not. */\nconst MAX_PENDING_WRITES = 200\nconst serverResponse = type({ server: serverViewSchema })\nconst serversResponse = type({ servers: serverViewSchema.array() })\nconst okResponse = type({ ok: 'boolean' })\n\n/** Unknown ids are 404; a server that exists but cannot start is a 409. */\nfunction statusFor(result: { ok: boolean, error?: string }): 200 | 404 | 409 {\n if (result.ok)\n return 200\n return result.error?.startsWith('unknown server') ? 404 : 409\n}\n\nfunction unknownServer(id: string): DetailedError {\n return new DetailedError(`unknown server \"${id}\"`, { statusCode: 404, code: 'UNKNOWN_SERVER' })\n}\n\nexport function createServersRoute(deps: AppDeps) {\n return appFactory.createApp()\n .get(\n '/',\n describeRoute({\n tags: ['servers'],\n summary: 'Every supervised server, with its live state',\n responses: { 200: { description: 'The servers', content: jsonBody(serversResponse) } },\n }),\n c => c.json({ servers: deps.supervisor.views() }),\n )\n\n .post(\n '/',\n describeRoute({\n tags: ['servers'],\n summary: 'Add a server',\n responses: {\n 201: { description: 'Created', content: jsonBody(serverResponse) },\n 400: ERROR_RESPONSES[400],\n },\n }),\n validate('json', serverCreateSchema),\n (c) => {\n const body = c.req.valid('json')\n try {\n return c.json({ server: deps.store.addServer(body) }, 201)\n }\n catch (error) {\n if (error instanceof ConfigError)\n throw new DetailedError(error.message, { statusCode: 400, code: 'INVALID_SERVER' })\n throw error\n }\n },\n )\n\n // Registered before `/:id` so the literal segments always win.\n .post(\n '/start-all',\n describeRoute({ tags: ['servers'], summary: 'Start every enabled server', responses: { 200: { description: 'The servers', content: jsonBody(serversResponse) } } }),\n async (c) => {\n await deps.supervisor.startAll()\n return c.json({ servers: deps.supervisor.views() })\n },\n )\n\n .post(\n '/stop-all',\n describeRoute({ tags: ['servers'], summary: 'Stop every server', responses: { 200: { description: 'The servers', content: jsonBody(serversResponse) } } }),\n async (c) => {\n await deps.supervisor.stopAll()\n return c.json({ servers: deps.supervisor.views() })\n },\n )\n\n .get(\n '/:id',\n describeRoute({ tags: ['servers'], summary: 'One server', responses: { 200: { description: 'The server', content: jsonBody(serverResponse) }, 404: ERROR_RESPONSES[404] } }),\n validate('param', idParam),\n (c) => {\n const { id } = c.req.valid('param')\n const server = deps.supervisor.views().find(entry => entry.id === id)\n if (!server)\n throw unknownServer(id)\n return c.json({ server })\n },\n )\n\n .get(\n '/:id/logs',\n describeRoute({ tags: ['servers'], summary: 'Buffered log lines from memory', responses: { 200: { description: 'Lines' }, 404: ERROR_RESPONSES[404] } }),\n validate('param', idParam),\n validate('query', logQuerySchema),\n (c) => {\n const { id } = c.req.valid('param')\n if (!deps.store.getServer(id))\n throw unknownServer(id)\n\n const { limit } = c.req.valid('query')\n const parsed = limit === undefined ? Number.NaN : Number.parseInt(limit, 10)\n // Clamped: a negative or huge value must not slice from the wrong end.\n const bounded = Number.isNaN(parsed) ? undefined : Math.min(Math.max(parsed, 1), 100_000)\n return c.json({ lines: deps.supervisor.logLines(id, bounded) })\n },\n )\n\n .get(\n '/:id/stream',\n describeRoute({ tags: ['servers'], summary: 'Server state and logs as server-sent events', responses: { 200: { description: 'text/event-stream' }, 404: ERROR_RESPONSES[404] } }),\n validate('param', idParam),\n (c) => {\n const { id } = c.req.valid('param')\n if (!deps.store.getServer(id))\n throw unknownServer(id)\n\n return streamSSE(c, async (stream) => {\n let closed = false\n let pending = 0\n let queue: Promise<void> = Promise.resolve()\n const send = (data: string, event: string): void => {\n if (closed)\n return\n // Same rule as the panel-wide stream: a slow client loses the chatty\n // server's log frames before it grows an unbounded write queue.\n if (event === 'log' && pending > MAX_PENDING_WRITES)\n return\n pending += 1\n queue = queue.then(() => stream.writeSSE({ event, data })).catch(() => {\n closed = true\n }).finally(() => {\n pending -= 1\n })\n }\n\n const unsubscribe = deps.hub.subscribe(id, (message) => {\n send(JSON.stringify(message), message.type)\n })\n stream.onAbort(() => {\n closed = true\n unsubscribe()\n })\n\n const server = deps.supervisor.views().find(entry => entry.id === id)\n send(JSON.stringify({ type: 'server', ts: Date.now(), serverId: id, server }), 'server')\n send(JSON.stringify({\n type: 'log',\n ts: Date.now(),\n serverId: id,\n lines: deps.supervisor.logLines(id, 200),\n }), 'log')\n\n while (true) {\n await stream.sleep(15000)\n if (closed)\n break\n await stream.writeSSE({ event: 'ping', data: String(Date.now()) })\n }\n })\n },\n )\n\n .post(\n '/:id/start',\n describeRoute({ tags: ['servers'], summary: 'Start a server', responses: { 200: { description: 'Result' }, 404: ERROR_RESPONSES[404] } }),\n validate('param', idParam),\n async (c) => {\n const result = await deps.supervisor.start(c.req.valid('param').id)\n return c.json(result, statusFor(result))\n },\n )\n\n .post(\n '/:id/stop',\n describeRoute({ tags: ['servers'], summary: 'Stop a server', responses: { 200: { description: 'Result' }, 404: ERROR_RESPONSES[404] } }),\n validate('param', idParam),\n async (c) => {\n const result = await deps.supervisor.stop(c.req.valid('param').id)\n return c.json(result, result.ok ? 200 : 404)\n },\n )\n\n .post(\n '/:id/restart',\n describeRoute({ tags: ['servers'], summary: 'Restart a server', responses: { 200: { description: 'Result' }, 404: ERROR_RESPONSES[404] } }),\n validate('param', idParam),\n async (c) => {\n const result = await deps.supervisor.restart(c.req.valid('param').id)\n return c.json(result, statusFor(result))\n },\n )\n\n .post(\n '/:id/clear-logs',\n describeRoute({ tags: ['servers'], summary: 'Forget the buffered log lines', responses: { 200: { description: 'Cleared', content: jsonBody(okResponse) } } }),\n validate('param', idParam),\n (c) => {\n deps.supervisor.clearLogs(c.req.valid('param').id)\n return c.json({ ok: true })\n },\n )\n\n /**\n * The escape hatch for `port x is already in use (pid x)`: it re-lists the\n * listeners itself, so what is killed is the process holding the port now —\n * never a pid quoted in an old message, and never one this panel supervises.\n */\n .post(\n '/:id/free-port',\n describeRoute({\n tags: ['servers'],\n summary: 'Ask whatever holds this server\\'s port to stop',\n responses: {\n 200: { description: 'What was signalled', content: jsonBody(freePortResultSchema) },\n 404: ERROR_RESPONSES[404],\n 409: { description: 'Nothing to free, or the holder is supervised by this panel' },\n },\n }),\n validate('param', idParam),\n async (c) => {\n const { id } = c.req.valid('param')\n const result = await deps.supervisor.freePort(id)\n if (!result.ok)\n throw new DetailedError(result.error ?? `could not free the port for \"${id}\"`, { statusCode: statusFor(result), code: 'FREE_PORT_FAILED' })\n return c.json(result)\n },\n )\n\n .patch(\n '/:id',\n describeRoute({ tags: ['servers'], summary: 'Edit a server', responses: { 200: { description: 'The server', content: jsonBody(serverResponse) }, 400: ERROR_RESPONSES[400], 404: ERROR_RESPONSES[404] } }),\n validate('param', idParam),\n validate('json', serverPatchSchema),\n (c) => {\n try {\n return c.json({ server: deps.store.updateServer(c.req.valid('param').id, c.req.valid('json')) })\n }\n catch (error) {\n if (error instanceof ConfigError) {\n const status = error.message.startsWith('unknown server') ? 404 : 400\n throw new DetailedError(error.message, { statusCode: status, code: status === 404 ? 'UNKNOWN_SERVER' : 'INVALID_SERVER' })\n }\n throw error\n }\n },\n )\n\n .delete(\n '/:id',\n describeRoute({ tags: ['servers'], summary: 'Stop and remove a server', responses: { 200: { description: 'Removed', content: jsonBody(okResponse) }, 404: ERROR_RESPONSES[404] } }),\n validate('param', idParam),\n async (c) => {\n const { id } = c.req.valid('param')\n try {\n await deps.supervisor.stop(id)\n deps.store.removeServer(id)\n return c.json({ ok: true })\n }\n catch (error) {\n if (error instanceof ConfigError)\n throw unknownServer(id)\n throw error\n }\n },\n )\n}\n","import type { AppDeps } from '#src/app'\nimport type { SettingsPatch } from '#src/shared/contracts'\nimport { Buffer } from 'node:buffer'\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport { DetailedError } from '@namesmt/utils'\nimport { describeRoute } from 'hono-openapi'\nimport { ConfigError } from '#src/config/store'\nimport { displayHost } from '#src/helpers/bind'\nimport { afterResponse } from '#src/helpers/deferred'\nimport { appFactory } from '#src/helpers/factory'\nimport { logger } from '#src/helpers/logger'\nimport { ERROR_RESPONSES, jsonBody } from '#src/helpers/openapi'\nimport { validate } from '#src/helpers/validator'\nimport { checkExposure } from '#src/services/exposure'\nimport { buildBackupsView, buildControlView } from '#src/services/state'\nimport { settingsPatchSchema, settingsSavedSchema, settingsViewSchema } from '#src/shared/contracts'\n\n/** Uploads are buffered in memory by `parseBody`, so they get a hard ceiling. */\nconst MAX_UI_UPLOAD_BYTES = 128 * 1024 * 1024\n\n/** The archive's name is only a label, so it never reaches the filesystem. */\nfunction sanitizeName(name: string): string {\n const cleaned = name.replace(/\\.zip$/i, '').replace(/[^\\w.-]+/g, '-').replace(/^-+|-+$/g, '')\n return cleaned.length > 0 ? cleaned.slice(0, 60) : 'custom-ui'\n}\n\nexport function createSettingsRoute(deps: AppDeps) {\n return appFactory.createApp()\n .get(\n '/settings',\n describeRoute({\n tags: ['panel'],\n summary: 'The panel, server defaults, logs, notifications, host and backups',\n responses: { 200: { description: 'The settings', content: jsonBody(settingsViewSchema) } },\n }),\n c => c.json({\n control: buildControlView(deps.store, deps.auth, deps.controlServer.endpoint, deps.tls),\n defaults: deps.store.defaults,\n logs: deps.store.config.logs,\n notifications: { telegram: deps.notifications.status() },\n host: deps.store.config.host,\n backups: buildBackupsView(deps.store, deps.backups),\n ui: deps.ui.status(),\n }),\n )\n\n .patch('/settings', describeRoute({\n tags: ['panel'],\n summary: 'Edit the panel, server defaults, logs, notifications, host and backups',\n responses: { 200: { description: 'Saved; the listener may be moving', content: jsonBody(settingsSavedSchema) }, 400: ERROR_RESPONSES[400] },\n }), validate('json', settingsPatchSchema), async (c) => {\n const patch: SettingsPatch = c.req.valid('json')\n const current = deps.store.config.control\n\n // Refuse an exposure that is not backed by a password before writing anything.\n const exposure = checkExposure(\n {\n ...current,\n host: patch.control?.host ?? current.host,\n auth: { ...current.auth, enabled: patch.control?.auth?.enabled ?? current.auth.enabled },\n },\n deps.auth.passwordSet,\n deps.auth.usingDefaultPassword,\n )\n if (exposure.blockedReason !== null)\n throw new DetailedError(exposure.blockedReason, { statusCode: 400, code: 'EXPOSURE_BLOCKED' })\n\n const previous = { trustProxy: current.auth.trustProxy, tlsEnabled: current.tls.enabled }\n try {\n if (patch.defaults !== undefined)\n deps.store.updateDefaults(patch.defaults)\n if (patch.logs !== undefined)\n deps.store.updateLogs(patch.logs)\n if (patch.notifications !== undefined)\n deps.store.updateNotifications(patch.notifications)\n if (patch.host !== undefined)\n deps.store.updateHost(patch.host)\n if (patch.backups !== undefined)\n deps.store.updateBackups(patch.backups)\n if (patch.control !== undefined)\n deps.store.updateControl(patch.control)\n }\n catch (error) {\n if (error instanceof ConfigError)\n throw new DetailedError(error.message, { statusCode: 400, code: 'INVALID_SETTINGS' })\n throw error\n }\n\n const next = deps.store.config.control\n const endpointChanged = next.host !== deps.controlServer.endpoint.host || next.port !== deps.controlServer.endpoint.port\n const proxyChanged = next.auth.trustProxy !== previous.trustProxy\n const tlsChanged = next.tls.enabled !== previous.tlsEnabled\n let targetUrl: string | null = null\n\n if (endpointChanged || proxyChanged || tlsChanged) {\n // Moving the listener kills the connection serving this very response, so\n // it happens after the response is written. A failure reverts the config\n // and shows up as `restartRequired` in the next state frame.\n const nextProtocol = tlsChanged ? (next.tls.enabled ? 'https' : 'http') : deps.controlServer.endpoint.protocol\n targetUrl = `${nextProtocol}://${displayHost(next.host)}:${next.port}`\n\n afterResponse(async () => {\n const result = endpointChanged\n ? await deps.controlServer.rebind({ host: next.host, port: next.port })\n : await deps.controlServer.restart()\n\n if (result.ok) {\n logger.info(`control panel listening on ${deps.controlServer.endpoint.url}`)\n return\n }\n\n logger.error(`could not move the control panel: ${result.error ?? 'unknown error'}`)\n deps.store.updateControl({\n host: deps.controlServer.endpoint.host,\n port: deps.controlServer.endpoint.port,\n ...(proxyChanged ? { auth: { trustProxy: previous.trustProxy } } : {}),\n ...(tlsChanged ? { tls: { enabled: previous.tlsEnabled } } : {}),\n })\n }, error => logger.error('control panel move failed', error))\n }\n\n return c.json({\n // `control` describes the listener that is live *right now*; `targetUrl`\n // is where it is about to be, which is what the client should open.\n control: buildControlView(deps.store, deps.auth, deps.controlServer.endpoint, deps.tls),\n defaults: deps.store.defaults,\n logs: deps.store.config.logs,\n notifications: { telegram: deps.notifications.status() },\n host: deps.store.config.host,\n backups: buildBackupsView(deps.store, deps.backups),\n ui: deps.ui.status(),\n rebinding: endpointChanged || proxyChanged || tlsChanged,\n targetUrl,\n })\n })\n\n /**\n * Replace the panel's UI with an uploaded static build. The archive is validated\n * and staged before it is swapped in, so a bad upload changes nothing.\n */\n .post('/settings/ui', describeRoute({\n tags: ['panel'],\n summary: 'Replace the panel UI with an uploaded static build',\n responses: { 200: { description: 'Installed' }, 400: ERROR_RESPONSES[400], 413: { description: 'Too large' } },\n }), async (c) => {\n const declared = Number.parseInt(c.req.header('content-length') ?? '0', 10)\n if (Number.isFinite(declared) && declared > MAX_UI_UPLOAD_BYTES)\n throw new DetailedError(`the upload is larger than ${Math.round(MAX_UI_UPLOAD_BYTES / 1024 / 1024)}MB`, { statusCode: 413, code: 'UPLOAD_TOO_LARGE' })\n\n const body = await c.req.parseBody()\n const file = body.file\n if (!(file instanceof File))\n throw new DetailedError('expected a `file` field with the UI archive', { statusCode: 400, code: 'MISSING_FILE' })\n if (file.size > MAX_UI_UPLOAD_BYTES)\n throw new DetailedError(`the upload is larger than ${Math.round(MAX_UI_UPLOAD_BYTES / 1024 / 1024)}MB`, { statusCode: 413, code: 'UPLOAD_TOO_LARGE' })\n\n const staging = path.join(path.dirname(deps.ui.directory), `.ui-upload-${Date.now()}.zip`)\n try {\n await fs.promises.writeFile(staging, Buffer.from(await file.arrayBuffer()))\n const result = await deps.ui.install(staging, sanitizeName(file.name))\n if (!result.ok)\n throw new DetailedError(result.error, { statusCode: 400, code: 'INVALID_UI' })\n\n logger.info(`UI replaced with ${result.meta.name} (${result.meta.files} files)`)\n return c.json({ ok: true, meta: result.meta, ui: deps.ui.status() })\n }\n finally {\n fs.rmSync(staging, { force: true })\n }\n })\n\n /** Back to the stock UI. */\n .delete('/settings/ui', describeRoute({\n tags: ['panel'],\n summary: 'Go back to the stock UI',\n responses: { 200: { description: 'Reverted' } },\n }), (c) => {\n const removed = deps.ui.revert()\n logger.info(removed ? 'custom UI removed — the stock panel is back' : 'no custom UI was installed')\n return c.json({ ok: true, removed, ui: deps.ui.status() })\n })\n}\n","import type { AppDeps } from '#src/app'\nimport { describeRoute } from 'hono-openapi'\nimport { appFactory } from '#src/helpers/factory'\nimport { jsonBody } from '#src/helpers/openapi'\nimport { appStateSchema } from '#src/shared/contracts'\n\n/** The whole panel in one payload: config, live server state and host vitals. */\nexport function createStateRoute(deps: AppDeps) {\n return appFactory.createApp()\n .get(\n '/state',\n describeRoute({\n tags: ['panel'],\n summary: 'Full snapshot of the panel',\n responses: { 200: { description: 'The snapshot', content: jsonBody(appStateSchema) } },\n }),\n c => c.json(deps.supervisor.getState()),\n )\n}\n","import type { Context } from 'hono'\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport { Hono } from 'hono'\n\nconst CONTENT_TYPES: Record<string, string> = {\n '.html': 'text/html; charset=utf-8',\n '.js': 'text/javascript; charset=utf-8',\n '.mjs': 'text/javascript; charset=utf-8',\n '.css': 'text/css; charset=utf-8',\n '.json': 'application/json; charset=utf-8',\n '.map': 'application/json; charset=utf-8',\n '.svg': 'image/svg+xml',\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.gif': 'image/gif',\n '.webp': 'image/webp',\n '.ico': 'image/x-icon',\n '.woff': 'font/woff',\n '.woff2': 'font/woff2',\n '.ttf': 'font/ttf',\n '.txt': 'text/plain; charset=utf-8',\n}\n\nexport interface StaticRouteOptions {\n /** Resolved per request, so a UI installed at runtime takes effect on refresh. */\n dir: string | (() => string)\n entry?: string\n}\n\n/**\n * Serves the built control UI and falls back to `index.html` for client routes,\n * so a deep URL like `/servers/static` works after a refresh.\n */\nexport function createStaticRoute(options: StaticRouteOptions): Hono {\n const route = new Hono()\n const entry = options.entry ?? 'index.html'\n const currentRoot = (): string => path.resolve(typeof options.dir === 'function' ? options.dir() : options.dir)\n\n route.get('*', async (c) => {\n const root = currentRoot()\n const pathname = safeDecode(new URL(c.req.url).pathname)\n if (pathname === null)\n return c.text('bad path', 400)\n\n const file = resolveWithin(root, pathname)\n if (file !== null) {\n const response = await serveFile(c, file, pathname)\n if (response !== null)\n return response\n }\n\n const indexFile = path.join(root, entry)\n if (fs.existsSync(indexFile)) {\n const response = await serveFile(c, indexFile, '/')\n if (response !== null)\n return response\n }\n\n return c.text('no UI is installed — build one and upload it under Settings → Interface', 503)\n })\n\n return route\n}\n\nfunction safeDecode(value: string): string | null {\n try {\n return decodeURIComponent(value)\n }\n catch {\n return null\n }\n}\n\nfunction resolveWithin(root: string, pathname: string): string | null {\n const resolved = path.resolve(root, `.${pathname}`)\n if (resolved !== root && !resolved.startsWith(`${root}${path.sep}`))\n return null\n return resolved\n}\n\nasync function serveFile(c: Context, file: string, pathname: string): Promise<Response | null> {\n let stats: fs.Stats\n try {\n stats = await fs.promises.stat(file)\n }\n catch {\n return null\n }\n if (!stats.isFile())\n return null\n\n const body = await fs.promises.readFile(file)\n const ext = path.extname(file).toLowerCase()\n const immutable = pathname.startsWith('/assets/')\n const payload = body.buffer.slice(body.byteOffset, body.byteOffset + body.byteLength) as ArrayBuffer\n\n return c.body(payload, 200, {\n 'Content-Type': CONTENT_TYPES[ext] ?? 'application/octet-stream',\n 'Content-Length': String(stats.size),\n 'Cache-Control': immutable ? 'public, max-age=31536000, immutable' : 'no-cache',\n })\n}\n","import type { AppDeps } from '#src/app'\nimport { DetailedError } from '@namesmt/utils'\nimport { describeRoute } from 'hono-openapi'\nimport { afterResponse } from '#src/helpers/deferred'\nimport { appFactory } from '#src/helpers/factory'\nimport { logger } from '#src/helpers/logger'\nimport { ERROR_RESPONSES } from '#src/helpers/openapi'\nimport { validate } from '#src/helpers/validator'\nimport { buildControlView } from '#src/services/state'\nimport { tlsUploadSchema } from '#src/shared/contracts'\n\n/**\n * Uploads the PEM pair used for https on the control panel.\n *\n * When TLS is already enabled the listener has to be rebuilt with the new pair,\n * which kills the connection serving this request — so the swap is deferred, and\n * the response tells the client where the panel will be.\n */\nexport function createTlsRoute(deps: AppDeps) {\n const review = () => ({\n control: buildControlView(deps.store, deps.auth, deps.controlServer.endpoint, deps.tls),\n rebinding: deps.store.config.control.tls.enabled,\n targetUrl: deps.controlServer.endpoint.url,\n })\n\n return appFactory.createApp()\n .post(\n '/settings/tls',\n describeRoute({\n tags: ['tls'],\n summary: 'Upload the certificate and key the panel should serve',\n responses: { 200: { description: 'Stored; the panel may be moving to https' }, 400: ERROR_RESPONSES[400] },\n }),\n validate('json', tlsUploadSchema),\n (c) => {\n const body = c.req.valid('json')\n const saved = deps.tls.save(body.certificate, body.privateKey)\n if (!saved.ok)\n throw new DetailedError(saved.error ?? 'the certificate pair was rejected', { statusCode: 400, code: 'INVALID_CERTIFICATE' })\n\n if (deps.store.config.control.tls.enabled) {\n afterResponse(async () => {\n const result = await deps.controlServer.restart()\n if (!result.ok)\n logger.error(`could not reload TLS: ${result.error ?? 'unknown error'}`)\n else logger.info(`control panel listening on ${deps.controlServer.endpoint.url} (https)`)\n }, error => logger.error('tls reload failed', error))\n }\n\n return c.json(review())\n },\n )\n\n .delete(\n '/settings/tls',\n describeRoute({\n tags: ['tls'],\n summary: 'Remove the certificate pair',\n responses: { 200: { description: 'Removed' } },\n }),\n (c) => {\n deps.tls.clear()\n if (deps.store.config.control.tls.enabled) {\n afterResponse(async () => {\n await deps.controlServer.restart()\n }, error => logger.error('tls reload failed', error))\n }\n return c.json(review())\n },\n )\n}\n","import type { DetailedError } from '@namesmt/utils'\nimport type { ErrorHandler as HonoErrorHandler } from 'hono'\nimport type { ContentfulStatusCode } from 'hono/utils/http-status'\nimport { HTTPException } from 'hono/http-exception'\nimport { logger } from '#src/helpers/logger'\n\n/**\n * The one error envelope this API speaks:\n *\n * ```json\n * { \"message\": \"human readable\", \"code\": \"MACHINE_READABLE\", \"detail\": … }\n * ```\n *\n * `@namesmt/utils`' `DetailedError` is the preferred way to fail — it carries the\n * status, a stable code and structured detail — so a client (and the OpenAPI\n * schema) can rely on the shape.\n */\nexport interface ApiErrorBody {\n message: string\n code: string\n detail?: unknown\n}\n\nexport const errorHandler: HonoErrorHandler = (error, c) => {\n const body = toErrorBody(error)\n const status = statusOf(error)\n\n if (status >= 500)\n logger.error(`${c.req.method} ${new URL(c.req.url).pathname} failed:`, error)\n else\n logger.debug(`${c.req.method} ${new URL(c.req.url).pathname} → ${status} ${body.message}`)\n\n return c.json(body, status)\n}\n\nfunction toErrorBody(error: unknown): ApiErrorBody {\n if (error instanceof HTTPException)\n return { message: error.message, code: 'HTTP_EXCEPTION' }\n\n // `DetailedError` can come from this code or from Hono's own parsing helpers,\n // so a name check is safer than `instanceof` across module instances.\n if (isDetailedError(error)) {\n return {\n message: error.message,\n code: error.code ?? 'DETAILED_ERROR',\n ...(error.detail === undefined ? {} : { detail: error.detail }),\n }\n }\n\n if (error instanceof Error)\n return { message: error.message, code: error.name === 'Error' ? 'INTERNAL_ERROR' : error.name.toUpperCase() }\n\n return { message: String(error), code: 'INTERNAL_ERROR' }\n}\n\nfunction isDetailedError(error: unknown): error is DetailedError {\n return error instanceof Error && error.name === 'DetailedError' && 'statusCode' in error\n}\n\nfunction statusOf(error: unknown): ContentfulStatusCode {\n const candidate = (error as { statusCode?: unknown, status?: unknown })?.statusCode ?? (error as { status?: unknown })?.status\n const status = typeof candidate === 'number' ? candidate : 500\n return status >= 400 && status <= 599 ? (status as ContentfulStatusCode) : 500\n}\n","import fs from 'node:fs'\nimport { Scalar } from '@scalar/hono-api-reference'\nimport { openAPIRouteHandler } from 'hono-openapi'\nimport { appFactory } from '#src/helpers/factory'\n\nconst PREFIX = '/openapi'\n\n/**\n * The machine-readable contract, generated from the same ArkType schemas the\n * routes validate with — one source of truth, no second set of DTOs to drift.\n * `/openapi/ui` is a browsable reference (Scalar) and needs no session, since a\n * UI author has to be able to read it before they can log in.\n */\n/** The version the package was built with, so the spec never drifts from it. */\nfunction packageVersion(): string {\n try {\n const manifest = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf8')) as { version?: string }\n return manifest.version ?? '0.0.0'\n }\n catch {\n return '0.0.0'\n }\n}\n\nexport function setupOpenAPI(app: Parameters<typeof openAPIRouteHandler>[0]) {\n return appFactory.createApp()\n .get(\n `${PREFIX}/spec.json`,\n openAPIRouteHandler(app, {\n documentation: {\n info: {\n title: 'home-hosted',\n version: packageVersion(),\n description: 'Control plane for the processes you host at home: servers, logs, vitals, backups and settings.',\n },\n tags: [\n { name: 'panel', description: 'Snapshot, health and the local shutdown channel' },\n { name: 'servers', description: 'The processes being supervised' },\n { name: 'logs', description: 'Live and persisted logs' },\n { name: 'backups', description: 'Archives of config, secrets, TLS and data paths' },\n { name: 'auth', description: 'Sessions and the panel password' },\n { name: 'notifications', description: 'Telegram delivery' },\n { name: 'tls', description: 'The panel certificate' },\n ],\n },\n }),\n )\n .get(\n `${PREFIX}/ui`,\n Scalar({ theme: 'deepSpace', url: `${PREFIX}/spec.json` }),\n )\n}\n","import type { SecretsStore } from '#src/config/secrets'\nimport type { ConfigStore } from '#src/config/store'\nimport type { AuthService } from '#src/services/auth'\nimport type { BackupService } from '#src/services/backups'\nimport type { ControlServer } from '#src/services/control-server'\nimport type { EventHub } from '#src/services/events'\nimport type { LogFiles } from '#src/services/log-files'\nimport type { NotificationService } from '#src/services/notifications'\nimport type { Supervisor } from '#src/services/supervisor'\nimport type { TlsStore } from '#src/services/tls'\nimport type { UiService } from '#src/services/ui'\nimport { createAuthRoute } from '#src/api/auth/$.routes'\nimport { createBackupsRoute } from '#src/api/backups'\nimport { createControlRoute } from '#src/api/control'\nimport { createEventsRoute } from '#src/api/events'\nimport { createHealthRoute } from '#src/api/health'\nimport { createLogsRoute } from '#src/api/logs'\nimport { createMetricsRoute } from '#src/api/metrics'\nimport { createNotificationsRoute } from '#src/api/notifications'\nimport { createServersRoute } from '#src/api/servers/$.routes'\nimport { createSettingsRoute } from '#src/api/settings'\nimport { createStateRoute } from '#src/api/state'\nimport { createStaticRoute } from '#src/api/static'\nimport { createTlsRoute } from '#src/api/tls'\nimport { errorHandler } from '#src/helpers/error'\nimport { appFactory } from '#src/helpers/factory'\nimport { logger } from '#src/helpers/logger'\nimport { createAuthGuard } from '#src/middleware/auth'\nimport { setupOpenAPI } from '#src/openapi'\n\nexport interface AppDeps {\n store: ConfigStore\n supervisor: Supervisor\n hub: EventHub\n auth: AuthService\n secrets: SecretsStore\n controlServer: ControlServer\n tls: TlsStore\n logFiles: LogFiles\n notifications: NotificationService\n backups: BackupService\n ui: UiService\n /** Token for the local `down` command, and the graceful stop it asks for. */\n runtimeToken: string\n onShutdown: () => Promise<void>\n}\n\n/**\n * The root app: middleware and sub-routes only, never a handler of its own.\n *\n * The whole thing is one chained expression on purpose — that is what keeps the\n * route map in the type, which `AppType` hands to `hc<AppType>` clients and to\n * `setupOpenAPI`.\n */\nexport function createRootApp(deps: AppDeps) {\n const app = appFactory.createApp()\n .use('*', async (c, next) => {\n const started = Date.now()\n await next()\n logger.debug(`${c.req.method} ${new URL(c.req.url).pathname} ${c.res.status} ${Date.now() - started}ms`)\n })\n\n .onError(errorHandler)\n\n // Outside `/api`, so a local `down` needs no session — but it needs the token\n // from `run.json` and a loopback peer. Registered before the guard by design.\n .route('/_hh', createControlRoute(deps))\n\n .use('/api/*', createAuthGuard({ auth: deps.auth }))\n\n .route('/api', createAuthRoute(deps))\n .route('/api', createStateRoute(deps))\n .route('/api', createEventsRoute(deps))\n .route('/api', createSettingsRoute(deps))\n .route('/api', createTlsRoute(deps))\n .route('/api', createLogsRoute(deps))\n .route('/api', createNotificationsRoute(deps))\n .route('/api', createMetricsRoute(deps))\n .route('/api', createBackupsRoute(deps))\n .route('/api/servers', createServersRoute(deps))\n\n .route('/', createHealthRoute(deps))\n\n // The spec is generated from the finished route table (and must be routed\n // *before* the static catch-all, which would otherwise swallow it).\n const documented = app.route('/', setupOpenAPI(app))\n return documented.route('/', createStaticRoute({ dir: () => deps.ui.resolveDir() }))\n}\n\n/** What a typed client (`hc<AppType>`) and the OpenAPI document are built from. */\nexport type AppType = ReturnType<typeof createRootApp>\n","import { randomBytes } from 'node:crypto'\nimport fs from 'node:fs'\nimport http from 'node:http'\nimport https from 'node:https'\nimport process from 'node:process'\nimport { type } from 'arktype'\nimport { writeFileAtomic } from '#src/helpers/atomic'\nimport { runtimePath } from '#src/helpers/paths'\n\n/**\n * `run.json` is how `status`/`down` find the live control plane and how they are\n * allowed to stop it without a password: the file is mode 0600, and the token in\n * it is what `POST /_hh/shutdown` checks.\n */\nexport const runtimeSchema = type({\n version: 'string',\n pid: 'number.integer >= 1',\n /** The address that was actually bound, for humans. */\n url: 'string',\n /** Always a reachable loopback address, for probes (`lan` binds to 0.0.0.0). */\n probeUrl: 'string',\n protocol: 'string',\n port: '1 <= number.integer <= 65535',\n bindHost: 'string',\n startedAt: 'number',\n projectDir: 'string',\n dataRoot: 'string',\n configPath: 'string',\n logFile: 'string',\n token: 'string >= 1',\n}).onUndeclaredKey('reject')\nexport type Runtime = typeof runtimeSchema.infer\n\nexport function readRuntime(): Runtime | null {\n try {\n const parsed = runtimeSchema(JSON.parse(fs.readFileSync(runtimePath, 'utf8')))\n return parsed instanceof type.errors ? null : parsed\n }\n catch {\n return null\n }\n}\n\nexport function writeRuntime(runtime: Runtime): void {\n writeFileAtomic(runtimePath, `${JSON.stringify(runtime, null, 2)}\\n`, { mode: 0o600 })\n}\n\nexport function clearRuntime(): void {\n fs.rmSync(runtimePath, { force: true })\n}\n\nexport function newToken(): string {\n return randomBytes(32).toString('base64url')\n}\n\n/** Signal 0 only probes the pid; `EPERM` still means the process is there. */\nexport function isProcessAlive(pid: number): boolean {\n try {\n process.kill(pid, 0)\n return true\n }\n catch (error) {\n return (error as NodeJS.ErrnoException).code === 'EPERM'\n }\n}\n\nexport interface RuntimeProbe {\n /** The panel answered on its own port — stronger than \"the pid exists\". */\n reachable: boolean\n /** It answered, but reports a crashed autostart server (`/healthz` is 503). */\n degraded: boolean\n}\n\n/** A degraded panel is still answering: a 503 must not read as \"not running\". */\nexport async function probeRuntime(runtime: Runtime, timeoutMs = 2500): Promise<RuntimeProbe> {\n const status = await localRequest(runtime, '/healthz', 'GET', undefined, timeoutMs)\n return { reachable: status !== null, degraded: status === 503 }\n}\n\n/**\n * Asks the daemon to stop through its own endpoint, so supervised servers are\n * shut down cleanly on every platform (a bare signal is not graceful on Windows).\n */\nexport async function requestShutdown(runtime: Runtime, timeoutMs = 4000): Promise<boolean> {\n const status = await localRequest(runtime, '/_hh/shutdown', 'POST', runtime.token, timeoutMs)\n return status !== null && status >= 200 && status < 300\n}\n\n/**\n * Talks to the panel over loopback. Node's `fetch` cannot be told to accept the\n * self-signed certificate an uploaded TLS pair usually is, which would break\n * `status` and the graceful `down` — so this speaks http/https directly.\n */\nfunction localRequest(runtime: Runtime, path: string, method: 'GET' | 'POST', token: string | undefined, timeoutMs: number): Promise<number | null> {\n return new Promise((resolve) => {\n const url = new URL(`${runtime.probeUrl}${path}`)\n const secure = url.protocol === 'https:'\n const request = (secure ? https : http).request({\n hostname: url.hostname,\n port: url.port,\n path: url.pathname,\n method,\n // Only ever pointed at our own listener on this machine.\n ...(secure ? { rejectUnauthorized: false } : {}),\n headers: token === undefined ? {} : { 'x-home-hosted-token': token },\n timeout: timeoutMs,\n }, (response) => {\n response.resume()\n response.once('end', () => resolve(response.statusCode ?? null))\n })\n\n request.once('error', () => resolve(null))\n request.once('timeout', () => {\n request.destroy()\n resolve(null)\n })\n request.end()\n })\n}\n","import { exec } from 'node:child_process'\nimport process from 'node:process'\n\n/** Best-effort browser launch; a headless host simply logs instead. */\nexport function openBrowser(url: string): void {\n const command = process.platform === 'darwin'\n ? `open \"${url}\"`\n : process.platform === 'win32'\n ? `start \"\" \"${url}\"`\n : `xdg-open \"${url}\"`\n\n exec(command, { windowsHide: true }, () => {\n // No display / no handler: the URL is already printed, so this is not an error.\n })\n}\n","export type TemplateVars = Record<string, string | number>\n\n/**\n * Replaces `{name}` placeholders. Unknown placeholders are left untouched so a\n * typo surfaces in the child's args instead of silently becoming an empty string.\n */\nexport function resolveTemplate(value: string, vars: TemplateVars): string {\n return value.replace(/(?<!\\$)\\{([a-z][\\w-]*)\\}/gi, (match, name: string) => {\n const replacement = vars[name]\n return replacement === undefined ? match : String(replacement)\n })\n}\n\nexport function resolveTemplates<T extends string | string[]>(value: T, vars: TemplateVars): T {\n if (Array.isArray(value))\n return value.map(entry => resolveTemplate(entry, vars)) as T\n return resolveTemplate(value as string, vars) as T\n}\n\nexport function resolveRecord(record: Record<string, string>, vars: TemplateVars): Record<string, string> {\n return Object.fromEntries(\n Object.entries(record).map(([key, value]) => [key, resolveTemplate(value, vars)]),\n )\n}\n","import { execFile } from 'node:child_process'\nimport net from 'node:net'\nimport process from 'node:process'\nimport { promisify } from 'node:util'\n\nconst execFileAsync = promisify(execFile)\n\n/** True when something accepts TCP connections on host:port. */\nexport function probePort(host: string, port: number, timeoutMs = 1500): Promise<boolean> {\n return new Promise((resolve) => {\n const socket = net.connect({ host, port })\n const done = (result: boolean): void => {\n socket.removeAllListeners()\n socket.destroy()\n resolve(result)\n }\n socket.setTimeout(timeoutMs)\n socket.once('connect', () => done(true))\n socket.once('timeout', () => done(false))\n socket.once('error', () => done(false))\n })\n}\n\n/** A port is free when nothing is listening on it (loopback is enough to detect conflicts). */\nexport async function isPortFree(port: number, host = '127.0.0.1', timeoutMs = 1000): Promise<boolean> {\n return !(await probePort(host, port, timeoutMs))\n}\n\n/**\n * Kills whatever holds the port, minus `exclude` (the panel's own process tree).\n * Only used as a last resort for wrappers that spawn their real server detached,\n * where a process-group signal cannot reach it.\n */\nexport async function killPortHolders(port: number, exclude?: ReadonlySet<number>): Promise<number[]> {\n const pids = (await listPortHolders(port)).filter(pid => !exclude?.has(pid))\n for (const pid of pids) {\n try {\n process.kill(pid, 'SIGKILL')\n }\n catch {\n // already gone\n }\n }\n return pids\n}\n\n/** Signal 0 asks the OS whether the pid still exists, without touching it. */\nexport function isProcessAlive(pid: number): boolean {\n try {\n process.kill(pid, 0)\n return true\n }\n catch {\n return false\n }\n}\n\nfunction signalPid(pid: number, name: NodeJS.Signals): void {\n try {\n process.kill(pid, name)\n }\n catch {\n // already gone, or not ours to signal\n }\n}\n\nfunction delay(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms))\n}\n\n/**\n * Asks specific pids to leave, politely first: a stray dev server stops on\n * SIGTERM, and only what ignores it is killed. Deciding *which* pids may be\n * touched belongs to the caller — this only does the signalling.\n */\nexport async function terminatePids(\n pids: number[],\n options: { graceMs?: number } = {},\n): Promise<{ stopped: number[], forced: number[] }> {\n const graceMs = options.graceMs ?? 3000\n const targets = [...new Set(pids)]\n // Only what was actually there is claimed as stopped; a pid that had already\n // exited is neither ours to report nor ours to kill.\n const aliveBefore = targets.filter(isProcessAlive)\n\n for (const pid of aliveBefore) signalPid(pid, 'SIGTERM')\n\n const deadline = Date.now() + graceMs\n let alive = aliveBefore.filter(isProcessAlive)\n while (alive.length > 0 && Date.now() < deadline) {\n await delay(100)\n alive = alive.filter(isProcessAlive)\n }\n\n const stopped = aliveBefore.filter(pid => !alive.includes(pid))\n for (const pid of alive) signalPid(pid, 'SIGKILL')\n if (alive.length > 0 && graceMs > 0)\n await delay(150)\n\n return { stopped, forced: alive }\n}\n\n/** `netstat -ano` lines: ` TCP 127.0.0.1:4010 0.0.0.0:0 LISTENING 1234` */\nexport function parseNetstatListeners(output: string, port: number): number[] {\n const pids = new Set<number>()\n\n for (const line of output.split(/\\r?\\n/)) {\n const cells = line.trim().split(/\\s+/)\n if (cells.length < 5)\n continue\n const local = cells[1] ?? ''\n const state = cells[3] ?? ''\n const pid = Number.parseInt(cells[4] ?? '', 10)\n const localPort = Number.parseInt(local.slice(local.lastIndexOf(':') + 1), 10)\n if (state.toUpperCase() !== 'LISTENING' || localPort !== port)\n continue\n if (Number.isInteger(pid) && pid > 0 && pid !== process.pid)\n pids.add(pid)\n }\n\n return [...pids]\n}\n\nexport async function listPortHolders(port: number): Promise<number[]> {\n if (process.platform === 'win32') {\n try {\n const { stdout } = await execFileAsync('netstat', ['-ano', '-p', 'tcp'], { timeout: 5000 })\n return parseNetstatListeners(stdout, port)\n }\n catch {\n return []\n }\n }\n\n try {\n const { stdout } = await execFileAsync('lsof', ['-ti', `tcp:${port}`, '-sTCP:LISTEN'], { timeout: 3000 })\n return parsePids(stdout)\n }\n catch {\n // lsof missing or nothing listening\n }\n\n try {\n const { stdout } = await execFileAsync('fuser', [`${port}/tcp`], { timeout: 3000 })\n return parsePids(stdout)\n }\n catch {\n return []\n }\n}\n\nfunction parsePids(stdout: string): number[] {\n return [...new Set(\n stdout.split(/\\s+/)\n .map(entry => Number.parseInt(entry, 10))\n .filter(pid => Number.isInteger(pid) && pid > 0 && pid !== process.pid),\n )]\n}\n","import fs from 'node:fs'\nimport path from 'node:path'\n\n/** `KEY=value` files, with optional `export `, `#` comments and quoted values. */\nexport function parseEnvFile(text: string): Record<string, string> {\n const env: Record<string, string> = {}\n\n for (const raw of text.split('\\n')) {\n const line = raw.trim()\n if (line.length === 0 || line.startsWith('#'))\n continue\n\n const assignment = line.startsWith('export ') ? line.slice(7) : line\n const separator = assignment.indexOf('=')\n if (separator <= 0)\n continue\n\n const key = assignment.slice(0, separator).trim()\n let value = assignment.slice(separator + 1).trim()\n if (value.length > 1 && ((value.startsWith('\"') && value.endsWith('\"')) || (value.startsWith('\\'') && value.endsWith('\\'')))) {\n value = value.slice(1, -1)\n }\n env[key] = value\n }\n\n return env\n}\n\n/** Missing files are not an error: an env file is an optional override layer. */\nexport function loadEnvFile(file: string): { env: Record<string, string>, path: string, error: string | null } {\n try {\n return { env: parseEnvFile(fs.readFileSync(file, 'utf8')), path: file, error: null }\n }\n catch (error) {\n const code = (error as NodeJS.ErrnoException).code\n if (code === 'ENOENT')\n return { env: {}, path: file, error: null }\n return { env: {}, path: file, error: error instanceof Error ? error.message : String(error) }\n }\n}\n\nconst VARIABLE = /\\$\\{([A-Z_]\\w*)\\}/gi\n\n/** Expands `${VAR}` from the given vars; unknown references are left visible. */\nexport function expandEnv(value: string, vars: Record<string, string | undefined>): string {\n return value.replace(VARIABLE, (match, name: string) => vars[name] ?? match)\n}\n\nexport function expandEnvRecord(record: Record<string, string>, vars: Record<string, string | undefined>): Record<string, string> {\n return Object.fromEntries(Object.entries(record).map(([key, value]) => [key, expandEnv(value, vars)]))\n}\n\nexport function expandEnvList(values: string[], vars: Record<string, string | undefined>): string[] {\n return values.map(value => expandEnv(value, vars))\n}\n\nexport function resolveEnvFilePath(file: string, cwd: string): string {\n if (path.isAbsolute(file))\n return file\n return path.resolve(cwd, file)\n}\n","import type { FileEntry } from '@zip.js/zip.js'\nimport { Buffer } from 'node:buffer'\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport { Readable, Writable } from 'node:stream'\nimport { finished } from 'node:stream/promises'\nimport { BlobReader, configure, ZipReader, ZipWriter } from '@zip.js/zip.js'\n\n/**\n * `Readable.toWeb` is typed against `node:stream/web`, while zip.js declares the\n * global `ReadableStream` — the same objects under two declarations, and which\n * one wins depends on the tsconfig (the SPA's adds lib.dom). These aliases take\n * the type zip.js expects, whichever it is where this file is compiled.\n */\ntype ZipInput = Parameters<ZipWriter<unknown>['add']>[1]\ntype ZipOutput = ConstructorParameters<typeof ZipWriter>[0] & { abort: (reason?: unknown) => Promise<void> }\ntype ZipDataOutput = Parameters<FileEntry['getData']>[0]\n\n/**\n * Backups are ordinary zip files: the same container whether or not they are\n * password-protected, openable by any archive manager (including the one built\n * into Windows and macOS), and produced without a native binary — zip.js is pure\n * JavaScript.\n *\n * A password means WinZip AES-256 (`encryptionStrength: 3`, AE-2): strong, and\n * still standard, unlike the legacy ZipCrypto encryption.\n */\n\n// Deterministic, in-process codecs: a bundled CLI has no worker file to load.\nconfigure({ useWebWorkers: false })\n\nconst ZIPS = [\n [0x50, 0x4B, 0x03, 0x04],\n [0x50, 0x4B, 0x05, 0x06],\n [0x50, 0x4B, 0x07, 0x08],\n]\n\n/** Recognised by content, never by the file's name. */\nexport function isZipArchive(file: string): boolean {\n let fd: number | null = null\n try {\n fd = fs.openSync(file, 'r')\n const head = Buffer.alloc(4)\n const read = fs.readSync(fd, head, 0, 4, 0)\n return read === 4 && ZIPS.some(magic => magic.every((byte, index) => head[index] === byte))\n }\n catch {\n return false\n }\n finally {\n if (fd !== null)\n fs.closeSync(fd)\n }\n}\n\nexport interface ArchiveEntry {\n /** Forward-slash path inside the archive; directories end with `/`. */\n name: string\n directory: boolean\n encrypted: boolean\n symlink: boolean\n /** Uncompressed size, for callers that cap what they will extract. */\n size: number\n}\n\n/** The central directory is not encrypted, so this works without a password. */\nexport async function listZip(file: string): Promise<ArchiveEntry[]> {\n const reader = await open(file)\n try {\n const entries = await reader.getEntries()\n return entries.map(entry => ({\n name: entry.filename,\n directory: entry.directory === true,\n encrypted: entry.encrypted === true,\n symlink: entry.symlink === true,\n size: entry.uncompressedSize ?? 0,\n }))\n }\n finally {\n await reader.close()\n }\n}\n\n/** True when the archive rejected the password we used. */\nexport function isInvalidPassword(error: unknown): boolean {\n return error instanceof Error && /password/i.test(error.message)\n}\n\n/**\n * Writes the contents of `sourceDir` into `destination`, preserving the tree.\n * Symlinks are followed, so a link to a directory is captured as a directory and\n * a link loop cannot recurse forever. Streams from disk to disk: nothing is\n * buffered whole.\n */\nexport async function createZip(sourceDir: string, destination: string, options: { password?: string } = {}): Promise<void> {\n const output = fs.createWriteStream(destination, { mode: 0o600 })\n // Attached up front: the fd closes as part of the web stream ending, so a\n // listener added afterwards would wait forever.\n const flushed = finished(output)\n const writer = Writable.toWeb(output) as unknown as ZipOutput\n const zip = new ZipWriter(writer, {\n ...(options.password === undefined ? {} : { password: options.password, encryptionStrength: 3 as const }),\n level: 6,\n keepOrder: true,\n })\n\n try {\n for (const item of walk(sourceDir)) {\n if (item.directory)\n await zip.add(item.name, null, { directory: true })\n else if (item.size === 0)\n // An empty file carries no content to protect, and leaving it as a plain\n // AE-2 entry makes older tools (p7zip 16.02) report a CRC failure on it.\n await zip.add(item.name, null, { directory: false })\n else\n await zip.add(item.name, Readable.toWeb(fs.createReadStream(item.absolute)) as unknown as ZipInput)\n }\n await zip.close()\n await flushed\n }\n catch (error) {\n await writer.abort(error).catch(() => {})\n // The file stream also fails here (a full disk, a directory in the way), and\n // an unobserved rejection would take the whole control plane down.\n await flushed.catch(() => {})\n throw error\n }\n}\n\n/**\n * Extracts the given entries (already validated by the caller) into\n * `destination`. Symbolic links are never recreated — an archive is not allowed\n * to make the filesystem point somewhere else.\n */\nexport async function extractZip(\n file: string,\n destination: string,\n options: { names: string[], password?: string },\n): Promise<{ skipped: string[] }> {\n const reader = await open(file, options.password)\n const skipped: string[] = []\n\n try {\n const entries = new Map((await reader.getEntries()).map(entry => [entry.filename, entry]))\n\n for (const name of options.names) {\n const entry = entries.get(name)\n if (entry === undefined) {\n skipped.push(name)\n continue\n }\n\n const target = path.join(destination, name)\n if (entry.directory) {\n fs.mkdirSync(target, { recursive: true })\n continue\n }\n if (entry.symlink) {\n skipped.push(name)\n continue\n }\n\n fs.mkdirSync(path.dirname(target), { recursive: true })\n await entry.getData(Writable.toWeb(fs.createWriteStream(target)) as unknown as ZipDataOutput, writeOptions(entry, options.password))\n }\n }\n finally {\n await reader.close()\n }\n\n return { skipped }\n}\n\nfunction writeOptions(entry: FileEntry, password: string | undefined): { password?: string } {\n return entry.encrypted && password !== undefined ? { password } : {}\n}\n\nasync function open(file: string, password?: string): Promise<ZipReader<unknown>> {\n // A lazily-read Blob keeps a multi-gigabyte archive out of memory: zip.js only\n // pulls the byte ranges it needs.\n const blob = await fs.openAsBlob(file, { type: 'application/zip' })\n return password === undefined\n ? new ZipReader(new BlobReader(blob))\n : new ZipReader(new BlobReader(blob), { password })\n}\n\ninterface WalkedFile {\n name: string\n absolute: string\n directory: boolean\n size: number\n}\n\n/** Sorted, deterministic walk with symlinks resolved and directory loops broken. */\nfunction walk(root: string): WalkedFile[] {\n const files: WalkedFile[] = []\n const seen = new Set<string>()\n\n const visit = (absolute: string, name: string): void => {\n let stats: fs.Stats\n try {\n stats = fs.statSync(absolute)\n }\n catch {\n return\n }\n\n if (stats.isDirectory()) {\n const real = fs.realpathSync(absolute)\n if (seen.has(real))\n return\n seen.add(real)\n files.push({ name: `${name}/`, absolute, directory: true, size: 0 })\n for (const child of fs.readdirSync(absolute).sort())\n visit(path.join(absolute, child), `${name}/${child}`)\n return\n }\n\n if (stats.isFile())\n files.push({ name, absolute, directory: false, size: stats.size })\n }\n\n for (const child of fs.readdirSync(root).sort())\n visit(path.join(root, child), child)\n\n return files\n}\n","export interface BackoffOptions {\n baseDelayMs: number\n factor: number\n maxDelayMs: number\n}\n\n/** Exponential backoff: base * factor^(attempt - 1), capped at maxDelayMs. */\nexport function computeBackoff(attempt: number, options: BackoffOptions): number {\n const normalized = Math.max(1, Math.floor(attempt))\n const raw = options.baseDelayMs * options.factor ** (normalized - 1)\n if (!Number.isFinite(raw))\n return options.maxDelayMs\n return Math.min(Math.max(0, raw), options.maxDelayMs)\n}\n","import type { HttpCheckConfig } from '#src/shared/contracts'\nimport { probePort } from '#src/providers/port'\n\nexport interface HealthProbeResult {\n healthy: boolean\n ms: number\n detail: string\n}\n\n/** TCP connect timing, used for the default `port` mode and readiness. */\nexport async function probeTcp(host: string, port: number, timeoutMs: number): Promise<HealthProbeResult> {\n const started = Date.now()\n const accepting = await probePort(host, port, timeoutMs)\n const ms = Date.now() - started\n return { healthy: accepting, ms, detail: accepting ? 'port accepted a connection' : 'port did not accept a connection' }\n}\n\nexport interface HttpProbeOptions extends Pick<HttpCheckConfig, 'path' | 'method' | 'expectBody' | 'expectStatusBelow'> {\n /** `null` counts as \"not set\", so a value saved by the UI can be cleared again. */\n expectStatus?: number | null\n timeoutMs: number\n}\n\n/**\n * Fetches the configured path and asserts the response, so \"listening\" is not\n * mistaken for \"working\".\n */\nexport async function probeHttp(host: string, port: number, options: HttpProbeOptions): Promise<HealthProbeResult> {\n const url = `http://${host}:${port}${options.path.startsWith('/') ? options.path : `/${options.path}`}`\n const started = Date.now()\n\n try {\n const response = await fetch(url, {\n method: options.method,\n redirect: 'manual',\n signal: AbortSignal.timeout(options.timeoutMs),\n })\n const ms = Date.now() - started\n\n const expected = options.expectStatus ?? null\n if (expected !== null && response.status !== expected) {\n return { healthy: false, ms, detail: `expected status ${expected}, got ${response.status}` }\n }\n if (expected === null && response.status >= options.expectStatusBelow) {\n return { healthy: false, ms, detail: `status ${response.status} is >= ${options.expectStatusBelow}` }\n }\n\n if (options.expectBody.length > 0 && options.method !== 'HEAD') {\n const body = await response.text()\n if (!body.includes(options.expectBody)) {\n return { healthy: false, ms, detail: `body does not contain ${JSON.stringify(options.expectBody)}` }\n }\n }\n\n return { healthy: true, ms, detail: `HTTP ${response.status}` }\n }\n catch (error) {\n const ms = Date.now() - started\n const reason = error instanceof Error ? error.message : String(error)\n return { healthy: false, ms, detail: `request failed: ${reason}` }\n }\n}\n\nexport async function probeHealth(options: {\n mode: 'port' | 'http'\n hosts: string[]\n port: number\n timeoutMs: number\n http: Pick<HttpCheckConfig, 'path' | 'method' | 'expectBody' | 'expectStatusBelow'> & { expectStatus?: number | null }\n}): Promise<HealthProbeResult> {\n let last: HealthProbeResult = { healthy: false, ms: 0, detail: 'not probed' }\n\n // Try each candidate host in order (loopback first), so a server bound to one\n // specific address is still probed somewhere it actually listens.\n for (const host of options.hosts) {\n const result = options.mode === 'http'\n ? await probeHttp(host, options.port, { ...options.http, timeoutMs: options.timeoutMs })\n : await probeTcp(host, options.port, options.timeoutMs)\n if (result.healthy)\n return result\n last = result\n }\n\n return last\n}\n","import type { ProcessResources } from '#src/shared/contracts'\nimport { execFile } from 'node:child_process'\nimport fs from 'node:fs'\nimport process from 'node:process'\nimport { promisify } from 'node:util'\n\nconst execFileAsync = promisify(execFile)\n\ninterface ProcRow {\n pid: number\n ppid: number\n rssKb: number\n /** Cumulative CPU seconds, when the platform reports it (Linux, Windows). */\n cpuSeconds?: number\n /** Instantaneous/decaying CPU percent, when the platform reports it (macOS). */\n cpuPercent?: number\n}\n\nexport function parsePsOutput(text: string): ProcRow[] {\n const rows: ProcRow[] = []\n for (const line of text.split('\\n')) {\n const parts = line.trim().split(/\\s+/)\n if (parts.length < 4)\n continue\n const [pid, ppid, rss, cpu] = parts.map(entry => Number.parseFloat(entry))\n if (pid === undefined || ppid === undefined || rss === undefined || !Number.isFinite(pid))\n continue\n rows.push({ pid, ppid, rssKb: rss, cpuPercent: Number.isFinite(cpu) ? cpu : undefined })\n }\n return rows\n}\n\n/** CSV from `Get-CimInstance ... | ConvertTo-Csv`, or wmic's `/format:csv`. */\nexport function parseWindowsCsv(text: string): ProcRow[] {\n const rows: ProcRow[] = []\n const lines = text.split(/\\r?\\n/).filter(line => line.trim().length > 0)\n const header = lines.shift()\n if (header === undefined)\n return rows\n\n const columns = header.split(',').map(entry => entry.replace(/\"/g, '').trim().toLowerCase())\n const index = (name: string): number => columns.indexOf(name.toLowerCase())\n const pidAt = index('ProcessId')\n const ppidAt = index('ParentProcessId')\n const rssAt = index('WorkingSetSize')\n const kernelAt = index('KernelModeTime')\n const userAt = index('UserModeTime')\n\n for (const line of lines) {\n const cells = line.split(',').map(entry => entry.replace(/\"/g, '').trim())\n const pid = Number.parseInt(cells[pidAt] ?? '', 10)\n if (!Number.isFinite(pid))\n continue\n\n // A missing time column must read as \"unknown\", never as zero CPU.\n const kernel = kernelAt >= 0 ? Number.parseInt(cells[kernelAt] ?? '', 10) : Number.NaN\n const user = userAt >= 0 ? Number.parseInt(cells[userAt] ?? '', 10) : Number.NaN\n const hasTimes = Number.isFinite(kernel) && Number.isFinite(user)\n\n rows.push({\n pid,\n ppid: Number.parseInt(cells[ppidAt] ?? '', 10) || 0,\n // WorkingSetSize is bytes on Windows; the sampler sums kilobytes.\n rssKb: (Number.parseInt(cells[rssAt] ?? '', 10) || 0) / 1024,\n cpuSeconds: hasTimes ? (kernel + user) / 1e7 /* 100ns units */ : undefined,\n })\n }\n\n return rows\n}\n\nlet clockTicks: number | null = null\n\n/** Linux jiffies per second; `getconf` is POSIX, with the usual default behind it. */\nasync function getClockTicks(): Promise<number> {\n if (clockTicks !== null)\n return clockTicks\n try {\n const { stdout } = await execFileAsync('getconf', ['CLK_TCK'], { timeout: 2000 })\n const parsed = Number.parseInt(stdout.trim(), 10)\n clockTicks = Number.isFinite(parsed) && parsed > 0 ? parsed : 100\n }\n catch {\n clockTicks = 100\n }\n return clockTicks\n}\n\n/**\n * `/proc/<pid>/stat` needs care: the comm field is parenthesised and may itself\n * contain spaces or parentheses, so parsing starts after the last `)`.\n */\nfunction parseStat(pid: number, content: string): ProcRow | null {\n const close = content.lastIndexOf(')')\n if (close < 0)\n return null\n const fields = content.slice(close + 2).split(' ')\n const ppid = Number.parseInt(fields[1] ?? '', 10)\n const utime = Number.parseInt(fields[11] ?? '', 10)\n const stime = Number.parseInt(fields[12] ?? '', 10)\n const rssPages = Number.parseInt(fields[21] ?? '', 10)\n\n if (!Number.isFinite(ppid) || !Number.isFinite(utime) || !Number.isFinite(stime))\n return null\n return { pid, ppid, rssKb: Number.isFinite(rssPages) ? rssPages * 4 : 0, cpuSeconds: utime + stime }\n}\n\nasync function readLinux(): Promise<ProcRow[]> {\n const rows: ProcRow[] = []\n let names: string[] = []\n try {\n names = fs.readdirSync('/proc')\n }\n catch {\n return rows\n }\n\n for (const name of names) {\n if (!/^\\d+$/.test(name))\n continue\n const pid = Number.parseInt(name, 10)\n try {\n const row = parseStat(pid, fs.readFileSync(`/proc/${pid}/stat`, 'utf8'))\n if (row === null)\n continue\n // VmRSS is exact; the stat page count assumes a 4K page.\n try {\n const vmRss = /^VmRSS:\\s+(\\d+)\\s+kB/m.exec(fs.readFileSync(`/proc/${pid}/status`, 'utf8'))?.[1]\n if (vmRss !== undefined)\n row.rssKb = Number.parseInt(vmRss, 10)\n }\n catch {\n // Fall back to the page count.\n }\n rows.push(row)\n }\n catch {\n // Exited between listing and reading.\n }\n }\n\n return rows\n}\n\nasync function readPosix(): Promise<ProcRow[]> {\n const { stdout } = await execFileAsync('ps', ['-Ao', 'pid=,ppid=,rss=,%cpu='], { timeout: 5000, maxBuffer: 16 * 1024 * 1024 })\n return parsePsOutput(stdout)\n}\n\nasync function readWindows(): Promise<ProcRow[]> {\n const script = 'Get-CimInstance Win32_Process | Select-Object ProcessId,ParentProcessId,WorkingSetSize,KernelModeTime,UserModeTime | ConvertTo-Csv -NoTypeInformation'\n try {\n const { stdout } = await execFileAsync('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', script], {\n timeout: 8000,\n maxBuffer: 16 * 1024 * 1024,\n })\n return parseWindowsCsv(stdout)\n }\n catch {\n try {\n const { stdout } = await execFileAsync('wmic', [\n 'process',\n 'get',\n 'ProcessId,ParentProcessId,WorkingSetSize,KernelModeTime,UserModeTime',\n '/format:csv',\n ], { timeout: 8000, maxBuffer: 16 * 1024 * 1024 })\n return parseWindowsCsv(stdout)\n }\n catch {\n // Neither tool is available; resource sampling degrades to \"unknown\".\n return []\n }\n }\n}\n\nasync function readProcesses(): Promise<ProcRow[]> {\n if (process.platform === 'linux')\n return readLinux()\n if (process.platform === 'win32')\n return readWindows()\n return readPosix()\n}\n\nfunction collectTree(rootPid: number, children: Map<number, number[]>): number[] {\n const pids: number[] = []\n const stack = [rootPid]\n const seen = new Set<number>()\n\n while (stack.length > 0) {\n const pid = stack.pop()!\n if (seen.has(pid))\n continue\n seen.add(pid)\n pids.push(pid)\n for (const child of children.get(pid) ?? []) stack.push(child)\n }\n\n return pids\n}\n\n/**\n * Samples CPU and RSS for a process *and its descendants*.\n *\n * Descendants matter: a wrapper that spawns the real server detached (the\n * omniroute CLI does) owns the tree, and only the tree's RSS means anything.\n *\n * Backends: `/proc` on Linux, `ps` on macOS/other POSIX, and Win32_Process via\n * PowerShell (wmic as a fallback) on Windows. When a backend cannot run, samples\n * are null rather than wrong.\n */\nexport class ProcessSampler {\n private readonly previous = new Map<number, { cpuSeconds: number, at: number }>()\n\n async sample(rootPid: number, now = Date.now()): Promise<ProcessResources | null> {\n const samples = await this.sampleMany([rootPid], now)\n return samples.get(rootPid) ?? null\n }\n\n async sampleMany(rootPids: number[], now = Date.now()): Promise<Map<number, ProcessResources | null>> {\n const results = new Map<number, ProcessResources | null>()\n if (rootPids.length === 0)\n return results\n\n let rows: ProcRow[] = []\n try {\n rows = await readProcesses()\n }\n catch {\n rows = []\n }\n\n const byPid = new Map(rows.map(row => [row.pid, row]))\n const children = new Map<number, number[]>()\n for (const row of rows) {\n const siblings = children.get(row.ppid) ?? []\n siblings.push(row.pid)\n children.set(row.ppid, siblings)\n }\n\n for (const rootPid of rootPids) {\n if (!byPid.has(rootPid)) {\n this.previous.delete(rootPid)\n results.set(rootPid, null)\n continue\n }\n\n const pids = collectTree(rootPid, children)\n let rssKb = 0\n let cpuSeconds: number | null = 0\n let percentAverage: number | null = null\n\n for (const pid of pids) {\n const row = byPid.get(pid)\n if (!row)\n continue\n rssKb += row.rssKb\n if (row.cpuSeconds === undefined)\n cpuSeconds = null\n else if (cpuSeconds !== null)\n cpuSeconds += row.cpuSeconds\n if (row.cpuPercent !== undefined)\n percentAverage = (percentAverage ?? 0) + row.cpuPercent\n }\n\n let cpuPercent: number | null = percentAverage\n if (cpuPercent === null && cpuSeconds !== null) {\n if (process.platform === 'linux') {\n const ticks = await getClockTicks()\n cpuSeconds /= ticks\n }\n\n const before = this.previous.get(rootPid)\n if (before !== undefined && now > before.at) {\n const elapsedSeconds = (now - before.at) / 1000\n const usedSeconds = cpuSeconds - before.cpuSeconds\n if (elapsedSeconds > 0 && usedSeconds >= 0)\n cpuPercent = (usedSeconds / elapsedSeconds) * 100\n }\n this.previous.set(rootPid, { cpuSeconds, at: now })\n }\n else {\n this.previous.delete(rootPid)\n }\n\n results.set(rootPid, {\n cpuPercent: cpuPercent === null ? null : Math.round(cpuPercent * 10) / 10,\n rssBytes: Math.round(rssKb * 1024),\n processes: pids.length,\n sampledAt: now,\n })\n }\n\n return results\n }\n\n forget(rootPid: number): void {\n this.previous.delete(rootPid)\n }\n}\n\n/**\n * Does this process carry the environment the supervisor gave the entry?\n *\n * A program that restarts itself — especially a plugin doing it — leaves behind a\n * detached process that still inherits `HHOSTED_SERVER_ID`, and that marker is what\n * tells a legitimate successor apart from a stranger squatting on the port.\n *\n * Linux reads `/proc`; macOS asks `ps -E`; Windows has no per-process environment,\n * so the answer is always \"no\" there and ownership falls back to the port policy.\n */\nexport async function processCarriesServerId(pid: number, serverId: string): Promise<boolean> {\n if (serverId.length === 0)\n return false\n const needle = `HHOSTED_SERVER_ID=${serverId}`\n\n if (process.platform === 'linux') {\n try {\n const raw = await fs.promises.readFile(`/proc/${pid}/environ`, 'utf8')\n return raw.split('\\0').includes(needle)\n }\n catch {\n return false\n }\n }\n\n if (process.platform === 'darwin') {\n try {\n const { stdout } = await execFileAsync('ps', ['-p', String(pid), '-E', '-ww', '-o', 'command='], { timeout: 3000 })\n // Values with spaces are unquoted in this output, so the marker is matched as a word.\n return new RegExp(`(?:^|\\\\s)${needle}(?:\\\\s|$)`).test(stdout)\n }\n catch {\n return false\n }\n }\n\n return false\n}\n","import { execFile } from 'node:child_process'\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport process from 'node:process'\nimport { promisify } from 'node:util'\nimport { processCarriesServerId } from '#src/providers/proc'\n\nconst execFileAsync = promisify(execFile)\n\n/** What the panel actually spawned for an entry: the argv it has to recognize again. */\nexport interface SpawnInfo {\n command: string\n args: string[]\n cwd: string\n}\n\n/**\n * Splits a command line into words, undoing the quoting Windows put there. `CommandLine`\n * is the raw string from the spawn call, so `\"C:\\a b\\x.cmd\" /c` is one argv entry plus\n * two words, and a quoted argument that contains spaces has to come back as one word.\n *\n * Windows also *escapes* a quote inside an argument as `\\\"` (Node does this for any argv\n * containing a quote), so the escape is undone here — otherwise every argument with a\n * quote in it survives as a stray backslash and never matches what the panel spawned.\n */\nexport function splitCommandLine(line: string): string[] {\n const words: string[] = []\n let current = ''\n let quoted = false\n let started = false\n\n for (const char of line.trim()) {\n if (char === '\"') {\n quoted = !quoted\n // A quote is both a delimiter and proof that a (possibly empty) word exists.\n started = true\n continue\n }\n if (!quoted && /\\s/.test(char)) {\n if (started)\n words.push(current)\n current = ''\n started = false\n continue\n }\n current += char\n started = true\n }\n\n if (started)\n words.push(current)\n return words\n}\n\nfunction comparable(target: string): string {\n // Quotes around a whole word are stripped; whitespace never is. `SpawnInfo.args` is the\n // argv `spawn` was handed, where a whitespace argument is still an argument, so trimming\n // here would make it compare equal to a missing one. A `\\\"` is Windows' escape for a\n // quote inside an argument, and the word it sits in is compared against the raw argv.\n const value = target.replace(/^\"(.*)\"$/, '$1').replace(/\\\\(?=\")/g, '')\n return process.platform === 'win32' ? value.toLowerCase() : value\n}\n\n/**\n * Literal comparison for an argument: the same text, modulo the case folding Windows needs\n * and the quoting a command line shuffles around.\n *\n * Deliberately *not* `sameWord`: folding an argument to its basename would let this\n * entry's `/srv/web/build/server.js` equal a stranger's `/tmp/evil/build/server.js`, and a\n * match here is what `reclaim` kills. Nor is it a plain string equality: reading an argv\n * back out of a Windows `CommandLine` cannot preserve quotes exactly — the OS escapes an\n * argument's own quote as `\\\"` and strips the structural ones — so quotes and backslashes\n * are dropped from both sides. That leaves the arguments' actual text, which is what the\n * match is about.\n */\nfunction sameArg(a: string, b: string): boolean {\n const normalize = (value: string): string => comparable(value).replace(/[\"\\\\]/g, '')\n return normalize(a) === normalize(b)\n}\n\n/** The same file spelled differently (`node`, `node.exe`, a relative path) compares equal. */\nfunction sameWord(a: string, b: string): boolean {\n const left = comparable(a)\n const right = comparable(b)\n if (left === right || path.basename(left) === path.basename(right))\n return true\n return path.extname(b) === '' && path.basename(left, path.extname(left)) === right\n}\n\n/**\n * True when the argv a process is running is the entry's own: the image must match where\n * `spawn` would have looked it up — the image Path, or the first word of the command line\n * — and the words after it must open with the entry's args, compared literally. So\n * `spawn --port 4000` also covers `spawn -p 4000 --extra`, which is what a self-restarting\n * wrapper does, while `/tmp/evil/server.js` never covers `/srv/web/server.js`.\n *\n * `words` must already be the process's own argv. A command-line *string* is only correct\n * on Windows, where the OS hands one out; `/proc/<pid>/cmdline` quotes are literal bytes\n * of an argument, so re-joining that argv into one string corrupts it.\n */\nexport function matchesSpawn(info: { words: string[], imagePath?: string | null }, spawn: SpawnInfo): boolean {\n const { words } = info\n const first = words[0] ?? ''\n\n // The image may be the first word, or absent from the command line entirely — a shim or\n // an interpreter reports its own image while the argv still carries what we passed.\n const imageMatches = sameWord(first, spawn.command)\n || (info.imagePath != null && info.imagePath !== '' && sameWord(info.imagePath, spawn.command))\n if (!imageMatches)\n return false\n\n // `>` rather than `>=`: a process has to have *more* words than we have args, so an\n // argument can never be satisfied by a word that is not there.\n const offset = sameWord(first, spawn.command) ? 1 : 0\n if (words.length < spawn.args.length + offset)\n return false\n\n return spawn.args.every((arg, index) => sameArg(words[index + offset]!, arg))\n}\n\n/** The argv of a pid on the platforms whose process table can answer it; null otherwise. */\nexport async function processArgv(pid: number): Promise<string[] | null> {\n if (process.platform === 'win32')\n return null\n\n if (process.platform === 'linux') {\n try {\n const raw = await fs.promises.readFile(`/proc/${pid}/cmdline`)\n const argv = raw.toString('utf8').split('\\0').filter(part => part.length > 0)\n return argv.length > 0 ? argv : null\n }\n catch {\n return null\n }\n }\n\n try {\n const { stdout } = await execFileAsync('ps', ['-p', String(pid), '-ww', '-o', 'command='], { timeout: 3000 })\n // macOS receives one line and has to split it back into words here. `ps` joins argv\n // with spaces and quotes none of them, so an argument that itself contains a space\n // cannot be told from two arguments — that entry then fails to match and blocks,\n // which is the safe direction to be wrong in.\n const words = splitCommandLine(stdout.trim())\n return words.length > 0 ? words : null\n }\n catch {\n return null\n }\n}\n\nconst windowsFilter = /^\\d+$/\n\n/**\n * The first two rows of `ConvertTo-Csv` output: the header and the first record. PowerShell\n * quotes and doubles its way around CSV, so `a,\"b\"\"c\"` is three fields with the second\n * reading `b\"c`.\n */\nfunction parseCsvRows(output: string): [string[] | null, string[] | null] {\n const rows: string[][] = []\n let row: string[] = []\n let field = ''\n let quoted = false\n\n for (let index = 0; index < output.length; index++) {\n const char = output[index]!\n if (quoted) {\n if (char !== '\"') {\n field += char\n continue\n }\n if (output[index + 1] === '\"') {\n field += '\"'\n index++\n continue\n }\n quoted = false\n continue\n }\n\n if (char === '\"') {\n quoted = true\n continue\n }\n if (char === ',') {\n row.push(field)\n field = ''\n continue\n }\n if (char === '\\n') {\n row.push(field.replace(/\\r$/, ''))\n rows.push(row)\n row = []\n field = ''\n continue\n }\n field += char\n }\n\n if (field.length > 0 || row.length > 0)\n rows.push([...row, field.replace(/\\r$/, '')])\n\n return [rows[0] ?? null, rows[1] ?? null]\n}\n\n/**\n * `Win32_Process` for one pid, or null when it cannot be read.\n *\n * The whole round trip — launching PowerShell, loading the CIM provider, serializing —\n * costs seconds on a cold runner, so the timeout is generous and the result is converted\n * to CSV rather than JSON: CSV survives a value that contains a quote or a newline, which\n * an argv legitimately can.\n */\nexport async function windowsProcessInfo(pid: number): Promise<{ commandLine: string, imagePath: string | null } | null> {\n if (!windowsFilter.test(String(pid)))\n return null\n\n try {\n const script = `Get-CimInstance Win32_Process -Filter \"ProcessId=${pid}\" | Select-Object CommandLine,ExecutablePath | ConvertTo-Csv -NoTypeInformation`\n const { stdout } = await execFileAsync('powershell', ['-NoProfile', '-NonInteractive', '-Command', script], { timeout: 20000, windowsHide: true })\n const [headers, values] = parseCsvRows(stdout)\n if (!headers || !values)\n return null\n\n const commandLineIndex = headers.indexOf('CommandLine')\n const imageIndex = headers.indexOf('ExecutablePath')\n const commandLine = commandLineIndex >= 0 ? values[commandLineIndex] : undefined\n if (commandLine === undefined || commandLine.length === 0)\n return null\n\n const imagePath = imageIndex >= 0 ? values[imageIndex] : undefined\n return { commandLine, imagePath: imagePath && imagePath.length > 0 ? imagePath : null }\n }\n catch {\n return null\n }\n}\n\n/**\n * Which of these pids look like this entry's own detached successor. The environment\n * marker is authoritative where the platform can read it; otherwise the answer rests on\n * the entry's own argv, which is the only signal a detached successor is obliged to keep\n * — and the only one Windows exposes at all.\n */\nexport async function identifyHolders(serverId: string, spawn: SpawnInfo, pids: number[]): Promise<number[]> {\n const found: number[] = []\n\n for (const pid of pids) {\n if (await processCarriesServerId(pid, serverId)) {\n found.push(pid)\n continue\n }\n\n // An empty argv proves nothing, so a marker is the only way in without it.\n if (spawn.args.length === 0)\n continue\n\n if (process.platform === 'win32') {\n const info = await windowsProcessInfo(pid)\n if (info && matchesSpawn({ words: splitCommandLine(info.commandLine), imagePath: info.imagePath }, spawn))\n found.push(pid)\n continue\n }\n\n const argv = await processArgv(pid)\n if (argv !== null && matchesSpawn({ words: argv }, spawn))\n found.push(pid)\n }\n\n return found\n}\n","import type { ChildProcess } from 'node:child_process'\nimport { execFile, spawn } from 'node:child_process'\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport process from 'node:process'\nimport { promisify } from 'node:util'\nimport { projectDir } from '#src/helpers/paths'\n\nconst execFileAsync = promisify(execFile)\n\nexport interface SpawnSpec {\n command: string\n args: string[]\n cwd: string\n env: Record<string, string>\n}\n\n/** Windows resolves a project-local bin to one of these shims, not the bare name. */\nconst WINDOWS_SHIM_EXTENSIONS = ['.cmd', '.exe', '.bat', '.ps1']\n\n/**\n * Resolves a bare command through the entry's own directory and the project's\n * `node_modules/.bin` first, so a server installed as a project dependency is\n * found even when the launcher's PATH has no pnpm-injected bin dir.\n */\nexport function resolveCommand(command: string, ...searchDirs: string[]): string {\n if (command.includes('/') || command.includes('\\\\'))\n return command\n\n const candidates = process.platform === 'win32' && path.extname(command) === ''\n ? [command, ...WINDOWS_SHIM_EXTENSIONS.map(extension => `${command}${extension}`)]\n : [command]\n\n for (const dir of searchDirs) {\n for (const candidate of candidates) {\n const local = path.join(dir, 'node_modules', '.bin', candidate)\n if (fs.existsSync(local))\n return local\n }\n }\n\n return command\n}\n\n/** Relative entry paths belong to the project that launched the panel. */\nexport function resolveCwd(cwd: string, base: string = projectDir): string {\n return path.resolve(base, cwd)\n}\n\n/** Node cannot spawn a Windows `.cmd`/`.bat` shim without a shell (EINVAL). */\nexport function needsShell(command: string): boolean {\n return process.platform === 'win32' && /\\.(?:cmd|bat)$/i.test(command)\n}\n\nexport function spawnManaged(spec: SpawnSpec): ChildProcess {\n return spawn(spec.command, spec.args, {\n cwd: spec.cwd,\n env: { ...process.env, ...spec.env },\n // Own process group: a stop can signal the whole tree with one kill(-pid).\n detached: true,\n stdio: ['ignore', 'pipe', 'pipe'],\n shell: needsShell(spec.command),\n windowsHide: true,\n })\n}\n\nexport interface TerminateOptions {\n signal: NodeJS.Signals\n killGroup: boolean\n graceMs: number\n}\n\n/** SIGTERM (default) to the child, escalating to SIGKILL after the grace period. */\nexport async function terminate(child: ChildProcess, options: TerminateOptions): Promise<'exited' | 'force-killed'> {\n if (child.exitCode !== null || child.signalCode !== null)\n return 'exited'\n\n const exited = waitForExit(child, options.graceMs)\n signalChild(child, options.signal, options.killGroup)\n\n if (await exited)\n return 'exited'\n\n signalChild(child, 'SIGKILL', options.killGroup)\n await waitForExit(child, 2000)\n return 'force-killed'\n}\n\n/**\n * Windows has no process groups and no SIGTERM: `taskkill /T` walks the tree and\n * `/F` is the only reliable way to stop a console process.\n */\nexport async function killTreeWindows(pid: number): Promise<void> {\n try {\n await execFileAsync('taskkill', ['/pid', String(pid), '/T', '/F'], { timeout: 5000 })\n }\n catch {\n // Already gone, or taskkill is unavailable.\n }\n}\n\nfunction signalChild(child: ChildProcess, signal: NodeJS.Signals, killGroup: boolean): void {\n const pid = child.pid\n if (pid === undefined)\n return\n signalPid(pid, signal, killGroup)\n}\n\nfunction signalPid(pid: number, signal: NodeJS.Signals, killGroup: boolean): void {\n if (process.platform === 'win32') {\n if (killGroup) {\n void killTreeWindows(pid)\n }\n else {\n try {\n process.kill(pid, signal)\n }\n catch {\n // already exited\n }\n }\n return\n }\n\n if (killGroup) {\n try {\n process.kill(-pid, signal)\n return\n }\n catch {\n // group already gone, fall through to the single pid\n }\n }\n\n try {\n process.kill(pid, signal)\n }\n catch {\n // already exited\n }\n}\n\nfunction alive(pid: number): boolean {\n try {\n process.kill(pid, 0)\n return true\n }\n catch {\n return false\n }\n}\n\n/**\n * The same shutdown a supervised child gets, for a process we adopted instead of\n * spawned: a detached successor is not our child, so it is signalled by pid (and\n * by process group when asked) and its exit is polled rather than awaited.\n */\nexport async function terminatePid(pid: number, options: TerminateOptions): Promise<'exited' | 'force-killed'> {\n if (!alive(pid))\n return 'exited'\n\n signalPid(pid, options.signal, options.killGroup)\n\n const deadline = Date.now() + Math.max(0, options.graceMs)\n while (Date.now() < deadline && alive(pid))\n await delay(50)\n if (!alive(pid))\n return 'exited'\n\n signalPid(pid, 'SIGKILL', options.killGroup)\n const hardDeadline = Date.now() + 2000\n while (Date.now() < hardDeadline && alive(pid))\n await delay(50)\n return 'force-killed'\n}\n\nfunction delay(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms))\n}\n\nfunction waitForExit(child: ChildProcess, timeoutMs: number): Promise<boolean> {\n if (child.exitCode !== null || child.signalCode !== null)\n return Promise.resolve(true)\n if (timeoutMs <= 0)\n return Promise.resolve(false)\n\n return new Promise((resolve) => {\n const timer = setTimeout(() => {\n child.removeListener('exit', onExit)\n resolve(false)\n }, timeoutMs)\n\n function onExit(): void {\n clearTimeout(timer)\n resolve(true)\n }\n\n child.once('exit', onExit)\n })\n}\n","import type { ServerConfig } from '#src/shared/contracts'\n\n/**\n * Orders servers so every dependency comes before its dependents.\n *\n * Cycles and unknown ids are ignored here — the config store reports them as\n * config errors — so this can never throw and stall supervision.\n */\nexport function orderByDependencies(servers: ServerConfig[]): ServerConfig[] {\n const byId = new Map(servers.map(server => [server.id, server]))\n const ordered: ServerConfig[] = []\n const visited = new Set<string>()\n\n const visit = (server: ServerConfig): void => {\n if (visited.has(server.id))\n return\n visited.add(server.id)\n for (const dependency of server.dependsOn) {\n const target = byId.get(dependency)\n if (target && target.id !== server.id)\n visit(target)\n }\n ordered.push(server)\n }\n\n for (const server of servers) visit(server)\n return ordered\n}\n\n/** The transitive dependencies of a server, nearest first. */\nexport function dependenciesOf(server: ServerConfig, servers: ServerConfig[]): ServerConfig[] {\n const byId = new Map(servers.map(entry => [entry.id, entry]))\n const found: ServerConfig[] = []\n const seen = new Set<string>()\n\n const walk = (current: ServerConfig): void => {\n for (const dependency of current.dependsOn) {\n if (seen.has(dependency))\n continue\n seen.add(dependency)\n const target = byId.get(dependency)\n if (!target)\n continue\n found.push(target)\n walk(target)\n }\n }\n\n walk(server)\n return found\n}\n\n/** Dependents that must stop before this server does. */\nexport function dependentsOf(server: ServerConfig, servers: ServerConfig[]): ServerConfig[] {\n return servers.filter(entry => entry.id !== server.id && dependenciesOf(entry, servers).some(d => d.id === server.id))\n}\n","import type { Buffer } from 'node:buffer'\nimport type { LogLine, LogStream } from '#src/shared/contracts'\n\n/** A partial line past this size is emitted rather than buffered further. */\nconst MAX_PENDING_CHARS = 64 * 1024\n\n/** Fixed-capacity line buffer; oldest lines are dropped first. */\nexport class LogBuffer {\n private lines: LogLine[] = []\n\n constructor(private capacity: number) {}\n\n push(line: LogLine): void {\n this.lines.push(line)\n if (this.lines.length > this.capacity)\n this.lines.splice(0, this.lines.length - this.capacity)\n }\n\n extend(lines: LogLine[]): void {\n for (const line of lines) this.push(line)\n }\n\n list(limit?: number): LogLine[] {\n if (limit === undefined || limit >= this.lines.length)\n return [...this.lines]\n return this.lines.slice(-limit)\n }\n\n clear(): void {\n this.lines = []\n }\n\n get size(): number {\n return this.lines.length\n }\n}\n\n/**\n * Splits a chunk into complete lines, keeping a trailing partial line buffered:\n * a child writing \"hel\" then \"lo\\n\" must surface one line, not two.\n */\nexport class LineSplitter {\n private pending = ''\n\n constructor(private readonly emit: (stream: LogStream, text: string) => void) {}\n\n push(stream: LogStream, chunk: string | Buffer): void {\n this.pending += chunk.toString()\n const parts = this.pending.split('\\n')\n this.pending = parts.pop() ?? ''\n for (const part of parts) this.emit(stream, part.replace(/\\r$/, ''))\n // A child that never sends a newline (a progress bar, one minified JSON blob)\n // must not grow this string without bound: past the cap it is emitted in pieces.\n while (this.pending.length > MAX_PENDING_CHARS) {\n this.emit(stream, this.pending.slice(0, MAX_PENDING_CHARS))\n this.pending = this.pending.slice(MAX_PENDING_CHARS)\n }\n }\n\n flush(stream: LogStream): void {\n if (this.pending.length === 0)\n return\n this.emit(stream, this.pending.replace(/\\r$/, ''))\n this.pending = ''\n }\n}\n","import type { ChildProcess } from 'node:child_process'\nimport type { ServerConfig } from '#src/config/schema'\nimport type { ConfigStore } from '#src/config/store'\nimport type { TemplateVars } from '#src/helpers/template'\nimport type { SpawnInfo } from '#src/providers/identity'\nimport type { ControlEndpoint } from '#src/services/control-server'\nimport type { EventHub } from '#src/services/events'\nimport type { HistoryStore } from '#src/services/history'\nimport type { HostMonitor } from '#src/services/host-monitor'\nimport type { LogFiles } from '#src/services/log-files'\nimport type { NotificationReason, NotificationService } from '#src/services/notifications'\nimport type {\n AppState,\n FreePortResult,\n HealthState,\n LogLine,\n LogStream,\n PortState,\n ProcessResources,\n ServerStatus,\n ServerView,\n} from '#src/shared/contracts'\nimport { spawn } from 'node:child_process'\nimport os from 'node:os'\nimport path from 'node:path'\nimport process from 'node:process'\nimport { computeBackoff } from '#src/helpers/backoff'\nimport { bindHost, displayHost, lanAddress } from '#src/helpers/bind'\nimport { expandEnvList, expandEnvRecord, loadEnvFile, resolveEnvFilePath } from '#src/helpers/env-file'\nimport { logger } from '#src/helpers/logger'\nimport { dataRoot, projectDir } from '#src/helpers/paths'\nimport { resolveRecord, resolveTemplates } from '#src/helpers/template'\nimport { probeHealth } from '#src/providers/health-check'\nimport { identifyHolders } from '#src/providers/identity'\nimport { isPortFree, isProcessAlive, killPortHolders, listPortHolders, probePort, terminatePids } from '#src/providers/port'\nimport { ProcessSampler } from '#src/providers/proc'\nimport { resolveCommand, resolveCwd, spawnManaged, terminate, terminatePid } from '#src/providers/process'\nimport { dependenciesOf, orderByDependencies } from '#src/services/dependencies'\nimport { LineSplitter, LogBuffer } from '#src/services/log-buffer'\n\nexport interface SupervisorOptions {\n configPath: string\n /** Live listener info, mutated by the control server when it rebinds. */\n control: ControlEndpoint\n /** Injected so SSE state frames carry the same view the API serves. */\n buildState: (views: ServerView[]) => AppState\n history: HistoryStore\n logFiles: LogFiles\n notifications: NotificationService\n hostMonitor: HostMonitor\n}\n\n/** Uptime/crash counters are reported over this window. */\nconst HISTORY_WINDOW_MS = 24 * 60 * 60 * 1000\nconst HISTORY_CACHE_MS = 5000\n\nexport interface StartResult {\n ok: boolean\n error?: string\n}\n\n/** What the port preflight found: nothing in the way, our own successor, or a blocker. */\ntype PreflightConflict\n = | { kind: 'free' }\n | { kind: 'adopt', pid: number }\n | { kind: 'blocked', error: string }\n\ninterface Entry {\n config: ServerConfig\n status: ServerStatus\n health: HealthState\n portState: PortState\n child: ChildProcess | null\n pid: number | null\n startedAt: number | null\n exitCode: number | null\n exitSignal: string | null\n restarts: number\n lastError: string | null\n nextRetryAt: number | null\n retryTimer: NodeJS.Timeout | null\n healthFailures: number\n unhealthySince: number | null\n lastProbeAt: number\n lastOccupancyProbeAt: number\n probing: boolean\n /** The running process is a detached successor we adopted, not a child we spawned. */\n adopted: boolean\n /** A start is in flight (set synchronously, unlike `status`). */\n starting: boolean\n stopping: boolean\n bootstrapDone: boolean\n logs: LogBuffer\n responseMs: number | null\n resources: ProcessResources | null\n resourcesSampledAt: number\n historyCache: { revision: number, at: number, summary: ServerView['history'] } | null\n}\n\nconst TICK_INTERVAL_MS = 1000\nconst PORT_STATE_INTERVAL_MS = 10000\nconst PORT_RELEASE_RECHECK_MS = 300\nconst RESOURCE_SAMPLE_INTERVAL_MS = 5000\n\nfunction delay(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms))\n}\n\n/** The placeholder set every server entry can use; shared with path resolvers. */\nexport function serverTemplateVars(config: ServerConfig): TemplateVars {\n return {\n id: config.id,\n label: config.label ?? config.id,\n port: config.port ?? '',\n host: bindHost(config.bind),\n displayHost: displayHost(config.bind),\n bind: config.bind,\n lanIp: lanAddress() ?? '127.0.0.1',\n cwd: resolveCwd(config.cwd),\n projectDir,\n dataRoot,\n home: os.homedir(),\n }\n}\n\nexport class Supervisor {\n private readonly sampler = new ProcessSampler()\n private readonly entries = new Map<string, Entry>()\n private readonly tickTimer: NodeJS.Timeout\n private disposed = false\n private lastStateSignature = ''\n\n constructor(\n private readonly store: ConfigStore,\n private readonly hub: EventHub,\n private readonly options: SupervisorOptions,\n ) {\n this.sync()\n this.store.onChange(() => this.sync())\n // A throw inside the tick must not become an unhandled rejection: on Node 24\n // that ends the process, and this timer is what keeps every server watched.\n this.tickTimer = setInterval(() => {\n void this.tick().catch((error: unknown) => logger.error('the supervisor tick failed', error))\n }, TICK_INTERVAL_MS)\n this.tickTimer.unref()\n }\n\n getState(): AppState {\n return this.options.buildState(this.views())\n }\n\n views(): ServerView[] {\n return [...this.entries.values()].map(entry => this.view(entry))\n }\n\n logLines(id: string, limit?: number): LogLine[] {\n return this.entries.get(id)?.logs.list(limit) ?? []\n }\n\n async startAll(options: { autostartOnly?: boolean } = {}): Promise<void> {\n const targets = [...this.entries.values()]\n .filter(entry => !options.autostartOnly || entry.config.autostart)\n .map(entry => entry.config)\n // Dependencies first; levels are still started concurrently.\n for (const config of orderByDependencies(targets)) {\n await this.start(config.id)\n }\n }\n\n async stopAll(): Promise<void> {\n const targets = orderByDependencies([...this.entries.values()].map(entry => entry.config)).reverse()\n for (const config of targets) {\n await this.stop(config.id)\n }\n }\n\n async start(id: string, options: { retry?: boolean } = {}): Promise<StartResult> {\n const entry = this.entries.get(id)\n if (!entry)\n return { ok: false, error: `unknown server \"${id}\"` }\n if (!entry.config.enabled)\n return { ok: false, error: `server \"${id}\" is disabled` }\n if (entry.status === 'running' || entry.status === 'starting' || entry.starting)\n return { ok: true }\n if (entry.stopping)\n return { ok: false, error: `server \"${id}\" is stopping` }\n\n // Set before the first await: two overlapping `start()` calls (a click and a\n // retry timer, say) would otherwise both reach `spawnEntry` and orphan one.\n entry.starting = true\n this.clearRetry(entry)\n if (!options.retry) {\n entry.restarts = 0\n entry.healthFailures = 0\n entry.unhealthySince = null\n }\n\n try {\n await this.startDependencies(entry)\n // A stop that arrived while we were waiting must win: the process must not\n // start and then be reported as stopped.\n if (entry.stopping || this.disposed)\n return { ok: false, error: `server \"${id}\" is stopping` }\n\n entry.lastError = null\n entry.status = 'starting'\n entry.health = entry.config.health.enabled ? 'unknown' : 'disabled'\n this.publishServer(entry)\n\n await this.runBootstrap(entry)\n if (entry.stopping)\n return { ok: false, error: `server \"${id}\" is stopping` }\n if (this.disposed)\n return { ok: false, error: 'supervisor is shutting down' }\n\n const conflict = await this.preflight(entry)\n if (conflict.kind === 'blocked')\n return { ok: false, error: conflict.error }\n if (conflict.kind === 'adopt')\n return this.adoptEntry(entry, conflict.pid)\n\n return this.spawnEntry(entry)\n }\n finally {\n entry.starting = false\n }\n }\n\n async stop(id: string): Promise<StartResult> {\n const entry = this.entries.get(id)\n if (!entry)\n return { ok: false, error: `unknown server \"${id}\"` }\n return this.stopEntry(entry)\n }\n\n async restart(id: string): Promise<StartResult> {\n await this.stop(id)\n return this.start(id)\n }\n\n /**\n * Frees the port this entry wants, by asking whatever listens on it to leave.\n *\n * The pid is never taken from a message: holders are listed again here, and a\n * listener this panel supervises is refused rather than killed — a port held by\n * a sibling entry is a configuration mistake, not a stray process. That also\n * keeps a stale `(pid 1234)` in an old banner from killing a recycled pid.\n */\n async freePort(id: string): Promise<FreePortResult & { error?: string }> {\n const entry = this.entries.get(id)\n const empty: FreePortResult & { error?: string } = { ok: false, port: null, terminated: [], forced: [], skipped: [], free: false }\n if (!entry)\n return { ...empty, error: `unknown server \"${id}\"` }\n\n const port = entry.config.port\n if (port === null)\n return { ...empty, error: `server \"${id}\" has no port configured` }\n if (entry.starting || entry.stopping)\n return { ...empty, port, error: `server \"${id}\" is busy — try again in a moment` }\n\n const { ours, foreign } = await this.portHolders(port)\n const holders = [...ours, ...foreign]\n\n if (holders.length === 0)\n return { ...empty, port, error: `nothing is listening on port ${port} any more` }\n if (foreign.length === 0) {\n const reason = `port ${port} is held by pid ${ours.join(', ')}, which this panel supervises — stop that server instead`\n return { ...empty, port, error: reason, skipped: ours }\n }\n\n this.log(entry, 'system', `freeing port ${port}: asking pid ${foreign.join(', ')} to stop`)\n const { stopped, forced } = await terminatePids(foreign, { graceMs: entry.config.stop.graceMs })\n if (forced.length > 0)\n this.log(entry, 'system', `pid ${forced.join(', ')} ignored SIGTERM and was killed`)\n\n // A held socket can take a moment to go away, so the port decides the outcome.\n const free = await this.waitForPortRelease(entry, port)\n if (free) {\n // The reason the entry was blocked is gone, so the banner goes too.\n if (entry.status === 'conflict') {\n entry.status = 'stopped'\n entry.lastError = null\n }\n entry.portState = 'free'\n this.log(entry, 'system', `port ${port} is free${ours.length > 0 ? ` (pid ${ours.join(', ')} left untouched)` : ''}`)\n this.publishServer(entry)\n }\n else {\n entry.lastError = `port ${port} is still in use after killing pid ${foreign.join(', ')}`\n this.log(entry, 'system', entry.lastError)\n this.publishServer(entry)\n }\n\n return { ok: true, port, terminated: stopped, forced, skipped: ours, free }\n }\n\n /**\n * Splits the port's listeners into the processes this panel owns and everyone\n * else. Only the foreign half may ever be signalled — a port held by a sibling\n * is a config mistake, not a stray process. That also keeps a stale `(pid 1234)`\n * in an old banner from killing a recycled pid.\n */\n private async portHolders(port: number): Promise<{ ours: number[], foreign: number[] }> {\n const supervised = this.supervisedPids()\n const holders = await listPortHolders(port)\n return {\n ours: holders.filter(pid => supervised.has(pid)),\n foreign: holders.filter(pid => !supervised.has(pid)),\n }\n }\n\n /** Pids of the child processes this panel owns, plus itself. */\n private supervisedPids(): Set<number> {\n const pids = new Set<number>([process.pid])\n for (const entry of this.entries.values()) {\n if (entry.pid !== null)\n pids.add(entry.pid)\n }\n return pids\n }\n\n /** The port's own answer, with the same short recheck the preflight uses. */\n private async waitForPortRelease(entry: Entry, port: number): Promise<boolean> {\n const freeOnAll = async (): Promise<boolean> => {\n const results = await Promise.all(this.occupancyHosts(entry).map(host => isPortFree(port, host)))\n return results.every(Boolean)\n }\n if (await freeOnAll())\n return true\n await delay(PORT_RELEASE_RECHECK_MS)\n return freeOnAll()\n }\n\n clearLogs(id: string): void {\n const entry = this.entries.get(id)\n if (!entry)\n return\n entry.logs.clear()\n this.publishServer(entry)\n }\n\n async dispose(): Promise<void> {\n this.disposed = true\n clearInterval(this.tickTimer)\n for (const entry of this.entries.values()) this.clearRetry(entry)\n await Promise.all([...this.entries.values()].map(entry => this.stopEntry(entry)))\n }\n\n /**\n * Starts whatever this server depends on and waits for it to accept\n * connections. A dependency that refuses to come up is logged and skipped\n * rather than blocking the dependent forever.\n */\n private async startDependencies(entry: Entry): Promise<void> {\n if (entry.config.dependsOn.length === 0)\n return\n\n for (const dependency of dependenciesOf(entry.config, this.store.servers)) {\n const target = this.entries.get(dependency.id)\n if (!target || !dependency.enabled)\n continue\n if (target.status === 'running' || target.child !== null)\n continue\n\n this.log(entry, 'system', `starting dependency \"${dependency.id}\" first`)\n await this.start(dependency.id)\n\n const deadline = Date.now() + dependency.health.startTimeoutMs\n const isReady = (): boolean => {\n const status = this.statusOf(target)\n if (status !== 'running')\n return false\n // A dependency that is listening but failing its check is not ready.\n return target.health !== 'unhealthy'\n }\n\n while (!isReady() && Date.now() < deadline) await delay(250)\n\n if (!isReady()) {\n const status = this.statusOf(target)\n this.log(entry, 'system', `dependency \"${dependency.id}\" is ${status}/${target.health} — starting anyway`)\n }\n }\n }\n\n /** Read through a method so TypeScript does not carry a stale narrowing across awaits. */\n private statusOf(entry: Entry): ServerStatus {\n return entry.status\n }\n\n /**\n * Summaries are rebuilt when history changes or every few seconds, because\n * `view()` runs on every state frame and the window math is O(events).\n */\n private summarizeHistory(entry: Entry): ServerView['history'] {\n const now = Date.now()\n const revision = this.options.history.revision\n const cached = entry.historyCache\n if (cached !== null && cached.revision === revision && now - cached.at < HISTORY_CACHE_MS)\n return cached.summary\n\n const runningSince = entry.child !== null && entry.startedAt !== null ? entry.startedAt : null\n const summary = this.options.history.summarize(entry.config.id, HISTORY_WINDOW_MS, now, runningSince)\n entry.historyCache = { revision, at: now, summary }\n return summary\n }\n\n private notify(entry: Entry, reason: NotificationReason, detail: string): void {\n this.options.notifications.notify({\n serverId: entry.config.id,\n label: entry.config.label ?? entry.config.id,\n reason,\n detail,\n })\n }\n\n private async stopEntry(entry: Entry): Promise<StartResult> {\n this.clearRetry(entry)\n entry.nextRetryAt = null\n\n // Set first: a start that is still bootstrapping (no child yet) checks this\n // after every await and aborts, instead of spawning behind our back.\n entry.stopping = true\n\n if (entry.child === null && entry.pid === null) {\n entry.stopping = false\n entry.status = 'stopped'\n this.publishServer(entry)\n return { ok: true }\n }\n\n entry.status = 'stopping'\n this.publishServer(entry)\n\n // An adopted successor is not our child: same shutdown, signalled by pid.\n const outcome = entry.child === null && entry.pid !== null\n ? await terminatePid(entry.pid, entry.config.stop)\n : await terminate(entry.child!, entry.config.stop)\n if (outcome === 'force-killed')\n this.log(entry, 'system', 'force-killed after grace period')\n\n const { port, stop } = entry.config\n if (stop.killPortHolders && port !== null) {\n // Only a stranger: a port held by a server this panel supervises is a config\n // mistake, not a leftover, and is never killed from here.\n const leftover = await killPortHolders(port, this.supervisedPids())\n if (leftover.length > 0)\n this.log(entry, 'system', `port ${port} was still held by pid ${leftover.join(', ')} — killed`)\n }\n\n entry.stopping = false\n entry.child = null\n entry.pid = null\n entry.adopted = false\n entry.status = 'stopped'\n this.log(entry, 'system', 'stopped')\n this.publishServer(entry)\n return { ok: true }\n }\n\n private createEntry(config: ServerConfig): Entry {\n return {\n config,\n status: 'stopped',\n health: config.health.enabled ? 'unknown' : 'disabled',\n portState: 'unknown',\n child: null,\n pid: null,\n startedAt: null,\n exitCode: null,\n exitSignal: null,\n restarts: 0,\n lastError: null,\n nextRetryAt: null,\n retryTimer: null,\n healthFailures: 0,\n unhealthySince: null,\n lastProbeAt: 0,\n lastOccupancyProbeAt: 0,\n probing: false,\n adopted: false,\n starting: false,\n stopping: false,\n bootstrapDone: !config.bootstrap,\n logs: new LogBuffer(config.logBufferLines),\n responseMs: null,\n resources: null,\n resourcesSampledAt: 0,\n historyCache: null,\n }\n }\n\n private sync(): void {\n const wanted = new Map(this.store.servers.map(server => [server.id, server]))\n\n for (const [id, entry] of [...this.entries]) {\n const config = wanted.get(id)\n if (!config) {\n this.entries.delete(id)\n void this.stopEntry(entry).catch((error: unknown) => {\n logger.error(`could not stop the removed server ${id}`, error)\n })\n continue\n }\n const bufferChanged = entry.config.logBufferLines !== config.logBufferLines\n entry.config = config\n if (bufferChanged) {\n const kept = entry.logs.list(config.logBufferLines)\n entry.logs = new LogBuffer(config.logBufferLines)\n entry.logs.extend(kept)\n }\n if (!config.enabled && this.isActive(entry)) {\n void this.stopEntry(entry).catch((error: unknown) => {\n logger.error(`could not stop the disabled server ${id}`, error)\n })\n }\n }\n\n for (const [id, config] of wanted) {\n if (!this.entries.has(id))\n this.entries.set(id, this.createEntry(config))\n }\n\n this.publishState()\n }\n\n private isActive(entry: Entry): boolean {\n // An adopted successor has no child of ours but is very much running, so it has to\n // count here — otherwise disabling the entry would leave it serving.\n return entry.child !== null || entry.pid !== null || entry.adopted || entry.status === 'backoff'\n }\n\n /** Loopback first, then the configured address, so a custom bind is still probed. */\n private probeHosts(entry: Entry): string[] {\n const primary = '127.0.0.1'\n const configured = displayHost(entry.config.bind)\n return configured === primary ? [primary] : [primary, configured]\n }\n\n /**\n * Where a port can actually be observed for this entry. A server bound to a\n * specific address is not reachable on loopback, and a `lan` bind is reachable\n * there *and* on this machine's LAN address.\n */\n private occupancyHosts(entry: Entry): string[] {\n const configured = bindHost(entry.config.bind)\n const candidates = configured === '0.0.0.0'\n ? ['127.0.0.1', lanAddress() ?? '127.0.0.1']\n : [configured, '127.0.0.1']\n return [...new Set(candidates)]\n }\n\n /** True when the port accepts a connection on any of the entry's addresses. */\n private async portAccepts(entry: Entry, port: number, timeoutMs: number): Promise<boolean> {\n const results = await Promise.all(this.occupancyHosts(entry).map(host => probePort(host, port, timeoutMs)))\n return results.some(Boolean)\n }\n\n /**\n * The port holder that is this entry's own detached successor — a program that\n * restarted itself leaves a process behind, and that process is the *same server*,\n * not a stranger to kill.\n *\n * The environment marker is authoritative where the platform can read it (Linux,\n * macOS). Failing that — Windows has no per-process environment at all — the entry's\n * own argv answers, which a successor keeps unless it re-execs under a different\n * image. Ambiguity is not resolved by guessing: two holders that both look like this\n * entry means we do not know which one is ours, so we act on neither.\n */\n private async ownPortHolder(entry: Entry, holders: number[]): Promise<number | null> {\n const spawn = this.resolveSpawn(entry)\n const candidates = await identifyHolders(entry.config.id, spawn, holders)\n\n if (candidates.length > 1) {\n this.log(entry, 'system', `pid ${candidates.join(', ')} all look like this entry: refusing to guess which is ours, set onPortConflict to \"kill\" to clear the port anyway`)\n return null\n }\n\n return candidates[0] ?? null\n }\n\n private async preflight(entry: Entry): Promise<PreflightConflict> {\n const port = entry.config.port\n if (port === null)\n return { kind: 'free' }\n\n // A listener that was just closed can still complete a handshake for a few\n // milliseconds, which is exactly the window a fast restart lands in — so a\n // busy-looking port gets a second look before it is treated as a conflict.\n // The configured address first: a server bound to a LAN ip is not \"free\" just\n // because nothing holds it on loopback.\n const hosts = this.occupancyHosts(entry)\n const freeOnAll = async (): Promise<boolean> => {\n const results = await Promise.all(hosts.map(host => isPortFree(port, host)))\n return results.every(Boolean)\n }\n\n let free = await freeOnAll()\n if (!free) {\n await delay(PORT_RELEASE_RECHECK_MS)\n free = await freeOnAll()\n }\n\n entry.portState = free ? 'free' : 'in-use'\n if (free)\n return { kind: 'free' }\n\n // One split serves every policy: the supervised half is never a target, and the\n // foreign half is where our own detached successor hides.\n const { ours, foreign } = await this.portHolders(port)\n const holders = [...ours, ...foreign]\n const own = await this.ownPortHolder(entry, foreign)\n const suffix = holders.length > 0 ? ` (pid ${holders.join(', ')})` : ''\n\n // `kill` asks for the port outright: whoever holds it goes, this entry's own\n // successor included. It never touches our own process tree — a port held by\n // the panel or a sibling stays a config mistake, exactly as `follow`/`reclaim`.\n if (entry.config.onPortConflict === 'kill') {\n if (ours.length > 0) {\n entry.status = 'conflict'\n entry.lastError = `port ${port} is held by pid ${ours.join(', ')}, which this panel supervises — stop that server instead`\n this.log(entry, 'system', `${entry.lastError} — not starting (onPortConflict: kill)`)\n this.publishServer(entry)\n return { kind: 'blocked', error: entry.lastError }\n }\n\n if (foreign.length === 0) {\n // The probe says busy but no listener could be listed: let the start decide.\n this.log(entry, 'system', `port ${port} looks busy but no listener could be found — starting anyway`)\n return { kind: 'free' }\n }\n\n this.log(entry, 'system', `port ${port} is held by pid ${foreign.join(', ')} — onPortConflict: kill, stopping the holder`)\n const { forced } = await terminatePids(foreign, { graceMs: entry.config.stop.graceMs })\n if (forced.length > 0)\n this.log(entry, 'system', `pid ${forced.join(', ')} ignored SIGTERM and was killed`)\n\n await delay(PORT_RELEASE_RECHECK_MS)\n if (await freeOnAll()) {\n entry.portState = 'free'\n return { kind: 'free' }\n }\n entry.status = 'conflict'\n entry.lastError = `port ${port} is still in use after killing pid ${foreign.join(', ')}`\n this.log(entry, 'system', entry.lastError)\n this.publishServer(entry)\n return { kind: 'blocked', error: entry.lastError }\n }\n\n // A detached restart of this same server. Following it keeps whatever the program\n // set up (at the cost of its output, which belongs to whoever spawned it);\n // reclaiming the port buys back a fully supervised process instead.\n if (own !== null && entry.config.onPortConflict === 'follow')\n return { kind: 'adopt', pid: own }\n\n if (own !== null && entry.config.onPortConflict === 'reclaim') {\n this.log(entry, 'system', `port ${port} is held by pid ${own}, a detached restart of this entry — replacing it with a supervised process`)\n const { forced } = await terminatePids([own], { graceMs: entry.config.stop.graceMs })\n if (forced.length > 0)\n this.log(entry, 'system', `pid ${forced.join(', ')} ignored SIGTERM and was killed`)\n await delay(PORT_RELEASE_RECHECK_MS)\n if (await freeOnAll()) {\n entry.portState = 'free'\n return { kind: 'free' }\n }\n entry.status = 'conflict'\n entry.lastError = `port ${port} is still in use after replacing pid ${own}`\n this.log(entry, 'system', entry.lastError)\n this.publishServer(entry)\n return { kind: 'blocked', error: entry.lastError }\n }\n\n const hint = own !== null\n ? ` — pid ${own} is a detached restart of this entry: set onPortConflict to \"follow\" to adopt it, \"reclaim\" to replace it with a supervised process, or \"kill\" to stop whatever holds the port`\n : ''\n\n // `follow` and `reclaim` refine `block`: never a stranger's port.\n if (entry.config.onPortConflict !== 'warn') {\n entry.status = 'conflict'\n entry.lastError = `port ${port} is already in use${suffix}${hint}`\n this.log(entry, 'system', `${entry.lastError} — not starting (onPortConflict: ${entry.config.onPortConflict})`)\n this.publishServer(entry)\n return { kind: 'blocked', error: entry.lastError }\n }\n\n this.log(entry, 'system', `warning: port ${port} is already in use${suffix}${hint} — starting anyway`)\n return { kind: 'free' }\n }\n\n /**\n * Takes over a detached successor: no spawn, no duplicate. The pid is supervised\n * from here on (liveness, health probe, resources, stop), while its output stays\n * wherever it was redirected.\n */\n private adoptEntry(entry: Entry, pid: number): StartResult {\n entry.adopted = true\n entry.pid = pid\n entry.child = null\n entry.status = 'running'\n entry.health = entry.config.health.enabled ? 'unknown' : 'disabled'\n entry.startedAt = Date.now()\n entry.lastError = null\n this.log(entry, 'system', `adopted pid ${pid}: a detached restart of this entry is already serving port ${entry.config.port}`)\n this.publishServer(entry)\n return { ok: true }\n }\n\n /** An adopted successor disappeared: fall back to the normal lifecycle. */\n private handleAdoptedExit(entry: Entry): void {\n if (entry.pid !== null)\n this.sampler.forget(entry.pid)\n const ranForMs = entry.startedAt === null ? 0 : Date.now() - entry.startedAt\n entry.adopted = false\n entry.pid = null\n entry.resources = null\n entry.responseMs = null\n entry.exitCode = null\n entry.exitSignal = null\n this.options.history.record(entry.config.id, {\n type: 'exit',\n detail: 'adopted process exited',\n runtimeMs: ranForMs,\n })\n this.log(entry, 'system', `the adopted process is gone after ${Math.max(1, Math.round(ranForMs / 1000))}s`)\n this.afterExit(entry, 'adopted process exited', ranForMs, false)\n }\n\n private async runBootstrap(entry: Entry): Promise<void> {\n const spec = entry.config.bootstrap\n if (!spec || (spec.runOnce && entry.bootstrapDone))\n return\n\n const vars = this.buildVars(entry)\n const cwd = resolveCwd(entry.config.cwd)\n const args = resolveTemplates(spec.args, vars)\n this.log(entry, 'system', `bootstrap: ${spec.command} ${args.join(' ')}`)\n\n const splitter = new LineSplitter((_stream, text) => {\n if (text.trim().length > 0)\n this.log(entry, 'system', `[bootstrap] ${text}`)\n })\n\n const child = spawn(resolveCommand(spec.command, cwd, projectDir), args, {\n cwd,\n env: { ...process.env, ...resolveRecord(spec.env, vars) },\n stdio: ['ignore', 'pipe', 'pipe'],\n windowsHide: true,\n })\n child.stdout?.on('data', chunk => splitter.push('stdout', chunk))\n child.stderr?.on('data', chunk => splitter.push('stderr', chunk))\n\n const code = await new Promise<number | null>((resolve) => {\n const timer = setTimeout(() => {\n this.log(entry, 'system', `bootstrap timed out after ${spec.timeoutMs}ms`)\n try {\n child.kill('SIGKILL')\n }\n catch {\n // already gone\n }\n }, spec.timeoutMs)\n child.once('exit', (exitCode) => {\n clearTimeout(timer)\n resolve(exitCode)\n })\n child.once('error', (error) => {\n clearTimeout(timer)\n this.log(entry, 'system', `bootstrap failed: ${(error as Error).message}`)\n resolve(null)\n })\n })\n\n entry.bootstrapDone = true\n if (code === 0)\n this.log(entry, 'system', 'bootstrap finished')\n else if (code !== null)\n this.log(entry, 'system', `bootstrap exited with code ${code} — continuing anyway`)\n }\n\n /**\n * Everything `spawn` needs for an entry: the resolved image, the expanded argv, the\n * cwd and the environment. The argv is also what the preflight recognizes the entry's\n * own detached successor by, so spawn and identification must never resolve it twice\n * with two different rules.\n */\n private resolveSpawn(entry: Entry): SpawnInfo & { env: Record<string, string>, loggedArgs: string[] } {\n const vars = this.buildVars(entry)\n const cwd = resolveCwd(entry.config.cwd)\n const command = resolveCommand(entry.config.command, cwd, projectDir)\n\n // A machine-local env file is layered over the tracked config and also feeds\n // `${VAR}` in args/env, so secrets stay out of servers.config.json.\n let fileEnv: Record<string, string> = {}\n if (entry.config.envFile.length > 0) {\n const file = resolveEnvFilePath(entry.config.envFile, cwd)\n const loaded = loadEnvFile(file)\n if (loaded.error !== null)\n this.log(entry, 'system', `env file ${file} could not be read: ${loaded.error}`)\n else if (Object.keys(loaded.env).length > 0)\n this.log(entry, 'system', `env file ${file} (${Object.keys(loaded.env).length} vars)`)\n fileEnv = loaded.env\n }\n\n const expansionVars: Record<string, string | undefined> = { ...process.env, ...fileEnv }\n // A data env's value is a directory this entry owns, so it is normalized to one\n // absolute native path: a config writes `{projectDir}/data`, and a Windows run would\n // otherwise hand the process a mixed `D:\\…\\app/data` that Backups has to re-resolve.\n const dataEnvs = resolveRecord(entry.config.dataEnvs, vars)\n const env: Record<string, string> = {\n // `envFile` is the machine-local layer, so it overrides the tracked `env`.\n ...expandEnvRecord(resolveRecord(entry.config.env, vars), expansionVars),\n ...fileEnv,\n // Data envs win over `env`: their value is the directory that gets backed\n // up, so the process has to be pointed at exactly that path.\n ...expandEnvRecord(dataEnvs, expansionVars),\n HHOSTED_SERVER_ID: entry.config.id,\n HHOSTED_CONTROL_PORT: String(this.options.control.port),\n }\n for (const key of Object.keys(dataEnvs))\n env[key] = path.resolve(cwd, env[key]!)\n\n return {\n command,\n args: expandEnvList(resolveTemplates(entry.config.args, vars), expansionVars),\n env,\n cwd,\n // Logged *before* `${VAR}` expansion: an argument like `${API_TOKEN}` must not\n // land in the ring buffer, the rotated files, SSE or Telegram.\n loggedArgs: resolveTemplates(entry.config.args, vars),\n }\n }\n\n private spawnEntry(entry: Entry): StartResult {\n const { command, args, env, cwd, loggedArgs } = this.resolveSpawn(entry)\n\n this.log(entry, 'system', `start: ${command} ${loggedArgs.join(' ')}`)\n\n let child: ChildProcess\n try {\n child = spawnManaged({ command, args, cwd, env })\n }\n catch (error) {\n entry.status = 'crashed'\n entry.lastError = (error as Error).message\n this.log(entry, 'system', `spawn failed: ${entry.lastError}`)\n this.publishServer(entry)\n return { ok: false, error: entry.lastError }\n }\n\n entry.child = child\n entry.pid = child.pid ?? null\n entry.startedAt = Date.now()\n this.options.history.record(entry.config.id, {\n type: 'start',\n detail: `${command} ${args.join(' ')}`.trim(),\n })\n entry.exitCode = null\n entry.exitSignal = null\n entry.lastProbeAt = 0\n entry.healthFailures = 0\n entry.unhealthySince = null\n this.publishServer(entry)\n\n const stdout = new LineSplitter((stream, text) => this.log(entry, stream, text))\n const stderr = new LineSplitter((stream, text) => this.log(entry, stream, text))\n child.stdout?.on('data', chunk => stdout.push('stdout', chunk))\n child.stderr?.on('data', chunk => stderr.push('stderr', chunk))\n\n child.once('error', (error) => {\n entry.lastError = (error as Error).message\n this.log(entry, 'system', `process error: ${entry.lastError}`)\n stdout.flush('stdout')\n stderr.flush('stderr')\n this.handleExit(entry, child, null, null)\n })\n\n child.once('exit', (code, signal) => {\n stdout.flush('stdout')\n stderr.flush('stderr')\n this.handleExit(entry, child, code, signal)\n })\n\n void this.awaitReadiness(entry, child)\n return { ok: true }\n }\n\n /** One probe using the configured mode (TCP or HTTP), with timing. */\n private async probeEntryHealth(entry: Entry): Promise<{ healthy: boolean, ms: number, detail: string }> {\n const { port, health } = entry.config\n if (port === null)\n return { healthy: true, ms: 0, detail: 'no port configured' }\n\n return probeHealth({\n mode: health.mode,\n hosts: this.probeHosts(entry),\n port,\n timeoutMs: health.timeoutMs,\n http: health.http,\n })\n }\n\n private async awaitReadiness(entry: Entry, child: ChildProcess): Promise<void> {\n const { port, health } = entry.config\n\n if (port === null) {\n if (entry.child !== child || entry.status !== 'starting')\n return\n entry.status = 'running'\n entry.health = health.enabled ? 'unknown' : 'disabled'\n this.log(entry, 'system', 'running (no port configured; readiness assumed on spawn)')\n this.publishServer(entry)\n return\n }\n\n const deadline = Date.now() + health.startTimeoutMs\n while (Date.now() < deadline) {\n if (entry.child !== child || entry.status !== 'starting' || this.disposed)\n return\n if (await this.portAccepts(entry, port, Math.min(health.timeoutMs, 1000))) {\n entry.portState = 'in-use'\n entry.health = health.enabled ? 'healthy' : 'disabled'\n entry.status = 'running'\n entry.lastProbeAt = Date.now()\n this.log(entry, 'system', `accepting connections on port ${port}`)\n this.publishServer(entry)\n return\n }\n await delay(300)\n }\n\n if (entry.child === child && entry.status === 'starting') {\n entry.status = 'running'\n entry.health = 'unhealthy'\n entry.unhealthySince = Date.now()\n this.log(entry, 'system', `no connection on port ${port} after ${health.startTimeoutMs}ms — supervising anyway`)\n this.publishServer(entry)\n }\n }\n\n private handleExit(entry: Entry, child: ChildProcess, code: number | null, signal: NodeJS.Signals | null): void {\n if (entry.child !== child)\n return\n if (entry.pid !== null)\n this.sampler.forget(entry.pid)\n entry.child = null\n entry.pid = null\n entry.adopted = false\n entry.resources = null\n entry.responseMs = null\n entry.exitCode = code\n entry.exitSignal = signal\n\n // Both null means the process never got off the ground — a missing command,\n // for instance — so its error is more useful than \"code null\".\n const neverStarted = code === null && signal === null && entry.lastError !== null\n const detail = neverStarted\n ? entry.lastError!\n : signal !== null ? `signal ${signal}` : `code ${code}`\n const ranForMs = entry.startedAt === null ? 0 : Date.now() - entry.startedAt\n\n // Recorded for *every* exit, not only the ones that end in `crashed`: the\n // rolling window (crashes, uptime, last exit) is built from these events.\n this.options.history.record(entry.config.id, {\n type: 'exit',\n detail,\n runtimeMs: ranForMs,\n })\n\n this.afterExit(entry, detail, ranForMs, neverStarted)\n }\n\n /**\n * What happens once a process is gone, whether it was a child we spawned or a\n * detached successor we adopted: back off and retry, or record the crash.\n */\n private afterExit(entry: Entry, detail: string, ranForMs: number, neverStarted: boolean): void {\n const ranFor = `${Math.max(1, Math.round(ranForMs / 1000))}s`\n\n if (entry.stopping) {\n entry.status = 'stopped'\n this.publishServer(entry)\n return\n }\n\n const restart = entry.config.restart\n if (ranForMs >= restart.resetAfterMs)\n entry.restarts = 0\n\n this.log(entry, 'system', neverStarted ? `did not start: ${detail}` : `exited with ${detail} after ${ranFor}`)\n entry.lastError = neverStarted ? detail : `exited with ${detail}`\n\n if (restart.enabled && entry.restarts < restart.maxRetries) {\n entry.restarts += 1\n const backoffMs = computeBackoff(entry.restarts, restart)\n entry.status = 'backoff'\n entry.nextRetryAt = Date.now() + backoffMs\n this.log(entry, 'system', `restart ${entry.restarts}/${restart.maxRetries} in ${backoffMs}ms`)\n entry.retryTimer = setTimeout(() => {\n entry.retryTimer = null\n void this.start(entry.config.id, { retry: true }).catch((error: unknown) => {\n logger.error(`could not restart ${entry.config.id}`, error)\n })\n }, backoffMs)\n entry.retryTimer.unref()\n }\n else {\n entry.status = 'crashed'\n entry.nextRetryAt = null\n entry.lastError = restart.enabled\n ? `gave up after ${restart.maxRetries} retries (${detail})`\n : `${detail} (automatic restart disabled)`\n this.log(entry, 'system', entry.lastError)\n this.options.history.record(entry.config.id, {\n type: 'crash',\n detail: entry.lastError,\n runtimeMs: ranForMs,\n })\n this.notify(entry, 'crash', entry.lastError)\n }\n\n this.publishServer(entry)\n }\n\n private async tick(): Promise<void> {\n if (this.disposed)\n return\n const now = Date.now()\n\n await this.options.hostMonitor.tick(now)\n await this.sampleResources(now)\n\n // Probes run concurrently: one slow server must not delay the others' health.\n await Promise.allSettled([...this.entries.values()].map(entry => this.probeEntry(entry, now)))\n\n for (const entry of this.entries.values()) {\n // An adopted process is not our child, so nothing tells us it died.\n if (entry.adopted && entry.pid !== null && !isProcessAlive(entry.pid)) {\n this.handleAdoptedExit(entry)\n continue\n }\n if (await this.enforceMemoryLimit(entry))\n continue\n if (this.shouldForceRestart(entry, now)) {\n await this.restart(entry.config.id)\n continue\n }\n if (entry.status === 'backoff' && entry.nextRetryAt !== null && now >= entry.nextRetryAt && entry.retryTimer === null) {\n void this.start(entry.config.id, { retry: true }).catch((error: unknown) => {\n logger.error(`could not restart ${entry.config.id}`, error)\n })\n }\n }\n\n this.publishState()\n }\n\n /** One scan of /proc covers every server; only live processes are sampled. */\n private async sampleResources(now: number): Promise<void> {\n const due = [...this.entries.values()].filter(entry =>\n entry.pid !== null\n && (entry.child !== null || entry.adopted)\n && now - entry.resourcesSampledAt >= RESOURCE_SAMPLE_INTERVAL_MS)\n if (due.length === 0)\n return\n\n for (const entry of due) entry.resourcesSampledAt = now\n try {\n const samples = await this.sampler.sampleMany(due.map(entry => entry.pid!))\n for (const entry of due) entry.resources = samples.get(entry.pid!) ?? null\n }\n catch {\n // Sampling is best effort; a missing /proc must not break supervision.\n }\n }\n\n private async probeEntry(entry: Entry, now: number): Promise<void> {\n const { port, health } = entry.config\n if (port === null || entry.probing)\n return\n\n entry.probing = true\n try {\n // Occupancy is shown even while stopped, so it keeps its own slower cadence\n // instead of sharing (and being skipped by) the health probe's timer.\n if (now - entry.lastOccupancyProbeAt >= PORT_STATE_INTERVAL_MS) {\n entry.lastOccupancyProbeAt = now\n const accepting = await this.portAccepts(entry, port, health.timeoutMs)\n entry.portState = accepting ? 'in-use' : 'free'\n }\n\n if (entry.status !== 'running' || !health.enabled)\n return\n if (now - entry.lastProbeAt < health.intervalMs)\n return\n\n const probe = await this.probeEntryHealth(entry)\n entry.lastProbeAt = now\n entry.responseMs = probe.ms\n entry.portState = probe.healthy ? 'in-use' : entry.portState\n\n if (probe.healthy) {\n if (entry.health === 'unhealthy') {\n this.log(entry, 'system', `${probe.detail} — healthy again (${probe.ms}ms)`)\n this.options.history.record(entry.config.id, { type: 'recovered', detail: probe.detail })\n this.notify(entry, 'recovered', probe.detail)\n }\n entry.health = 'healthy'\n entry.healthFailures = 0\n entry.unhealthySince = null\n return\n }\n\n entry.healthFailures += 1\n if (entry.healthFailures >= health.unhealthyThreshold) {\n if (entry.unhealthySince === null) {\n entry.unhealthySince = now\n this.log(entry, 'system', `unhealthy: ${probe.detail} (${entry.healthFailures} failed probes) — warning only`)\n this.options.history.record(entry.config.id, { type: 'unhealthy', detail: probe.detail })\n this.notify(entry, 'unhealthy', probe.detail)\n }\n entry.health = 'unhealthy'\n }\n }\n finally {\n entry.probing = false\n }\n }\n\n private async enforceMemoryLimit(entry: Entry): Promise<boolean> {\n const limit = entry.config.resources.maxRssBytes\n const rss = entry.resources?.rssBytes ?? null\n if (limit <= 0 || rss === null || entry.child === null || entry.status !== 'running' || rss <= limit)\n return false\n\n const detail = `process tree uses ${Math.round(rss / 1024 / 1024)}MB, over the ${Math.round(limit / 1024 / 1024)}MB limit`\n this.log(entry, 'system', `${detail} — restarting`)\n this.options.history.record(entry.config.id, { type: 'forced-restart', detail })\n this.notify(entry, 'rss', detail)\n await this.restart(entry.config.id)\n return true\n }\n\n private shouldForceRestart(entry: Entry, now: number): boolean {\n const { port, health } = entry.config\n if (port === null || entry.status !== 'running' || entry.health !== 'unhealthy')\n return false\n if (entry.unhealthySince === null || health.forceRestartAfterMs <= 0)\n return false\n if (now - entry.unhealthySince < health.forceRestartAfterMs)\n return false\n\n this.log(entry, 'system', `unhealthy for ${health.forceRestartAfterMs}ms — forcing a restart`)\n this.options.history.record(entry.config.id, { type: 'forced-restart', detail: `health check stayed unhealthy` })\n this.notify(entry, 'forced-restart', `unhealthy for ${Math.round(health.forceRestartAfterMs / 1000)}s`)\n return true\n }\n\n private clearRetry(entry: Entry): void {\n if (entry.retryTimer !== null) {\n clearTimeout(entry.retryTimer)\n entry.retryTimer = null\n }\n }\n\n private buildVars(entry: Entry): TemplateVars {\n return serverTemplateVars(entry.config)\n }\n\n private view(entry: Entry): ServerView {\n const config = entry.config\n const host = displayHost(config.bind)\n return {\n id: config.id,\n config,\n bindHost: bindHost(config.bind),\n url: config.port === undefined || config.port === null ? null : `http://${host}:${config.port}`,\n status: entry.status,\n health: entry.health,\n portState: entry.portState,\n pid: entry.pid,\n // Omitted when false: an optional ArkType property rejects an explicit undefined.\n ...(entry.adopted ? { adopted: true } : {}),\n startedAt: entry.startedAt,\n exitCode: entry.exitCode,\n exitSignal: entry.exitSignal,\n restarts: entry.restarts,\n maxRetries: config.restart.maxRetries,\n lastError: entry.lastError,\n nextRetryAt: entry.nextRetryAt,\n unhealthySince: entry.unhealthySince,\n bufferedLines: entry.logs.size,\n history: this.summarizeHistory(entry),\n responseMs: entry.responseMs,\n resources: entry.resources,\n }\n }\n\n private log(entry: Entry, stream: LogStream, text: string): void {\n const line: LogLine = { ts: Date.now(), stream, text }\n entry.logs.push(line)\n this.options.logFiles.append(entry.config.id, line)\n this.hub.publish({ type: 'log', ts: line.ts, serverId: entry.config.id, lines: [line] })\n if (stream === 'system')\n logger.debug(`[${entry.config.id}] ${text}`)\n }\n\n private publishServer(entry: Entry): void {\n if (!this.entries.has(entry.config.id))\n return\n this.hub.publish({\n type: 'server',\n ts: Date.now(),\n serverId: entry.config.id,\n server: this.view(entry),\n })\n }\n\n private publishState(): void {\n const state = this.getState()\n const signature = [\n state.configError ?? '',\n ...state.servers.map(server => [\n server.id,\n server.status,\n server.health,\n server.portState,\n server.pid,\n server.restarts,\n server.nextRetryAt,\n server.lastError,\n server.bufferedLines,\n // A new resource sample *is* news: the UI builds its charts by sampling\n // these frames, so leaving them out of the signature means a fleet where\n // nothing structural changes emits no frames at all — and every graph\n // stays empty until something else moves.\n server.responseMs,\n server.resources?.sampledAt,\n ].join(':')),\n ].join('|')\n\n if (signature === this.lastStateSignature)\n return\n this.lastStateSignature = signature\n this.hub.publish({ type: 'state', ts: Date.now(), state })\n }\n}\n","/**\n * Names that only ever hold build output or installed dependencies — the things\n * a `.gitignore` almost always lists.\n *\n * A backup walks a declared data directory and copies whatever it finds; on a\n * project directory that is mostly `node_modules` and framework caches, which\n * bloats the archive with content nobody needs to restore. An entry with\n * `backupIgnoreGenerated` (on by default) skips these.\n *\n * Matching is by exact name, on any segment, at any depth: `dist/` and\n * `app/node_modules/` are generated, `distributed/` and `my-node_modules/` are\n * not.\n *\n * `.git` is deliberately absent however much a `.gitignore` would not list it:\n * a package manager can reinstall `node_modules`, but nobody can restore a\n * commit that was never pushed.\n */\n\n/** Whole directories that are regenerated, never authored. */\nexport const GENERATED_DIRS: readonly string[] = [\n // Installed dependencies and package-manager stores.\n 'node_modules',\n 'bower_components',\n 'jspm_packages',\n '.yarn',\n '.pnpm',\n '.pnpm-store',\n '.npm',\n // Framework and bundler output.\n '.next',\n '.nuxt',\n '.svelte-kit',\n '.astro',\n '.output',\n '.vercel',\n '.netlify',\n '.angular',\n 'dist',\n 'build',\n 'out',\n // Caches.\n '.cache',\n '.parcel-cache',\n '.turbo',\n '.vite',\n '.rollup.cache',\n '.swc',\n // Test and coverage output.\n 'coverage',\n '.nyc_output',\n '__pycache__',\n '.pytest_cache',\n '.mypy_cache',\n '.ruff_cache',\n '.tox',\n // Other language toolchains.\n 'target',\n '.gradle',\n '.dart_tool',\n]\n\n/** One-off files worth skipping, by exact name. */\nexport const GENERATED_FILES: readonly string[] = [\n '.DS_Store',\n 'Thumbs.db',\n 'desktop.ini',\n '.eslintcache',\n '.stylelintcache',\n 'npm-debug.log',\n 'yarn-error.log',\n 'pnpm-debug.log',\n]\n\nconst DIR_NAME_SET = new Set(GENERATED_DIRS)\nconst FILE_NAME_SET = new Set(GENERATED_FILES)\n\n/**\n * True when a path *relative to the declared root* looks generated. Accepts\n * either separator, so a `path.relative` result can be passed straight in.\n */\nexport function isGeneratedPath(relative: string): boolean {\n const segments = relative.split(/[\\\\/]+/).filter(segment => segment.length > 0 && segment !== '.')\n if (segments.length === 0)\n return false\n if (FILE_NAME_SET.has(segments[segments.length - 1]!))\n return true\n return segments.some(segment => DIR_NAME_SET.has(segment))\n}\n","import type { BackupFile, BackupPath, BackupsConfig, ServerConfig } from '#src/shared/contracts'\nimport fs from 'node:fs'\nimport os from 'node:os'\nimport path from 'node:path'\nimport process from 'node:process'\nimport { parseConfig } from '#src/config/parse'\nimport { writeFileAtomic } from '#src/helpers/atomic'\nimport { expandEnv } from '#src/helpers/env-file'\nimport { dataRoot, projectDir, resolveUserPath } from '#src/helpers/paths'\nimport { resolveTemplate } from '#src/helpers/template'\nimport { createZip, extractZip, isInvalidPassword, isZipArchive, listZip } from '#src/providers/archive'\nimport { serverTemplateVars } from '#src/services/supervisor'\nimport { isGeneratedPath } from '#src/shared/generated'\n\nconst MANIFEST = 'manifest.json'\nconst ALLOWED_ROOTS = new Set(['config', 'secrets', 'tls', 'data'])\n/** Every archive this service writes is a zip, encrypted or not. */\nconst SUFFIX = '.zip'\n\n/**\n * Structural allowlist for archive entries. A regex alone is not enough: `..`\n * is made of allowed characters, so segments are checked explicitly and any\n * entry that could resolve outside the staging directory aborts the restore.\n */\nexport function isSafeArchiveEntry(entry: string): boolean {\n if (entry.startsWith('/') || entry.includes('\\0'))\n return false\n\n const cleaned = entry.replace(/^\\.\\//, '').replace(/\\/+$/, '')\n if (cleaned.length === 0)\n return true\n if (cleaned === 'manifest.json')\n return true\n\n const segments = cleaned.split('/')\n if (segments.some(segment => segment.length === 0 || segment === '.' || segment === '..'))\n return false\n if (!ALLOWED_ROOTS.has(segments[0]!))\n return false\n return segments.every(segment => /^[\\w.-]+$/.test(segment))\n}\n\n/** True when `child` is `parent` itself or lives underneath it. */\nexport function isInside(parent: string, child: string): boolean {\n if (parent === child)\n return true\n // `path.relative` is case-insensitive on Windows, and empty for a case-only\n // difference — which is still the same directory.\n const relative = path.relative(parent, child)\n return !path.isAbsolute(relative) && (relative.length === 0 || !relative.startsWith('..'))\n}\n\ninterface DeclaredPath {\n path: string\n origin: string\n depth: number\n order: number\n ignoreGenerated: boolean\n}\n\n/**\n * Every path a backup should capture: the global list, then each server's\n * `backupPaths` and the values of its `dataEnvs`. A path already covered by a\n * declared parent is reported but not captured, so an entry only has to name\n * the shallowest directory it cares about.\n */\nexport function resolveBackupPaths(servers: ServerConfig[], includePaths: string[] = []): BackupPath[] {\n const declared: DeclaredPath[] = []\n const globalVars = { projectDir, dataRoot, home: os.homedir() }\n\n const add = (value: string, origin: string, vars: Record<string, string | number>, ignoreGenerated: boolean): void => {\n if (value.trim().length === 0)\n return\n // Normalized, so a trailing slash or a doubled one cannot defeat the\n // parent/child comparison below.\n const resolved = path.normalize(resolveUserPath(expandEnv(resolveTemplate(value, vars), process.env)))\n declared.push({\n path: resolved,\n origin,\n depth: path.normalize(resolved).split(path.sep).filter(Boolean).length,\n order: declared.length,\n ignoreGenerated,\n })\n }\n\n // A global extra path is named by hand, so it is captured as it stands.\n for (const value of includePaths) add(value, 'global', globalVars, false)\n\n for (const config of servers) {\n const vars = serverTemplateVars(config)\n const ignoreGenerated = config.backupIgnoreGenerated !== false\n for (const [name, value] of Object.entries(config.dataEnvs)) add(value, `${config.id}:${name}`, vars, ignoreGenerated)\n for (const value of config.backupPaths) add(value, `${config.id}:backupPaths`, vars, ignoreGenerated)\n }\n\n // Shallowest first, so a parent always absorbs its descendants whatever order\n // the config declared them in; ties keep declaration order.\n const sorted = [...declared].sort((a, b) => a.depth - b.depth || a.order - b.order)\n\n return sorted.map((entry, index) => {\n const parent = sorted.slice(0, index).find(candidate => isInside(candidate.path, entry.path))\n if (parent === undefined)\n return { path: entry.path, origin: entry.origin, included: true, note: null, ignoreGenerated: entry.ignoreGenerated }\n const note = parent.path === entry.path\n ? `already declared by ${parent.origin}`\n : `covered by ${parent.path}`\n return { path: entry.path, origin: entry.origin, included: false, note, ignoreGenerated: entry.ignoreGenerated }\n })\n}\n\nexport interface BackupSources {\n configPath: string\n secretsPath: string\n tlsDir: string\n /** Declared paths, resolved, with their origin and inclusion verdict. */\n paths: BackupPath[]\n}\n\nexport interface BackupManifest {\n version: 1\n createdAt: number\n hostname: string\n /** `origin` is what lets a restore land under *this* machine's paths. */\n data: Array<{ slug: string, path: string, origin?: string }>\n}\n\nexport interface RestoreOptions {\n confirm: boolean\n /** Required for, and ignored by, archives that are not password-protected. */\n password?: string\n /** Item ids to restore; omitted means every restorable item. */\n include?: string[]\n}\n\nexport interface RestorePlan {\n dryRun: boolean\n encrypted: boolean\n needsPassword: boolean\n items: Array<{\n id: string\n label: string\n kind: 'config' | 'secrets' | 'tls' | 'data'\n restorable: boolean\n selected: boolean\n note: string | null\n }>\n applied: string[]\n skipped: string[]\n restartRequired: boolean\n /** The panel re-read the restored config in this same run. */\n reloaded: boolean\n error?: string\n}\n\n/** One place a restore may write a data path to, and what declared it. */\ninterface DeclaredTarget {\n path: string\n origin: string\n}\n\n/** The panel's own listener is the only thing a restart is needed for. */\nfunction controlBlock(configText: string | null): unknown {\n try {\n return (JSON.parse(configText ?? '{}') as { control?: unknown }).control ?? null\n }\n catch {\n return null\n }\n}\n\n/**\n * A restored config is accepted when this release can read it — through the same\n * tolerant parser the store uses, so an archive from a newer release keeps only\n * the keys this one understands instead of being refused outright.\n */\nfunction isUsableConfig(text: string | null): boolean {\n if (text === null)\n return false\n try {\n return parseConfig(JSON.parse(text)).config !== null\n }\n catch {\n return false\n }\n}\n\n/**\n * The data paths the archive's own config declares, resolved against *this*\n * machine — so a backup made with `{home}` templates restores under this user's\n * paths, and one restored onto a blank instance brings its servers with it.\n */\nfunction archiveTargets(configText: string | null): DeclaredTarget[] {\n if (configText === null)\n return []\n try {\n const parsed = parseConfig(JSON.parse(configText)).config\n if (parsed === null)\n return []\n return resolveBackupPaths(parsed.servers, parsed.backups.includePaths)\n .filter(entry => entry.included)\n .map(entry => ({ path: entry.path, origin: entry.origin }))\n }\n catch {\n return []\n }\n}\n\n/**\n * The manifest is written by us, but an uploaded archive's copy is attacker\n * controlled: never join an unvalidated slug into a path.\n */\nfunction safeSlug(slug: unknown): string | null {\n if (typeof slug !== 'string' || slug.length === 0 || slug.length > 80)\n return null\n if (!/^[\\w.-]+$/.test(slug) || slug === '.' || slug === '..')\n return null\n return slug\n}\n\n/** A flag is valid for one exact file revision, not for the name alone. */\nfunction cacheKey(file: { sizeBytes: number, createdAt: number }): string {\n return `${file.sizeBytes}:${file.createdAt}`\n}\n\n/** Stable, filesystem-safe name for a data path inside the archive. */\nexport function slugifyPath(target: string): string {\n const cleaned = target.replace(/[^A-Z0-9]+/gi, '-').replace(/^-+|-+$/g, '')\n return cleaned.length > 0 ? cleaned.slice(-80) : 'path'\n}\n\n/**\n * Archives of the control plane's own state plus whatever paths the config\n * declares. Two rules keep restore safe: the archive layout is an allowlist, and\n * a data path is only written back when the *current* config still declares it —\n * an uploaded archive can never choose where to write.\n *\n * A backup is always a zip; a password makes it a WinZip-AES one, so the same\n * file opens in any archive manager either way.\n */\nexport class BackupService {\n /**\n * Whether an archive is encrypted is only knowable by reading its central\n * directory, which is async while `list()` is not. The flags are cached here\n * and refreshed in the background, so a state frame stays cheap.\n */\n private readonly flags = new Map<string, { key: string, encrypted: boolean }>()\n private refreshing: Promise<void> | null = null\n\n constructor(\n private readonly options: {\n /** Relative `backups.dir` values resolve against it. */\n dataRoot: string\n getConfig: () => BackupsConfig\n getSources: () => BackupSources\n /** Called after this instance's own config was overwritten by a restore. */\n onConfigRestored?: () => void\n },\n ) {}\n\n /** Reads every archive once, so the first `list()` is already accurate. */\n async warm(): Promise<void> {\n await this.refresh()\n }\n\n get directory(): string {\n return this.resolveDir()\n }\n\n /** Declared paths with their verdict, as the UI shows them. */\n get paths(): BackupPath[] {\n const dir = path.resolve(this.resolveDir())\n return this.options.getSources().paths.map((entry) => {\n // Capturing a directory that contains the archive directory would make the\n // archive contain itself.\n if (isInside(entry.path, dir))\n return { ...entry, included: false, note: 'contains the backup directory' }\n return entry\n })\n }\n\n /** Only what actually goes into a backup. */\n get dataPaths(): string[] {\n return this.paths.filter(entry => entry.included).map(entry => entry.path)\n }\n\n list(): BackupFile[] {\n const files = this.scan()\n for (const file of files) {\n const cached = this.flags.get(file.name)\n if (cached === undefined || cached.key !== cacheKey(file))\n void this.scheduleRefresh()\n }\n\n return files.map((file) => {\n const cached = this.flags.get(file.name)\n return {\n ...file,\n encrypted: cached !== undefined && cached.key === cacheKey(file) ? cached.encrypted : false,\n }\n })\n }\n\n /** Validated absolute path for a download, or null when the name is not a backup. */\n resolve(name: string): string | null {\n if (!/^[A-Z0-9][\\w.-]*$/i.test(name) || name.includes('..'))\n return null\n const file = path.join(this.resolveDir(), name)\n return fs.existsSync(file) ? file : null\n }\n\n /** `password` encrypts the archive; it is never stored anywhere. */\n async create(options: { password?: string } = {}): Promise<{ ok: boolean, file?: BackupFile, error?: string }> {\n const config = this.options.getConfig()\n if (!config.enabled)\n return { ok: false, error: 'backups are disabled' }\n\n const password = options.password !== undefined && options.password.length > 0 ? options.password : null\n\n const dir = this.resolveDir()\n const sources = this.options.getSources()\n const staging = path.join(dir, `.staging-${Date.now()}`)\n const createdAt = Date.now()\n // Milliseconds matter: two backups in the same second must not collide.\n const name = `backup-${new Date(createdAt).toISOString().replace(/[:T]/g, '-').replace(/\\.\\d+Z$/, '')}-${createdAt % 1000}${SUFFIX}`\n const destination = path.join(dir, name)\n\n try {\n fs.mkdirSync(staging, { recursive: true })\n this.copyInto(staging, 'config/servers.config.json', sources.configPath)\n this.copyInto(staging, 'secrets/control-secrets.json', sources.secretsPath)\n this.copyInto(staging, 'tls', sources.tlsDir)\n\n const data: BackupManifest['data'] = []\n for (const declared of this.paths) {\n if (!declared.included || !fs.existsSync(declared.path))\n continue\n const slug = slugifyPath(declared.path)\n if (data.some(entry => entry.slug === slug))\n continue\n this.copyInto(staging, path.join('data', slug), declared.path, declared.ignoreGenerated === true)\n data.push({ slug, path: declared.path, origin: declared.origin })\n }\n\n const manifest: BackupManifest = { version: 1, createdAt, hostname: os.hostname(), data }\n fs.writeFileSync(path.join(staging, MANIFEST), `${JSON.stringify(manifest, null, 2)}\\n`)\n\n await createZip(staging, destination, password === null ? {} : { password })\n\n fs.rmSync(staging, { recursive: true, force: true })\n this.prune()\n\n const stats = fs.statSync(destination)\n this.flags.set(name, { key: `${stats.size}:${Math.round(stats.mtimeMs)}`, encrypted: password !== null })\n return { ok: true, file: { name, sizeBytes: stats.size, createdAt, encrypted: password !== null } }\n }\n catch (error) {\n fs.rmSync(staging, { recursive: true, force: true })\n fs.rmSync(destination, { force: true })\n return { ok: false, error: error instanceof Error ? error.message : String(error) }\n }\n }\n\n remove(name: string): boolean {\n const file = this.resolve(name)\n if (file === null)\n return false\n fs.rmSync(file, { force: true })\n this.flags.delete(name)\n return true\n }\n\n /**\n * Validates an archive, then (unless `confirm` is false) applies whichever of\n * its items were selected. Data paths come from the *current* config, never\n * from the archive's manifest.\n */\n async restore(archivePath: string, options: RestoreOptions): Promise<RestorePlan> {\n const password = options.password !== undefined && options.password.length > 0 ? options.password : null\n const plan: RestorePlan = {\n dryRun: !options.confirm,\n encrypted: false,\n needsPassword: false,\n items: [],\n applied: [],\n skipped: [],\n restartRequired: false,\n reloaded: false,\n }\n\n if (!isZipArchive(archivePath))\n return { ...plan, error: 'the archive is not a home-hosted backup (a zip file was expected)' }\n\n const staging = path.join(this.resolveDir(), `.restore-${Date.now()}`)\n\n try {\n // The central directory is readable without a password, so a backup can be\n // listed and its selection offered before the password is ever entered.\n let entries\n try {\n entries = await listZip(archivePath)\n }\n catch (error) {\n return { ...plan, error: `the archive could not be read: ${error instanceof Error ? error.message : String(error)}` }\n }\n\n plan.encrypted = entries.some(entry => entry.encrypted)\n if (plan.encrypted && password === null)\n return { ...plan, needsPassword: true, error: 'this backup is password-protected' }\n\n if (entries.length === 0)\n return { ...plan, error: 'the archive is empty' }\n if (entries.length > 100_000)\n return { ...plan, error: 'the archive has too many entries' }\n\n const invalid = entries.filter(entry => !isSafeArchiveEntry(entry.name))\n if (invalid.length > 0) {\n return { ...plan, error: `the archive contains unexpected entries (e.g. ${invalid.slice(0, 3).map(entry => entry.name).join(', ')})` }\n }\n\n fs.mkdirSync(staging, { recursive: true })\n const extracted = await extractZip(archivePath, staging, {\n names: entries.map(entry => entry.name),\n ...(password === null ? {} : { password }),\n })\n for (const name of extracted.skipped)\n plan.skipped.push(`${name} (symbolic link, skipped)`)\n\n const manifestPath = path.join(staging, MANIFEST)\n if (!fs.existsSync(manifestPath))\n return { ...plan, error: 'the archive has no manifest' }\n const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) as BackupManifest\n\n const sources = this.options.getSources()\n const configInArchive = path.join(staging, 'config', 'servers.config.json')\n const secretsInArchive = path.join(staging, 'secrets', 'control-secrets.json')\n const tlsInArchive = path.join(staging, 'tls')\n\n const selectedIds = options.include === undefined ? null : new Set(options.include)\n const actions = new Map<string, () => void>()\n\n const addItem = (item: RestorePlan['items'][number], apply: (() => void) | null): void => {\n if (apply === null) {\n plan.items.push({ ...item, selected: false })\n plan.skipped.push(`${item.label}${item.note === null ? '' : ` (${item.note})`}`)\n return\n }\n const selected = selectedIds === null || selectedIds.has(item.id)\n plan.items.push({ ...item, selected })\n if (selected) {\n actions.set(item.id, apply)\n }\n else {\n plan.skipped.push(`${item.label} (not selected)`)\n }\n }\n\n const archivedConfig = fs.existsSync(configInArchive) ? fs.readFileSync(configInArchive, 'utf8') : null\n // A config from an archive replaces the live one, so it has to validate\n // first — otherwise a malformed upload silently removes every server.\n const restoredConfig = isUsableConfig(archivedConfig) ? archivedConfig : null\n if (archivedConfig !== null && restoredConfig === null)\n plan.skipped.push('config/servers.config.json (the archive\\'s config is not valid)')\n if (restoredConfig !== null) {\n addItem({ id: 'config', label: 'config/servers.config.json', kind: 'config', restorable: true, selected: false, note: null }, () => {\n writeFileAtomic(sources.configPath, restoredConfig)\n })\n }\n if (fs.existsSync(secretsInArchive)) {\n const restored = fs.readFileSync(secretsInArchive, 'utf8')\n addItem({ id: 'secrets', label: 'secrets/control-secrets.json', kind: 'secrets', restorable: true, selected: false, note: null }, () => {\n writeFileAtomic(sources.secretsPath, restored, { mode: 0o600 })\n })\n }\n if (fs.existsSync(tlsInArchive)) {\n const files = fs.readdirSync(tlsInArchive).filter(file => fs.statSync(path.join(tlsInArchive, file)).isFile())\n addItem({ id: 'tls', label: 'tls/', kind: 'tls', restorable: true, selected: false, note: null }, () => {\n fs.mkdirSync(sources.tlsDir, { recursive: true })\n for (const file of files) {\n const from = path.join(tlsInArchive, file)\n const mode = file.endsWith('.key.pem') ? { mode: 0o600 } : {}\n writeFileAtomic(path.join(sources.tlsDir, file), fs.readFileSync(from, 'utf8'), mode)\n }\n })\n }\n\n // A data path is written to a path a *config* declares — this instance's, or\n // the one the archive brings. That second source is what makes a blank\n // instance restorable: the backup's own `servers.config.json` names its data\n // directories, so restoring the config restores the whole setup.\n const fromArchive = archiveTargets(restoredConfig)\n const candidates: DeclaredTarget[] = [\n // The restored config wins, because it is the one that will be live.\n ...(actions.has('config') ? fromArchive : []),\n ...this.paths.filter(entry => entry.included).map(entry => ({ path: entry.path, origin: entry.origin })),\n ]\n\n for (const entry of manifest.data ?? []) {\n const target = candidates.find(candidate => entry.origin !== undefined && candidate.origin === entry.origin)\n ?? candidates.find(candidate => candidate.path === entry.path)\n const from = path.join(staging, 'data', safeSlug(entry.slug) ?? slugifyPath(entry.path))\n const common = {\n id: `data:${entry.path}`,\n label: target?.path ?? entry.path,\n kind: 'data' as const,\n restorable: false,\n selected: false,\n note: null,\n }\n\n if (target === undefined) {\n const archiveOnly = fromArchive.some(candidate => candidate.origin === entry.origin)\n addItem({\n ...common,\n note: archiveOnly && !actions.has('config')\n ? 'declared by the backup\\'s config, which is not being restored'\n : 'not declared by this config, nor by the backup',\n }, null)\n continue\n }\n if (!fs.existsSync(from)) {\n addItem({ ...common, note: 'missing from the archive' }, null)\n continue\n }\n\n addItem(\n { ...common, restorable: true, note: target.path === entry.path ? null : `restored from ${entry.path}` },\n () => fs.cpSync(from, target.path, { recursive: true, force: true }),\n )\n }\n\n // The plan has to say whether a restart is needed even in a dry run: only\n // the panel's own listener does, the servers are re-read from the file.\n if (restoredConfig !== null && actions.has('config')) {\n const current = fs.existsSync(sources.configPath) ? fs.readFileSync(sources.configPath, 'utf8') : null\n plan.restartRequired = JSON.stringify(controlBlock(restoredConfig)) !== JSON.stringify(controlBlock(current))\n }\n\n if (!options.confirm) {\n // A dry run reports what *would* happen, so the UI can show the plan\n // and the selection before anything is written.\n plan.applied = [...actions.keys()].map(id => plan.items.find(item => item.id === id)!.label)\n return plan\n }\n\n for (const [id, apply] of actions) {\n apply()\n plan.applied.push(plan.items.find(item => item.id === id)!.label)\n }\n\n if (actions.has('config') && this.options.onConfigRestored !== undefined) {\n plan.reloaded = true\n // The panel re-reads the restored file here, so the servers it declares\n // exist immediately instead of after a restart.\n this.options.onConfigRestored()\n }\n\n return plan\n }\n catch (error) {\n // Extraction happens before anything is written, so a rejected password has\n // changed nothing at all.\n if (isInvalidPassword(error))\n return { ...plan, encrypted: true, needsPassword: true, error: 'the password is wrong' }\n // A restore is not transactional: say what already landed, so a failure\n // cannot look like nothing happened.\n const done = plan.applied.length > 0 ? ` — already applied: ${plan.applied.join(', ')}` : ''\n return { ...plan, error: `${error instanceof Error ? error.message : String(error)}${done}` }\n }\n finally {\n fs.rmSync(staging, { recursive: true, force: true })\n }\n }\n\n /** Newest-first listing, without the encryption flag, which needs a read. */\n private scan(): Array<Omit<BackupFile, 'encrypted'>> {\n const dir = this.resolveDir()\n let names: string[] = []\n try {\n names = fs.readdirSync(dir)\n }\n catch {\n return []\n }\n\n return names\n .filter(name => name.endsWith(SUFFIX))\n .flatMap((name) => {\n try {\n const stats = fs.statSync(path.join(dir, name))\n return [{ name, sizeBytes: stats.size, createdAt: Math.round(stats.mtimeMs) }]\n }\n catch {\n return []\n }\n })\n .sort((a, b) => b.createdAt - a.createdAt)\n }\n\n /** Single-flight: a state frame must never queue a pile of reads. */\n private scheduleRefresh(): Promise<void> {\n this.refreshing ??= this.refresh().finally(() => {\n this.refreshing = null\n })\n return this.refreshing\n }\n\n private async refresh(): Promise<void> {\n const dir = this.resolveDir()\n const listed = this.scan()\n\n for (const file of listed) {\n const key = cacheKey(file)\n if (this.flags.get(file.name)?.key === key)\n continue\n try {\n const entries = await listZip(path.join(dir, file.name))\n this.flags.set(file.name, { key, encrypted: entries.some(entry => entry.encrypted) })\n }\n catch {\n // Unreadable stays unmarked here; restoring it reports the real reason.\n this.flags.set(file.name, { key, encrypted: false })\n }\n }\n\n const present = new Set(listed.map(file => file.name))\n for (const name of [...this.flags.keys()]) {\n if (!present.has(name))\n this.flags.delete(name)\n }\n }\n\n private copyInto(staging: string, relative: string, source: string, ignoreGenerated = false): void {\n if (!fs.existsSync(source))\n return\n const target = path.join(staging, relative)\n fs.mkdirSync(path.dirname(target), { recursive: true })\n // Never copy the archive directory into itself, however broad a declared\n // path is (`fs.cpSync` would walk it while writing into it).\n const archiveDir = path.resolve(this.resolveDir())\n const root = path.resolve(source)\n fs.cpSync(source, target, {\n recursive: true,\n force: true,\n filter: (from) => {\n const resolved = path.resolve(from)\n if (isInside(archiveDir, resolved))\n return false\n if (!ignoreGenerated || resolved === root)\n return true\n return !isGeneratedPath(path.relative(root, resolved))\n },\n })\n }\n\n private prune(): void {\n const { keep } = this.options.getConfig()\n for (const file of this.list().slice(keep)) this.remove(file.name)\n }\n\n private resolveDir(): string {\n const configured = this.options.getConfig().dir\n return path.isAbsolute(configured) ? configured : path.resolve(this.options.dataRoot, configured)\n }\n}\n","import type { FSWatcher } from 'node:fs'\nimport fs from 'node:fs'\nimport path from 'node:path'\n\n/**\n * Tells the caller when the config file *may* have changed. Whether it really did\n * is the store's decision: it remembers the bytes it last read and the bytes it\n * wrote, so a save from the panel's own UI never reloads anything.\n *\n * The watch is on the file's directory, because that is what catches the common\n * edit — an editor writes a temporary file and renames it over the target, which\n * replaces the inode and would slip past a watch on the file itself. `fs.watch` is\n * not dependable on network mounts, so a slow poll backs it up: one small read\n * every couple of seconds costs nothing next to a state frame.\n */\n\nexport interface ConfigWatchOptions {\n /** The config file; its directory is what is actually watched. */\n file: string\n /** Called after a burst of changes settles. */\n onChange: () => void\n /** How long to wait for an editor to finish writing. */\n debounceMs?: number\n /** Backup poll interval in ms; 0 turns the poll off. */\n pollMs?: number\n /** A watch that cannot be established is reported; the poll still covers the file. */\n onError?: (error: unknown) => void\n}\n\nconst DEFAULT_DEBOUNCE_MS = 150\nconst DEFAULT_POLL_MS = 2000\n\nexport class ConfigWatch {\n private readonly debounceMs: number\n private readonly pollMs: number\n private watcher: FSWatcher | null = null\n private debounceTimer: NodeJS.Timeout | null = null\n private pollTimer: NodeJS.Timeout | null = null\n private disposed = false\n\n constructor(private readonly options: ConfigWatchOptions) {\n this.debounceMs = options.debounceMs ?? DEFAULT_DEBOUNCE_MS\n this.pollMs = options.pollMs ?? DEFAULT_POLL_MS\n }\n\n start(): void {\n if (this.disposed || this.watcher !== null)\n return\n\n try {\n this.watcher = fs.watch(path.dirname(this.options.file), (_event, filename) => {\n // Only our file: the directory is shared with the logs, the secrets file and\n // whatever else home-hosted keeps beside its config. The name is compared by\n // basename because a platform may hand back a path, not the bare name — macOS\n // has been seen to — and an exact compare then drops the edit it was told about.\n if (filename !== null && path.basename(filename) !== path.basename(this.options.file))\n return\n this.schedule()\n })\n this.watcher.on('error', (error) => {\n // A watch can die with the mount it was opened on. Stop pretending it works\n // and let the poll carry the file.\n this.options.onError?.(error)\n this.closeWatcher()\n })\n this.watcher.unref()\n }\n catch (error) {\n this.options.onError?.(error)\n }\n\n if (this.pollMs > 0) {\n this.pollTimer = setInterval(() => this.check(), this.pollMs)\n this.pollTimer.unref()\n }\n }\n\n /** One check, exactly what the poll does — tests drive this instead of waiting. */\n check(): void {\n this.schedule()\n }\n\n dispose(): void {\n this.disposed = true\n if (this.debounceTimer !== null) {\n clearTimeout(this.debounceTimer)\n this.debounceTimer = null\n }\n if (this.pollTimer !== null) {\n clearInterval(this.pollTimer)\n this.pollTimer = null\n }\n this.closeWatcher()\n }\n\n /** One reload per burst: an editor writing in pieces is still one edit. */\n private schedule(): void {\n if (this.disposed || this.debounceTimer !== null)\n return\n this.debounceTimer = setTimeout(() => {\n this.debounceTimer = null\n if (!this.disposed)\n this.options.onChange()\n }, this.debounceMs)\n this.debounceTimer.unref()\n }\n\n private closeWatcher(): void {\n this.watcher?.close()\n this.watcher = null\n }\n}\n","import type { Server } from 'srvx'\nimport type { Bind } from '#src/shared/contracts'\nimport { serve } from 'srvx'\nimport { bindHost, displayHost } from '#src/helpers/bind'\nimport { isPortFree } from '#src/providers/port'\n\nexport interface ControlEndpoint {\n /** Configured bind value: `local` | `lan` | ipv4. */\n host: Bind\n port: number\n /** Address actually bound. */\n bindHost: string\n url: string\n protocol: 'http' | 'https'\n}\n\nexport interface ControlServerOptions {\n /** A thunk, so the app can be built after this server exists. */\n fetch: (request: Request) => Response | Promise<Response>\n /** Read per (re)bind, so a settings change applies without a restart. */\n trustProxy: () => boolean\n /** The uploaded PEM pair, or null for plain http. Read per (re)bind. */\n tls: () => { cert: string, key: string } | null\n}\n\nexport interface RebindResult {\n ok: boolean\n error?: string\n}\n\n/**\n * Owns the control panel's own listener, so the settings page can move it to a\n * new host/port without stopping the supervised servers.\n */\nexport class ControlServer {\n readonly endpoint: ControlEndpoint\n private server: Server | null = null\n\n constructor(\n private readonly options: ControlServerOptions,\n initial: { host: Bind, port: number, tls?: boolean },\n ) {\n this.endpoint = {\n host: initial.host,\n port: initial.port,\n bindHost: bindHost(initial.host),\n url: `${initial.tls ? 'https' : 'http'}://${displayHost(initial.host)}:${initial.port}`,\n protocol: initial.tls ? 'https' : 'http',\n }\n }\n\n get liveHost(): string {\n return this.endpoint.host\n }\n\n get livePort(): number {\n return this.endpoint.port\n }\n\n async start(): Promise<void> {\n await this.listenWithRetry(this.endpoint.host, this.endpoint.port)\n }\n\n /** Re-listens on the same endpoint, e.g. after `trustProxy` changed. */\n async restart(): Promise<RebindResult> {\n const { host, port } = this.endpoint\n await this.close()\n try {\n await this.listenWithRetry(host, port)\n return { ok: true }\n }\n catch (error) {\n return { ok: false, error: `restart failed: ${error instanceof Error ? error.message : String(error)}` }\n }\n }\n\n /**\n * Moves the listener. Preflights the new port, and restores the previous\n * endpoint if the new one refuses to bind — otherwise the panel would become\n * unreachable and need a manual restart.\n */\n async rebind(next: { host: Bind, port: number }): Promise<RebindResult> {\n if (next.host === this.endpoint.host && next.port === this.endpoint.port)\n return { ok: true }\n\n const previous = { host: this.endpoint.host, port: this.endpoint.port }\n if (next.port !== previous.port && !(await isPortFree(next.port))) {\n return { ok: false, error: `port ${next.port} is already in use` }\n }\n\n await this.close()\n try {\n await this.listenWithRetry(next.host, next.port)\n return { ok: true }\n }\n catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n try {\n await this.listenWithRetry(previous.host, previous.port)\n }\n catch {\n // Nothing left to fall back to; the caller surfaces the original error.\n }\n return { ok: false, error: `rebind failed: ${message}` }\n }\n }\n\n async close(force = true): Promise<void> {\n const server = this.server\n this.server = null\n if (!server)\n return\n try {\n await server.close(force)\n }\n catch {\n // Already gone.\n }\n }\n\n /** A just-closed listener can refuse a rebind for a moment, so retry briefly. */\n private async listenWithRetry(host: Bind, port: number, attempts = 3): Promise<void> {\n let lastError: unknown\n for (let attempt = 1; attempt <= attempts; attempt++) {\n try {\n await this.listen(host, port)\n return\n }\n catch (error) {\n lastError = error\n if (attempt < attempts)\n await new Promise(resolve => setTimeout(resolve, 200))\n }\n }\n throw lastError\n }\n\n private async listen(host: Bind, port: number): Promise<void> {\n const tls = this.options.tls()\n const server = serve({\n fetch: this.options.fetch,\n port,\n hostname: bindHost(host),\n trustProxy: this.options.trustProxy(),\n ...(tls === null ? {} : { tls: { cert: tls.cert, key: tls.key } }),\n })\n\n // Without a listener, a failed bind would surface as an unhandled 'error' event.\n const nodeServer = server.node?.server\n const failure = new Promise<Error>((resolve) => {\n nodeServer?.once('error', error => resolve(error as Error))\n })\n\n const outcome = await Promise.race([\n server.ready().then(() => null).catch((error: unknown) => error as Error),\n failure,\n ])\n if (outcome !== null)\n throw outcome\n\n this.server = server\n this.endpoint.host = host\n this.endpoint.port = port\n this.endpoint.bindHost = bindHost(host)\n this.endpoint.protocol = tls === null ? 'http' : 'https'\n this.endpoint.url = `${this.endpoint.protocol}://${displayHost(host)}:${port}`\n }\n}\n","import type { SseMessage } from '#src/shared/contracts'\n\nexport type EventListener = (message: SseMessage) => void\n\nconst ALL = '*'\n\n/** Fan-out for SSE subscribers, optionally scoped to a single server. */\nexport class EventHub {\n private readonly listeners = new Map<string, Set<EventListener>>()\n\n subscribe(serverId: string | null, listener: EventListener): () => void {\n const key = serverId ?? ALL\n const bucket = this.listeners.get(key) ?? new Set<EventListener>()\n bucket.add(listener)\n this.listeners.set(key, bucket)\n\n return () => {\n bucket.delete(listener)\n if (bucket.size === 0)\n this.listeners.delete(key)\n }\n }\n\n publish(message: SseMessage): void {\n this.dispatch(ALL, message)\n if (message.serverId)\n this.dispatch(message.serverId, message)\n }\n\n get subscriberCount(): number {\n let total = 0\n for (const bucket of this.listeners.values()) total += bucket.size\n return total\n }\n\n private dispatch(key: string, message: SseMessage): void {\n const bucket = this.listeners.get(key)\n if (!bucket)\n return\n for (const listener of [...bucket]) {\n try {\n listener(message)\n }\n catch {\n bucket.delete(listener)\n }\n }\n }\n}\n","import type { HistoryEvent, ServerHistory } from '#src/shared/contracts'\nimport fs from 'node:fs'\nimport { writeFileAtomic } from '#src/helpers/atomic'\n\nconst MAX_EVENTS = 5000\nconst SAVE_DEBOUNCE_MS = 2000\nconst EVENTS_IN_VIEW = 8\n\nexport type HistoryEventType = HistoryEvent['type']\n\n/**\n * Bounded event log per server, persisted as JSON so uptime and crash counts\n * survive a restart of the control plane.\n *\n * Uptime is derived from recorded runtimes (each exit stores how long the process\n * was up) rather than from sampling, so it stays accurate without a background\n * poller.\n */\nexport class HistoryStore {\n private events: HistoryEvent[] = []\n private saveTimer: NodeJS.Timeout | null = null\n private loaded = false\n /** Bumped on every record, so readers can cache their summaries. */\n private version = 0\n\n constructor(private readonly file: string) {}\n\n get revision(): number {\n return this.version\n }\n\n load(): void {\n if (this.loaded)\n return\n this.loaded = true\n try {\n const parsed = JSON.parse(fs.readFileSync(this.file, 'utf8')) as { events?: HistoryEvent[] }\n this.events = Array.isArray(parsed.events) ? parsed.events.slice(-MAX_EVENTS) : []\n }\n catch {\n this.events = []\n }\n }\n\n record(serverId: string, event: Omit<HistoryEvent, 'serverId' | 'ts'>, ts = Date.now()): void {\n this.load()\n this.events.push({ serverId, ts, ...event })\n this.version += 1\n if (this.events.length > MAX_EVENTS)\n this.events.splice(0, this.events.length - MAX_EVENTS)\n this.scheduleSave()\n }\n\n all(): HistoryEvent[] {\n this.load()\n return [...this.events]\n }\n\n /** `runningSince` adds the in-flight up-interval so a long-running server shows its real ratio. */\n summarize(serverId: string, windowMs: number, now = Date.now(), runningSince: number | null = null): ServerHistory {\n this.load()\n const since = now - windowMs\n const mine = this.events.filter(event => event.serverId === serverId)\n const recent = mine.filter(event => event.ts >= since)\n\n let upMs = 0\n for (const event of recent) {\n if (event.runtimeMs !== undefined)\n upMs += Math.min(event.runtimeMs, windowMs)\n }\n if (runningSince !== null)\n upMs += Math.max(0, now - Math.max(runningSince, since))\n\n const lastCrash = [...mine].reverse().find(event => event.type === 'crash')\n const lastExit = [...mine].reverse().find(event => event.type === 'exit' || event.type === 'crash')\n\n return {\n windowMs,\n uptimeRatio: mine.length === 0 ? null : Math.max(0, Math.min(1, upMs / windowMs)),\n restarts: recent.filter(event => event.type === 'start').length,\n crashes: recent.filter(event => event.type === 'crash').length,\n forcedRestarts: recent.filter(event => event.type === 'forced-restart').length,\n lastCrashAt: lastCrash?.ts ?? null,\n lastExitAt: lastExit?.ts ?? null,\n lastRuntimeMs: lastExit?.runtimeMs ?? null,\n events: mine.slice(-EVENTS_IN_VIEW),\n }\n }\n\n dispose(): void {\n if (this.saveTimer !== null)\n clearTimeout(this.saveTimer)\n this.saveTimer = null\n this.save()\n }\n\n private scheduleSave(): void {\n if (this.saveTimer !== null)\n return\n this.saveTimer = setTimeout(() => {\n this.saveTimer = null\n this.save()\n }, SAVE_DEBOUNCE_MS)\n this.saveTimer.unref()\n }\n\n private save(): void {\n try {\n writeFileAtomic(this.file, `${JSON.stringify({ version: 1, events: this.events })}\\n`)\n }\n catch {\n // History is best-effort; never let it break supervision.\n }\n }\n}\n","import type { HostConfig, HostView } from '#src/shared/contracts'\nimport { execFile } from 'node:child_process'\nimport fs from 'node:fs'\nimport os from 'node:os'\nimport path from 'node:path'\nimport process from 'node:process'\nimport { promisify } from 'node:util'\n\nconst execFileAsync = promisify(execFile)\n\n/** Swap usage per platform: /proc on Linux, sysctl on macOS, CIM on Windows. */\nasync function swapUsedPercent(): Promise<number> {\n if (process.platform === 'linux')\n return 0 // filled by memoryInfo below\n\n if (process.platform === 'darwin') {\n try {\n const { stdout } = await execFileAsync('sysctl', ['-n', 'vm.swapusage'], { timeout: 3000 })\n const total = /total\\s*=\\s*([\\d.]+)M/.exec(stdout)?.[1]\n const used = /used\\s*=\\s*([\\d.]+)M/.exec(stdout)?.[1]\n const totalMb = Number.parseFloat(total ?? '0')\n const usedMb = Number.parseFloat(used ?? '0')\n return totalMb > 0 ? (usedMb / totalMb) * 100 : 0\n }\n catch {\n return 0\n }\n }\n\n if (process.platform === 'win32') {\n try {\n const script = 'Get-CimInstance Win32_PageFileUsage | Select-Object AllocatedBaseSize,CurrentUsage | ConvertTo-Csv -NoTypeInformation'\n const { stdout } = await execFileAsync('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', script], { timeout: 5000 })\n const line = stdout.split(/\\r?\\n/).slice(1).find(entry => entry.trim().length > 0)\n const cells = (line ?? '').split(',').map(entry => entry.replace(/\"/g, '').trim())\n const totalMb = Number.parseFloat(cells[0] ?? '0')\n const usedMb = Number.parseFloat(cells[1] ?? '0')\n return totalMb > 0 ? (usedMb / totalMb) * 100 : 0\n }\n catch {\n return 0\n }\n }\n\n return 0\n}\n\n/** `/proc/meminfo` counts cache as available, which `os.freemem()` does not. */\nexport function memoryInfo(): { memoryUsedPercent: number, swapUsedPercent: number } {\n try {\n const info = fs.readFileSync('/proc/meminfo', 'utf8')\n const read = (key: string): number => Number.parseInt(new RegExp(`^${key}:\\\\s+(\\\\d+)`, 'm').exec(info)?.[1] ?? '0', 10)\n const total = read('MemTotal')\n const available = read('MemAvailable')\n const swapTotal = read('SwapTotal')\n const swapFree = read('SwapFree')\n\n return {\n memoryUsedPercent: total > 0 ? ((total - available) / total) * 100 : 0,\n swapUsedPercent: swapTotal > 0 ? ((swapTotal - swapFree) / swapTotal) * 100 : 0,\n }\n }\n catch {\n const total = os.totalmem()\n const free = os.freemem()\n return { memoryUsedPercent: total > 0 ? ((total - free) / total) * 100 : 0, swapUsedPercent: 0 }\n }\n}\n\n/**\n * Best-effort CPU temperature from Linux thermal zones / hwmon. macOS and\n * Windows expose no unprivileged sensor, so those platforms report null and the\n * UI simply hides the reading.\n */\nexport function cpuTemperature(): number | null {\n const readings: number[] = []\n\n const inspect = (file: string): void => {\n try {\n const raw = Number.parseInt(fs.readFileSync(file, 'utf8').trim(), 10)\n if (!Number.isFinite(raw))\n return\n const celsius = raw / 1000 // both interfaces report millidegrees\n if (celsius > 0 && celsius < 150)\n readings.push(celsius)\n }\n catch {\n // Absent on this machine.\n }\n }\n\n try {\n for (const zone of fs.readdirSync('/sys/class/thermal')) {\n if (zone.startsWith('thermal_zone'))\n inspect(path.join('/sys/class/thermal', zone, 'temp'))\n }\n }\n catch {\n // No thermal class.\n }\n\n try {\n for (const hwmon of fs.readdirSync('/sys/class/hwmon')) {\n const dir = path.join('/sys/class/hwmon', hwmon)\n for (const entry of fs.readdirSync(dir)) {\n if (/^temp\\d+_input$/.test(entry))\n inspect(path.join(dir, entry))\n }\n }\n }\n catch {\n // No hwmon.\n }\n\n return readings.length > 0 ? Math.max(...readings) : null\n}\n\nasync function diskUsage(target: string): Promise<HostView['disks'][number] | null> {\n try {\n const stats = await fs.promises.statfs(target)\n const totalBytes = stats.blocks * stats.bsize\n const freeBytes = stats.bavail * stats.bsize\n return {\n path: target,\n totalBytes,\n freeBytes,\n usedPercent: totalBytes > 0 ? ((totalBytes - freeBytes) / totalBytes) * 100 : 0,\n }\n }\n catch {\n return null\n }\n}\n\n/**\n * Samples the machine itself: the failures a home server actually dies from are\n * a full disk, exhausted memory or a runaway load — none of which a port probe\n * can see.\n */\nexport async function sampleHost(config: HostConfig, resolvePath: (target: string) => string): Promise<HostView> {\n const cpus = os.cpus().length || 1\n const loadAvg = os.loadavg()\n const memory = memoryInfo()\n // `os.loadavg()` is always zero on Windows, so per-cpu load would alert forever.\n if (process.platform === 'win32')\n loadAvg.fill(0)\n if (memory.swapUsedPercent === 0 && process.platform !== 'linux') {\n memory.swapUsedPercent = await swapUsedPercent()\n }\n const tempCelsius = cpuTemperature()\n\n const disks = (await Promise.all(config.diskPaths.map(entry => diskUsage(resolvePath(entry))))).filter(\n (disk): disk is HostView['disks'][number] => disk !== null,\n )\n\n const alerts: string[] = []\n for (const disk of disks) {\n if (config.diskUsedPercent > 0 && disk.usedPercent >= config.diskUsedPercent) {\n alerts.push(`disk ${disk.path} is ${disk.usedPercent.toFixed(1)}% full`)\n }\n }\n if (config.memoryUsedPercent > 0 && memory.memoryUsedPercent >= config.memoryUsedPercent) {\n alerts.push(`memory is ${memory.memoryUsedPercent.toFixed(1)}% used`)\n }\n if (config.swapUsedPercent > 0 && memory.swapUsedPercent >= config.swapUsedPercent) {\n alerts.push(`swap is ${memory.swapUsedPercent.toFixed(1)}% used`)\n }\n const loadPerCpu = Number(loadAvg[0] ?? 0) / cpus\n if (config.loadPerCpu > 0 && loadPerCpu >= config.loadPerCpu) {\n alerts.push(`load ${loadPerCpu.toFixed(2)}/cpu exceeds ${config.loadPerCpu}`)\n }\n if (tempCelsius !== null && config.tempCelsius > 0 && tempCelsius >= config.tempCelsius) {\n alerts.push(`cpu temperature is ${tempCelsius.toFixed(0)}°C`)\n }\n\n return {\n enabled: config.enabled,\n cpus,\n loadAvg: [...loadAvg],\n uptimeMs: os.uptime() * 1000,\n memoryUsedPercent: memory.memoryUsedPercent,\n swapUsedPercent: memory.swapUsedPercent,\n tempCelsius,\n disks,\n alerts,\n sampledAt: Date.now(),\n }\n}\n\nexport function emptyHostView(config: HostConfig): HostView {\n return {\n enabled: config.enabled,\n cpus: os.cpus().length || 1,\n loadAvg: [0, 0, 0],\n uptimeMs: os.uptime() * 1000,\n memoryUsedPercent: 0,\n swapUsedPercent: 0,\n tempCelsius: null,\n disks: [],\n alerts: [],\n sampledAt: null,\n }\n}\n","import type { NotificationService } from '#src/services/notifications'\nimport type { HostConfig, HostView } from '#src/shared/contracts'\nimport { emptyHostView, sampleHost } from '#src/providers/host'\n\n/**\n * Samples host vitals on their own (slower) interval and turns threshold\n * breaches into one notification per transition, not one per sample.\n */\nexport class HostMonitor {\n private current: HostView\n private lastSampleAt = 0\n private alerting = false\n\n constructor(\n private readonly getConfig: () => HostConfig,\n private readonly resolvePath: (target: string) => string,\n private readonly notifications: NotificationService,\n ) {\n this.current = emptyHostView(getConfig())\n }\n\n get view(): HostView {\n return this.current\n }\n\n /** Cheap when the interval has not elapsed; safe to call every tick. */\n async tick(now = Date.now()): Promise<void> {\n const config = this.getConfig()\n if (!config.enabled) {\n if (this.current.enabled)\n this.current = { ...this.current, enabled: false }\n return\n }\n if (now - this.lastSampleAt < config.intervalMs)\n return\n\n this.lastSampleAt = now\n this.current = await sampleHost(config, this.resolvePath)\n\n if (this.current.alerts.length > 0) {\n if (!this.alerting) {\n this.alerting = true\n this.notifications.notify({\n serverId: 'host',\n label: 'Host',\n reason: 'host',\n detail: this.current.alerts.join('; '),\n })\n }\n return\n }\n\n if (this.alerting) {\n this.alerting = false\n this.notifications.notify({\n serverId: 'host',\n label: 'Host',\n reason: 'host-recovered',\n detail: 'every host threshold is back to normal',\n })\n }\n }\n}\n","import type { LogLine, LogsConfig } from '#src/shared/contracts'\nimport { Buffer } from 'node:buffer'\nimport fs from 'node:fs'\nimport path from 'node:path'\n\n/**\n * Append-only JSONL per server, one line per log entry, rotated by size.\n *\n * JSONL keeps the tail readable without parsing a stream, and appending needs no\n * rewrite of the existing file. Writes are batched on a short timer so a chatty\n * child cannot turn into a syscall per line.\n */\nconst FLUSH_INTERVAL_MS = 250\nconst MAX_PENDING_LINES = 500\nconst READ_CHUNK_BYTES = 256 * 1024\n\nexport interface LogFileInfo {\n name: string\n sizeBytes: number\n}\n\nexport class LogFiles {\n private readonly pending = new Map<string, LogLine[]>()\n private timer: NodeJS.Timeout | null = null\n private closed = false\n\n constructor(\n private readonly dir: string,\n private readonly getConfig: () => LogsConfig,\n ) {}\n\n get directory(): string {\n return this.dir\n }\n\n append(serverId: string, line: LogLine): void {\n if (this.closed || !this.getConfig().persist)\n return\n\n const bucket = this.pending.get(serverId) ?? []\n bucket.push(line)\n this.pending.set(serverId, bucket)\n\n if (bucket.length >= MAX_PENDING_LINES) {\n this.flush()\n return\n }\n this.timer ??= setTimeout(() => {\n this.timer = null\n this.flush()\n }, FLUSH_INTERVAL_MS)\n this.timer.unref()\n }\n\n flush(): void {\n if (this.pending.size === 0)\n return\n\n const batches = [...this.pending.entries()]\n this.pending.clear()\n\n for (const [serverId, lines] of batches) {\n try {\n this.write(serverId, lines)\n }\n catch {\n // Logging must never take the control plane down.\n }\n }\n }\n\n info(serverId: string): { enabled: boolean, sizeBytes: number, files: LogFileInfo[] } {\n const config = this.getConfig()\n const files: LogFileInfo[] = []\n let sizeBytes = 0\n\n for (const file of this.rotateTargets(serverId)) {\n try {\n const stats = fs.statSync(file)\n files.push({ name: path.basename(file), sizeBytes: stats.size })\n if (file === this.currentPath(serverId))\n sizeBytes = stats.size\n }\n catch {\n // Not rotated there yet.\n }\n }\n\n return { enabled: config.persist, sizeBytes, files }\n }\n\n /** Reads the last `tail` lines, newest file first, padding from one rotation back. */\n readTail(serverId: string, tail: number): LogLine[] {\n const sources = [this.currentPath(serverId), this.rotatedPath(serverId, 1)]\n const lines: LogLine[] = []\n\n for (const file of sources) {\n if (lines.length >= tail)\n break\n const chunk = this.readTailChunk(file, tail - lines.length)\n lines.unshift(...chunk)\n }\n\n return lines.slice(-tail)\n }\n\n clear(serverId: string): void {\n for (const file of this.rotateTargets(serverId)) {\n try {\n fs.rmSync(file, { force: true })\n }\n catch {\n // Nothing to remove.\n }\n }\n }\n\n dispose(): void {\n this.closed = true\n if (this.timer !== null)\n clearTimeout(this.timer)\n this.timer = null\n this.flush()\n }\n\n private write(serverId: string, lines: LogLine[]): void {\n const { maxBytes } = this.getConfig()\n const file = this.currentPath(serverId)\n fs.mkdirSync(this.dir, { recursive: true })\n\n // A batch is split at the size limit: writing it whole would sail past\n // maxBytes long before the next rotation check runs.\n let encoded: string[] = []\n let bytes = 0\n\n const commit = (): void => {\n if (encoded.length === 0)\n return\n const payload = `${encoded.join('\\n')}\\n`\n const currentSize = fs.existsSync(file) ? fs.statSync(file).size : 0\n if (currentSize + Buffer.byteLength(payload) > maxBytes)\n this.rotate(serverId)\n fs.appendFileSync(this.currentPath(serverId), payload)\n encoded = []\n bytes = 0\n }\n\n for (const line of lines) {\n const json = JSON.stringify(line)\n const size = Buffer.byteLength(json) + 1\n if (bytes > 0 && bytes + size > maxBytes)\n commit()\n encoded.push(json)\n bytes += size\n }\n\n commit()\n }\n\n private rotate(serverId: string): void {\n const { keep } = this.getConfig()\n for (let index = keep - 1; index >= 1; index--) {\n const from = this.rotatedPath(serverId, index)\n if (!fs.existsSync(from))\n continue\n fs.renameSync(from, this.rotatedPath(serverId, index + 1))\n }\n if (fs.existsSync(this.currentPath(serverId))) {\n fs.renameSync(this.currentPath(serverId), this.rotatedPath(serverId, 1))\n }\n }\n\n private readTailChunk(file: string, tail: number): LogLine[] {\n let handle: number\n try {\n handle = fs.openSync(file, 'r')\n }\n catch {\n return []\n }\n\n try {\n const size = fs.fstatSync(handle).size\n const length = Math.min(size, READ_CHUNK_BYTES)\n const buffer = Buffer.alloc(length)\n fs.readSync(handle, buffer, 0, length, size - length)\n\n const text = buffer.toString('utf8')\n // A mid-file cut can leave a partial first line, which is dropped.\n const raw = text.split('\\n').filter(entry => entry.trim().length > 0)\n const parsed: LogLine[] = []\n for (const entry of raw.slice(size > length ? 1 : 0)) {\n try {\n parsed.push(JSON.parse(entry) as LogLine)\n }\n catch {\n // Partial line from a rotation boundary.\n }\n }\n return parsed.slice(-tail)\n }\n finally {\n fs.closeSync(handle)\n }\n }\n\n private currentPath(serverId: string): string {\n return path.join(this.dir, `${serverId}.log`)\n }\n\n private rotatedPath(serverId: string, index: number): string {\n return path.join(this.dir, `${serverId}.log.${index}`)\n }\n\n private rotateTargets(serverId: string): string[] {\n const { keep } = this.getConfig()\n const targets = [this.currentPath(serverId)]\n for (let index = 1; index <= keep; index++) targets.push(this.rotatedPath(serverId, index))\n return targets\n }\n}\n","import type { SecretsStore } from '#src/config/secrets'\nimport type { LogsConfig, NotificationsConfig, TelegramStatus } from '#src/shared/contracts'\nimport { logger } from '#src/helpers/logger'\nimport { formatTelegramMessage, listTelegramChats, sendTelegramMessage, verifyTelegramToken } from '#src/providers/telegram'\n\nexport type NotificationReason = 'crash' | 'unhealthy' | 'forced-restart' | 'recovered' | 'rss' | 'host' | 'host-recovered'\n\nexport interface NotificationEvent {\n serverId: string\n label: string\n reason: NotificationReason\n detail: string\n}\n\nconst REASON_LABEL: Record<NotificationReason, string> = {\n 'crash': 'gave up restarting',\n 'unhealthy': 'health check failing',\n 'forced-restart': 'force restarted',\n 'recovered': 'recovered',\n 'rss': 'exceeded its memory limit',\n 'host': 'host thresholds breached',\n 'host-recovered': 'host thresholds recovered',\n}\n\nconst TITLE: Record<NotificationReason, string> = {\n 'crash': '🔴 server down',\n 'unhealthy': '🟠 server unhealthy',\n 'forced-restart': '🔁 server force restarted',\n 'recovered': '🟢 server recovered',\n 'rss': '🔴 server over its memory limit',\n 'host': '🟠 host warning',\n 'host-recovered': '🟢 host recovered',\n}\n\n/**\n * Fans supervision events out to notification transports.\n *\n * Telegram is the only transport so far. The bot token never leaves the secrets\n * file, and every send is rate-limited per server *and* reason so a flapping\n * server cannot flood the chat.\n */\nexport class NotificationService {\n private readonly cooldowns = new Map<string, number>()\n private lastResult: string | null = null\n private lastResultAt: number | null = null\n\n constructor(\n private readonly secrets: SecretsStore,\n private readonly getConfig: () => NotificationsConfig,\n private readonly getLogsConfig: () => LogsConfig,\n ) {}\n\n get telegramTokenSet(): boolean {\n return this.secrets.telegramTokenSet\n }\n\n status(): TelegramStatus {\n const telegram = this.getConfig().telegram\n return {\n enabled: telegram.enabled,\n tokenSet: this.secrets.telegramTokenSet,\n chatId: telegram.chatId,\n onCrash: telegram.onCrash,\n onUnhealthy: telegram.onUnhealthy,\n onForcedRestart: telegram.onForcedRestart,\n onRecovered: telegram.onRecovered,\n onHost: telegram.onHost,\n cooldownMs: telegram.cooldownMs,\n lastResult: this.lastResult,\n lastResultAt: this.lastResultAt,\n }\n }\n\n /** Enabled for this reason *and* outside its cooldown window. */\n shouldNotify(event: NotificationEvent, now = Date.now()): boolean {\n const telegram = this.getConfig().telegram\n if (!telegram.enabled)\n return false\n\n const reasonEnabled = {\n 'crash': telegram.onCrash,\n 'unhealthy': telegram.onUnhealthy,\n 'forced-restart': telegram.onForcedRestart,\n 'recovered': telegram.onRecovered,\n 'rss': telegram.onCrash,\n 'host': telegram.onHost,\n 'host-recovered': telegram.onHost,\n }[event.reason]\n if (!reasonEnabled)\n return false\n\n const until = this.cooldowns.get(`${event.serverId}:${event.reason}`) ?? 0\n return !(telegram.cooldownMs > 0 && until > now)\n }\n\n /** Starts the cooldown window for this event, so a flapping server stays quiet. */\n markSent(event: NotificationEvent, now = Date.now()): void {\n this.cooldowns.set(`${event.serverId}:${event.reason}`, now + this.getConfig().telegram.cooldownMs)\n }\n\n /** Fire-and-forget by design: supervision must never wait on a chat API. */\n notify(event: NotificationEvent): void {\n void this.dispatch(event).catch((error: unknown) => {\n logger.warn(`notification failed: ${error instanceof Error ? error.message : String(error)}`)\n })\n }\n\n async dispatch(event: NotificationEvent): Promise<boolean> {\n // `shouldNotify` owns the toggles and the cooldown, so the policy lives once.\n if (!this.shouldNotify(event))\n return false\n this.markSent(event)\n\n return this.sendTelegram(\n formatTelegramMessage(TITLE[event.reason], [\n `${event.label} (${event.serverId}) ${REASON_LABEL[event.reason]}`,\n event.detail,\n ]),\n )\n }\n\n /** Used by the \"send test\" button in settings. */\n async sendTest(overrides: { botToken?: string, chatId?: string } = {}): Promise<{ ok: boolean, error?: string }> {\n const chatId = overrides.chatId ?? this.getConfig().telegram.chatId\n if (chatId.length === 0)\n return { ok: false, error: 'no chat id configured' }\n\n const token = this.resolveToken(overrides.botToken)\n if (token === null)\n return { ok: false, error: 'no bot token configured' }\n\n const result = await sendTelegramMessage(\n token,\n chatId,\n formatTelegramMessage('✅ home-hosted test', ['notifications are wired up correctly']),\n )\n this.remember(result.ok ? 'test message sent' : result.error ?? 'test failed')\n return result\n }\n\n async detectChats(overrides: { botToken?: string } = {}): Promise<{ ok: boolean, chats: Array<{ id: number | string, title: string }>, error?: string }> {\n const token = this.resolveToken(overrides.botToken)\n if (token === null)\n return { ok: false, chats: [], error: 'no bot token configured' }\n\n const result = await listTelegramChats(token)\n this.remember(result.ok ? `${result.chats.length} chat(s) found` : result.error ?? 'detect failed')\n return result\n }\n\n /** Verifies a token without sending anything. */\n async verifyToken(token: string): Promise<{ ok: boolean, username?: string, error?: string }> {\n return verifyTelegramToken(token)\n }\n\n private resolveToken(tokenOverride?: string): string | null {\n const token = tokenOverride?.trim() ?? this.secrets.telegramToken ?? ''\n return token.length > 0 ? token : null\n }\n\n private async sendTelegram(html: string): Promise<boolean> {\n const telegram = this.getConfig().telegram\n if (telegram.chatId.length === 0) {\n this.remember('no chat id configured')\n return false\n }\n\n const token = this.resolveToken()\n if (token === null) {\n this.remember('no bot token configured')\n return false\n }\n\n const result = await sendTelegramMessage(token, telegram.chatId, html)\n this.remember(result.ok ? 'sent' : result.error ?? 'send failed')\n return result.ok\n }\n\n private remember(message: string): void {\n this.lastResult = message\n this.lastResultAt = Date.now()\n }\n}\n","import type { TlsStatus } from '#src/shared/contracts'\nimport { Buffer } from 'node:buffer'\nimport { createPrivateKey, createPublicKey, X509Certificate } from 'node:crypto'\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport { writeFileAtomic } from '#src/helpers/atomic'\n\n/**\n * Stores an uploaded PEM pair and reports what it contains.\n *\n * The key is written 0600 and both files stay out of git. Nothing here binds a\n * socket — the control server reads the pair and hands it to srvx.\n */\nexport class TlsStore {\n private cached: { cert: string, key: string } | null = null\n private cachedMtime = ''\n\n constructor(private readonly dir: string) {}\n\n get directory(): string {\n return this.dir\n }\n\n get certPath(): string {\n return path.join(this.dir, 'control.crt.pem')\n }\n\n get keyPath(): string {\n return path.join(this.dir, 'control.key.pem')\n }\n\n get present(): boolean {\n return fs.existsSync(this.certPath) && fs.existsSync(this.keyPath)\n }\n\n /** Returns the PEM pair, re-read when the files change on disk. */\n load(): { cert: string, key: string } | null {\n if (!this.present) {\n this.cached = null\n return null\n }\n\n const key = [this.certPath, this.keyPath].map((file) => {\n try {\n return `${fs.statSync(file).mtimeMs}`\n }\n catch {\n return 'x'\n }\n }).join(':')\n\n if (this.cached !== null && key === this.cachedMtime)\n return this.cached\n\n try {\n this.cached = {\n cert: fs.readFileSync(this.certPath, 'utf8'),\n key: fs.readFileSync(this.keyPath, 'utf8'),\n }\n this.cachedMtime = key\n return this.cached\n }\n catch {\n this.cached = null\n return null\n }\n }\n\n save(certificate: string, privateKey: string): { ok: boolean, error?: string } {\n const validation = validatePair(certificate, privateKey)\n if (!validation.ok)\n return { ok: false, error: validation.error }\n\n fs.mkdirSync(this.dir, { recursive: true })\n writeFileAtomic(this.certPath, `${certificate.trimEnd()}\\n`)\n writeFileAtomic(this.keyPath, `${privateKey.trimEnd()}\\n`, { mode: 0o600 })\n this.cached = null\n this.cachedMtime = ''\n return { ok: true }\n }\n\n clear(): void {\n for (const file of [this.certPath, this.keyPath]) {\n try {\n fs.rmSync(file, { force: true })\n }\n catch {\n // Nothing to remove.\n }\n }\n this.cached = null\n }\n\n status(enabled: boolean): TlsStatus {\n const base: TlsStatus = {\n enabled,\n certPresent: this.present,\n subject: null,\n issuer: null,\n validFrom: null,\n validTo: null,\n daysRemaining: null,\n fingerprint: null,\n keyMatches: null,\n error: null,\n }\n\n if (!this.present) {\n return enabled ? { ...base, error: 'TLS is enabled but no certificate has been uploaded' } : base\n }\n\n const pair = this.load()\n if (pair === null)\n return { ...base, error: 'the stored certificate could not be read' }\n\n try {\n const x509 = new X509Certificate(pair.cert)\n const validTo = new Date(x509.validTo)\n const daysRemaining = Math.floor((validTo.getTime() - Date.now()) / 86_400_000)\n return {\n ...base,\n subject: x509.subject.replace(/\\n/g, ', '),\n issuer: x509.issuer.replace(/\\n/g, ', '),\n validFrom: new Date(x509.validFrom).toISOString(),\n validTo: validTo.toISOString(),\n daysRemaining,\n fingerprint: x509.fingerprint256,\n keyMatches: validatePair(pair.cert, pair.key).ok,\n error: daysRemaining < 0 ? 'the certificate has expired' : null,\n }\n }\n catch (error) {\n return { ...base, error: `invalid certificate: ${error instanceof Error ? error.message : String(error)}` }\n }\n }\n}\n\n/** Checks the certificate parses, is time-valid, and matches the private key. */\nexport function validatePair(certificate: string, privateKey: string): { ok: boolean, error?: string } {\n let x509: X509Certificate\n try {\n x509 = new X509Certificate(certificate)\n }\n catch (error) {\n return { ok: false, error: `certificate is not a valid PEM: ${error instanceof Error ? error.message : String(error)}` }\n }\n\n try {\n const key = createPrivateKey(privateKey)\n const fromKey = createPublicKey(key).export({ type: 'spki', format: 'der' })\n const fromCert = x509.publicKey.export({ type: 'spki', format: 'der' })\n if (!Buffer.from(fromKey).equals(Buffer.from(fromCert))) {\n return { ok: false, error: 'the private key does not match the certificate' }\n }\n }\n catch (error) {\n return { ok: false, error: `private key is not a valid PEM: ${error instanceof Error ? error.message : String(error)}` }\n }\n\n if (new Date(x509.validTo).getTime() < Date.now()) {\n return { ok: false, error: `the certificate expired on ${x509.validTo}` }\n }\n\n return { ok: true }\n}\n","import type { ArchiveEntry } from '#src/providers/archive'\nimport type { UiMeta, UiStatus } from '#src/shared/contracts'\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport process from 'node:process'\nimport { type } from 'arktype'\nimport { writeFileAtomic } from '#src/helpers/atomic'\nimport { extractZip, isZipArchive, listZip } from '#src/providers/archive'\nimport { uiMetaSchema } from '#src/shared/contracts'\n\n/**\n * The panel's UI is replaceable: the stock SPA ships in the package, and a user\n * can put their own build in `$HHOSTED_HOME/.ui` — by hand, or by uploading a zip\n * in the settings page. Everything that serves files asks `resolveDir()` per\n * request, so an install (or `home-hosted ui-revert`) applies on the next refresh.\n */\n\nconst META = 'ui.json'\n/** A UI is static files; these caps keep a hostile or accidental archive harmless. */\nconst MAX_ENTRIES = 20_000\nconst MAX_BYTES = 512 * 1024 * 1024\nconst MAX_NAME = 120\n\n/** What a UI author may declare in a root `ui.json` inside their archive. */\nconst manifestSchema = type({\n 'name?': 'string',\n 'version?': 'string',\n 'repo?': 'string',\n 'tag?': 'string',\n 'asset?': 'string',\n 'unix?': 'number.integer >= 0',\n})\n\n/**\n * A UI archive is a static site: relative paths only, no traversal, no absolute\n * paths, no drive letters, and an `index.html` to serve. Symlinks are dropped by\n * the extractor.\n */\nexport function isSafeUiEntry(entry: string): boolean {\n if (entry.length === 0 || entry.length > MAX_NAME)\n return false\n if (entry.startsWith('/') || entry.includes('\\\\') || entry.includes('\\0'))\n return false\n\n const cleaned = entry.replace(/^\\.\\//, '').replace(/\\/+$/, '')\n if (cleaned.length === 0)\n return false\n return cleaned.split('/').every(segment => segment !== '' && segment !== '.' && segment !== '..' && !segment.includes(':'))\n}\n\nexport type UiInstallResult\n = | { ok: true, meta: UiMeta }\n | { ok: false, error: string }\n\nexport class UiService {\n constructor(private readonly options: { dataRoot: string, stockDir?: string }) {}\n\n /** `$HHOSTED_HOME/.ui` — the only place a user UI is ever read from. */\n get directory(): string {\n return path.join(this.options.dataRoot, '.ui')\n }\n\n /** True when a user UI is installed and complete. */\n get custom(): boolean {\n return fs.existsSync(path.join(this.directory, 'index.html'))\n }\n\n /** What the panel should serve right now. */\n resolveDir(): string {\n if (this.custom)\n return this.directory\n return this.options.stockDir ?? this.directory\n }\n\n status(): UiStatus {\n return { custom: this.custom, dir: this.directory, meta: this.readMeta() }\n }\n\n readMeta(): UiMeta | null {\n try {\n const parsed = uiMetaSchema(JSON.parse(fs.readFileSync(path.join(this.directory, META), 'utf8')))\n return parsed instanceof type.errors ? null : parsed\n }\n catch {\n return null\n }\n }\n\n /**\n * Installs a UI from a zip. The archive is extracted beside `.ui` and only then\n * swapped in, so a failed upload leaves the previous UI (or the stock one)\n * serving.\n */\n async install(archivePath: string, fallbackName = 'custom-ui', installedTag?: string): Promise<UiInstallResult> {\n if (!isZipArchive(archivePath))\n return { ok: false, error: 'the upload is not a zip archive' }\n\n // Unique per attempt, not just per millisecond: two installs starting together would\n // otherwise stage into the same directory and rename each other's tree away.\n const staging = path.join(this.options.dataRoot, `.ui-staging-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`)\n\n try {\n return await this.stage(archivePath, staging, fallbackName, installedTag)\n }\n catch (error) {\n return { ok: false, error: error instanceof Error ? error.message : String(error) }\n }\n finally {\n fs.rmSync(staging, { recursive: true, force: true })\n }\n }\n\n /** Removes the user UI, putting the stock panel back. */\n revert(): boolean {\n const existed = fs.existsSync(this.directory)\n fs.rmSync(this.directory, { recursive: true, force: true })\n return existed\n }\n\n private async stage(archivePath: string, staging: string, fallbackName: string, installedTag?: string): Promise<UiInstallResult> {\n let entries: ArchiveEntry[]\n try {\n entries = await listZip(archivePath)\n }\n catch (error) {\n return { ok: false, error: `the archive could not be read: ${error instanceof Error ? error.message : String(error)}` }\n }\n\n if (entries.length === 0)\n return { ok: false, error: 'the archive is empty' }\n if (entries.length > MAX_ENTRIES)\n return { ok: false, error: `the archive has more than ${MAX_ENTRIES} entries` }\n\n const bytes = entries.reduce((total, entry) => total + entry.size, 0)\n if (bytes > MAX_BYTES)\n return { ok: false, error: `the archive is larger than ${Math.round(MAX_BYTES / 1024 / 1024)}MB uncompressed` }\n\n const unusable = entries.find(entry => !isSafeUiEntry(entry.name))\n if (unusable !== undefined)\n return { ok: false, error: `the archive contains an unusable path: ${unusable.name}` }\n\n fs.mkdirSync(staging, { recursive: true })\n await extractZip(archivePath, staging, { names: entries.map(entry => entry.name) })\n\n const root = resolveRoot(staging)\n if (root === null)\n return { ok: false, error: 'the archive has no index.html at its root' }\n\n const manifest = readManifest(root)\n const meta: UiMeta = {\n name: manifest?.name ?? fallbackName,\n version: manifest?.version ?? null,\n uploadedAt: Date.now(),\n files: countFiles(root),\n // The declared identity is carried into the installed metadata, so `ui-update` can\n // still tell which release this UI came from long after the zip is gone. Omitted\n // rather than defaulted: a UI that declares nothing has nothing to say here.\n ...(manifest?.repo === undefined ? {} : { repo: manifest.repo }),\n // The caller that fetched this from a release knows the tag better than the archive\n // does: a UI zip is built *before* the release is cut, so its own `tag` is the\n // previous release at best. Recording the archive's value here is what made a panel\n // re-download and re-install the same UI on every boot.\n ...(installedTag !== undefined ? { tag: installedTag } : manifest?.tag === undefined ? {} : { tag: manifest.tag }),\n ...(manifest?.asset === undefined ? {} : { asset: manifest.asset }),\n ...(manifest?.unix === undefined ? {} : { unix: manifest.unix }),\n }\n\n // Metadata is written while the tree is still in staging, so the swap below is two\n // renames and nothing else — the window where `.ui` does not exist shrinks to that.\n writeFileAtomic(path.join(root, META), `${JSON.stringify(meta, null, 2)}\\n`)\n\n return commitSwap(this.options.dataRoot, this.directory, root, meta)\n }\n\n /**\n * Puts a half-finished install back together, and is safe to call on every boot.\n *\n * An install that was interrupted between its two renames — a kill, a power cut, a\n * crash — leaves `.ui` missing with the user's only copy sitting in a `.previous-*`\n * sibling. Without this the panel would quietly serve the stock UI forever, because\n * `custom` is false and nothing ever looked at the backup.\n */\n recover(): { restored: string | null, swept: number } {\n const resting = this.directory\n let restored: string | null = null\n\n if (!fs.existsSync(path.join(resting, 'index.html'))) {\n const backup = newestBackup(this.options.dataRoot, resting)\n if (backup !== null) {\n fs.rmSync(resting, { recursive: true, force: true })\n fs.renameSync(backup, resting)\n restored = path.basename(backup)\n }\n }\n\n // Only after the live tree is whole again: a leftover backup is the last resort.\n return { restored, swept: sweepJunk(this.options.dataRoot) }\n }\n}\n\n/**\n * Serializes installs per data root. `Settings → Interface`, the boot hook and a second\n * panel can all reach the same `.ui`, and two interleaved swaps could leave it absent.\n * The key is the directory rather than the instance: every caller builds its own\n * `UiService`. Cross-process overlap is still possible, which is why the swap is\n * crash-recoverable — this closes the window that was open *within* a process.\n */\nconst installLocks = new Map<string, Promise<unknown>>()\n\nfunction withInstallLock<T>(dataRoot: string, run: () => Promise<T>): Promise<T> {\n const key = path.resolve(dataRoot)\n const previous = installLocks.get(key) ?? Promise.resolve()\n // Chain whether or not the predecessor succeeded: a failed install must not wedge the lock.\n const next = previous.then(run, run)\n installLocks.set(key, next.catch(() => {}))\n return next\n}\n\n/** Renames the staged tree into place, keeping the old one until that has certainly worked. */\nfunction commitSwap(dataRoot: string, resting: string, staged: string, meta: UiMeta): Promise<UiInstallResult> {\n return withInstallLock(dataRoot, async () => {\n // One rename, and no window at all: POSIX renames a directory onto an existing one.\n try {\n fs.renameSync(staged, resting)\n }\n catch {\n // Windows refuses that when the target is a non-empty directory, so the old tree\n // steps aside first — immediately, with no I/O in between.\n const previous = `${resting}.previous-${process.pid}-${Date.now()}`\n const hadPrevious = fs.existsSync(resting)\n if (hadPrevious)\n fs.renameSync(resting, previous)\n\n try {\n fs.renameSync(staged, resting)\n }\n catch (error) {\n // Put the user's UI back before reporting. If even that fails, keep the backup\n // and say where it is — deleting it would destroy the only copy.\n try {\n fs.rmSync(resting, { recursive: true, force: true })\n if (hadPrevious && fs.existsSync(previous))\n fs.renameSync(previous, resting)\n fs.rmSync(previous, { recursive: true, force: true })\n }\n catch {\n return { ok: false, error: `${describeError(error)} — the previous UI is kept at ${previous}` }\n }\n return { ok: false, error: describeError(error) }\n }\n\n fs.rmSync(previous, { recursive: true, force: true })\n }\n\n // Any backup older than this successful swap is now unreferenced; sweep them so an\n // interrupted install cannot leave a pile of stale full copies behind.\n sweepBackups(dataRoot, resting)\n return { ok: true, meta }\n })\n}\n\nfunction sweepBackups(dataRoot: string, resting: string): void {\n const prefix = `${path.basename(resting)}.previous-`\n for (const entry of readDataRoot(dataRoot)) {\n if (entry.startsWith(prefix))\n fs.rmSync(path.join(dataRoot, entry), { recursive: true, force: true })\n }\n}\n\n/** The most recently abandoned UI tree, or null when there is not one. */\nfunction newestBackup(dataRoot: string, resting: string): string | null {\n const prefix = `${path.basename(resting)}.previous-`\n let best: string | null = null\n let bestStamp = -1\n\n for (const entry of readDataRoot(dataRoot)) {\n if (!entry.startsWith(prefix))\n continue\n const full = path.join(dataRoot, entry)\n if (!fs.existsSync(path.join(full, 'index.html')))\n continue\n // `<name>.previous-<pid>-<stamp>`: the longest stamp is the newest attempt.\n const stamp = Number(entry.slice(prefix.length).split('-').pop() ?? '')\n if (Number.isFinite(stamp) && stamp > bestStamp) {\n bestStamp = stamp\n best = full\n }\n }\n return best\n}\n\n/** Dropped staging trees and superseded backups, once the live UI is whole. */\nfunction sweepJunk(dataRoot: string): number {\n let swept = 0\n for (const entry of readDataRoot(dataRoot)) {\n if (!entry.startsWith('.ui-staging-') && !entry.startsWith(`.ui.previous-`))\n continue\n fs.rmSync(path.join(dataRoot, entry), { recursive: true, force: true })\n swept += 1\n }\n return swept\n}\n\nfunction readDataRoot(dataRoot: string): string[] {\n try {\n return fs.readdirSync(dataRoot)\n }\n catch {\n return []\n }\n}\n\nfunction describeError(error: unknown): string {\n return error instanceof Error ? error.message : String(error)\n}\n\n/**\n * Where the site actually starts: the archive root, or a single wrapper directory\n * (`zip -r ui.zip dist` is a common way to build one).\n */\nfunction resolveRoot(staging: string): string | null {\n if (fs.existsSync(path.join(staging, 'index.html')))\n return staging\n\n const directories = fs.readdirSync(staging, { withFileTypes: true }).filter(entry => entry.isDirectory())\n if (directories.length !== 1)\n return null\n\n const inner = path.join(staging, directories[0]!.name)\n return fs.existsSync(path.join(inner, 'index.html')) ? inner : null\n}\n\nfunction readManifest(root: string): typeof manifestSchema.infer | null {\n try {\n const parsed = manifestSchema(JSON.parse(fs.readFileSync(path.join(root, META), 'utf8')))\n return parsed instanceof type.errors ? null : parsed\n }\n catch {\n return null\n }\n}\n\nfunction countFiles(root: string): number {\n let total = 0\n for (const entry of fs.readdirSync(root, { withFileTypes: true, recursive: true })) {\n if (entry.isFile() && entry.name !== META)\n total += 1\n }\n return Math.max(1, total)\n}\n","import fs from 'node:fs'\nimport os from 'node:os'\nimport path from 'node:path'\n/**\n * The GitHub-release side of installing a UI: which repo, which tag, which asset, how\n * to read the release metadata and how to download one. `ui-switch` picks a release by\n * hand and `ui-update` follows what an installed UI declared, but both go through this\n * one implementation — the failure messages and the asset matching are the same problem.\n */\n\nexport const DEFAULT_REPO = 'NamesMT/home-hosted'\n/** Mirrors the cap `UiService` enforces uncompressed — the same body, before it is parsed. */\nexport const MAX_DOWNLOAD_BYTES = 512 * 1024 * 1024\n\nexport interface RepoSlug {\n owner: string\n name: string\n}\n\nexport interface GithubAsset {\n name?: string\n url?: string\n browser_download_url?: string\n}\n\nexport interface GithubRelease {\n tag_name?: string\n name?: string\n draft?: boolean\n prerelease?: boolean\n published_at?: string\n assets?: GithubAsset[]\n}\n\n/** What both commands need from the CLI that owns the readline prompts and the colours. */\nexport interface UiSourceIo {\n write: (text: string) => void\n prompt?: (question: string) => Promise<string>\n style: {\n bold: (text: string) => string\n dim: (text: string) => string\n green: (text: string) => string\n }\n}\n\nexport interface UiSourceContext {\n io: UiSourceIo\n version: string\n token: string | null\n /** Suppress progress lines: the startup hook runs with nobody watching. */\n quiet?: boolean\n}\n\n/** `owner/name`, the only slug GitHub releases are addressed by. */\nexport function parseRepoSlug(value: string): RepoSlug | null {\n const match = /^([\\w.-]+)\\/([\\w.-]+)$/.exec(value.trim())\n if (match === null)\n return null\n return { owner: match[1]!, name: match[2]! }\n}\n\nexport function repoSlug(repo: RepoSlug): string {\n return `${repo.owner}/${repo.name}`\n}\n\n/** The repo the default tag rule is about; `DEFAULT_REPO` is its only definition. */\nexport function isOwnRepo(repo: RepoSlug): boolean {\n return repoSlug(repo).toLowerCase() === DEFAULT_REPO.toLowerCase()\n}\n\n/**\n * Our own release tag matches this CLI's version, so a UI is paired with the panel\n * it was built for. Another repo has no such pairing, so its latest release is used.\n * `null` means \"latest\".\n */\nexport function defaultReleaseTag(repo: RepoSlug, version: string): string | null {\n return isOwnRepo(repo) ? `v${version}` : null\n}\n\nexport function releaseApiUrl(repo: RepoSlug, tag: string | null): string {\n const base = `https://api.github.com/repos/${repo.owner}/${repo.name}/releases`\n if (tag === null || tag.length === 0 || tag === 'latest')\n return `${base}/latest`\n return `${base}/tags/${encodeURIComponent(tag)}`\n}\n\n/** Every published release, newest first, as GitHub orders them. */\nexport function releasesApiUrl(repo: RepoSlug, perPage = 30): string {\n return `https://api.github.com/repos/${repo.owner}/${repo.name}/releases?per_page=${perPage}`\n}\n\n/** Generous and predictable: a UI bundle is an asset whose name ends in `.zip`. */\nexport function isUiAsset(name: string): boolean {\n return /\\.zip$/i.test(name.trim())\n}\n\n/**\n * Resolves a wanted asset: an exact name, a case-insensitive name, or a single\n * unambiguous substring (`--asset stock` for `home-hosted-ui-stock.zip`).\n */\nexport function matchAsset(names: readonly string[], query: string): { ok: true, name: string } | { ok: false, error: string } {\n const wanted = query.trim()\n if (wanted.length === 0)\n return { ok: false, error: 'no asset name was given' }\n\n const exact = names.find(name => name === wanted)\n if (exact !== undefined)\n return { ok: true, name: exact }\n\n const lower = wanted.toLowerCase()\n const insensitive = names.filter(name => name.toLowerCase() === lower)\n if (insensitive.length === 1)\n return { ok: true, name: insensitive[0]! }\n\n const partial = names.filter(name => name.toLowerCase().includes(lower))\n if (partial.length === 0)\n return { ok: false, error: `no asset matches \"${wanted}\" (available: ${names.join(', ') || 'none'})` }\n if (partial.length > 1)\n return { ok: false, error: `\"${wanted}\" matches more than one asset: ${partial.join(', ')} — use the full name` }\n return { ok: true, name: partial[0]! }\n}\n\n/** `^https?://` means a URL; anything else is a filesystem path with `~` expanded. */\nexport function parseFileSource(value: string): { kind: 'url', url: string } | { kind: 'path', path: string } {\n const trimmed = value.trim()\n if (/^https?:\\/\\//i.test(trimmed))\n return { kind: 'url', url: trimmed }\n return { kind: 'path', path: expandHome(trimmed) }\n}\n\nfunction expandHome(value: string): string {\n if (value === '~')\n return os.homedir()\n if (value.startsWith('~/') || value.startsWith('~\\\\'))\n return path.join(os.homedir(), value.slice(2))\n return value\n}\n\n/**\n * A token is only ever sent to GitHub: `--file <url>` may point anywhere, and a\n * credential must not leak to a host the user did not vouch for.\n */\nexport function isGithubHost(url: string): boolean {\n try {\n const host = new URL(url).hostname.toLowerCase()\n return host === 'github.com' || host.endsWith('.github.com')\n || host === 'githubusercontent.com' || host.endsWith('.githubusercontent.com')\n }\n catch {\n return false\n }\n}\n\n/**\n * A hung connection must not become a hung command. `fetch` has no default timeout, and\n * the startup hook is fire-and-forget: without this a stalled request would keep that\n * promise (and its temp directory) pending for as long as the process lives.\n */\nconst REQUEST_TIMEOUT_MS = 30_000\nconst DOWNLOAD_TIMEOUT_MS = 120_000\n\nfunction timeoutSignal(ms: number): AbortSignal | undefined {\n // `AbortSignal.timeout` exists from Node 17.3; the engines field requires 24.\n return typeof AbortSignal !== 'undefined' && typeof AbortSignal.timeout === 'function'\n ? AbortSignal.timeout(ms)\n : undefined\n}\n\nfunction isTimeout(error: unknown): boolean {\n return error instanceof Error && (error.name === 'TimeoutError' || error.name === 'AbortError')\n}\n\n/** What `UiService` names the install when the archive carries no `ui.json`. */\nexport function fallbackUiName(source: string): string {\n const base = path.basename(source).replace(/\\.zip$/i, '')\n const stripped = base.replace(/^home-hosted-ui-/i, '')\n return stripped.length > 0 ? stripped : 'custom-ui'\n}\n\n/** One release's assets, or a message that says what was wrong with the answer. */\nexport async function fetchRelease(repo: RepoSlug, tag: string | null, context: UiSourceContext): Promise<{ tag: string, assets: GithubAsset[] }> {\n const url = releaseApiUrl(repo, tag)\n let response: Response\n try {\n response = await fetch(url, { headers: apiHeaders(context), signal: timeoutSignal(REQUEST_TIMEOUT_MS) })\n }\n catch (error) {\n throw new Error(isTimeout(error) ? `GitHub did not answer within ${REQUEST_TIMEOUT_MS / 1000}s` : `could not reach GitHub: ${describeError(error)}`)\n }\n\n if (!response.ok)\n throw new Error(describeReleaseFailure(response.status, repo, tag))\n\n const body = await response.json() as GithubRelease\n return { tag: body.tag_name ?? tag ?? 'latest', assets: Array.isArray(body.assets) ? body.assets : [] }\n}\n\n/** Every published release, newest first. Drafts and unusable entries are dropped. */\nexport async function fetchReleases(repo: RepoSlug, context: UiSourceContext, perPage = 30): Promise<Array<{ tag: string, assets: GithubAsset[], publishedAt: string | null }>> {\n let response: Response\n try {\n response = await fetch(releasesApiUrl(repo, perPage), { headers: apiHeaders(context), signal: timeoutSignal(REQUEST_TIMEOUT_MS) })\n }\n catch (error) {\n throw new Error(isTimeout(error) ? `GitHub did not answer within ${REQUEST_TIMEOUT_MS / 1000}s` : `could not reach GitHub: ${describeError(error)}`)\n }\n\n if (!response.ok)\n throw new Error(describeReleaseFailure(response.status, repo, null))\n\n const body = await response.json()\n if (!Array.isArray(body))\n return []\n\n return (body as GithubRelease[])\n .filter(release => release.draft !== true && typeof release.tag_name === 'string')\n .map(release => ({\n tag: release.tag_name!,\n assets: Array.isArray(release.assets) ? release.assets : [],\n publishedAt: typeof release.published_at === 'string' ? release.published_at : null,\n }))\n}\n\nexport function assetDownloadUrl(asset: GithubAsset): string {\n const url = asset.url ?? asset.browser_download_url\n if (url === undefined || url.length === 0)\n throw new Error(`the release metadata for \"${asset.name ?? 'an asset'}\" carries no download URL`)\n return url\n}\n\n/** Streams to `os.tmpdir()` so a large body is never buffered in memory. */\nexport async function downloadToTemp(url: string, headers: Record<string, string>, context: UiSourceContext): Promise<{ dir: string, file: string }> {\n let response: Response\n try {\n response = await fetch(url, { headers, redirect: 'follow', signal: timeoutSignal(DOWNLOAD_TIMEOUT_MS) })\n }\n catch (error) {\n throw new Error(isTimeout(error) ? `the download stalled for ${DOWNLOAD_TIMEOUT_MS / 1000}s: ${url}` : `could not reach ${url}: ${describeError(error)}`)\n }\n\n if (!response.ok)\n throw new Error(describeDownloadFailure(response.status, url))\n\n const declared = Number(response.headers.get('content-length') ?? '0')\n if (!Number.isFinite(declared) || declared < 0)\n throw new Error(`the download from ${url} reported an unusable size`)\n if (declared > MAX_DOWNLOAD_BYTES)\n throw new Error(tooLargeMessage(declared))\n if (response.body === null)\n throw new Error(`the download from ${url} had no body`)\n\n const dir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'hh-ui-'))\n const file = path.join(dir, 'ui.zip')\n const handle = await fs.promises.open(file, 'w')\n let received = 0\n\n try {\n for await (const chunk of response.body) {\n received += chunk.length\n if (received > MAX_DOWNLOAD_BYTES)\n throw new Error(tooLargeMessage(received))\n await handle.write(chunk)\n }\n }\n catch (error) {\n await handle.close()\n fs.rmSync(dir, { recursive: true, force: true })\n throw error instanceof Error ? error : new Error(String(error))\n }\n await handle.close()\n\n if (context.quiet !== true)\n context.io.write(`${context.io.style.dim(`downloaded ${formatBytes(received)}`)}\\n`)\n return { dir, file }\n}\n\nexport function apiHeaders(context: UiSourceContext): Record<string, string> {\n const headers: Record<string, string> = {\n 'accept': 'application/vnd.github+json',\n 'user-agent': `home-hosted/${context.version}`,\n 'x-github-api-version': '2022-11-28',\n }\n if (context.token !== null && context.token.length > 0)\n headers.authorization = `Bearer ${context.token}`\n return headers\n}\n\nexport function noAssetsMessage(where: string, skipped: string[], tag: string | null): string {\n const lines = [`no usable UI assets in ${where} (expected a .zip)`]\n lines.push(skipped.length > 0 ? ` skipped: ${skipped.join(', ')}` : ' the release has no assets at all')\n if (tag !== null && tag !== 'latest')\n lines.push(' installing a different version silently is worse than failing: try the newest release with --tag latest')\n return lines.join('\\n')\n}\n\nexport function describeReleaseFailure(status: number, repo: RepoSlug, tag: string | null): string {\n const slug = repoSlug(repo)\n if (status === 404) {\n if (tag !== null && tag !== 'latest')\n return `no release tagged \"${tag}\" in ${slug}\\n list what exists with: home-hosted ui-switch --repo ${slug} --tag latest --list`\n return `no repository or published release at ${slug}\\n check the --repo slug (owner/name), and that the repository is public`\n }\n if (status === 403 || status === 429)\n return `GitHub refused the request (HTTP ${status}) — the unauthenticated API rate limit is per address.\\n set a token to raise it: --token <token>, or GITHUB_TOKEN / GH_TOKEN`\n if (status === 401)\n return 'GitHub rejected the token (HTTP 401) — check --token, GITHUB_TOKEN or GH_TOKEN'\n return `GitHub answered HTTP ${status} while reading the release of ${slug}`\n}\n\nfunction describeDownloadFailure(status: number, url: string): string {\n const github = isGithubHost(url)\n if (status === 404) {\n return github\n ? `the asset is gone (HTTP 404) — ${url}\\n the release may have been rebuilt since it was listed; run the command again`\n : `nothing is served at that URL (HTTP 404) — ${url}`\n }\n if (status === 403 || status === 429) {\n return github\n ? `GitHub refused the download (HTTP ${status}) — a token raises the rate limit: --token <token>, or GITHUB_TOKEN / GH_TOKEN`\n : `the host refused the download (HTTP ${status}) — ${url}`\n }\n if (status === 401 && github)\n return 'GitHub rejected the token on the download (HTTP 401) — check --token, GITHUB_TOKEN or GH_TOKEN'\n return `the download failed (HTTP ${status}) — ${url}`\n}\n\nfunction tooLargeMessage(bytes: number): string {\n return `the download is ${formatBytes(bytes)}, larger than the ${MAX_DOWNLOAD_BYTES / 1024 / 1024}MB a UI may be`\n}\n\nexport function formatBytes(bytes: number): string {\n if (bytes < 1024)\n return `${bytes} B`\n if (bytes < 1024 * 1024)\n return `${Math.round(bytes / 1024)} KB`\n return `${(bytes / 1024 / 1024).toFixed(1)} MB`\n}\n\nexport function describeError(error: unknown): string {\n return error instanceof Error ? error.message : String(error)\n}\n\n/**\n * Tag ordering: `v1.2.3` beats `v1.2.2`, a prerelease loses to its release, and anything\n * that is not a version sorts below every version that is. Enough to answer \"is this\n * release newer than the one the UI came from\" without a semver dependency.\n */\nexport function compareTags(a: string, b: string): number {\n const left = parseTag(a)\n const right = parseTag(b)\n if (left === null && right === null)\n return a.localeCompare(b)\n if (left === null)\n return -1\n if (right === null)\n return 1\n\n for (let index = 0; index < 3; index++) {\n const difference = (left.parts[index] ?? 0) - (right.parts[index] ?? 0)\n if (difference !== 0)\n return difference\n }\n\n // A release outranks its own prereleases, and two prereleases are ordered by their\n // identifiers — `rc.2` after `rc.1`. Comparing only \"is it a prerelease\" made every\n // pair of them equal, so `ui-update` hid each one from the other.\n if (left.prerelease.length === 0 && right.prerelease.length === 0)\n return 0\n if (left.prerelease.length === 0)\n return 1\n if (right.prerelease.length === 0)\n return -1\n\n for (let index = 0; index < Math.max(left.prerelease.length, right.prerelease.length); index++) {\n const one = left.prerelease[index]\n const two = right.prerelease[index]\n if (one === undefined)\n return -1\n if (two === undefined)\n return 1\n if (one === two)\n continue\n const numeric = /^\\d+$/\n if (numeric.test(one) && numeric.test(two))\n return Number(one) - Number(two)\n // Numeric identifiers rank below alphanumeric ones, per semver.\n if (numeric.test(one))\n return -1\n if (numeric.test(two))\n return 1\n return one.localeCompare(two)\n }\n return 0\n}\n\nfunction parseTag(tag: string): { parts: number[], prerelease: string[] } | null {\n const match = /^v?(\\d+)(?:\\.(\\d+))?(?:\\.(\\d+))?(?:-([0-9A-Za-z.-]+))?(?:\\+.*)?$/.exec(tag.trim())\n if (match === null)\n return null\n return {\n parts: [Number(match[1]), Number(match[2] ?? 0), Number(match[3] ?? 0)],\n prerelease: match[4] === undefined ? [] : match[4].split('.'),\n }\n}\n","import type { UiSourceContext } from '#src/providers/ui-release'\nimport type { UiService } from '#src/services/ui'\nimport process from 'node:process'\nimport { logger } from '#src/helpers/logger'\nimport { appVersion } from '#src/helpers/version'\nimport {\n assetDownloadUrl,\n DEFAULT_REPO,\n downloadToTemp,\n fetchRelease,\n isOwnRepo,\n isUiAsset,\n matchAsset,\n parseRepoSlug,\n} from '#src/providers/ui-release'\n\n/**\n * Keeping an *official* UI paired with the panel that serves it, without a person\n * having to notice. A UI from `NamesMT/home-hosted` is built for a release, and this\n * panel knows its own release, so \"the wrong tag\" is a fact rather than a preference —\n * unlike someone else's UI, where only the person can say what they want.\n *\n * Only ever installed by tag, never by guess, and every failure is a log line: this runs\n * at startup, and a UI problem must never stop a panel from serving.\n */\n\nexport type UiSyncResult = { kind: 'not-custom' }\n | { kind: 'foreign' }\n | { kind: 'no-identity' }\n | { kind: 'current', tag: string }\n | { kind: 'updated', tag: string }\n | { kind: 'failed', error: string }\n\n/**\n * `ui.json` says which release this build came from. Anything else — an unofficial UI,\n * or one that never declared itself — is left for `home-hosted ui-update`.\n */\nexport function officialTagFor(\n meta: { repo?: string, tag?: string } | null,\n runningVersion: string,\n): { tag: string, repo: string } | null {\n if (meta === null || typeof meta.repo !== 'string')\n return null\n const repo = parseRepoSlug(meta.repo)\n if (repo === null || !isOwnRepo(repo))\n return null\n return { tag: `v${runningVersion}`, repo: `${repo.owner}/${repo.name}` }\n}\n\nexport async function syncOfficialUi(ui: UiService, runningVersion = appVersion()): Promise<UiSyncResult> {\n if (!ui.custom)\n return { kind: 'not-custom' }\n\n const meta = ui.readMeta()\n if (meta === null)\n return { kind: 'no-identity' }\n\n const repo = meta.repo === undefined ? null : parseRepoSlug(meta.repo)\n if (repo === null)\n return { kind: 'no-identity' }\n if (!isOwnRepo(repo))\n return { kind: 'foreign' }\n\n const target = `v${runningVersion}`\n if (meta.tag === target)\n return { kind: 'current', tag: target }\n\n const context: UiSourceContext = {\n io: { write: () => {}, style: { bold: (t: string) => t, dim: (t: string) => t, green: (t: string) => t } },\n version: runningVersion,\n // The one unattended call may use a token like the interactive commands do; without\n // one, a shared or CI address hits GitHub's 60/hour unauthenticated limit.\n token: process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN ?? null,\n quiet: true,\n }\n\n try {\n const release = await fetchRelease(repo, target, context)\n const names = release.assets.map(asset => asset.name ?? '').filter(isUiAsset)\n if (names.length === 0)\n throw new Error(`no UI asset in ${DEFAULT_REPO}@${release.tag}`)\n\n // The asset this UI came from, or the only one on offer when it never said.\n const wanted = meta.asset ?? ''\n const matched = wanted.length > 0 ? matchAsset(names, wanted) : { ok: true as const, name: names[0]! }\n if (!matched.ok)\n throw new Error(matched.error)\n\n const asset = release.assets.find(entry => entry.name === matched.name)\n if (asset === undefined)\n throw new Error(`no asset named ${matched.name} in ${release.tag}`)\n\n const download = await downloadToTemp(assetDownloadUrl(asset), {\n 'accept': 'application/octet-stream',\n 'user-agent': `home-hosted/${runningVersion}`,\n }, context)\n\n try {\n // The tag is the release we just fetched, never what the archive claims.\n const result = await ui.install(download.file, matched.name.replace(/\\.zip$/i, ''), release.tag)\n if (!result.ok)\n throw new Error(result.error)\n }\n finally {\n const fs = await import('node:fs')\n fs.rmSync(download.dir, { recursive: true, force: true })\n }\n\n return { kind: 'updated', tag: release.tag }\n }\n catch (error) {\n return { kind: 'failed', error: error instanceof Error ? error.message : String(error) }\n }\n}\n\n/**\n * The startup hook. Never awaited by the caller and never allowed to throw: the panel\n * serves the UI it already has while this runs, and the next request picks up the new\n * one, because `UiService.resolveDir()` is read per request.\n */\nexport function autoUpdateOfficialUi(ui: UiService, runningVersion = appVersion()): void {\n if (!ui.custom)\n return\n\n const meta = ui.readMeta()\n const plan = officialTagFor(meta, runningVersion)\n if (plan === null || meta?.tag === plan.tag)\n return\n\n logger.info(`ui: ${meta?.name ?? 'custom UI'} ${meta?.version ?? ''} came from ${meta?.tag ?? 'an unknown release'}; this panel is ${plan.tag} — updating`)\n\n void syncOfficialUi(ui, runningVersion).then((result) => {\n if (result.kind === 'updated')\n logger.info(`ui: updated to ${result.tag} — refresh the browser`)\n else if (result.kind === 'failed')\n logger.warn(`ui: could not update to ${plan.tag}: ${result.error}`)\n }).catch((error: unknown) => {\n logger.warn(`ui: could not update: ${error instanceof Error ? error.message : String(error)}`)\n })\n}\n","import type { AppType } from '#src/app'\nimport type { Runtime } from '#src/helpers/daemon'\nimport fs from 'node:fs'\nimport os from 'node:os'\nimport path from 'node:path'\nimport process from 'node:process'\nimport { fileURLToPath } from 'node:url'\nimport { createRootApp } from '#src/app'\nimport { SecretsStore } from '#src/config/secrets'\nimport { SEED_CONFIG } from '#src/config/seed'\nimport { ConfigStore } from '#src/config/store'\nimport { clearRuntime, isProcessAlive, newToken, readRuntime, writeRuntime } from '#src/helpers/daemon'\nimport { logger } from '#src/helpers/logger'\nimport { openBrowser } from '#src/helpers/open'\nimport {\n daemonLogPath,\n dataRoot,\n defaultConfigPath,\n defaultHistoryPath,\n defaultLogsDir,\n defaultSecretsPath,\n defaultTlsDir,\n projectDir,\n resolveUserPath,\n} from '#src/helpers/paths'\nimport { resolveTemplate } from '#src/helpers/template'\nimport { appVersion } from '#src/helpers/version'\nimport { isPortFree } from '#src/providers/port'\nimport { AuthService, DEFAULT_PASSWORD } from '#src/services/auth'\nimport { BackupService, resolveBackupPaths } from '#src/services/backups'\nimport { ConfigWatch } from '#src/services/config-watch'\nimport { ControlServer } from '#src/services/control-server'\nimport { EventHub } from '#src/services/events'\nimport { checkExposure } from '#src/services/exposure'\nimport { HistoryStore } from '#src/services/history'\nimport { HostMonitor } from '#src/services/host-monitor'\nimport { LogFiles } from '#src/services/log-files'\nimport { NotificationService } from '#src/services/notifications'\nimport { buildAppState } from '#src/services/state'\nimport { Supervisor } from '#src/services/supervisor'\nimport { TlsStore } from '#src/services/tls'\nimport { UiService } from '#src/services/ui'\nimport { autoUpdateOfficialUi } from '#src/services/ui-update'\nimport { parseBind } from '#src/shared/contracts'\n\n/** The package root: one level above this file, whether it is `src/` or `dist/`. */\nexport const packageRoot = path.resolve(fileURLToPath(new URL('..', import.meta.url)))\n\nfunction packageVersion(): string {\n try {\n const manifest = JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8')) as { version?: string }\n return manifest.version ?? '0.0.0'\n }\n catch {\n return '0.0.0'\n }\n}\n\nexport interface ControlPlaneOptions {\n /** `--config`, or `$HHOSTED_HOME/servers.config.json`. */\n configPath?: string\n /** One-off overrides, persisted only after every guard below passes. */\n port?: number\n host?: string\n autostart: boolean\n open: boolean\n /** Print the effective config and exit without starting anything. */\n printConfig: boolean\n}\n\n/** A probe target for a `lan` bind, which is not connectable as `0.0.0.0`. */\nfunction probeHostFor(bindHost: string): string {\n return bindHost === '0.0.0.0' || bindHost === '::' ? '127.0.0.1' : bindHost\n}\n\n/**\n * Runs the control plane in *this* process until it is stopped. `home-hosted up`\n * detaches a child that calls this; `--foreground` (systemd, docker) calls it\n * directly.\n */\nexport async function runControlPlane(options: ControlPlaneOptions): Promise<void> {\n const existing = readRuntime()\n if (existing !== null && existing.pid !== process.pid && isProcessAlive(existing.pid)) {\n logger.error(`already running (pid ${existing.pid}) at ${existing.url} — run \\`home-hosted down\\` first`)\n process.exit(1)\n }\n if (existing !== null)\n clearRuntime()\n\n const configPath = options.configPath ?? defaultConfigPath\n const store = new ConfigStore(configPath, SEED_CONFIG)\n store.load()\n store.writeJsonSchema()\n\n // A config this release cannot read is refused rather than run with defaults: the\n // groups would fall back silently, and for `control` that means a different port,\n // bind and auth policy than the file asked for. Pinning the release that wrote it,\n // or migrating, is the way forward — see the Compatibility section of AGENTS.md.\n // Nothing is written before this point, so a refused start leaves the file alone.\n if (store.configError !== null) {\n logger.error(`refusing to start: ${store.configError}`)\n logger.info(`fix ${store.path}, or install the release that wrote it`)\n process.exit(1)\n }\n if (store.pendingMigrations.length > 0) {\n logger.error(`refusing to start: ${store.path} needs ${store.pendingMigrations.length} migration(s) before ${appVersion()} can use it`)\n logger.info('run `home-hosted migrate` to see and apply them')\n process.exit(1)\n }\n\n const secrets = new SecretsStore(defaultSecretsPath)\n const auth = new AuthService(secrets, () => store.config.control.auth)\n const tls = new TlsStore(defaultTlsDir)\n const logFiles = new LogFiles(defaultLogsDir, () => store.config.logs)\n const history = new HistoryStore(defaultHistoryPath)\n const notifications = new NotificationService(\n secrets,\n () => store.config.notifications,\n () => store.config.logs,\n )\n const hostMonitor = new HostMonitor(\n () => store.config.host,\n target => resolveUserPath(resolveTemplate(target, { projectDir, dataRoot, home: os.homedir() })),\n notifications,\n )\n // Restoring a backup replaces the config file, which no store write covers: the\n // hook below re-reads it and brings the restored autostart entries up, so a\n // blank instance ends up running the setup the archive carried.\n let onConfigRestored: (() => void) | undefined\n const backups = new BackupService({\n dataRoot,\n getConfig: () => store.config.backups,\n getSources: () => ({\n configPath: store.path,\n secretsPath: secrets.path,\n tlsDir: tls.directory,\n paths: resolveBackupPaths(store.servers, store.config.backups.includePaths),\n }),\n onConfigRestored: () => onConfigRestored?.(),\n })\n\n // Auth is on by default, so a first boot needs *a* password; the default is\n // deliberately weak and flagged, which keeps LAN/exposure binding blocked (and\n // is announced on the login page) until it is changed.\n if (!auth.passwordSet) {\n auth.ensureDefaultPassword(DEFAULT_PASSWORD)\n logger.warn(`no password was set — created the default \"${DEFAULT_PASSWORD}\"; change it in Settings → Authentication`)\n }\n\n const configured = store.config.control\n const intendedHost = options.host === undefined ? configured.host : parseBind(options.host)\n if (intendedHost === null) {\n logger.error(`invalid control host: ${String(options.host)} (expected local, lan or an ipv4 address)`)\n process.exit(1)\n }\n\n const intended = { host: intendedHost, port: options.port ?? configured.port }\n if (!Number.isInteger(intended.port) || intended.port <= 0 || intended.port > 65535) {\n logger.error(`invalid control port: ${String(options.port)}`)\n process.exit(1)\n }\n\n if (options.printConfig) {\n process.stdout.write(`${JSON.stringify(store.config, null, 2)}\\n`)\n return\n }\n\n // Never serve the panel beyond loopback without a password behind it.\n const exposure = checkExposure({ ...configured, host: intended.host }, auth.passwordSet, auth.usingDefaultPassword)\n if (exposure.blockedReason !== null) {\n logger.error(`refusing to start: ${exposure.blockedReason}`)\n logger.info('bind the panel back to `local`, or set a password with `home-hosted set-password` and enable auth in the settings page')\n process.exit(1)\n }\n\n if (!(await isPortFree(intended.port))) {\n logger.error(`control port ${intended.port} is already in use — is another home-hosted running?`)\n process.exit(1)\n }\n\n if (intended.host !== configured.host || intended.port !== configured.port)\n store.updateControl({ host: intended.host, port: intended.port })\n\n const ui = new UiService({ dataRoot, stockDir: path.join(packageRoot, 'uis', 'stock', 'dist') })\n // An install killed between its two renames leaves `.ui` missing and the user's copy in\n // a backup; put that back before anything reads the directory, or the panel would serve\n // the stock UI forever with the real one sitting right beside it.\n const uiRecovery = ui.recover()\n if (uiRecovery.restored !== null)\n logger.warn(`ui: restored the installed UI from ${uiRecovery.restored} — an earlier update was interrupted`)\n const hub = new EventHub()\n let app: AppType | undefined\n const token = newToken()\n\n const controlServer = new ControlServer(\n {\n fetch: (request) => {\n if (!app)\n throw new Error('the control app is not ready yet')\n return app.fetch(request)\n },\n trustProxy: () => store.config.control.auth.trustProxy,\n tls: () => (store.config.control.tls.enabled ? tls.load() : null),\n },\n { host: intended.host, port: intended.port, tls: store.config.control.tls.enabled },\n )\n\n const supervisor = new Supervisor(store, hub, {\n configPath: store.path,\n control: controlServer.endpoint,\n buildState: views => buildAppState({\n store,\n auth,\n control: controlServer.endpoint,\n tls,\n notifications,\n hostMonitor,\n backups,\n logsDir: logFiles.directory,\n views,\n }),\n history,\n logFiles,\n notifications,\n hostMonitor,\n })\n\n /**\n * A config edited by hand — a text editor, a `git checkout`, a config-management\n * tool — is picked up without a restart. A revision this release cannot read is\n * reported in the state frame and the panel keeps running what it had, so a typo\n * never stops a server. A definition that *did* change takes effect on that\n * entry's next start; a newly added entry with `autostart` starts now, the way it\n * would after a restart, and a removed one is stopped and forgotten.\n */\n let lastConfigError: string | null = store.configError\n const configWatch = new ConfigWatch({\n file: store.path,\n onChange: () => {\n const before = new Set(store.servers.map(server => server.id))\n const result = store.reloadFromDisk()\n\n // One line per change of state, not one per poll: the file stays bad until\n // somebody fixes it.\n if (store.configError !== lastConfigError) {\n if (store.configError === null)\n logger.info('the config file is readable again')\n else\n logger.error(`${store.configError} — keeping the config already running`)\n lastConfigError = store.configError\n }\n\n if (!result.applied) {\n if (result.changed && result.error === null)\n logger.info('the config file changed, but not in a way that changes the config')\n return\n }\n\n const added = store.servers.filter(server => !before.has(server.id))\n const removed = [...before].filter(id => !store.getServer(id))\n logger.info(`config reloaded from disk — ${store.servers.length} server(s)${added.length === 0 ? '' : `, ${added.length} added`}${removed.length === 0 ? '' : `, ${removed.length} removed`}`)\n\n // `--no-autostart` means \"do not start anything on your own\", and a reload is\n // not an exception to that.\n if (!options.autostart)\n return\n for (const server of added) {\n // A disabled entry is left alone even when the file says autostart.\n if (!server.enabled || !server.autostart)\n continue\n void supervisor.start(server.id).catch((error: unknown) => {\n logger.error(`could not start the added server ${server.id}`, error)\n })\n }\n },\n onError: error => logger.warn(`cannot watch ${path.basename(store.path)} for changes: ${error instanceof Error ? error.message : String(error)}`),\n })\n configWatch.start()\n\n let shuttingDown = false\n const shutdown = async (reason: string): Promise<void> => {\n if (shuttingDown)\n return\n shuttingDown = true\n logger.info(`${reason} — stopping ${supervisor.views().length} server(s)`)\n clearRuntime()\n configWatch.dispose()\n auth.dispose()\n await supervisor.dispose()\n logFiles.dispose()\n history.dispose()\n await controlServer.close(true)\n process.exit(0)\n }\n\n app = createRootApp({\n store,\n supervisor,\n hub,\n auth,\n secrets,\n controlServer,\n tls,\n logFiles,\n notifications,\n backups,\n ui,\n runtimeToken: token,\n onShutdown: () => shutdown('shutdown requested locally'),\n })\n\n await controlServer.start()\n // Reads every existing archive once, so the first state frame already shows\n // which backups are password-protected.\n await backups.warm()\n\n const endpoint = controlServer.endpoint\n const runtime: Runtime = {\n version: packageVersion(),\n pid: process.pid,\n url: endpoint.url,\n probeUrl: `${endpoint.protocol}://${probeHostFor(endpoint.bindHost)}:${endpoint.port}`,\n protocol: endpoint.protocol,\n port: endpoint.port,\n bindHost: endpoint.bindHost,\n startedAt: Date.now(),\n projectDir,\n dataRoot,\n configPath: store.path,\n logFile: daemonLogPath,\n token,\n }\n writeRuntime(runtime)\n\n logger.box(`home-hosted ${runtime.version}\\n${endpoint.url}`)\n logger.info(`config: ${store.path}`)\n logger.info(`secrets: ${secrets.path}${auth.passwordSet ? '' : ' (no password set)'}`)\n logger.info(`auth: ${auth.isRequired() ? 'required' : 'disabled'}${auth.usingDefaultPassword ? ' (default password)' : ''}${auth.apiTokenSet ? ' · API token set' : ''}${exposure.exposed ? ' · exposed beyond loopback' : ''}`)\n logger.info(`logs: ${store.config.logs.persist ? `${logFiles.directory} (max ${store.config.logs.maxBytes} B x ${store.config.logs.keep})` : 'memory only'}`)\n logger.info(`project: ${projectDir}`)\n if (ui.custom) {\n const meta = ui.status().meta\n logger.warn(`custom UI in use${meta === null ? '' : ` (${meta.name}${meta.version === null ? '' : ` ${meta.version}`})`} — if it breaks, run \\`home-hosted ui-revert\\``)\n // An official UI is paired with a release, so a panel upgrade re-pairs it without\n // asking. Never awaited: the UI already on disk keeps serving until it lands.\n autoUpdateOfficialUi(ui, runtime.version)\n }\n for (const warning of store.configWarnings)\n logger.warn(warning)\n for (const entry of supervisor.views())\n logger.info(` ${entry.id.padEnd(12)} ${entry.config.command} ${entry.config.args.join(' ')}`.trimEnd())\n\n if (configured.openBrowser || options.open)\n openBrowser(endpoint.url)\n\n if (options.autostart) {\n void supervisor.startAll({ autostartOnly: true }).catch((error: unknown) => {\n logger.error('autostart failed', error)\n })\n }\n\n onConfigRestored = () => {\n store.load()\n\n // The restored config is a config write like any other, so the exposure rule\n // applies: a backup taken from a local instance must not open a LAN panel.\n const exposure = checkExposure(store.config.control, auth.passwordSet, auth.usingDefaultPassword)\n if (exposure.blockedReason !== null) {\n logger.warn(`the restored config would expose the panel (${exposure.blockedReason}) — forcing authentication on`)\n store.updateControl({ auth: { enabled: true } })\n }\n\n logger.info(`config restored — ${store.servers.length} server(s) reloaded`)\n // `--no-autostart` means \"do not start anything on your own\", restores included.\n if (!options.autostart)\n return\n void supervisor.startAll({ autostartOnly: true }).catch((error: unknown) => {\n logger.error('could not start the restored servers', error)\n })\n }\n\n // A failure while tearing down must still end the process: a rejected promise\n // here would leave the panel half-stopped.\n const onSignal = (signal: string): void => {\n void shutdown(signal).catch((error: unknown) => {\n logger.error(`shutdown after ${signal} failed`, error)\n process.exit(1)\n })\n }\n process.on('SIGINT', () => onSignal('SIGINT'))\n process.on('SIGTERM', () => onSignal('SIGTERM'))\n}\n","import type { ChildProcess } from 'node:child_process'\nimport type { UpFlags } from '#src/cli/args'\nimport { spawn } from 'node:child_process'\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport process from 'node:process'\nimport { defineCommand } from 'citty'\nimport { buildDaemonArgv } from '#src/cli/args'\nimport { bold, delay, dim, fail, green, paint } from '#src/cli/io'\n\n/** `up` starts the panel; without `--foreground` it re-spawns itself detached. */\n\nconst DEFAULT_PORT = 3999\nconst LOG_ROTATE_BYTES = 5 * 1024 * 1024\n\nexport const upArgs = {\n config: { type: 'string', alias: 'c', description: 'servers config (default: <state>/servers.config.json)' },\n port: { type: 'string', alias: 'p', description: `control panel port (default: ${DEFAULT_PORT})` },\n host: { type: 'string', description: 'local | lan | an ipv4 address (default: local)' },\n autostart: { type: 'boolean', default: true, negativeDescription: 'do not start the entries marked autostart' },\n open: { type: 'boolean', description: 'open the panel in a browser once it is up' },\n foreground: { type: 'boolean', description: 'run in this process instead of detaching (systemd/docker)' },\n printConfig: { type: 'boolean', description: 'print the effective config and exit' },\n} as const\n\ninterface RawUpArgs {\n config?: string\n port?: string\n host?: string\n autostart?: boolean\n open?: boolean\n foreground?: boolean\n printConfig?: boolean\n}\n\n/** citty only parses; the port's range and the boolean defaults are decided here. */\nexport function toUpFlags(args: RawUpArgs): UpFlags {\n let port: number | undefined\n if (args.port !== undefined) {\n port = Number.parseInt(args.port, 10)\n if (!Number.isInteger(port) || port <= 0 || port > 65535)\n fail(`invalid port: ${args.port}`)\n }\n\n return {\n config: args.config,\n port,\n host: args.host,\n autostart: args.autostart !== false,\n open: args.open === true,\n foreground: args.foreground === true,\n printConfig: args.printConfig === true,\n }\n}\n\n/**\n * How to run this CLI again in the same runtime. Under tsx that means passing the\n * resolved loader too, because the daemon's working directory is the project's,\n * not the package's.\n */\nfunction runtimeArgs(): string[] {\n let resolved: string | null = null\n const resolveTsx = (): string => resolved ??= import.meta.resolve('tsx')\n\n return process.execArgv.map((arg) => {\n if (arg === 'tsx')\n return resolveTsx()\n if (arg.startsWith('--import=') && arg.slice('--import='.length) === 'tsx')\n return `--import=${resolveTsx()}`\n return arg\n })\n}\n\n/** One rotation is enough for a console log. */\nfunction rotateLog(file: string): void {\n try {\n if (fs.statSync(file).size < LOG_ROTATE_BYTES)\n return\n fs.rmSync(`${file}.1`, { force: true })\n fs.renameSync(file, `${file}.1`)\n }\n catch {\n // no log yet\n }\n}\n\nfunction tailLog(file: string, lines = 15): string {\n try {\n return fs.readFileSync(file, 'utf8').split('\\n').slice(-lines).join('\\n').trimEnd()\n }\n catch {\n return ''\n }\n}\n\nexport async function runUp(flags: UpFlags, entry: string): Promise<void> {\n const { runControlPlane } = await import('#src/index')\n\n // `--print-config` reports the effective config and returns; it never detaches,\n // because there would be a daemon left with nothing to serve.\n if (flags.foreground || flags.printConfig) {\n await runControlPlane({\n configPath: flags.config,\n port: flags.port,\n host: flags.host,\n autostart: flags.autostart,\n open: flags.open,\n printConfig: flags.printConfig,\n })\n return\n }\n\n const { clearRuntime, isProcessAlive, readRuntime } = await import('#src/helpers/daemon')\n const { daemonLogPath, dataRoot, projectDir } = await import('#src/helpers/paths')\n\n const existing = readRuntime()\n if (existing !== null && isProcessAlive(existing.pid)) {\n process.stdout.write(`${green('already running')} (pid ${existing.pid}) at ${existing.url}\\n`)\n process.stdout.write(`${dim('stop it with `home-hosted down`')}\\n`)\n return\n }\n if (existing !== null)\n clearRuntime()\n\n fs.mkdirSync(path.dirname(daemonLogPath), { recursive: true })\n rotateLog(daemonLogPath)\n const log = fs.openSync(daemonLogPath, 'a')\n\n const child = spawn(process.execPath, [...runtimeArgs(), entry, ...buildDaemonArgv(flags)], {\n detached: true,\n cwd: projectDir,\n env: { ...process.env, HHOSTED_HOME: dataRoot, HHOSTED_PROJECT: projectDir },\n stdio: ['ignore', log, log],\n windowsHide: true,\n })\n child.unref()\n fs.closeSync(log)\n\n const runtime = await waitForStartup(child)\n if (runtime === null) {\n const output = tailLog(daemonLogPath)\n process.stderr.write(`${paint('31', 'error')} the control panel did not start\\n`)\n if (output.length > 0)\n process.stderr.write(`${dim(`${daemonLogPath}:`)}\\n${output}\\n`)\n process.exit(1)\n }\n\n process.stdout.write(`${green('home-hosted is up')} (pid ${runtime.pid})\\n`)\n process.stdout.write(` ${bold(runtime.url)}\\n`)\n process.stdout.write(` ${dim(`project ${runtime.projectDir}`)}\\n`)\n process.stdout.write(` ${dim(`state ${runtime.dataRoot}`)}\\n`)\n process.stdout.write(` ${dim(`log ${runtime.logFile}`)}\\n`)\n}\n\nasync function waitForStartup(child: ChildProcess, timeoutMs = 20000) {\n const { readRuntime } = await import('#src/helpers/daemon')\n const deadline = Date.now() + timeoutMs\n\n for (;;) {\n if (child.exitCode !== null || child.signalCode !== null)\n return null\n\n const runtime = readRuntime()\n if (runtime !== null && runtime.pid === child.pid)\n return runtime\n\n if (Date.now() > deadline)\n return null\n await delay(150)\n }\n}\n\nexport function upCommand(entry: string) {\n return defineCommand({\n meta: { name: 'up', description: 'start the panel in the background (detached)' },\n args: upArgs,\n run: async ({ args }) => {\n await runUp(toUpFlags(args), entry)\n },\n })\n}\n","import { spawnSync } from 'node:child_process'\nimport process from 'node:process'\nimport { defineCommand } from 'citty'\nimport { delay, dim, green } from '#src/cli/io'\n\n/** `down` stops the panel and everything it supervises. */\n\nexport async function runDown(): Promise<void> {\n const { clearRuntime, isProcessAlive, readRuntime, requestShutdown } = await import('#src/helpers/daemon')\n\n const runtime = readRuntime()\n if (runtime === null) {\n process.stdout.write('home-hosted is not running\\n')\n return\n }\n if (!isProcessAlive(runtime.pid)) {\n clearRuntime()\n process.stdout.write('home-hosted is not running (removed a stale run.json)\\n')\n return\n }\n\n process.stdout.write(`stopping pid ${runtime.pid}…\\n`)\n // The panel's own endpoint stops supervised servers cleanly on every platform;\n // a signal is the fallback for a wedged or unreachable process.\n if (!(await requestShutdown(runtime)))\n signal(runtime.pid, 'SIGTERM')\n\n if (await waitForExit(runtime.pid, 20000)) {\n clearRuntime()\n process.stdout.write(`${green('stopped')}\\n`)\n return\n }\n\n process.stdout.write(`${dim('it did not stop in time — forcing')}\\n`)\n forceStop(runtime.pid)\n await waitForExit(runtime.pid, 5000)\n clearRuntime()\n process.stdout.write(`${green('stopped')} (forced)\\n`)\n}\n\nasync function waitForExit(pid: number, timeoutMs: number): Promise<boolean> {\n const { isProcessAlive } = await import('#src/helpers/daemon')\n const deadline = Date.now() + timeoutMs\n\n while (Date.now() < deadline) {\n if (!isProcessAlive(pid))\n return true\n await delay(200)\n }\n return !isProcessAlive(pid)\n}\n\nfunction signal(pid: number, name: NodeJS.Signals): void {\n try {\n process.kill(pid, name)\n }\n catch {\n // already gone\n }\n}\n\n/** Windows cannot deliver a graceful signal, so the whole tree is killed. */\nfunction forceStop(pid: number): void {\n if (process.platform === 'win32') {\n spawnSync('taskkill', ['/PID', String(pid), '/T', '/F'], { windowsHide: true })\n return\n }\n signal(pid, 'SIGKILL')\n}\n\nexport const downCommand = defineCommand({\n meta: { name: 'down', description: 'stop it, and everything it supervises' },\n run: async () => {\n await runDown()\n },\n})\n","import { defineCommand } from 'citty'\nimport { runDown } from '#src/cli/down'\nimport { runUp, toUpFlags, upArgs } from '#src/cli/up'\n\n/** `restart` is `down`, then `up` with exactly the flags it was given. */\n\nexport function restartCommand(entry: string) {\n return defineCommand({\n meta: { name: 'restart', description: 'down, then up' },\n args: upArgs,\n run: async ({ args }) => {\n await runDown()\n await runUp(toUpFlags(args), entry)\n },\n })\n}\n","import process from 'node:process'\nimport { defineCommand } from 'citty'\nimport { bold, dim, green, paint } from '#src/cli/io'\n\n/** `status` answers \"is it running, where, and how do I reach it\". */\n\nexport const statusArgs = {\n json: { type: 'boolean', description: 'print machine-readable JSON' },\n} as const\n\nexport async function runStatus(json: boolean): Promise<void> {\n const { isProcessAlive, probeRuntime, readRuntime } = await import('#src/helpers/daemon')\n const { UiService } = await import('#src/services/ui')\n const { dataRoot } = await import('#src/helpers/paths')\n const runtime = readRuntime()\n\n if (runtime === null) {\n if (json)\n process.stdout.write(`${JSON.stringify({ running: false }, null, 2)}\\n`)\n else\n process.stdout.write('home-hosted is not running\\n')\n process.exitCode = 1\n return\n }\n\n const running = isProcessAlive(runtime.pid)\n const probe = running ? await probeRuntime(runtime) : { reachable: false, degraded: false }\n\n if (json) {\n // The token is what authorises a local shutdown; a script only needs the rest.\n const { token: _token, ...safe } = runtime\n process.stdout.write(`${JSON.stringify({ running, answering: probe.reachable, degraded: probe.degraded, ...safe }, null, 2)}\\n`)\n if (!running)\n process.exitCode = 1\n return\n }\n\n const uptime = formatDuration(Date.now() - runtime.startedAt)\n const state = !running\n ? paint('31', 'stale (the process is gone)')\n : probe.degraded\n ? paint('33', 'running — a server needs attention')\n : probe.reachable ? green('running') : paint('33', 'running, but not answering')\n\n const ui = new UiService({ dataRoot })\n const rows: Array<[string, string]> = [\n ['status', state],\n ['pid', running ? `${runtime.pid} · up ${uptime}` : String(runtime.pid)],\n ['url', `${runtime.url} ${dim(`(${runtime.protocol})`)}`],\n ['version', runtime.version],\n ['project', runtime.projectDir],\n ['state', runtime.dataRoot],\n ['config', runtime.configPath],\n ['log', runtime.logFile],\n ['ui', ui.custom ? `custom — ${ui.status().meta?.name ?? 'installed'} (revert with \\`home-hosted ui-revert\\`)` : 'stock'],\n ]\n\n process.stdout.write(`${bold(`home-hosted ${runtime.version}`)}\\n`)\n for (const [label, value] of rows)\n process.stdout.write(` ${dim(label.padEnd(8))} ${value}\\n`)\n if (!running)\n process.exitCode = 1\n}\n\nfunction formatDuration(ms: number): string {\n const seconds = Math.max(0, Math.round(ms / 1000))\n if (seconds < 60)\n return `${seconds}s`\n const minutes = Math.floor(seconds / 60)\n if (minutes < 60)\n return `${minutes}m`\n const hours = Math.floor(minutes / 60)\n if (hours < 24)\n return `${hours}h ${minutes % 60}m`\n return `${Math.floor(hours / 24)}d ${hours % 24}h`\n}\n\nexport const statusCommand = defineCommand({\n meta: { name: 'status', description: 'is it running, where, and how to reach it' },\n args: statusArgs,\n run: async ({ args }) => {\n await runStatus(args.json === true)\n },\n})\n","import process from 'node:process'\nimport { defineCommand } from 'citty'\nimport { dim, fail, green, promptHidden } from '#src/cli/io'\nimport { SecretsStore } from '#src/config/secrets'\nimport { defaultSecretsPath } from '#src/helpers/paths'\n\n/** `set-password` sets the panel password without the API. */\n\nexport const setPasswordArgs = {\n clear: { type: 'boolean', description: 'remove the password, which disables authentication' },\n} as const\n\nexport async function runSetPassword(clear: boolean): Promise<void> {\n const store = new SecretsStore(defaultSecretsPath)\n\n if (clear) {\n store.clearPassword()\n process.stdout.write(`cleared the control panel password in ${defaultSecretsPath}\\n`)\n process.stdout.write(`${dim('authentication stays disabled until you enable it again in the settings page')}\\n`)\n return\n }\n\n const interactive = process.stdin.isTTY === true\n let password = process.env.HHOSTED_PASSWORD\n\n if (password === undefined && interactive) {\n password = await promptHidden('New control panel password: ')\n const again = await promptHidden('Repeat it: ')\n if (password !== again)\n fail('the passwords do not match')\n }\n\n if (password === undefined || password.length === 0) {\n fail('no password given: run interactively, or set HHOSTED_PASSWORD for a non-interactive run')\n }\n\n store.setPassword(password)\n process.stdout.write(`${green('password stored')} in ${defaultSecretsPath} (mode 0600)\\n`)\n if (password.length < 8)\n process.stdout.write(`${dim(`\"${password}\" is short — easy to guess if the panel is reachable beyond loopback`)}\\n`)\n process.stdout.write(`${dim('restart the panel for it to take effect: home-hosted restart')}\\n`)\n}\n\nexport const setPasswordCommand = defineCommand({\n meta: { name: 'set-password', description: 'set the panel password without the API' },\n args: setPasswordArgs,\n run: async ({ args }) => {\n await runSetPassword(args.clear === true)\n },\n})\n","import process from 'node:process'\nimport { defineCommand } from 'citty'\nimport { bold, dim, fail, green, promptHidden } from '#src/cli/io'\nimport { generateApiToken, SecretsStore } from '#src/config/secrets'\nimport { defaultSecretsPath } from '#src/helpers/paths'\n\n/** `set-token` sets the bearer credential scripts and agents use. */\n\nconst DEFAULT_PORT = 3999\n\nexport const setTokenArgs = {\n generate: { type: 'boolean', description: 'create a strong token and print it once' },\n clear: { type: 'boolean', description: 'remove the token, so it stops working' },\n} as const\n\nexport async function runSetToken(generate: boolean, clear: boolean): Promise<void> {\n if (generate && clear)\n fail('use either --generate or --clear, not both')\n\n const store = new SecretsStore(defaultSecretsPath)\n\n if (clear) {\n if (!store.apiTokenSet) {\n process.stdout.write('no API token is set — nothing to clear\\n')\n return\n }\n store.clearApiToken()\n process.stdout.write(`${green('API token cleared')} in ${defaultSecretsPath} — it stops working immediately\\n`)\n return\n }\n\n let token: string | null = process.env.HHOSTED_TOKEN ?? null\n if (generate)\n token = generateApiToken()\n else if (token === null && process.stdin.isTTY === true)\n token = await promptHidden('API token: ')\n if (token !== null)\n token = token.trim()\n if (token === null || token.length === 0) {\n fail('no token given: run `home-hosted set-token --generate`, set HHOSTED_TOKEN, or paste one interactively')\n }\n\n store.setApiToken(token)\n process.stdout.write(`${green(generate ? 'token generated' : 'token stored')} in ${defaultSecretsPath} (mode 0600)\\n`)\n if (generate) {\n process.stdout.write(` ${bold(token)}\\n`)\n process.stdout.write(`${dim(' shown once — only its SHA-256 is kept on disk, so copy it now')}\\n`)\n }\n else {\n process.stdout.write(`${dim(` stored as ${token.slice(0, 8)}… — the file keeps only its hash`)}\\n`)\n }\n\n const { readRuntime } = await import('#src/helpers/daemon')\n // Only the panel that owns *this* state directory is worth asking: guessing a\n // port would probe someone else's panel and call the mismatch a failure.\n const runtime = readRuntime()\n const base = (runtime?.url ?? `http://127.0.0.1:${DEFAULT_PORT}`).replace(/\\/+$/, '')\n\n if (runtime !== null) {\n const verified = await verifyToken(base, token)\n if (verified === true)\n process.stdout.write(`${dim(`verified: ${base}/api/auth/session accepted it`)}\\n`)\n else if (verified === false)\n process.stdout.write(`${dim(`the panel at ${base} did not accept it — is it running with this state directory?`)}\\n`)\n }\n\n process.stdout.write(`Use it from a script or an agent:\\n`)\n process.stdout.write(` ${bold(`curl -H \"Authorization: Bearer ${generate ? token : '<token>'}\" ${base}/api/state`)}\\n`)\n process.stdout.write(`${dim('It needs no restart, outlives sessions, and holds the same access as a signed-in browser.')}\\n`)\n process.stdout.write(`${dim('Remove it any time with: home-hosted set-token --clear')}\\n`)\n if (store.usingDefaultPassword)\n process.stdout.write(`${dim('The panel password is still the default — change it before the panel is reachable beyond loopback.')}\\n`)\n}\n\n/** Asks the live panel whether it accepts the token, without failing when it cannot. */\nasync function verifyToken(base: string, token: string): Promise<boolean | null> {\n // An https endpoint is usually TLS this project generated itself, which a plain\n // fetch refuses; the liveness probe in `helpers/daemon` is the one that knows how.\n if (!base.startsWith('http://'))\n return null\n try {\n const response = await fetch(`${base}/api/auth/session`, { headers: { authorization: `Bearer ${token}` } })\n if (!response.ok)\n return false\n const body = await response.json() as { authenticated?: boolean }\n return body.authenticated === true\n }\n catch {\n return null\n }\n}\n\nexport const setTokenCommand = defineCommand({\n meta: { name: 'set-token', description: 'set the API token that scripts and agents use' },\n args: setTokenArgs,\n run: async ({ args }) => {\n await runSetToken(args.generate === true, args.clear === true)\n },\n})\n","import fs from 'node:fs'\nimport path from 'node:path'\nimport process from 'node:process'\nimport { defineCommand } from 'citty'\nimport { dim, fail, green, prompt } from '#src/cli/io'\nimport { applyConfigMigrations, CONFIG_SCHEMA, planConfigMigrations } from '#src/config/migrations'\nimport { parseConfig, stampConfig } from '#src/config/parse'\nimport { writeFileAtomic } from '#src/helpers/atomic'\nimport { defaultConfigPath } from '#src/helpers/paths'\nimport { appVersion } from '#src/helpers/version'\n\n/**\n * `migrate` brings `servers.config.json` up to the schema this release understands.\n *\n * Deliberately loud and deliberate: it prints every step first, backs the file up\n * before writing, refuses to write a config it cannot read, and never runs on its\n * own — a detached daemon cannot prompt, so consent comes from `--yes`, from\n * `HHOSTED_MIGRATE=allow`, or from a person at a terminal.\n */\n\nexport const migrateArgs = {\n config: { type: 'string', alias: 'c', description: 'servers config (default: <state>/servers.config.json)' },\n dryRun: { type: 'boolean', description: 'print what would change, write nothing' },\n yes: { type: 'boolean', alias: 'y', description: 'apply without asking (or set HHOSTED_MIGRATE=allow)' },\n} as const\n\nexport async function runMigrate(config: string | undefined, dryRun: boolean, yes: boolean): Promise<void> {\n const file = config ?? defaultConfigPath\n if (!fs.existsSync(file))\n fail(`no config at ${file} — nothing to migrate`)\n\n let raw: Record<string, unknown>\n try {\n raw = JSON.parse(fs.readFileSync(file, 'utf8')) as Record<string, unknown>\n }\n catch (error) {\n fail(`cannot read ${file}: ${error instanceof Error ? error.message : String(error)}`)\n }\n\n const meta = typeof raw.meta === 'object' && raw.meta !== null ? raw.meta as { schema?: number, writtenBy?: string } : {}\n const from = typeof meta.schema === 'number' ? meta.schema : CONFIG_SCHEMA\n const plan = planConfigMigrations(from)\n\n if (plan.tooNew) {\n fail(`${file} was written by home-hosted ${meta.writtenBy ?? 'a newer release'} (config schema ${from});\\n this release understands schema ${plan.to}. Install that version, or edit the file yourself.`)\n }\n\n const stamped = stampConfig(raw)\n const upToDate = plan.steps.length === 0\n if (upToDate && !dryRun && JSON.stringify(stamped) !== JSON.stringify(raw)) {\n writeFileAtomic(file, `${JSON.stringify(stamped, null, 2)}\\n`)\n process.stdout.write(`${green('config stamped')} in ${file} — written by home-hosted ${appVersion()}, schema ${plan.to}\\n`)\n return\n }\n\n if (upToDate) {\n process.stdout.write(`config schema ${from} is already what home-hosted ${appVersion()} understands — nothing to migrate\\n`)\n return\n }\n\n process.stdout.write(`migrating ${file}: config schema ${from} → ${plan.to}\\n`)\n for (const [index, step] of plan.steps.entries())\n process.stdout.write(` ${index + 1}. ${step.describe}\\n`)\n\n if (dryRun) {\n process.stdout.write(`${dim(`nothing was written (--dry-run, ${plan.steps.length} step(s) pending)`)}\\n`)\n return\n }\n\n const consented = yes || (process.env.HHOSTED_MIGRATE ?? '').toLowerCase() === 'allow'\n if (!consented) {\n if (process.stdin.isTTY !== true) {\n fail(`this config needs ${plan.steps.length} migration(s) and this session cannot ask.\\n re-run with --yes, or set HHOSTED_MIGRATE=allow for unattended runs`)\n }\n const answer = await prompt(`Apply ${plan.steps.length} migration(s) to ${path.basename(file)}? [y/N] `)\n if (!/^yes$|^y$/i.test(answer.trim())) {\n process.stdout.write('cancelled — nothing was written\\n')\n return\n }\n }\n\n const { config: migrated, applied } = applyConfigMigrations(raw, from)\n const parsed = parseConfig(migrated)\n if (parsed.config === null) {\n fail(`the migration produced a config this release cannot read:\\n ${parsed.errors.join('\\n ')}`)\n }\n\n const backup = `${file}.bak`\n fs.copyFileSync(file, backup)\n writeFileAtomic(file, `${JSON.stringify(stampConfig(migrated), null, 2)}\\n`)\n process.stdout.write(`${green(`migrated to schema ${plan.to}`)} (${applied.length} step(s)) in ${file}\\n`)\n process.stdout.write(` ${dim(`previous file kept at ${backup}`)}\\n`)\n for (const key of parsed.unknownKeys)\n process.stdout.write(` ${dim(`still ignoring an unrecognized key: ${key}`)}\\n`)\n}\n\nexport const migrateCommand = defineCommand({\n meta: { name: 'migrate', description: 'bring the config up to this release\\'s schema' },\n args: migrateArgs,\n run: async ({ args }) => {\n await runMigrate(args.config, args.dryRun === true, args.yes === true)\n },\n})\n","import type { Stats } from 'node:fs'\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport { appVersion } from '#src/helpers/version'\n\nexport type PackageManager = 'pnpm' | 'npm' | 'yarn' | 'bun'\n\nexport interface InitOptions {\n dir: string\n name: string\n pm: PackageManager\n install: boolean\n git: boolean\n}\n\nexport interface InitResult {\n dir: string\n created: boolean\n files: string[]\n installed: boolean\n git: boolean\n}\n\n/** The scripts every scaffolded project gets; all of them keep state inside the project. */\nexport function projectScripts(): Record<string, string> {\n const withState = (args: string): string => `home-hosted ${args} --home ./state`\n return {\n 'up': withState('up'),\n 'down': withState('down'),\n 'restart': withState('restart'),\n 'status': withState('status'),\n 'set-password': withState('set-password'),\n 'set-token': withState('set-token'),\n 'migrate': withState('migrate'),\n }\n}\n\n/** `home-hosted` is pinned to the version that wrote the project, and open to newer ones. */\nexport function projectManifest(name: string): string {\n return `${JSON.stringify({\n name,\n version: '0.1.0',\n private: true,\n description: 'Servers supervised by home-hosted.',\n type: 'module',\n engines: { node: '>=24.0.0' },\n scripts: projectScripts(),\n dependencies: { 'home-hosted': `^${appVersion()}` },\n }, null, 2)}\\n`\n}\n\n/**\n * State is generated, so it stays out — except the file that declares the servers,\n * which is the one thing worth committing.\n */\nexport function projectGitignore(): string {\n return `node_modules/\n\n# home-hosted: state is local, the server definitions are tracked\nstate/*\n!state/servers.config.json\ndata/\n`\n}\n\nexport function isEmptyDir(dir: string): boolean {\n try {\n return fs.readdirSync(dir).every(entry => entry === '.git')\n }\n catch {\n return true\n }\n}\n\nfunction assertUsableDir(dir: string): void {\n let stats: Stats | null = null\n try {\n stats = fs.statSync(dir)\n }\n catch {\n return\n }\n if (!stats.isDirectory())\n throw new Error(`${dir} exists and is not a directory`)\n if (!isEmptyDir(dir))\n throw new Error(`${dir} is not empty — pick another directory, or empty it first`)\n}\n\n/** Writes the project skeleton. Nothing is installed or initialized here. */\nexport function scaffold(options: InitOptions): InitResult {\n const dir = path.resolve(options.dir)\n assertUsableDir(dir)\n\n const existed = fs.existsSync(dir)\n fs.mkdirSync(dir, { recursive: true })\n\n const files: Array<[string, string]> = [\n ['package.json', projectManifest(options.name)],\n ['.gitignore', projectGitignore()],\n ]\n for (const [name, contents] of files)\n fs.writeFileSync(path.join(dir, name), contents)\n\n return {\n dir,\n created: !existed,\n files: files.map(([name]) => name),\n installed: false,\n git: false,\n }\n}\n\nexport const PACKAGE_MANAGERS: PackageManager[] = ['pnpm', 'npm', 'yarn', 'bun']\n\n/** The package manager this machine actually has, preferring pnpm. */\nexport function detectPackageManager(exists: (command: string) => boolean): PackageManager {\n const found = PACKAGE_MANAGERS.find(pm => exists(pm))\n return found ?? 'npm'\n}\n\n/** How to run one of the project's scripts with a given package manager. */\nexport function runCommand(pm: PackageManager, script: string): string {\n return pm === 'npm' ? `npm run ${script}` : `${pm} run ${script}`\n}\n\n/** Install arguments for a scaffolded project (the manifest already lists the dependency). */\nexport function installArgs(pm: PackageManager): string[] {\n return pm === 'yarn' ? [] : ['install']\n}\n","import { spawnSync } from 'node:child_process'\nimport path from 'node:path'\nimport process from 'node:process'\nimport { defineCommand } from 'citty'\nimport { bold, confirm, dim, fail, green, paint, prompt } from '#src/cli/io'\nimport { detectPackageManager, installArgs, PACKAGE_MANAGERS, runCommand, scaffold } from '#src/services/init'\n\n/**\n * `init` scaffolds a project that keeps its whole setup — state, data and the\n * server definitions — inside its own directory. Interactive by nature, but\n * `--yes` takes every default so an agent or a CI job can run it unattended.\n */\n\nexport const initArgs = {\n dir: { type: 'string', description: 'where to scaffold (default: ./my-servers)' },\n name: { type: 'string', description: 'package name (default: the directory name)' },\n pm: { type: 'string', description: 'pnpm | npm | yarn | bun (default: the first one installed)' },\n install: { type: 'boolean', default: true, negativeDescription: 'write the files, install nothing' },\n yes: { type: 'boolean', alias: 'y', description: 'take every default, ask nothing' },\n} as const\n\nfunction which(command: string): boolean {\n const probe = spawnSync(command, ['--version'], { stdio: 'ignore', shell: process.platform === 'win32' })\n return probe.status === 0\n}\n\nexport async function runInit(options: { dir?: string, name?: string, pm?: string, noInstall: boolean, yes: boolean }): Promise<void> {\n const assumeYes = options.yes\n if (!assumeYes && process.stdin.isTTY !== true) {\n fail('init needs a terminal to ask in.\\n take the defaults with: home-hosted init --yes [--dir <dir>]')\n }\n\n const defaultDir = options.dir ?? './my-servers'\n const dir = assumeYes ? defaultDir : (await prompt(`Project directory (${defaultDir}) `)).trim() || defaultDir\n const defaultName = path.basename(path.resolve(dir))\n const name = options.name ?? (assumeYes ? defaultName : (await prompt(`Package name (${defaultName}) `)).trim() || defaultName)\n\n let pm = options.pm\n if (pm !== undefined && !(PACKAGE_MANAGERS as string[]).includes(pm))\n fail(`unknown package manager: ${pm} (expected one of ${PACKAGE_MANAGERS.join(', ')})`)\n const detected = detectPackageManager(which)\n if (pm === undefined)\n pm = assumeYes ? detected : (await prompt(`Package manager (${detected}) `)).trim() || detected\n\n const install = options.noInstall\n ? false\n : assumeYes || (await confirm('Install the dependencies now?', true))\n const git = assumeYes ? which('git') : which('git') && (await confirm('Initialize a git repository?', true))\n\n try {\n const result = scaffold({ dir, name, pm: pm as never, install, git })\n process.stdout.write(`${green('project created')} in ${result.dir}\\n`)\n for (const file of result.files)\n process.stdout.write(` ${dim(file)}\\n`)\n }\n catch (error) {\n fail(error instanceof Error ? error.message : String(error))\n }\n\n const target = path.resolve(dir)\n if (git) {\n spawnSync('git', ['init', '-q'], { cwd: target, stdio: 'inherit', shell: process.platform === 'win32' })\n process.stdout.write(` ${dim('git repository initialized')}\\n`)\n }\n\n if (install) {\n process.stdout.write(`${dim(`installing with ${pm}…`)}\\n`)\n const result = spawnSync(pm as string, installArgs(pm as never), {\n cwd: target,\n stdio: 'inherit',\n shell: process.platform === 'win32',\n })\n if (result.status !== 0) {\n process.stdout.write(`${paint('33', 'install failed')} — run it yourself in ${target}\\n`)\n }\n }\n\n // A path inside the working directory reads better relative; anything else absolute.\n const relative = path.relative(process.cwd(), target)\n const where = relative.length === 0 || relative.startsWith('..') ? target : relative\n const cd = relative.length === 0 ? '' : `cd ${where} && `\n process.stdout.write(`\\nNext:\\n`)\n process.stdout.write(` ${bold(`${cd}${runCommand(pm as never, 'up')}`)} start the panel (default password \\`hh\\`)\\n`)\n process.stdout.write(` ${dim('then change that password under Settings → Authentication, and add your servers')}\\n`)\n process.stdout.write(` ${dim(`${runCommand(pm as never, 'set-token')} --generate for scripts and agents`)}\\n`)\n}\n\nexport const initCommand = defineCommand({\n meta: { name: 'init', description: 'scaffold a project that keeps its state in the repo' },\n args: initArgs,\n run: async ({ args }) => {\n await runInit({\n dir: args.dir,\n name: args.name,\n pm: args.pm,\n noInstall: args.install === false,\n yes: args.yes === true,\n })\n },\n})\n","import type { GithubAsset } from '#src/providers/ui-release'\nimport fs from 'node:fs'\nimport process from 'node:process'\nimport { parseArgs } from 'node:util'\nimport { defineCommand } from 'citty'\nimport { prompt, style } from '#src/cli/io'\nimport {\n assetDownloadUrl,\n DEFAULT_REPO,\n defaultReleaseTag,\n downloadToTemp,\n fallbackUiName,\n fetchRelease,\n isGithubHost,\n isUiAsset,\n matchAsset,\n noAssetsMessage,\n parseFileSource,\n parseRepoSlug,\n} from '#src/providers/ui-release'\n\n/**\n * `home-hosted ui-switch` installs the panel's frontend UI without the settings\n * page: a GitHub release asset (the default), a local zip, or an http(s) URL.\n *\n * This module is only ever imported dynamically, after `--home` has been applied,\n * so the modules that read the state directories are imported inside the command\n * rather than at the top. The release-side helpers it shares with `ui-update` are\n * in `#src/providers/ui-release`, and re-exported here so existing importers keep working.\n */\n\nexport * from '#src/providers/ui-release'\n\n/** What the command needs from the CLI that owns the readline prompts and the colours. */\nexport interface UiSwitchIo {\n write: (text: string) => void\n prompt: (question: string) => Promise<string>\n style: {\n bold: (text: string) => string\n dim: (text: string) => string\n green: (text: string) => string\n }\n}\n\ninterface SwitchContext {\n io: UiSwitchIo\n version: string\n token: string | null\n}\n\nexport async function uiSwitch(argv: string[], io: UiSwitchIo): Promise<void> {\n const { values } = parseArgs({\n args: argv,\n options: {\n repo: { type: 'string' },\n tag: { type: 'string' },\n asset: { type: 'string' },\n file: { type: 'string' },\n list: { type: 'boolean' },\n token: { type: 'string' },\n yes: { type: 'boolean', short: 'y' },\n },\n allowPositionals: false,\n })\n\n const { appVersion } = await import('#src/helpers/version')\n const context: SwitchContext = {\n io,\n version: appVersion(),\n token: values.token ?? process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN ?? null,\n }\n\n if (values.file !== undefined) {\n if (values.repo !== undefined || values.tag !== undefined || values.asset !== undefined || values.list === true)\n throw new Error('--file installs a zip directly; it cannot be combined with --repo, --tag, --asset or --list')\n await installFromFile(values.file, context)\n return\n }\n\n const repo = parseRepoSlug(values.repo ?? DEFAULT_REPO)\n if (repo === null)\n throw new Error(`invalid --repo \"${values.repo}\" — expected an \"owner/name\" slug, e.g. ${DEFAULT_REPO}`)\n\n const requestedTag = values.tag ?? defaultReleaseTag(repo, context.version)\n const release = await fetchRelease(repo, requestedTag, context)\n const skipped = release.assets.filter(asset => !isUiAsset(asset.name ?? '')).map(asset => asset.name ?? '?')\n const usable = release.assets.filter(asset => isUiAsset(asset.name ?? ''))\n const where = `${repo.owner}/${repo.name}@${release.tag}`\n\n if (values.list === true) {\n if (usable.length === 0)\n throw new Error(noAssetsMessage(where, skipped, requestedTag))\n // One write, so `ui-switch --list | head` does not die on a broken pipe.\n const lines = [`${io.style.bold(where)} — ${usable.length} usable UI asset(s)`]\n for (const asset of usable)\n lines.push(` ${asset.name}`)\n if (skipped.length > 0)\n lines.push(io.style.dim(` skipped (not a .zip): ${skipped.join(', ')}`))\n io.write(`${lines.join('\\n')}\\n`)\n return\n }\n\n if (usable.length === 0)\n throw new Error(noAssetsMessage(where, skipped, requestedTag))\n\n const chosen = await chooseAsset(usable, values, context)\n if (chosen === null) {\n io.write('cancelled — nothing was installed\\n')\n return\n }\n\n await installFromUrl(assetDownloadUrl(chosen), fallbackUiName(chosen.name ?? ''), context)\n}\n\nasync function chooseAsset(assets: GithubAsset[], values: { asset?: string, yes?: boolean }, context: SwitchContext): Promise<GithubAsset | null> {\n const { io } = context\n\n if (values.asset !== undefined) {\n const matched = matchAsset(assets.map(asset => asset.name ?? ''), values.asset)\n if (!matched.ok)\n throw new Error(matched.error)\n return assets.find(asset => asset.name === matched.name)!\n }\n\n const interactive = process.stdin.isTTY === true && values.yes !== true\n if (!interactive) {\n if (assets.length === 1)\n return assets[0]!\n throw new Error([\n `${assets.length} UI assets are available and this session cannot ask which one:`,\n ...assets.map(asset => ` ${asset.name}`),\n ' pick one with --asset <name>, or list them with --list',\n ].join('\\n'))\n }\n\n // One write, so the listing is complete before the prompt (and a piped stdout\n // cannot interleave with it).\n io.write(`${[io.style.bold('Available UI assets'), ...assets.map((asset, index) => ` ${index + 1}) ${asset.name}`)].join('\\n')}\\n`)\n\n for (;;) {\n const answer = (await io.prompt(`Select an asset [1-${assets.length}] (empty to cancel) `)).trim()\n if (answer.length === 0)\n return null\n\n if (/^\\d+$/.test(answer)) {\n const index = Number.parseInt(answer, 10)\n if (index >= 1 && index <= assets.length)\n return assets[index - 1]!\n }\n\n const matched = matchAsset(assets.map(asset => asset.name ?? ''), answer)\n if (matched.ok)\n return assets.find(asset => asset.name === matched.name)!\n io.write(` ${io.style.dim(matched.error)}\\n`)\n }\n}\n\nasync function installFromFile(value: string, context: SwitchContext): Promise<void> {\n const source = parseFileSource(value)\n if (source.kind === 'path') {\n if (!fs.existsSync(source.path))\n throw new Error(`no file at ${source.path}`)\n if (!fs.statSync(source.path).isFile())\n throw new Error(`${source.path} is not a file`)\n await installArchive(source.path, fallbackUiName(source.path), context)\n return\n }\n await installFromUrl(source.url, fallbackUiName(new URL(source.url).pathname), context)\n}\n\nasync function installFromUrl(url: string, fallbackName: string, context: SwitchContext): Promise<void> {\n const headers: Record<string, string> = {\n 'accept': 'application/octet-stream',\n 'user-agent': `home-hosted/${context.version}`,\n }\n if (context.token !== null && context.token.length > 0 && isGithubHost(url))\n headers.authorization = `Bearer ${context.token}`\n\n const download = await downloadToTemp(url, headers, context)\n try {\n await installArchive(download.file, fallbackName, context)\n }\n finally {\n fs.rmSync(download.dir, { recursive: true, force: true })\n }\n}\n\nasync function installArchive(archivePath: string, fallbackName: string, context: SwitchContext): Promise<void> {\n const { UiService } = await import('#src/services/ui')\n const { dataRoot } = await import('#src/helpers/paths')\n const ui = new UiService({ dataRoot })\n const result = await ui.install(archivePath, fallbackName)\n\n if (!result.ok)\n throw new Error(`nothing was installed: ${result.error}`)\n\n const { meta } = result\n const label = meta.version === null ? meta.name : `${meta.name} ${meta.version}`\n context.io.write(`${context.io.style.green('UI installed')} — ${label}\\n`)\n context.io.write(` ${context.io.style.dim('state')} ${ui.directory}\\n`)\n context.io.write(` ${context.io.style.dim('files')} ${meta.files}\\n`)\n context.io.write(`refresh the browser to see it\\n`)\n}\n\n/**\n * The citty entry. `ui-switch` keeps parsing its own flags (and its own error\n * messages) inside `uiSwitch`; citty dispatches with the argv it was handed, so\n * the invocation changes and the helpers do not.\n */\nexport const uiSwitchCommand = defineCommand({\n meta: { name: 'ui-switch', description: 'install a UI from a release asset, a zip file or a URL' },\n run: async ({ rawArgs }) => {\n await uiSwitch(rawArgs, {\n write: text => process.stdout.write(text),\n prompt,\n style,\n })\n },\n})\n","import type { UiSourceContext } from '#src/providers/ui-release'\nimport type { UiMeta } from '#src/shared/contracts'\nimport fs from 'node:fs'\nimport process from 'node:process'\nimport { parseArgs } from 'node:util'\nimport { defineCommand } from 'citty'\nimport { prompt, style } from '#src/cli/io'\nimport {\n assetDownloadUrl,\n compareTags,\n DEFAULT_REPO,\n downloadToTemp,\n fetchRelease,\n fetchReleases,\n isOwnRepo,\n isUiAsset,\n matchAsset,\n parseRepoSlug,\n repoSlug,\n} from '#src/providers/ui-release'\n\n/**\n * `home-hosted ui-update` keeps an installed UI in step with the panel it talks to.\n *\n * Three cases, in the order they are decided:\n * the stock UI is always current by definition — nothing to do;\n * a UI from our own repo is *paired* with a release, so it is updated to the tag\n * of the running panel without asking (that pairing is the whole point);\n * anyone else's UI declares its own `repo`, and the person picks from the releases\n * that actually carry the asset they are using — newer ones, or `--old` for older.\n */\n\ninterface UpdateContext extends UiSourceContext {\n /** null when the session cannot be asked (not a TTY, or `--yes`). */\n ask: ((question: string) => Promise<string>) | null\n}\n\n/** The identity an installed UI declared about itself, when it declared one. */\nexport interface UiIdentity {\n name: string\n version: string | null\n repo: string | null\n tag: string | null\n asset: string | null\n unix: number | null\n}\n\nexport type UiUpdatePlan\n /** Nothing installed: the stock UI is served, and it is always current. */\n = { kind: 'stock' }\n /** Installed, but it never said where it came from, so there is nothing to follow. */\n | { kind: 'unidentifiable', identity: UiIdentity }\n /** Ours: paired with this panel's release. `target` already matches when current. */\n | { kind: 'official', identity: UiIdentity, target: string }\n /** Someone else's: the person chooses from `candidates`. */\n | { kind: 'choice', identity: UiIdentity, asset: string, candidates: UpdateCandidate[] }\n\nexport interface UpdateCandidate {\n tag: string\n asset: string\n}\n\n/**\n * Which of a release list applies to what is installed. A candidate is only offered when\n * its assets carry the asset the person is actually using, so a UI is never swapped for a\n * different flavour by an update.\n */\nexport function usableCandidates(\n releases: Array<{ tag: string, assets: Array<{ name?: string }> }>,\n asset: string,\n order: 'newer' | 'older',\n currentTag: string | null,\n): UpdateCandidate[] {\n const found: UpdateCandidate[] = []\n\n for (const release of releases) {\n const names = release.assets.map(entry => entry.name ?? '').filter(isUiAsset)\n const matched = matchAsset(names, asset)\n if (!matched.ok)\n continue\n if (currentTag !== null) {\n const difference = compareTags(release.tag, currentTag)\n if (order === 'newer' && difference <= 0)\n continue\n if (order === 'older' && difference >= 0)\n continue\n }\n found.push({ tag: release.tag, asset: matched.name })\n }\n\n return found\n}\n\n/** What the asset the UI is using is likely called, when it never declared one. */\nexport function impliedAsset(identity: UiIdentity): string | null {\n if (identity.asset !== null && identity.asset.length > 0)\n return identity.asset\n if (identity.name.length > 0)\n return `${identity.name}.zip`\n return null\n}\n\n/**\n * The decision, with no I/O in it: given what is installed and this panel's release,\n * what should `ui-update` do? `releases` is only consulted for someone else's UI.\n */\nexport function planUiUpdate(\n installed: UiIdentity | null,\n runningTag: string,\n releases: Array<{ tag: string, assets: Array<{ name?: string }> }> = [],\n order: 'newer' | 'older' = 'newer',\n): UiUpdatePlan {\n if (installed === null)\n return { kind: 'stock' }\n\n const repo = installed.repo === null ? null : parseRepoSlug(installed.repo)\n if (repo === null)\n return { kind: 'unidentifiable', identity: installed }\n\n if (isOwnRepo(repo))\n return { kind: 'official', identity: installed, target: runningTag }\n\n const asset = impliedAsset(installed)\n if (asset === null)\n return { kind: 'unidentifiable', identity: installed }\n\n return { kind: 'choice', identity: installed, asset, candidates: usableCandidates(releases, asset, order, installed.tag) }\n}\n\n/** `home-hosted ui-update` — see the module comment for the three cases. */\nexport async function uiUpdate(argv: string[], io: { write: (text: string) => void, prompt: (question: string) => Promise<string>, style: { bold: (text: string) => string, dim: (text: string) => string, green: (text: string) => string } }): Promise<void> {\n const { values } = parseArgs({\n args: argv,\n options: {\n tag: { type: 'string' },\n asset: { type: 'string' },\n token: { type: 'string' },\n yes: { type: 'boolean', short: 'y' },\n old: { type: 'boolean' },\n check: { type: 'boolean' },\n repo: { type: 'string' },\n },\n allowPositionals: false,\n })\n\n const { appVersion } = await import('#src/helpers/version')\n const { dataRoot } = await import('#src/helpers/paths')\n const { UiService } = await import('#src/services/ui')\n\n const context: UpdateContext = {\n io: { write: io.write, style: io.style },\n ask: process.stdin.isTTY === true && values.yes !== true ? io.prompt : null,\n version: appVersion(),\n token: values.token ?? process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN ?? null,\n }\n\n const ui = new UiService({ dataRoot })\n const status = ui.status()\n const installed = identityOf(status.meta)\n\n if (installed === null) {\n io.write(`${io.style.dim('the stock UI is in use — it ships with the panel and is always current')}\\n`)\n return\n }\n\n // `--repo` makes a UI that never declared one updateable, without reinstalling by hand.\n if (values.repo !== undefined) {\n const repo = parseRepoSlug(values.repo)\n if (repo === null)\n throw new Error(`invalid --repo \"${values.repo}\" — expected an \"owner/name\" slug, e.g. ${DEFAULT_REPO}`)\n installed.repo = repoSlug(repo)\n }\n\n const order = values.old === true ? 'older' : 'newer'\n const repo = installed.repo === null ? null : parseRepoSlug(installed.repo)\n\n // A request to install cannot be honoured without a repo to install from: saying so\n // beats discarding the flag and printing the \"does not say where it came from\" notice.\n if (values.tag !== undefined && repo === null) {\n throw new Error([\n '--tag needs a repository to fetch from, and this UI does not declare one',\n ` pass it: home-hosted ui-update --repo ${DEFAULT_REPO} --tag ${values.tag}`,\n ].join('\\n'))\n }\n\n // `--tag` names the release outright, so nothing has to be listed or chosen. `--check`\n // outranks it: the flag documents itself as installing nothing, so it must come first\n // or `--check --tag x` would install x.\n if (values.check === true && repo !== null) {\n if (isOwnRepo(repo)) {\n const target = values.tag ?? `v${context.version}`\n printCheck(io, installed, target)\n return\n }\n const releases = await fetchReleases(repo, context)\n const asset = impliedAsset(installed)\n printCheck(io, installed, null, asset === null ? [] : usableCandidates(releases, asset, order, installed.tag))\n return\n }\n\n if (values.tag !== undefined && repo !== null) {\n await installTag(repo, values.asset ?? installed.asset ?? null, values.tag, context)\n return\n }\n\n let releases: Array<{ tag: string, assets: Array<{ name?: string }> }> = []\n if (repo !== null && !isOwnRepo(repo))\n releases = await fetchReleases(repo, context)\n\n const plan = planUiUpdate(installed, `v${context.version}`, releases, order)\n if (plan.kind === 'unidentifiable') {\n io.write(`${io.style.bold('this UI does not say where it came from')} — nothing to follow automatically\\n`)\n printIdentity(io, plan.identity)\n io.write(` ${io.style.dim('point it at a repo to make this work: home-hosted ui-update --repo owner/name')}\\n`)\n return\n }\n\n if (plan.kind === 'official') {\n if (plan.identity.tag === plan.target) {\n io.write(`${io.style.green('already current')} — ${io.style.bold(plan.identity.name)} is at ${plan.target}\\n`)\n return\n }\n\n io.write(`${io.style.bold(`${plan.identity.name} ${plan.identity.version ?? ''}`.trim())} is on ${plan.identity.tag ?? 'an unknown tag'}; this panel is ${plan.target} — updating\\n`)\n await installTag(repo!, plan.identity.asset ?? values.asset ?? null, plan.target, context)\n return\n }\n\n // Someone else's UI: show what is installed, then what is actually available.\n if (plan.kind !== 'choice')\n return\n\n io.write(`${io.style.bold(plan.identity.name)} ${plan.identity.version ?? ''} — ${repoSlug(repo!)}@${plan.identity.tag ?? 'unknown tag'}\\n`)\n io.write(` ${io.style.dim(`asset: ${plan.asset}`)}\\n`)\n\n if (plan.candidates.length === 0) {\n const direction = order === 'newer' ? 'newer' : 'older'\n io.write(`no ${direction} releases carry ${plan.asset}\\n`)\n if (order === 'newer')\n io.write(` ${io.style.dim('list older releases with: home-hosted ui-update --old')}\\n`)\n return\n }\n\n const chosen = await chooseCandidate(plan.candidates, context, order)\n if (chosen === null) {\n io.write('cancelled — nothing was installed\\n')\n return\n }\n\n await installTag(repo!, chosen.asset, chosen.tag, context)\n}\n\nfunction identityOf(meta: UiMeta | null): UiIdentity | null {\n if (meta === null)\n return null\n return {\n // Both are declared by the author, so neither is ever guaranteed.\n name: meta.name ?? 'custom-ui',\n version: meta.version ?? null,\n repo: meta.repo ?? null,\n tag: meta.tag ?? null,\n asset: meta.asset ?? null,\n unix: meta.unix ?? null,\n }\n}\n\nfunction printIdentity(io: { write: (text: string) => void }, identity: UiIdentity): void {\n io.write(` name ${identity.name}\\n`)\n io.write(` version ${identity.version ?? 'unspecified'}\\n`)\n io.write(` repo ${identity.repo ?? 'unspecified'}${identity.tag === null ? '' : ` @ ${identity.tag}`}\\n`)\n if (identity.unix !== null)\n io.write(` built ${new Date(identity.unix * 1000).toISOString()}\\n`)\n}\n\nfunction printCheck(\n io: { write: (text: string) => void, style: { dim: (text: string) => string, green: (text: string) => string } },\n identity: UiIdentity,\n target: string | null,\n candidates: UpdateCandidate[] = [],\n): void {\n if (target !== null) {\n if (identity.tag === target) {\n io.write(`${io.style.green('up to date')} — ${identity.version ?? identity.name} at ${target}\\n`)\n return\n }\n io.write(`update available: ${identity.tag ?? 'unknown'} → ${target}\\n`)\n return\n }\n if (candidates.length === 0) {\n io.write(`up to date — no release carries ${identity.asset ?? identity.name}.zip\\n`)\n return\n }\n io.write(`${candidates.length} release(s) available:\\n`)\n for (const candidate of candidates)\n io.write(` ${candidate.tag} ${io.style.dim(candidate.asset)}\\n`)\n}\n\nasync function chooseCandidate(candidates: UpdateCandidate[], context: UpdateContext, order: 'newer' | 'older'): Promise<UpdateCandidate | null> {\n const headline = order === 'newer' ? 'Newer releases' : 'Older releases'\n const { io } = context\n io.write(`${io.style.bold(headline)}\\n`)\n for (const [index, candidate] of candidates.entries())\n io.write(` ${index + 1}) ${candidate.tag}\\n`)\n\n if (context.ask === null) {\n if (candidates.length === 1)\n return candidates[0]!\n throw new Error([\n `${candidates.length} releases are available and this session cannot ask which one:`,\n ...candidates.map(candidate => ` ${candidate.tag}`),\n ' run it in a terminal, or pass --tag <tag>',\n ].join('\\n'))\n }\n\n for (;;) {\n const answer = (await context.ask(`Select a release [1-${candidates.length}] (empty to cancel) `)).trim()\n if (answer.length === 0)\n return null\n if (/^\\d+$/.test(answer)) {\n const index = Number.parseInt(answer, 10)\n if (index >= 1 && index <= candidates.length)\n return candidates[index - 1]!\n }\n const byTag = candidates.find(candidate => candidate.tag === answer)\n if (byTag !== undefined)\n return byTag\n io.write(` ${io.style.dim(`no such choice — enter 1-${candidates.length} or a tag`)}\\n`)\n }\n}\n\n/** Fetches one release's asset and installs it, reporting what landed. */\nasync function installTag(\n repo: { owner: string, name: string },\n wantedAsset: string | null,\n tag: string,\n context: UpdateContext,\n): Promise<void> {\n const release = await fetchRelease(repo, tag, context)\n const names = release.assets.map(asset => asset.name ?? '').filter(isUiAsset)\n\n let assetName: string\n if (wantedAsset !== null && wantedAsset.length > 0) {\n const matched = matchAsset(names, wantedAsset)\n if (!matched.ok)\n throw new Error(`${matched.error}\\n in ${repo.owner}/${repo.name}@${release.tag}`)\n assetName = matched.name\n }\n else if (names.length === 1) {\n assetName = names[0]!\n }\n else {\n throw new Error(`${names.length} UI assets in ${repo.owner}/${repo.name}@${release.tag} — name one with --asset`)\n }\n\n const asset = release.assets.find(entry => entry.name === assetName)!\n const download = await downloadToTemp(assetDownloadUrl(asset), {\n 'accept': 'application/octet-stream',\n 'user-agent': `home-hosted/${context.version}`,\n ...(context.token !== null && context.token.length > 0 ? { authorization: `Bearer ${context.token}` } : {}),\n }, context)\n\n try {\n const { UiService } = await import('#src/services/ui')\n const { dataRoot } = await import('#src/helpers/paths')\n const result = await new UiService({ dataRoot }).install(download.file, assetName)\n\n if (!result.ok)\n throw new Error(`nothing was installed: ${result.error}`)\n\n const { meta } = result\n const { io } = context\n io.write(`${io.style.green('UI updated')} — ${meta.name} ${meta.version ?? ''} at ${release.tag}\\n`)\n io.write(` ${io.style.dim('refresh the browser to see it')}\\n`)\n }\n finally {\n fs.rmSync(download.dir, { recursive: true, force: true })\n }\n}\n\n/**\n * The citty entry. Like `ui-switch`, the parsing and the messages stay in the plain\n * function so they can be exercised without a terminal.\n */\nexport const uiUpdateCommand = defineCommand({\n meta: { name: 'ui-update', description: 'update the installed UI to match this panel, or pick a release' },\n run: async ({ rawArgs }) => {\n await uiUpdate(rawArgs, {\n write: text => process.stdout.write(text),\n prompt,\n style,\n })\n },\n})\n","import process from 'node:process'\nimport { defineCommand } from 'citty'\nimport { green } from '#src/cli/io'\nimport { dataRoot } from '#src/helpers/paths'\nimport { UiService } from '#src/services/ui'\n\n/** `ui-revert` drops a user-installed UI so the stock panel serves again. */\n\nexport async function runUiRevert(): Promise<void> {\n const ui = new UiService({ dataRoot })\n\n if (!ui.custom) {\n process.stdout.write('no custom UI is installed — the stock panel is already in use\\n')\n return\n }\n ui.revert()\n process.stdout.write(`${green('custom UI removed')} — the stock panel is back; refresh the browser\\n`)\n}\n\nexport const uiRevertCommand = defineCommand({\n meta: { name: 'ui-revert', description: 'go back to the stock control panel UI' },\n run: async () => {\n await runUiRevert()\n },\n})\n","import type { SubCommandsDef } from 'citty'\nimport fs from 'node:fs'\nimport process from 'node:process'\nimport { fileURLToPath } from 'node:url'\nimport { defineCommand, runCommand } from 'citty'\nimport { applyDirFlags, extractDirFlags, rejectUnknownFlags, resolveInvocation } from './cli/args'\nimport { cyan, dim, fail, heading } from './cli/io'\n\n/**\n * The command line, and nothing else. Two things happen before citty is asked\n * anything: `--home`/`--project` are peeled off and applied (because\n * `#src/helpers/paths.ts` resolves at import time), and the curated surface —\n * `help`, `version`, `unknown command`, and the `-p 4000` shorthand for `up` —\n * is decided. The command modules are then resolved lazily by citty, so their\n * own `#src` imports are safe: by the time one is imported, the directories are\n * already in the environment.\n *\n * The only static imports here are node builtins, citty, and the two path-free\n * local modules the pre-pass needs; every command (and so every state-reading\n * module) is a dynamic import behind `subCommands`.\n */\n\nconst CLI_ENTRY = fileURLToPath(import.meta.url)\n\n/**\n * The curated prose, kept here because citty cannot generate it. One named\n * piece per command, so the full reference and a single command's help are\n * composed from the same lines and can never drift apart. Every section lays\n * its left column out at the same width, so the two views read alike.\n */\n\nconst HEADER = 'home-hosted — a control panel for the processes on your home server'\n\n/** The one line the full reference lists for a command. */\nconst SYNOPSIS: Record<string, string> = {\n 'up': 'home-hosted up [options]',\n 'down': 'home-hosted down',\n 'restart': 'home-hosted restart [options]',\n 'status': 'home-hosted status [--json]',\n 'set-password': 'home-hosted set-password',\n 'set-token': 'home-hosted set-token',\n 'migrate': 'home-hosted migrate',\n 'init': 'home-hosted init',\n 'ui-switch': 'home-hosted ui-switch',\n 'ui-update': 'home-hosted ui-update',\n 'ui-revert': 'home-hosted ui-revert',\n}\n\nconst SUMMARIES: Record<string, string> = {\n 'up': 'start it in the background (detached)',\n 'down': 'stop it, and everything it supervises',\n 'restart': 'down, then up',\n 'status': 'is it running, where, and how to reach it',\n 'set-password': 'set the panel password without the API',\n 'set-token': 'set the API token that scripts and agents use',\n 'migrate': 'bring the config up to this release\\'s schema',\n 'init': 'scaffold a project that keeps its state in the repo',\n 'ui-switch': 'install a UI from a release asset, a zip file or a URL',\n 'ui-update': 'bring the installed UI up to date, or pick a release',\n 'ui-revert': 'go back to the stock control panel UI',\n}\n\n/** One option line, as the left column and the description that follows it. */\ntype OptionLine = [left: string, right: string]\n\n/** A heading and the option lines under it. */\ninterface OptionSection {\n heading: string\n lines: OptionLine[]\n}\n\n/** Every section lays its left column out at this width, so the two views align. */\nconst OPTION_WIDTH = 19\n\nconst UP_SECTION: OptionSection = {\n heading: 'Options for up/restart',\n lines: [\n ['-c, --config <file>', 'servers config (default: <state>/servers.config.json)'],\n ['-p, --port <port>', 'control panel port (default: 3999)'],\n ['--host <bind>', 'local | lan | an ipv4 address (default: local)'],\n ['--open', 'open the panel in a browser once it is up'],\n ['--no-autostart', 'do not start the entries marked autostart'],\n ['--foreground', 'run in this process instead of detaching (systemd/docker)'],\n ['--print-config', 'print the effective config and exit'],\n ],\n}\n\nconst STATUS_SECTION: OptionSection = {\n heading: 'Options for status',\n lines: [\n ['--json', 'print machine-readable JSON'],\n ],\n}\n\nconst SET_PASSWORD_SECTION: OptionSection = {\n heading: 'Options for set-password',\n lines: [\n ['--clear', 'remove the password, which disables authentication'],\n ],\n}\n\nconst SET_TOKEN_SECTION: OptionSection = {\n heading: 'Options for set-token',\n lines: [\n ['--generate', 'create a strong token and print it once'],\n ['--clear', 'remove the token, so it stops working'],\n ],\n}\n\nconst MIGRATE_SECTION: OptionSection = {\n heading: 'Options for migrate',\n lines: [\n ['--dry-run', 'print what would change, write nothing'],\n ['-y, --yes', 'apply without asking (or set HHOSTED_MIGRATE=allow)'],\n ],\n}\n\nconst INIT_SECTION: OptionSection = {\n heading: 'Options for init',\n lines: [\n ['--dir <dir>', 'where to scaffold (default: ./my-servers)'],\n ['--name <name>', 'package name (default: the directory name)'],\n ['--pm <manager>', 'pnpm | npm | yarn | bun (default: the first one installed)'],\n ['--no-install', 'write the files, install nothing'],\n ['-y, --yes', 'take every default, ask nothing'],\n ],\n}\n\nconst UI_SWITCH_SECTION: OptionSection = {\n heading: 'Options for ui-switch',\n lines: [\n ['--repo <owner/name>', 'release repo (default: NamesMT/home-hosted)'],\n ['--tag <tag>', 'release tag (default: this release\\'s tag, or latest for another repo)'],\n ['--asset <name>', 'asset to install (exact or unambiguous match)'],\n ['--file <path|url>', 'install a zip from a local path or an http(s) URL'],\n ['--list', 'list the usable assets and install nothing'],\n ['--token <token>', 'GitHub token (or GITHUB_TOKEN / GH_TOKEN)'],\n ['-y, --yes', 'take the only asset instead of asking'],\n ],\n}\n\nconst UI_UPDATE_SECTION: OptionSection = {\n heading: 'Options for ui-update',\n lines: [\n ['--check', 'report whether an update is available and install nothing'],\n ['--tag <tag>', 'install that release instead of asking'],\n ['--asset <name>', 'asset to install (defaults to the one in use)'],\n ['--old', 'list older releases instead of newer ones'],\n ['--repo <owner/name>', 'for a UI that does not declare its own repo'],\n ['--token <token>', 'GitHub token (or GITHUB_TOKEN / GH_TOKEN)'],\n ['-y, --yes', 'take the only release instead of asking'],\n ],\n}\n\nconst EVERYWHERE_SECTION: OptionSection = {\n heading: 'Everywhere',\n lines: [\n ['--home <dir>', 'state directory (default: $HHOSTED_HOME or ~/.home-hosted)'],\n ['--project <dir>', 'base for relative entry paths (default: the current directory)'],\n ['-h, --help', 'this text'],\n ['-v, --version', 'the version'],\n ],\n}\n\nconst ALIAS_SECTION: OptionSection = {\n heading: 'Alias',\n lines: [\n ['hh', 'the same CLI, on a machine where home-hosted is installed'],\n ],\n}\n\nconst ENVIRONMENT_SECTION: OptionSection = {\n heading: 'Environment',\n lines: [\n ['HHOSTED_HOME', 'where config, secrets, logs, TLS and backups live'],\n ['HHOSTED_PROJECT', 'base for relative entry paths'],\n ['HHOSTED_PASSWORD', 'the password for a non-interactive set-password'],\n ['HHOSTED_TOKEN', 'the token for a non-interactive set-token'],\n ['GITHUB_TOKEN', 'a GitHub token for ui-switch (GH_TOKEN also works)'],\n ],\n}\n\n/** The command list of the full reference, aligned as it always was. */\nfunction renderCommandList(): string {\n const width = Math.max(...Object.values(SYNOPSIS).map(synopsis => synopsis.length))\n return Object.keys(SYNOPSIS)\n .map((name) => {\n const synopsis = SYNOPSIS[name]!\n return ` ${cyan(synopsis)}${' '.repeat(width + 1 - synopsis.length)} ${SUMMARIES[name]}`\n })\n .join('\\n')\n}\n\nfunction renderSection(section: OptionSection): string {\n const lines = section.lines\n .map(([left, right]) => ` ${cyan(left)}${' '.repeat(Math.max(0, OPTION_WIDTH - left.length))} ${right}`)\n .join('\\n')\n return `${heading(section.heading)}\\n${lines}`\n}\n\n/** The trailer every command shares: the global flags, the alias and the environment. */\nconst SHARED_TRAILER = [\n renderSection(EVERYWHERE_SECTION),\n '',\n renderSection(ALIAS_SECTION),\n '',\n renderSection(ENVIRONMENT_SECTION),\n '',\n].join('\\n')\n\n/** The full reference: every command, every option, the trailer. */\nconst USAGE = [\n dim(HEADER),\n '',\n heading('Usage'),\n renderCommandList(),\n '',\n renderSection(UP_SECTION),\n '',\n renderSection(SET_PASSWORD_SECTION),\n '',\n renderSection(SET_TOKEN_SECTION),\n '',\n renderSection(MIGRATE_SECTION),\n '',\n renderSection(INIT_SECTION),\n '',\n renderSection(UI_SWITCH_SECTION),\n '',\n renderSection(STATUS_SECTION),\n '',\n SHARED_TRAILER,\n].join('\\n')\n\n// `restart` is `up` behind the scenes, so it reads `up`'s flags under its own heading.\nconst UP_COMMANDS = new Set(['up', 'restart'])\n\nconst SECTIONS: Record<string, OptionSection> = {\n 'status': STATUS_SECTION,\n 'set-password': SET_PASSWORD_SECTION,\n 'set-token': SET_TOKEN_SECTION,\n 'migrate': MIGRATE_SECTION,\n 'init': INIT_SECTION,\n 'ui-switch': UI_SWITCH_SECTION,\n 'ui-update': UI_UPDATE_SECTION,\n}\n\n/**\n * `home-hosted <command> --help`: the usage line, that command's own options,\n * and the shared trailer. A command with no options says so instead of printing\n * an empty heading.\n */\nexport function commandHelp(command: string): string {\n const synopsis = SYNOPSIS[command]\n if (synopsis === undefined)\n return USAGE\n\n const section = UP_COMMANDS.has(command) ? UP_SECTION : SECTIONS[command]\n const options = section === undefined ? dim('no options') : renderSection(section)\n\n return [\n dim(HEADER),\n '',\n cyan(synopsis),\n '',\n options,\n '',\n SHARED_TRAILER,\n ].join('\\n')\n}\n\nfunction manifestVersion(): string {\n try {\n const manifest = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf8')) as { version?: string }\n return manifest.version ?? '0.0.0'\n }\n catch {\n return '0.0.0'\n }\n}\n\nfunction version(): void {\n process.stdout.write(`${manifestVersion()}\\n`)\n}\n\n/**\n * citty owns dispatch and argument parsing. Each entry is a lazy import, so a\n * command's `#src` modules are only evaluated once `applyDirFlags()` has run.\n */\nconst COMMANDS = {\n 'up': () => import('#src/cli/up').then(module => module.upCommand(CLI_ENTRY)),\n 'down': () => import('#src/cli/down').then(module => module.downCommand),\n 'restart': () => import('#src/cli/restart').then(module => module.restartCommand(CLI_ENTRY)),\n 'status': () => import('#src/cli/status').then(module => module.statusCommand),\n 'set-password': () => import('#src/cli/set-password').then(module => module.setPasswordCommand),\n 'set-token': () => import('#src/cli/set-token').then(module => module.setTokenCommand),\n 'migrate': () => import('#src/cli/migrate').then(module => module.migrateCommand),\n 'init': () => import('#src/cli/init').then(module => module.initCommand),\n 'ui-switch': () => import('#src/cli/ui-switch').then(module => module.uiSwitchCommand),\n 'ui-update': () => import('#src/cli/ui-update').then(module => module.uiUpdateCommand),\n 'ui-revert': () => import('#src/cli/ui-revert').then(module => module.uiRevertCommand),\n} satisfies SubCommandsDef\n\nconst rootCommand = defineCommand({\n meta: {\n name: 'home-hosted',\n version: manifestVersion(),\n description: 'a control panel for the processes on your home server',\n },\n subCommands: COMMANDS,\n})\n\nconst commandNames = Object.keys(COMMANDS)\n\nasync function main(): Promise<void> {\n const dirFlags = extractDirFlags(process.argv.slice(2))\n if (dirFlags.error !== undefined)\n fail(dirFlags.error)\n applyDirFlags(dirFlags)\n\n const invocation = resolveInvocation(dirFlags.rest, commandNames)\n\n if (invocation.kind === 'help') {\n process.stdout.write(invocation.command === undefined ? USAGE : commandHelp(invocation.command))\n return\n }\n if (invocation.kind === 'version') {\n version()\n return\n }\n if (invocation.kind === 'unknown') {\n process.stderr.write(`unknown command: ${invocation.command}\\n\\n${USAGE}`)\n process.exit(1)\n }\n\n // citty parses permissively, so the refusal of a mistyped flag is asked of\n // citty's own definitions first; a command that declares none (ui-switch) keeps\n // its own parseArgs, which is strict already.\n const sub = COMMANDS[invocation.argv[0]! as keyof typeof COMMANDS]\n const command = typeof sub === 'function' ? await sub() : sub\n const problem = rejectUnknownFlags(invocation.argv.slice(1), command?.args)\n if (problem !== null)\n fail(problem)\n\n // `runCommand`, not `runMain`: citty's `runMain` prints its own usage and\n // `console.error`s the message before `process.exit(1)`, so a wrapper can never\n // turn a failure back into `fail()`'s red `error <message>`. Letting the error\n // out keeps every failure on the one shape this CLI has always had.\n try {\n await runCommand(rootCommand, { rawArgs: invocation.argv })\n }\n catch (error) {\n fail(error instanceof Error ? error.message : String(error))\n }\n}\n\nvoid main().catch((error: unknown) => {\n fail(error instanceof Error ? error.message : String(error))\n})\n"],"mappings":"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"}