Add url to wifi status and use dynamic hostname for mDNS

This commit is contained in:
Chris Wanstrath 2026-02-24 19:59:25 -08:00
parent 3d40de5582
commit 7073cab8b5
5 changed files with 15 additions and 9 deletions

View File

@ -19,6 +19,7 @@ echo ">> Updating system libraries"
quiet sudo apt-get update quiet sudo apt-get update
quiet sudo apt-get install -y libcap2-bin quiet sudo apt-get install -y libcap2-bin
quiet sudo apt-get install -y avahi-utils quiet sudo apt-get install -y avahi-utils
quiet sudo apt-get install -y dnsmasq
quiet sudo apt-get install -y fish quiet sudo apt-get install -y fish
echo ">> Setting fish as default shell for toes user" echo ">> Setting fish as default shell for toes user"

View File

@ -13,7 +13,7 @@ export const getLogDates = (name: string): Promise<string[]> =>
export const getLogsForDate = (name: string, date: string): Promise<string[]> => export const getLogsForDate = (name: string, date: string): Promise<string[]> =>
fetch(`/api/apps/${name}/logs?date=${date}`).then(r => r.json()) fetch(`/api/apps/${name}/logs?date=${date}`).then(r => r.json())
export const getWifiStatus = (): Promise<WifiStatus & { setupMode: boolean }> => export const getWifiStatus = (): Promise<WifiStatus & { setupMode: boolean, url: string }> =>
fetch('/api/wifi/status').then(r => r.json()) fetch('/api/wifi/status').then(r => r.json())
export const restartApp = (name: string) => fetch(`/api/apps/${name}/restart`, { method: 'POST' }) export const restartApp = (name: string) => fetch(`/api/apps/${name}/restart`, { method: 'POST' })

View File

@ -85,12 +85,14 @@ export function SettingsPage({ render }: { render: () => void }) {
const [error, setError] = useState('') const [error, setError] = useState('')
const [successSsid, setSuccessSsid] = useState('') const [successSsid, setSuccessSsid] = useState('')
const [successIp, setSuccessIp] = useState('') const [successIp, setSuccessIp] = useState('')
const [serverUrl, setServerUrl] = useState('')
const fetchStatus = () => { const fetchStatus = () => {
getWifiStatus().then(status => { getWifiStatus().then(status => {
setConnected(status.connected) setConnected(status.connected)
setCurrentSsid(status.ssid) setCurrentSsid(status.ssid)
setCurrentIp(status.ip) setCurrentIp(status.ip)
if (status.url) setServerUrl(status.url)
}).catch(() => {}) }).catch(() => {})
} }
@ -278,10 +280,10 @@ export function SettingsPage({ render }: { render: () => void }) {
}}> }}>
<p style={{ marginBottom: 8 }}>Reconnect your device to <strong>{successSsid}</strong> and visit:</p> <p style={{ marginBottom: 8 }}>Reconnect your device to <strong>{successSsid}</strong> and visit:</p>
<a <a
href="http://toes.local" href={serverUrl}
style={{ color: theme('colors-statusRunning'), fontSize: 18, fontWeight: 600, textDecoration: 'none' }} style={{ color: theme('colors-statusRunning'), fontSize: 18, fontWeight: 600, textDecoration: 'none' }}
> >
http://toes.local {serverUrl}
</a> </a>
</div> </div>
) : ( ) : (

View File

@ -1,3 +1,4 @@
import { TOES_URL } from '$apps'
import { Hype } from '@because/hype' import { Hype } from '@because/hype'
import { connectToWifi, getWifiStatus, isSetupMode, onSetupModeChange, scanNetworks } from '../wifi' import { connectToWifi, getWifiStatus, isSetupMode, onSetupModeChange, scanNetworks } from '../wifi'
@ -6,7 +7,7 @@ const router = Hype.router()
// GET /api/wifi/status - current WiFi state + setup mode flag // GET /api/wifi/status - current WiFi state + setup mode flag
router.get('/status', async c => { router.get('/status', async c => {
const status = await getWifiStatus() const status = await getWifiStatus()
return c.json({ ...status, setupMode: isSetupMode() }) return c.json({ ...status, setupMode: isSetupMode(), url: TOES_URL })
}) })
// GET /api/wifi/scan - list available networks // GET /api/wifi/scan - list available networks

View File

@ -1,10 +1,11 @@
import type { Subprocess } from 'bun' import type { Subprocess } from 'bun'
import { toSubdomain } from '@urls' import { toSubdomain } from '@urls'
import { networkInterfaces } from 'os' import { hostname, networkInterfaces } from 'os'
import { hostLog } from './tui' import { hostLog } from './tui'
const _publishers = new Map<string, Subprocess>() const _publishers = new Map<string, Subprocess>()
const HOST_DOMAIN = `${hostname()}.local`
const isEnabled = process.env.NODE_ENV === 'production' && process.platform === 'linux' const isEnabled = process.env.NODE_ENV === 'production' && process.platform === 'linux'
function getLocalIp(): string | null { function getLocalIp(): string | null {
@ -24,7 +25,8 @@ export function cleanupStalePublishers() {
if (!isEnabled) return if (!isEnabled) return
try { try {
const result = Bun.spawnSync(['pkill', '-f', 'avahi-publish.*toes\\.local']) const pattern = HOST_DOMAIN.replace(/\./g, '\\.')
const result = Bun.spawnSync(['pkill', '-f', `avahi-publish.*${pattern}`])
if (result.exitCode === 0) { if (result.exitCode === 0) {
hostLog('mDNS: cleaned up stale avahi-publish processes') hostLog('mDNS: cleaned up stale avahi-publish processes')
} }
@ -41,7 +43,7 @@ export function publishApp(name: string) {
return return
} }
const hostname = `${toSubdomain(name)}.toes.local` const hostname = `${toSubdomain(name)}.${HOST_DOMAIN}`
try { try {
const proc = Bun.spawn(['avahi-publish', '-a', hostname, '-R', ip], { const proc = Bun.spawn(['avahi-publish', '-a', hostname, '-R', ip], {
@ -68,7 +70,7 @@ export function unpublishApp(name: string) {
proc.kill() proc.kill()
_publishers.delete(name) _publishers.delete(name)
hostLog(`mDNS: unpublished ${toSubdomain(name)}.toes.local`) hostLog(`mDNS: unpublished ${toSubdomain(name)}.${HOST_DOMAIN}`)
} }
export function unpublishAll() { export function unpublishAll() {
@ -76,7 +78,7 @@ export function unpublishAll() {
for (const [name, proc] of _publishers) { for (const [name, proc] of _publishers) {
proc.kill() proc.kill()
hostLog(`mDNS: unpublished ${toSubdomain(name)}.toes.local`) hostLog(`mDNS: unpublished ${toSubdomain(name)}.${HOST_DOMAIN}`)
} }
_publishers.clear() _publishers.clear()
} }