1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8with lib;
9
10let
11 cfg = config.services.elasticsearch;
12
13 es7 = builtins.compareVersions cfg.package.version "7" >= 0;
14
15 esConfig = ''
16 network.host: ${cfg.listenAddress}
17 cluster.name: ${cfg.cluster_name}
18 ${lib.optionalString cfg.single_node "discovery.type: single-node"}
19 ${lib.optionalString (cfg.single_node && es7) "gateway.auto_import_dangling_indices: true"}
20
21 http.port: ${toString cfg.port}
22 transport.port: ${toString cfg.tcp_port}
23
24 ${cfg.extraConf}
25 '';
26
27 configDir = cfg.dataDir + "/config";
28
29 elasticsearchYml = pkgs.writeTextFile {
30 name = "elasticsearch.yml";
31 text = esConfig;
32 };
33
34 loggingConfigFilename = "log4j2.properties";
35 loggingConfigFile = pkgs.writeTextFile {
36 name = loggingConfigFilename;
37 text = cfg.logging;
38 };
39
40 esPlugins = pkgs.buildEnv {
41 name = "elasticsearch-plugins";
42 paths = cfg.plugins;
43 postBuild = "${pkgs.coreutils}/bin/mkdir -p $out/plugins";
44 };
45
46in
47{
48
49 ###### interface
50
51 options.services.elasticsearch = {
52 enable = mkOption {
53 description = "Whether to enable elasticsearch.";
54 default = false;
55 type = types.bool;
56 };
57
58 package = mkPackageOption pkgs "elasticsearch" { };
59
60 listenAddress = mkOption {
61 description = "Elasticsearch listen address.";
62 default = "127.0.0.1";
63 type = types.str;
64 };
65
66 port = mkOption {
67 description = "Elasticsearch port to listen for HTTP traffic.";
68 default = 9200;
69 type = types.port;
70 };
71
72 tcp_port = mkOption {
73 description = "Elasticsearch port for the node to node communication.";
74 default = 9300;
75 type = types.int;
76 };
77
78 cluster_name = mkOption {
79 description = "Elasticsearch name that identifies your cluster for auto-discovery.";
80 default = "elasticsearch";
81 type = types.str;
82 };
83
84 single_node = mkOption {
85 description = "Start a single-node cluster";
86 default = true;
87 type = types.bool;
88 };
89
90 extraConf = mkOption {
91 description = "Extra configuration for elasticsearch.";
92 default = "";
93 type = types.str;
94 example = ''
95 node.name: "elasticsearch"
96 node.master: true
97 node.data: false
98 '';
99 };
100
101 logging = mkOption {
102 description = "Elasticsearch logging configuration.";
103 default = ''
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 '';
115 type = types.str;
116 };
117
118 dataDir = mkOption {
119 type = types.path;
120 default = "/var/lib/elasticsearch";
121 description = ''
122 Data directory for elasticsearch.
123 '';
124 };
125
126 extraCmdLineOptions = mkOption {
127 description = "Extra command line options for the elasticsearch launcher.";
128 default = [ ];
129 type = types.listOf types.str;
130 };
131
132 extraJavaOptions = mkOption {
133 description = "Extra command line options for Java.";
134 default = [ ];
135 type = types.listOf types.str;
136 example = [ "-Djava.net.preferIPv4Stack=true" ];
137 };
138
139 plugins = mkOption {
140 description = "Extra elasticsearch plugins";
141 default = [ ];
142 type = types.listOf types.package;
143 example = lib.literalExpression "[ pkgs.elasticsearchPlugins.discovery-ec2 ]";
144 };
145
146 restartIfChanged = mkOption {
147 type = types.bool;
148 description = ''
149 Automatically restart the service on config change.
150 This can be set to false to defer restarts on a server or cluster.
151 Please consider the security implications of inadvertently running an older version,
152 and the possibility of unexpected behavior caused by inconsistent versions across a cluster when disabling this option.
153 '';
154 default = true;
155 };
156
157 };
158
159 ###### implementation
160
161 config = mkIf cfg.enable {
162 systemd.services.elasticsearch = {
163 description = "Elasticsearch Daemon";
164 wantedBy = [ "multi-user.target" ];
165 after = [ "network.target" ];
166 path = [ pkgs.inetutils ];
167 inherit (cfg) restartIfChanged;
168 environment = {
169 ES_HOME = cfg.dataDir;
170 ES_JAVA_OPTS = toString cfg.extraJavaOptions;
171 ES_PATH_CONF = configDir;
172 };
173 serviceConfig = {
174 ExecStart = "${cfg.package}/bin/elasticsearch ${toString cfg.extraCmdLineOptions}";
175 User = "elasticsearch";
176 PermissionsStartOnly = true;
177 LimitNOFILE = "1024000";
178 Restart = "always";
179 TimeoutStartSec = "infinity";
180 };
181 preStart = ''
182 ${optionalString (!config.boot.isContainer) ''
183 # Only set vm.max_map_count if lower than ES required minimum
184 # This avoids conflict if configured via boot.kernel.sysctl
185 if [ `${pkgs.procps}/bin/sysctl -n vm.max_map_count` -lt 262144 ]; then
186 ${pkgs.procps}/bin/sysctl -w vm.max_map_count=262144
187 fi
188 ''}
189
190 mkdir -m 0700 -p ${cfg.dataDir}
191
192 # Install plugins
193 ln -sfT ${esPlugins}/plugins ${cfg.dataDir}/plugins
194 ln -sfT ${cfg.package}/lib ${cfg.dataDir}/lib
195 ln -sfT ${cfg.package}/modules ${cfg.dataDir}/modules
196
197 # elasticsearch needs to create the elasticsearch.keystore in the config directory
198 # so this directory needs to be writable.
199 mkdir -m 0700 -p ${configDir}
200
201 # Note that we copy config files from the nix store instead of symbolically linking them
202 # because otherwise X-Pack Security will raise the following exception:
203 # java.security.AccessControlException:
204 # access denied ("java.io.FilePermission" "/var/lib/elasticsearch/config/elasticsearch.yml" "read")
205
206 cp ${elasticsearchYml} ${configDir}/elasticsearch.yml
207 # Make sure the logging configuration for old elasticsearch versions is removed:
208 rm -f "${configDir}/logging.yml"
209 cp ${loggingConfigFile} ${configDir}/${loggingConfigFilename}
210 mkdir -p ${configDir}/scripts
211 cp ${cfg.package}/config/jvm.options ${configDir}/jvm.options
212 # redirect jvm logs to the data directory
213 mkdir -m 0700 -p ${cfg.dataDir}/logs
214 ${pkgs.sd}/bin/sd 'logs/gc.log' '${cfg.dataDir}/logs/gc.log' ${configDir}/jvm.options \
215
216 if [ "$(id -u)" = 0 ]; then chown -R elasticsearch:elasticsearch ${cfg.dataDir}; fi
217 '';
218 postStart = ''
219 # Make sure elasticsearch is up and running before dependents
220 # are started
221 while ! ${pkgs.curl}/bin/curl -sS -f http://${cfg.listenAddress}:${toString cfg.port} 2>/dev/null; do
222 sleep 1
223 done
224 '';
225 };
226
227 environment.systemPackages = [ cfg.package ];
228
229 users = {
230 groups.elasticsearch.gid = config.ids.gids.elasticsearch;
231 users.elasticsearch = {
232 uid = config.ids.uids.elasticsearch;
233 description = "Elasticsearch daemon user";
234 home = cfg.dataDir;
235 group = "elasticsearch";
236 };
237 };
238 };
239}