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