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)
}
})
// Main WiFi configuration page
app.get("/", (c) => {
return c.html()
})
// Service logs
app.get("/logs", async (c) => {
const service = c.req.query("service") || "phone-ap"
const validServices = ["phone-ap", "phone-web", "phone"]
// Default to phone-ap if invalid service
const selectedService = validServices.includes(service) ? service : "phone-ap"
try {
const logs = await $`journalctl -u ${selectedService}.service -n 200 --no-pager --no-hostname`.text()
return c.html()
} catch (error) {
return c.html()
}
})
// 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
})
const port = process.env.PORT ? Number(process.env.PORT) : 80
export default { port, fetch: app.fetch }
console.log(`Server running on http://0.0.0.0:${port}`)
console.log("Access via WiFi or AP at http://phone.local")