{ config, lib, pkgs, ... }: with lib; let cfg = config.ultracloud.network; clusterCfg = config.ultracloud.cluster; hostName = config.networking.hostName; clusterNode = if clusterCfg.enable or false && clusterCfg.nodes ? "${hostName}" then clusterCfg.nodes.${hostName} else null; clusterNodeIp = if clusterNode != null then clusterNode.ip else "127.0.0.1"; # BGP peer type for FiberLB bgpPeerType = types.submodule { options = { address = mkOption { type = types.str; description = "Peer IP address (ToR switch, upstream router)"; example = "192.168.1.1"; }; asn = mkOption { type = types.int; description = "Peer AS number"; example = 65001; }; description = mkOption { type = types.str; default = ""; description = "Human-readable peer description"; example = "ToR switch rack1"; }; }; }; in { options.ultracloud.network = { fiberlbBgp = { enable = mkEnableOption "FiberLB BGP VIP advertisement"; vips = mkOption { type = types.listOf types.str; default = [ ]; description = "Legacy static VIP hints. FiberLB native BGP ignores this list and advertises active load balancer VIPs dynamically."; example = [ "203.0.113.10/32" "203.0.113.11/32" ]; }; peers = mkOption { type = types.listOf bgpPeerType; default = [ ]; description = "BGP peers (ToR switches, upstream routers)"; example = literalExpression '' [ { address = "192.168.1.1"; asn = 65001; description = "ToR switch rack1"; } { address = "192.168.1.2"; asn = 65001; description = "ToR switch rack2"; } ] ''; }; routerId = mkOption { type = types.nullOr types.str; default = null; description = "BGP router ID (auto-detected from primary IP if null)"; }; }; prismnetIntegration = { enable = mkEnableOption "PrismNET OVN integration"; encapType = mkOption { type = types.enum [ "geneve" "vxlan" ]; default = "geneve"; description = "Encapsulation type reserved for future OVN controller wiring."; }; encapIp = mkOption { type = types.nullOr types.str; default = null; description = "Explicit OVN encapsulation IP. Defaults to the node IP when future controller wiring is added."; }; }; }; config = mkMerge [ # FiberLB BGP configuration (mkIf cfg.fiberlbBgp.enable { # Assertions assertions = [ { assertion = clusterCfg.bgp.asn > 0; message = "ultracloud.cluster.bgp.asn must be configured for FiberLB BGP"; } { assertion = (length cfg.fiberlbBgp.peers) > 0; message = "ultracloud.network.fiberlbBgp.peers must contain at least one BGP peer"; } { assertion = config.services.fiberlb.enable or false; message = "ultracloud.network.fiberlbBgp.enable requires services.fiberlb.enable"; } ]; services.fiberlb.bgp = { enable = true; localAs = clusterCfg.bgp.asn; routerId = if cfg.fiberlbBgp.routerId != null then cfg.fiberlbBgp.routerId else clusterNodeIp; peers = cfg.fiberlbBgp.peers; }; services.fiberlb.vipOwnership.enable = mkDefault true; }) # PrismNET OVN integration (mkIf cfg.prismnetIntegration.enable { virtualisation.vswitch.enable = true; environment.systemPackages = [ pkgs.ovn ]; warnings = [ "ultracloud.network.prismnetIntegration.enable currently enables Open vSwitch and installs OVN tooling only. Wire ovn-controller explicitly before expecting PrismNET dataplane automation." ]; }) ]; }