package headscale import ( "context" "fmt" "time" "go.uber.org/zap" "github.com/guildhouse-co/kedge/internal/config" ) // PeerUpdater is implemented by the mesh manager to receive peer updates. type PeerUpdater interface { AddPeer(pubKey string, endpoint string, allowedIPs []string) error } // Client connects to Headscale for mesh peer discovery. type Client struct { cfg config.HeadscaleConfig peers PeerUpdater log *zap.SugaredLogger } // NewClient creates a new Headscale peer discovery client. func NewClient(cfg config.HeadscaleConfig, peers PeerUpdater, log *zap.SugaredLogger) *Client { return &Client{cfg: cfg, peers: peers, log: log} } // Run starts the Headscale peer discovery loop. func (c *Client) Run(ctx context.Context) error { if !c.cfg.Enabled { c.log.Info("headscale peer discovery disabled") <-ctx.Done() return nil } c.log.Infof("starting headscale peer discovery (endpoint: %s, interval: %s)", c.cfg.Endpoint, c.cfg.PollInterval) ticker := time.NewTicker(c.cfg.PollInterval) defer ticker.Stop() // Initial discovery. if err := c.discoverPeers(ctx); err != nil { c.log.Warnw("initial peer discovery failed", "error", err) } for { select { case <-ctx.Done(): return nil case <-ticker.C: if err := c.discoverPeers(ctx); err != nil { c.log.Warnw("peer discovery failed", "error", err) } } } } func (c *Client) discoverPeers(ctx context.Context) error { // TODO: Call Headscale gRPC API to list nodes in the mesh namespace. // For each node, extract WireGuard public key, endpoint, and allowed IPs, // then call c.peers.AddPeer() to update the mesh manager. _ = ctx return fmt.Errorf("headscale peer discovery not yet implemented") }