1{ config, lib, options, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.services.etcd;
7 opt = options.services.etcd;
8
9in {
10
11 options.services.etcd = {
12 enable = mkOption {
13 description = lib.mdDoc "Whether to enable etcd.";
14 default = false;
15 type = types.bool;
16 };
17
18 name = mkOption {
19 description = lib.mdDoc "Etcd unique node name.";
20 default = config.networking.hostName;
21 defaultText = literalExpression "config.networking.hostName";
22 type = types.str;
23 };
24
25 advertiseClientUrls = mkOption {
26 description = lib.mdDoc "Etcd list of this member's client URLs to advertise to the rest of the cluster.";
27 default = cfg.listenClientUrls;
28 defaultText = literalExpression "config.${opt.listenClientUrls}";
29 type = types.listOf types.str;
30 };
31
32 listenClientUrls = mkOption {
33 description = lib.mdDoc "Etcd list of URLs to listen on for client traffic.";
34 default = ["http://127.0.0.1:2379"];
35 type = types.listOf types.str;
36 };
37
38 listenPeerUrls = mkOption {
39 description = lib.mdDoc "Etcd list of URLs to listen on for peer traffic.";
40 default = ["http://127.0.0.1:2380"];
41 type = types.listOf types.str;
42 };
43
44 initialAdvertisePeerUrls = mkOption {
45 description = lib.mdDoc "Etcd list of this member's peer URLs to advertise to rest of the cluster.";
46 default = cfg.listenPeerUrls;
47 defaultText = literalExpression "config.${opt.listenPeerUrls}";
48 type = types.listOf types.str;
49 };
50
51 initialCluster = mkOption {
52 description = lib.mdDoc "Etcd initial cluster configuration for bootstrapping.";
53 default = ["${cfg.name}=http://127.0.0.1:2380"];
54 defaultText = literalExpression ''["''${config.${opt.name}}=http://127.0.0.1:2380"]'';
55 type = types.listOf types.str;
56 };
57
58 initialClusterState = mkOption {
59 description = lib.mdDoc "Etcd initial cluster configuration for bootstrapping.";
60 default = "new";
61 type = types.enum ["new" "existing"];
62 };
63
64 initialClusterToken = mkOption {
65 description = lib.mdDoc "Etcd initial cluster token for etcd cluster during bootstrap.";
66 default = "etcd-cluster";
67 type = types.str;
68 };
69
70 discovery = mkOption {
71 description = lib.mdDoc "Etcd discovery url";
72 default = "";
73 type = types.str;
74 };
75
76 clientCertAuth = mkOption {
77 description = lib.mdDoc "Whether to use certs for client authentication";
78 default = false;
79 type = types.bool;
80 };
81
82 trustedCaFile = mkOption {
83 description = lib.mdDoc "Certificate authority file to use for clients";
84 default = null;
85 type = types.nullOr types.path;
86 };
87
88 certFile = mkOption {
89 description = lib.mdDoc "Cert file to use for clients";
90 default = null;
91 type = types.nullOr types.path;
92 };
93
94 keyFile = mkOption {
95 description = lib.mdDoc "Key file to use for clients";
96 default = null;
97 type = types.nullOr types.path;
98 };
99
100 peerCertFile = mkOption {
101 description = lib.mdDoc "Cert file to use for peer to peer communication";
102 default = cfg.certFile;
103 defaultText = literalExpression "config.${opt.certFile}";
104 type = types.nullOr types.path;
105 };
106
107 peerKeyFile = mkOption {
108 description = lib.mdDoc "Key file to use for peer to peer communication";
109 default = cfg.keyFile;
110 defaultText = literalExpression "config.${opt.keyFile}";
111 type = types.nullOr types.path;
112 };
113
114 peerTrustedCaFile = mkOption {
115 description = lib.mdDoc "Certificate authority file to use for peer to peer communication";
116 default = cfg.trustedCaFile;
117 defaultText = literalExpression "config.${opt.trustedCaFile}";
118 type = types.nullOr types.path;
119 };
120
121 peerClientCertAuth = mkOption {
122 description = lib.mdDoc "Whether to check all incoming peer requests from the cluster for valid client certificates signed by the supplied CA";
123 default = false;
124 type = types.bool;
125 };
126
127 extraConf = mkOption {
128 description = lib.mdDoc ''
129 Etcd extra configuration. See
130 <https://github.com/coreos/etcd/blob/master/Documentation/op-guide/configuration.md#configuration-flags>
131 '';
132 type = types.attrsOf types.str;
133 default = {};
134 example = literalExpression ''
135 {
136 "CORS" = "*";
137 "NAME" = "default-name";
138 "MAX_RESULT_BUFFER" = "1024";
139 "MAX_CLUSTER_SIZE" = "9";
140 "MAX_RETRY_ATTEMPTS" = "3";
141 }
142 '';
143 };
144
145 dataDir = mkOption {
146 type = types.path;
147 default = "/var/lib/etcd";
148 description = lib.mdDoc "Etcd data directory.";
149 };
150 };
151
152 config = mkIf cfg.enable {
153 systemd.tmpfiles.rules = [
154 "d '${cfg.dataDir}' 0700 etcd - - -"
155 ];
156
157 systemd.services.etcd = {
158 description = "etcd key-value store";
159 wantedBy = [ "multi-user.target" ];
160 after = [ "network.target" ];
161
162 environment = (filterAttrs (n: v: v != null) {
163 ETCD_NAME = cfg.name;
164 ETCD_DISCOVERY = cfg.discovery;
165 ETCD_DATA_DIR = cfg.dataDir;
166 ETCD_ADVERTISE_CLIENT_URLS = concatStringsSep "," cfg.advertiseClientUrls;
167 ETCD_LISTEN_CLIENT_URLS = concatStringsSep "," cfg.listenClientUrls;
168 ETCD_LISTEN_PEER_URLS = concatStringsSep "," cfg.listenPeerUrls;
169 ETCD_INITIAL_ADVERTISE_PEER_URLS = concatStringsSep "," cfg.initialAdvertisePeerUrls;
170 ETCD_PEER_CLIENT_CERT_AUTH = toString cfg.peerClientCertAuth;
171 ETCD_PEER_TRUSTED_CA_FILE = cfg.peerTrustedCaFile;
172 ETCD_PEER_CERT_FILE = cfg.peerCertFile;
173 ETCD_PEER_KEY_FILE = cfg.peerKeyFile;
174 ETCD_CLIENT_CERT_AUTH = toString cfg.clientCertAuth;
175 ETCD_TRUSTED_CA_FILE = cfg.trustedCaFile;
176 ETCD_CERT_FILE = cfg.certFile;
177 ETCD_KEY_FILE = cfg.keyFile;
178 }) // (optionalAttrs (cfg.discovery == ""){
179 ETCD_INITIAL_CLUSTER = concatStringsSep "," cfg.initialCluster;
180 ETCD_INITIAL_CLUSTER_STATE = cfg.initialClusterState;
181 ETCD_INITIAL_CLUSTER_TOKEN = cfg.initialClusterToken;
182 }) // (mapAttrs' (n: v: nameValuePair "ETCD_${n}" v) cfg.extraConf);
183
184 unitConfig = {
185 Documentation = "https://github.com/coreos/etcd";
186 };
187
188 serviceConfig = {
189 Type = "notify";
190 ExecStart = "${pkgs.etcd}/bin/etcd";
191 User = "etcd";
192 LimitNOFILE = 40000;
193 };
194 };
195
196 environment.systemPackages = [ pkgs.etcd ];
197
198 users.users.etcd = {
199 isSystemUser = true;
200 group = "etcd";
201 description = "Etcd daemon user";
202 home = cfg.dataDir;
203 };
204 users.groups.etcd = {};
205 };
206}