/ src / lib / server / daemon / admin-metadata.ts
admin-metadata.ts
 1  import fs from 'node:fs'
 2  import path from 'node:path'
 3  
 4  import { DATA_DIR } from '@/lib/server/data-dir'
 5  import type { DaemonAdminMetadata } from '@/lib/server/daemon/types'
 6  
 7  function resolveHomeDir(): string {
 8    const configured = process.env.SWARMCLAW_HOME?.trim()
 9    if (configured) return path.resolve(configured)
10    return path.dirname(DATA_DIR)
11  }
12  
13  export const DAEMON_ADMIN_METADATA_PATH = path.join(resolveHomeDir(), 'daemon-admin.json')
14  export const DAEMON_LOG_PATH = path.join(resolveHomeDir(), 'daemon.log')
15  
16  export function isProcessRunning(pid: number | null | undefined): boolean {
17    if (typeof pid !== 'number' || !Number.isFinite(pid) || pid <= 0) return false
18    try {
19      process.kill(pid, 0)
20      return true
21    } catch {
22      return false
23    }
24  }
25  
26  function normalizeMetadata(value: unknown): DaemonAdminMetadata | null {
27    if (!value || typeof value !== 'object') return null
28    const candidate = value as Partial<DaemonAdminMetadata>
29    const pid = typeof candidate.pid === 'number' && Number.isFinite(candidate.pid) ? Math.trunc(candidate.pid) : null
30    const port = typeof candidate.port === 'number' && Number.isFinite(candidate.port) ? Math.trunc(candidate.port) : null
31    const token = typeof candidate.token === 'string' ? candidate.token.trim() : ''
32    if (!pid || !port || !token) return null
33    return {
34      pid,
35      port,
36      token,
37      launchedAt: typeof candidate.launchedAt === 'number' && Number.isFinite(candidate.launchedAt)
38        ? Math.trunc(candidate.launchedAt)
39        : Date.now(),
40      source: typeof candidate.source === 'string' ? candidate.source : null,
41    }
42  }
43  
44  export function readDaemonAdminMetadata(): DaemonAdminMetadata | null {
45    try {
46      const raw = JSON.parse(fs.readFileSync(DAEMON_ADMIN_METADATA_PATH, 'utf8'))
47      return normalizeMetadata(raw)
48    } catch {
49      return null
50    }
51  }
52  
53  export function writeDaemonAdminMetadata(metadata: DaemonAdminMetadata): void {
54    fs.mkdirSync(path.dirname(DAEMON_ADMIN_METADATA_PATH), { recursive: true })
55    fs.writeFileSync(DAEMON_ADMIN_METADATA_PATH, `${JSON.stringify(metadata, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 })
56  }
57  
58  export function clearDaemonAdminMetadata(): void {
59    try {
60      fs.unlinkSync(DAEMON_ADMIN_METADATA_PATH)
61    } catch {
62      // Ignore missing metadata file.
63    }
64  }