#!/usr/bin/env bun
import { Hono } from "hono"
import { join } from "node:path"
import { $ } from "bun"
import { IndexPage } from "./components/IndexPage"
import { LogsPage } from "./components/LogsPage"
import { ConnectingPage } from "./components/ConnectingPage"
const app = new Hono()
// Ping endpoint for connectivity check
app.get("/ping", (c) => {
return c.json({ ok: true })
})
// Serve static CSS
app.get("/pico.css", async (c) => {
const cssPath = join(import.meta.dir, "./static/pico.min.css")
const file = Bun.file(cssPath)
return new Response(file)
})
// API endpoint to get available WiFi networks
app.get("/api/networks", async (c) => {
try {
const result = await $`nmcli -t -f SSID device wifi list`.text()
const networks = result
.trim()
.split("\n")
.filter((ssid) => ssid && ssid !== "SSID") // Remove empty and header
.filter((ssid, index, self) => self.indexOf(ssid) === index) // Remove duplicates
return c.json({ networks })
} catch (error) {
return c.json({ networks: [], error: String(error) }, 500)
}
})
// API endpoint to get logs (for auto-refresh)
app.get("/api/logs", async (c) => {
try {
const logs =
await $`journalctl -u phone-ap.service -u phone-web.service -n 200 --no-pager`.text()
return c.json({ logs: logs.trim() })
} catch (error) {
return c.json({ logs: "", error: String(error) }, 500)
}
})
// Main WiFi configuration page
app.get("/", (c) => {
return c.html()
})
// Service logs with auto-refresh
app.get("/logs", async (c) => {
try {
const logs =
await $`journalctl -u phone-ap.service -u phone-web.service -n 200 --no-pager`.text()
return c.html()
} catch (error) {
throw new Error(`Failed to fetch logs: ${error}`)
}
})
// Handle WiFi configuration submission
app.post("/save", async (c) => {
const formData = await c.req.parseBody()
const ssid = formData.ssid as string
const password = formData.password as string
// Return the connecting page immediately
const response = c.html()
// Trigger connection in background after a short delay (allows response to be sent)
setTimeout(async () => {
try {
await $`sudo nmcli device wifi connect ${ssid} password ${password}`
console.log(`[WiFi] Successfully connected to ${ssid}`)
} catch (error) {
console.error(`[WiFi] Failed to connect to ${ssid}:`, error)
// Delete the failed connection profile so ap-monitor doesn't try to use it
try {
await $`sudo nmcli connection delete ${ssid}`.nothrow()
console.log(`[WiFi] Deleted failed connection profile for ${ssid}`)
} catch (deleteError) {
console.error(`[WiFi] Failed to delete connection profile:`, deleteError)
}
}
}, 1000) // 1 second delay
return response
})
export default { port: 80, fetch: app.fetch }
console.log("Server running on http://0.0.0.0:80")
console.log("Access via WiFi or AP at http://phone.local")