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