package agent import ( "context" "fmt" "log/slog" "net" "os" "path/filepath" ) // SocketPath is the unix socket on which flock-agent serves RPCs from the // CNI plugin. Mirrors pkg/cni.SocketPath; kept as a separate constant so the // agent package has no import-cycle on the CNI package. const SocketPath = "/run/flock/flock.sock" // Server is the agent's runtime container: state store, kubernetes informers, // netlink, BIRD, nftables. M1 wires only the state store and a placeholder // listener so the binary boots and exits cleanly under a context. type Server struct { Node string Store *Store Logger *slog.Logger socket string closeCh chan struct{} } // Config configures NewServer. type Config struct { Node string StatePath string // typically /var/lib/flock/allocations.json Socket string // typically /run/flock/flock.sock Logger *slog.Logger } // NewServer constructs a Server. It does NOT start any goroutines; call Run. func NewServer(cfg Config) (*Server, error) { if cfg.Node == "" { return nil, fmt.Errorf("Node must be set") } if cfg.StatePath == "" { cfg.StatePath = "/var/lib/flock/allocations.json" } if cfg.Socket == "" { cfg.Socket = SocketPath } if cfg.Logger == nil { cfg.Logger = slog.Default() } if err := os.MkdirAll(filepath.Dir(cfg.StatePath), 0o750); err != nil { return nil, fmt.Errorf("mkdir state dir: %w", err) } store, err := NewStore(cfg.StatePath, cfg.Node) if err != nil { return nil, fmt.Errorf("open store: %w", err) } return &Server{ Node: cfg.Node, Store: store, Logger: cfg.Logger, socket: cfg.Socket, closeCh: make(chan struct{}), }, nil } // Run starts the agent and blocks until ctx is cancelled. M1 only opens the // unix listener (proving permissions/path); the RPC handler is a no-op // returning ENOSYS until M2. func (s *Server) Run(ctx context.Context) error { if err := os.MkdirAll(filepath.Dir(s.socket), 0o750); err != nil { return fmt.Errorf("mkdir socket dir: %w", err) } _ = os.Remove(s.socket) l, err := net.Listen("unix", s.socket) if err != nil { return fmt.Errorf("listen %s: %w", s.socket, err) } defer l.Close() s.Logger.Info("flock-agent started", "node", s.Node, "socket", s.socket, "allocations", len(s.Store.Snapshot()), ) // Accept loop: M1 closes every accepted conn immediately. M2 will dispatch. go func() { for { conn, err := l.Accept() if err != nil { return // listener closed } _ = conn.Close() } }() <-ctx.Done() s.Logger.Info("flock-agent stopping") return nil }