import type { Subprocess } from 'bun' import { toSubdomain } from '@urls' import { LOCAL_HOST } from '%config' import { networkInterfaces } from 'os' import { hostLog } from './tui' const _publishers = new Map() const isEnabled = process.env.NODE_ENV === 'production' && process.platform === 'linux' function getLocalIp(): string | null { const interfaces = networkInterfaces() for (const iface of Object.values(interfaces)) { if (!iface) continue for (const addr of iface) { if (addr.family === 'IPv4' && !addr.internal) { return addr.address } } } return null } export function cleanupStalePublishers() { if (!isEnabled) return try { const result = Bun.spawnSync(['pkill', '-f', `avahi-publish.*${LOCAL_HOST}`]) if (result.exitCode === 0) { hostLog('mDNS: cleaned up stale avahi-publish processes') } } catch {} } export function publishApp(name: string) { if (!isEnabled) return if (_publishers.has(name)) return const ip = getLocalIp() if (!ip) { hostLog(`mDNS: no local IP found, skipping ${name}`) return } const host = `${toSubdomain(name)}.${LOCAL_HOST}` try { const proc = Bun.spawn(['avahi-publish', '-a', host, '-R', ip], { stdout: 'ignore', stderr: 'ignore', }) _publishers.set(name, proc) hostLog(`mDNS: published ${host} -> ${ip}`) proc.exited.then(() => { _publishers.delete(name) }) } catch { hostLog(`mDNS: failed to publish ${host}`) } } export function unpublishApp(name: string) { if (!isEnabled) return const proc = _publishers.get(name) if (!proc) return proc.kill() _publishers.delete(name) hostLog(`mDNS: unpublished ${toSubdomain(name)}.${LOCAL_HOST}`) } export function unpublishAll() { if (!isEnabled) return for (const [name, proc] of _publishers) { proc.kill() hostLog(`mDNS: unpublished ${toSubdomain(name)}.${LOCAL_HOST}`) } _publishers.clear() }