Files
ihasvpn/internal/engine/hub.go
T
jcoffey 6c006e1d4d WireGuard server with an embedded admin console
Go backend that drives kernel WireGuard over netlink (wireguard-go as the
fallback), nftables NAT with MSS clamping, forwarding and buffer sysctls,
SQLite for peers, users, sessions, traffic history and the audit log.

React console: dashboard with live rates and usage history, peer management
with QR codes and .conf downloads, disconnect, session reset, key rotation,
expiry, client-supplied keys, settings, users with admin and viewer roles,
two-factor authentication with recovery codes, audit log.

Docker image on Alpine with compose files for bridged and host networking,
CI and GHCR publish workflows, performance notes.
2026-09-12 19:56:08 -07:00

61 lines
1.2 KiB
Go

package engine
import (
"encoding/json"
"sync"
)
// Event is one server-sent event.
type Event struct {
Name string
Data []byte
}
// Hub fans events out to every open dashboard. A subscriber that cannot keep
// up loses events rather than stalling the collector; the next status
// snapshot carries the full picture anyway.
type Hub struct {
mu sync.Mutex
subs map[chan Event]struct{}
}
// NewHub returns an empty hub.
func NewHub() *Hub { return &Hub{subs: map[chan Event]struct{}{}} }
// Subscribe returns a channel of events and a function to leave.
func (h *Hub) Subscribe() (<-chan Event, func()) {
ch := make(chan Event, 16)
h.mu.Lock()
h.subs[ch] = struct{}{}
h.mu.Unlock()
return ch, func() {
h.mu.Lock()
delete(h.subs, ch)
h.mu.Unlock()
}
}
// Publish encodes v as JSON and sends it to every subscriber.
func (h *Hub) Publish(name string, v any) {
data, err := json.Marshal(v)
if err != nil {
return
}
ev := Event{Name: name, Data: data}
h.mu.Lock()
defer h.mu.Unlock()
for ch := range h.subs {
select {
case ch <- ev:
default:
}
}
}
// Subscribers is how many dashboards are listening.
func (h *Hub) Subscribers() int {
h.mu.Lock()
defer h.mu.Unlock()
return len(h.subs)
}