summaryrefslogtreecommitdiffstats
path: root/nixos/modules/services/networking/consul.nix
blob: ebc836814089804ce940b1af19d0a5ded03a3aa9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
{ config, lib, pkgs, utils, ... }:

with lib;
let

  dataDir = "/var/lib/consul";
  cfg = config.services.consul;

  configOptions = {
    data_dir = dataDir;
    rejoin_after_leave = true;
  }
  // (if cfg.webUi then { ui_dir = "${pkgs.consul.ui}"; } else { })
  // cfg.extraConfig;

  configFiles = [ "/etc/consul.json" "/etc/consul-addrs.json" ]
    ++ cfg.extraConfigFiles;

  devices = attrValues (filterAttrs (_: i: i != null) cfg.interface);
  systemdDevices = flip map devices
    (i: "sys-subsystem-net-devices-${utils.escapeSystemdPath i}.device");
in
{
  options = {

    services.consul = {

      enable = mkOption {
        type = types.bool;
        default = false;
        description = ''
          Enables the consul daemon.
        '';
      };

      webUi = mkOption {
        type = types.bool;
        default = false;
        description = ''
          Enables the web interface on the consul http port.
        '';
      };

      interface = {

        advertise = mkOption {
          type = types.nullOr types.str;
          default = null;
          description = ''
            The name of the interface to pull the advertise_addr from.
          '';
        };

        bind = mkOption {
          type = types.nullOr types.str;
          default = null;
          description = ''
            The name of the interface to pull the bind_addr from.
          '';
        };

      };

      forceIpv4 = mkOption {
        type = types.bool;
        default = false;
        description = ''
          Whether we should force the interfaces to only pull ipv4 addresses.
        '';
      };

      dropPrivileges = mkOption {
        type = types.bool;
        default = true;
        description = ''
          Whether the consul agent should be run as a non-root consul user.
        '';
      };

      extraConfig = mkOption {
        default = { };
        description = ''
          Extra configuration options which are serialized to json and added
          to the config.json file.
        '';
      };

      extraConfigFiles = mkOption {
        default = [ ];
        type = types.listOf types.str;
        description = ''
          Additional configuration files to pass to consul
          NOTE: These will not trigger the service to be restarted when altered.
        '';
      };

    };

  };

  config = mkIf cfg.enable {

    users.extraUsers."consul" = {
      description = "Consul agent daemon user";
      uid = config.ids.uids.consul;
    };

    environment = {
      etc."consul.json".text = builtins.toJSON configOptions;
      systemPackages = with pkgs; [ consul ];
    };

    systemd.services.consul = {
      wantedBy = [ "multi-user.target" ];
      after = [ "network.target" ] ++ systemdDevices;
      bindsTo = systemdDevices;
      restartTriggers = [ config.environment.etc."consul.json".source ];

      serviceConfig = {
        ExecStart = "@${pkgs.consul}/bin/consul consul agent"
          + concatMapStrings (n: " -config-file ${n}") configFiles;
        ExecStop = "${pkgs.consul}/bin/consul leave";
        ExecReload = "${pkgs.consul}/bin/consul reload";
        PermissionsStartOnly = true;
        User = if cfg.dropPrivileges then "consul" else null;
      };

      path = with pkgs; [ iproute gnugrep gawk ];
      preStart = ''
        mkdir -m 0700 -p ${dataDir}
        chown -R consul ${dataDir}

        # Determine interface addresses
        getAddrOnce () {
          ip addr show dev "$1" \
            | grep 'inet${optionalString (cfg.forceIpv4) " "}.*scope global' \
            | awk -F '[ /\t]*' '{print $3}' | head -n 1
        }
        getAddr () {
          ADDR="$(getAddrOnce $1)"
          LEFT=60 # Die after 1 minute
          while [ -z "$ADDR" ]; do
            sleep 1
            LEFT=$(expr $LEFT - 1)
            if [ "$LEFT" -eq "0" ]; then
              echo "Address lookup timed out"
              exit 1
            fi
            ADDR="$(getAddrOnce $1)"
          done
          echo "$ADDR"
        }
        echo "{" > /etc/consul-addrs.json
      ''
      + concatStrings (flip mapAttrsToList cfg.interface (name: i:
        optionalString (i != null) ''
          echo "    \"${name}_addr\": \"$(getAddr "${i}")\"," >> /etc/consul-addrs.json
        ''))
      + ''
        echo "    \"\": \"\"" >> /etc/consul-addrs.json
        echo "}" >> /etc/consul-addrs.json
      '';
    };

  };
}