package config import ( "fmt" "os" "time" ) // Config is the top-level configuration for the Kedge DaemonSet. type Config struct { NodeID string `json:"node_id"` ClusterID string `json:"cluster_id"` Mesh MeshConfig `json:"mesh"` VLAN VLANConfig `json:"vlan"` Shellstream ShellstreamConfig `json:"shellstream"` Quartermaster QuartermasterConfig `json:"quartermaster"` Underlay UnderlayConfig `json:"underlay"` Health HealthConfig `json:"health"` Headscale HeadscaleConfig `json:"headscale"` } // MeshConfig configures the WireGuard overlay mesh. type MeshConfig struct { Enabled bool `json:"enabled"` InterfaceName string `json:"interface_name"` ListenPort int `json:"listen_port"` PrivateKeyPath string `json:"private_key_path"` HealthInterval time.Duration `json:"health_interval"` DeadPeerTimeout time.Duration `json:"dead_peer_timeout"` InitialPeers []PeerEntry `json:"initial_peers"` } // PeerEntry defines a WireGuard peer from configuration. type PeerEntry struct { PublicKey string `json:"public_key"` Endpoint string `json:"endpoint"` AllowedIPs []string `json:"allowed_ips"` } // VLANConfig configures underlay VLAN interfaces. type VLANConfig struct { Enabled bool `json:"enabled"` VLANs []VLANEntry `json:"vlans"` } // VLANEntry defines a VLAN to be managed on the node. type VLANEntry struct { ID int `json:"id"` ParentInterface string `json:"parent_interface"` Subnet string `json:"subnet"` Description string `json:"description"` } // ShellstreamConfig configures the Shellstream handshake listener. type ShellstreamConfig struct { ListenAddr string `json:"listen_addr"` ClusterID string `json:"cluster_id"` TrustBundlePath string `json:"trust_bundle_path"` AccordPolicyPath string `json:"accord_policy_path"` } // QuartermasterConfig configures the Quartermaster gRPC client. type QuartermasterConfig struct { Endpoint string `json:"endpoint"` UseTLS bool `json:"use_tls"` CertPath string `json:"cert_path,omitempty"` KeyPath string `json:"key_path,omitempty"` CAPath string `json:"ca_path,omitempty"` } // UnderlayConfig configures the YANG watcher and compilation trigger. type UnderlayConfig struct { Enabled bool `json:"enabled"` Namespace string `json:"namespace"` ConfigMapName string `json:"configmap_name"` CompilerPath string `json:"compiler_path"` } // HealthConfig configures the health and metrics server. type HealthConfig struct { ListenAddr string `json:"listen_addr"` } // HeadscaleConfig configures Headscale peer discovery. type HeadscaleConfig struct { Enabled bool `json:"enabled"` Endpoint string `json:"endpoint"` APIKey string `json:"api_key"` PollInterval time.Duration `json:"poll_interval"` } // Load reads the DaemonSet configuration from environment variables and // the config file at /etc/kedge/config.json. func Load() (*Config, error) { cfg := &Config{ NodeID: getEnv("KEDGE_NODE_ID", ""), ClusterID: getEnv("KEDGE_CLUSTER_ID", ""), Mesh: MeshConfig{ Enabled: getEnv("KEDGE_OVERLAY_ENABLED", "true") == "true", InterfaceName: getEnv("KEDGE_WG_INTERFACE", "wg0"), ListenPort: 51820, PrivateKeyPath: getEnv("KEDGE_WG_PRIVATE_KEY", "/etc/kedge/wg-private.key"), HealthInterval: 30 * time.Second, DeadPeerTimeout: 5 * time.Minute, }, VLAN: VLANConfig{ Enabled: getEnv("KEDGE_UNDERLAY_ENABLED", "false") == "true", }, Shellstream: ShellstreamConfig{ ListenAddr: getEnv("KEDGE_SHELLSTREAM_ADDR", ":8443"), ClusterID: getEnv("KEDGE_CLUSTER_ID", ""), TrustBundlePath: getEnv("KEDGE_TRUST_BUNDLE", "/run/spire/bundle/bundle.pem"), AccordPolicyPath: getEnv("KEDGE_ACCORD_POLICY", "/etc/kedge/accord-policy.json"), }, Quartermaster: QuartermasterConfig{ Endpoint: getEnv("KEDGE_QM_ENDPOINT", "quartermaster.guildhouse.svc:50051"), }, Underlay: UnderlayConfig{ Enabled: getEnv("KEDGE_UNDERLAY_ENABLED", "false") == "true", Namespace: getEnv("KEDGE_UNDERLAY_NAMESPACE", "kedge"), ConfigMapName: getEnv("KEDGE_UNDERLAY_CONFIGMAP", "kedge-underlay"), CompilerPath: getEnv("KEDGE_YANG_COMPILER", "/opt/kedge/yang/compiler/compile.py"), }, Health: HealthConfig{ ListenAddr: getEnv("KEDGE_HEALTH_ADDR", ":9090"), }, Headscale: HeadscaleConfig{ Enabled: getEnv("KEDGE_HEADSCALE_ENABLED", "false") == "true", Endpoint: getEnv("KEDGE_HEADSCALE_ENDPOINT", ""), PollInterval: 60 * time.Second, }, } if cfg.NodeID == "" { hostname, err := os.Hostname() if err != nil { return nil, fmt.Errorf("KEDGE_NODE_ID not set and hostname unavailable: %w", err) } cfg.NodeID = hostname } if cfg.ClusterID == "" { return nil, fmt.Errorf("KEDGE_CLUSTER_ID is required") } return cfg, nil } func getEnv(key, fallback string) string { if v := os.Getenv(key); v != "" { return v } return fallback }