1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8with lib;
9
10let
11 cfg = config.networking.ucarp;
12
13 ucarpExec = concatStringsSep " " (
14 [
15 "${cfg.package}/bin/ucarp"
16 "--interface=${cfg.interface}"
17 "--srcip=${cfg.srcIp}"
18 "--vhid=${toString cfg.vhId}"
19 "--passfile=${cfg.passwordFile}"
20 "--addr=${cfg.addr}"
21 "--advbase=${toString cfg.advBase}"
22 "--advskew=${toString cfg.advSkew}"
23 "--upscript=${cfg.upscript}"
24 "--downscript=${cfg.downscript}"
25 "--deadratio=${toString cfg.deadratio}"
26 ]
27 ++ (optional cfg.preempt "--preempt")
28 ++ (optional cfg.neutral "--neutral")
29 ++ (optional cfg.shutdown "--shutdown")
30 ++ (optional cfg.ignoreIfState "--ignoreifstate")
31 ++ (optional cfg.noMcast "--nomcast")
32 ++ (optional (cfg.extraParam != null) "--xparam=${cfg.extraParam}")
33 );
34in
35{
36 options.networking.ucarp = {
37 enable = mkEnableOption "ucarp, userspace implementation of CARP";
38
39 interface = mkOption {
40 type = types.str;
41 description = "Network interface to bind to.";
42 example = "eth0";
43 };
44
45 srcIp = mkOption {
46 type = types.str;
47 description = "Source (real) IP address of this host.";
48 };
49
50 vhId = mkOption {
51 type = types.ints.between 1 255;
52 description = "Virtual IP identifier shared between CARP hosts.";
53 example = 1;
54 };
55
56 passwordFile = mkOption {
57 type = types.str;
58 description = "File containing shared password between CARP hosts.";
59 example = "/run/keys/ucarp-password";
60 };
61
62 preempt = mkOption {
63 type = types.bool;
64 description = ''
65 Enable preemptive failover.
66 Thus, this host becomes the CARP master as soon as possible.
67 '';
68 default = false;
69 };
70
71 neutral = mkOption {
72 type = types.bool;
73 description = "Do not run downscript at start if the host is the backup.";
74 default = false;
75 };
76
77 addr = mkOption {
78 type = types.str;
79 description = "Virtual shared IP address.";
80 };
81
82 advBase = mkOption {
83 type = types.ints.unsigned;
84 description = "Advertisement frequency in seconds.";
85 default = 1;
86 };
87
88 advSkew = mkOption {
89 type = types.ints.unsigned;
90 description = "Advertisement skew in seconds.";
91 default = 0;
92 };
93
94 upscript = mkOption {
95 type = types.path;
96 description = ''
97 Command to run after become master, the interface name, virtual address
98 and optional extra parameters are passed as arguments.
99 '';
100 example = literalExpression ''
101 pkgs.writeScript "upscript" '''
102 #!/bin/sh
103 ''${pkgs.iproute2}/bin/ip addr add "$2"/24 dev "$1"
104 ''';
105 '';
106 };
107
108 downscript = mkOption {
109 type = types.path;
110 description = ''
111 Command to run after become backup, the interface name, virtual address
112 and optional extra parameters are passed as arguments.
113 '';
114 example = literalExpression ''
115 pkgs.writeScript "downscript" '''
116 #!/bin/sh
117 ''${pkgs.iproute2}/bin/ip addr del "$2"/24 dev "$1"
118 ''';
119 '';
120 };
121
122 deadratio = mkOption {
123 type = types.ints.unsigned;
124 description = "Ratio to consider a host as dead.";
125 default = 3;
126 };
127
128 shutdown = mkOption {
129 type = types.bool;
130 description = "Call downscript at exit.";
131 default = false;
132 };
133
134 ignoreIfState = mkOption {
135 type = types.bool;
136 description = "Ignore interface state, e.g., down or no carrier.";
137 default = false;
138 };
139
140 noMcast = mkOption {
141 type = types.bool;
142 description = "Use broadcast instead of multicast advertisements.";
143 default = false;
144 };
145
146 extraParam = mkOption {
147 type = types.nullOr types.str;
148 description = "Extra parameter to pass to the up/down scripts.";
149 default = null;
150 };
151
152 package = mkPackageOption pkgs "ucarp" {
153 extraDescription = ''
154 Please note that the default package, pkgs.ucarp, has not received any
155 upstream updates for a long time and can be considered as unmaintained.
156 '';
157 };
158 };
159
160 config = mkIf cfg.enable {
161 systemd.services.ucarp = {
162 description = "ucarp, userspace implementation of CARP";
163
164 wantedBy = [ "multi-user.target" ];
165 after = [ "network.target" ];
166
167 serviceConfig = {
168 Type = "exec";
169 ExecStart = ucarpExec;
170
171 ProtectSystem = "strict";
172 ProtectHome = true;
173 PrivateTmp = true;
174 ProtectClock = true;
175 ProtectKernelModules = true;
176 ProtectControlGroups = true;
177 MemoryDenyWriteExecute = true;
178 RestrictRealtime = true;
179 };
180 };
181 };
182
183 meta.maintainers = with lib.maintainers; [ oxzi ];
184}