toes/src/server/mdns.ts

84 lines
1.9 KiB
TypeScript

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<string, Subprocess>()
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()
}