| import crypto from "node:crypto"; |
| import fs from "node:fs/promises"; |
| import path from "node:path"; |
| import { resolveStateDir } from "../config/paths.js"; |
| import { writeJsonAtomic } from "../infra/json-files.js"; |
|
|
| export type NodeHostGatewayConfig = { |
| host?: string; |
| port?: number; |
| tls?: boolean; |
| tlsFingerprint?: string; |
| }; |
|
|
| export type NodeHostConfig = { |
| version: 1; |
| nodeId: string; |
| token?: string; |
| displayName?: string; |
| gateway?: NodeHostGatewayConfig; |
| }; |
|
|
| const NODE_HOST_FILE = "node.json"; |
|
|
| export function resolveNodeHostConfigPath(): string { |
| return path.join(resolveStateDir(), NODE_HOST_FILE); |
| } |
|
|
| function normalizeConfig(config: Partial<NodeHostConfig> | null): NodeHostConfig { |
| const base: NodeHostConfig = { |
| version: 1, |
| nodeId: "", |
| token: config?.token, |
| displayName: config?.displayName, |
| gateway: config?.gateway, |
| }; |
| if (config?.version === 1 && typeof config.nodeId === "string") { |
| base.nodeId = config.nodeId.trim(); |
| } |
| if (!base.nodeId) { |
| base.nodeId = crypto.randomUUID(); |
| } |
| return base; |
| } |
|
|
| export async function loadNodeHostConfig(): Promise<NodeHostConfig | null> { |
| const filePath = resolveNodeHostConfigPath(); |
| try { |
| const raw = await fs.readFile(filePath, "utf8"); |
| const parsed = JSON.parse(raw) as Partial<NodeHostConfig>; |
| return normalizeConfig(parsed); |
| } catch { |
| return null; |
| } |
| } |
|
|
| export async function saveNodeHostConfig(config: NodeHostConfig): Promise<void> { |
| const filePath = resolveNodeHostConfigPath(); |
| await writeJsonAtomic(filePath, config, { mode: 0o600 }); |
| } |
|
|
| export async function ensureNodeHostConfig(): Promise<NodeHostConfig> { |
| const existing = await loadNodeHostConfig(); |
| const normalized = normalizeConfig(existing); |
| await saveNodeHostConfig(normalized); |
| return normalized; |
| } |
|
|