#!/usr/bin/env bun import { $ } from "bun" // Configuration const CONFIG = { checkInterval: 10_000, // 10 seconds ap: { ip: "192.168.4.1", dhcpRange: "192.168.4.2,192.168.4.20", channel: 7, }, } // Get AP SSID from hostname const hostname = (await $`hostname`.text()).trim() const AP_SSID = `${hostname}-setup` const AP_CONNECTION_NAME = `${AP_SSID}-ap` console.log("Starting WiFi AP Monitor...") console.log(`AP SSID will be: ${AP_SSID}`) console.log(`AP connection name: ${AP_CONNECTION_NAME}`) console.log(`Checking connectivity every ${CONFIG.checkInterval / 1000} seconds\n`) let apRunning = false async function isConnectedToWiFi(): Promise { try { // Check if wlan0 is connected to a WiFi network AS A CLIENT (not in AP mode) // We need to check if there's an active connection that is NOT our AP const activeConnections = await $`nmcli -t -f NAME,TYPE connection show --active`.quiet().text() const lines = activeConnections.trim().split("\n") for (const line of lines) { const [name, type] = line.split(":") // Check if there's a wifi connection that's NOT our AP if (type === "802-11-wireless" && name !== AP_CONNECTION_NAME) { return true } } return false } catch (error) { console.error("[isConnectedToWiFi] ERROR: Failed to check WiFi status") console.error(error) return false } } async function startAP() { if (apRunning) { console.log("[startAP] AP already running, skipping") return } console.log("🔴 Not connected to WiFi - Starting WiFi AP...") console.log(`[startAP] AP SSID: ${AP_SSID}`) console.log(`[startAP] AP Connection: ${AP_CONNECTION_NAME}`) try { // Check if connection profile already exists console.log("[startAP] Checking if connection profile exists...") const existsResult = await $`sudo nmcli connection show ${AP_CONNECTION_NAME}`.nothrow().quiet() if (existsResult.exitCode !== 0) { console.log("[startAP] Connection profile does not exist, creating...") // Create AP connection profile (open network, no password) await $`sudo nmcli connection add type wifi ifname wlan0 con-name ${AP_CONNECTION_NAME} autoconnect no ssid ${AP_SSID} 802-11-wireless.mode ap 802-11-wireless.band bg 802-11-wireless.channel ${CONFIG.ap.channel} ipv4.method shared ipv4.addresses ${CONFIG.ap.ip}/24` console.log("[startAP] ✓ Connection profile created (open network)") } else { console.log("[startAP] Connection profile already exists, reusing") } // Bring up the AP console.log("[startAP] Bringing up AP connection...") await $`sudo nmcli connection up ${AP_CONNECTION_NAME}` console.log("[startAP] ✓ AP connection activated") apRunning = true console.log(`✓ AP started successfully!`) console.log(` SSID: ${AP_SSID}`) console.log(` Password: None (open network)`) console.log(` Connect and visit: http://${CONFIG.ap.ip}\n`) } catch (error) { console.error("[startAP] ERROR: Failed to start AP") console.error(error) apRunning = false } } async function stopAP() { if (!apRunning) { console.log("[stopAP] AP not running, skipping") return } console.log("🟢 Connected to WiFi - Stopping AP...") console.log(`[stopAP] Bringing down connection: ${AP_CONNECTION_NAME}`) try { // Bring down the AP connection await $`sudo nmcli connection down ${AP_CONNECTION_NAME}`.nothrow() console.log("[stopAP] ✓ AP connection deactivated") apRunning = false console.log("✓ AP stopped successfully\n") } catch (error) { console.error("[stopAP] ERROR: Failed to stop AP") console.error(error) // Set to false anyway to allow retry apRunning = false } } async function checkAndManageAP() { const connected = await isConnectedToWiFi() if (connected && apRunning) { console.log("[checkAndManageAP] WiFi connected and AP running → stopping AP") await stopAP() } else if (!connected && !apRunning) { console.log("[checkAndManageAP] WiFi disconnected and AP stopped → starting AP") await startAP() } else if (!connected && apRunning) { // AP is running but no WiFi client connection // Check if our saved WiFi network is available console.log("[checkAndManageAP] AP running, checking if saved WiFi is available...") const savedNetwork = await findAvailableSavedNetwork() if (savedNetwork) { console.log( `[checkAndManageAP] Found available saved network: ${savedNetwork}, attempting connection...`, ) // Try to connect first const connected = await tryConnect(savedNetwork) if (connected) { console.log(`[checkAndManageAP] Successfully connected to ${savedNetwork}, stopping AP...`) await stopAP() } else { console.log(`[checkAndManageAP] Failed to connect to ${savedNetwork}, keeping AP running`) // Delete the failed connection profile try { await $`sudo nmcli connection delete ${savedNetwork}`.nothrow() console.log(`[checkAndManageAP] Deleted failed connection profile for ${savedNetwork}`) } catch (error) { console.error(`[checkAndManageAP] Failed to delete connection profile:`, error) } } } } } async function findAvailableSavedNetwork(): Promise { try { // Get all saved WiFi connections (exclude our AP) const savedConnections = await $`nmcli -t -f NAME,TYPE connection show`.quiet().text() const lines = savedConnections.trim().split("\n") const savedWiFiNames: string[] = [] for (const line of lines) { const [name, type] = line.split(":") if (type === "802-11-wireless" && name !== AP_CONNECTION_NAME) { savedWiFiNames.push(name!) } } if (savedWiFiNames.length === 0) { return null } // Get the actual SSIDs for logging const savedSSIDs: string[] = [] for (const savedName of savedWiFiNames) { const connInfo = await $`nmcli -t -f 802-11-wireless.ssid connection show ${savedName}` .quiet() .nothrow() .text() const ssid = connInfo.split(":")[1]?.trim() if (ssid) { savedSSIDs.push(ssid) } } console.log(`[findAvailableSavedNetwork] Saved WiFi networks: ${savedSSIDs.join(", ")}`) // Scan for available networks const scanResult = await $`nmcli -t -f SSID device wifi list`.quiet().nothrow().text() const availableSSIDs = scanResult.trim().split("\n") // Check if any saved network is available for (const savedName of savedWiFiNames) { // NetworkManager connection names often match SSIDs, but let's also check the connection's SSID const connInfo = await $`nmcli -t -f 802-11-wireless.ssid connection show ${savedName}` .quiet() .nothrow() .text() const ssid = connInfo.split(":")[1]?.trim() if (ssid && availableSSIDs.includes(ssid)) { console.log(`[findAvailableSavedNetwork] Found available network: ${ssid}`) return savedName // Return the connection name } } return null } catch (error) { console.error("[findAvailableSavedNetwork] ERROR checking for WiFi") console.error(error) return null } } async function tryConnect(connectionName: string): Promise { try { console.log(`[tryConnect] Attempting to connect to ${connectionName}...`) await $`sudo nmcli connection up ${connectionName}` console.log(`[tryConnect] Successfully connected to ${connectionName}`) return true } catch (error) { console.error(`[tryConnect] Failed to connect to ${connectionName}:`, error) return false } } // Initial check const connected = await isConnectedToWiFi() console.log( `[checkAndManageAP] WiFi: ${connected ? "connected" : "disconnected"}, AP: ${ apRunning ? "running" : "stopped" }`, ) await checkAndManageAP() // Check periodically setInterval(checkAndManageAP, CONFIG.checkInterval) console.log("Monitor running...")