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