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