import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; import { randomBytes } from 'node:crypto'; type JsonRecord = Record; export interface IHiveCastNatsConfig { readonly url: string; readonly wsUrl?: string; readonly leafnodeUrl?: string; } export interface IHiveCastNatsCredentials { readonly accountSeed?: string; readonly jwt?: string; readonly seed?: string; } export interface IHiveCastHostLinkRecord { readonly version: 1; readonly cloudUrl: string; readonly deviceRegistryRoot?: string; readonly hostLinkId?: string; readonly hostId?: string; readonly principalId?: string; readonly hostName?: string; readonly deviceSlug?: string; readonly authorityCoordinator?: boolean; readonly spaceId?: string; readonly routeKey?: string; readonly publicNamespace?: string; readonly authorityRoot: string; readonly linkedAt: string; readonly credentials: { readonly hostLinkTokenRef?: string; readonly registryTokenRef?: string; readonly natsCredentialRef: string; readonly natsLeafnodeCredentialRef?: string; }; } interface ILocalOwnerTransportSnapshot { readonly root: string; readonly authorityRoot?: string; readonly nats: JsonRecord; } export interface IHiveCastLocalOwnerTransport { readonly root: string; readonly authorityRoot?: string; } interface ILinkedAuthorityTransportSnapshot { readonly authorityRoot: string; readonly nats: JsonRecord; readonly leafnode?: { readonly url: string; readonly credentialsRef: string; }; readonly authorityCoordinator?: boolean; readonly routeKey?: string; readonly publicNamespace?: string; readonly spaceId?: string; } export interface IHiveCastLinkPaths { readonly matrixHome: string; readonly credentialsDir: string; readonly installIdentityPath: string; readonly linkPath: string; readonly natsCredentialsPath: string; readonly natsLeafnodeCredentialsPath: string; readonly hostLinkTokenPath: string; readonly hostCredentialsPath: string; readonly hostConfigPath: string; } export interface IHiveCastInstallIdentity { readonly version: 1; readonly installId: string; readonly hostName?: string; readonly createdAt: string; readonly updatedAt: string; } export interface ISaveHiveCastHostLinkOptions { readonly cwd: string; readonly matrixHome?: string; readonly cloudUrl: string; readonly deviceRegistryRoot?: string; readonly authorityRoot: string; readonly nats: IHiveCastNatsConfig; readonly natsCredentials: IHiveCastNatsCredentials; readonly hostLinkToken?: string; readonly hostLinkId?: string; readonly hostId?: string; readonly principalId?: string; readonly hostName?: string; readonly deviceSlug?: string; readonly authorityCoordinator?: boolean; readonly spaceId?: string; readonly routeKey?: string; readonly publicNamespace?: string; readonly linkedAt?: string; } export interface IHiveCastLinkStatus { readonly linked: boolean; readonly matrixHome: string; readonly linkPath: string; readonly natsCredentialsPath: string; readonly hostConfigPath: string; readonly natsCredentialsPresent: boolean; readonly hostConfigPresent: boolean; readonly hostConfigLinked: boolean; readonly hostLinkId?: string; readonly authorityRoot?: string; readonly routeKey?: string; readonly publicNamespace?: string; readonly spaceId?: string; readonly authorityCoordinator?: boolean; readonly cloudUrl?: string; readonly linkedAt?: string; } export interface IRemoveHiveCastHostLinkResult { readonly removed: boolean; readonly hostConfigReset: boolean; readonly paths: IHiveCastLinkPaths; } export interface IRemoveHiveCastHostLinkOptions { readonly preserveCredentials?: boolean; } const DEFAULT_MATRIX_HOME = path.join(os.homedir(), '.matrix'); const HIVECAST_NATS_JSON_CREDENTIAL_REF = 'file:credentials/hivecast-nats.json'; const HIVECAST_NATS_LEAFNODE_CREDENTIAL_REF = 'file:credentials/hivecast-nats.creds'; const HIVECAST_DEFAULT_RUNTIME_KEYS = [ 'SYSTEM', 'GATEWAY', 'INFERENCE', 'AGENTS', 'WEB', 'EDGE', 'DIRECTOR', 'CHAT', 'INFERENCE-SETTINGS', 'FLOWPAD', 'SMITHERS', 'HOST-CONTROL', 'HOST-OPS', ] as const; function asRecord(value: unknown): JsonRecord | null { if (!value || typeof value !== 'object' || Array.isArray(value)) { return null; } return value as JsonRecord; } function optionalString(value: unknown): string | undefined { return typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined; } function expandHome(value: string): string { if (value === '~') { return os.homedir(); } return value.startsWith('~/') ? path.join(os.homedir(), value.slice(2)) : value; } export function resolveHiveCastMatrixHome(cwd: string, matrixHome?: string): string { const raw = matrixHome ?? process.env.MATRIX_HOST_HOME ?? process.env.MATRIX_HOME ?? DEFAULT_MATRIX_HOME; const expanded = expandHome(raw); return path.isAbsolute(expanded) ? path.resolve(expanded) : path.resolve(cwd, expanded); } export function hiveCastLinkPaths(cwd: string, matrixHome?: string): IHiveCastLinkPaths { const resolvedHome = resolveHiveCastMatrixHome(cwd, matrixHome); const credentialsDir = path.join(resolvedHome, 'credentials'); return { matrixHome: resolvedHome, credentialsDir, installIdentityPath: path.join(credentialsDir, 'hivecast-install.json'), linkPath: path.join(credentialsDir, 'hivecast-link.json'), natsCredentialsPath: path.join(credentialsDir, 'hivecast-nats.json'), natsLeafnodeCredentialsPath: path.join(credentialsDir, 'hivecast-nats.creds'), hostLinkTokenPath: path.join(credentialsDir, 'hivecast-host-link-token'), hostCredentialsPath: path.join(resolvedHome, 'credentials.json'), hostConfigPath: path.join(resolvedHome, 'host.json'), }; } export function ensureHiveCastInstallIdentity(options: { readonly cwd: string; readonly matrixHome?: string; readonly hostName?: string; }): { readonly identity: IHiveCastInstallIdentity; readonly paths: IHiveCastLinkPaths } { const paths = hiveCastLinkPaths(options.cwd, options.matrixHome); const existing = readJsonFile(paths.installIdentityPath); const existingInstallId = optionalString(existing?.installId); const now = new Date().toISOString(); const hostName = optionalString(options.hostName); if (existingInstallId) { const existingHostName = optionalString(existing?.hostName); const nextHostName = hostName ?? existingHostName; const identity: IHiveCastInstallIdentity = { version: 1, installId: existingInstallId, ...(nextHostName ? { hostName: nextHostName } : {}), createdAt: optionalString(existing?.createdAt) ?? now, updatedAt: hostName && hostName !== existingHostName ? now : (optionalString(existing?.updatedAt) ?? now), }; if (hostName && hostName !== existingHostName) { writePrivateJson(paths.installIdentityPath, identity); } return { identity, paths }; } const identity: IHiveCastInstallIdentity = { version: 1, installId: `install_${randomBytes(10).toString('hex')}`, ...(hostName ? { hostName } : {}), createdAt: now, updatedAt: now, }; writePrivateJson(paths.installIdentityPath, identity); return { identity, paths }; } function localOwnerRootToken(installId: string): string { const token = installId .toLowerCase() .replace(/[^a-z0-9]+/g, '-') .replace(/^-+|-+$/g, ''); if (!token) { throw new Error('HIVECAST_INSTALL_IDENTITY_INVALID: installId cannot produce a local owner root'); } return token; } function localOwnerAuthorityRoot(matrixHome: string): string { const { identity } = ensureHiveCastInstallIdentity({ cwd: process.cwd(), matrixHome }); return `local-${localOwnerRootToken(identity.installId)}`; } function runtimeScopeToken(installId: string): string { const token = installId .toUpperCase() .replace(/[^A-Z0-9]+/g, '-') .replace(/^-+|-+$/g, ''); if (!token) { throw new Error('HIVECAST_INSTALL_IDENTITY_INVALID: installId cannot produce a runtime scope'); } return token; } function installRuntimeScope(matrixHome: string): string { const { identity } = ensureHiveCastInstallIdentity({ cwd: process.cwd(), matrixHome }); return runtimeScopeToken(identity.installId); } function writePrivateJson(filePath: string, payload: object): void { fs.mkdirSync(path.dirname(filePath), { recursive: true }); fs.writeFileSync(filePath, `${JSON.stringify(payload, null, 2)}\n`, 'utf8'); try { fs.chmodSync(filePath, 0o600); } catch { } } function writePrivateText(filePath: string, payload: string): void { fs.mkdirSync(path.dirname(filePath), { recursive: true }); fs.writeFileSync(filePath, payload, 'utf8'); try { fs.chmodSync(filePath, 0o600); } catch { } } function natsUserCredentialsFile(credentials: IHiveCastNatsCredentials): string | undefined { const jwt = optionalString(credentials.jwt); const seed = optionalString(credentials.seed); if (!jwt || !seed) { return undefined; } return [ '-----BEGIN NATS USER JWT-----', jwt, '------END NATS USER JWT------', '', '-----BEGIN USER NKEY SEED-----', seed, '------END USER NKEY SEED------', '', ].join('\n'); } function ownerForPath(filePath: string): { readonly uid: number; readonly gid: number } { const stat = fs.statSync(filePath); return { uid: stat.uid, gid: stat.gid }; } function alignOwnership(filePath: string, owner: { readonly uid: number; readonly gid: number }): void { if (typeof process.getuid !== 'function' || process.getuid() !== 0) { return; } fs.chownSync(filePath, owner.uid, owner.gid); } function readJsonFile(filePath: string): JsonRecord | null { if (!fs.existsSync(filePath)) { return null; } try { const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8')) as unknown; return asRecord(parsed); } catch { return null; } } function buildHostConfig( existing: JsonRecord | null, matrixHome: string, localOwnerTransportOverride?: ILocalOwnerTransportSnapshot, linkedAuthorityTransport?: ILinkedAuthorityTransportSnapshot, ): JsonRecord { const currentHttp = asRecord(existing?.http); const currentRuntimeStorage = asRecord(existing?.runtimeStorage); const currentPackageStorage = asRecord(existing?.packageStorage); const currentAuth = asRecord(existing?.auth); const localOwnerTransport = readLocalOwnerTransportSnapshotForHome(matrixHome, existing) ?? localOwnerTransportOverride ?? captureLocalOwnerTransport(matrixHome, existing); const generatedLocalOwnerRoot = localOwnerAuthorityRoot(matrixHome); const generatedLocalOwnerTransport = { root: generatedLocalOwnerRoot, authorityRoot: generatedLocalOwnerRoot, addressRoot: generatedLocalOwnerRoot, nats: defaultLocalOwnerNatsTransport(), }; const localOwnerHostTransport = localOwnerTransport ? localOwnerHostConfigTransport(localOwnerTransport) : generatedLocalOwnerTransport; const hostTransport = linkedAuthorityTransport ? linkedAuthorityTransport.authorityCoordinator === false ? linkedLeafHostConfigTransport(linkedAuthorityTransport, localOwnerHostTransport) : linkedAuthorityHostConfigTransport(linkedAuthorityTransport) : localOwnerHostTransport; const nextConfig: JsonRecord = { ...(existing ?? {}), kind: optionalString(existing?.kind) ?? 'MatrixHostConfig', version: typeof existing?.version === 'number' ? existing.version : 1, home: matrixHome, ...(localOwnerTransport ? { hivecastLocalOwnerTransport: localOwnerTransport } : {}), http: currentHttp ?? { host: '127.0.0.1', port: 3100, }, transport: hostTransport, auth: currentAuth ?? { mode: 'local-client', }, runtimeStorage: currentRuntimeStorage ?? { recordsDir: 'runtimes', logsDir: 'logs/runtimes', }, packageStorage: currentPackageStorage ?? { globalDir: 'packages/global', systemDir: 'packages/system', }, }; if (linkedAuthorityTransport?.authorityCoordinator === false && linkedAuthorityTransport.leafnode) { nextConfig.natsTopology = linkedLeafNatsTopology(hostTransport.nats as JsonRecord, linkedAuthorityTransport.leafnode); } else { delete nextConfig.natsTopology; } return nextConfig; } function linkedLeafNatsTopology( localNats: JsonRecord, leafnode: { readonly url: string; readonly credentialsRef: string }, ): JsonRecord { const clientUrl = optionalString(localNats.url); const wsUrl = optionalString(localNats.wsUrl); return { mode: 'leafnode', ...(clientUrl ? { clientUrl } : {}), ...(wsUrl ? { wsUrl } : {}), leafnodes: [{ url: leafnode.url, credentialsRef: leafnode.credentialsRef, }], }; } function assertLinkedHostConfigConverged( record: IHiveCastHostLinkRecord, hostConfig: JsonRecord, ): void { const transport = asRecord(hostConfig.transport); const root = optionalString(transport?.root); const authorityRoot = optionalString(transport?.authorityRoot); const addressRoot = optionalString(transport?.addressRoot); for (const [field, actual] of [ ['transport.root', root], ['transport.authorityRoot', authorityRoot], ['transport.addressRoot', addressRoot], ] as const) { if (actual !== record.authorityRoot) { throw new Error( `HIVECAST_LINK_ROOT_CONVERGENCE_FAILED: expected host config ${field} ${record.authorityRoot}, got ${actual ?? '(missing)'}`, ); } } } function linkedAuthorityHostConfigTransport(snapshot: ILinkedAuthorityTransportSnapshot): JsonRecord { return { root: snapshot.authorityRoot, authorityRoot: snapshot.authorityRoot, addressRoot: snapshot.authorityRoot, ...(snapshot.spaceId ? { spaceId: snapshot.spaceId } : {}), ...(snapshot.routeKey ? { routeKey: snapshot.routeKey } : {}), ...(snapshot.publicNamespace ? { publicNamespace: snapshot.publicNamespace } : {}), nats: { ...snapshot.nats }, }; } function linkedLeafHostConfigTransport( snapshot: ILinkedAuthorityTransportSnapshot, localOwnerTransport: JsonRecord, ): JsonRecord { return { root: snapshot.authorityRoot, authorityRoot: snapshot.authorityRoot, addressRoot: snapshot.authorityRoot, ...(snapshot.spaceId ? { spaceId: snapshot.spaceId } : {}), ...(snapshot.routeKey ? { routeKey: snapshot.routeKey } : {}), ...(snapshot.publicNamespace ? { publicNamespace: snapshot.publicNamespace } : {}), nats: localNatsTransportForLinkedLeaf(snapshot, localOwnerTransport), }; } function localNatsTransportForLinkedLeaf( snapshot: ILinkedAuthorityTransportSnapshot, localOwnerTransport: JsonRecord, ): JsonRecord { const localNats = asRecord(localOwnerTransport.nats); return stripNatsCredentialFields(localNats ?? snapshot.nats); } function defaultLocalOwnerNatsTransport(): JsonRecord { return { mode: 'external', url: 'nats://127.0.0.1:4222', wsUrl: 'ws://127.0.0.1:4223', port: 4222, wsPort: 4223, dataDir: 'nats/host-default', pidFile: 'nats/host-default/nats-server.pid', }; } function stripNatsCredentialFields(nats: JsonRecord): JsonRecord { const { credentialsRef: _credentialsRef, credentials: _credentials, natsCredentialRef: _natsCredentialRef, ...rest } = nats; return rest; } function linkedAuthorityNatsTransport( nats: IHiveCastNatsConfig, natsCredentialsPath: string, ): JsonRecord { const url = optionalString(nats.url); if (!url) { throw new Error('HIVECAST_LINK_AUTHORITY_NATS_MISSING: linked Device setup requires nats.url from the setup exchange'); } if (/^wss?:\/\//i.test(url)) { throw new Error( `HIVECAST_LINK_AUTHORITY_NATS_WS_ONLY: linked Device setup requires a server NATS URL for runtimes, got ${url}`, ); } const wsUrl = optionalString(nats.wsUrl); return { mode: 'external', url, ...(wsUrl ? { wsUrl } : {}), credentialsRef: `file:${natsCredentialsPath}`, }; } function localOwnerHostConfigTransport(snapshot: ILocalOwnerTransportSnapshot): JsonRecord { return { root: snapshot.root, authorityRoot: snapshot.authorityRoot ?? snapshot.root, addressRoot: snapshot.root, nats: { ...snapshot.nats }, }; } function captureLocalOwnerTransport(matrixHome: string, existing: JsonRecord | null): ILocalOwnerTransportSnapshot | undefined { return readLocalOwnerTransportSnapshotFromTransportForHome(matrixHome, asRecord(existing?.transport)); } function captureLocalOwnerTransportFromCurrentNats(matrixHome: string, existing: JsonRecord | null): ILocalOwnerTransportSnapshot | undefined { const transport = asRecord(existing?.transport); const nats = asRecord(transport?.nats); if (!nats) return undefined; return readLocalOwnerTransportSnapshotFromTransportForHome(matrixHome, { root: localOwnerAuthorityRoot(matrixHome), nats: { ...nats }, }); } function readLocalOwnerTransportSnapshot(config: JsonRecord | null): ILocalOwnerTransportSnapshot | undefined { const snapshot = asRecord(config?.hivecastLocalOwnerTransport); if (!snapshot) return undefined; const root = optionalString(snapshot.root); const nats = asRecord(snapshot.nats); if (!root || !nats) return undefined; return readLocalOwnerTransportSnapshotFromTransport({ root, ...(optionalString(snapshot.authorityRoot) ? { authorityRoot: optionalString(snapshot.authorityRoot) } : {}), nats: { ...nats }, }); } function readLocalOwnerTransportSnapshotForHome( matrixHome: string, config: JsonRecord | null, ): ILocalOwnerTransportSnapshot | undefined { const snapshot = readLocalOwnerTransportSnapshot(config); if (snapshot) return snapshot; const saved = asRecord(config?.hivecastLocalOwnerTransport); if (!saved) return undefined; return readLocalOwnerTransportSnapshotFromTransportForHome(matrixHome, saved); } function readLocalOwnerTransportSnapshotFromStatus(matrixHome: string): ILocalOwnerTransportSnapshot | undefined { const status = readJsonFile(path.join(matrixHome, 'host.status.json')); if (optionalString(status?.status) !== 'running') return undefined; return readLocalOwnerTransportSnapshotFromTransportForHome(matrixHome, asRecord(status?.transport)); } function readLocalOwnerTransportSnapshotFromRuntimeRecords(matrixHome: string): ILocalOwnerTransportSnapshot | undefined { const runtimesDir = path.join(matrixHome, 'runtimes'); if (!fs.existsSync(runtimesDir)) return undefined; const localScope = localDefaultRuntimeScope(matrixHome); const preferredRuntimeIds = HIVECAST_DEFAULT_RUNTIME_KEYS.map((key) => `RUNTIME-HOST-${localScope}-${key}`); for (const runtimeId of preferredRuntimeIds) { const snapshot = readLocalOwnerTransportSnapshotFromRuntimeRecord(matrixHome, path.join(runtimesDir, runtimeId, 'runtime.json')); if (snapshot) return snapshot; } for (const entry of fs.readdirSync(runtimesDir, { withFileTypes: true })) { if (!entry.isDirectory() || !entry.name.startsWith(`RUNTIME-HOST-${localScope}-`)) continue; const snapshot = readLocalOwnerTransportSnapshotFromRuntimeRecord(matrixHome, path.join(runtimesDir, entry.name, 'runtime.json')); if (snapshot) return snapshot; } return undefined; } function readLocalOwnerTransportSnapshotFromRuntimeRecord( matrixHome: string, recordPath: string, ): ILocalOwnerTransportSnapshot | undefined { const record = readJsonFile(recordPath); const metadata = asRecord(record?.metadata); return readLocalOwnerTransportSnapshotFromTransportForHome(matrixHome, asRecord(metadata?.transport)); } function readLocalOwnerTransportSnapshotFromTransportForHome( _matrixHome: string, transport: JsonRecord | null, ): ILocalOwnerTransportSnapshot | undefined { return readLocalOwnerTransportSnapshotFromTransport(transport); } function readLocalOwnerTransportSnapshotFromTransport(transport: JsonRecord | null): ILocalOwnerTransportSnapshot | undefined { if (!transport) return undefined; if ( optionalString(transport.routeKey) || optionalString(transport.publicNamespace) || optionalString(transport.spaceId) ) { return undefined; } const root = optionalString(transport.root); if (!root) return undefined; const nats = asRecord(transport.nats); if (!nats || optionalString(nats.credentialsRef)) return undefined; const mode = optionalString(nats.mode); if (mode !== 'embedded' && mode !== 'external') return undefined; const natsPort = positivePort(nats.port) ?? loopbackUrlPort(optionalString(nats.url)); const natsWsPort = positivePort(nats.wsPort) ?? loopbackUrlPort(optionalString(nats.wsUrl)); const maxPayloadBytes = positiveInteger(nats.maxPayloadBytes); if (!natsPort || !natsWsPort) return undefined; const natsUrl = mode === 'external' ? optionalString(nats.url) ?? `nats://127.0.0.1:${natsPort}` : undefined; const natsWsUrl = mode === 'external' ? optionalString(nats.wsUrl) ?? `ws://127.0.0.1:${natsWsPort}` : undefined; const authorityRoot = optionalString(transport.authorityRoot); return { root, ...(authorityRoot && authorityRoot !== root ? { authorityRoot } : {}), nats: { mode, ...(natsUrl ? { url: natsUrl } : {}), ...(natsWsUrl ? { wsUrl: natsWsUrl } : {}), port: natsPort, wsPort: natsWsPort, ...(maxPayloadBytes ? { maxPayloadBytes } : {}), dataDir: optionalString(nats.dataDir) ?? 'nats/host-default', pidFile: optionalString(nats.pidFile) ?? 'nats/host-default/nats-server.pid', ...(optionalString(nats.binaryPath) ? { binaryPath: optionalString(nats.binaryPath) } : {}), }, }; } export function readHiveCastLocalOwnerTransport( cwd: string, matrixHome?: string, ): IHiveCastLocalOwnerTransport | null { const paths = hiveCastLinkPaths(cwd, matrixHome); const config = readJsonFile(paths.hostConfigPath); const snapshot = readLocalOwnerTransportSnapshot(config) ?? readLocalOwnerTransportSnapshotForHome(paths.matrixHome, config) ?? captureLocalOwnerTransport(paths.matrixHome, config) ?? readLocalOwnerTransportSnapshotFromStatus(paths.matrixHome) ?? readLocalOwnerTransportSnapshotFromRuntimeRecords(paths.matrixHome); if (!snapshot) return null; return { root: snapshot.root, ...(snapshot.authorityRoot ? { authorityRoot: snapshot.authorityRoot } : {}), }; } function positivePort(value: unknown): number | undefined { if (typeof value !== 'number' || !Number.isInteger(value) || value <= 0) { return undefined; } return value; } function positiveInteger(value: unknown): number | undefined { if (typeof value !== 'number' || !Number.isInteger(value) || value <= 0) { return undefined; } return value; } function loopbackUrlPort(raw: string | undefined): number | undefined { if (!raw) return undefined; try { const parsed = new URL(raw); if (!['127.0.0.1', 'localhost', '[::1]', '::1'].includes(parsed.hostname)) { return undefined; } const port = Number.parseInt(parsed.port, 10); return Number.isFinite(port) && port > 0 ? port : undefined; } catch { return undefined; } } function localDefaultRuntimeScope(matrixHome: string): string { return installRuntimeScope(matrixHome); } function activeDefaultRuntimeScope(matrixHome: string): string { return localDefaultRuntimeScope(matrixHome); } function defaultRuntimeIdsForScope(scope: string): Set { return new Set(HIVECAST_DEFAULT_RUNTIME_KEYS.map((key) => `RUNTIME-HOST-${scope}-${key}`)); } function isHiveCastDefaultRuntimeId(runtimeId: string): boolean { return runtimeId.startsWith('RUNTIME-HOST-') && HIVECAST_DEFAULT_RUNTIME_KEYS.some((key) => runtimeId.endsWith(`-${key}`)); } function updateHiveCastDefaultRuntimePolicies(matrixHome: string, activeScope: string): void { const runtimesDir = path.join(matrixHome, 'runtimes'); if (!fs.existsSync(runtimesDir)) return; const activeIds = defaultRuntimeIdsForScope(activeScope); for (const entry of fs.readdirSync(runtimesDir, { withFileTypes: true })) { if (!entry.isDirectory()) continue; const runtimeId = entry.name; if (!isHiveCastDefaultRuntimeId(runtimeId)) continue; const recordPath = path.join(runtimesDir, runtimeId, 'runtime.json'); const record = readJsonFile(recordPath); if (!record) continue; const recordOwner = ownerForPath(recordPath); const active = activeIds.has(runtimeId); const nextRecord = { ...record, startup: active ? 'auto' : 'manual', restart: active ? 'always' : 'never', updatedAt: new Date().toISOString(), metadata: { ...(asRecord(record.metadata) ?? {}), hivecastDefaultRuntimePolicy: active ? 'active' : 'inactive', hivecastDefaultRuntimePolicyUpdatedAt: new Date().toISOString(), }, }; writePrivateJson(recordPath, nextRecord); alignOwnership(recordPath, recordOwner); } } export function saveHiveCastHostLink(options: ISaveHiveCastHostLinkOptions): { readonly record: IHiveCastHostLinkRecord; readonly paths: IHiveCastLinkPaths; } { const paths = hiveCastLinkPaths(options.cwd, options.matrixHome); fs.mkdirSync(paths.matrixHome, { recursive: true }); const homeOwner = ownerForPath(paths.matrixHome); fs.mkdirSync(paths.credentialsDir, { recursive: true }); alignOwnership(paths.credentialsDir, homeOwner); writePrivateJson(paths.natsCredentialsPath, options.natsCredentials); alignOwnership(paths.natsCredentialsPath, homeOwner); writePrivateJson(paths.hostCredentialsPath, options.natsCredentials); alignOwnership(paths.hostCredentialsPath, homeOwner); const leafnodeUrl = optionalString(options.nats.leafnodeUrl); const leafnodeCredentialBody = natsUserCredentialsFile(options.natsCredentials); if (options.authorityCoordinator !== true && leafnodeUrl) { if (!leafnodeCredentialBody) { throw new Error('HIVECAST_LINK_LEAFNODE_CREDENTIALS_MISSING: linked Device leaf-node transport requires jwt+seed credentials'); } writePrivateText(paths.natsLeafnodeCredentialsPath, leafnodeCredentialBody); alignOwnership(paths.natsLeafnodeCredentialsPath, homeOwner); } else { fs.rmSync(paths.natsLeafnodeCredentialsPath, { force: true }); } if (options.hostLinkToken) { writePrivateText(paths.hostLinkTokenPath, `${options.hostLinkToken}\n`); alignOwnership(paths.hostLinkTokenPath, homeOwner); } else { fs.rmSync(paths.hostLinkTokenPath, { force: true }); } const record: IHiveCastHostLinkRecord = { version: 1, cloudUrl: options.cloudUrl, ...(options.deviceRegistryRoot ? { deviceRegistryRoot: options.deviceRegistryRoot } : {}), ...(options.hostLinkId ? { hostLinkId: options.hostLinkId } : {}), ...(options.hostId ? { hostId: options.hostId } : {}), ...(options.principalId ? { principalId: options.principalId } : {}), ...(options.hostName ? { hostName: options.hostName } : {}), ...(options.deviceSlug ? { deviceSlug: options.deviceSlug } : {}), authorityCoordinator: options.authorityCoordinator === true, ...(options.spaceId ? { spaceId: options.spaceId } : {}), ...(options.routeKey ? { routeKey: options.routeKey } : {}), ...(options.publicNamespace ? { publicNamespace: options.publicNamespace } : {}), authorityRoot: options.authorityRoot, linkedAt: options.linkedAt ?? new Date().toISOString(), credentials: { ...(options.hostLinkToken ? { hostLinkTokenRef: 'file:credentials/hivecast-host-link-token' } : {}), natsCredentialRef: HIVECAST_NATS_JSON_CREDENTIAL_REF, ...(options.authorityCoordinator !== true && leafnodeUrl ? { natsLeafnodeCredentialRef: HIVECAST_NATS_LEAFNODE_CREDENTIAL_REF } : {}), }, }; writePrivateJson(paths.linkPath, record); alignOwnership(paths.linkPath, homeOwner); const existingHostConfig = readJsonFile(paths.hostConfigPath); const localOwnerTransport = readLocalOwnerTransportSnapshotForHome(paths.matrixHome, existingHostConfig) ?? captureLocalOwnerTransportFromCurrentNats(paths.matrixHome, existingHostConfig) ?? readLocalOwnerTransportSnapshotFromStatus(paths.matrixHome) ?? readLocalOwnerTransportSnapshotFromRuntimeRecords(paths.matrixHome) ?? captureLocalOwnerTransport(paths.matrixHome, existingHostConfig); const linkedAuthorityTransport: ILinkedAuthorityTransportSnapshot = { authorityRoot: options.authorityRoot, ...(options.routeKey ? { routeKey: options.routeKey } : {}), ...(options.publicNamespace ? { publicNamespace: options.publicNamespace } : {}), ...(options.spaceId ? { spaceId: options.spaceId } : {}), authorityCoordinator: options.authorityCoordinator === true, ...(options.authorityCoordinator !== true && leafnodeUrl ? { leafnode: { url: leafnodeUrl, credentialsRef: HIVECAST_NATS_LEAFNODE_CREDENTIAL_REF } } : {}), nats: linkedAuthorityNatsTransport(options.nats, paths.natsCredentialsPath), }; const hostConfig = buildHostConfig( existingHostConfig, paths.matrixHome, localOwnerTransport, linkedAuthorityTransport, ); assertLinkedHostConfigConverged(record, hostConfig); writePrivateJson(paths.hostConfigPath, hostConfig); alignOwnership(paths.hostConfigPath, homeOwner); updateHiveCastDefaultRuntimePolicies(paths.matrixHome, activeDefaultRuntimeScope(paths.matrixHome)); return { record, paths }; } export function readHiveCastHostLink(cwd: string, matrixHome?: string): { readonly record: IHiveCastHostLinkRecord | null; readonly paths: IHiveCastLinkPaths; } { const paths = hiveCastLinkPaths(cwd, matrixHome); const parsed = readJsonFile(paths.linkPath); if (!parsed || parsed.version !== 1) { return { record: null, paths }; } const authorityRoot = optionalString(parsed.authorityRoot); const cloudUrl = optionalString(parsed.cloudUrl); const credentials = asRecord(parsed.credentials); const natsCredentialRef = optionalString(credentials?.natsCredentialRef); const linkedAt = optionalString(parsed.linkedAt); if (!authorityRoot || !cloudUrl || !natsCredentialRef || !linkedAt) { return { record: null, paths }; } const hostId = optionalString(parsed.hostId); const deviceRegistryRoot = optionalString(parsed.deviceRegistryRoot); const hostLinkId = optionalString(parsed.hostLinkId); const principalId = optionalString(parsed.principalId); const hostName = optionalString(parsed.hostName); const deviceSlug = optionalString(parsed.deviceSlug); const authorityCoordinator = typeof parsed.authorityCoordinator === 'boolean' ? parsed.authorityCoordinator : undefined; const spaceId = optionalString(parsed.spaceId); const routeKey = optionalString(parsed.routeKey); const publicNamespace = optionalString(parsed.publicNamespace); const hostLinkTokenRef = optionalString(credentials?.hostLinkTokenRef); const registryTokenRef = optionalString(credentials?.registryTokenRef); const natsLeafnodeCredentialRef = optionalString(credentials?.natsLeafnodeCredentialRef); return { paths, record: { version: 1, cloudUrl, ...(deviceRegistryRoot ? { deviceRegistryRoot } : {}), ...(hostLinkId ? { hostLinkId } : {}), ...(hostId ? { hostId } : {}), ...(principalId ? { principalId } : {}), ...(hostName ? { hostName } : {}), ...(deviceSlug ? { deviceSlug } : {}), ...(typeof authorityCoordinator === 'boolean' ? { authorityCoordinator } : {}), ...(spaceId ? { spaceId } : {}), ...(routeKey ? { routeKey } : {}), ...(publicNamespace ? { publicNamespace } : {}), authorityRoot, linkedAt, credentials: { natsCredentialRef, ...(hostLinkTokenRef ? { hostLinkTokenRef } : {}), ...(registryTokenRef ? { registryTokenRef } : {}), ...(natsLeafnodeCredentialRef ? { natsLeafnodeCredentialRef } : {}), }, }, }; } export function removeHiveCastCredentialFiles( paths: Pick, ): boolean { const existed = fs.existsSync(paths.natsCredentialsPath) || fs.existsSync(paths.natsLeafnodeCredentialsPath) || fs.existsSync(paths.hostCredentialsPath) || fs.existsSync(paths.hostLinkTokenPath); fs.rmSync(paths.natsCredentialsPath, { force: true }); fs.rmSync(paths.natsLeafnodeCredentialsPath, { force: true }); fs.rmSync(paths.hostCredentialsPath, { force: true }); fs.rmSync(paths.hostLinkTokenPath, { force: true }); return existed; } export function removeHiveCastHostLink( cwd: string, matrixHome?: string, options: IRemoveHiveCastHostLinkOptions = {}, ): IRemoveHiveCastHostLinkResult { const paths = hiveCastLinkPaths(cwd, matrixHome); const existed = fs.existsSync(paths.linkPath) || fs.existsSync(paths.natsCredentialsPath) || fs.existsSync(paths.natsLeafnodeCredentialsPath) || fs.existsSync(paths.hostLinkTokenPath) || fs.existsSync(paths.hostCredentialsPath) || isHostConfigLinked(paths.hostConfigPath); fs.rmSync(paths.linkPath, { force: true }); updateHiveCastDefaultRuntimePolicies(paths.matrixHome, localDefaultRuntimeScope(paths.matrixHome)); const hostConfigReset = resetHostConfigToLocalOwner(paths.hostConfigPath); if (options.preserveCredentials !== true) { removeHiveCastCredentialFiles(paths); } return { removed: existed, hostConfigReset, paths }; } export function readHiveCastLinkStatus(cwd: string, matrixHome?: string): IHiveCastLinkStatus { const { record, paths } = readHiveCastHostLink(cwd, matrixHome); const hostConfigLinked = Boolean(record) && fs.existsSync(paths.hostConfigPath); return { linked: Boolean(record), matrixHome: paths.matrixHome, linkPath: paths.linkPath, natsCredentialsPath: paths.natsCredentialsPath, hostConfigPath: paths.hostConfigPath, natsCredentialsPresent: fs.existsSync(paths.natsCredentialsPath), hostConfigPresent: fs.existsSync(paths.hostConfigPath), hostConfigLinked, ...(record?.hostLinkId ? { hostLinkId: record.hostLinkId } : {}), ...(record?.authorityRoot ? { authorityRoot: record.authorityRoot } : {}), ...(record?.routeKey ? { routeKey: record.routeKey } : {}), ...(record?.publicNamespace ? { publicNamespace: record.publicNamespace } : {}), ...(record?.spaceId ? { spaceId: record.spaceId } : {}), ...(typeof record?.authorityCoordinator === 'boolean' ? { authorityCoordinator: record.authorityCoordinator } : {}), ...(record?.cloudUrl ? { cloudUrl: record.cloudUrl } : {}), ...(record?.linkedAt ? { linkedAt: record.linkedAt } : {}), }; } function isHostConfigLinked(hostConfigPath: string): boolean { const config = readJsonFile(hostConfigPath); const transport = asRecord(config?.transport); if (!transport) return false; const nats = asRecord(transport.nats); const root = optionalString(transport.root); const authorityRoot = optionalString(transport.authorityRoot); const matrixHome = optionalString(config?.home) ?? path.dirname(hostConfigPath); const localOwnerTransport = readLocalOwnerTransportSnapshotForHome(matrixHome, config); const localOwnerRoot = localOwnerTransport?.root; const hasCloudAuthority = Boolean( authorityRoot && authorityRoot !== root, ) && (localOwnerRoot ? authorityRoot !== localOwnerRoot : true); const externalNatsIsHiveCast = nats?.mode === 'external' && ( Boolean(optionalString(nats.credentialsRef)) || (!loopbackUrlPort(optionalString(nats.url)) && !loopbackUrlPort(optionalString(nats.wsUrl))) ); return Boolean( optionalString(transport.routeKey) || optionalString(transport.publicNamespace) || optionalString(transport.spaceId) || hasCloudAuthority || externalNatsIsHiveCast, ); } function resetHostConfigToLocalOwner(hostConfigPath: string): boolean { const config = readJsonFile(hostConfigPath); if (!config) return false; const transport = asRecord(config.transport); if (!transport) return false; const currentNats = asRecord(transport.nats); const matrixHome = optionalString(config.home) ?? path.dirname(hostConfigPath); const localOwnerTransport = readLocalOwnerTransportSnapshotForHome(matrixHome, config) ?? readLocalOwnerTransportSnapshotFromRuntimeRecords(matrixHome) ?? readLocalOwnerTransportSnapshotFromStatus(matrixHome); const nextTransport: JsonRecord = { ...transport }; const localRoot = localOwnerAuthorityRoot(matrixHome); nextTransport.root = localOwnerTransport?.root ?? localRoot; if (localOwnerTransport?.authorityRoot) { nextTransport.authorityRoot = localOwnerTransport.authorityRoot; } else { delete nextTransport.authorityRoot; } nextTransport.addressRoot = localOwnerTransport?.root ?? localRoot; delete nextTransport.routeKey; delete nextTransport.publicNamespace; delete nextTransport.spaceId; nextTransport.nats = localOwnerTransport?.nats ?? (currentNats && currentNats.mode !== 'external' ? currentNats : defaultLocalOwnerNatsTransport()); const nextConfig: JsonRecord = { ...config, transport: nextTransport, }; delete nextConfig.natsTopology; writePrivateJson(hostConfigPath, nextConfig); return true; }