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