at 25.11-pre 8.6 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 utils, 6 ... 7}: 8let 9 10 dataDir = "/var/lib/consul"; 11 cfg = config.services.consul; 12 13 configOptions = { 14 data_dir = dataDir; 15 ui_config = { 16 enabled = cfg.webUi; 17 }; 18 } // cfg.extraConfig; 19 20 configFiles = [ 21 "/etc/consul.json" 22 "/etc/consul-addrs.json" 23 ] ++ cfg.extraConfigFiles; 24 25 devices = lib.attrValues (lib.filterAttrs (_: i: i != null) cfg.interface); 26 systemdDevices = lib.forEach devices ( 27 i: "sys-subsystem-net-devices-${utils.escapeSystemdPath i}.device" 28 ); 29in 30{ 31 options = { 32 33 services.consul = { 34 35 enable = lib.mkOption { 36 type = lib.types.bool; 37 default = false; 38 description = '' 39 Enables the consul daemon. 40 ''; 41 }; 42 43 package = lib.mkPackageOption pkgs "consul" { }; 44 45 webUi = lib.mkOption { 46 type = lib.types.bool; 47 default = false; 48 description = '' 49 Enables the web interface on the consul http port. 50 ''; 51 }; 52 53 leaveOnStop = lib.mkOption { 54 type = lib.types.bool; 55 default = false; 56 description = '' 57 If enabled, causes a leave action to be sent when closing consul. 58 This allows a clean termination of the node, but permanently removes 59 it from the cluster. You probably don't want this option unless you 60 are running a node which going offline in a permanent / semi-permanent 61 fashion. 62 ''; 63 }; 64 65 interface = { 66 67 advertise = lib.mkOption { 68 type = lib.types.nullOr lib.types.str; 69 default = null; 70 description = '' 71 The name of the interface to pull the advertise_addr from. 72 ''; 73 }; 74 75 bind = lib.mkOption { 76 type = lib.types.nullOr lib.types.str; 77 default = null; 78 description = '' 79 The name of the interface to pull the bind_addr from. 80 ''; 81 }; 82 }; 83 84 forceAddrFamily = lib.mkOption { 85 type = lib.types.enum [ 86 "any" 87 "ipv4" 88 "ipv6" 89 ]; 90 default = "any"; 91 description = '' 92 Whether to bind ipv4/ipv6 or both kind of addresses. 93 ''; 94 }; 95 96 forceIpv4 = lib.mkOption { 97 type = lib.types.nullOr lib.types.bool; 98 default = null; 99 description = '' 100 Deprecated: Use consul.forceAddrFamily instead. 101 Whether we should force the interfaces to only pull ipv4 addresses. 102 ''; 103 }; 104 105 dropPrivileges = lib.mkOption { 106 type = lib.types.bool; 107 default = true; 108 description = '' 109 Whether the consul agent should be run as a non-root consul user. 110 ''; 111 }; 112 113 extraConfig = lib.mkOption { 114 default = { }; 115 type = lib.types.attrsOf lib.types.anything; 116 description = '' 117 Extra configuration options which are serialized to json and added 118 to the config.json file. 119 ''; 120 }; 121 122 extraConfigFiles = lib.mkOption { 123 default = [ ]; 124 type = lib.types.listOf lib.types.str; 125 description = '' 126 Additional configuration files to pass to consul 127 NOTE: These will not trigger the service to be restarted when altered. 128 ''; 129 }; 130 131 alerts = { 132 enable = lib.mkEnableOption "consul-alerts"; 133 134 package = lib.mkPackageOption pkgs "consul-alerts" { }; 135 136 listenAddr = lib.mkOption { 137 description = "Api listening address."; 138 default = "localhost:9000"; 139 type = lib.types.str; 140 }; 141 142 consulAddr = lib.mkOption { 143 description = "Consul api listening address"; 144 default = "localhost:8500"; 145 type = lib.types.str; 146 }; 147 148 watchChecks = lib.mkOption { 149 description = "Whether to enable check watcher."; 150 default = true; 151 type = lib.types.bool; 152 }; 153 154 watchEvents = lib.mkOption { 155 description = "Whether to enable event watcher."; 156 default = true; 157 type = lib.types.bool; 158 }; 159 }; 160 161 }; 162 163 }; 164 165 config = lib.mkIf cfg.enable ( 166 lib.mkMerge [ 167 { 168 169 users.users.consul = { 170 description = "Consul agent daemon user"; 171 isSystemUser = true; 172 group = "consul"; 173 # The shell is needed for health checks 174 shell = "/run/current-system/sw/bin/bash"; 175 }; 176 users.groups.consul = { }; 177 178 environment = { 179 etc."consul.json".text = builtins.toJSON configOptions; 180 # We need consul.d to exist for consul to start 181 etc."consul.d/dummy.json".text = "{ }"; 182 systemPackages = [ cfg.package ]; 183 }; 184 185 warnings = lib.flatten [ 186 (lib.optional (cfg.forceIpv4 != null) '' 187 The option consul.forceIpv4 is deprecated, please use 188 consul.forceAddrFamily instead. 189 '') 190 ]; 191 192 systemd.services.consul = { 193 wantedBy = [ "multi-user.target" ]; 194 after = [ "network.target" ] ++ systemdDevices; 195 bindsTo = systemdDevices; 196 restartTriggers = 197 [ config.environment.etc."consul.json".source ] 198 ++ lib.mapAttrsToList (_: d: d.source) ( 199 lib.filterAttrs (n: _: lib.hasPrefix "consul.d/" n) config.environment.etc 200 ); 201 202 serviceConfig = 203 { 204 ExecStart = 205 "@${lib.getExe cfg.package} consul agent -config-dir /etc/consul.d" 206 + lib.concatMapStrings (n: " -config-file ${n}") configFiles; 207 ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; 208 PermissionsStartOnly = true; 209 User = if cfg.dropPrivileges then "consul" else null; 210 Restart = "on-failure"; 211 TimeoutStartSec = "infinity"; 212 } 213 // (lib.optionalAttrs (cfg.leaveOnStop) { 214 ExecStop = "${lib.getExe cfg.package} leave"; 215 }); 216 217 path = with pkgs; [ 218 iproute2 219 gawk 220 cfg.package 221 ]; 222 preStart = 223 let 224 family = 225 if cfg.forceAddrFamily == "ipv6" then 226 "-6" 227 else if cfg.forceAddrFamily == "ipv4" then 228 "-4" 229 else 230 ""; 231 in 232 '' 233 mkdir -m 0700 -p ${dataDir} 234 chown -R consul ${dataDir} 235 236 # Determine interface addresses 237 getAddrOnce () { 238 ip ${family} addr show dev "$1" scope global \ 239 | awk -F '[ /\t]*' '/inet/ {print $3}' | head -n 1 240 } 241 getAddr () { 242 ADDR="$(getAddrOnce $1)" 243 LEFT=60 # Die after 1 minute 244 while [ -z "$ADDR" ]; do 245 sleep 1 246 LEFT=$(expr $LEFT - 1) 247 if [ "$LEFT" -eq "0" ]; then 248 echo "Address lookup timed out" 249 exit 1 250 fi 251 ADDR="$(getAddrOnce $1)" 252 done 253 echo "$ADDR" 254 } 255 echo "{" > /etc/consul-addrs.json 256 delim=" " 257 '' 258 + lib.concatStrings ( 259 lib.flip lib.mapAttrsToList cfg.interface ( 260 name: i: 261 lib.optionalString (i != null) '' 262 echo "$delim \"${name}_addr\": \"$(getAddr "${i}")\"" >> /etc/consul-addrs.json 263 delim="," 264 '' 265 ) 266 ) 267 + '' 268 echo "}" >> /etc/consul-addrs.json 269 ''; 270 }; 271 } 272 273 # deprecated 274 (lib.mkIf (cfg.forceIpv4 != null && cfg.forceIpv4) { 275 services.consul.forceAddrFamily = "ipv4"; 276 }) 277 278 (lib.mkIf (cfg.alerts.enable) { 279 systemd.services.consul-alerts = { 280 wantedBy = [ "multi-user.target" ]; 281 after = [ "consul.service" ]; 282 283 path = [ cfg.package ]; 284 285 serviceConfig = { 286 ExecStart = '' 287 ${lib.getExe cfg.alerts.package} start \ 288 --alert-addr=${cfg.alerts.listenAddr} \ 289 --consul-addr=${cfg.alerts.consulAddr} \ 290 ${lib.optionalString cfg.alerts.watchChecks "--watch-checks"} \ 291 ${lib.optionalString cfg.alerts.watchEvents "--watch-events"} 292 ''; 293 User = if cfg.dropPrivileges then "consul" else null; 294 Restart = "on-failure"; 295 }; 296 }; 297 }) 298 299 ] 300 ); 301}