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}