1{ config, pkgs, lib, ... }:
2
3with lib;
4
5let
6 cfg = config.services.n8n;
7 format = pkgs.formats.json {};
8 configFile = format.generate "n8n.json" cfg.settings;
9in
10{
11 options.services.n8n = {
12 enable = mkEnableOption (lib.mdDoc "n8n server");
13
14 openFirewall = mkOption {
15 type = types.bool;
16 default = false;
17 description = lib.mdDoc "Open ports in the firewall for the n8n web interface.";
18 };
19
20 settings = mkOption {
21 type = format.type;
22 default = {};
23 description = lib.mdDoc ''
24 Configuration for n8n, see <https://docs.n8n.io/hosting/environment-variables/configuration-methods/>
25 for supported values.
26 '';
27 };
28
29 };
30
31 config = mkIf cfg.enable {
32 services.n8n.settings = {
33 # We use this to open the firewall, so we need to know about the default at eval time
34 port = lib.mkDefault 5678;
35 };
36
37 systemd.services.n8n = {
38 description = "N8N service";
39 after = [ "network.target" ];
40 wantedBy = [ "multi-user.target" ];
41 environment = {
42 # This folder must be writeable as the application is storing
43 # its data in it, so the StateDirectory is a good choice
44 N8N_USER_FOLDER = "/var/lib/n8n";
45 HOME = "/var/lib/n8n";
46 N8N_CONFIG_FILES = "${configFile}";
47
48 # Don't phone home
49 N8N_DIAGNOSTICS_ENABLED = "false";
50 N8N_VERSION_NOTIFICATIONS_ENABLED = "false";
51 };
52 serviceConfig = {
53 Type = "simple";
54 ExecStart = "${pkgs.n8n}/bin/n8n";
55 Restart = "on-failure";
56 StateDirectory = "n8n";
57
58 # Basic Hardening
59 NoNewPrivileges = "yes";
60 PrivateTmp = "yes";
61 PrivateDevices = "yes";
62 DevicePolicy = "closed";
63 DynamicUser = "true";
64 ProtectSystem = "strict";
65 ProtectHome = "read-only";
66 ProtectControlGroups = "yes";
67 ProtectKernelModules = "yes";
68 ProtectKernelTunables = "yes";
69 RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_NETLINK";
70 RestrictNamespaces = "yes";
71 RestrictRealtime = "yes";
72 RestrictSUIDSGID = "yes";
73 MemoryDenyWriteExecute = "no"; # v8 JIT requires memory segments to be Writable-Executable.
74 LockPersonality = "yes";
75 };
76 };
77
78 networking.firewall = mkIf cfg.openFirewall {
79 allowedTCPPorts = [ cfg.settings.port ];
80 };
81 };
82}