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 }