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