1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7let
8 cfg = config.services.opensearch;
9
10 settingsFormat = pkgs.formats.yaml { };
11
12 configDir = cfg.dataDir + "/config";
13
14 usingDefaultDataDir = cfg.dataDir == "/var/lib/opensearch";
15 usingDefaultUserAndGroup = cfg.user == "opensearch" && cfg.group == "opensearch";
16
17 opensearchYml = settingsFormat.generate "opensearch.yml" cfg.settings;
18
19 loggingConfigFilename = "log4j2.properties";
20 loggingConfigFile = pkgs.writeTextFile {
21 name = loggingConfigFilename;
22 text = cfg.logging;
23 };
24in
25{
26
27 options.services.opensearch = {
28 enable = lib.mkEnableOption "OpenSearch";
29
30 package = lib.mkPackageOption pkgs "OpenSearch" {
31 default = [ "opensearch" ];
32 };
33
34 settings = lib.mkOption {
35 type = lib.types.submodule {
36 freeformType = settingsFormat.type;
37
38 options."network.host" = lib.mkOption {
39 type = lib.types.str;
40 default = "127.0.0.1";
41 description = ''
42 Which port this service should listen on.
43 '';
44 };
45
46 options."cluster.name" = lib.mkOption {
47 type = lib.types.str;
48 default = "opensearch";
49 description = ''
50 The name of the cluster.
51 '';
52 };
53
54 options."discovery.type" = lib.mkOption {
55 type = lib.types.str;
56 default = "single-node";
57 description = ''
58 The type of discovery to use.
59 '';
60 };
61
62 options."http.port" = lib.mkOption {
63 type = lib.types.port;
64 default = 9200;
65 description = ''
66 The port to listen on for HTTP traffic.
67 '';
68 };
69
70 options."transport.port" = lib.mkOption {
71 type = lib.types.port;
72 default = 9300;
73 description = ''
74 The port to listen on for transport traffic.
75 '';
76 };
77
78 options."plugins.security.disabled" = lib.mkOption {
79 type = lib.types.bool;
80 default = true;
81 description = ''
82 Whether to enable the security plugin,
83 `plugins.security.ssl.transport.keystore_filepath` or
84 `plugins.security.ssl.transport.server.pemcert_filepath` and
85 `plugins.security.ssl.transport.client.pemcert_filepath`
86 must be set for this plugin to be enabled.
87 '';
88 };
89 };
90
91 default = { };
92
93 description = ''
94 OpenSearch configuration.
95 '';
96 };
97
98 logging = lib.mkOption {
99 description = "opensearch logging configuration.";
100
101 default = ''
102 logger.action.name = org.opensearch.action
103 logger.action.level = info
104
105 appender.console.type = Console
106 appender.console.name = console
107 appender.console.layout.type = PatternLayout
108 appender.console.layout.pattern = [%d{ISO8601}][%-5p][%-25c{1.}] %marker%m%n
109
110 rootLogger.level = info
111 rootLogger.appenderRef.console.ref = console
112 '';
113 type = lib.types.str;
114 };
115
116 dataDir = lib.mkOption {
117 type = lib.types.path;
118 default = "/var/lib/opensearch";
119 apply = lib.converge (lib.removeSuffix "/");
120 description = ''
121 Data directory for OpenSearch. If you change this, you need to
122 manually create the directory. You also need to create the
123 `opensearch` user and group, or change
124 [](#opt-services.opensearch.user) and
125 [](#opt-services.opensearch.group) to existing ones with
126 access to the directory.
127 '';
128 };
129
130 user = lib.mkOption {
131 type = lib.types.str;
132 default = "opensearch";
133 description = ''
134 The user OpenSearch runs as. Should be left at default unless
135 you have very specific needs.
136 '';
137 };
138
139 group = lib.mkOption {
140 type = lib.types.str;
141 default = "opensearch";
142 description = ''
143 The group OpenSearch runs as. Should be left at default unless
144 you have very specific needs.
145 '';
146 };
147
148 extraCmdLineOptions = lib.mkOption {
149 description = "Extra command line options for the OpenSearch launcher.";
150 default = [ ];
151 type = lib.types.listOf lib.types.str;
152 };
153
154 extraJavaOptions = lib.mkOption {
155 description = "Extra command line options for Java.";
156 default = [ ];
157 type = lib.types.listOf lib.types.str;
158 example = [ "-Djava.net.preferIPv4Stack=true" ];
159 };
160
161 restartIfChanged = lib.mkOption {
162 type = lib.types.bool;
163 description = ''
164 Automatically restart the service on config change.
165 This can be set to false to defer restarts on a server or cluster.
166 Please consider the security implications of inadvertently running an older version,
167 and the possibility of unexpected behavior caused by inconsistent versions across a cluster when disabling this option.
168 '';
169 default = true;
170 };
171 };
172
173 config = lib.mkIf cfg.enable {
174 systemd.services.opensearch = {
175 description = "OpenSearch Daemon";
176 wantedBy = [ "multi-user.target" ];
177 after = [ "network.target" ];
178 path = [ pkgs.inetutils ];
179 inherit (cfg) restartIfChanged;
180 environment = {
181 OPENSEARCH_HOME = cfg.dataDir;
182 OPENSEARCH_JAVA_OPTS = toString cfg.extraJavaOptions;
183 OPENSEARCH_PATH_CONF = configDir;
184 };
185 serviceConfig = {
186 ExecStartPre =
187 let
188 startPreFullPrivileges = ''
189 set -o errexit -o pipefail -o nounset -o errtrace
190 shopt -s inherit_errexit
191 ''
192 + (lib.optionalString (!config.boot.isContainer) ''
193 # Only set vm.max_map_count if lower than ES required minimum
194 # This avoids conflict if configured via boot.kernel.sysctl
195 if [ $(${pkgs.procps}/bin/sysctl -n vm.max_map_count) -lt 262144 ]; then
196 ${pkgs.procps}/bin/sysctl -w vm.max_map_count=262144
197 fi
198 '');
199 startPreUnprivileged = ''
200 set -o errexit -o pipefail -o nounset -o errtrace
201 shopt -s inherit_errexit
202
203 # Install plugins
204
205 # remove plugins directory if it is empty.
206 if [[ -d ${cfg.dataDir}/plugins && -z "$(ls -A ${cfg.dataDir}/plugins)" ]]; then
207 rm -r "${cfg.dataDir}/plugins"
208 fi
209
210 ln -sfT "${cfg.package}/plugins" "${cfg.dataDir}/plugins"
211 ln -sfT ${cfg.package}/lib ${cfg.dataDir}/lib
212 ln -sfT ${cfg.package}/modules ${cfg.dataDir}/modules
213
214 # opensearch needs to create the opensearch.keystore in the config directory
215 # so this directory needs to be writable.
216 mkdir -p ${configDir}
217 chmod 0700 ${configDir}
218
219 # Note that we copy config files from the nix store instead of symbolically linking them
220 # because otherwise X-Pack Security will raise the following exception:
221 # java.security.AccessControlException:
222 # access denied ("java.io.FilePermission" "/var/lib/opensearch/config/opensearch.yml" "read")
223
224 rm -f ${configDir}/opensearch.yml
225 cp ${opensearchYml} ${configDir}/opensearch.yml
226
227 # Make sure the logging configuration for old OpenSearch versions is removed:
228 rm -f "${configDir}/logging.yml"
229 rm -f ${configDir}/${loggingConfigFilename}
230 cp ${loggingConfigFile} ${configDir}/${loggingConfigFilename}
231 mkdir -p ${configDir}/scripts
232
233 rm -f ${configDir}/jvm.options
234 cp ${cfg.package}/config/jvm.options ${configDir}/jvm.options
235
236 # redirect jvm logs to the data directory
237 mkdir -p ${cfg.dataDir}/logs
238 chmod 0700 ${cfg.dataDir}/logs
239 sed -e '#logs/gc.log#${cfg.dataDir}/logs/gc.log#' -i ${configDir}/jvm.options
240 '';
241 in
242 [
243 "+${pkgs.writeShellScript "opensearch-start-pre-full-privileges" startPreFullPrivileges}"
244 "${pkgs.writeShellScript "opensearch-start-pre-unprivileged" startPreUnprivileged}"
245 ];
246 ExecStartPost = pkgs.writeShellScript "opensearch-start-post" ''
247 set -o errexit -o pipefail -o nounset -o errtrace
248 shopt -s inherit_errexit
249
250 # Make sure opensearch is up and running before dependents
251 # are started
252 while ! ${pkgs.curl}/bin/curl -sS -f http://${cfg.settings."network.host"}:${
253 toString cfg.settings."http.port"
254 } 2>/dev/null; do
255 sleep 1
256 done
257 '';
258 ExecStart = "${cfg.package}/bin/opensearch ${toString cfg.extraCmdLineOptions}";
259 User = cfg.user;
260 Group = cfg.group;
261 LimitNOFILE = "1024000";
262 Restart = "always";
263 TimeoutStartSec = "infinity";
264 DynamicUser = usingDefaultUserAndGroup && usingDefaultDataDir;
265 }
266 // (lib.optionalAttrs (usingDefaultDataDir) {
267 StateDirectory = "opensearch";
268 StateDirectoryMode = "0700";
269 });
270 };
271
272 environment.systemPackages = [ cfg.package ];
273 };
274}