1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.services.elasticsearch;
7
8 es5 = builtins.compareVersions (builtins.parseDrvName cfg.package.name).version "5" >= 0;
9 es6 = builtins.compareVersions (builtins.parseDrvName cfg.package.name).version "6" >= 0;
10
11 esConfig = ''
12 network.host: ${cfg.listenAddress}
13 cluster.name: ${cfg.cluster_name}
14
15 ${if es5 then ''
16 http.port: ${toString cfg.port}
17 transport.tcp.port: ${toString cfg.tcp_port}
18 '' else ''
19 network.port: ${toString cfg.port}
20 network.tcp.port: ${toString cfg.tcp_port}
21 # TODO: find a way to enable security manager
22 security.manager.enabled: false
23 ''}
24
25 ${cfg.extraConf}
26 '';
27
28 configDir = pkgs.buildEnv {
29 name = "elasticsearch-config";
30 paths = [
31 (pkgs.writeTextDir "elasticsearch.yml" esConfig)
32 (if es5 then (pkgs.writeTextDir "log4j2.properties" cfg.logging)
33 else (pkgs.writeTextDir "logging.yml" cfg.logging))
34 ];
35 # Elasticsearch 5.x won't start when the scripts directory does not exist
36 postBuild = if es5 then "${pkgs.coreutils}/bin/mkdir -p $out/scripts" else "";
37 };
38
39 esPlugins = pkgs.buildEnv {
40 name = "elasticsearch-plugins";
41 paths = cfg.plugins;
42 # Elasticsearch 5.x won't start when the plugins directory does not exist
43 postBuild = if es5 then "${pkgs.coreutils}/bin/mkdir -p $out/plugins" else "";
44 };
45
46in {
47
48 ###### interface
49
50 options.services.elasticsearch = {
51 enable = mkOption {
52 description = "Whether to enable elasticsearch.";
53 default = false;
54 type = types.bool;
55 };
56
57 package = mkOption {
58 description = "Elasticsearch package to use.";
59 default = pkgs.elasticsearch2;
60 defaultText = "pkgs.elasticsearch2";
61 type = types.package;
62 };
63
64 listenAddress = mkOption {
65 description = "Elasticsearch listen address.";
66 default = "127.0.0.1";
67 type = types.str;
68 };
69
70 port = mkOption {
71 description = "Elasticsearch port to listen for HTTP traffic.";
72 default = 9200;
73 type = types.int;
74 };
75
76 tcp_port = mkOption {
77 description = "Elasticsearch port for the node to node communication.";
78 default = 9300;
79 type = types.int;
80 };
81
82 cluster_name = mkOption {
83 description = "Elasticsearch name that identifies your cluster for auto-discovery.";
84 default = "elasticsearch";
85 type = types.str;
86 };
87
88 extraConf = mkOption {
89 description = "Extra configuration for elasticsearch.";
90 default = "";
91 type = types.str;
92 example = ''
93 node.name: "elasticsearch"
94 node.master: true
95 node.data: false
96 '';
97 };
98
99 logging = mkOption {
100 description = "Elasticsearch logging configuration.";
101 default =
102 if es5 then ''
103 logger.action.name = org.elasticsearch.action
104 logger.action.level = info
105
106 appender.console.type = Console
107 appender.console.name = console
108 appender.console.layout.type = PatternLayout
109 appender.console.layout.pattern = [%d{ISO8601}][%-5p][%-25c{1.}] %marker%m%n
110
111 rootLogger.level = info
112 rootLogger.appenderRef.console.ref = console
113 '' else ''
114 rootLogger: INFO, console
115 logger:
116 action: INFO
117 com.amazonaws: WARN
118 appender:
119 console:
120 type: console
121 layout:
122 type: consolePattern
123 conversionPattern: "[%d{ISO8601}][%-5p][%-25c] %m%n"
124 '';
125 type = types.str;
126 };
127
128 dataDir = mkOption {
129 type = types.path;
130 default = "/var/lib/elasticsearch";
131 description = ''
132 Data directory for elasticsearch.
133 '';
134 };
135
136 extraCmdLineOptions = mkOption {
137 description = "Extra command line options for the elasticsearch launcher.";
138 default = [];
139 type = types.listOf types.str;
140 };
141
142 extraJavaOptions = mkOption {
143 description = "Extra command line options for Java.";
144 default = [];
145 type = types.listOf types.str;
146 example = [ "-Djava.net.preferIPv4Stack=true" ];
147 };
148
149 plugins = mkOption {
150 description = "Extra elasticsearch plugins";
151 default = [];
152 type = types.listOf types.package;
153 };
154
155 };
156
157 ###### implementation
158
159 config = mkIf cfg.enable {
160 systemd.services.elasticsearch = {
161 description = "Elasticsearch Daemon";
162 wantedBy = [ "multi-user.target" ];
163 after = [ "network.target" ];
164 path = [ pkgs.inetutils ];
165 environment = {
166 ES_HOME = cfg.dataDir;
167 ES_JAVA_OPTS = toString ( optional (!es6) [ "-Des.path.conf=${configDir}" ]
168 ++ cfg.extraJavaOptions);
169 } // optionalAttrs es6 {
170 ES_PATH_CONF = configDir;
171 };
172 serviceConfig = {
173 ExecStart = "${cfg.package}/bin/elasticsearch ${toString cfg.extraCmdLineOptions}";
174 User = "elasticsearch";
175 PermissionsStartOnly = true;
176 LimitNOFILE = "1024000";
177 };
178 preStart = ''
179 ${optionalString (!config.boot.isContainer) ''
180 # Only set vm.max_map_count if lower than ES required minimum
181 # This avoids conflict if configured via boot.kernel.sysctl
182 if [ `${pkgs.procps}/bin/sysctl -n vm.max_map_count` -lt 262144 ]; then
183 ${pkgs.procps}/bin/sysctl -w vm.max_map_count=262144
184 fi
185 ''}
186
187 mkdir -m 0700 -p ${cfg.dataDir}
188
189 # Install plugins
190 ln -sfT ${esPlugins}/plugins ${cfg.dataDir}/plugins
191 ln -sfT ${cfg.package}/lib ${cfg.dataDir}/lib
192 ln -sfT ${cfg.package}/modules ${cfg.dataDir}/modules
193 if [ "$(id -u)" = 0 ]; then chown -R elasticsearch ${cfg.dataDir}; fi
194 '';
195 };
196
197 environment.systemPackages = [ cfg.package ];
198
199 users = {
200 groups.elasticsearch.gid = config.ids.gids.elasticsearch;
201 users.elasticsearch = {
202 uid = config.ids.uids.elasticsearch;
203 description = "Elasticsearch daemon user";
204 home = cfg.dataDir;
205 group = "elasticsearch";
206 };
207 };
208 };
209}