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