at 23.11-pre 5.8 kB view raw
1{ config, lib, pkgs, ... }: 2 3with lib; 4 5let 6 7 cfg = config.services.frr; 8 9 services = [ 10 "static" 11 "bgp" 12 "ospf" 13 "ospf6" 14 "rip" 15 "ripng" 16 "isis" 17 "pim" 18 "ldp" 19 "nhrp" 20 "eigrp" 21 "babel" 22 "sharp" 23 "pbr" 24 "bfd" 25 "fabric" 26 ]; 27 28 allServices = services ++ [ "zebra" ]; 29 30 isEnabled = service: cfg.${service}.enable; 31 32 daemonName = service: if service == "zebra" then service else "${service}d"; 33 34 configFile = service: 35 let 36 scfg = cfg.${service}; 37 in 38 if scfg.configFile != null then scfg.configFile 39 else pkgs.writeText "${daemonName service}.conf" 40 '' 41 ! FRR ${daemonName service} configuration 42 ! 43 hostname ${config.networking.hostName} 44 log syslog 45 service password-encryption 46 ! 47 ${scfg.config} 48 ! 49 end 50 ''; 51 52 serviceOptions = service: 53 { 54 enable = mkEnableOption (lib.mdDoc "the FRR ${toUpper service} routing protocol"); 55 56 configFile = mkOption { 57 type = types.nullOr types.path; 58 default = null; 59 example = "/etc/frr/${daemonName service}.conf"; 60 description = lib.mdDoc '' 61 Configuration file to use for FRR ${daemonName service}. 62 By default the NixOS generated files are used. 63 ''; 64 }; 65 66 config = mkOption { 67 type = types.lines; 68 default = ""; 69 example = 70 let 71 examples = { 72 rip = '' 73 router rip 74 network 10.0.0.0/8 75 ''; 76 77 ospf = '' 78 router ospf 79 network 10.0.0.0/8 area 0 80 ''; 81 82 bgp = '' 83 router bgp 65001 84 neighbor 10.0.0.1 remote-as 65001 85 ''; 86 }; 87 in 88 examples.${service} or ""; 89 description = lib.mdDoc '' 90 ${daemonName service} configuration statements. 91 ''; 92 }; 93 94 vtyListenAddress = mkOption { 95 type = types.str; 96 default = "localhost"; 97 description = lib.mdDoc '' 98 Address to bind to for the VTY interface. 99 ''; 100 }; 101 102 vtyListenPort = mkOption { 103 type = types.nullOr types.int; 104 default = null; 105 description = lib.mdDoc '' 106 TCP Port to bind to for the VTY interface. 107 ''; 108 }; 109 110 extraOptions = mkOption { 111 type = types.listOf types.str; 112 default = []; 113 description = lib.mdDoc '' 114 Extra options for the daemon. 115 ''; 116 }; 117 }; 118 119in 120 121{ 122 123 ###### interface 124 imports = [ 125 { 126 options.services.frr = { 127 zebra = (serviceOptions "zebra") // { 128 enable = mkOption { 129 type = types.bool; 130 default = any isEnabled services; 131 description = lib.mdDoc '' 132 Whether to enable the Zebra routing manager. 133 134 The Zebra routing manager is automatically enabled 135 if any routing protocols are configured. 136 ''; 137 }; 138 }; 139 }; 140 } 141 { options.services.frr = (genAttrs services serviceOptions); } 142 ]; 143 144 ###### implementation 145 146 config = mkIf (any isEnabled allServices) { 147 148 environment.systemPackages = [ 149 pkgs.frr # for the vtysh tool 150 ]; 151 152 users.users.frr = { 153 description = "FRR daemon user"; 154 isSystemUser = true; 155 group = "frr"; 156 }; 157 158 users.groups = { 159 frr = {}; 160 # Members of the frrvty group can use vtysh to inspect the FRR daemons 161 frrvty = { members = [ "frr" ]; }; 162 }; 163 164 environment.etc = let 165 mkEtcLink = service: { 166 name = "frr/${service}.conf"; 167 value.source = configFile service; 168 }; 169 in 170 (builtins.listToAttrs 171 (map mkEtcLink (filter isEnabled allServices))) // { 172 "frr/vtysh.conf".text = ""; 173 }; 174 175 systemd.tmpfiles.rules = [ 176 "d /run/frr 0750 frr frr -" 177 ]; 178 179 systemd.services = 180 let 181 frrService = service: 182 let 183 scfg = cfg.${service}; 184 daemon = daemonName service; 185 in 186 nameValuePair daemon ({ 187 wantedBy = [ "multi-user.target" ]; 188 after = [ "network-pre.target" "systemd-sysctl.service" ] ++ lib.optionals (service != "zebra") [ "zebra.service" ]; 189 bindsTo = lib.optionals (service != "zebra") [ "zebra.service" ]; 190 wants = [ "network.target" ]; 191 192 description = if service == "zebra" then "FRR Zebra routing manager" 193 else "FRR ${toUpper service} routing daemon"; 194 195 unitConfig.Documentation = if service == "zebra" then "man:zebra(8)" 196 else "man:${daemon}(8) man:zebra(8)"; 197 198 restartTriggers = [ 199 (configFile service) 200 ]; 201 reloadIfChanged = true; 202 203 serviceConfig = { 204 PIDFile = "frr/${daemon}.pid"; 205 ExecStart = "${pkgs.frr}/libexec/frr/${daemon} -f /etc/frr/${service}.conf" 206 + optionalString (scfg.vtyListenAddress != "") " -A ${scfg.vtyListenAddress}" 207 + optionalString (scfg.vtyListenPort != null) " -P ${toString scfg.vtyListenPort}" 208 + " " + (concatStringsSep " " scfg.extraOptions); 209 ExecReload = "${pkgs.python3.interpreter} ${pkgs.frr}/libexec/frr/frr-reload.py --reload --daemon ${daemonName service} --bindir ${pkgs.frr}/bin --rundir /run/frr /etc/frr/${service}.conf"; 210 Restart = "on-abnormal"; 211 }; 212 }); 213 in 214 listToAttrs (map frrService (filter isEnabled allServices)); 215 216 }; 217 218 meta.maintainers = with lib.maintainers; [ woffs ]; 219 220}