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