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