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 = literalExample ''
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.services.etcd = {
146 description = "etcd key-value store";
147 wantedBy = [ "multi-user.target" ];
148 after = [ "network.target" ];
149
150 environment = (filterAttrs (n: v: v != null) {
151 ETCD_NAME = cfg.name;
152 ETCD_DISCOVERY = cfg.discovery;
153 ETCD_DATA_DIR = cfg.dataDir;
154 ETCD_ADVERTISE_CLIENT_URLS = concatStringsSep "," cfg.advertiseClientUrls;
155 ETCD_LISTEN_CLIENT_URLS = concatStringsSep "," cfg.listenClientUrls;
156 ETCD_LISTEN_PEER_URLS = concatStringsSep "," cfg.listenPeerUrls;
157 ETCD_INITIAL_ADVERTISE_PEER_URLS = concatStringsSep "," cfg.initialAdvertisePeerUrls;
158 ETCD_PEER_TRUSTED_CA_FILE = cfg.peerTrustedCaFile;
159 ETCD_PEER_CERT_FILE = cfg.peerCertFile;
160 ETCD_PEER_KEY_FILE = cfg.peerKeyFile;
161 ETCD_CLIENT_CERT_AUTH = toString cfg.peerClientCertAuth;
162 ETCD_TRUSTED_CA_FILE = cfg.trustedCaFile;
163 ETCD_CERT_FILE = cfg.certFile;
164 ETCD_KEY_FILE = cfg.keyFile;
165 }) // (optionalAttrs (cfg.discovery == ""){
166 ETCD_INITIAL_CLUSTER = concatStringsSep "," cfg.initialCluster;
167 ETCD_INITIAL_CLUSTER_STATE = cfg.initialClusterState;
168 ETCD_INITIAL_CLUSTER_TOKEN = cfg.initialClusterToken;
169 }) // (mapAttrs' (n: v: nameValuePair "ETCD_${n}" v) cfg.extraConf);
170
171 unitConfig = {
172 Documentation = "https://github.com/coreos/etcd";
173 };
174
175 serviceConfig = {
176 Type = "notify";
177 ExecStart = "${pkgs.etcd.bin}/bin/etcd";
178 User = "etcd";
179 PermissionsStartOnly = true;
180 LimitNOFILE = 40000;
181 };
182
183 preStart = ''
184 mkdir -m 0700 -p ${cfg.dataDir}
185 if [ "$(id -u)" = 0 ]; then chown etcd ${cfg.dataDir}; fi
186 '';
187 };
188
189 environment.systemPackages = [ pkgs.etcdctl ];
190
191 users.extraUsers = singleton {
192 name = "etcd";
193 uid = config.ids.uids.etcd;
194 description = "Etcd daemon user";
195 home = cfg.dataDir;
196 };
197 };
198}