1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8with lib;
9
10let
11 cfg = config.services.node-red;
12 defaultUser = "node-red";
13in
14{
15 options.services.node-red = {
16 enable = mkEnableOption "the Node-RED service";
17
18 package = mkPackageOption pkgs [ "node-red" ] { };
19
20 openFirewall = mkOption {
21 type = types.bool;
22 default = false;
23 description = ''
24 Open ports in the firewall for the server.
25 '';
26 };
27
28 withNpmAndGcc = mkOption {
29 type = types.bool;
30 default = false;
31 description = ''
32 Give Node-RED access to NPM and GCC at runtime, so 'Nodes' can be
33 downloaded and managed imperatively via the 'Palette Manager'.
34 '';
35 };
36
37 configFile = mkOption {
38 type = types.path;
39 default = "${cfg.package}/lib/node_modules/node-red/packages/node_modules/node-red/settings.js";
40 defaultText = literalExpression ''"''${package}/lib/node_modules/node-red/packages/node_modules/node-red/settings.js"'';
41 description = ''
42 Path to the JavaScript configuration file.
43 See <https://github.com/node-red/node-red/blob/master/packages/node_modules/node-red/settings.js>
44 for a configuration example.
45 '';
46 };
47
48 port = mkOption {
49 type = types.port;
50 default = 1880;
51 description = "Listening port.";
52 };
53
54 user = mkOption {
55 type = types.str;
56 default = defaultUser;
57 description = ''
58 User under which Node-RED runs.If left as the default value this user
59 will automatically be created on system activation, otherwise the
60 sysadmin is responsible for ensuring the user exists.
61 '';
62 };
63
64 group = mkOption {
65 type = types.str;
66 default = defaultUser;
67 description = ''
68 Group under which Node-RED runs.If left as the default value this group
69 will automatically be created on system activation, otherwise the
70 sysadmin is responsible for ensuring the group exists.
71 '';
72 };
73
74 userDir = mkOption {
75 type = types.path;
76 default = "/var/lib/node-red";
77 description = ''
78 The directory to store all user data, such as flow and credential files and all library data. If left
79 as the default value this directory will automatically be created before the node-red service starts,
80 otherwise the sysadmin is responsible for ensuring the directory exists with appropriate ownership
81 and permissions.
82 '';
83 };
84
85 safe = mkOption {
86 type = types.bool;
87 default = false;
88 description = "Whether to launch Node-RED in --safe mode.";
89 };
90
91 define = mkOption {
92 type = types.attrs;
93 default = { };
94 description = "List of settings.js overrides to pass via -D to Node-RED.";
95 example = literalExpression ''
96 {
97 "logging.console.level" = "trace";
98 }
99 '';
100 };
101 };
102
103 config = mkIf cfg.enable {
104 users.users = optionalAttrs (cfg.user == defaultUser) {
105 ${defaultUser} = {
106 isSystemUser = true;
107 group = defaultUser;
108 };
109 };
110
111 users.groups = optionalAttrs (cfg.group == defaultUser) {
112 ${defaultUser} = { };
113 };
114
115 networking.firewall = mkIf cfg.openFirewall {
116 allowedTCPPorts = [ cfg.port ];
117 };
118
119 systemd.services.node-red = {
120 description = "Node-RED Service";
121 wantedBy = [ "multi-user.target" ];
122 after = [ "networking.target" ];
123 environment = {
124 HOME = cfg.userDir;
125 };
126 path = lib.optionals cfg.withNpmAndGcc [
127 pkgs.nodejs
128 pkgs.gcc
129 ];
130 serviceConfig = mkMerge [
131 {
132 User = cfg.user;
133 Group = cfg.group;
134 ExecStart = "${cfg.package}/bin/node-red ${pkgs.lib.optionalString cfg.safe "--safe"} --settings ${cfg.configFile} --port ${toString cfg.port} --userDir ${cfg.userDir} ${
135 concatStringsSep " " (mapAttrsToList (name: value: "-D ${name}=${value}") cfg.define)
136 }";
137 PrivateTmp = true;
138 Restart = "always";
139 WorkingDirectory = cfg.userDir;
140 }
141 (mkIf (cfg.userDir == "/var/lib/node-red") { StateDirectory = "node-red"; })
142 ];
143 };
144 };
145}