at 21.11-pre 9.0 kB view raw
1{ config, lib, pkgs, ... }: 2 3with lib; 4 5let 6 7 cfg = config.services.keepalived; 8 9 keepalivedConf = pkgs.writeText "keepalived.conf" '' 10 global_defs { 11 ${optionalString cfg.enableScriptSecurity "enable_script_security"} 12 ${snmpGlobalDefs} 13 ${cfg.extraGlobalDefs} 14 } 15 16 ${vrrpScriptStr} 17 ${vrrpInstancesStr} 18 ${cfg.extraConfig} 19 ''; 20 21 snmpGlobalDefs = with cfg.snmp; optionalString enable ( 22 optionalString (socket != null) "snmp_socket ${socket}\n" 23 + optionalString enableKeepalived "enable_snmp_keepalived\n" 24 + optionalString enableChecker "enable_snmp_checker\n" 25 + optionalString enableRfc "enable_snmp_rfc\n" 26 + optionalString enableRfcV2 "enable_snmp_rfcv2\n" 27 + optionalString enableRfcV3 "enable_snmp_rfcv3\n" 28 + optionalString enableTraps "enable_traps" 29 ); 30 31 vrrpScriptStr = concatStringsSep "\n" (map (s: 32 '' 33 vrrp_script ${s.name} { 34 script "${s.script}" 35 interval ${toString s.interval} 36 fall ${toString s.fall} 37 rise ${toString s.rise} 38 timeout ${toString s.timeout} 39 weight ${toString s.weight} 40 user ${s.user} ${optionalString (s.group != null) s.group} 41 42 ${s.extraConfig} 43 } 44 '' 45 ) vrrpScripts); 46 47 vrrpInstancesStr = concatStringsSep "\n" (map (i: 48 '' 49 vrrp_instance ${i.name} { 50 interface ${i.interface} 51 state ${i.state} 52 virtual_router_id ${toString i.virtualRouterId} 53 priority ${toString i.priority} 54 ${optionalString i.noPreempt "nopreempt"} 55 56 ${optionalString i.useVmac ( 57 "use_vmac" + optionalString (i.vmacInterface != null) " ${i.vmacInterface}" 58 )} 59 ${optionalString i.vmacXmitBase "vmac_xmit_base"} 60 61 ${optionalString (i.unicastSrcIp != null) "unicast_src_ip ${i.unicastSrcIp}"} 62 unicast_peer { 63 ${concatStringsSep "\n" i.unicastPeers} 64 } 65 66 virtual_ipaddress { 67 ${concatMapStringsSep "\n" virtualIpLine i.virtualIps} 68 } 69 70 ${optionalString (builtins.length i.trackScripts > 0) '' 71 track_script { 72 ${concatStringsSep "\n" i.trackScripts} 73 } 74 ''} 75 76 ${optionalString (builtins.length i.trackInterfaces > 0) '' 77 track_interface { 78 ${concatStringsSep "\n" i.trackInterfaces} 79 } 80 ''} 81 82 ${i.extraConfig} 83 } 84 '' 85 ) vrrpInstances); 86 87 virtualIpLine = (ip: 88 ip.addr 89 + optionalString (notNullOrEmpty ip.brd) " brd ${ip.brd}" 90 + optionalString (notNullOrEmpty ip.dev) " dev ${ip.dev}" 91 + optionalString (notNullOrEmpty ip.scope) " scope ${ip.scope}" 92 + optionalString (notNullOrEmpty ip.label) " label ${ip.label}" 93 ); 94 95 notNullOrEmpty = s: !(s == null || s == ""); 96 97 vrrpScripts = mapAttrsToList (name: config: 98 { 99 inherit name; 100 } // config 101 ) cfg.vrrpScripts; 102 103 vrrpInstances = mapAttrsToList (iName: iConfig: 104 { 105 name = iName; 106 } // iConfig 107 ) cfg.vrrpInstances; 108 109 vrrpInstanceAssertions = i: [ 110 { assertion = i.interface != ""; 111 message = "services.keepalived.vrrpInstances.${i.name}.interface option cannot be empty."; 112 } 113 { assertion = i.virtualRouterId >= 0 && i.virtualRouterId <= 255; 114 message = "services.keepalived.vrrpInstances.${i.name}.virtualRouterId must be an integer between 0..255."; 115 } 116 { assertion = i.priority >= 0 && i.priority <= 255; 117 message = "services.keepalived.vrrpInstances.${i.name}.priority must be an integer between 0..255."; 118 } 119 { assertion = i.vmacInterface == null || i.useVmac; 120 message = "services.keepalived.vrrpInstances.${i.name}.vmacInterface has no effect when services.keepalived.vrrpInstances.${i.name}.useVmac is not set."; 121 } 122 { assertion = !i.vmacXmitBase || i.useVmac; 123 message = "services.keepalived.vrrpInstances.${i.name}.vmacXmitBase has no effect when services.keepalived.vrrpInstances.${i.name}.useVmac is not set."; 124 } 125 ] ++ flatten (map (virtualIpAssertions i.name) i.virtualIps) 126 ++ flatten (map (vrrpScriptAssertion i.name) i.trackScripts); 127 128 virtualIpAssertions = vrrpName: ip: [ 129 { assertion = ip.addr != ""; 130 message = "The 'addr' option for an services.keepalived.vrrpInstances.${vrrpName}.virtualIps entry cannot be empty."; 131 } 132 ]; 133 134 vrrpScriptAssertion = vrrpName: scriptName: { 135 assertion = builtins.hasAttr scriptName cfg.vrrpScripts; 136 message = "services.keepalived.vrrpInstances.${vrrpName} trackscript ${scriptName} is not defined in services.keepalived.vrrpScripts."; 137 }; 138 139 pidFile = "/run/keepalived.pid"; 140 141in 142{ 143 144 options = { 145 services.keepalived = { 146 147 enable = mkOption { 148 type = types.bool; 149 default = false; 150 description = '' 151 Whether to enable Keepalived. 152 ''; 153 }; 154 155 enableScriptSecurity = mkOption { 156 type = types.bool; 157 default = false; 158 description = '' 159 Don't run scripts configured to be run as root if any part of the path is writable by a non-root user. 160 ''; 161 }; 162 163 snmp = { 164 165 enable = mkOption { 166 type = types.bool; 167 default = false; 168 description = '' 169 Whether to enable the builtin AgentX subagent. 170 ''; 171 }; 172 173 socket = mkOption { 174 type = types.nullOr types.str; 175 default = null; 176 description = '' 177 Socket to use for connecting to SNMP master agent. If this value is 178 set to null, keepalived's default will be used, which is 179 unix:/var/agentx/master, unless using a network namespace, when the 180 default is udp:localhost:705. 181 ''; 182 }; 183 184 enableKeepalived = mkOption { 185 type = types.bool; 186 default = false; 187 description = '' 188 Enable SNMP handling of vrrp element of KEEPALIVED MIB. 189 ''; 190 }; 191 192 enableChecker = mkOption { 193 type = types.bool; 194 default = false; 195 description = '' 196 Enable SNMP handling of checker element of KEEPALIVED MIB. 197 ''; 198 }; 199 200 enableRfc = mkOption { 201 type = types.bool; 202 default = false; 203 description = '' 204 Enable SNMP handling of RFC2787 and RFC6527 VRRP MIBs. 205 ''; 206 }; 207 208 enableRfcV2 = mkOption { 209 type = types.bool; 210 default = false; 211 description = '' 212 Enable SNMP handling of RFC2787 VRRP MIB. 213 ''; 214 }; 215 216 enableRfcV3 = mkOption { 217 type = types.bool; 218 default = false; 219 description = '' 220 Enable SNMP handling of RFC6527 VRRP MIB. 221 ''; 222 }; 223 224 enableTraps = mkOption { 225 type = types.bool; 226 default = false; 227 description = '' 228 Enable SNMP traps. 229 ''; 230 }; 231 232 }; 233 234 vrrpScripts = mkOption { 235 type = types.attrsOf (types.submodule (import ./vrrp-script-options.nix { 236 inherit lib; 237 })); 238 default = {}; 239 description = "Declarative vrrp script config"; 240 }; 241 242 vrrpInstances = mkOption { 243 type = types.attrsOf (types.submodule (import ./vrrp-instance-options.nix { 244 inherit lib; 245 })); 246 default = {}; 247 description = "Declarative vhost config"; 248 }; 249 250 extraGlobalDefs = mkOption { 251 type = types.lines; 252 default = ""; 253 description = '' 254 Extra lines to be added verbatim to the 'global_defs' block of the 255 configuration file 256 ''; 257 }; 258 259 extraConfig = mkOption { 260 type = types.lines; 261 default = ""; 262 description = '' 263 Extra lines to be added verbatim to the configuration file. 264 ''; 265 }; 266 267 }; 268 }; 269 270 config = mkIf cfg.enable { 271 272 assertions = flatten (map vrrpInstanceAssertions vrrpInstances); 273 274 systemd.timers.keepalived-boot-delay = { 275 description = "Keepalive Daemon delay to avoid instant transition to MASTER state"; 276 after = [ "network.target" "network-online.target" "syslog.target" ]; 277 requires = [ "network-online.target" ]; 278 wantedBy = [ "multi-user.target" ]; 279 timerConfig = { 280 OnActiveSec = "5s"; 281 Unit = "keepalived.service"; 282 }; 283 }; 284 285 systemd.services.keepalived = { 286 description = "Keepalive Daemon (LVS and VRRP)"; 287 after = [ "network.target" "network-online.target" "syslog.target" ]; 288 wants = [ "network-online.target" ]; 289 serviceConfig = { 290 Type = "forking"; 291 PIDFile = pidFile; 292 KillMode = "process"; 293 ExecStart = "${pkgs.keepalived}/sbin/keepalived" 294 + " -f ${keepalivedConf}" 295 + " -p ${pidFile}" 296 + optionalString cfg.snmp.enable " --snmp"; 297 ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; 298 Restart = "always"; 299 RestartSec = "1s"; 300 }; 301 }; 302 }; 303}