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