1{
2 lib,
3 pkgs,
4 config,
5 options,
6 ...
7}:
8
9let
10 cfg = config.services.nifi;
11 opt = options.services.nifi;
12
13 env = {
14 NIFI_OVERRIDE_NIFIENV = "true";
15 NIFI_HOME = "/var/lib/nifi";
16 NIFI_PID_DIR = "/run/nifi";
17 NIFI_LOG_DIR = "/var/log/nifi";
18 };
19
20 envFile = pkgs.writeText "nifi.env" (
21 lib.concatMapStrings (s: s + "\n") (
22 (lib.concatLists (
23 lib.mapAttrsToList (name: value: lib.optional (value != null) ''${name}="${toString value}"'') env
24 ))
25 )
26 );
27
28 nifiEnv = pkgs.writeShellScriptBin "nifi-env" ''
29 set -a
30 source "${envFile}"
31 eval -- "\$@"
32 '';
33
34in
35{
36 options = {
37 services.nifi = {
38 enable = lib.mkEnableOption "Apache NiFi";
39
40 package = lib.mkOption {
41 type = lib.types.package;
42 default = pkgs.nifi;
43 defaultText = lib.literalExpression "pkgs.nifi";
44 description = "Apache NiFi package to use.";
45 };
46
47 user = lib.mkOption {
48 type = lib.types.str;
49 default = "nifi";
50 description = "User account where Apache NiFi runs.";
51 };
52
53 group = lib.mkOption {
54 type = lib.types.str;
55 default = "nifi";
56 description = "Group account where Apache NiFi runs.";
57 };
58
59 enableHTTPS = lib.mkOption {
60 type = lib.types.bool;
61 default = true;
62 description = "Enable HTTPS protocol. Don`t use in production.";
63 };
64
65 listenHost = lib.mkOption {
66 type = lib.types.str;
67 default = if cfg.enableHTTPS then "0.0.0.0" else "127.0.0.1";
68 defaultText = lib.literalExpression ''
69 if config.${opt.enableHTTPS}
70 then "0.0.0.0"
71 else "127.0.0.1"
72 '';
73 description = "Bind to an ip for Apache NiFi web-ui.";
74 };
75
76 listenPort = lib.mkOption {
77 type = lib.types.int;
78 default = if cfg.enableHTTPS then 8443 else 8080;
79 defaultText = lib.literalExpression ''
80 if config.${opt.enableHTTPS}
81 then "8443"
82 else "8000"
83 '';
84 description = "Bind to a port for Apache NiFi web-ui.";
85 };
86
87 proxyHost = lib.mkOption {
88 type = lib.types.nullOr lib.types.str;
89 default = if cfg.enableHTTPS then "0.0.0.0" else null;
90 defaultText = lib.literalExpression ''
91 if config.${opt.enableHTTPS}
92 then "0.0.0.0"
93 else null
94 '';
95 description = "Allow requests from a specific host.";
96 };
97
98 proxyPort = lib.mkOption {
99 type = lib.types.nullOr lib.types.int;
100 default = if cfg.enableHTTPS then 8443 else null;
101 defaultText = lib.literalExpression ''
102 if config.${opt.enableHTTPS}
103 then "8443"
104 else null
105 '';
106 description = "Allow requests from a specific port.";
107 };
108
109 initUser = lib.mkOption {
110 type = lib.types.nullOr lib.types.str;
111 default = null;
112 description = "Initial user account for Apache NiFi. Username must be at least 4 characters.";
113 };
114
115 initPasswordFile = lib.mkOption {
116 type = lib.types.nullOr lib.types.path;
117 default = null;
118 example = "/run/keys/nifi/password-nifi";
119 description = "nitial password for Apache NiFi. Password must be at least 12 characters.";
120 };
121
122 initJavaHeapSize = lib.mkOption {
123 type = lib.types.nullOr lib.types.int;
124 default = null;
125 example = 1024;
126 description = "Set the initial heap size for the JVM in MB.";
127 };
128
129 maxJavaHeapSize = lib.mkOption {
130 type = lib.types.nullOr lib.types.int;
131 default = null;
132 example = 2048;
133 description = "Set the initial heap size for the JVM in MB.";
134 };
135 };
136 };
137
138 config = lib.mkIf cfg.enable {
139 assertions = [
140 {
141 assertion = cfg.initUser != null || cfg.initPasswordFile == null;
142 message = ''
143 <option>services.nifi.initUser</option> needs to be set if <option>services.nifi.initPasswordFile</option> enabled.
144 '';
145 }
146 {
147 assertion = cfg.initUser == null || cfg.initPasswordFile != null;
148 message = ''
149 <option>services.nifi.initPasswordFile</option> needs to be set if <option>services.nifi.initUser</option> enabled.
150 '';
151 }
152 {
153 assertion = cfg.proxyHost == null || cfg.proxyPort != null;
154 message = ''
155 <option>services.nifi.proxyPort</option> needs to be set if <option>services.nifi.proxyHost</option> value specified.
156 '';
157 }
158 {
159 assertion = cfg.proxyHost != null || cfg.proxyPort == null;
160 message = ''
161 <option>services.nifi.proxyHost</option> needs to be set if <option>services.nifi.proxyPort</option> value specified.
162 '';
163 }
164 {
165 assertion = cfg.initJavaHeapSize == null || cfg.maxJavaHeapSize != null;
166 message = ''
167 <option>services.nifi.maxJavaHeapSize</option> needs to be set if <option>services.nifi.initJavaHeapSize</option> value specified.
168 '';
169 }
170 {
171 assertion = cfg.initJavaHeapSize != null || cfg.maxJavaHeapSize == null;
172 message = ''
173 <option>services.nifi.initJavaHeapSize</option> needs to be set if <option>services.nifi.maxJavaHeapSize</option> value specified.
174 '';
175 }
176 ];
177
178 warnings = lib.optional (cfg.enableHTTPS == false) ''
179 Please do not disable HTTPS mode in production. In this mode, access to the nifi is opened without authentication.
180 '';
181
182 systemd.tmpfiles.settings."10-nifi" = {
183 "/var/lib/nifi/conf".d = {
184 inherit (cfg) user group;
185 mode = "0750";
186 };
187 "/var/lib/nifi/lib"."L+" = {
188 argument = "${cfg.package}/lib";
189 };
190 };
191
192 systemd.services.nifi = {
193 description = "Apache NiFi";
194 after = [ "network.target" ];
195 wantedBy = [ "multi-user.target" ];
196
197 environment = env;
198 path = [ pkgs.gawk ];
199
200 serviceConfig = {
201 Type = "forking";
202 PIDFile = "/run/nifi/nifi.pid";
203 ExecStartPre = pkgs.writeScript "nifi-pre-start.sh" ''
204 #!/bin/sh
205 umask 077
206 test -f '/var/lib/nifi/conf/authorizers.xml' || (cp '${cfg.package}/share/nifi/conf/authorizers.xml' '/var/lib/nifi/conf/' && chmod 0640 '/var/lib/nifi/conf/authorizers.xml')
207 test -f '/var/lib/nifi/conf/bootstrap.conf' || (cp '${cfg.package}/share/nifi/conf/bootstrap.conf' '/var/lib/nifi/conf/' && chmod 0640 '/var/lib/nifi/conf/bootstrap.conf')
208 test -f '/var/lib/nifi/conf/bootstrap-hashicorp-vault.conf' || (cp '${cfg.package}/share/nifi/conf/bootstrap-hashicorp-vault.conf' '/var/lib/nifi/conf/' && chmod 0640 '/var/lib/nifi/conf/bootstrap-hashicorp-vault.conf')
209 test -f '/var/lib/nifi/conf/bootstrap-notification-services.xml' || (cp '${cfg.package}/share/nifi/conf/bootstrap-notification-services.xml' '/var/lib/nifi/conf/' && chmod 0640 '/var/lib/nifi/conf/bootstrap-notification-services.xml')
210 test -f '/var/lib/nifi/conf/logback.xml' || (cp '${cfg.package}/share/nifi/conf/logback.xml' '/var/lib/nifi/conf/' && chmod 0640 '/var/lib/nifi/conf/logback.xml')
211 test -f '/var/lib/nifi/conf/login-identity-providers.xml' || (cp '${cfg.package}/share/nifi/conf/login-identity-providers.xml' '/var/lib/nifi/conf/' && chmod 0640 '/var/lib/nifi/conf/login-identity-providers.xml')
212 test -f '/var/lib/nifi/conf/nifi.properties' || (cp '${cfg.package}/share/nifi/conf/nifi.properties' '/var/lib/nifi/conf/' && chmod 0640 '/var/lib/nifi/conf/nifi.properties')
213 test -f '/var/lib/nifi/conf/stateless-logback.xml' || (cp '${cfg.package}/share/nifi/conf/stateless-logback.xml' '/var/lib/nifi/conf/' && chmod 0640 '/var/lib/nifi/conf/stateless-logback.xml')
214 test -f '/var/lib/nifi/conf/stateless.properties' || (cp '${cfg.package}/share/nifi/conf/stateless.properties' '/var/lib/nifi/conf/' && chmod 0640 '/var/lib/nifi/conf/stateless.properties')
215 test -f '/var/lib/nifi/conf/state-management.xml' || (cp '${cfg.package}/share/nifi/conf/state-management.xml' '/var/lib/nifi/conf/' && chmod 0640 '/var/lib/nifi/conf/state-management.xml')
216 test -f '/var/lib/nifi/conf/zookeeper.properties' || (cp '${cfg.package}/share/nifi/conf/zookeeper.properties' '/var/lib/nifi/conf/' && chmod 0640 '/var/lib/nifi/conf/zookeeper.properties')
217 test -d '/var/lib/nifi/docs/html' || (mkdir -p /var/lib/nifi/docs && cp -r '${cfg.package}/share/nifi/docs/html' '/var/lib/nifi/docs/html')
218 ${lib.optionalString ((cfg.initUser != null) && (cfg.initPasswordFile != null)) ''
219 awk -F'[<|>]' '/property name="Username"/ {if ($3!="") f=1} END{exit !f}' /var/lib/nifi/conf/login-identity-providers.xml || ${cfg.package}/bin/nifi.sh set-single-user-credentials ${cfg.initUser} $(cat ${cfg.initPasswordFile})
220 ''}
221 ${lib.optionalString (cfg.enableHTTPS == false) ''
222 sed -i /var/lib/nifi/conf/nifi.properties \
223 -e 's|nifi.remote.input.secure=.*|nifi.remote.input.secure=false|g' \
224 -e 's|nifi.web.http.host=.*|nifi.web.http.host=${cfg.listenHost}|g' \
225 -e 's|nifi.web.http.port=.*|nifi.web.http.port=${(toString cfg.listenPort)}|g' \
226 -e 's|nifi.web.https.host=.*|nifi.web.https.host=|g' \
227 -e 's|nifi.web.https.port=.*|nifi.web.https.port=|g' \
228 -e 's|nifi.security.keystore=.*|nifi.security.keystore=|g' \
229 -e 's|nifi.security.keystoreType=.*|nifi.security.keystoreType=|g' \
230 -e 's|nifi.security.truststore=.*|nifi.security.truststore=|g' \
231 -e 's|nifi.security.truststoreType=.*|nifi.security.truststoreType=|g' \
232 -e '/nifi.security.keystorePasswd/s|^|#|' \
233 -e '/nifi.security.keyPasswd/s|^|#|' \
234 -e '/nifi.security.truststorePasswd/s|^|#|'
235 ''}
236 ${lib.optionalString (cfg.enableHTTPS == true) ''
237 sed -i /var/lib/nifi/conf/nifi.properties \
238 -e 's|nifi.remote.input.secure=.*|nifi.remote.input.secure=true|g' \
239 -e 's|nifi.web.http.host=.*|nifi.web.http.host=|g' \
240 -e 's|nifi.web.http.port=.*|nifi.web.http.port=|g' \
241 -e 's|nifi.web.https.host=.*|nifi.web.https.host=${cfg.listenHost}|g' \
242 -e 's|nifi.web.https.port=.*|nifi.web.https.port=${(toString cfg.listenPort)}|g' \
243 -e 's|nifi.security.keystore=.*|nifi.security.keystore=./conf/keystore.p12|g' \
244 -e 's|nifi.security.keystoreType=.*|nifi.security.keystoreType=PKCS12|g' \
245 -e 's|nifi.security.truststore=.*|nifi.security.truststore=./conf/truststore.p12|g' \
246 -e 's|nifi.security.truststoreType=.*|nifi.security.truststoreType=PKCS12|g' \
247 -e '/nifi.security.keystorePasswd/s|^#\+||' \
248 -e '/nifi.security.keyPasswd/s|^#\+||' \
249 -e '/nifi.security.truststorePasswd/s|^#\+||'
250 ''}
251 ${lib.optionalString
252 ((cfg.enableHTTPS == true) && (cfg.proxyHost != null) && (cfg.proxyPort != null))
253 ''
254 sed -i /var/lib/nifi/conf/nifi.properties \
255 -e 's|nifi.web.proxy.host=.*|nifi.web.proxy.host=${cfg.proxyHost}:${(toString cfg.proxyPort)}|g'
256 ''
257 }
258 ${lib.optionalString
259 ((cfg.enableHTTPS == false) || (cfg.proxyHost == null) && (cfg.proxyPort == null))
260 ''
261 sed -i /var/lib/nifi/conf/nifi.properties \
262 -e 's|nifi.web.proxy.host=.*|nifi.web.proxy.host=|g'
263 ''
264 }
265 ${lib.optionalString ((cfg.initJavaHeapSize != null) && (cfg.maxJavaHeapSize != null)) ''
266 sed -i /var/lib/nifi/conf/bootstrap.conf \
267 -e 's|java.arg.2=.*|java.arg.2=-Xms${(toString cfg.initJavaHeapSize)}m|g' \
268 -e 's|java.arg.3=.*|java.arg.3=-Xmx${(toString cfg.maxJavaHeapSize)}m|g'
269 ''}
270 ${lib.optionalString ((cfg.initJavaHeapSize == null) && (cfg.maxJavaHeapSize == null)) ''
271 sed -i /var/lib/nifi/conf/bootstrap.conf \
272 -e 's|java.arg.2=.*|java.arg.2=-Xms512m|g' \
273 -e 's|java.arg.3=.*|java.arg.3=-Xmx512m|g'
274 ''}
275 '';
276 ExecStart = "${cfg.package}/bin/nifi.sh start";
277 ExecStop = "${cfg.package}/bin/nifi.sh stop";
278 # User and group
279 User = cfg.user;
280 Group = cfg.group;
281 # Runtime directory and mode
282 RuntimeDirectory = "nifi";
283 RuntimeDirectoryMode = "0750";
284 # State directory and mode
285 StateDirectory = "nifi";
286 StateDirectoryMode = "0750";
287 # Logs directory and mode
288 LogsDirectory = "nifi";
289 LogsDirectoryMode = "0750";
290 # Proc filesystem
291 ProcSubset = "pid";
292 ProtectProc = "invisible";
293 # Access write directories
294 ReadWritePaths = [ cfg.initPasswordFile ];
295 UMask = "0027";
296 # Capabilities
297 CapabilityBoundingSet = "";
298 # Security
299 NoNewPrivileges = true;
300 # Sandboxing
301 ProtectSystem = "strict";
302 ProtectHome = true;
303 PrivateTmp = true;
304 PrivateDevices = true;
305 PrivateIPC = true;
306 PrivateUsers = true;
307 ProtectHostname = true;
308 ProtectClock = true;
309 ProtectKernelTunables = true;
310 ProtectKernelModules = true;
311 ProtectKernelLogs = true;
312 ProtectControlGroups = true;
313 RestrictAddressFamilies = [ "AF_INET AF_INET6" ];
314 RestrictNamespaces = true;
315 LockPersonality = true;
316 MemoryDenyWriteExecute = false;
317 RestrictRealtime = true;
318 RestrictSUIDSGID = true;
319 RemoveIPC = true;
320 PrivateMounts = true;
321 # System Call Filtering
322 SystemCallArchitectures = "native";
323 SystemCallFilter = [
324 "~@cpu-emulation @debug @keyring @memlock @mount @obsolete @resources @privileged @setuid"
325 "@chown"
326 ];
327 };
328 };
329
330 users.users = lib.mkMerge [
331 (lib.mkIf (cfg.user == "nifi") {
332 nifi = {
333 group = cfg.group;
334 isSystemUser = true;
335 home = cfg.package;
336 };
337 })
338 (lib.attrsets.setAttrByPath [ cfg.user "packages" ] [ cfg.package nifiEnv ])
339 ];
340
341 users.groups = lib.optionalAttrs (cfg.group == "nifi") {
342 nifi = { };
343 };
344 };
345}