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