100 lines
3.0 KiB
TypeScript
100 lines
3.0 KiB
TypeScript
#!/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(<IndexPage />)
|
|
})
|
|
|
|
// 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(<LogsPage logs={logs} />)
|
|
} 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(<ConnectingPage ssid={ssid} />)
|
|
|
|
// 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")
|