Harden API surface and station/wifi backends
Bind to localhost by default and stop echoing backend errors (which can embed credentials or low-level details) back over the API and log broadcast. Validate hotspot SSID/passphrase/channel before writing hostapd.conf and tighten its mode to 0600 since it stores the WPA PSK. Restrict WebSocket upgrades to same-origin so a LAN browser can't be turned into a proxy for the API. Guard shared state: status reads/writes go through StatusMutex (the periodic updater races with the toggle and status handlers otherwise), broadcastToWebSockets no longer mutates the client map under RLock, and station-event callbacks now run under SafeGo so a panic in app code can't take down the daemon. Stop channels in hostapd, dhcp, and iwd signal monitors are now closed under sync.Once to survive concurrent Stop calls. App.Shutdown is idempotent and waits for the periodic loops before closing backends, so signal-driven and deferred shutdowns no longer race. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
77370eff19
commit
07f8673f2f
14 changed files with 237 additions and 85 deletions
|
|
@ -28,6 +28,10 @@ type App struct {
|
|||
Assets embed.FS
|
||||
Config *config.Config
|
||||
SyslogTailer *syslog.SyslogTailer
|
||||
|
||||
stopCh chan struct{}
|
||||
stopOnce sync.Once
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
// New creates a new application instance
|
||||
|
|
@ -44,6 +48,7 @@ func New(assets embed.FS) *App {
|
|||
},
|
||||
StartTime: time.Now(),
|
||||
Assets: assets,
|
||||
stopCh: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -99,6 +104,7 @@ func (a *App) Initialize(cfg *config.Config) error {
|
|||
}
|
||||
|
||||
// Start periodic tasks
|
||||
a.wg.Add(2)
|
||||
go a.periodicStatusUpdate()
|
||||
go a.periodicDeviceUpdate()
|
||||
|
||||
|
|
@ -108,26 +114,36 @@ func (a *App) Initialize(cfg *config.Config) error {
|
|||
|
||||
// Run starts the HTTP server
|
||||
func (a *App) Run(addr string) error {
|
||||
router := api.SetupRouter(&a.Status, a.Config, a.Assets)
|
||||
router := api.SetupRouter(&a.Status, &a.StatusMutex, a.Config, a.Assets)
|
||||
|
||||
logging.AddLog("Système", "Serveur API démarré sur "+addr)
|
||||
return router.Run(addr)
|
||||
}
|
||||
|
||||
// Shutdown gracefully shuts down the application
|
||||
// Shutdown gracefully shuts down the application. Idempotent — main wires
|
||||
// it both to a signal handler and a defer, and the second call must be a
|
||||
// no-op (channel close panics otherwise; backend Close paths assume single
|
||||
// invocation).
|
||||
func (a *App) Shutdown() {
|
||||
// Stop syslog tailing if running
|
||||
if a.SyslogTailer != nil {
|
||||
a.SyslogTailer.Stop()
|
||||
}
|
||||
a.stopOnce.Do(func() {
|
||||
// Signal periodic loops to exit, then wait for them so we don't
|
||||
// race with backends being closed below.
|
||||
close(a.stopCh)
|
||||
a.wg.Wait()
|
||||
|
||||
// Stop station monitoring and close backend
|
||||
station.StopEventMonitoring()
|
||||
station.Close()
|
||||
// Stop syslog tailing if running
|
||||
if a.SyslogTailer != nil {
|
||||
a.SyslogTailer.Stop()
|
||||
}
|
||||
|
||||
wifi.StopEventMonitoring()
|
||||
wifi.Close()
|
||||
logging.AddLog("Système", "Application arrêtée")
|
||||
// Stop station monitoring and close backend
|
||||
station.StopEventMonitoring()
|
||||
station.Close()
|
||||
|
||||
wifi.StopEventMonitoring()
|
||||
wifi.Close()
|
||||
logging.AddLog("Système", "Application arrêtée")
|
||||
})
|
||||
}
|
||||
|
||||
// getSystemUptime reads system uptime from /proc/uptime
|
||||
|
|
@ -178,10 +194,17 @@ func getInterfaceBytes(interfaceName string) (rxBytes, txBytes int64) {
|
|||
|
||||
// periodicStatusUpdate updates WiFi connection status periodically
|
||||
func (a *App) periodicStatusUpdate() {
|
||||
defer a.wg.Done()
|
||||
ticker := time.NewTicker(5 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for range ticker.C {
|
||||
for {
|
||||
select {
|
||||
case <-a.stopCh:
|
||||
return
|
||||
case <-ticker.C:
|
||||
}
|
||||
|
||||
a.StatusMutex.Lock()
|
||||
a.Status.Connected = wifi.IsConnected()
|
||||
a.Status.ConnectionState = wifi.GetConnectionState()
|
||||
|
|
@ -204,10 +227,17 @@ func (a *App) periodicStatusUpdate() {
|
|||
|
||||
// periodicDeviceUpdate updates connected devices list periodically
|
||||
func (a *App) periodicDeviceUpdate() {
|
||||
defer a.wg.Done()
|
||||
ticker := time.NewTicker(10 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for range ticker.C {
|
||||
for {
|
||||
select {
|
||||
case <-a.stopCh:
|
||||
return
|
||||
case <-ticker.C:
|
||||
}
|
||||
|
||||
devices, err := station.GetStations()
|
||||
if err != nil {
|
||||
log.Printf("Error getting connected devices: %v", err)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue