Improve wifi setup reliability and error handling
This commit is contained in:
parent
7073cab8b5
commit
6b6d29ef38
|
|
@ -63,14 +63,16 @@ BUNDLED_APPS="clock code cron env stats versions"
|
|||
for app in $BUNDLED_APPS; do
|
||||
if [ -d "apps/$app" ]; then
|
||||
echo " Installing $app..."
|
||||
# Copy app to ~/apps
|
||||
cp -r "apps/$app" ~/apps/
|
||||
# Find the version directory and create current symlink
|
||||
version_dir=$(ls -1 ~/apps/$app | grep -E '^[0-9]{8}-[0-9]{6}$' | sort -r | head -1)
|
||||
if [ -n "$version_dir" ]; then
|
||||
ln -sfn "$version_dir" ~/apps/$app/current
|
||||
# Install dependencies
|
||||
(cd ~/apps/$app/current && bun install --frozen-lockfile) > /dev/null 2>&1
|
||||
if ! (cd ~/apps/$app/current && bun install --frozen-lockfile) > /dev/null 2>&1; then
|
||||
echo " WARNING: bun install failed for $app, trying without lockfile..."
|
||||
(cd ~/apps/$app/current && bun install) > /dev/null 2>&1 || echo " ERROR: bun install failed for $app"
|
||||
fi
|
||||
else
|
||||
echo " WARNING: no version directory found for $app, skipping"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
|
@ -119,7 +121,6 @@ EOF
|
|||
fi
|
||||
|
||||
echo ">> Done! Rebooting in 5 seconds..."
|
||||
quiet systemctl status "$SERVICE_NAME" --no-pager -l || true
|
||||
systemctl status "$SERVICE_NAME" --no-pager -l || true
|
||||
sleep 5
|
||||
quiet sudo nohup reboot >/dev/null 2>&1 &
|
||||
exit 0
|
||||
sudo reboot
|
||||
|
|
|
|||
|
|
@ -13,7 +13,3 @@ no-resolv
|
|||
|
||||
# Don't read /etc/hosts
|
||||
no-hosts
|
||||
|
||||
# Log queries for debugging
|
||||
log-queries
|
||||
log-facility=/tmp/toes-dnsmasq.log
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ import {
|
|||
import { theme } from '../themes'
|
||||
import type { WifiNetwork } from '../../shared/types'
|
||||
|
||||
type WifiStep = 'status' | 'scanning' | 'networks' | 'password' | 'connecting' | 'success' | 'error'
|
||||
type WifiStep = 'status' | 'scanning' | 'networks' | 'password' | 'connecting' | 'success'
|
||||
|
||||
function signalBars(signal: number) {
|
||||
const level = signal > 75 ? 4 : signal > 50 ? 3 : signal > 25 ? 2 : 1
|
||||
|
|
@ -209,7 +209,6 @@ export function SettingsPage({ render }: { render: () => void }) {
|
|||
<SpinnerWrap>
|
||||
<Spinner />
|
||||
<p style={{ color: theme('colors-textMuted') }}>Scanning for networks...</p>
|
||||
<style>{`@keyframes spin { to { transform: rotate(360deg); } }`}</style>
|
||||
</SpinnerWrap>
|
||||
)}
|
||||
|
||||
|
|
@ -257,7 +256,6 @@ export function SettingsPage({ render }: { render: () => void }) {
|
|||
<SpinnerWrap>
|
||||
<Spinner />
|
||||
<p style={{ color: theme('colors-textMuted') }}>Connecting to <strong>{selectedNetwork?.ssid}</strong>...</p>
|
||||
<style>{`@keyframes spin { to { transform: rotate(360deg); } }`}</style>
|
||||
</SpinnerWrap>
|
||||
)}
|
||||
|
||||
|
|
|
|||
|
|
@ -51,21 +51,9 @@ getWifiStatus().then(status => {
|
|||
}
|
||||
}).catch(() => {})
|
||||
|
||||
// SSE for WiFi setup mode changes
|
||||
const wifiEvents = new EventSource('/api/wifi/stream')
|
||||
wifiEvents.onmessage = e => {
|
||||
const data = JSON.parse(e.data)
|
||||
setSetupMode(data.setupMode)
|
||||
if (data.setupMode) {
|
||||
setCurrentView('settings')
|
||||
}
|
||||
render()
|
||||
}
|
||||
|
||||
// SSE connection for app state
|
||||
const events = new EventSource('/api/apps/stream')
|
||||
events.onmessage = e => {
|
||||
const prev = apps
|
||||
setApps(JSON.parse(e.data))
|
||||
|
||||
if (selectedApp && !apps.some(a => a.name === selectedApp)) {
|
||||
|
|
|
|||
|
|
@ -95,3 +95,10 @@ export const WifiColumn = define('WifiColumn', {
|
|||
gap: 16,
|
||||
maxWidth: 400,
|
||||
})
|
||||
|
||||
// Inject spin keyframes once
|
||||
if (typeof document !== 'undefined') {
|
||||
const style = document.createElement('style')
|
||||
style.textContent = '@keyframes spin { to { transform: rotate(360deg); } }'
|
||||
document.head.appendChild(style)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { TOES_URL } from '$apps'
|
||||
import { Hype } from '@because/hype'
|
||||
import { connectToWifi, getWifiStatus, isSetupMode, onSetupModeChange, scanNetworks } from '../wifi'
|
||||
import { connectToWifi, getWifiStatus, isSetupMode, scanNetworks } from '../wifi'
|
||||
|
||||
const router = Hype.router()
|
||||
|
||||
|
|
@ -28,11 +28,4 @@ router.post('/connect', async c => {
|
|||
return c.json(result)
|
||||
})
|
||||
|
||||
// SSE stream for setup mode changes
|
||||
router.sse('/stream', (send, c) => {
|
||||
send({ setupMode: isSetupMode() })
|
||||
const unsub = onSetupModeChange(setupMode => send({ setupMode }))
|
||||
return () => unsub()
|
||||
})
|
||||
|
||||
export default router
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@ async function dnsStart(): Promise<void> {
|
|||
async function dnsStop(): Promise<void> {
|
||||
if (await Bun.file(DNSMASQ_PID).exists()) {
|
||||
const pid = (await Bun.file(DNSMASQ_PID).text()).trim()
|
||||
await sudo(['kill', pid]).catch(() => {})
|
||||
await sudo(['rm', '-f', DNSMASQ_PID]).catch(() => {})
|
||||
await sudo(['kill', pid]).catch(e => hostLog(`dnsStop: failed to kill pid ${pid}: ${e}`))
|
||||
await sudo(['rm', '-f', DNSMASQ_PID]).catch(e => hostLog(`dnsStop: failed to remove pid file: ${e}`))
|
||||
}
|
||||
await sudo(['pkill', '-f', 'dnsmasq.*wifi-captive.conf']).catch(() => {})
|
||||
}
|
||||
|
|
@ -64,7 +64,11 @@ export async function connectToNetwork(ssid: string, password?: string): Promise
|
|||
|
||||
if (result.exitCode !== 0) {
|
||||
// Connection failed — restart hotspot so user can retry
|
||||
await startHotspot()
|
||||
try {
|
||||
await startHotspot()
|
||||
} catch (e) {
|
||||
hostLog(`CRITICAL: failed to restart hotspot after connection failure: ${e instanceof Error ? e.message : String(e)}`)
|
||||
}
|
||||
const error = result.stderr || result.stdout || 'Connection failed'
|
||||
return { ok: false, error }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,26 +5,20 @@ import type { ConnectResult, WifiNetwork, WifiStatus } from '@types'
|
|||
export type { ConnectResult, WifiNetwork, WifiStatus } from '@types'
|
||||
|
||||
let _setupMode = false
|
||||
const _listeners = new Set<(setupMode: boolean) => void>()
|
||||
|
||||
export const isSetupMode = () => _setupMode
|
||||
|
||||
export const onSetupModeChange = (cb: (setupMode: boolean) => void) => {
|
||||
_listeners.add(cb)
|
||||
return () => _listeners.delete(cb)
|
||||
}
|
||||
|
||||
function setSetupMode(mode: boolean) {
|
||||
if (_setupMode === mode) return
|
||||
_setupMode = mode
|
||||
hostLog(mode ? 'Entering WiFi setup mode' : 'Exiting WiFi setup mode')
|
||||
for (const cb of _listeners) cb(mode)
|
||||
}
|
||||
|
||||
export async function getWifiStatus(): Promise<WifiStatus> {
|
||||
try {
|
||||
return await nmcli.status()
|
||||
} catch {
|
||||
} catch (e) {
|
||||
hostLog(`WiFi status check failed: ${e instanceof Error ? e.message : String(e)}`)
|
||||
return { connected: false, ssid: '', ip: '' }
|
||||
}
|
||||
}
|
||||
|
|
@ -32,7 +26,8 @@ export async function getWifiStatus(): Promise<WifiStatus> {
|
|||
export async function scanNetworks(): Promise<WifiNetwork[]> {
|
||||
try {
|
||||
return await nmcli.scanNetworks()
|
||||
} catch {
|
||||
} catch (e) {
|
||||
hostLog(`WiFi scan failed: ${e instanceof Error ? e.message : String(e)}`)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user